package com.bernard.greposimu.engine.game;

import java.util.List;
import java.util.Map;

import com.bernard.greposimu.model.DefContext;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.OffContext;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.researches.Research;
import com.bernard.greposimu.model.game.units.FightType;
import com.bernard.greposimu.model.game.units.NavalUnit;
import com.bernard.greposimu.model.game.units.TerrestrialUnit;
import com.bernard.greposimu.model.game.units.Unit;

public class Fight {

	public FightResult simulateFight(GameConfig gc, OffContext off, DefContext def) {
		FightStats offStats = computeOffStats(gc, off);
		FightStats defStats = computeDefStats(gc, def);
		
		// Combat Naval
		if(offStats.getShip() > defStats.getShip()) {
			// Off wins ship
		} else {
			
		}
		//TODO simulateFight
		throw new UnsupportedOperationException("Simulator not created");
	}

	
	public static FightStats computeDefStats(GameConfig gc, DefContext def) {
		
		FightStats everyoneStatsBonus = FightStats.zero();
		Map<Unit,FightStats> unitsBonuses;
		FightStats cityBaseStats;
		
		// Heroes
		unitsBonuses = Heroes.heroFightBonuses(gc, def.getHero(), def.getHeroLevel(), false);
		
		// Tower & wall
		if(def.hasTrojanDefense()) {
			cityBaseStats = Buildings.cityBaseStats(def.getWallLevel()+1);
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, Buildings.wallBonus(def.getWallLevel()+1));
		} else {
			cityBaseStats = Buildings.cityBaseStats(def.getWallLevel());
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, Buildings.wallBonus(def.getWallLevel()));
		}
		if(def.hasTower())
			// Add 10% to all units
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(0.1));
		
		// Researches
		if(def.hasDivineSelection()) 
			for(Unit u : gc.getUnits())
				if(u.isMythological()) 
					unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
		if(def.hasPhalanx()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
		if(def.hasRam()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
		
		// Counsellors
		if(def.hasPriest()) 
			for(Unit u : gc.getUnits())
				if(u.isMythological()) 
					unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2)));
		if(def.hasCommander()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2));
		if(def.hasCaptain()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.2));
		
		// Powers
		if(def.hasMyrmidionAttack()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
		if(def.hasDefenseBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05));
		if(def.hasDefensePenalty()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
		if(def.hasLongtermDefenseBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05));
		if(def.hasRareDefenseBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05));
		if(def.hasEpicDefenseBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
		if(def.getOlympicTorchGrepolympiaSummerLevel()!=0) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*def.getOlympicTorchGrepolympiaSummerLevel()));
		if(def.getSoteriasShrineLevel()!=0)
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.007*def.getSoteriasShrineLevel()));
		if(def.hasNarcissism())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
		
		// Night Bonus
		if(def.isNightBonus())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(1.0));

		// Units
		FightStats total = cityBaseStats.clone();
		for(Unit u : gc.getUnits()) {
			// total = total + ucount * ((1+bonus+bonus) * ustats)
			total = FightStats.add(total,
					FightStats.prod(def.unitCount(u),
					FightStats.mul(
					FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(u, FightStats.zero()))
					, makeDefStats(u))
				));
		}
		
		// HeroStat
		if(def.getHero() != null) {
			total = FightStats.add(total, new FightStats(
				def.getHero().getHackDef() * (1.0+0.1*def.getHeroLevel()),
				def.getHero().getPierceDef() * (1.0+0.1*def.getHeroLevel()),
				def.getHero().getDistanceDef() * (1.0+0.1*def.getHeroLevel()),
				0.0
			));
		}
		
		return total;
	}
	
	public static FightStats makeDefStats(Unit u) {
		if(u instanceof TerrestrialUnit) {
			TerrestrialUnit tu = (TerrestrialUnit)u;
			return new FightStats(tu.getHackDef(), tu.getPierceDef(), tu.getDistanceDef(), 0.0);
		}else if(u instanceof NavalUnit) {
			NavalUnit nu = (NavalUnit)u;
			return new FightStats(0.0, 0.0, 0.0, nu.getDefense());
		} 
		throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName());
	}
	
	public static FightStats computeOffStats(GameConfig gc, OffContext off) {
		
		FightStats everyoneStatsBonus = FightStats.zero();
		Map<Unit,FightStats> unitsBonuses;
		
		// Heroes
		unitsBonuses = Heroes.heroFightBonuses(gc, off.getHero(), off.getHeroLevel(), false);
		
		// Researches
		if(off.hasDivineSelection()) 
			for(Unit u : gc.getUnits())
				if(u.isMythological()) 
					unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
		if(off.hasPhalanx()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
		if(off.hasRam()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
		
		
		// Counsellors
		if(off.hasPriest()) 
			for(Unit u : gc.getUnits())
				if(u.isMythological()) 
					unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2)));
		if(off.hasCommander()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2));
		if(off.hasCaptain()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.2));
		
		// Powers
		if(off.hasMyrmidionAttack()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
		if(off.hasAttackBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
		if(off.hasAttackPenalty()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
		if(off.hasLongtermAttackBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
		if(off.hasLuxurious_residence())
			;//XXX Unimplemented
		if(off.hasAttack_ship_attack_boost_small())
			unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.1)));
		if(off.hasAttack_ship_attack_boost_medium())
			unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.2)));
		if(off.hasAttack_ship_attack_boost_large())
			unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.3)));
		if(off.hasRareAttackBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
		if(off.hasEpicAttackBoost()) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.2));
		if(off.getOlympicSwordGrepolympiaSummerLevel()!=0) 
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*off.getOlympicSwordGrepolympiaSummerLevel()));
		if(off.getAresRageLevel()!=0)
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.01*off.getAresRageLevel()));
		if(off.getBloodlust()!=0)
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.05+0.01*Math.floorDiv(off.getBloodlust(),200)));
		if(off.hasFairWind())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
		if(off.hasDesire())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
		if(off.hasStrengthOfHeroes())
			//XXX check if working
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
		if(off.hasEffortOfTheHuntress())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.ofType(FightType.DISTANCE,0.15));
		
		if(off.hasStrategyBreach())
			everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(-0.5));
		
		
		// Units
		FightStats total = FightStats.zero();
		for(Unit u : gc.getUnits()) {
			// total = total + ucount * ((1+bonusA+bonusB) * ustats)
			total = FightStats.add(total,
					FightStats.prod(off.unitCount(u),
					FightStats.mul(
					FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(u, FightStats.zero()))
					, makeOffStats(u))
				));
		}
		if(off.getAresArmyFurySpent()!=0)
			// Ading aresarmy/25 spartiates
			total = FightStats.add(total,
					FightStats.prod(Math.floorDiv(off.getAresArmyFurySpent(), 25),
					FightStats.mul(
					FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(gc.getUnit("spartoi"), FightStats.zero()))
					, makeOffStats(gc.getUnit("spartoi")))
				));
		
		
		
		// HeroStat
		if(off.getHero() != null) {
			total = FightStats.add(total, new FightStats(
				off.getHero().getHackDef() * (1.0+0.1*off.getHeroLevel()),
				off.getHero().getPierceDef() * (1.0+0.1*off.getHeroLevel()),
				off.getHero().getDistanceDef() * (1.0+0.1*off.getHeroLevel()),
				0.0
			));
		}
		
		// Bonuses
		everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(off.getLuck()/100.0));
		everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.cst(-(100.0-off.getMorale())/100.0));
		
				
		return total;
	}
	
	public static FightStats makeOffStats(Unit u) {
		if(u instanceof TerrestrialUnit) {
			TerrestrialUnit tu = (TerrestrialUnit)u;
			return FightStats.ofType(tu.getAttackType(), tu.getAttack());
		}else if(u instanceof NavalUnit) {
			NavalUnit nu = (NavalUnit)u;
			return new FightStats(0.0, 0.0, 0.0, nu.getAttack());
		} 
		throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName());
	}
	
	public static List<Unit> relevantDefUnits(GameConfig gc) {
		return gc.getUnits().stream().toList();
	}
	public static List<Unit> relevantOffUnits(GameConfig gc) {
		return gc.getUnits().stream().toList();
	}
	
	
	
	
	public static class FightResult {
		
		Map<String, Integer> def_losses;
		Map<String, Integer> att_losses;
		int wall_loss;
		int att_battle_points;
		int def_battle_points;
		
	}

}
