using System.Collections.Generic;
using UnityEngine;

public class UnitModules {
	private int parent;
	private List<Tuple<Module, ModuleData>> modules;

	public UnitModules(int parent, Class unitClass, bool constructModules = true) {
		this.parent = parent;

		modules = new List<Tuple<Module, ModuleData>>();

		if (constructModules) {
			foreach(Module module in unitClass.getModules()) {
				modules.Add(new Tuple<Module, ModuleData>(module, module.getData(parent)));
			}
		}
	}

	//SHOULD ONLY BE CALLED BY NETWORK MANAGER, WHEN RECEIVING A CREATE MODULE DATA COMMAND
	public void addModuleData(ModuleData data) {
		modules.Add(new Tuple<Module, ModuleData>(data.getTemplate(), data));
	}

	public List<Tuple<Module, ModuleData>> getModules() {
		return modules;
	}

	public void addReadyToLaunch(StoredUnit stored) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is LauncherModule) {
				Requests.setStore(stored, module._2);
				return;
			}
		}
	}

	public void store(StoredUnit stored) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is LauncherModule || module._1 is StorageModule) {
				Requests.setStore(stored, module._2);
				return;
			}
		}
	}

	public void addToStorage(StoredUnit stored) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is StorageModule) {
				Requests.setStore(stored, module._2);
				return;
			}
		}
	}

	public int getNumberStored(Class unitClass, bool immediate = false) {
		int total = 0;

		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData && module._2.isOperationnal()) {
				LauncherModuleData launcherData = (LauncherModuleData)module._2;

				total += launcherData.getNumberReadyToLaunch(unitClass);
			}
			else if (!immediate && module._2 is StorageModuleData && module._2.isOperationnal()) {
				StorageModuleData storageData = (StorageModuleData)module._2;

				total += storageData.getNumberStored(unitClass);
			}
		}

		return total;
	}

	public bool canLaunch(Class unitClass, int number, Loadout loadout = null, bool immediate = false) {
		int total = 0;

		if (getNumberStored(unitClass, immediate) < number) {
			return false; //not enough vehicle to launch
		}

		if (loadout == null) {
			return true;
		}

		//Check loadout ammo
		foreach(KeyValuePair<Class, int> pair in loadout.getAmmo()) {
			if (getNumberStored(pair.Key) < pair.Value * number) {
				return false; //not enough ammo for each vehicle
			}
		}

		return true;
	}

	public bool canBeLandedOn(DeployedUnit unit) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData && module._2.isOperationnal()) {
				LauncherModule launcher = (LauncherModule)module._1;

				if (launcher.canLaunch(unit.getClass())) {
					return true;
				}
			}
		}

		return false;
	}

	public bool tryToFire(Class missileClass, DeployedUnit target) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData && module._2.isOperationnal()) {
				LauncherModuleData launcherData = (LauncherModuleData)module._2;

				if (launcherData.tryToFire(missileClass, target)) {
					return true;
				}
			}
		}

		return false;
	}

	public bool tryToLaunch(StoredUnit unit) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData && module._2.isOperationnal()) {
				if (((LauncherModuleData)module._2).tryToLaunch(unit)) {
					return true;
				}
			}
		}

		return false;
	}

	public StoredUnit prepareLoading(Class unitClass) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData && module._2.isOperationnal()) {
				StoredUnit loadingUnit = ((LauncherModuleData)module._2).prepareLoading(unitClass);
				if (loadingUnit != null) {
					return loadingUnit;
				}
			}
			else if (module._2 is StorageModuleData && module._2.isOperationnal()) {
				StoredUnit loadingUnit = ((StorageModuleData)module._2).prepareLoading(unitClass);
				if (loadingUnit != null) {
					return loadingUnit;
				}
			} 
		}

		return null;
	}

	public int loadInto(StoredUnit unit, Class ammoType, int count) {
		int loaded = 0;

		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is StorageModuleData && module._2.isOperationnal()) {
				loaded += ((StorageModuleData)module._2).loadInto(unit, ammoType, count - loaded);

				if (loaded == count) {
					return loaded;
				}
			} 
		}

		return loaded;
	}

	public void setNewStore(int storedUnitID, int newStoreID) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2 is LauncherModuleData) {
				((LauncherModuleData)module._2).removeFromReadyIfExists(newStoreID);
			}
			else if (module._2 is StorageModuleData) {
				((StorageModuleData)module._2).removeFromStoredIfExists(newStoreID);
			}
		}

		Debug.LogError("This unit doesn't contain any valid module with this id");
	}

	public void deployUnit(int moduleDataID, int storedUnitID) { 
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._2.getID() == moduleDataID && module._2.isOperationnal()) {
				module._2.deployUnit(storedUnitID);
				return;
			}
		}

		Debug.LogError("This unit doesn't contain any valid module with this id");
	}

	public float getMaxSpeed() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getMaxSpeed();
				}
			}
		}

		return 0.0f;
	}

	public float getMinSpeed() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) { //maybe change later
					return ((EngineModule)module._1).getMinSpeed();
				}
			}
		}

		return 0.0f;
	}

	public float getAcceleration() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getAcceleration();
				}
			}
		}

		return 0.0f;
	}

	public float getDeceleration() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule) {
				return ((EngineModule)module._1).getAcceleration();
			}
		}

		return 0.0f;
	}

	public float getAngularSpeed() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getAngularSpeed();
				}
			}
		}

		return 0.0f;
	}

	public float getVerticalAngleRange() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getVerticalAngleRange();
				}
			}
		}

		return 0.0f;
	}

	public float getCruiseAltitude() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getCruiseAltitude();
				}
			}
		}

		return 0.0f;
	}

	public float getMinAltitude() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getMinAltitude();
				}
			}
		}

		return 0.0f;
	}

	public float getMaxAltitude() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				if (getFuelAmount(((EngineModule)module._1).getFuelType()) > 0.0f) {
					return ((EngineModule)module._1).getMaxAltitude();
				}
			}
		}

		return 0.0f;
	}

	public MissileTechnique getMissileTechnique() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is GuidanceModule && module._2.isOperationnal()) {
				return ((GuidanceModule)module._1).getMissileTechnique();
			}
		}

		return MissileTechnique.SeaSkimming;
	}

	public List<Tuple<float, float>> getLandingPattern() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is CommandModule && module._2.isOperationnal()) {
				return ((CommandModule)module._1).getLandingPattern();
			}
		}

		return new List<Tuple<float, float>>();
	}

	public float getFailProbability(float delta) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is GuidanceModule && module._2.isOperationnal()) {
				return ((GuidanceModule)module._1).getFailProbability(delta);
			}
		}

		return 1.0f;
	}

	public float getDamage() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is WarheadModule && module._2.isOperationnal()) {
				return ((WarheadModule)module._1).getDamage();
			}
		}

		return 0.0f;
	}

	public float getCIWSRange() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is CIWSModule && module._2.isOperationnal()) {
				return ((CIWSModule)module._1).getRange();
			}
		}

		return 0.0f;
	}

	public float getFuelAmount(FuelType fuelType) {
		float sum = 0.0f;

		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is FuelTankModule && module._2.isOperationnal() && ((FuelTankModule)module._1).getFuelType() == fuelType) {
				sum += ((FuelTankModuleData)module._2).getFuelLeft();
			}
		}

		return sum;
	}
		
	public void addDamage(float amount) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			module._2.addDamage(amount);
		}
	}

	public void updateModulesTime(float delta) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			module._2.updateTime(delta);
		}
	}

	public void updateModulesState() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			module._2.updateState();
		}
	}

	public bool isHullDead() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is HullModule && !module._2.isOperationnal()) {
				return true;
			}
		}

		return false;
	}

	public bool hasRadarOn() {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is DetectionModule && module._2.isOperationnal()) {
				return true;
			}
		}

		return false;
	}

	public void consumeFuel(float delta) {
		//Temporary, always assume that all engines at are 100 %  

		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is EngineModule && module._2.isOperationnal()) {
				removeFuel(((EngineModule)module._1).getFuelType(), ((EngineModule)module._1).getFuelConsumption() * delta);
			}
		}
	}

	private void removeFuel(FuelType fuelType, float amount) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is FuelTankModule && module._2.isOperationnal() && ((FuelTankModule)module._1).getFuelType() == fuelType) {
				float leftInTank = ((FuelTankModuleData)module._2).getFuelLeft();

				if (leftInTank < amount) {
					amount -= leftInTank;
					((FuelTankModuleData)module._2).setFuelLeft(0.0f);
				}
				else {
					((FuelTankModuleData)module._2).setFuelLeft(leftInTank - amount);
					return;
				}
			}
		}
	}

	public void detectUnits() {
		//When detect units is called, then the unit is deployed
		DeployedUnit parentUnit = GameController.fromID(parent);

		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is DetectionModule && module._2.isOperationnal()) {

				foreach (DeployedUnit unit in GameController.getDeployedUnits()) {
					

					if (((DetectionModule)module._1).canDetect(unit.getClass().getClassType()) && unit.getNewVisibility(parentUnit.getClass().getCountry()) != Visibility.Visible) { //useless to detect if already visible
						Vertex position = parentUnit.getPosition();
						GameVertex gamePosition = Converter.metersToGameCoo(position);

						Vector3 direction = (unit.getPosition() - position).normalized().getValue();
						float distance = (unit.getPosition() - position).norm();

						DetectionModule mod = ((DetectionModule)module._1);

						if (!Physics.Raycast(new Vector3(gamePosition.getVector().x, gamePosition.getVector().y, position.z()), direction, Converter.metersToGameCoo(distance), (int)Mathf.Pow(2, 2))) {
							if (distance <= mod.getRange()) {
								float val = Mathf.Pow(1 - distance / mod.getRange(), 0.3f) * mod.getPower(); //nice formula :)

								if (unit.hasRadarOn()) {
									val *= 3;
								}

								if (val > 0.95) {
									unit.setNewVisibility(parentUnit.getClass().getCountry(), Visibility.Visible);
								}
								else if (val > 0.9 && unit.getNewVisibility(parentUnit.getClass().getCountry()) == Visibility.Invisible) {
									unit.setNewVisibility(parentUnit.getClass().getCountry(), Visibility.Unknown);
								}
							}
						}
					}
				}
			}
		}
	}

	public void autoDef(float delta) {
		//When autoDef is called, then the unit is deployed
		DeployedUnit parentUnit = GameController.fromID(parent);

		float CIWSRange = getCIWSRange();

		foreach(DeployedUnit missile in GameController.getDeployedUnits()) {
			float distance = (parentUnit.getPosition()- missile.getPosition()).norm();
			if (missile.getClass().getClassType() == ClassType.Missile && missile.getClass().getCountry().getRelation(parentUnit.getClass().getCountry()) == Relation.Ennemy && !parentUnit.hasAlreadyBeenTargeted(missile)) {
				bool hasFiredMissiles = false;

				if (distance < Constants.MAX_DISTANCE_MISSILE_AUTO_DEF) { //first, try to fire missiles
					hasFiredMissiles = autoDefMissilesFire(parentUnit, missile);
				}

				if (!hasFiredMissiles && distance < CIWSRange) {
					CIWSFire(parentUnit, missile, delta, distance);
				}
			}
		}
	}

	public void CIWSFire(DeployedUnit parentUnit, DeployedUnit missile, float delta, float distance) {
		foreach(Tuple<Module, ModuleData> module in modules) {
			if (module._1 is CIWSModule && module._2.isOperationnal()) {
				((CIWSModuleData)module._2).fire(missile, delta, distance);
				parentUnit.setTargeted(missile);
			}
		}
	}

	public bool autoDefMissilesFire(DeployedUnit parentUnit, DeployedUnit target) {
		foreach(Class antiMissile in parentUnit.getClass().getMissileAutoDef()) {

			if (canLaunch(antiMissile, 1, null, true)) {
				parentUnit.fire(antiMissile, 1, target);
				parentUnit.setTargeted(target);

				return true;
			}
		}

		return false;
	}
}
