package org.ggp.base.validator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.ggp.base.util.game.CloudGameRepository;
import org.ggp.base.util.game.Game;
import org.ggp.base.util.game.GameRepository;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.prover.aima.AimaProver;
import org.ggp.base.util.statemachine.MachineState;
import org.ggp.base.util.statemachine.Move;
import org.ggp.base.util.statemachine.Role;
import org.ggp.base.util.statemachine.StateMachine;
import org.ggp.base.util.statemachine.exceptions.MoveDefinitionException;
import org.ggp.base.util.statemachine.exceptions.TransitionDefinitionException;
import org.ggp.base.util.statemachine.implementation.prover.ProverStateMachine;

import com.google.common.collect.ImmutableList;


public class BasesInputsValidator implements GameValidator {
    private static final GdlVariable X = GdlPool.getVariable("?x");
    private static final GdlVariable Y = GdlPool.getVariable("?y");

    private int millisecondsToTest;
    public BasesInputsValidator(int millisecondsToTest) {
        this.millisecondsToTest = millisecondsToTest;
    }

    @Override
    public List<ValidatorWarning> checkValidity(Game theGame) throws ValidatorException {
        try {
            StateMachine sm = new ProverStateMachine();
            sm.initialize(theGame.getRules());

            AimaProver prover = new AimaProver(theGame.getRules());
            GdlSentence basesQuery = GdlPool.getRelation(GdlPool.BASE, new GdlTerm[] {X});
            Set<GdlSentence> bases = prover.askAll(basesQuery, Collections.<GdlSentence>emptySet());
            GdlSentence inputsQuery = GdlPool.getRelation(GdlPool.INPUT, new GdlTerm[] {X, Y});
            Set<GdlSentence> inputs = prover.askAll(inputsQuery, Collections.<GdlSentence>emptySet());

            if (bases.isEmpty()) {
                throw new ValidatorException("Could not find base propositions.");
            } else if (inputs.isEmpty()) {
                throw new ValidatorException("Could not find input propositions.");
            }

            Set<GdlSentence> truesFromBases = new HashSet<GdlSentence>();
            for (GdlSentence base : bases) {
                truesFromBases.add(GdlPool.getRelation(GdlPool.TRUE, base.getBody()));
            }
            Set<GdlSentence> legalsFromInputs = new HashSet<GdlSentence>();
            for (GdlSentence input : inputs) {
                legalsFromInputs.add(GdlPool.getRelation(GdlPool.LEGAL, input.getBody()));
            }

            if (truesFromBases.isEmpty() && legalsFromInputs.isEmpty()) {
                return ImmutableList.of();
            }

            MachineState initialState = sm.getInitialState();
            MachineState state = initialState;
            long startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() < startTime + millisecondsToTest) {
                //Check state against bases, inputs
                if (!truesFromBases.isEmpty()) {
                    if (!truesFromBases.containsAll(state.getContents())) {
                        Set<GdlSentence> missingBases = new HashSet<GdlSentence>();
                        missingBases.addAll(state.getContents());
                        missingBases.removeAll(truesFromBases);
                        throw new ValidatorException("Found missing bases: " + missingBases);
                    }
                }

                if (!legalsFromInputs.isEmpty()) {
                    List<GdlSentence> legalSentences = new ArrayList<GdlSentence>();
                    for (Role role : sm.getRoles()) {
                        List<Move> legalMoves = sm.getLegalMoves(state, role);
                        for (Move move : legalMoves) {
                            legalSentences.add(GdlPool.getRelation(GdlPool.LEGAL, new GdlTerm[] {role.getName(), move.getContents()}));
                        }
                    }
                    if (!legalsFromInputs.containsAll(legalSentences)) {
                        Set<GdlSentence> missingInputs = new HashSet<GdlSentence>();
                        missingInputs.addAll(legalSentences);
                        missingInputs.removeAll(legalsFromInputs);
                        throw new ValidatorException("Found missing inputs: " + missingInputs);
                    }
                }

                state = sm.getRandomNextState(state);
                if (sm.isTerminal(state)) {
                    state = initialState;
                }
            }
        } catch (MoveDefinitionException mde) {
            throw new ValidatorException("Could not find legal moves while simulating: " + mde);
        } catch (TransitionDefinitionException tde) {
            throw new ValidatorException("Could not find transition definition while simulating: " + tde);
        } catch (RuntimeException e) {
            throw new ValidatorException("Ran into a runtime exception while simulating: " + e);
        } catch (StackOverflowError e) {
            throw new ValidatorException("Ran into a stack overflow while simulating: " + e);
        } catch (OutOfMemoryError e) {
            throw new ValidatorException("Ran out of memory while simulating: " + e);
        }
        return ImmutableList.of();
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        GameRepository gameRepo = new CloudGameRepository("http://games.ggp.org/stanford/");

        for (String gameKey : gameRepo.getGameKeys()) {
            if (!gameKey.equals("amazons") //Skip games that currently result in out-of-memory errors
                    && !gameKey.equals("alexChess")) {
                try {
                    new BasesInputsValidator(20000).checkValidity(gameRepo.getGame(gameKey));
                    System.out.println("Game " + gameKey + " has valid base/input propositions.");
                } catch (ValidatorException ve) {
                    System.out.println("Game " + gameKey + " is invalid: " + ve.getMessage());
                }
            }
        }
    }
}