package com.bernard.greposimu.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

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 com.bernard.greposimu.GrepoSimu;
import com.bernard.greposimu.engine.Optimizer;
import com.bernard.greposimu.engine.Optimizer.UnitesProportions;
import com.bernard.greposimu.engine.Registerar;
import com.bernard.greposimu.engine.game.Fight;
import com.bernard.greposimu.engine.game.Game;
import com.bernard.greposimu.model.DefContext;
import com.bernard.greposimu.model.Dieu;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.Heros;
import com.bernard.greposimu.model.OffDefStats;
import com.bernard.greposimu.model.Unite;
import com.bernard.greposimu.model.game.GameData;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

@Controller
public class GrepoSimuController {
	
	@GetMapping("/gamedata")
	public String gamedata(Model model) {
		System.out.println("Reading game data");
		GameData data;
		try {
			data = GrepoSimu.readGameData();
			model.addAttribute("content",data.toString());
		} catch (IOException e) {
			StringWriter writer = new StringWriter();
			e.printStackTrace(new PrintWriter(writer));
			model.addAttribute("content",writer.toString());
		}
		return "debug";
	}
	
	@GetMapping("/simulator")
	public String simulator(Model model) throws IOException {
		GameData data = GrepoSimu.readGameData();
		model.addAttribute("heroes", data.heroes.values());
		model.addAttribute("defUnits", Fight.relevantDefUnits(data));	
		model.addAttribute("defCounsellors",Fight.relevantDefCounsellors(data));
		model.addAttribute("defResearches",Fight.relevantDefResearch(data));
		model.addAttribute("defPowers",Fight.relevantDefPowers(data));
		model.addAttribute("defCtx",new DefContext());
		return "simulator";
	}
	
	@PostMapping("/simulate")
	@GetMapping("/simulate")
	public String simulate(@ModelAttribute DefContext defCtx, Model model) throws IOException {
		if(defCtx == null)
			defCtx = new DefContext();
		GameData data = GrepoSimu.readGameData();
		Game g = new Game(data);
		
		FightStats cityStats = g.fight.computeDefStats(defCtx);
	    ObjectMapper mapper = new ObjectMapper();
	    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
	    
		model.addAttribute("content",cityStats.toString()+"\n"+mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx));
		return "debug";
	}
	
	@GetMapping("/optimizer")
	public String greeting(Model model) {
		model.addAttribute("heros", Registerar.heros);
		
		// Liste des unités
		Map<String,List<Unite>> godUnites = Registerar.unites.stream()
				.filter(u -> u.getDieu() != null)
				.collect(Collectors.groupingBy(u -> u.getDieu().getPname()));
		List<Unite> otherUnites = Registerar.unites.stream()
				.filter(u -> u.getDieu() == null)
				.collect(Collectors.toList());
		System.out.println(godUnites);
		model.addAttribute("otherunites", otherUnites);
		model.addAttribute("godunites", godUnites);
		model.addAttribute("dieux", Arrays.stream(Dieu.values()).map(Dieu::getPname).collect(Collectors.toList()));
		model.addAttribute("optinput", new OptimizerInput());
		return "optimizer";
	}
	
	@PostMapping("/optimizer")
	public String greetingSubmit(@ModelAttribute OptimizerInput optinput, Model model) {
		Optimizer.Contexte ctx = new Optimizer.Contexte();
		ctx.h = Registerar.getHeros(optinput.heros);
		ctx.level = optinput.herosLvl;
		ctx.scoring = optinput.getOffDefStats();
		ctx.d = null;
		
		Set<Unite> unites = Registerar.unites.stream().filter(u -> optinput.unites.get(u.getPname())).collect(Collectors.toSet());
		
		Map<String,OptimizerOutput> outputs = new HashMap<>();
		outputs.put("none", new OptimizerOutput(Optimizer.optimize(ctx,unites), ctx.h, ctx.level, optinput.population));
		for(Dieu d : Dieu.values()) {
			ctx.d = d;
			outputs.put(d.getPname(), new OptimizerOutput(Optimizer.optimize(ctx,unites), ctx.h, ctx.level, optinput.population));
		}
		model.addAttribute("dieux", Arrays.stream(Dieu.values()).map(Dieu::getPname).collect(Collectors.toList()));
		model.addAttribute("outputs", outputs);
		model.addAttribute("requestedPop",optinput.population);
		return "optimizerResult";
	}
	
	public static class OptimizerInput {
		
		public String heros = Registerar.heros.stream().map(Heros::getPname).min(Comparator.comparing(Function.identity())).get();
		public int herosLvl = 1;

		public double att_hack_proportion = 0.0;
		public double att_pierce_proportion = 0.0;
		public double att_distance_proportion = 0.0;
		public double ship_attack_proportion = 0.0;
		public double def_hack_proportion = 1.0;
		public double def_pierce_proportion = 1.0;
		public double def_distance_proportion = 1.0;
		public double ship_defense_proportion = 1.0;
		
		public int population = 1000;
		
		public Map<String,Boolean> unites = Registerar.unites.stream().collect(Collectors.toMap(Unite::getPname, u -> true));
		
		

		public String getHeros() {
			return heros;
		}

		public void setHeros(String heros) {
			this.heros = heros;
		}

		public int getHerosLvl() {
			return herosLvl;
		}

		public void setHerosLvl(int herosLvl) {
			this.herosLvl = herosLvl;
		}

		public double getAtt_hack_proportion() {
			return att_hack_proportion;
		}

		public void setAtt_hack_proportion(double att_hack_proportion) {
			this.att_hack_proportion = att_hack_proportion;
		}

		public double getAtt_pierce_proportion() {
			return att_pierce_proportion;
		}

		public void setAtt_pierce_proportion(double att_pierce_proportion) {
			this.att_pierce_proportion = att_pierce_proportion;
		}

		public double getAtt_distance_proportion() {
			return att_distance_proportion;
		}

		public void setAtt_distance_proportion(double att_distance_proportion) {
			this.att_distance_proportion = att_distance_proportion;
		}

		public double getShip_attack_proportion() {
			return ship_attack_proportion;
		}

		public void setShip_attack_proportion(double ship_attack_proportion) {
			this.ship_attack_proportion = ship_attack_proportion;
		}

		public double getDef_hack_proportion() {
			return def_hack_proportion;
		}

		public void setDef_hack_proportion(double def_hack_proportion) {
			this.def_hack_proportion = def_hack_proportion;
		}

		public double getDef_pierce_proportion() {
			return def_pierce_proportion;
		}

		public void setDef_pierce_proportion(double def_pierce_proportion) {
			this.def_pierce_proportion = def_pierce_proportion;
		}

		public double getDef_distance_proportion() {
			return def_distance_proportion;
		}

		public void setDef_distance_proportion(double def_distance_proportion) {
			this.def_distance_proportion = def_distance_proportion;
		}

		public double getShip_defense_proportion() {
			return ship_defense_proportion;
		}

		public void setShip_defense_proportion(double ship_defense_proportion) {
			this.ship_defense_proportion = ship_defense_proportion;
		}

		public int getPopulation() {
			return population;
		}

		public void setPopulation(int population) {
			this.population = population;
		}

		public Map<String, Boolean> getUnites() {
			return unites;
		}

		public void setUnites(Map<String, Boolean> unites) {
			this.unites = unites;
		}
		
		public OffDefStats getOffDefStats() {
			return new OffDefStats(att_hack_proportion, att_pierce_proportion, att_distance_proportion, ship_attack_proportion, def_hack_proportion, def_pierce_proportion, def_distance_proportion, ship_defense_proportion);
		}

		@Override
		public String toString() {
			return "OptimizerInput [heros=" + heros + ", herosLvl=" + herosLvl + ", att_hack_proportion="
					+ att_hack_proportion + ", att_pierce_proportion=" + att_pierce_proportion
					+ ", att_distance_proportion=" + att_distance_proportion + ", ship_attack_proportion="
					+ ship_attack_proportion + ", def_hack_proportion=" + def_hack_proportion
					+ ", def_pierce_proportion=" + def_pierce_proportion + ", def_distance_proportion="
					+ def_distance_proportion + ", ship_defense_proportion=" + ship_defense_proportion + ", population="
					+ population + ", unites=" + unites + "]";
		}
		
		
	}
	public static class OptimizerOutput {
		OffDefStats total;
		int totalpop;
		List<Unite> unites;
		Map<Unite,Integer> ucounts;
		
		public OptimizerOutput(UnitesProportions data, Heros h, int level, int pop) {
			this.ucounts = new HashMap<>(data.getData().size());
			for(Unite u : data.getData().keySet()) 
				this.ucounts.put(u, (int) Math.floor((data.getData().get(u) * pop) / u.getPopulation()));
			this.unites = Registerar.unites.stream().filter(u -> ucounts.containsKey(u)).toList();
			this.totalpop = this.ucounts.entrySet().stream().mapToInt(e -> e.getKey().getPopulation() * e.getValue()).sum();
			this.total = this.ucounts.entrySet().stream()
					.map(e -> ((h==null)?e.getKey().getStats():h.applyToUnit(e.getKey(), level)).times(e.getValue()))
					.reduce(OffDefStats.zero,(a,b) -> a.plus(b));
		}

		public OffDefStats getTotal() {
			return total;
		}

		public int getTotalpop() {
			return totalpop;
		}

		public Map<Unite, Integer> getUcounts() {
			return ucounts;
		}
		
		public List<Unite> getUnites() {
			return unites;
		}
		
		
	}
}