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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.ggp.base.util.game.TestGameRepository;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.statemachine.MachineState;
import org.ggp.base.util.statemachine.Move;
import org.ggp.base.util.statemachine.Role;
import org.junit.Assert;
import org.junit.Test;

import com.google.common.collect.ImmutableSet;

public class ProverStateMachineTest extends Assert {

    protected final ProverStateMachine sm = new ProverStateMachine();
    protected final GdlConstant C1 = GdlPool.getConstant("1");
    protected final GdlConstant C2 = GdlPool.getConstant("2");
    protected final GdlConstant C3 = GdlPool.getConstant("3");
    protected final GdlConstant C50 = GdlPool.getConstant("50");
    protected final GdlConstant C100 = GdlPool.getConstant("100");

    @Test
    public void testProverOnTicTacToe() throws Exception {
        List<Gdl> ticTacToeDesc = new TestGameRepository().getGame("ticTacToe").getRules();
        sm.initialize(ticTacToeDesc);
        MachineState state = sm.getInitialState();
        assertFalse(sm.isTerminal(state));
        GdlConstant X_PLAYER = GdlPool.getConstant("xplayer");
        GdlConstant O_PLAYER = GdlPool.getConstant("oplayer");
        Role xRole = new Role(X_PLAYER);
        Role oRole = new Role(O_PLAYER);
        List<Role> roles = Arrays.asList(xRole, oRole);
        assertEquals(roles, sm.getRoles());

        assertEquals(9, sm.getLegalJointMoves(state).size());
        assertEquals(9, sm.getLegalMoves(state, xRole).size());
        assertEquals(1, sm.getLegalMoves(state, oRole).size());
        Move noop = new Move(GdlPool.getConstant("noop"));
        assertEquals(noop, sm.getLegalMoves(state, oRole).get(0));

        Move m11 = move("mark 1 1");
        assertTrue(sm.getLegalMoves(state, xRole).contains(m11));
        state = sm.getNextState(state, Arrays.asList(new Move[] {m11, noop}));
        assertFalse(sm.isTerminal(state));

        Move m13 = move("mark 1 3");
        assertTrue(sm.getLegalMoves(state, oRole).contains(m13));
        state = sm.getNextState(state, Arrays.asList(new Move[] {noop, m13}));
        assertFalse(sm.isTerminal(state));

        Move m31 = move("mark 3 1");
        assertTrue(sm.getLegalMoves(state, xRole).contains(m31));
        state = sm.getNextState(state, Arrays.asList(new Move[] {m31, noop}));
        assertFalse(sm.isTerminal(state));

        Move m22 = move("mark 2 2");
        assertTrue(sm.getLegalMoves(state, oRole).contains(m22));
        state = sm.getNextState(state, Arrays.asList(new Move[] {noop, m22}));
        assertFalse(sm.isTerminal(state));

        Move m21 = move("mark 2 1");
        assertTrue(sm.getLegalMoves(state, xRole).contains(m21));
        state = sm.getNextState(state, Arrays.asList(new Move[] {m21, noop}));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, xRole));
        assertEquals(0, sm.getGoal(state, oRole));
        assertEquals(Arrays.asList(new Integer[] {100, 0}), sm.getGoals(state));

        //My expectations for the behavior, but there's no consensus...
        /*Move m23 = new Move(GdlPool.getRelation(PLAY, new GdlTerm[] {C2, C3, O}));
        try {
            sm.getNextState(state, Arrays.asList(new Move[] {noop, m23}));
            fail("Should throw an exception when trying to transition from a terminal state");
        } catch(TransitionDefinitionException e) {
            //Expected
        }*/
    }

    @Test
    public void testCase1A() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_1a").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
        state = sm.getNextState(state, Collections.singletonList(move("proceed")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    @Test
    public void testCase3C() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_3c").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role xplayer = new Role(GdlPool.getConstant("xplayer"));
        assertFalse(sm.isTerminal(state));
        assertEquals(1, sm.getLegalMoves(state, xplayer).size());
        assertEquals(move("win"), sm.getLegalMoves(state, xplayer).get(0));
        state = sm.getNextState(state, Collections.singletonList(move("win")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, xplayer));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    @Test
    public void testCase5A() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_5a").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(1, sm.getLegalMoves(state, you).size());
        assertEquals(move("proceed"), sm.getLegalMoves(state, you).get(0));
        state = sm.getNextState(state, Collections.singletonList(move("proceed")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    @Test
    public void testCase5B() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_5b").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(1, sm.getLegalMoves(state, you).size());
        assertEquals(move("draw 1 1 1 2"), sm.getLegalMoves(state, you).get(0));
        state = sm.getNextState(state, Collections.singletonList(move("draw 1 1 1 2")));
        assertTrue(sm.isTerminal(state));
    }

    @Test
    public void testCase5C() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_5c").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(1, sm.getLegalMoves(state, you).size());
        assertEquals(move("proceed"), sm.getLegalMoves(state, you).get(0));
        state = sm.getNextState(state, Collections.singletonList(move("proceed")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    @Test
    public void testCase5D() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_5d").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(1, sm.getLegalMoves(state, you).size());
        assertEquals(move("proceed"), sm.getLegalMoves(state, you).get(0));
        state = sm.getNextState(state, Collections.singletonList(move("proceed")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    @Test
    public void testCase5E() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_case_5e").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role robot = new Role(GdlPool.getConstant("robot"));
        assertFalse(sm.isTerminal(state));
        System.out.println(sm.getLegalMoves(state, robot));
        assertEquals(7, sm.getLegalMoves(state, robot).size());
        assertEquals(ImmutableSet.of(
                move("reduce a 0"),
                move("reduce a 1"),
                move("reduce c 0"),
                move("reduce c 1"),
                move("reduce c 2"),
                move("reduce c 3"),
                move("reduce c 4")),
                ImmutableSet.copyOf(sm.getLegalMoves(state, robot)));
    }

    @Test
    public void testDistinctAtBeginningOfRule() throws Exception {
        List<Gdl> desc = new TestGameRepository().getGame("test_distinct_beginning_rule").getRules();
        sm.initialize(desc);
        MachineState state = sm.getInitialState();
        Role you = new Role(GdlPool.getConstant("you"));
        assertFalse(sm.isTerminal(state));
        assertEquals(2, sm.getLegalMoves(state, you).size());
        state = sm.getNextState(state, Collections.singletonList(move("do a b")));
        assertTrue(sm.isTerminal(state));
        assertEquals(100, sm.getGoal(state, you));
        assertEquals(Collections.singletonList(100), sm.getGoals(state));
    }

    protected Move move(String description) {
        String[] parts = description.split(" ");
        GdlConstant head = GdlPool.getConstant(parts[0]);
        if(parts.length == 1)
            return new Move(head);
        List<GdlTerm> body = new ArrayList<GdlTerm>();
        for(int i = 1; i < parts.length; i++) {
            body.add(GdlPool.getConstant(parts[i]));
        }
        return new Move(GdlPool.getFunction(head, body));
    }
}
