package com.bernard.greposimu.model.simulator.objective;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.bernard.greposimu.model.game.Building;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.God;
import com.bernard.greposimu.model.game.researches.Research;
import com.bernard.greposimu.model.game.units.Unit;
import com.bernard.greposimu.model.simulator.Troupes;
import com.bernard.greposimu.model.simulator.Ville;
import com.bernard.greposimu.model.simulator.command.BuildCommand;
import com.bernard.greposimu.model.simulator.command.Command;
import com.bernard.greposimu.model.simulator.command.HideStoreCommand;
import com.bernard.greposimu.model.simulator.command.RecruitCommand;
import com.bernard.greposimu.model.simulator.command.ResearchCommand;

public class TownObjective {
	GameConfig gc;

	Map<Building,Integer> buildings;
	Map<Unit,Double> unitsProportions;
	Set<Research> researches;
	int hide;
	God god;
	
	TownObjective() {}
	
	public TownObjective(GameConfig gc, Map<Building, Integer> buildings, Map<Unit, Double> unitsProportions,
			Set<Research> researches, int hide, God god) {
		this.gc = gc;
		this.buildings = buildings;
		this.unitsProportions = unitsProportions;
		this.researches = researches;
		this.hide = hide;
		this.god = god;
	}
	
	public static final int villeMaxPop(GameConfig gc, Ville v) {
		return GameConfig.getTotalPop(
			v.getBatiments().getOrDefault(Building.FARM,0),
			v.getBatiments().getOrDefault(Building.THERMAL, 0)>=1,
			v.getResearches().contains(gc.getResearch("plow")),
			v.getGod().getId().equals("aphrodite"),
			0); //TODO take popultion_boost into account
	}
	public Troupes targetTroupes() {
		int popRest = GameConfig.getTotalPop(
				this.buildings.getOrDefault(Building.FARM,0),
				this.buildings.getOrDefault(Building.THERMAL, 0)>=1,
				this.researches.contains(gc.getResearch("plow")),
				this.god.getId().equals("aphrodite"),
				0) //TODO take popultion_boost into account
				-
				this.buildings.entrySet().stream().mapToInt(e -> e.getKey().getRequiredPop(e.getValue())).sum();
		double totalProp = this.unitsProportions.values().stream().collect(Collectors.summarizingDouble(d -> d)).getSum();
		return new Troupes(this.unitsProportions.entrySet().stream().collect(Collectors.toMap(
				e -> e.getKey(),
				e -> (int)Math.floor(e.getValue()*popRest/totalProp/e.getKey().getPopulation()))));
	}

	public Set<Command> getDifferences(Ville v) {
		
		Set<Command> commands = new HashSet<>();
		
		for(Building b :gc.getBuildings()) {
			int cur = v.getBatiments().getOrDefault(b, 0);
			int obj = this.buildings.getOrDefault(b, 0);
			if(cur != obj) {
				if(obj>cur)
					for(int i=cur+1;i<=obj;i++)
						commands.add(new BuildCommand(v.getId(), null, b, i, false));
				else
					for(int i=cur-1;i>=obj;i--)
						commands.add(new BuildCommand(v.getId(), null, b, i, true));
			}
			//TODO check queue
		}
		
		for(Research r : gc.getResearches()) {
			boolean cur = v.getResearches().contains(r);
			boolean obj = this.researches.contains(r);
			if(cur && !obj)
				commands.add(new ResearchCommand(v.getId(),null,r,true));
			else if (!cur && obj)
				commands.add(new ResearchCommand(v.getId(),null,r,false));
			//TODO check queue
		}
		Troupes tot = Troupes.add(v.getTroupes(),v.getSoutiens().values().stream().reduce(Troupes::add).orElse(new Troupes(Map.of())));
		Troupes target = this.targetTroupes();
		for(Unit u : gc.getUnits()) {
			int diff = target.getUnites().getOrDefault(u,0) - tot.getUnites().getOrDefault(u,0);
			if(diff > 0)
				commands.add(new RecruitCommand(v.getId(), null, u, diff));
			//TODO manage unit removal planned
		}
		
		if(v.getPiecesStoquees() < hide)
			commands.add(new HideStoreCommand(v.getId(), hide - v.getPiecesStoquees()));
		return commands;
	}

	public Map<Building, Integer> getBuildings() {
		return buildings;
	}

	public Map<Unit, Double> getUnitsProportions() {
		return unitsProportions;
	}

	public Set<Research> getResearches() {
		return researches;
	}

	public int getHide() {
		return hide;
	}

	public God getGod() {
		return god;
	}

	@Override
	public String toString() {
		return "TownObjective [gc=" + gc + ", buildings=" + buildings + ", unitsProportions=" + unitsProportions
				+ ", researches=" + researches + ", hide=" + hide + ", god=" + god + "]";
	}

}
