using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System;

/*
 * Core class of the game
*/
using System.IO;
using UnityEngine.Networking;
using UnityEngine.UI;
using UnityEngine.EventSystems;


public class GameController : MonoBehaviour {
    private static List<MapPolygon> map = new List<MapPolygon>();
    private static List<Country> countries = new List<Country>();
	private static List<DTerrain> terrains = new List<DTerrain> ();

    private static Set<TerrainVertex> terrainVertices = new Set<TerrainVertex>();
    private static Set<TerrainSegment> terrainSegments = new Set<TerrainSegment>();
    private static Set<TerrainTriangle> terrainTriangles = new Set<TerrainTriangle>();

	public static float minX = float.MaxValue; // min and max coordinates of the polygons in the svg file
	public static float maxX = float.MinValue;
	public static float minY = float.MaxValue;
	public static float maxY = float.MinValue;

	private static float currentScale = 1.0f;
	private static float gameSpeed = 1.0f; //1 sec IRL = gameSpeed sec in game
	private static bool pause = false;

	public static Material defaultMaterial;

	public Camera camera;

	public GameObject nonStaticDefaultUnit;
	public static GameObject defaultUnit;
	public GameObject nonStaticGridObject;
	public static GameObject gridObject;
	public GameObject nonStaticUnitInfoPanel;
	public static GameObject unitInfoPanel;
	public GameObject nonStaticSelectedUnitsContent;
	public static GameObject selectedUnitsContent;
	public GameObject nonStaticSelectedUnitToggle;
	public static GameObject selectedUnitToggle;
	public GameObject nonStaticInfoSelectedUnitContent;
	public static GameObject infoSelectedUnitContent;
	public GameObject nonStaticModulePanel;
	public static GameObject modulePanel;
	public GameObject nonStaticModulesContent;
	public static GameObject modulesContent;
	public GameObject nonStaticUnitCommandInput;
	public static GameObject unitCommandInput;

	public GameObject nonStaticGameSpeedText;
	public static GameObject gameSpeedText;
	public GameObject nonStaticPauseText;
	public static GameObject pauseText;

	private static List<Module> modules;
	private static List<Class> classes;
	private static List< Dictionary<Relation, Sprite> > sprites;
	private static List< Dictionary<Relation, Sprite> > selections;
	private static Dictionary<Relation, Material> relationMaterials;

	private static List<DeployedUnit> units;
	private static List<DeployedUnit> newUnits;
	private static List<DeployedUnit> destroyedUnits;
	private static List<StoredUnit> storedUnits;
	private static List<ModuleData> moduleDatas;
	private static List<Bullets> bulletsList;
	private static List<Bullets> destroyedBullets;

	private static List<DeployedUnit> selectedUnits;
	private static DeployedUnit infoSelectedUnit;

	private static Country playerCountry;

	private Vector3 oldMousePosition;

    // Use this for initialization
    void Start() {
		defaultMaterial = Resources.Load("Materials/Default", typeof(Material)) as Material;
		defaultUnit = nonStaticDefaultUnit;
		gridObject = nonStaticGridObject;
		unitInfoPanel = nonStaticUnitInfoPanel;
		selectedUnitsContent = nonStaticSelectedUnitsContent;
		selectedUnitToggle = nonStaticSelectedUnitToggle;
		infoSelectedUnitContent = nonStaticInfoSelectedUnitContent;
		modulePanel = nonStaticModulePanel;
		modulesContent = nonStaticModulesContent;
		unitCommandInput = nonStaticUnitCommandInput;

		gameSpeedText = nonStaticGameSpeedText;
		pauseText = nonStaticPauseText;

		createCountries();
		initTerrains ();

		List<MapPolygon> tempPoly = parseMap();
		genMap(tempPoly);

		VolumeTerrain radar = new VolumeTerrain();

		loadModules();
		loadClasses();
		loadSprites();

		foreach(Module module in modules) {
			module.updateClasses();
		}

		playerCountry = countries[1];

		units = new List<DeployedUnit>();
		newUnits = new List<DeployedUnit>();
		destroyedUnits = new List<DeployedUnit>();

		storedUnits = new List<StoredUnit>();
		moduleDatas = new List<ModuleData>();

		bulletsList = new List<Bullets>();
		destroyedBullets = new List<Bullets>();

		if (NetworkManager.isServer()) {
			units.Add(new DeployedUnit(classFromUniqueName("Arleigh Burke"), new Vertex(500000, -200000, 0)));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));
			units[0].addReadyToLaunch(new StoredUnit(classFromUniqueName("Harpoon")));

			units.Add(new DeployedUnit(classFromUniqueName("Luyang"), new Vertex(500000, -800000, 0)));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("AntiMissile")));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("AntiMissile")));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("AntiMissile")));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("AntiMissile")));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("AntiMissile")));
			units[1].addReadyToLaunch(new StoredUnit(classFromUniqueName("Chopper")));
			units[1].addToStorage(new StoredUnit(classFromUniqueName("Harpoon")));
			units[1].addToStorage(new StoredUnit(classFromUniqueName("AntiMissile")));
		}


		selectedUnits = new List<DeployedUnit>();
    }

    // Update is called once per frame
    void Update() {
		//Set focus to command
		EventSystem.current.SetSelectedGameObject(unitCommandInput, null);
		unitCommandInput.GetComponent<InputField>().OnPointerClick(new PointerEventData(EventSystem.current));
		unitCommandInput.GetComponent<InputField>().text = unitCommandInput.GetComponent<InputField>().text.Replace("+", "").Replace("-", "");

		//Inputs

        if (Input.GetMouseButtonDown(1)) {
			RaycastHit hit; 
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 
			if (! Physics.Raycast (ray, out hit, float.MaxValue, (int)Mathf.Pow(2, 10))) { //check if right click is not on object
				Vector2 clickLocation = Camera.main.ScreenToWorldPoint(Input.mousePosition);
				GameVertex endPoint = new GameVertex(clickLocation);


				foreach(DeployedUnit unit in selectedUnits) {
					Requests.moveUnit(unit, Converter.gameCooToMeters(endPoint, 0));
				}
			}
        }

		if (Input.GetMouseButton(2)) {
			camera.transform.position -= (Camera.main.ScreenToWorldPoint(Input.mousePosition) - oldMousePosition);
		}

		if (Input.GetAxis("Zoom") != 0.0f) {
			float amount = (1 - Input.GetAxis("Zoom") * Constants.ZOOM_SPEED);

			camera.orthographicSize *= amount;

			camera.transform.position -= (Camera.main.ScreenToWorldPoint(Input.mousePosition) - oldMousePosition);

			DeployedUnit.staticScale(amount);
			foreach(DeployedUnit unit in units) {
				unit.scaleSprite(amount);
			}

			currentScale *=amount;
		}

		if (unitCommandInput.GetComponent<InputField>().text == "") {
			camera.transform.position += new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * Constants.CAMERA_SPEED;
		}

		oldMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);

		if (Input.GetButtonDown("Submit")) {
			submitOrder();
		}

		if (Input.GetButtonDown("SpeedUp")) {
			if (gameSpeed < 20) {
				Requests.changeGameSpeed(gameSpeed + 1);
			}
		}

		if (Input.GetButtonDown("SpeedDown")) {
			if (gameSpeed > 1) {
				Requests.changeGameSpeed(gameSpeed - 1);
			}
		}

		if (unitCommandInput.GetComponent<InputField>().text.Trim() == "" && Input.GetButtonDown("Pause")) {
			Requests.changePause(!pause);
			unitCommandInput.GetComponent<InputField>().text = "";
		}

		//Updates

		if (!pause) {
			foreach(DeployedUnit unit in units) {
				unit.move(Time.deltaTime * gameSpeed);

				if (NetworkManager.isServer()) {
					unit.updateFireMissiles();
					unit.updateLaunches();
					unit.checkPathOutdated();
					unit.checkMissile(Time.deltaTime * gameSpeed);
					unit.checkLanding(Time.deltaTime * gameSpeed);
					unit.checkAutoDef(Time.deltaTime * gameSpeed);
					unit.checkHull();
				}
			}

			if (NetworkManager.isServer()) {
				foreach(Bullets bullets in bulletsList) {
					if (bullets.hasArrived(Time.deltaTime * gameSpeed)) {
						bullets.dealDamage();
						destroyedBullets.Add(bullets);
					}
				}
			}


			//detection
			foreach(DeployedUnit unit in units) {
				unit.resetVisibility();
			}
			foreach(DeployedUnit unit in units) {
				if (NetworkManager.isServer() || unit.getClass().getCountry().getRelation(playerCountry) == Relation.Player) {
					unit.detectUnits();
				}
			}
			foreach(DeployedUnit unit in units) {
				unit.switchVisibility();
			}
		}

		foreach(DeployedUnit unit in units) {
			unit.updateVisibility();
			unit.updatePathLine();
			unit.updateDirectionLine();
			unit.updateTargetLine();
			unit.updateSprite();
		}

		if (infoSelectedUnit != null) {
			updateInfoSelectedUnitPanel();
			updateModulesInfo();
		}

		if (NetworkManager.isServer()) {
			foreach(DeployedUnit unit in units) {
				if (!pause) {
					unit.updateModulesTime(Time.deltaTime * gameSpeed);
				}

				unit.updateModulesState();
			}
		}

		units.AddRange(newUnits);
		newUnits.Clear();

		foreach(DeployedUnit unit in destroyedUnits) {
			units.Remove(unit);

			unit.destroy();

			if (selectedUnits.Contains(unit)) {
				selectedUnits.Remove(unit);

				updateAfterSelectionChange();
			}

			if (infoSelectedUnit == unit) {
				infoSelectedUnit = null;
			}
		}
		destroyedUnits.Clear();

		foreach(Bullets bullets in destroyedBullets) {
			bulletsList.Remove(bullets);
		}
		destroyedBullets.Clear();


		foreach(DeployedUnit unit in units) {
			if (unit.getTarget() != null && unit.getTarget().getVisibility(playerCountry) == Visibility.Invisible) { //target is invisible
				Requests.setNullTarget(unit);
			}

			if (unit.getVisibility(playerCountry) == Visibility.Invisible) {
				if (selectedUnits.Contains(unit)) {
					selectedUnits.Remove(unit);

					updateAfterSelectionChange();
				}

				if (infoSelectedUnit == unit) {
					infoSelectedUnit = null;
				}
			}
		}

		if (NetworkManager.isServer()) {
			foreach(DeployedUnit unit in units) {
				if (unit.getTarget() != null && !units.Contains(unit.getTarget())) { //target has been destroyed
					Requests.setNullTarget(unit);
				}
			}
		}

		updateGrid();
    }

	void updateGrid() {
		//Delete children (ie existing grid elements)
		List<GameObject> children = new List<GameObject>();
		foreach (Transform child in transform)  {
			children.Add(child.gameObject); 
		}

		foreach (GameObject child in children) {
			Destroy(child);
		}
			
		float size= (int)Mathf.Pow(10, ((int)Mathf.Log10(5000000 * currentScale)));

		float alpha = Mathf.Log10(5000000 * currentScale) - (int)Mathf.Log10(5000000 * currentScale);

		addGridPoints(size, alpha);
		if (size >= 10) {
			addGridPoints(size / 10, (1 - alpha));
		}
	}

	private void addGridPoints(float size, float alpha) {
		float width = Converter.gameCooToMeters((Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, 0)) - Camera.main.ScreenToWorldPoint(new Vector2(0, 0))).x); //width in m
		float height = Converter.gameCooToMeters((Camera.main.ScreenToWorldPoint(new Vector2(0, Screen.height)) - Camera.main.ScreenToWorldPoint(new Vector2(0, 0))).y); //height in m

		float startPositionX = Converter.gameCooToMeters(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).x);
		float startPositionY = Converter.gameCooToMeters(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).y);

		int maxPoints = 10000;
		int count = 0;
		for (float x = ((int)(startPositionX / size)) * size; x < startPositionX + width; x += size) {
			
			if (count > maxPoints) {
				break;
			}
			for (float y = ((int)(startPositionY / size)) * size; y < startPositionY + height; y += size) {
				if (count > maxPoints) {
					break;
				}

				GameVertex gridPosition = Converter.metersToGameCoo(new Vertex(x, y, 0));
				if (isOutsideMap(gridPosition)) {
					continue;
				}

				GameObject clone = GameObject.Instantiate(gridObject, transform);

				clone.transform.position = gridPosition.getVector(-1.0f);
				clone.transform.localScale *= currentScale;
				clone.GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, alpha);

				count++;
			}
			count++;
			if (count > maxPoints) {
				break;
			}
		}
	}

	// Init methods
	void initTerrains() {
		terrains.Add(new DTerrain(-100,
			"#3b498b",
			Resources.Load("Materials/Water", typeof(Material)) as Material));

		terrains.Add(new DTerrain(0,
			"#b7b42d",
			Resources.Load("Materials/Water", typeof(Material)) as Material));

		terrains.Add(new DTerrain(100,
			"#2db78b",
			Resources.Load("Materials/Grass", typeof(Material)) as Material));

		terrains.Add(new DTerrain(200,
			"#35a481",
			Resources.Load("Materials/Mountains", typeof(Material)) as Material));
	}

    void createCountries() {
        Country usa = new Country("USA");
        countries.Add(usa);

		Country china = new Country("China");
		countries.Add(china);
    }

    //Parsing of the svg
    public List<MapPolygon> parseMap() {
        List<MapPolygon> list = new List<MapPolygon>();

        char[] space = { ' ' };
        char[] comma = { ',' };
        char[] semiColon = { ';' };

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load("Assets/Resources/Maps/map2.svg");

        XmlNode svg = xmlDoc.GetElementsByTagName("svg")[0];
        XmlNode g = svg["g"];
        for (int i = 0; i < g.ChildNodes.Count; i++) {
            String path = g.ChildNodes[i].Attributes["d"].InnerText; //polygon

            ContourBuilder contourBuilder = new ContourBuilder();
            String[] points = path.Split(space);
            float x = 0.0f;
            float y = 0.0f;
			bool isCurve = false;
			int curveCount = 0;
            for (int j = 0; j < points.Length; j++) {
                if (points[j].Contains(",")) {

					if (isCurve) {
						curveCount ++;
						if (curveCount % 3 != 0) {
							continue;
						}
					}

                    String[] coo = points[j].Split(comma);

                    x += float.Parse(coo[0]);
                    y += -float.Parse(coo[1]);
					contourBuilder.addVertex(Converter.metersToGameCoo(new Vertex(x * 1000, y * 1000, 0)));
                } 
				else if (points[j] == "L") {
                    x = float.Parse(points[++j]);
                    y = -float.Parse(points[++j]);
					contourBuilder.addVertex(Converter.metersToGameCoo(new Vertex(x * 1000, y * 1000, 0)));
                }
				else if (points[j] == "c") {
					isCurve = true;
					curveCount = 0;
				}
            }

            for (int j = 0; j < contourBuilder.count(); j++) {
                x = contourBuilder.pointAt(j).x();
                y = contourBuilder.pointAt(j).y();

                if (x < minX) {
                    minX = x;
                }
                if (x > maxX) {
                    maxX = x;
                }

                if (y < minY) {
                    minY = y;
                }
                if (y > maxY) {
                    maxY = y;
                }
            }

            String style = g.ChildNodes[i].Attributes["style"].InnerText; // color 
            String[] styles = style.Split(semiColon);

            MapPolygon p = null;

            for (int j = 0; j < styles.Length; j++) {
                if (styles[j].StartsWith("fill:")) {
                    String color = styles[j].Remove(0, "fill:".Length);

                    for (int k = 0; k < terrains.Count; k++) {
                        if (terrains[k].getColor() == color) {
                            
                            p = new MapPolygon(contourBuilder.build(), terrains[k]);
                        }
                    }
                }
            }

            if (p == null) { //in case the fill argument is not present / the color is invalid
                p = new MapPolygon(contourBuilder.build(), terrains[0]);
            }

            list.Add(p);
        }

        return list;
    }

	private void loadModules() {
		modules = new List<Module>();

		modules.AddRange(HullModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Hulls", "*.damodule")));
		modules.AddRange(EngineModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Engines", "*.damodule")));
		modules.AddRange(LauncherModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Launchers", "*.damodule")));
		modules.AddRange(GuidanceModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Guidance", "*.damodule")));
		modules.AddRange(WarheadModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Warheads", "*.damodule")));
		modules.AddRange(CIWSModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/CIWS", "*.damodule")));
		modules.AddRange(FuelTankModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/FuelTanks", "*.damodule")));
		modules.AddRange(DetectionModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Detection", "*.damodule")));
		modules.AddRange(CommandModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Commands", "*.damodule")));
		modules.AddRange(StorageModule.loadModules(Directory.GetFiles("Assets/Resources/Data/Modules/Storage", "*.damodule")));
	}

	private void loadClasses() {
		classes = new List<Class>();

		classes.AddRange(Class.loadClasses(Directory.GetFiles("Assets/Resources/Data/Classes/Missiles", "*.daclass"), ClassType.Missile));
		classes.AddRange(Class.loadClasses(Directory.GetFiles("Assets/Resources/Data/Classes/Ships", "*.daclass"), ClassType.Ship));
		classes.AddRange(Class.loadClasses(Directory.GetFiles("Assets/Resources/Data/Classes/Helicopters", "*.daclass"), ClassType.Helicopter));
	}

	private void loadSprites() {
		sprites = new List< Dictionary<Relation, Sprite> >();
		sprites.Add(new Dictionary<Relation, Sprite>());
		sprites[0].Add(Relation.Player, Resources.Load("Sprites/Units/Stack/player", typeof(Sprite)) as Sprite);
		sprites[0].Add(Relation.Ennemy, Resources.Load("Sprites/Units/Stack/ennemy", typeof(Sprite)) as Sprite);
		sprites[0].Add(Relation.Multiple, Resources.Load("Sprites/Units/Stack/multiple", typeof(Sprite)) as Sprite);

		sprites.Add(new Dictionary<Relation, Sprite>());
		sprites[1].Add(Relation.Player, Resources.Load("Sprites/Units/Navy/player", typeof(Sprite)) as Sprite);
		sprites[1].Add(Relation.Ennemy, Resources.Load("Sprites/Units/Navy/ennemy", typeof(Sprite)) as Sprite);

		sprites.Add(new Dictionary<Relation, Sprite>());
		sprites[2].Add(Relation.Player, Resources.Load("Sprites/Units/Planes/player", typeof(Sprite)) as Sprite);
		sprites[2].Add(Relation.Ennemy, Resources.Load("Sprites/Units/Planes/ennemy", typeof(Sprite)) as Sprite);


		selections = new List< Dictionary<Relation, Sprite> >();
		selections.Add(new Dictionary<Relation, Sprite>());
		selections[0].Add(Relation.Player, Resources.Load("Sprites/Selections/Navy/player", typeof(Sprite)) as Sprite);
		selections[0].Add(Relation.Ennemy, Resources.Load("Sprites/Selections/Navy/ennemy", typeof(Sprite)) as Sprite);

		selections.Add(new Dictionary<Relation, Sprite>());
		selections[1].Add(Relation.Player, Resources.Load("Sprites/Selections/Planes/player", typeof(Sprite)) as Sprite);
		selections[1].Add(Relation.Ennemy, Resources.Load("Sprites/Selections/Planes/ennemy", typeof(Sprite)) as Sprite);


		relationMaterials = new Dictionary<Relation, Material>();
		relationMaterials.Add(Relation.Player, Resources.Load("Materials/RelationsMaterials/Player", typeof(Material)) as Material);
		relationMaterials.Add(Relation.Ennemy, Resources.Load("Materials/RelationsMaterials/Ennemy", typeof(Material)) as Material);
	}

    //Generates the map based on the simple polygons (that can be overlapping) of the svg file
    public void genMap(List<MapPolygon> mapPolygons) {
        List<SlicedPolygon> remains = new List<SlicedPolygon>(mapPolygons.Count); // one SlicedPolygon per original poly

        foreach (MapPolygon mapPolygon in mapPolygons) { // init
            remains.Add(mapPolygon.getSlicedPolygon());
        }

        for (int i = 0; i < mapPolygons.Count; i++) { // add holes

            //remove from poly, all other poly that are above (<=> with a greater index)
            remains[i] = remains[i].difference(i + 1, remains);
        }

        // At this point, no polygons are overlapping

        for (int i = 0; i < mapPolygons.Count; i++) {
            MapPolygon mapPoly = new MapPolygon(remains[i].cutLongEdges(), mapPolygons[i].getTerrain());
            mapPoly.addToGame();
            map.Add(mapPoly);

            mapPoly.triangulate(terrainVertices, terrainSegments, terrainTriangles);
        }
    }

    public static List<DTerrain> getTerrains() {
        return terrains;
    }

	public static Set<TerrainTriangle> getTerrainTriangles() {
		return terrainTriangles;
	}

	public static List<MapPolygon> getMap() {
		return map;
	}

	public static bool isOutsideMap(GameVertex vertex) {
		return vertex.x() < minX || vertex.x() > maxX || vertex.y() < minY || vertex.y() > maxY;
	}

    public static DTerrain getTerrainAt(GameVertex p) {
        foreach (MapPolygon mapPolygon in map) {
            if (mapPolygon.isPointInside(p)) {
                return mapPolygon.getTerrain();
            }
        }

        return terrains[0];
    }

	public static float altitudeAt(GameVertex p) {
		MapPolygon location = null;
		foreach (MapPolygon mapPolygon in map) {
			if (mapPolygon.isPointInside(p)) {
				location = mapPolygon;
			}
		}

		if (location == null) {
			return 0.0f;
		}
		else{
			return location.getTerrain().getAltitude(); //temporary
		}

		int index = 0;

		for (int i = 0; i < terrains.Count; i++) {
			if (terrains[i] == location.getTerrain()) {
				index = i;
				break;
			}
		}

		if (index == terrains.Count - 1) {
			return location.getTerrain().getAltitude();
		}

		DTerrain upperTerrain = terrains[index + 1];
		float minDistUpper = float.MaxValue;

		foreach (MapPolygon mapPolygon in map) {
			if (mapPolygon.getTerrain() == upperTerrain) {
				float dist = mapPolygon.getSlicedPolygon().distanceTo(p);
				if (dist < minDistUpper) {
					minDistUpper = dist;
				}
			}
		}

		//Debug.Log(minDistUpper);

		if (minDistUpper >= Constants.MAX_DISTANCE_SLOPE) {
			return location.getTerrain().getAltitude();
		}

		float coeff = (Constants.MAX_DISTANCE_SLOPE - minDistUpper) / Constants.MAX_DISTANCE_SLOPE;

		return coeff * upperTerrain.getAltitude() + (1 - coeff) * location.getTerrain().getAltitude();
	}

	public static float altitudeAt(GameVertex p, PathConstraints constraints) {
		float alt = altitudeAt(p);

		if (alt < 0 && constraints.isSurface()) {
			return 0.0f;
		}
		else {
			return alt;
		}
	}

    public static Set<TerrainVertex> getTerrainVertices() {
        return terrainVertices;
    }

	public static Sprite getSprite(int num, Visibility visibility, Relation relation, bool isStacked, bool differentRelations) {
		if (visibility == Visibility.Invisible) {
			return null;
		}

		int index;

		if (!isStacked) {
			index = num;
		}
		else {
			index = 0;

			if (differentRelations) {
				return sprites[index][Relation.Multiple];
			}
		}

		if (visibility == Visibility.Unknown) {
			return sprites[index][Relation.Unknown];
		}

		return sprites[index][relation];
	}

	public static Sprite getSelection(int num, Visibility visibility, Relation relation) {
		if (visibility == Visibility.Invisible) {
			return null;
		}

		if (visibility == Visibility.Unknown) {
			return selections[num-1][Relation.Unknown];
		}

		return selections[num-1][relation];
	}

	public static Material getRelationMaterial(Relation relation) {
		return relationMaterials[relation];
	}

	public static Country getPlayerCountry() {
		return playerCountry;
	}

	public static List<Country> getCountries() {
		return countries;
	}

	public static GameObject getDefaultUnit() {
		return defaultUnit;
	}

	public static List<DeployedUnit> getDeployedUnits() {
		return units;
	}

	public static List<StoredUnit> getStoredUnits() {
		return storedUnits;
	}

	public static List<ModuleData> getModuleDatas() {
		return moduleDatas;
	}

	public static List<Class> getClasses() {
		return classes;
	}

	// UI

	public static void leftClick(DeployedUnit unit) {
		if (selectedUnits.Contains(unit)) {
			selectedUnits.Remove(unit);
			unit.setSelected(false);
		}
		else {
			foreach (DeployedUnit otherUnit in selectedUnits) {
				otherUnit.setSelected(false);
			}
			selectedUnits.Clear();

			selectedUnits.Add(unit);
			unit.setSelected(true);
		}

		updateAfterSelectionChange();
	}

	public static void rightClick(DeployedUnit unit) {
		foreach (DeployedUnit otherUnit in selectedUnits) {
			Requests.changeTarget(otherUnit, unit);
		}
	}

	private static void updateAfterSelectionChange() {
		unitInfoPanel.SetActive(selectedUnits.Count > 0);

		if (selectedUnits.Count == 0) {
			infoSelectedUnit = null;
		}
		else {
			modulesContent.GetComponent<RectTransform>().sizeDelta = new Vector2(GameObject.Find("Info&Modules").GetComponent<RectTransform>().rect.width - 10, 10);
		}

		if ((infoSelectedUnit == null || !selectedUnits.Contains(infoSelectedUnit)) && selectedUnits.Count > 0) {
			infoSelectedUnit = selectedUnits[0];
		}

		updateSelectedUnitsPanel();
		updateInfoSelectedUnitPanel();
	}

	public void selectedUnitToggleChanged(bool value) {
		foreach(Toggle toggle in nonStaticSelectedUnitsContent.GetComponentsInChildren<Toggle>()) {
			if (toggle.isOn) {
				infoSelectedUnit = toggle.GetComponentInParent<SelectedUnitBehavior>().getSelectedUnit();
			}
		}
	}

	public void submitOrder() {
		if (infoSelectedUnit == null) {
			return;
		}

		Requests.unitOrder(infoSelectedUnit, unitCommandInput.GetComponent<InputField>().text);
	}

	public static void addDeployedUnit(DeployedUnit deployed) {
		newUnits.Add(deployed);
	}

	public static void destroyUnit(DeployedUnit unit) {
		destroyedUnits.Add(unit);
	}

	public static void addStoredUnit(StoredUnit stored) {
		storedUnits.Add(stored);
	}

	public static void removedStoredUnit(StoredUnit stored) {
		storedUnits.Remove(stored);
	}

	public static void addModuleData(ModuleData data) {
		moduleDatas.Add(data);
	}

	public static void removeModuleData(ModuleData data) {
		moduleDatas.Remove(data);
	}

	public static void addBullets(Bullets bullets) {
		bulletsList.Add(bullets);
	}

	public static void setGameSpeed(float newGameSpeed) {
		gameSpeed = newGameSpeed;
		gameSpeedText.GetComponent<Text>().text = "x " + (int)gameSpeed;
	}

	public static void setPause(bool value) {
		pause = value;
		pauseText.SetActive(pause);
	}

	private static void updateSelectedUnitsPanel() {

		//Delete children
		List<GameObject> children = new List<GameObject>();
		foreach (Transform child in selectedUnitsContent.transform)  {
			children.Add(child.gameObject); 
		}

		foreach (GameObject child in children) {
			Destroy(child);
		}

		for (int i = 0; i < selectedUnits.Count; i++) {
			GameObject clone = GameObject.Instantiate(selectedUnitToggle, selectedUnitsContent.transform);

			clone.transform.FindChild("UnitName").gameObject.GetComponent<Text>().text = selectedUnits[i].getClass().getName();

			//temporary
			clone.transform.FindChild("Background").gameObject.GetComponent<Image>().sprite = selectedUnits[i].getClass().getSprite(Visibility.Visible, selectedUnits[i].getOwner().getRelation(GameController.getPlayerCountry()), false, false);
			clone.transform.FindChild("Background").FindChild("Checkmark").gameObject.GetComponent<Image>().sprite = selectedUnits[i].getClass().getSelection(Visibility.Visible, selectedUnits[i].getOwner().getRelation(GameController.getPlayerCountry()));

			clone.GetComponent<SelectedUnitBehavior>().setSelectedUnit(selectedUnits[i]);
			clone.GetComponent<Toggle>().isOn = (selectedUnits[i] == infoSelectedUnit);

		}

		constructModulesInfo();
	}

	private static void updateInfoSelectedUnitPanel() {
		if (infoSelectedUnit == null) {
			return;
		}

		infoSelectedUnitContent.transform.FindChild("SpeedText").GetComponent<Text>().text = "Current speed : " + infoSelectedUnit.getcurrentSpeed() + " m/s";
		infoSelectedUnitContent.transform.FindChild("AltitudeText").GetComponent<Text>().text = "Current altitude : " + infoSelectedUnit.getPosition().z() + " m";
	}

	public static void constructModulesInfo() {
		if (infoSelectedUnit == null) {
			return;
		}

		//Delete children
		List<GameObject> children = new List<GameObject>();
		foreach (Transform child in modulesContent.transform)  {
			children.Add(child.gameObject); 
		}

		foreach (GameObject child in children) {
			Destroy(child);
		}

		foreach(Tuple<Module, ModuleData> module in infoSelectedUnit.getModules()) {
			GameObject clone = GameObject.Instantiate(modulePanel, modulesContent.transform);

			clone.GetComponent<ModuleBehavior>().setModule(module);
		}
	}

	public static void updateModulesInfo() {
		foreach (Transform child in modulesContent.transform)  {
			Tuple<Module, ModuleData> module = child.gameObject.GetComponent<ModuleBehavior>().getModule();

			Color color = Util.getColorFromValue(module._2.getHealth() / module._1.getHealth());
			color.a = 0.5f;

			child.gameObject.GetComponent<Image>().color = color;

			child.FindChild("Image").gameObject.GetComponent<Image>().sprite = module._1.getSprite();

			child.FindChild("Image").FindChild("Name").GetComponent<Text>().text = module._1.getName();

			child.FindChild("Image").FindChild("Destroyed").gameObject.SetActive(module._2.isDestroyed());
			child.FindChild("Image").FindChild("Firing").gameObject.SetActive(module._2.isFiring() && (Time.frameCount % 30) < 15);
			child.FindChild("Image").FindChild("Disabled").gameObject.SetActive(module._2.isDisabled() && !module._2.isDestroyed());

			if (module._2 is FuelTankModuleData && ((FuelTankModuleData)module._2).getFuelLeft() == 0.0f) {
				child.FindChild("Image").FindChild("Important").gameObject.SetActive(!module._2.isDestroyed());
				child.FindChild("Image").FindChild("Important").FindChild("Text").GetComponent<Text>().text = "EMPTY";
			}
			if (module._2 is EngineModuleData && !infoSelectedUnit.hasFuelLeft(((EngineModule)module._1).getFuelType())) {
				child.FindChild("Image").FindChild("Important").gameObject.SetActive(!module._2.isDestroyed());
				child.FindChild("Image").FindChild("Important").FindChild("Text").GetComponent<Text>().text = "NO FUEL";
			}

			child.FindChild("Power").gameObject.SetActive(module._1.canBeDisabled() && !module._2.isDestroyed());
			//clone.transform.FindChild("HealthBar").FindChild("RemainingHealth").GetComponent<RectTransform>().localScale = new Vector3(module._2.getHealth() / module._1.getHealth(), 1, 1);
			//clone.transform.FindChild("HealthBar").FindChild("RemainingHealth").GetComponent<RawImage>().color = Util.getColorFromValue(module._2.getHealth() / module._1.getHealth());
		}
	}

	public void buttonDisableModule(Button button) {
		Requests.disableModule(button.GetComponentInParent<ModuleBehavior>().getModule()._2, !button.GetComponentInParent<ModuleBehavior>().getModule()._2.isDisabled());
	}

	// Network

	public static Class classFromUniqueName(string uniqueName) {
		foreach(Class clas in classes) {
			if (clas.getName() == uniqueName) {
				return clas;
			}
		}

		return null;
	}

	public static Class classFromUniqueNameLower(string uniqueName) {
		foreach(Class clas in classes) {
			if (clas.getName().ToLower() == uniqueName.ToLower()) {
				return clas;
			}
		}

		return null;
	}

	public static DeployedUnit fromID(int unitID) {
		foreach(DeployedUnit unit in units) {
			if (unit.getID() == unitID) {
				return unit;
			}
		}

		foreach(DeployedUnit unit in newUnits) {
			if (unit.getID() == unitID) {
				return unit;
			}
		}

		return null;
	}

	public static StoredUnit storedFromID(int unitID) {
		foreach(StoredUnit unit in storedUnits) {
			if (unit.getID() == unitID) {
				return unit;
			}
		}

		return null;
	}

	public static Unit unitFromID(int unitID) {
		DeployedUnit deployed = GameController.fromID(unitID);
		if (deployed != null) {
			return deployed;
		}

		StoredUnit stored = GameController.storedFromID(unitID);
		return stored; // return even if null (the unit must have been destroyed)
	}

	public static ModuleData moduleDataFromID(int moduleDataID) {
		foreach(ModuleData data in moduleDatas) {
			if (data.getID() == moduleDataID) {
				return data;
			}
		}

		return null;
	}

	public static Module moduleFromUniqueName(string uniqueName) {
		foreach(Module module in modules) {
			if (module.getUniqueName() == uniqueName) {
				return module;
			}
		}

		return null;
	}
}
