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

import java.util.Optional;
import java.util.concurrent.Callable;

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

import pl.edu.pw.mini.jk.sg.game.common.advanceable.AdvanceableState;

public abstract class AdvanceableMultiMoveSideState implements AdvanceableState<SingleUnitDestination> {

    private final ImmutableList<Integer> unitPositions;
    private final ImmutableList<ImmutableSet<Integer>> possibleDestinationFromVertex;
    private final int currentUnit;
    private final int maxUnitsInGame;
    private final Optional<Cache<Integer, ImmutableList<SingleUnitDestination>>> moveCache;
    private final int hash;
    private final ImmutableList<SingleUnitDestination> possibleMoves;
    private final int timeStep;


    public int getTimeStep() {
	return timeStep;
    }
    
    public AdvanceableMultiMoveSideState(int timeStep, ImmutableList<Integer> unitPositions,
            ImmutableList<ImmutableSet<Integer>> possibleDestinationFromVertex, int currentUnit, int maxUnitsInGame,
            Optional<Cache<Integer, ImmutableList<SingleUnitDestination>>> moveCache) {
        super();
        this.unitPositions = unitPositions;
        this.possibleDestinationFromVertex = possibleDestinationFromVertex;
        this.currentUnit = currentUnit;
        this.maxUnitsInGame = maxUnitsInGame;
        this.moveCache = moveCache;
        this.hash = calculateHash();
        this.possibleMoves = getPossibleMovesInternal();
	this.timeStep = timeStep;
    }

    public ImmutableList<ImmutableSet<Integer>> getPossibleDestinationFromVertex() {
        return possibleDestinationFromVertex;
    }

    public ImmutableList<Integer> getUnitPositions() {
        return unitPositions;
    }

    public int getCurrentUnit() {
        return currentUnit;
    }

    public int getMaxUnitsInGame() {
        return maxUnitsInGame;
    }

    public int advanceCurrentUnit() {
        if (isLastMoveInRound())
            return 0;
        else
            return currentUnit + 1;
    }

    public boolean isLastMoveInRound() {
        return currentUnit + 1 == maxUnitsInGame;
    }

    public Optional<Cache<Integer, ImmutableList<SingleUnitDestination>>> getMoveCache() {
        return moveCache;
    }

    private ImmutableList<SingleUnitDestination> calculatePossibleMoves(int position) {
        if (position == -1) {
            return ImmutableList.of(new SingleUnitDestination(-1));
        } else {
            return ImmutableList.copyOf(getPossibleDestinationFromVertex().get(position).stream()
                    .map(SingleUnitDestination::new).iterator());
        }
    }

    public ImmutableList<Integer> advanceUnitPositinos(SingleUnitDestination move) {
        return ImmutableList.copyOf(StreamUtils.zipWithIndex(getUnitPositions().stream())
                .map(i -> i.getIndex() == currentUnit ? move.getDestination() : i.getValue()).iterator());
    }

    public ImmutableList<SingleUnitDestination> getPossibleMovesInternal() {
        final int position = currentUnit < unitPositions.size() ? getUnitPositions().get(currentUnit) : -1;
        final Callable<ImmutableList<SingleUnitDestination>> supplier = () -> calculatePossibleMoves(position);

        try {
            if (moveCache.isPresent()) {

                return moveCache.get().get(position, supplier);

            } else {
                return supplier.call();
            }
        } catch (Exception e) { // Urok callable
            throw new RuntimeException(e);
        }
    }

    @Override
    public ImmutableList<SingleUnitDestination> getPossibleMoves(){
        return possibleMoves;
    }

    
    
    @Override
    public int hashCode() {
        return hash;
    }

    private int calculateHash() {
        final int prime = 31;
        int result = 1;
        result = prime * result + currentUnit;
        result = prime * result + maxUnitsInGame;
        result = prime * result + ((unitPositions == null) ? 0 : unitPositions.hashCode());
        return result + timeStep;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AdvanceableMultiMoveSideState other = (AdvanceableMultiMoveSideState) obj;
        if (currentUnit != other.currentUnit)
            return false;
        if (maxUnitsInGame != other.maxUnitsInGame)
            return false;
        if (unitPositions == null) {
            if (other.unitPositions != null)
                return false;
        } else if (!unitPositions.equals(other.unitPositions))
            return false;
	if(timeStep != other.timeStep)
	    return false;
        return true;
    }

}
