package org.ggp.base.util.statemachine.implementation.prover;

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

import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlRelation;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.logging.GamerLogger;
import org.ggp.base.util.prover.Prover;
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.GoalDefinitionException;
import org.ggp.base.util.statemachine.exceptions.MoveDefinitionException;
import org.ggp.base.util.statemachine.exceptions.TransitionDefinitionException;
import org.ggp.base.util.statemachine.implementation.prover.query.ProverQueryBuilder;
import org.ggp.base.util.statemachine.implementation.prover.result.ProverResultParser;

import com.google.common.collect.ImmutableList;


public class ProverStateMachine extends StateMachine
{
    private MachineState initialState;
    private Prover prover;
    private ImmutableList<Role> roles;

    /**
     * Initialize must be called before using the StateMachine
     */
    public ProverStateMachine()
    {

    }

    @Override
    public void initialize(List<Gdl> description)
    {
        prover = new AimaProver(description);
        roles = ImmutableList.copyOf(Role.computeRoles(description));
        initialState = computeInitialState();
    }

    private MachineState computeInitialState()
    {
        Set<GdlSentence> results = prover.askAll(ProverQueryBuilder.getInitQuery(), new HashSet<GdlSentence>());
        return new ProverResultParser().toState(results);
    }

    @Override
    public int getGoal(MachineState state, Role role) throws GoalDefinitionException
    {
        Set<GdlSentence> results = prover.askAll(ProverQueryBuilder.getGoalQuery(role), ProverQueryBuilder.getContext(state));

        if (results.size() != 1)
        {
            GamerLogger.logError("StateMachine", "Got goal results of size: " + results.size() + " when expecting size one.");
            throw new GoalDefinitionException(state, role);
        }

        try
        {
            GdlRelation relation = (GdlRelation) results.iterator().next();
            GdlConstant constant = (GdlConstant) relation.get(1);

            return Integer.parseInt(constant.toString());
        }
        catch (Exception e)
        {
            throw new GoalDefinitionException(state, role);
        }
    }

    @Override
    public MachineState getInitialState()
    {
        return initialState;
    }

    @Override
    public List<Move> getLegalMoves(MachineState state, Role role) throws MoveDefinitionException
    {
        Set<GdlSentence> results = prover.askAll(ProverQueryBuilder.getLegalQuery(role), ProverQueryBuilder.getContext(state));

        if (results.isEmpty())
        {
            throw new MoveDefinitionException(state, role);
        }

        return new ProverResultParser().toMoves(results);
    }

    @Override
    public MachineState getNextState(MachineState state, List<Move> moves) throws TransitionDefinitionException
    {
        Set<GdlSentence> results = prover.askAll(ProverQueryBuilder.getNextQuery(), ProverQueryBuilder.getContext(state, getRoles(), moves));

        for (GdlSentence sentence : results)
        {
            if (!sentence.isGround())
            {
                throw new TransitionDefinitionException(state, moves);
            }
        }

        return new ProverResultParser().toState(results);
    }

    @Override
    public List<Role> getRoles()
    {
        return roles;
    }

    @Override
    public boolean isTerminal(MachineState state)
    {
        return prover.prove(ProverQueryBuilder.getTerminalQuery(), ProverQueryBuilder.getContext(state));
    }
}