using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Assertions;

public class DeployedUnit : Unit {
	private bool selected;
	private bool visible;
	private bool isMouseOver;
	private GameObject gameObject;
	private SpriteRenderer unitSpriteRenderer;
	private GameObject mapInfo;
	private LineRenderer pathLineRenderer;
	private LineRenderer directionLineRenderer;
	private LineRenderer targetLineRenderer;
	private static float currentScale = 1.0f;
	/*private DeployedUnit stackedParent = null;
	private Set<DeployedUnit> stackedChildren = new Set<DeployedUnit>();*/

	private Path path;
	private DeployedUnit target;
	private bool isMoving = false;
	private Vertex position;
	private float angle; // in radians
	private float verticalAngle; // in radians
	private float currentSpeed = 0.0f; // in m/s
	private float userTargetSpeed = 0.0f;
	private float targetSpeed = 0.0f;
	private int speedButtonIndex = 3;
	private float userCruiseAltitude = 0.0f;
	private bool isUserCruiseAltitudeSet = false;
	private bool isLanding = false;

	private List<Tuple<StoredUnit, DeployedUnit>> toBeFired;
	private List<Transfert> transferts;

	private Set<DeployedUnit> alreadyTargeted;
	private bool illuminated;
	private bool EMCON = true;

	private bool displayRadar = false;
	private List<RadarPolygon> radarPolygons = null;

	private float rangeShown = 0.0f;
	private RangePolygon missileRange = null;

	private Dictionary<Country, Visibility> visibility;
	private Dictionary<Country, Visibility> newVisibility;

	private Dictionary<Class, int> antimissileSalvo;

	public DeployedUnit(Class unitClass, Vertex position) : base(unitClass) {
		this.position = position;
		init();
	}

	public DeployedUnit(StoredUnit stored, Vertex position) : base (stored) {
		this.position = position;
		init();
	}

	public DeployedUnit(int id, int classID) : base (id, classID) {
		this.position = new Vertex(-100000000, -100000000, -100000000);
		init();
	}

	private void init() {
		gameObject = GameObject.Instantiate(GameController.getDefaultUnit(), Converter.metersToGameCoo(position).getVector(-10), Quaternion.identity);

		gameObject.GetComponent<UnitBehavior>().setUnit(this);

		unitSpriteRenderer = gameObject.GetComponentsInChildren<SpriteRenderer>()[0];
		mapInfo = gameObject.transform.FindChild("Info").gameObject;

		directionLineRenderer = gameObject.GetComponentsInChildren<LineRenderer>()[0];
		targetLineRenderer = gameObject.GetComponentsInChildren<LineRenderer>()[1];
		pathLineRenderer = gameObject.GetComponentsInChildren<LineRenderer>()[2];

		setSelected(false);

		toBeFired = new List<Tuple<StoredUnit, DeployedUnit>>();
		transferts = new List<Transfert>();
		alreadyTargeted = new Set<DeployedUnit>();

		gameObject.transform.localScale *= currentScale;
		pathLineRenderer.widthMultiplier *= currentScale;
		directionLineRenderer.widthMultiplier *= currentScale;
		targetLineRenderer.widthMultiplier *= currentScale;

		antimissileSalvo = new Dictionary<Class, int>();
		radarPolygons = new List<RadarPolygon>();

		resetVisibility();
		switchVisibility();

		updateVisibility();
		updateSprite();
	}

	public Vertex getPosition() {
		return position;
	}

	public Vertex getDestination() {
		if (path == null) {
			return position;
		}
		else {
			return path.getDestination();
		}
	}

	public float getAngle() {
		return angle;
	}

	public float getVerticalAngle() {
		return verticalAngle;
	}

	public DeployedUnit getTarget() {
		return target;
	}

	public Path getPath() {
		return path;
	}

	public float getcurrentSpeed() {
		return currentSpeed;
	}

	public float getCruiseAltitude() {
		if (!isUserCruiseAltitudeSet) {
			return modules.getCruiseAltitude();
		}
		else {
			return userCruiseAltitude;
		}
	}

	public MissileTechnique getMissileTechnique() {
		return modules.getMissileTechnique();
	}

	public List<Tuple<float, float>> getLandingPattern() {
		return modules.getLandingPattern();
	}

	public int getSpeedButtonIndex() {
		return speedButtonIndex;
	}

	public FuelType getMainFuelType() {
		return modules.getMainFuelType();
	}

	public float getFuelLeft(FuelType type) {
		return modules.getFuelLeft(type);
	}

	public float getConsumption(FuelType type) {
		return modules.getConsumption(type, currentSpeed);
	}

	public void setSelected(bool selected) {
		this.selected = selected;
	}

	public static void staticScale(float amount) {
		currentScale *= amount;
	}

	public void scaleSprite(float amount) {
		gameObject.transform.localScale *= amount;
		pathLineRenderer.widthMultiplier *= amount;
		directionLineRenderer.widthMultiplier *= amount;
		targetLineRenderer.widthMultiplier *= amount;
	}

	private void stop() {
		path = null;
		isMoving = false;
		targetSpeed = modules.getMinSpeed();
		isLanding = false;
	}

	public void setPosition(Vertex newPosition) {
		this.position = newPosition;
	}

	public void setAngle(float angle) {
		this.angle = angle;
	}

	public void setVerticalAngle(float angle) {
		this.verticalAngle = angle;
	}

	public void setPath(Path newPath, bool keepIsLanding) {
		this.path = newPath;
		isMoving = true;

		if (!keepIsLanding) {
			isLanding = false;
		}

		if (userTargetSpeed == 0.0f) {
			userTargetSpeed = modules.getMaxSpeed();
		}

		targetSpeed = userTargetSpeed;
	}

	public void addPath(Path newPath) {
		if (newPath.getEndWaypoints() != null && newPath.getEndWaypoints().Count > 0) {
			setPath(newPath, false);
		}
		else if (path == null) {
			setPath(newPath, false);
		}
		else {
			setPath(path.addPath(newPath), false);
		}
	}

	public void removePath() {
		stop();
	}

	public void setTarget(DeployedUnit target) {
		this.target = target;

		if (isLanding) {
			stop();
		}
	}

	public void setTargetSpeed(float newSpeed) {
		userTargetSpeed = newSpeed;
		if (path != null) {
			targetSpeed = newSpeed;
		}
	}

	public void setSpeedButton(int buttonIndex) {
		speedButtonIndex = buttonIndex;
	}

	public void setCruiseAltitude(float newCruiseAltitude) {
		userCruiseAltitude = newCruiseAltitude;
		isUserCruiseAltitudeSet = true;
	}

	public void updateVisibility() {

		visible = visibility[GameController.getPlayerCountry()] != Visibility.Invisible;
		gameObject.SetActive(visible);

		/*if (!visible) {
			setStackedParent(null); //if we don't do this, the player may have some info about invisible units
		}
		if (stackedParent != null && !stackedParent.visible) {
			setStackedParent(null);
		}*/
	}

	public void updateDirectionLine() {
		/*if (hasStackedChildren() || isStacked()) {
			directionLineRenderer.enabled = false;
			return;
		}*/

		directionLineRenderer.enabled = true;

		if (getVisibility(GameController.getPlayerCountry()) == Visibility.Visible) {
			directionLineRenderer.material = GameController.getRelationMaterial(getOwner().getRelation(GameController.getPlayerCountry()));
		}
		else {
			directionLineRenderer.material = GameController.getRelationMaterial(Relation.Unknown);
		}


		directionLineRenderer.numPositions = 2;
		directionLineRenderer.SetPosition(0, Converter.metersToGameCoo(position).getVector(-11));
		directionLineRenderer.SetPosition(1, (Converter.metersToGameCoo(position) + new Vector(Mathf.Cos(angle), Mathf.Sin(angle), -2) * Constants.DIRECTION_LENGTH * currentScale).getVector(-11));
	}

	public void updateTargetLine() {
		/*if (hasStackedChildren() || isStacked()) {
			targetLineRenderer.enabled = false;
			return;
		}*/

		targetLineRenderer.enabled = selected && (target != null);

		if (!targetLineRenderer.enabled) {
			return;
		}

		targetLineRenderer.material = GameController.getRelationMaterial(getOwner().getRelation(target.getOwner()));

		targetLineRenderer.numPositions = 2;
		targetLineRenderer.SetPosition(0, Converter.metersToGameCoo(position).getVector(-11));
		targetLineRenderer.SetPosition(1, Converter.metersToGameCoo(target.position).getVector(-11));
	}

	public void updatePathLine() {
		/*if (hasStackedChildren() || isStacked()) {
			pathLineRenderer.enabled = false;
			return;
		}*/

		if (!selected || !isMoving) {
			pathLineRenderer.enabled = false;
			return;
		}

		pathLineRenderer.enabled = true;

		List<Vertex> vertices = new List<Vertex>();
		vertices.Add(position);

		foreach(Waypoint waypoint in path.toList()) {
			vertices.Add(waypoint.getVertex());
		}

		Vector3[] vectors = new Vector3[vertices.Count];
		for (int i = 0; i < vertices.Count; i++) {
			vectors[i] = Converter.metersToGameCoo(vertices[i]).getVector(-5);
		}

		pathLineRenderer.numPositions = vertices.Count;
		pathLineRenderer.SetPositions(vectors);
	}

	public void updateSprite() {
		/*if (hasStackedChildren()) {
			mapInfo.SetActive(false);
		}
		else if (isStacked()){
			unitSpriteRenderer.enabled = false;
			mapInfo.SetActive(false);
		}
		else {*/
			unitSpriteRenderer.enabled = visibility[GameController.getPlayerCountry()] != Visibility.Invisible;
			mapInfo.SetActive(unitSpriteRenderer.enabled && (selected || isMouseOver));
		//}

		Relation relation = getOwner().getRelation(GameController.getPlayerCountry());
		Vertex meanPosition = position;
		bool differentRelations = false;

		/*foreach(DeployedUnit stackedChild in stackedChildren) {
			meanPosition += stackedChild.position;

			if (stackedChild.getOwner().getRelation(GameController.getPlayerCountry()) != relation) {
				differentRelations = true;
			}
		}

		meanPosition /= (1 + stackedChildren.count());*/

		gameObject.transform.position = Converter.metersToGameCoo(position).getVector(-10); //sets the collider to the real position
		unitSpriteRenderer.transform.position = Converter.metersToGameCoo(meanPosition).getVector(-10); //sets the sprite to the mean position of the stack

		unitSpriteRenderer.sprite = unitClass.getSprite(visibility[GameController.getPlayerCountry()], relation, false, differentRelations);
	}

	public void updateMapInfos() {
		if (!mapInfo.activeSelf) {
			return;
		}

		GameController.deleteAllChildren(mapInfo.transform.FindChild("Canvas"), true);

		if (visibility[GameController.getPlayerCountry()] == Visibility.Unknown) {
			addInfoText("????");
		}
		else {
			addInfoText(getClass().getName());
		}

		if (!canBeCommanded()) {
			addInfoText("NO COMMAND", true);
		}

		if (!canBeSteered()) {
			addInfoText("NO BRIDGE", true);
		}

		if (currentSpeed != 0) {
			addInfoText("Speed : " + (int)currentSpeed + " m/s");
		}
		if (position.z() != 0) {
			addInfoText("Alt. : " + (int)position.z() + " m");
		}
	}

	private void addInfoText(string text, bool red = false) {
		string name = red ? "RedText" : "Text";
		GameObject clone = GameObject.Instantiate(mapInfo.transform.FindChild("Canvas").FindChild(name).gameObject, mapInfo.transform.FindChild("Canvas"));

		clone.SetActive(true);
		clone.GetComponent<Text>().text = text;
	}

	public void updateRadarDisplay() {
		if (!selected || !isDisplayingRadar()) {
			if (radarPolygons.Count > 0) {
				foreach(RadarPolygon poly in radarPolygons) {
					poly.removeFromGame();
				}
				radarPolygons.Clear();
			}
			return;
		}

		if (radarPolygons.Count > 0 && Time.frameCount % 50 != 0) { //TODO : update more often if turning
			return;
		}

		foreach(RadarPolygon poly in radarPolygons) {
			poly.removeFromGame();
		}
		radarPolygons.Clear();

		foreach(Tuple<Module, ModuleData> tuple in getModules()) {
			if (tuple._2 is ActiveModuleData && tuple._2.isOperationnal()) {
				radarPolygons.Add(getRadarPolygon((AngularModule)tuple._1, (AngularModuleData)tuple._2));
			}
		}
	}

	private RadarPolygon getRadarPolygon(AngularModule template, AngularModuleData data) {
		ContourBuilder builder = new ContourBuilder();
		ContourBuilder innerBuilder = null;

		if (template.getMinRange() > 0.0f) {
			innerBuilder = new ContourBuilder();
		}

		bool centerAdded = false;
		Vector3 origin = new Vector3(Converter.metersToGameCoo(position.x()), Converter.metersToGameCoo(position.y()), position.z());
		float minDistance = Converter.metersToGameCoo(template.getMinRange());
		float maxDistance = Converter.metersToGameCoo(template.getMaxRange());
		int mask = (int)Mathf.Pow(2, 2);

		for (int i = 0; i < Constants.SAMPLE_RADAR_DISPLAY; i++) {

			float angle = i * Mathf.PI * 2 / Constants.SAMPLE_RADAR_DISPLAY;
			Vector3 direction = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle));

			if (innerBuilder != null) {
				innerBuilder.addVertex(new GameVertex(origin.x + minDistance * direction.x, origin.y + minDistance * direction.y));
			}

			if (!data.isAngleOk(angle)) {
				if (!centerAdded) {
					builder.addVertex(new GameVertex(origin.x, origin.y));
				}
				centerAdded = true;

			}
			else {
				RaycastHit info;
				if (!template.getDisplayIgnoreRaycast() && Physics.Raycast(origin, direction, out info, maxDistance, mask)) {
					if (info.point.x < 0) {
						builder.addVertex(new GameVertex(origin.x + maxDistance * direction.x, origin.y + maxDistance * direction.y));
					}
					else {
						builder.addVertex(new GameVertex(info.point.x, info.point.y));
					}
				}
				else {
					builder.addVertex(new GameVertex(origin.x + maxDistance * direction.x, origin.y + maxDistance * direction.y));
				}
			}
		}

		Contour contour = builder.build();
		Contour innerContour = null;
		if (innerBuilder != null) {
			innerContour = innerBuilder.build();
		}

		SlicedPolygon zone = new SlicedPolygon(contour);
		if (innerContour != null) {
			zone = zone.difference(new SlicedPolygon(innerContour));
		}

		return new RadarPolygon(zone, template.getName(), minDistance, maxDistance, origin, template.getDisplayColor());
	}

	public void updateShowMissileRange() {
		if (missileRange != null) {
			missileRange.removeFromGame();
			missileRange = null;
		}

		if (rangeShown == 0.0f) {
			return;
		}

		missileRange = new RangePolygon(Converter.metersToGameCoo(position), Converter.metersToGameCoo(rangeShown), new Color(1.0f, 0, 0, 0.3f));
	}

	// Modules interactions

	public string canFireMessage(Class missileClass, int number, DeployedUnit target) {
		if (!canBeCommanded()) {
			return "NO COMMAND";
		}
		else if (target == null) {
			return "NO TARGET";
		}
		else if (!missileClass.canHit(target.getClass().getClassType())) {
			return "CANNOT HIT TARGET";
		}
		else if (target.getClass().getCountry().getRelation(getClass().getCountry()) == Relation.Player) {
			return "NO FRIENDLY FIRE";
		}
		else if (getNumberReadyToLaunch(missileClass) < number || number == 0) {
			return "NOT ENOUGH AMMO";
		}
		else if (missileClass.needsMissileControl() && getAvailableMissileControlSlots() < number) {
			return "NOT ENOUGH MISSILE CONTROL SLOTS";
		}
		else if (missileClass.getOptimalRange() < position.distance(target.position)) {
			return "NOT ENOUGH RANGE";
		}

		return "";
	}

	public bool canFire(Class missileClass, int number, DeployedUnit target) {
		return canFireMessage(missileClass, number, target) == "";
	}

	public string canLaunchMessage(Class launchedClass, int number, Loadout loadout) {
		if (!canBeCommanded()) {
			return "NO COMMAND";
		}
		else if (getNumberIdleStored(launchedClass) < number || number == 0) {
			return "NOT ENOUGH VEHICLE";
		}

		foreach(KeyValuePair<Class, int> pair in loadout.getAmmo()) {
			if (getNumberIdleStored(pair.Key) < number * pair.Value) {
				return "NOT ENOUGH AMMO";
			}
		}

		if (!launchedClass.getPathConstraints().canGoThrough(GameController.getTerrainAt(Converter.metersToGameCoo(position)))) {
			return "CANNOT BE DEPLOYED HERE";
		}

		return "";
	}

	public string canLandMessage() {
		if (!canBeCommanded()) {
			return "NO COMMAND";
		}
		else if (target == null) {
			return "NO TARGET";
		}
		else if (!target.canBeLandedOn(this)) {
			return "CANNOT HIT TARGET";
		}
		else if (target.getClass().getCountry().getRelation(getClass().getCountry()) != Relation.Player) {
			return "CAN ONLY LAND ON FRIENDLY UNIT";
		}

		return "";
	}

	public bool canLand() {
		return canLandMessage() == "";
	}

	public bool canLaunch(Class launchedClass, int number, Loadout loadout) {
		return canLaunchMessage(launchedClass, number, loadout) == "";
	}

	public bool canBeLandedOn(DeployedUnit unit) {
		return modules.canBeLandedOn(unit);
	}

	public bool hasAvailableIlluminatingSlots(DeployedUnit target) {
		return modules.hasAvailableIlluminatingSlots(target);
	}

	public int getNumberReadyToLaunch(Class unitClass) {
		return modules.getNumberReadyToLaunch(unitClass);
	}

	public int getNumberIdleStored(Class unitClass) {
		return modules.getNumberIdleStored(unitClass);
	}

	public int getAvailableMissileControlSlots() {
		int slots = modules.availableMissileControlSlots();

		foreach(Tuple<StoredUnit, DeployedUnit> tuple in toBeFired) {
			if (tuple._1.getClass().needsMissileControl()) {
				slots --;
			}
		}

		return Mathf.Max(slots, 0);
	}

	public bool isFiring(Class unitClass) {
		return modules.isFiring(unitClass);
	}

	public void fire(Class missileClass, int number, DeployedUnit missileTarget) {
		for (int i = 0; i < number; i++) {
			StoredUnit stored = modules.getIdleReadyToLaunch(missileClass);

			if (stored != null) {

				if (!missileClass.needsMissileControl() || availableMissileControlSlots() > 0) {
					Requests.setLoadingState(stored, LoadingState.BeingFired);
					toBeFired.Add(new Tuple<StoredUnit, DeployedUnit>(stored, missileTarget));

					if (missileClass.needsMissileControl()) {
						addControlledMissile(stored);
					}
				}
				else {
					Debug.LogWarning("A missile control slot is missing");
				}

			}
			else {
				Debug.LogWarning("A ready missile is missing");
			}
		}
	}

	public bool isPreparing(Class launchedClass) {
		foreach(Transfert transfert in transferts) {
			if (transfert.toBeMoved.getClass() == launchedClass && transfert.shouldBeLaunched && !hasNoCurrentTransfert(transfert.toBeMoved.getID())) {
				return true;
			}
		}

		return false;
	}

	public bool isLaunching(Class launchedClass) {
		foreach(Transfert transfert in transferts) {
			if (transfert.toBeMoved.getClass() == launchedClass && transfert.shouldBeLaunched && hasNoCurrentTransfert(transfert.toBeMoved.getID())) {
				return true;
			}
		}

		return false;
	}

	public void launch(Class vehicleClass, int number, Loadout loadout) {
		for (int i = 0; i < number; i++) {
			StoredUnit stored = modules.getIdleStored(vehicleClass);

			if (stored != null) {
				LauncherModuleData launcher = modules.getLauncherModule(stored.getClass());

				if (launcher != null) {

					//add unit to transferts
					Requests.setLoadingState(stored, LoadingState.BeingPrepared);
					Requests.addToTransferts(this, new Transfert(stored, launcher, true));//transfert even if at the right module

					//for now assume that the unit has no missile
					foreach(KeyValuePair<Class, int> kv in loadout.getAmmo()) {
						Class loadoutClass = kv.Key;
						for (int j = 0; j < kv.Value; j++) {
							StoredUnit loadoutUnit = modules.getIdleStored(loadoutClass);
							ContainerModuleData container = stored.getContainerModule(loadoutClass);

							if (loadoutUnit != null && container != null) {
								Requests.setLoadingState(loadoutUnit, LoadingState.BeingLoaded);
								Requests.addToTransferts(this, new Transfert(loadoutUnit, container, false));
							}
							else {
								Debug.LogWarning("Unit cannot be fully loaded");
							}
						}
					}
				}
			}
		}
	}

	public void addTransfert(Transfert transfert) {
		transferts.Add(transfert);
	}

	public void removeTransfert(int storedID) {
		transferts.RemoveAll(transfert => transfert.toBeMoved.getID() == storedID);
	}

	public void landOn(DeployedUnit unit) {
		Requests.landOn(this, unit);
		isLanding = true;
	}

	public void illuminateTarget(DeployedUnit target) {
		modules.illuminateTarget(target);
	}

	public void destroy() {
		/*setStackedParent(null);

		foreach(DeployedUnit stackedChild in stackedChildren) {
			stackedChild.stackedParent = null;
		}*/

		if (missileRange != null) {
			missileRange.removeFromGame();
		}

		foreach(RadarPolygon radar in radarPolygons) {
			radar.removeFromGame();
		}

		GameObject.Destroy(gameObject);
	}


	// Update methods

	public void move(float delta) {
		if (!canBeSteered()) {
			removePath(); //no need to share as all players will run this function
		}

		if (path == null) {

			float minSpeed = modules.getMinSpeed();
			if (minSpeed > 0.0) {
				angle += modules.getAngularSpeed() / 10 * delta; //do not turn at full rate
			}

			updateSpeed(delta);
			modules.consumeFuel(delta, currentSpeed); 
			position += (new Vector(Mathf.Cos(angle), Mathf.Sin(angle), 0)) * currentSpeed * delta;

			return;
		}

		modules.consumeFuel(delta, currentSpeed);
		if (!modules.hasFuelLeft()) {
			userTargetSpeed = targetSpeed = 0;
		}

		Vertex nextVertex = path.getNextWaypoint().getVertex();

		Vector direction = (nextVertex - position); //enlever z
		Vector currentDirection = new Vector(Mathf.Cos(angle), Mathf.Sin(angle), 0);

		//Changes in angle
		if (direction.x() != 0.0f || direction.y() != 0.0f) { // if the destination is at the same position as the unit, no need to change angle
			float angleDiff = direction.angle(currentDirection);
			float angularSpeed = modules.getAngularSpeed(); //angular speed in radian/s

			if (Mathf.Abs(angleDiff) < angularSpeed * delta) {
				angle = Mathf.Acos(direction.normalized().x()) * Mathf.Sign(direction.normalized().y());
			}
			else if (angleDiff > 0) {
				angle -= angularSpeed * delta;
			}
			else {
				angle += angularSpeed * delta;
			}
		}

		//Changes in vertical angle
		float speedChange = 1.0f;
		if (direction.z() != 0.0f) {
			float theoreticalVerticalAngle = Mathf.Asin(direction.z() / ((nextVertex - position).norm()));

			//for simplicity, only brutal changes in vertical angle
			verticalAngle = Mathf.Clamp(theoreticalVerticalAngle, -modules.getVerticalAngleRange(), modules.getVerticalAngleRange());

			//Debug.Log(verticalAngle);

			speedChange -= Mathf.Sin(verticalAngle) * Mathf.Sign(verticalAngle) / 2; //simulate gravity

			//Debug.Log(speedChange);
		}
		else {
			verticalAngle = 0.0f;
		}

		//Speed and movement
		updateSpeed(delta);

		float distTravelled = currentSpeed * delta * speedChange;

		if (distTravelled * Constants.TOLERANCE_DISTANCE_FACTOR >= direction.norm()) {
			path.removeFirstWaypoint();
			position = nextVertex;

			if (path.size() == 0) {
				stop();
			}
		}
		else {
			position += (new Vector(Mathf.Cos(angle) * Mathf.Cos(verticalAngle), Mathf.Sin(angle) * Mathf.Cos(verticalAngle), Mathf.Sin(verticalAngle))) * distTravelled;
		}
	}

	private void updateSpeed(float delta) {
		
		if (currentSpeed < targetSpeed) {
			float acceleration = modules.getAcceleration(); //acceleration in m/s^2

			if (Mathf.Abs(currentSpeed - targetSpeed) <= acceleration * delta) {
				currentSpeed = targetSpeed;
			}
			else {
				currentSpeed += acceleration * delta;
			}
		}
		else {
			float deceleration = modules.getDeceleration(); //acceleration in m/s^2

			if (Mathf.Abs(currentSpeed - targetSpeed) <= deceleration * delta) {
				currentSpeed = targetSpeed;
			}
			else {
				currentSpeed -= deceleration * delta;
			}
		}
	}

	public void updateFireMissiles() {
		List<Tuple<StoredUnit, DeployedUnit>> toBeRemoved = new List<Tuple<StoredUnit, DeployedUnit>>();

		foreach (Tuple<StoredUnit, DeployedUnit> tuple in toBeFired) {
			if (GameController.fromID(tuple._2.getID()) == null) { //target is dead
				Requests.setLoadingState(tuple._1, LoadingState.None);
				toBeRemoved.Add(tuple);
			}
			else if (((LauncherModuleData)tuple._1.getStore()).tryToFire(tuple._1, tuple._2)) { //if fired 
				toBeRemoved.Add(tuple);
			}
		}

		foreach (Tuple<StoredUnit, DeployedUnit> tuple in toBeRemoved) {
			toBeFired.Remove(tuple);
		}
	}

	public void updateTransferts() {
		if (transferts.Count == 0) {
			return;
		}

		List<Transfert> toBeRemoved = new List<Transfert>();

		foreach(Transfert transfert in transferts) {
			if (hasNoCurrentTransfert(transfert.toBeMoved.getID())) {
				bool hasBeenTransfered = transfert.destination.tryToTransfert(transfert.toBeMoved);

				if (hasBeenTransfered) {
					if (transfert.shouldBeLaunched) {
						if (((LauncherModuleData)transfert.destination).tryToLaunch(transfert.toBeMoved)) {
							toBeRemoved.Add(transfert);
						}
						else {
							Requests.setLoadingState(transfert.toBeMoved, LoadingState.ReadyToLaunch);
							//unit cannot be launched yet, the launcher is busy
						}
					}
					else {
						Requests.setLoadingState(transfert.toBeMoved, LoadingState.None);
						toBeRemoved.Add(transfert);
					}
				}
			}
		}

		foreach(Transfert transfert in toBeRemoved) {
			Requests.removeFromTransferts(this, transfert);
		}
	}

	private bool hasNoCurrentTransfert(int unitID) {
		foreach(Transfert transfert in transferts) {
			if (transfert.destination.getParent() == unitID) {
				return false;
			}
		}

		return true;
	}

	public void checkPathOutdated() {
		if (path == null) {
			return;
		}

		if (path.isOutdated()) {
			Requests.updatePath(this);
		}
	}

	//all verification relating to missiles
	public void checkMissile(float delta) {
		if (getClass().getClassType() != ClassType.Missile) {
			return;
		}

		//check if target still exists
		if (target == null) {
			Requests.destroyUnit(this);
			NetworkManager.addToLog("A " + getClass().getName() + " lost its target and crashed");
			return;
		}

		//check if arrived at target
		if ((position - target.position).norm() < currentSpeed * delta * Constants.TOLERANCE_DISTANCE_FACTOR) {
			//missile hits target
			Requests.destroyUnit(this);
			target.addDamage(modules.getDamage());

			NetworkManager.addToLog("A " + getClass().getName() + " hit a " + target.getClass().getName());
			return;
		}

		if (Random.value <= modules.getFailProbability(delta)) {
			//missile fails
			Requests.destroyUnit(this);
			NetworkManager.addToLog("A " + getClass().getName() + " has failed and crashed");
			return;
		}

		if (getClass().needsIlluminatedTarget() && !target.illuminated && position.distance(target.position) / (currentSpeed + target.currentSpeed) < Constants.ILLUMINATION_TIME) {
			if (GameController.hasAvailableIlluminatingSlots(getClass().getCountry(), target)) {
				GameController.illuminateTarget(getClass().getCountry(), target);
			}
			else {
				Requests.destroyUnit(this);
				NetworkManager.addToLog("A " + getClass().getName() + " had no illuminated target and crashed");
			}
		}

		if (EMCON != (position.distance(target.position) < getClass().getDistanceRadarOn())) { //radar are off is distance > or on if distance is <
			Requests.setEMCON(this, !EMCON);
		}
	}

	public void checkLanding(float delta) {
		if (!isLanding) {
			return;
		}

		//no need to check if target is still valid, this is taken care of in GameController.Update()

		//check if arrived at target
		if ((position - target.position).norm() < currentSpeed * delta * Constants.TOLERANCE_DISTANCE_FACTOR) {
			//unit arrived at target
			Requests.storeUnit(this);
			target.addReadyToLaunch(GameController.storedFromID(this.getID()));

			NetworkManager.addToLog("A " + getClass().getName() + " has landed on a " + target.getClass().getName());
		}
	}

	public void checkAutoDef(float delta) {
		if (getClass().getClassType() != ClassType.Ship) { //Maybe add other types after
			return;
		}

		modules.autoDef(delta);
	}

	public void checkHull() {
		if (modules.isHullDead()) {
			Requests.destroyUnit(this);
			NetworkManager.addToLog("A " + getClass().getName() + " has been destroyed");
		}
	}

	public void addDamage(float amount) {
		modules.addDamage(amount);
	}

	//detection
	public Visibility getNewVisibility(Country country) {
		return newVisibility[country];
	}

	public void setNewVisibility(Country country, Visibility newCountryVisibility) {
		newVisibility[country] = newCountryVisibility;
	}

	public void resetVisibility() {
		Country country = getClass().getCountry();

		newVisibility = new Dictionary<Country, Visibility>();
		foreach(Country other in GameController.getCountries()) {
			if (country.getRelation(other) == Relation.Player) {
				newVisibility.Add(other, Visibility.Visible);
			}
			else {
				newVisibility.Add(other, Visibility.Invisible);
			}
		}
	}

	public void detectUnits() {
		modules.detectUnits();
	}

	public void switchVisibility() {
		visibility = newVisibility;
		
		if (visibility[GameController.getPlayerCountry()] == Visibility.Invisible) {
			setMouseOver(false); //fixes a bug occuring when the mouse is over a disappearing unit
		}
	}

	public Visibility getVisibility(Country country) {
		return visibility[country];
	}


	/*public bool isStacked() {
		return stackedParent != null;
	}

	public bool hasStackedChildren() {
		return stackedChildren.count() > 0;
	}

	public DeployedUnit getStackedParent() {
		return stackedParent;
	}

	public void setStackedParent(DeployedUnit newStackedParent) {
		

		if (stackedParent != null) {
			stackedParent.stackedChildren.remove(this);
		}

		if (newStackedParent != null) {

			newStackedParent.stackedChildren.add(this);

			foreach(DeployedUnit stackedChild in stackedChildren) {
				if (newStackedParent != stackedChild) {

					stackedChild.stackedParent = newStackedParent;
					newStackedParent.stackedChildren.add(stackedChild);
				}
			}

			stackedChildren.clear();
		}

		stackedParent = newStackedParent;
	}*/

	public void setMouseOver(bool val) {
		isMouseOver = val;
	}

	//Targeted units
	public bool hasAlreadyBeenTargeted(DeployedUnit missileTarget) {
		return alreadyTargeted.contains(missileTarget);
	}

	public void setTargeted(DeployedUnit missileTarget) {
		alreadyTargeted.add(missileTarget);
	}

	public bool isIlluminated () {
		return illuminated;
	}

	public void setIlluminated(bool value) {
		illuminated = value;
	}

	public bool isEMCON() {
		return EMCON;
	}

	public void setEMCON(bool value) {
		EMCON = value;

		modules.setEMCON(value);
	}

	public bool isDisplayingRadar() {
		return displayRadar;
	}

	public void setDisplayRadar(bool val) {
		displayRadar = val;
	}

	public void setAntimissileSalvo(Class antimissile, int num) {
		antimissileSalvo[antimissile] = num;
	}

	public int getAntimissileSalvo(Class antimissile) {
		if (antimissileSalvo.ContainsKey(antimissile)) {
			return antimissileSalvo[antimissile];
		}
		return 1;
	}

	public void showRange(float range) {
		rangeShown = range;
	}

	public void unshowRange() {
		rangeShown = 0.0f;
	}
}
