package pl.edu.pw.mini.jk.sg.mixeduct.attackerresponse;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.util.Pair;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import pl.edu.pw.mini.jk.sg.game.DefenderAttackerGame;
import pl.edu.pw.mini.jk.sg.game.common.DefenderMove;
import pl.edu.pw.mini.jk.sg.game.common.advanceable.AdvanceableAttackerState;
import pl.edu.pw.mini.jk.sg.game.common.advanceable.AdvanceableDefenderState;
import pl.edu.pw.mini.jk.sg.gteval.GameMatrices;

public class OptimalAttacker implements AttackerSolver {
	public static final double ATTACKER_STRATEGY_EPS = 1e-15;

	@Override
	public <AM, AS extends AdvanceableAttackerState<AM>, DM extends DefenderMove, DS extends AdvanceableDefenderState<DM>> AttackerSolverResult<AM> calculateAttackerStrategy(
			Map<ImmutableList<DM>, Double> defenderMoveProbabilities, GameMatrices<AM, DM, AS, DS, ?> gameMatrices,
			RandomGenerator rng) {
	    return new AttackerSolverResult<AM>(OptimalAttacker.solveAttackerOptimalStrategy(gameMatrices, defenderMoveProbabilities), true);
	}

	public static <DM extends DefenderMove, AM, AS extends AdvanceableAttackerState<AM>, G extends pl.edu.pw.mini.jk.sg.game.common.Cloneable<G> & DefenderAttackerGame<AM, DM, AS, ?, ?>> ImmutableList<Pair<ImmutableList<AM>, Double>> solveAttackerOptimalStrategyPlus(
			GameMatrices<AM, DM, AS, ?, G> gameMatrices, final Map<ImmutableList<DM>, Double> defenderProbabilityMap) {
		LoggerFactory.getLogger("stackelberg.optimal.response").debug("Number of defender strategies: {}",
				defenderProbabilityMap.size());
		Map<ImmutableList<AM>, ExpectedPayoff> payoffMap = new HashMap<>();

		for (final Entry<ImmutableList<DM>, Double> defenderStrategy : defenderProbabilityMap.entrySet()) {
			final ClonableDefenderGameView<AM, DM, AS, G> gameView = new ClonableDefenderGameView<>(
					gameMatrices.getNoaiFactory().create(), 0, defenderStrategy.getKey());
			OptimalAttacker2.moveSequenceHelper(gameView, payoffMap, gameMatrices.getGameLength(),
					defenderStrategy.getValue());
		}

		double attackerMax = payoffMap.values().stream().mapToDouble(ExpectedPayoff::attackerPayoff).max()
				.getAsDouble();
		ImmutableList<ImmutableList<AM>> attackerMaxStr = ImmutableList.copyOf(payoffMap.entrySet().stream()
				.filter(e -> attackerMax - e.getValue().attackerPayoff() < ATTACKER_STRATEGY_EPS)
				.map(e -> e.getKey()).iterator());

		double defenderMax = attackerMaxStr.stream().mapToDouble(i -> payoffMap.get(i).defenderPayoff()).max()
				.getAsDouble();
		ImmutableList<ImmutableList<AM>> adMaxStr = ImmutableList.copyOf(attackerMaxStr.stream()
				.filter(i -> defenderMax - payoffMap.get(i).defenderPayoff() < ATTACKER_STRATEGY_EPS).iterator());

		final double prob = 1.0 / adMaxStr.size();

		return ImmutableList.copyOf(adMaxStr.stream().map(i -> new Pair<>(i, prob)).iterator());
	}

    public static <DM extends DefenderMove, AM, AS extends AdvanceableAttackerState<AM>, G extends pl.edu.pw.mini.jk.sg.game.common.Cloneable<G> & DefenderAttackerGame<AM, DM, AS, ?, ?>> ImmutableMap<ImmutableList<AM>, Double> solveAttackerOptimalStrategy(
			GameMatrices<AM, DM, AS, ?, G> gameMatrices, final Map<ImmutableList<DM>, Double> defenderProbabilityMap) {
	return ImmutableMap.copyOf(solveAttackerOptimalStrategyPlus(gameMatrices, defenderProbabilityMap).stream()
			       .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
}


    
	@Override
	public boolean isExact() {
		return true;
	}
}
