package pl.edu.pw.mini.jk.sg.game.graph.moves.many.advanceable;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.codepoetics.protonpack.StreamUtils;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;

import pl.edu.pw.mini.jk.sg.game.DefenderAttackerGame;
import pl.edu.pw.mini.jk.sg.game.common.Accident;
import pl.edu.pw.mini.jk.sg.game.common.Cloneable;
import pl.edu.pw.mini.jk.sg.game.common.TwoPlayerPayoffInfo;
import pl.edu.pw.mini.jk.sg.game.graph.AbstractStaticGraphGame;
import pl.edu.pw.mini.jk.sg.game.graph.advanceable.AdvanceableGraphGameConfiguration;

public class ManyMovesAdvanceableStateGame
		extends AbstractStaticGraphGame<SingleUnitDestination, AdvanceableMultiMoveState> implements
		DefenderAttackerGame<SingleUnitDestination, SingleUnitDestination, AdvanceableMultiMoveAttackerState, AdvanceableMultiMoveDefenderState, AdvanceableMultiMoveState>,
		Cloneable<ManyMovesAdvanceableStateGame> {

	private final int maxUnits;

	private final List<Integer> defenderPositions;
	private final List<Integer> attackerPositions;
	private final List<Boolean> activeAttackers;

	private int currentUnit = 0;
	private Optional<Integer> defenderDestination = Optional.empty();
	private Optional<Integer> attackerDestination = Optional.empty();

	private final Optional<Cache<Integer, ImmutableList<SingleUnitDestination>>> moveCache;

	private double attackerResult = 0;
	private double defenderResult = 0;
    private final boolean normalized;

	private Optional<AdvanceableMultiMoveState> currentState = Optional.empty();
	private final AdvanceableMultiMoveDefenderState initialDefenderState;
	private final AdvanceableMultiMoveAttackerState initialAttackerState;

    public ManyMovesAdvanceableStateGame(ManyMovesAdvanceableStateGame game) {
	super(game);
		this.maxUnits=game.maxUnits;
		
		this.defenderPositions=new ArrayList<>(game.defenderPositions);
		this.attackerPositions=new ArrayList<>(game.attackerPositions);
		this.activeAttackers=new ArrayList<>(game.activeAttackers);
		
		this.moveCache=game.moveCache;
		this.attackerResult=game.attackerResult;
		this.defenderResult=game.defenderResult;
		this.currentState=Optional.empty();
		this.initialDefenderState=game.initialDefenderState;
		this.initialAttackerState=game.initialAttackerState;
		this.normalized = game.normalized;
		this.attackerDestination = game.attackerDestination;
		this.defenderDestination = game.defenderDestination;
		this.currentUnit = game.currentUnit;
	}
	
	public ManyMovesAdvanceableStateGame(AdvanceableGraphGameConfiguration aggc,
					     Optional<Cache<Integer, ImmutableList<SingleUnitDestination>>> moveCache, boolean normalized) {
	    super(aggc.getStaticGameConfiguration());

		defenderPositions = new ArrayList<>(getDefenderCount());
		for (int i = 0; i < getDefenderCount(); i++)
			defenderPositions.add(0);
		attackerPositions = new ArrayList<>(aggc.getNumberOfAttackers());
		activeAttackers = new ArrayList<>(aggc.getNumberOfAttackers());
		for (int i = 0; i < aggc.getNumberOfAttackers(); i++) {
			attackerPositions.add(getStaticGameConfiguration().getSpawns().get(0));
			activeAttackers.add(true);
		}

		this.maxUnits = Math.max(defenderPositions.size(), attackerPositions.size());
		this.moveCache = moveCache;

		initialDefenderState = getCurrentState().getDefenderVisibleState();
		initialAttackerState = getCurrentState().getAttackerVisibleState();
		this.normalized = normalized;
	}

	@Override
	public AdvanceableMultiMoveState getCurrentState() {
		if (!currentState.isPresent())
			currentState = Optional.of(new AdvanceableMultiMoveState(defenderPositions, attackerPositions,
					getTimeStep(), currentUnit, getPossibleTargetFromVertex(), moveCache));
		return currentState.get();
	}


	@Override
	public void playDefenderMove(SingleUnitDestination desiredConfigurationMove) {
		if (!getCurrentState().getDefenderVisibleState().getPossibleMoves().contains(desiredConfigurationMove))
			throw new IllegalArgumentException("Bad defender move");
		if (defenderDestination.isPresent())
			throw new IllegalStateException("Defender already moved");
		defenderDestination = Optional.of(desiredConfigurationMove.getDestination());
	}

	@Override
	public void playAttackerMove(SingleUnitDestination desiredConfigurationMove) {
		if (!getCurrentState().getAttackerVisibleState().getPossibleMoves().contains(desiredConfigurationMove))
			throw new IllegalArgumentException("Bad attacker move");
		if (attackerDestination.isPresent())
			throw new IllegalStateException("Defender already moved");
		attackerDestination = Optional.of(desiredConfigurationMove.getDestination());
	}

    
    private double normalizeReward(double reward) {
	return normalized ? getStaticGameConfiguration().normalizeReward(reward) : reward;
    }

    
	@Override
	public TwoPlayerPayoffInfo finishRound() {
		if (!defenderDestination.isPresent() || !attackerDestination.isPresent())
			throw new IllegalStateException("Not all players moved!");

		if (currentUnit < defenderPositions.size()) {
			defenderPositions.set(currentUnit, defenderDestination.get());
		}
		if (currentUnit < attackerPositions.size()) {
			attackerPositions.set(currentUnit, attackerDestination.get());
		}

		attackerDestination = Optional.empty();
		defenderDestination = Optional.empty();
		currentState = Optional.empty();

		final TwoPlayerPayoffInfo payoff;

		if (currentUnit == maxUnits - 1) {
			currentUnit = 0;
			payoff = checkColisions();
			increaseTimeStep();
		} else {
			currentUnit++;
			payoff = new TwoPlayerPayoffInfo(0, 0, 0, 0);
		}
		return payoff;
	}

	private TwoPlayerPayoffInfo checkColisions() {
		final ImmutableList<Integer> caughtAttackersIndex = ImmutableList
				.copyOf(StreamUtils.zipWithIndex(attackerPositions.stream())
						.filter((in) -> activeAttackers.get((int) in.getIndex())
								&& defenderPositions.contains((int) in.getValue()))
						.map((in) -> (int) in.getIndex()).iterator());

		caughtAttackersIndex.forEach(i -> activeAttackers.set(i, false));

		final ImmutableList<Integer> successfulAttackerIndex = ImmutableList
				.copyOf(StreamUtils.zipWithIndex(attackerPositions.stream())
						.filter((in) -> activeAttackers.get((int) in.getIndex())
								&& getStaticGameConfiguration().getTargets().contains(in.getValue()))
						.map((in) -> (int) in.getIndex()).iterator());

		successfulAttackerIndex.forEach(i -> activeAttackers.set(i, false));

		final double attackerScore = successfulAttackerIndex.stream()
				.mapToDouble(i -> getAttackerReward(
						getStaticGameConfiguration().vertexToTarget(attackerPositions.get(i)), 0))
				.sum()
				+ caughtAttackersIndex.stream().mapToDouble(i -> vertexAttackerPenalty(attackerPositions.get(i), 0)).sum();

		final double defenderScore = caughtAttackersIndex.stream()
				.mapToDouble(i -> vertexDefenderReward(attackerPositions.get(i), 0)).sum()
				+ successfulAttackerIndex.stream()
						.mapToDouble(i -> getDefenderPenalty(
								getStaticGameConfiguration().vertexToTarget(attackerPositions.get(i)), 0))
						.sum();

		successfulAttackerIndex
				.forEach((i) -> registerAccident(new Accident(getTimeStep(), 0, attackerPositions.get(i), true)));
		caughtAttackersIndex
				.forEach((i) -> registerAccident(new Accident(getTimeStep(), 0, attackerPositions.get(i), false)));

		defenderResult += defenderScore;
		attackerResult += attackerScore;

		return new TwoPlayerPayoffInfo(normalizeReward(defenderScore),
						normalizeReward(attackerScore), caughtAttackersIndex.size(), successfulAttackerIndex.size());
	}

	@Override
	public double getDefenderResult() {
	    return normalizeReward(defenderResult);
	}

	@Override
	public double getAttackerResult() {
	    return normalizeReward(attackerResult);
	}

	@Override
	public AdvanceableMultiMoveDefenderState getDefenderInitialState() {
		return initialDefenderState;
	}

	@Override
	public AdvanceableMultiMoveAttackerState getAttackerInitialState() {
		return initialAttackerState;
	}

	@Override
	public ManyMovesAdvanceableStateGame typedClone() {
		return new ManyMovesAdvanceableStateGame(this);
	}

}
