package com.waspring.wasservice.net.busii.loc;

import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.Properties;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.aiyc.server.standalone.core.Fingerprint;
import com.aiyc.server.standalone.core.Location;
import com.aiyc.server.standalone.core.Measurement;
import com.aiyc.server.standalone.core.measure.WiFiReading;
import com.aiyc.server.standalone.db.HomeFactory;
import com.aiyc.server.standalone.db.homes.FingerprintHome;
import com.aiyc.server.standalone.db.homes.MeasurementHome;
import com.aiyc.server.standalone.locator.ILocator;
import com.aiyc.server.standalone.locator.MeasurementComparator;
import com.aiyc.server.standalone.util.Log;

public class MapLocator implements ILocator {

	MeasurementHome measurementHome = null;
	FingerprintHome fingerprintHome = null;
	Logger log;
	Level loglevel = Level.FINEST;

	private static double ID_POS_CONTRIBUTION = 1;
	private static double ID_NEG_CONTRIBUTION = -0.4;

	public static double SIGNAL_CONTRIBUTION = 1;
	public static double SIGNAL_PENALTY_THRESHOLD = 10;
	public static double SIGNAL_GRAPH_LEVELING = 0.2;

	/* accuracy level */
	public static final int LOCATION_KNOWN = 10;
	public static final int LOCATION_UNKNOWN = 0;
	public static int LOCATION_THRESHOLD = 2;

	public static boolean debug = false;

	private String mapId;

	public MapLocator(String mapId) {
		this.mapId = mapId;
		measurementHome = HomeFactory.getMeasurementHome();
		fingerprintHome = HomeFactory.getFingerprintHome();
		log = Log.getLogger();

		loadParameters();
	}

	public void loadParameters() {
		Properties p = new Properties();
		File f = new File("redpinlocator.properties");
		if (f.exists()) {
			try {
				FileInputStream reader = new FileInputStream(f);
				p.load(reader);

				debug = Boolean.valueOf(
						p.getProperty("debug", Boolean.valueOf(debug)
								.toString())).booleanValue();

				LOCATION_THRESHOLD = Integer.parseInt(p.getProperty(
						"LOCATION_THRESHOLD", new Integer(LOCATION_THRESHOLD)
								.toString()));
				ID_POS_CONTRIBUTION = Double.parseDouble(p.getProperty(
						"ID_POS_CONTRIBUTION", new Double(ID_POS_CONTRIBUTION)
								.toString()));
				ID_NEG_CONTRIBUTION = Double.parseDouble(p.getProperty(
						"ID_NEG_CONTRIBUTION", new Double(ID_NEG_CONTRIBUTION)
								.toString()));
				SIGNAL_CONTRIBUTION = Double.parseDouble(p.getProperty(
						"SIGNAL_CONTRIBUTION", new Double(SIGNAL_CONTRIBUTION)
								.toString()));
				SIGNAL_PENALTY_THRESHOLD = Double.parseDouble(p.getProperty(
						"SIGNAL_PENALTY_THRESHOLD", new Double(
								SIGNAL_PENALTY_THRESHOLD).toString()));
				SIGNAL_GRAPH_LEVELING = Double.parseDouble(p.getProperty(
						"SIGNAL_GRAPH_LEVELING", new Double(
								SIGNAL_GRAPH_LEVELING).toString()));

			} catch (Exception e) {
				Log.getLogger().log(
						Level.WARNING,
						"RedpinLocator Config initialization failed: "
								+ e.getMessage(), e);

			}
		}
	}

	public Location locate(Measurement currentMeasurement) {

		if (debug) {
			loadParameters();
		}

		Location loc = null;

		// get all measurement in database
		List<Measurement> list = measurementHome.getAll(mapId);
		/* check for similarity */
		TreeSet<Measurement> hits = new TreeSet<Measurement>(
				new MeasurementComparator(currentMeasurement));

		for (Measurement m : list) {
			int level = measurementSimilarityLevel(m, currentMeasurement);

			if (debug) {
				// debug
				Fingerprint fp = fingerprintHome.getByMeasurementId(m.getId());

				if (fp != null) {
					Location l = (Location) fp.getLocation();
					if (l != null) {
						log.log(Level.FINE, "location \""
								+ l.getMap().getMapName() + l.getSymbolicID()
								+ "\" achieved similarity level " + level);
					}
				}
				// end debug
			}

			if (level > LOCATION_THRESHOLD) {
				hits.add(m);
			}
		}

		if (hits.size() > 0) {

			Measurement bestMatch = hits.first();

			Fingerprint f = fingerprintHome.getByMeasurementId(bestMatch
					.getId());

			if (f != null) {
				loc = (Location) f.getLocation();
				loc.setAccuracy(measurementSimilarityLevel(bestMatch,
						currentMeasurement));

				if (debug) {
					log.log(Level.FINE, "Best match: \""
							+ loc.getMap().getMapName() + loc.getSymbolicID()
							+ "\" achieved similarity level "
							+ loc.getAccuracy());
				}
			}

		}

		return loc;
	}

	@SuppressWarnings("unchecked")
	public int measurementSimilarityLevel(com.aiyc.base.core.Measurement t,
			com.aiyc.base.core.Measurement o) {

		Location mloc = null;
		if (debug) {
			Fingerprint tf = fingerprintHome
					.getByMeasurementId(((Measurement) t).getId());

			if (tf != null) {
				mloc = (Location) tf.getLocation();
			}

			if (mloc != null) {
				log.log(loglevel,
						"Calculating similarity Level between current measurement and "
								+ mloc.getMap().getMapName()
								+ mloc.getSymbolicID());
			} else {
				log.log(loglevel,
						"Calculating similarity Level between current Measurements and #"
								+ ((Measurement) t).getId());
			}
		} else {
			log.log(loglevel,
					"Calculating similarity Level between current Measurements and #"
							+ ((Measurement) t).getId());
		}

		/* total amount of credit that can be achieved */
		double totalCredit = 0;

		/* account that holds the achieved credit */
		double account = 0;

		/* counts the nr of positive matches of reading ID's */
		int matches;

		/*
		 * log.log(loglevel, "WiFi ID penalty: (" + readings + " read - " holds
		 * the max nr of measured readings. max of reference measurement and
		 * current measurement
		 */
		int readings;

		java.util.Vector<WiFiReading> this_vect = t.getWiFiReadings();
		java.util.Vector<WiFiReading> other_vect = o.getWiFiReadings();

		/* check WiFiReadings */
		// Vector wifiReadings1 = this.wifiReadings;
		// Vector wifiReadings2 = m.getWiFiReadings();
		matches = 0;

		for (int i = 0; i < this_vect.size(); i++) {
			WiFiReading this_wifi = this_vect.elementAt(i);
			for (int j = 0; j < other_vect.size(); j++) {
				WiFiReading other_wifi = other_vect.elementAt(j);

				/*
				 * bssid match: add ID contribution and signal strength
				 * contribution
				 */
				if (this_wifi != null && this_wifi.getBssid() != null
						&& other_wifi != null && other_wifi.getBssid() != null
						&& this_wifi.getBssid().equals(other_wifi.getBssid())) {

					// log.log(loglevel, "WiFi + ID + SC");

					account += ID_POS_CONTRIBUTION;
					account += signalContribution(this_wifi.getRssi(),
							other_wifi.getRssi());
					matches++;
				}
			}
		}

		/*
		 * penalty if for each net that did not match
		 */
		readings = Math.max(this_vect.size(), other_vect.size());
		account += (readings - matches) * ID_NEG_CONTRIBUTION;

		log.log(loglevel, "WiFi ID penalty: (" + readings + " read - "
				+ matches + " matches) * " + ID_NEG_CONTRIBUTION + " = "
				+ (readings - matches) * ID_NEG_CONTRIBUTION);

		/*
		 * get the total credit for this measurement.
		 */
		totalCredit += this_vect.size() * ID_POS_CONTRIBUTION;

		totalCredit += this_vect.size() * SIGNAL_CONTRIBUTION;

		/* get accuracy level defined by bounds */
		int factor = LOCATION_KNOWN - LOCATION_UNKNOWN;

		/* a negative account results immediately in accuracy equals zero */
		int accuracy = 0;
		if (account > 0) {
			/*
			 * compute percentage of account from totalCredit -> [0,1]; stretch
			 * by accuracy span -> [0,MAX]; and in case min accuracy would not
			 * be zero, add this offset
			 */
			double a = (account / totalCredit) * factor + LOCATION_UNKNOWN;

			/* same as Math.round */
			accuracy = (int) Math.floor(a + 0.5d);
		}
		String logmsg = "Comparing measurements..."
				+ "\n"
				+ "-> Testing Measur: "
				+ (mloc == null ? ((Measurement) t).getId() : mloc.getMap()
						.getMapName()
						+ mloc.getSymbolicID()) + "\n" + "-> Credit achieved: "
				+ account + "\n" + "-> Total Credit possible: " + totalCredit
				+ "\n" + "-> Accuracy achieved: " + accuracy;
		log.log(loglevel, logmsg);

		return accuracy;
	}

	public Boolean measurmentAreSimilar(com.aiyc.base.core.Measurement t,
			com.aiyc.base.core.Measurement o) {

		if (measurementSimilarityLevel(t, o) > LOCATION_THRESHOLD) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * computes the credit contributed by the received signal strength of any
	 * wireless scan
	 */
	private double signalContribution(double rssi1, double rssi2) {

		/*
		 * we take the reference value of the rssi as base for further
		 * computations
		 */
		double base = rssi1;

		log.log(loglevel, "  Base: " + base);

		/*
		 * in order that +20 and -20 dB are treated the same, the penalty
		 * function uses the difference of the rssi's.
		 */
		double diff = Math.abs(rssi1 - rssi2);

		log.log(loglevel, "  Diff: " + diff);

		/* get percentage of error */
		double x = diff / base;

		log.log(loglevel, "  Diff percents of base: " + x);

		/* prevent division by zero */
		if (x > 0.0) {
			/*
			 * small error should result in a high contribution, big error in a
			 * small -> reciprocate (1/x) MIN = 1, MAX = infinity
			 */
			double y = 1 / x;

			log.log(loglevel, "  Current Contribution: " + y);

			/*
			 * compute percentage of treshold regarding the current base
			 */
			double t = SIGNAL_PENALTY_THRESHOLD / base;

			/*
			 * shift down the resulting graph. the root (zero) will then be
			 * exactly at x = treshold for every base, e.g. measurement, and
			 * signal differences above the treshold will result in a negative
			 * contribution
			 */
			y -= 1 / t;

			/*
			 * graph increases fast, so that a difference of 15 still results in
			 * a maximal contribution. With this adjustment, the graph gets flat
			 * and this has also an impact on the penalty (difference to big)
			 */
			y = y * SIGNAL_GRAPH_LEVELING;

			log.log(loglevel, "  Shifted Contribution: " + y + " (shifted by "
					+ t + ")");

			if ((-1 * SIGNAL_CONTRIBUTION <= y) && (y <= SIGNAL_CONTRIBUTION)) {

				log.log(loglevel, "  Returned: " + y);
				return y;
			} else {

				log.log(loglevel, "  Returned: " + SIGNAL_CONTRIBUTION
						+ " (CUTOFF, y was " + y + ")");

				/* don't exceed the max possible credits/penalty */
				return SIGNAL_CONTRIBUTION;
			}
		} else {

			log.log(loglevel, "  Returned: " + SIGNAL_CONTRIBUTION
					+ " (diff was zero)");
			return SIGNAL_CONTRIBUTION;
		}
	}

}