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

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

import com.codepoetics.protonpack.StreamUtils;
import com.google.common.base.Preconditions;
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.TwoPlayerPayoffInfo;
import pl.edu.pw.mini.jk.sg.game.configuration.StaticGameConfiguration;
import pl.edu.pw.mini.jk.sg.game.graph.AbstractStaticGraphGame;
import pl.edu.pw.mini.jk.sg.game.graph.moves.single.DesiredConfigurationMove;

public class AdvanceableStateGraphGame
        extends AbstractStaticGraphGame<DesiredConfigurationMove, AdvanceableGraphGameState> implements
        DefenderAttackerGame<DesiredConfigurationMove, DesiredConfigurationMove, AttackerAdvaceableGraphState, AdvanceableDefenderGraphState, AdvanceableGraphGameState>, 
        pl.edu.pw.mini.jk.sg.game.common.Cloneable<AdvanceableStateGraphGame> {
    private static final int DEFENDER_START = 0;
    private ImmutableList<Integer> defenderPositions;
    private ImmutableList<Integer> attackerPositions;
    private Optional<ImmutableList<Integer>> desiredDefenderPositions;
    private Optional<ImmutableList<Integer>> desiredAttackerPositions;

    private final List<Boolean> activeAttackers;
    private final AdvanceableDefenderGraphState initialDefenderState;
    private final AttackerAdvaceableGraphState initialAttackerState;
    private final Optional<Cache<Object, ImmutableList<DesiredConfigurationMove>>> moveCache;
    private final boolean normalized;

    private AdvanceableGraphGameState currentState;

    private double attackerResult;
    private double defenderResult;

    public AdvanceableStateGraphGame(AdvanceableStateGraphGame game) {
    	super(game);
    	defenderPositions=game.defenderPositions;
    	attackerPositions=game.attackerPositions;
    	desiredDefenderPositions=game.desiredDefenderPositions;
    	desiredAttackerPositions=game.desiredAttackerPositions;
    	activeAttackers=new ArrayList<>(game.activeAttackers);
    	initialDefenderState=game.initialDefenderState;
    	initialAttackerState=game.initialAttackerState;
    	moveCache=game.moveCache;
    	currentState=null;
    	attackerResult=game.attackerResult;
    	defenderResult=game.defenderResult;
	normalized = game.normalized;
    }
    
    public AdvanceableStateGraphGame(AdvanceableGraphGameConfiguration advanceableGraphGameConfiguration,
				     Cache<Object, ImmutableList<DesiredConfigurationMove>> moveCache, boolean normalized) {
        this(advanceableGraphGameConfiguration.getNumberOfAttackers(),
                advanceableGraphGameConfiguration.getStaticGameConfiguration(),
	     Optional.of(Preconditions.checkNotNull(moveCache)), normalized);
    }

    public AdvanceableStateGraphGame(AdvanceableGraphGameConfiguration advanceableGraphGameConfiguration, boolean normalized) {
        this(advanceableGraphGameConfiguration.getNumberOfAttackers(),
	     advanceableGraphGameConfiguration.getStaticGameConfiguration(), Optional.empty(), normalized);
    }

    public AdvanceableStateGraphGame(AdvanceableGraphGameConfiguration advanceableGraphGameConfiguration,
				     Optional<Cache<Object, ImmutableList<DesiredConfigurationMove>>> moveCache, boolean normalized) {
        this(advanceableGraphGameConfiguration.getNumberOfAttackers(),
	     advanceableGraphGameConfiguration.getStaticGameConfiguration(), moveCache, normalized);
    }

    public AdvanceableStateGraphGame(int attackerUnits, StaticGameConfiguration staticGameConfiguration,
				     Optional<Cache<Object, ImmutableList<DesiredConfigurationMove>>> moveCache, boolean normalized) {
        super(staticGameConfiguration);
        Preconditions.checkArgument(staticGameConfiguration.getSpawns().size() == 1);
        final int attackerSpawn = staticGameConfiguration.getSpawns().get(0);

        activeAttackers = new ArrayList<>();
        final ImmutableList.Builder<Integer> dpBuilder = ImmutableList.builder();
        final ImmutableList.Builder<Integer> apBuilder = ImmutableList.builder();

        for (int i = 0; i < attackerUnits; i++) {
            apBuilder.add(attackerSpawn);
            activeAttackers.add(true);
        }
        for (int i = 0; i < staticGameConfiguration.getDefenderCount(); i++)
            dpBuilder.add(DEFENDER_START);

        attackerPositions = apBuilder.build();
        defenderPositions = dpBuilder.build();
        desiredDefenderPositions = Optional.empty();
        desiredAttackerPositions = Optional.empty();

        this.moveCache = Preconditions.checkNotNull(moveCache);
        currentState = null;
        initialAttackerState = getCurrentState().getAttackerVisibleState();
        initialDefenderState = getCurrentState().getDefenderVisibleState();
        attackerResult = 0;
        defenderResult = 0;
	this.normalized = normalized;
    }

    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * getCurrentState()
     */
    @Override
    public AdvanceableGraphGameState getCurrentState() {
        if (currentState == null) {
            currentState = new AdvanceableGraphGameState(getTimeStep(), defenderPositions, attackerPositions,
                    getPossibleTargetFromVertex(), ImmutableList.copyOf(activeAttackers), moveCache);
        }
        return currentState;
    }

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

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

    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * playDefenderMove(pl.edu.pw.mini.jk.sg.game.graph.moves.single.
     * DesiredConfigurationMove)
     */
    @Override
    public void playDefenderMove(DesiredConfigurationMove desiredConfigurationMove) {
        if (desiredDefenderPositions.isPresent()) {
            throw new IllegalStateException("Defender already moved!");
        } else if (!getCurrentState().getDefenderVisibleState().getPossibleMoves().contains(desiredConfigurationMove)) {
            throw new IllegalArgumentException("Illegal defender move");
        }
        desiredDefenderPositions = Optional.of(desiredConfigurationMove.getTargetVertices());
    }

    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * playAttackerMove(pl.edu.pw.mini.jk.sg.game.graph.moves.single.
     * DesiredConfigurationMove)
     */
    @Override
    public void playAttackerMove(DesiredConfigurationMove desiredConfigurationMove) {
        if (desiredAttackerPositions.isPresent()) {
            throw new IllegalStateException("Attacker already moved!");
        } else if (!getCurrentState().getAttackerVisibleState().getPossibleMoves().contains(desiredConfigurationMove)) {
            throw new IllegalArgumentException("Illegal move!");
        }

        desiredAttackerPositions = Optional.of(desiredConfigurationMove.getTargetVertices());
    }


    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * finishRound()
     */
    @Override
    public TwoPlayerPayoffInfo finishRound() {
        if (!desiredAttackerPositions.isPresent() || !desiredDefenderPositions.isPresent()) {
            throw new IllegalStateException("One of players haven't moved");
        }

        attackerPositions = desiredAttackerPositions.get();
        defenderPositions = desiredDefenderPositions.get();
        desiredAttackerPositions = Optional.empty();
        desiredDefenderPositions = Optional.empty();

        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;

        increaseTimeStep();
        currentState = null;

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

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

    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * getDefenderResult()
     */
    @Override
    public double getDefenderResult() {
        return normalizeReward(defenderResult);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pl.edu.pw.mini.jk.sg.game.graph.advanceable.DefenderAttackerGame#
     * getAttackerResult()
     */
    @Override
    public double getAttackerResult() {
        return normalizeReward(attackerResult);
    }

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