package com.bernard.greposimu.engine;

import java.util.ArrayList;
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.ojalgo.optimisation.Expression;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.Optimisation.Result;
import org.ojalgo.optimisation.Variable;

import com.bernard.greposimu.model.Dieu;
import com.bernard.greposimu.model.Heros;
import com.bernard.greposimu.model.OffDefStats;
import com.bernard.greposimu.model.Unite;

public class Optimizer {
	
	public static UnitesProportions optimize(Contexte c,Set<Unite> uniteList) {
		// Computing the stats of the unités in this context
		Map<Unite,OffDefStats> stats = uniteList.stream()
				.filter(u -> u.getDieu() == null || u.getDieu().equals(c.d))
				.collect(Collectors.toMap(
						Function.identity(),
						u -> (OffDefStats)((c.h==null)?u.getStats():c.h.applyToUnit(u, c.level)).div(u.getPopulation())
				));
		// Clean list of unités
		List<Unite> unites = stats.keySet().stream().sorted((u,v) -> u.getNom().compareTo(v.getNom())).toList();
		
		// Création du modèle
		ExpressionsBasedModel model = new ExpressionsBasedModel();
		// Variable à optimiser
		Variable y = model.addVariable("y").weight(1.0);
		// Création des variables correspondant aux proportions, elles ne sont pas «optimisées» (weight 0.0) et sont positives
		List<Variable> varz = new ArrayList<>(unites.size());
		for(Unite u : unites) {
			Variable xi = model.addVariable(u.getNom()).weight(0.0);
			model.addExpression().lower(0.0).set(xi, 1.0); // 0 <= x_i
			varz.add(xi);
		}
		// Les proportions doivent être maximalement 1 (en pratique, 1, parce que croissante)
		Expression esum = model.addExpression().upper(1.0);
		// y doit se comparer à chaque somme suivant ce que le scoring précise
		Expression eAttqCont = model.addExpression().lower(0.0).set(y, -c.scoring.getAttqCont()); // y <= sum(a_1_i * x_i)
		Expression eAttqBlan = model.addExpression().lower(0.0).set(y, -c.scoring.getAttqBlan()); // y <= sum(a_1_i * x_i)
		Expression eAttqJet  = model.addExpression().lower(0.0).set(y, -c.scoring.getAttqJet() ); // y <= sum(a_1_i * x_i)
		Expression eAttqNav  = model.addExpression().lower(0.0).set(y, -c.scoring.getAttqNav() ); // y <= sum(a_1_i * x_i)
		Expression eDefCont  = model.addExpression().lower(0.0).set(y, -c.scoring.getDefCont() ); // y <= sum(a_1_i * x_i)
		Expression eDefBlan  = model.addExpression().lower(0.0).set(y, -c.scoring.getDefBlan() ); // y <= sum(a_1_i * x_i)
		Expression eDefJet   = model.addExpression().lower(0.0).set(y, -c.scoring.getDefJet()  ); // y <= sum(a_1_i * x_i)
		Expression eDefNav   = model.addExpression().lower(0.0).set(y, -c.scoring.getDefNav()  ); // y <= sum(a_1_i * x_i)
		
		for(int i=0;i<varz.size();i++) {
			esum.set(varz.get(i), 1.0);
			eAttqCont.set(varz.get(i), stats.get(unites.get(i)).getAttqCont());
			eAttqBlan.set(varz.get(i), stats.get(unites.get(i)).getAttqBlan());
			eAttqJet .set(varz.get(i), stats.get(unites.get(i)).getAttqJet());
			eAttqNav .set(varz.get(i), stats.get(unites.get(i)).getAttqNav());
			eDefCont .set(varz.get(i), stats.get(unites.get(i)).getDefCont());
			eDefBlan .set(varz.get(i), stats.get(unites.get(i)).getDefBlan());
			eDefJet  .set(varz.get(i), stats.get(unites.get(i)).getDefJet());
			eDefNav  .set(varz.get(i), stats.get(unites.get(i)).getDefNav());
		}
		
		Result r = model.maximise(); // maximize y
		return new UnitesProportions(unites, r);
	}
	
	public static class UnitesProportions {
		Map<Unite,Double> data;
		
		public UnitesProportions(List<Unite> unites, Optimisation.Result result) {
			this.data = new HashMap<>();
			for(int i=0;i<unites.size();i++) {
				double val = result.doubleValue(1+i);
				if(val>=0.0001) {
					this.data.put(unites.get(i), val);
				}
			}
		}
		
		public void print(Heros h, int level, int pop) {
			Map<Unite,Integer> ucounts = new HashMap<>(data.size());
			for(Unite u : data.keySet()) 
				ucounts.put(u, (int) Math.floor((data.get(u) * pop) / u.getPopulation()));
			int totalpop = ucounts.entrySet().stream().mapToInt(e -> e.getKey().getPopulation() * e.getValue()).sum();
			OffDefStats t = ucounts.entrySet().stream()
					.map(e -> h.applyToUnit(e.getKey(), level).times(e.getValue()))
					.reduce(OffDefStats.zero,(a,b) -> a.plus(b));
			System.out.println("Population totale de %d".formatted(totalpop));
			for(Unite u : data.keySet())
				System.out.println("|-- %d %s (%.1f%% de la population)".formatted(ucounts.get(u),u.getNom(),data.get(u)*100));
			System.out.println("--- Attaque (%d,%d,%d,%d) Défense (%d,%d,%d,%d)".formatted(
				(int)t.getAttqCont(),(int)t.getAttqBlan(),(int)t.getAttqJet(),(int)t.getAttqNav(),(int)t.getDefCont(),(int)t.getDefBlan(),(int)t.getDefJet(),(int)t.getDefNav()
			));
			
			
		}
		
		public Map<Unite, Double> getData() {
			return data;
		}
	}
	
	public static class Contexte {
		public Dieu d;
		public Heros h;
		public int level;
		public OffDefStats scoring;
	}
}
