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

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;

import org.apache.commons.collections4.Factory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import pl.edu.pw.mini.jk.sg.game.common.Accident;
import pl.edu.pw.mini.jk.sg.game.common.TwoPlayerPayoffInfo;
import pl.edu.pw.mini.jk.sg.game.configuration.DiscreteProbabilityFunction;
import pl.edu.pw.mini.jk.sg.game.configuration.Edge;
import pl.edu.pw.mini.jk.sg.game.configuration.GraphDescription;
import pl.edu.pw.mini.jk.sg.game.configuration.StaticGameConfiguration;
import pl.edu.pw.mini.jk.sg.game.graph.moves.single.DesiredConfigurationMove;

@RunWith(Parameterized.class)
public class AdvanceableStateGraphGameTest {

	private final Factory<AdvanceableStateGraphGame> gameFactory;
	private AdvanceableStateGraphGame game;

	private static StaticGameConfiguration getGameConfiguration() {
		java.util.List<Edge> edges = new ArrayList<>();

		for (int i = 0; i < 4; i++) {
			edges.add(new Edge(i, 4));
			edges.add(new Edge(4, i));
		}

		return new StaticGameConfiguration(new GraphDescription(5, edges), Arrays.asList(2, 3), Arrays.asList(1),
				new int[][] { { 10 }, { 15 } }, new int[][] { { 8 }, { 10 } },
				new int[][] { { 1 }, { 2 }, { 3 }, { 4 }, { 5 } }, new int[][] { { 5 }, { 4 }, { 3 }, { 2 }, { 1 } }, 1,
						   3, DiscreteProbabilityFunction.instance(), 0, 0);
	}



	@Parameters
	public static final Collection<Object[]> parameters() {
		return ImmutableList.of(new Object[] { getGameConfiguration() });
	}

	public AdvanceableStateGraphGameTest(StaticGameConfiguration gameConfiguration) {
		super();
		this.gameFactory = () -> new AdvanceableStateGraphGame(2, gameConfiguration, Optional.empty(), false) ;
	}
	
	@Before
	public void setUp() {
		this.game = gameFactory.create();
	}

	@Test(expected = IllegalArgumentException.class)
	public void illegalDefenderMoveTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 2, 2, 2 }));
	}

	@Test(expected = IllegalStateException.class)
	public void doubleDefenderMoveTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 0, 0 }));
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 0, 0 }));
	}

	@Test
	public void properDefenderMoveTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 0, 0 }));
	}

	@Test
	public void singleRoundDefenderMoveTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 1 }));
		final TwoPlayerPayoffInfo pi = game.finishRound();
		final AdvanceableGraphGameState state = game.getCurrentState();

		assertEquals(1, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(0, pi.catchAccidentsCount());
		assertEquals(0, pi.defenderPayoff(), 1e-6);
	}

	@Test
	public void singleRoundCatchTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 4 }));
		final TwoPlayerPayoffInfo pi = game.finishRound();
		final AdvanceableGraphGameState state = game.getCurrentState();

		assertEquals(1, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(1, pi.catchAccidentsCount());
		assertEquals(5, pi.defenderPayoff(), 1e-6);
		assertEquals(5, game.getDefenderResult(), 1e-6);
		assertEquals(-1, game.getAttackerResult(), 1e-6);
		assertEquals(Arrays.asList(true, false), game.getCurrentState().getActiveAttackers());
		assertEquals(Arrays.asList(1, 4), game.getCurrentState().getAttackerVisibleState().getUnitPositions());
		assertEquals(game.getPreviousAccidents(), ImmutableList.of(new Accident(0, 0, 4, false)));
	}

	@Test
	public void inactiveAttackerTest() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 4 }));
		TwoPlayerPayoffInfo pi = game.finishRound();
		AdvanceableGraphGameState state = game.getCurrentState();

		assertEquals(1, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(1, pi.catchAccidentsCount());
		assertEquals(5, pi.defenderPayoff(), 1e-6);
		assertEquals(5, game.getDefenderResult(), 1e-6);
		assertEquals(-1, game.getAttackerResult(), 1e-6);
		assertEquals(Arrays.asList(true, false), game.getCurrentState().getActiveAttackers());
		assertEquals(Arrays.asList(1, 4), game.getCurrentState().getAttackerVisibleState().getUnitPositions());

		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 4 }));
		pi = game.finishRound();
		state = game.getCurrentState();

		assertEquals(2, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(0, pi.catchAccidentsCount());
		assertEquals(0, pi.defenderPayoff(), 1e-6);
		assertEquals(5, game.getDefenderResult(), 1e-6);
		assertEquals(-1, game.getAttackerResult(), 1e-6);
		assertEquals(Arrays.asList(true, false), game.getCurrentState().getActiveAttackers());
		assertEquals(Arrays.asList(1, 4), game.getCurrentState().getAttackerVisibleState().getUnitPositions());
	}

	@Test
	public void inactiveAttackerTestAttack() {
		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 4 }));
		TwoPlayerPayoffInfo pi = game.finishRound();
		AdvanceableGraphGameState state = game.getCurrentState();

		assertEquals(1, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(1, pi.catchAccidentsCount());
		assertEquals(5, pi.defenderPayoff(), 1e-6);
		assertEquals(5, game.getDefenderResult(), 1e-6);
		assertEquals(-1, game.getAttackerResult(), 1e-6);
		assertEquals(Arrays.asList(true, false), game.getCurrentState().getActiveAttackers());
		assertEquals(Arrays.asList(1, 4), game.getCurrentState().getAttackerVisibleState().getUnitPositions());

		game.playDefenderMove(new DesiredConfigurationMove(new int[] { 0, 4, 4 }));
		game.playAttackerMove(new DesiredConfigurationMove(new int[] { 1, 2 }));
		pi = game.finishRound();
		state = game.getCurrentState();

		assertEquals(2, state.getDefenderVisibleState().getTimeStep());
		assertEquals(Arrays.asList(0, 4, 4), state.getDefenderVisibleState().getUnitPositions());
		assertEquals(0, pi.attackAccidentsCount());
		assertEquals(0, pi.catchAccidentsCount());
		assertEquals(0, pi.defenderPayoff(), 1e-6);
		assertEquals(5, game.getDefenderResult(), 1e-6);
		assertEquals(-1, game.getAttackerResult(), 1e-6);
		assertEquals(Arrays.asList(true, false), game.getCurrentState().getActiveAttackers());
		assertEquals(Arrays.asList(1, 2), game.getCurrentState().getAttackerVisibleState().getUnitPositions());
	}

	@Test
	public void possibleMovesTest() {
		ImmutableSet<DesiredConfigurationMove> expectedSet = ImmutableSet.of(
				new DesiredConfigurationMove(new int[] { 0, 0, 0 }),
				new DesiredConfigurationMove(new int[] { 0, 0, 4 }),
				new DesiredConfigurationMove(new int[] { 0, 4, 0 }),
				new DesiredConfigurationMove(new int[] { 0, 4, 4 }),
				new DesiredConfigurationMove(new int[] { 4, 0, 0 }),
				new DesiredConfigurationMove(new int[] { 4, 0, 4 }),
				new DesiredConfigurationMove(new int[] { 4, 4, 0 }),
				new DesiredConfigurationMove(new int[] { 4, 4, 4 }));

		assertEquals(expectedSet, ImmutableSet.copyOf(game.possibleDefenderMoves()));
	}

}
