/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.topology.movement.modularosm;

import de.tud.kom.p2psim.api.topology.Topology;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.AttractionPointImpl;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisualizationInjector;
import de.tudarmstadt.maki.simonstrator.api.Binder;

/**
 *
 * CHANGELOG
 * - 26.06.2017 Clemens Krug:
 * Change some calculations that used numbers that were not comprehensible and not documented.
 * In the previous version, changing the zoom of the visualisation / map would fuck up the whole
 * positioning calculations. This has been fixed now.
 *
 *
 *
 * @author Martin Hellwig
 * @version 1.0, Nov 3, 2015
 */
public class GPSCalculation {

	private static double latCenter;

	private static double lonCenter;

	private static int zoom;

	private static double metersPerPixel;

	// Earth Circumference in meters
	private static int EARTH_CIRCUMFERENCE = 40030173;

	/*
	 * http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-
	 * latitude-longitude-by-some-amount-of-meters
	 * 
	 * If your displacements aren't too great (less than a few kilometers) and
	 * you're not right at the poles, use the quick and dirty estimate that
	 * 111,111 meters (111.111 km) in the y direction is 1 degree (of latitude)
	 * and 111,111 * cos(latitude) meters in the x direction is 1 degree (of
	 * longitude).
	 * 
	 * -> Latitude: 1m = 1 deg / 111111
	 */
	private static double scaleLon;

	public GPSCalculation() {
	}

	private void setScaleFactor() {
		// this.scaleFactor = Math.pow(2.0d, (13 - zoom));
		/*
		 * BR: set scaleFactor to fixed zoom level 15 ==> 0.125 (as in this
		 * case, 1px == 1m) - this way, the world-size specified in the configs
		 * is valid on all zoom levels.
		 */
		// 17: 2, 16: 1, 15: 0.5, 14: 0.25
//		VisualizationInjector.setScale(Math.pow(2.0d, (zoom - 16)));
		VisualizationInjector.setScale(1/metersPerPixel);
	}

	public static double getLatCenter() {
		return latCenter;
	}

	public static double getLonCenter() {
		return lonCenter;
	}

	public static int getZoom() {
		return zoom;
	}

	public static double getMetersPerPixel()
	{
		return metersPerPixel;
	}
	
	public static double getWorldDimensionsX() {
		return Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getX();
	}
	
	public static double getWorldDimensionsY() {
		return Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getY();
	}

	public static double getLatUpper() {
		return latCenter + ((Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getY()/2) / 111111d);
	}

	public static double getLatLower() {
		return latCenter - ((Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getY()/2) / 111111d);
	}

	public static double getLonLeft() {
		return lonCenter - ((Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getX()/2) / (111111d * scaleLon));
	}

	public static double getLonRight() {
		return lonCenter + ((Binder.getComponentOrNull(Topology.class)
				.getWorldDimensions().getX()/2) / (111111d * scaleLon));
	}

	public void setLatCenter(double latCenter) {
		this.latCenter = latCenter;
		this.scaleLon = Math.cos(Math.toRadians(latCenter));
	}

	public void setLonCenter(double lonCenter) {
		this.lonCenter = lonCenter;
	}
	
	/**
	 * Projects the gps coordinates in the given gps window to the world-coordinates given in world-dimensions
	 * @param lat
	 * @param lon
	 * @return The projected position in world-dimensions
	 */
	public static PositionVector transformGPSWindowToOwnWorld(double lat, double lon) {
		double x = getWorldDimensionsX() * (lon - getLonLeft())/(getLonRight() - getLonLeft());
		//Invert the y value, because in Java Swing we start drawing in the upper left corner instead in the lower left one
		double y = getWorldDimensionsY() - getWorldDimensionsY() * (lat - getLatLower())/(getLatUpper() - getLatLower());
		return new PositionVector(x, y);
	}
	
	/**
	 * Checks if the given latitute and longitude is within the simulated GPS area.
	 * 
	 * @param lat
	 * @param lon
	 * @return True if within the GPS area, false otherwise.
	 */
	public static boolean isWithinGPSBoundaries(double lat, double lon) {
		if(lat > getLatLower() && lat < getLatUpper() &&
				lon > getLonLeft() && lon < getLonRight())		{
			return true;			
		}		
		else return false;
	}
		
	/**
	 * Formula http://wiki.openstreetmap.org/wiki/Zoom_levels
	 * 
	 * @param zoom
	 */
	public void setZoom(int zoom) {
		GPSCalculation.zoom = zoom;
		GPSCalculation.metersPerPixel = EARTH_CIRCUMFERENCE * Math.cos(Math.toRadians(GPSCalculation.getLatCenter())) / Math.pow(2, GPSCalculation.getZoom() + 8);
		setScaleFactor();
	}
}
