package pl.edu.pw.mini.jk.sg.game.configuration;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

public class StaticGameConfiguration {
	private final GraphDescription graphDescription;
	private final ImmutableList<ImmutableSet<Integer>> possibleTargetFromVertex;

	private final int[][] vertexDefenderRewards;
	private final int[][] vertexAttackerPenalties;
	private final int[][] defenderPenalties;
	private final int[][] attackerRewards;

	private final ImmutableList<Integer> targets;
	private final BiMap<Integer, Integer> targetToVertex;
	private final ImmutableList<Integer> spawns;
	private final int defenderCount;

	private final DefenseProbabilityFunction defenseSuccesProbabilityFunc;

	private final int stateHistoryHorizon;
	private final int historyTimeHorizon;
	private final int attackerCount;

    private final double normalizationMultiplier;
	
	@JsonProperty("defenseProbabilityFunc")
	public DefenseProbabilityFunction getDefenseProbabilityFunc() {
		return defenseSuccesProbabilityFunc;
	}

	public double getVertexDefenderReward(int vertex, int attacker) {
		return vertexDefenderRewards[vertex][attacker];
	}

	public double getVertexAttackerPenalty(int vertex, int attacker) {
		return -vertexAttackerPenalties[vertex][attacker];
	}

	public double getDefenderPenalty(int target, int attacker) {
		return -defenderPenalties[target][attacker];
	}

	public double getAttackerReward(int target, int attacker) {
		return attackerRewards[target][attacker];
	}


    public double normalizeReward(double reward) {
	return reward/normalizationMultiplier;
    }
    
	public List<Integer> getTargets() {
		return targets;
	}

	public List<Integer> getSpawns() {
		return spawns;
	}

	public int getDefenderCount() {
		return defenderCount;
	}

	private static boolean checkArrayDimensions(int[][] array, int d1, int d2) {
		if (array.length != d1)
			return false;

		return Arrays.stream(array).allMatch(a -> a.length == d2);
	}

	public StaticGameConfiguration(@JsonProperty(value = "graph", required = true) GraphDescription graphDescription,
			@JsonProperty(value = "targets", required = true) List<Integer> targets,
			@JsonProperty(value = "spawns", required = true) List<Integer> spawns,
			@JsonProperty(value = "defenderPenalties", required = true) int[][] defenderPenalties,
			@JsonProperty(value = "attackerRewards", required = true) int[][] attackerRewards,
			@JsonProperty(value = "vertexDefenderRewards", required = true) int[][] vertexDefenderRewards,
			@JsonProperty(value = "vertexAttackerPenalties", required = true) int[][] vertexAttackerPenalties,
			@JsonProperty(value = "attackerCount", required = true) final int attackerCount,
			@JsonProperty(value = "defenderCount", required = true) final int defenderCount,
			@JsonProperty(value = "defenseProbabilityFunc", required = true) final DefenseProbabilityFunction defenseProbabilityFunction,
			@JsonProperty(value = "stateHistoryHorizon", required = true) final int stateHistoryHorizon,
			@JsonProperty(value = "historyTimeHorizon", required = true) final int historyTimeHorizon) {
		this(graphDescription, vertexDefenderRewards, vertexAttackerPenalties,
				defenderPenalties, attackerRewards, targets, spawns, defenderCount, attackerCount,
				defenseProbabilityFunction, stateHistoryHorizon, historyTimeHorizon);
	}

    private final double minPayoff() {
	return - Math.max(Arrays.stream(defenderPenalties).flatMapToInt(Arrays::stream).max().getAsInt(), Arrays.stream(vertexAttackerPenalties).flatMapToInt(Arrays::stream).max().getAsInt());
    }

    private final double maxPayoff() {
	return Math.max(Arrays.stream(vertexDefenderRewards).flatMapToInt(Arrays::stream).max().getAsInt(), Arrays.stream(attackerRewards).flatMapToInt(Arrays::stream).max().getAsInt());
    }

	private StaticGameConfiguration(GraphDescription graphDescription,
			int[][] vertexDefenderRewards, int[][] vertexAttackerPenalties, int[][] defenderPenalties,
			int[][] attackerRewards, List<Integer> targets, List<Integer> spawns, int defenderCount, int attackerCount,
			final DefenseProbabilityFunction defenseSuccesProbabilityFunc, final int stateHistoryHorizon,
			final int historyTimeHorizon) {
		super();

		Preconditions.checkArgument(checkArrayDimensions(defenderPenalties, targets.size(), attackerCount));
		Preconditions.checkArgument(checkArrayDimensions(attackerRewards, targets.size(), attackerCount));


		this.attackerCount = attackerCount;
		this.vertexDefenderRewards = vertexDefenderRewards;
		this.vertexAttackerPenalties = vertexAttackerPenalties;
		this.defenderPenalties = defenderPenalties;
		this.attackerRewards = attackerRewards;
		this.targets = ImmutableList.copyOf(targets);
		this.spawns = ImmutableList.copyOf(spawns);
		this.defenderCount = defenderCount;
		this.defenseSuccesProbabilityFunc = defenseSuccesProbabilityFunc;
		this.stateHistoryHorizon = stateHistoryHorizon;
		this.historyTimeHorizon = historyTimeHorizon;
		this.graphDescription = graphDescription;
		this.targetToVertex = ImmutableBiMap.copyOf(IntStream.range(0, targets.size()).boxed()
				.collect(Collectors.toMap(Function.identity(), targets::get)));

		possibleTargetFromVertex = ImmutableList.copyOf(IntStream.range(0, graphDescription.vertexCount())
				.mapToObj(i -> ImmutableSet.copyOf(
								   Stream.concat(Stream.of(i), Stream.concat(graphDescription.getEdges().stream().filter(e -> e.from() == i).map(e -> e.to()),
													     graphDescription.getEdges().stream().filter(e -> e.to() == i).map(e -> e.from())))
							      .iterator()))
								.iterator());
		this.normalizationMultiplier = Math.max(maxPayoff(),-minPayoff());

	}

	public int getStateHistoryHorizon() {
		return stateHistoryHorizon;
	}

	public int getHistoryTimeHorizon() {
		return historyTimeHorizon;
	}

    	public int[][] getVertexDefenderRewards() {
		return vertexDefenderRewards;
	}

	public int[][] getVertexAttackerPenalties() {
		return vertexAttackerPenalties;
	}

	public int[][] getDefenderPenalties() {
		return defenderPenalties;
	}

	public int[][] getAttackerRewards() {
		return attackerRewards;
	}
    
	public int getAttackerCount() {
		return attackerCount;
	}

	@JsonIgnore
	public int targetToVertex(int target) {
	    return targetToVertex.get(target);
	}

	@JsonIgnore
	public int vertexToTarget(int vertex) {
	    return targetToVertex.inverse().get(vertex);
	}

	@JsonProperty("graph")
	public GraphDescription getGraphDescription() {
		return graphDescription;
	}

	@JsonIgnore
	public ImmutableList<ImmutableSet<Integer>> getPossibleTargetFromVertex() {
		return possibleTargetFromVertex;
	}
}
