package com.bernard.greposimu.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.bernard.greposimu.GrepoSimuApplication;
import com.bernard.greposimu.Utils;
import com.bernard.greposimu.engine.game.Fight;
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.God;
import com.bernard.greposimu.model.game.units.Unit;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

@Controller
public class SimulatorController {
	
	@GetMapping("/simulator")
	public String simulator(Model model, @RequestParam boolean random) throws IOException {
		GameConfig gc = GrepoSimuApplication.GREPOLIS_GC;
		model.addAttribute("heroes", gc.getHeroes());
		model.addAttribute("defUnits", Fight.relevantDefUnits(gc));	
		model.addAttribute("defCounsellors",DefContext.COUNSELLORS);
		model.addAttribute("defResearches",DefContext.RESEARCHES);
		model.addAttribute("defPowers",DefContext.POWERS);
		model.addAttribute("offUnits", Fight.relevantOffUnits(gc));	
		model.addAttribute("offCounsellors",OffContext.COUNSELLORS);
		model.addAttribute("offResearches",OffContext.RESEARCHES);
		model.addAttribute("offPowers",OffContext.POWERS);
		SimulatorParams params = new SimulatorParams();
		if(random)
			params.randomize(new Random(), gc);
		model.addAttribute("ctx",params);
		return "simulator";
	}
	
	@PostMapping("/simulate")
	@GetMapping("/simulate")
	public String simulate(@ModelAttribute SimulatorParams params, Model model) throws IOException {
		if(params == null)	
			params = new SimulatorParams();
		GameConfig gc = GrepoSimuApplication.GREPOLIS_GC;
		DefContext defCtx = params.asDefContext(gc);
		OffContext offCtx = params.asOffContext(gc);
		FightStats defStats = Fight.computeDefStats(gc,defCtx);
		FightStats offStats = Fight.computeOffStats(gc,offCtx);
		ObjectMapper mapper = new ObjectMapper();
	    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
	    
		model.addAttribute("content",
				defStats.toString()+"\n"+
				offStats.toString()+"\n"+
				mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx)+"\n"+
				mapper.writerWithDefaultPrettyPrinter().writeValueAsString(offCtx)
		);
		return "debug";
	}
	
public static class SimulatorParams {
		
		// unitID -> number of units
		public Map<String, Integer> defUnits = new HashMap<>();
		public Map<String, Integer> offUnits = new HashMap<>();
	
		public String defHero = "";
		public int defHeroLevel = 0;
		public String offHero = "";
		public int offHeroLevel = 0;
		
		public int wallLevel = 0;
		public boolean hasTower = false;
		
		public Set<String> defPowers = new HashSet<>();
		public Set<String> offPowers = new HashSet<>();
		public Set<String> defResearches = new HashSet<>();
		public Set<String> offResearches = new HashSet<>();
		public Set<String> defCounsellors = new HashSet<>();
		public Set<String> offCounsellors = new HashSet<>();
		
		public boolean nightBonus = false;
		
		int luck, moral;
		
		int olympicSwordGrepolympiaSummerLevel = 1;
		int offOlympicSensesGrepolympiaSummerLevel = 1;
		int aresRageLevel = 1;
		int aresArmyFurySpent = 0;
		int bloodlustFurySpent = 0;
		
		int defOlympicSensesGrepolympiaSummerLevel = 1;
		int olympicTorchGrepolympiaSummerLevel = 1;
		int soteriasShrineLevel = 1;

		boolean strategyBreach = false;
		boolean allianceModifier = false;
		
		public DefContext asDefContext(GameConfig gc) {
			Map<Unit,Integer> unitsU = new HashMap<>(defUnits.size());
			for(String u : defUnits.keySet()) 
				unitsU.put(gc.getUnit(u), defUnits.get(u));
			return new DefContext(
				unitsU,
				gc.getHero(defHero),
				defHeroLevel,
				wallLevel,
				hasTower,
				defPowers,
				defOlympicSensesGrepolympiaSummerLevel, olympicTorchGrepolympiaSummerLevel, soteriasShrineLevel,
				defResearches,
				defCounsellors,
				nightBonus
			);
		}

		public static Object random() {
			// TODO Auto-generated method stub
			return null;
		}

		public OffContext asOffContext(GameConfig gc) {
			Map<Unit,Integer> unitsU = new HashMap<>(offUnits.size());
			for(String u : offUnits.keySet()) 
				unitsU.put(gc.getUnit(u), offUnits.get(u));
			return new OffContext(
				unitsU,
				gc.getHero(defHero),
				defHeroLevel,
				luck, moral, defPowers,
				olympicSwordGrepolympiaSummerLevel, offOlympicSensesGrepolympiaSummerLevel, aresRageLevel, aresArmyFurySpent, bloodlustFurySpent, defResearches,
				defCounsellors, allianceModifier, allianceModifier
			);
		}
		
		public void randomize(Random r,GameConfig gc) {
			
			offUnits = new HashMap<>();
			God g = null;
			if(r.nextDouble()<0.8)
				g = Utils.randFromSet(r, gc.getGods());
			for(Unit u : Fight.relevantOffUnits(gc))
				if(u.getGod() == null || u.getGod().equals(g))
					if(r.nextDouble() < 0.7)
						offUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1)));
			defUnits = new HashMap<>();
			g = null;
			if(r.nextDouble()<0.8)	
				g = Utils.randFromSet(r, gc.getGods());
			for(Unit u : Fight.relevantDefUnits(gc))
				if(u.getGod() == null || u.getGod().equals(g))
					if(r.nextDouble() < 0.7)
						defUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1)));
				
			offHero = null;
			offHeroLevel = 0;
			if(r.nextDouble()<.7) {
				offHero = Utils.randFromSet(r, gc.getHeroes()).getId();
				offHeroLevel = r.nextInt(1, 21);
			}
			defHero = null;
			defHeroLevel = 0;
			if(r.nextDouble()<.7) {
				defHero = Utils.randFromSet(r, gc.getHeroes()).getId();
				defHeroLevel = r.nextInt(1, 21);
			}
			
			wallLevel = r.nextInt(0, 26);
			hasTower = (wallLevel>20) && (r.nextDouble()<0.2);
			
			luck = (int) (Math.tanh(r.nextGaussian())*20);
			moral = 100;
			if(r.nextDouble()<.7)
				moral = (int) Math.max(100-(r.nextExponential()*20),0);
			
			offPowers = new HashSet<>();
			defPowers = new HashSet<>();
			for(String p : OffContext.POWERS) {
				if(r.nextDouble()<0.05)
					offPowers.add(p);
				if(r.nextDouble()<0.05)
					defPowers.add(p);
			}
			
			olympicSwordGrepolympiaSummerLevel = r.nextInt(1, 5);
			offOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5);
			aresRageLevel = r.nextInt(1, 11);
			aresArmyFurySpent = r.nextInt(1, 5000);
			bloodlustFurySpent = r.nextInt(1, 5000);
			defOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5);
			olympicTorchGrepolympiaSummerLevel = r.nextInt(1, 5);
			soteriasShrineLevel = r.nextInt(1, 11);
			
			offResearches = new HashSet<>();
			defResearches = new HashSet<>();
			for(String p : OffContext.RESEARCHES) {
				if(r.nextDouble()<0.20)
					offResearches.add(p);
				if(r.nextDouble()<0.20)
					defResearches.add(p);
			}
			offCounsellors = new HashSet<>();
			defCounsellors = new HashSet<>();
			for(String p : OffContext.COUNSELLORS) {
				if(r.nextDouble()<0.05)
					offCounsellors.add(p);
				if(r.nextDouble()<0.05)
					defCounsellors.add(p);
			}
			
			strategyBreach = (r.nextDouble()<0.02);
			allianceModifier = (r.nextDouble()<0.001);
			nightBonus = (r.nextDouble()<0.02);
			
		}

		
		public Map<String, Integer> getDefUnits() {
			return defUnits;
		}

		public void setDefUnits(Map<String, Integer> defUnits) {
			this.defUnits = defUnits;
		}

		public Map<String, Integer> getOffUnits() {
			return offUnits;
		}

		public void setOffUnits(Map<String, Integer> offUnits) {
			this.offUnits = offUnits;
		}

		public String getDefHero() {
			return defHero;
		}

		public void setDefHero(String defHero) {
			this.defHero = defHero;
		}

		public int getDefHeroLevel() {
			return defHeroLevel;
		}

		public void setDefHeroLevel(int defHeroLevel) {
			this.defHeroLevel = defHeroLevel;
		}

		public String getOffHero() {
			return offHero;
		}

		public void setOffHero(String offHero) {
			this.offHero = offHero;
		}

		public int getOffHeroLevel() {
			return offHeroLevel;
		}

		public void setOffHeroLevel(int offHeroLevel) {
			this.offHeroLevel = offHeroLevel;
		}

		public int getWallLevel() {
			return wallLevel;
		}

		public void setWallLevel(int wallLevel) {
			this.wallLevel = wallLevel;
		}

		public boolean isHasTower() {
			return hasTower;
		}

		public void setHasTower(boolean hasTower) {
			this.hasTower = hasTower;
		}

		public Set<String> getDefPowers() {
			return defPowers;
		}

		public void setDefPowers(Set<String> defPowers) {
			this.defPowers = defPowers;
		}

		public Set<String> getOffPowers() {
			return offPowers;
		}

		public void setOffPowers(Set<String> offPowers) {
			this.offPowers = offPowers;
		}

		public Set<String> getDefResearches() {
			return defResearches;
		}

		public void setDefResearches(Set<String> defResearches) {
			this.defResearches = defResearches;
		}

		public Set<String> getOffResearches() {
			return offResearches;
		}

		public void setOffResearches(Set<String> offResearches) {
			this.offResearches = offResearches;
		}

		public Set<String> getDefCounsellors() {
			return defCounsellors;
		}

		public void setDefCounsellors(Set<String> defCounsellors) {
			this.defCounsellors = defCounsellors;
		}

		public Set<String> getOffCounsellors() {
			return offCounsellors;
		}

		public void setOffCounsellors(Set<String> offCounsellors) {
			this.offCounsellors = offCounsellors;
		}

		public int getLuck() {
			return luck;
		}

		public void setLuck(int luck) {
			this.luck = luck;
		}

		public int getMoral() {
			return moral;
		}

		public void setMoral(int moral) {
			this.moral = moral;
		}

		public boolean isStrategyBreach() {
			return strategyBreach;
		}

		public void setStrategyBreach(boolean strategyBreach) {
			this.strategyBreach = strategyBreach;
		}

		public boolean isAllianceModifier() {
			return allianceModifier;
		}

		public void setAllianceModifier(boolean allianceModifier) {
			this.allianceModifier = allianceModifier;
		}

		public Map<String, Boolean> getDefPowersAsMap() {
			return Utils.setToMap(defPowers);
		}

		public Map<String, Boolean> getDefResearchesAsMap() {
			return Utils.setToMap(defResearches);
		}

		public Map<String, Boolean> getDefCounsellorsAsMap() {
			return Utils.setToMap(defCounsellors);
		}

		public boolean isNightBonus() {
			return nightBonus;
		}

		public void setNightBonus(boolean nightBonus) {
			this.nightBonus = nightBonus;
		}
		
	}
}
