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

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

import pl.edu.pw.mini.jk.sg.game.graph.moves.single.DesiredConfigurationMove;

public class AdvanceableSideGraphState implements Comparable<AdvanceableSideGraphState> {

    protected final ImmutableList<Integer> unitPositions;
    protected final ImmutableList<ImmutableSet<Integer>> possibleDestinationFromVertex;
    protected final ImmutableList<DesiredConfigurationMove> possibleMoves;
    private final int timeStep;
    private final int hash;

    public AdvanceableSideGraphState(int timeStep, ImmutableList<Integer> unitPositions,
            ImmutableList<ImmutableSet<Integer>> possibleDestinationFromVertex,
            Cache<Object, ImmutableList<DesiredConfigurationMove>> moveCache) {
        super();
        this.unitPositions = unitPositions;
	this.timeStep = timeStep;
        this.possibleDestinationFromVertex = possibleDestinationFromVertex;
        try {
            this.possibleMoves = moveCache.get(
                    new PositionGraphCacheKey(possibleDestinationFromVertex, this.unitPositions),
                    () -> generatePossibleMoves(unitPositions, possibleDestinationFromVertex));
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        this.hash = calculateHash();
    }

    public AdvanceableSideGraphState(int timeStep, ImmutableList<Integer> unitPositions,
            ImmutableList<ImmutableSet<Integer>> possibleDestinationFromVertex) {
        super();
        this.unitPositions = unitPositions;
        this.possibleDestinationFromVertex = possibleDestinationFromVertex;
        this.possibleMoves = generatePossibleMoves(unitPositions, possibleDestinationFromVertex);
        this.hash = calculateHash();
	this.timeStep = timeStep;
    }

    public int getTimeStep() {
	return timeStep;
    }
    
    protected boolean checkLegalMove(DesiredConfigurationMove move) {
        return possibleMoves.contains(move);
    }

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

    protected ImmutableList<DesiredConfigurationMove> getPossibleMoves() {
        return possibleMoves;
    }

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


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

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

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

    public static ImmutableList<DesiredConfigurationMove> generatePossibleMoves(List<Integer> unitPositions,
            ImmutableList<ImmutableSet<Integer>> possibleDestinationsFromVertex) {
        return ImmutableList
                .copyOf(Sets
                        .cartesianProduct(unitPositions.stream().map(possibleDestinationsFromVertex::get)
                                .collect(Collectors.toList()))
                        .stream().map(l -> new DesiredConfigurationMove(l.stream().mapToInt(a -> a).toArray()))
                        .iterator());
    }

    @Override
    public int compareTo(AdvanceableSideGraphState o) {
        Preconditions.checkArgument(o.getUnitPositions().size() == getUnitPositions().size());
        Preconditions.checkArgument(o.getPossibleDestinationFromVertex().equals(getPossibleDestinationFromVertex()));

        return Ordering.<Integer> from((x, y) -> Integer.compare(x, y)).lexicographical().compare(getUnitPositions(),
                o.getUnitPositions());
    }
}
