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 Vertex positionTarget;
	private bool isGPSGuided; //server only
	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<Triple<StoredUnit, DeployedUnit, Vertex>> toBeFired;
	private List<Transfert> transferts;
	private List<StoredUnit> preparingUnits;

	private List<DeployedUnit> birds;
	private DeployedUnit motherShip;

	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<Triple<StoredUnit, DeployedUnit, Vertex>>();
		transferts = new List<Transfert>();
		preparingUnits = new List<StoredUnit>();
		alreadyTargeted = new Set<DeployedUnit>();

		birds = new List<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, getGraphParams());
	}

	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) {
			GraphParams param = new GraphParams();
			userTargetSpeed = modules.getMaxSpeed(param);
		}

		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;
		this.positionTarget = null;

		if (isLanding) {
			stop();
		}
	}

	public void setPositionTarget(Vertex positionTarget) {
		this.target = null;
		this.positionTarget = positionTarget;
	}

	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 || positionTarget != null);

		if (!targetLineRenderer.enabled) {
			return;
		}

		targetLineRenderer.material = GameController.getRelationMaterial(target != null ? getOwner().getRelation(target.getOwner()) : Relation.Ennemy);

		targetLineRenderer.numPositions = 2;
		targetLineRenderer.SetPosition(0, Converter.metersToGameCoo(position).getVector(-11));
		targetLineRenderer.SetPosition(1, Converter.metersToGameCoo(target != null ? target.position : positionTarget).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(Mathf.Min(template.getMaxRange(), Mathf.Sqrt(2*Constants.EARTH_RADIUS*(position.z() * 200 + data.getPosition().z() + 1))));
		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 && positionTarget == null) {
			return "NO TARGET";
		}
		else if (positionTarget != null && !missileClass.hasGPSGuidance()) {
			return "CAN ONLY BE LAUNCHED AT A DIRECT TARGET";
		}
		else if (target != null && !missileClass.canHit(target.getClass().getClassType())) {
			return "CANNOT HIT TARGET";
		}
		else if (target != null && 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() && availableMissileControlSlots(target) < number) {
			return "NOT ENOUGH MISSILE CONTROL SLOTS";
		}
		else if (missileClass.getOptimalRange() < position.distance(target != null ? target.position : positionTarget)) {
			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 bool canLaunch(Class launchedClass, int number, Loadout loadout) {
		return canLaunchMessage(launchedClass, number, loadout) == "";
	}

	public string canPrepareMessage(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";
			}
		}

		return "";
	}

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

	public string canTakeOffMessage(StoredUnit stored) {
		if (stored.getLoadingState() != LoadingState.None) {
			return "UNIT NOT READY";
		}

		ContainerModuleData container = stored.getStore();

		if (!(container is LauncherModuleData)) {
			return "NOT IN A LAUNCHER";
		}
			
		return ((LauncherModuleData)container).canLaunchMessage(stored);
	}

	public bool canTakeOff(StoredUnit stored) {
		return canTakeOffMessage(stored) == "";
	}

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

		return "";
	}

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

	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 getNumberInStorage(Class unitClass) {
		return modules.getNumberInStorage(unitClass);
	}

	public List<StoredUnit> getUnitsOnDeck() {
		return modules.getUnitsOnDeck();
	}

	public List<StoredUnit> getUnitsReady() {
		return modules.getUnitsReady();
	}

	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(missileTarget) > 0) {
					Requests.setLoadingState(stored, LoadingState.BeingFired);

					if (missileClass.hasGPSGuidance()) {
						toBeFired.Add(new Triple<StoredUnit, DeployedUnit, Vertex>(stored, null, target != null ? missileTarget.position : positionTarget));
					}
					else {
						toBeFired.Add(new Triple<StoredUnit, DeployedUnit, Vertex>(stored, missileTarget, null));	
					}

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

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

	public void cancelFire(int storedUnitID) {
		foreach(Triple<StoredUnit, DeployedUnit, Vertex> triple in toBeFired) {
			if (triple._1.getID() == storedUnitID) {
				Requests.setLoadingState(triple._1, LoadingState.None);
				toBeFired.Remove(triple);
				break;
			}
		}
	}

	public bool canSafelyTurnOffMissileControl() {
		return modules.canSafelyTurnOffMissileControl();
	}

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

			if (stored != null) {
				Requests.setLoadout(stored, loadout);
				ContainerModuleData loader = modules.getLoaderModule(stored.getClass());

				if (loader != null) {

					//add unit to transferts
					Requests.setLoadingState(stored, LoadingState.TransferringToLoader);
					Requests.addToTransferts(this, new Transfert(stored, loader));//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));
							}
							else if (loadoutUnit == null) {
								Debug.LogWarning("Cannot find enough ammo : " + loadoutClass.getUniqueName());
							}
							else {
								Debug.LogWarning("Cannot find container for " +  loadoutClass.getUniqueName());
							}
						}
					}
				}
				else {
					Debug.LogWarning("No loader");
				}
			}
			else {
				Debug.LogWarning("Not enough stored");
			}
		}
	}

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

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

	public void takeOff(StoredUnit stored) {
		LauncherModuleData launcher = (LauncherModuleData)stored.getStore();

		launcher.launch(stored);
	}

	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);
	}

	GraphParams getGraphParams() {
		GraphParams param = new GraphParams();

		param.currentSpeed = currentSpeed;

		param.distanceTarget = float.MaxValue;
		if (target != null) {
			param.distanceTarget = target.position.distance(position);
		}

		return param;
	}


	// 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, getGraphParams()); 
			position += (new Vector(Mathf.Cos(angle), Mathf.Sin(angle), 0)) * currentSpeed * delta;

			return;
		}

		modules.consumeFuel(delta, getGraphParams());
		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 (getClass().getClassType() == ClassType.Missile) {
			targetSpeed = modules.getMaxSpeed(getGraphParams());
		}
		
		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(); //deceleration in m/s^2

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

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

		foreach (Triple<StoredUnit, DeployedUnit, Vertex> tuple in toBeFired) {
			if (tuple._2 != null && 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, tuple._2 == null ? tuple._3 : tuple._2.getPosition())) { //if fired 
				toBeRemoved.Add(tuple);
			}
		}

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

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

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

		foreach(Transfert transfert in transferts) {
			if (transfert.toBeMoved.getLoadingState() != LoadingState.BeingLoaded || canLoadOn(transfert.destination.getParent())) {
				bool hasBeenTransfered = transfert.destination.tryToTransfert(transfert.toBeMoved);

				if (hasBeenTransfered) {
					if (transfert.toBeMoved.getLoadingState() == LoadingState.TransferringToLoader) {
						Requests.setLoadingState(transfert.toBeMoved, LoadingState.BeingPrepared);
						preparingUnits.Add(transfert.toBeMoved);
					}
					else {
						Requests.setLoadingState(transfert.toBeMoved, LoadingState.None);
					}
					toBeRemoved.Add(transfert);
				}
			}
		}

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


		foreach(StoredUnit stored in preparingUnits) {
			if (!hasCurrentTransfert(stored.getID())) {
				Requests.setLoadingState(stored, LoadingState.None);
			}
		}

		preparingUnits.RemoveAll(stored => stored.getLoadingState() == LoadingState.None);
	}

	private bool canLoadOn(int unitID) {
		foreach(Transfert transfert in transferts) {
			if (transfert.toBeMoved.getID() == unitID && transfert.toBeMoved.getLoadingState() != LoadingState.BeingPrepared) {
				return false;
			}
		}

		return true;
	}

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

		return false;
	}

	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;
		}

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

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

		if (path == null) {
			Debug.LogWarning("Null path for missile");
			return;
		}

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

			if (isGPSGuided && EMCON) { //the missile just turned on its radars
				DeployedUnit newTarget = null;

				float minDist = float.MaxValue;
				foreach(DeployedUnit ennemy in GameController.getDeployedUnits()) {
					if (ennemy.getClass().getCountry().getRelation(this.getClass().getCountry()) != Relation.Player 
						&& this.getClass().canHit(ennemy.getClass().getClassType()) 
						&& minDist > this.position.distance(ennemy.position)) {

						minDist = this.position.distance(ennemy.position);
						newTarget = ennemy;
					}
				}

				if (newTarget != null) {
					Requests.changeTarget(this, newTarget);
					Requests.missileMovement(this, newTarget, newTarget.position, LaunchingTechnique.None);
				}
				isGPSGuided = false;
			}
		}

		if (isGPSGuided) { //no need for further checks
			return;
		}

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

			NetworkManager.addToLog("A " + getClass().getName() + " hit a " + target.getClass().getName());
			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");
			}
		}


	}

	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 - motherShip.position).norm() < currentSpeed * delta * Constants.TOLERANCE_DISTANCE_FACTOR) {
			//unit arrived at target
			Requests.storeUnit(this);
			Requests.removeBird(motherShip, this);

			LauncherModuleData landingZone = motherShip.modules.getLauncherModule(this.getClass());
			Requests.setStore(GameController.storedFromID(this.getID()), landingZone);

			//TODO : remove ammo and transfert to storgae

			NetworkManager.addToLog("A " + getClass().getName() + " has landed on a " + motherShip.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;
	}

	public void setGPSGuided() {
		isGPSGuided = true;
	}

	public void addBird(DeployedUnit bird) {
		birds.Add(bird);
	}

	public void removeBird(int birdID) {
		birds.RemoveAll(x => x.getID() == birdID);
	}

	public List<DeployedUnit> getBirds() {
		return birds;
	}

	public void setMotherShip(DeployedUnit newMotherShip) {
		motherShip = newMotherShip;
	}

	public DeployedUnit getMotherShip() {
		return motherShip;
	}
}
