package jrummikub.model;

import static jrummikub.model.StoneColor.*;
import static jrummikub.model.StoneSet.Type.*;
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;

import jrummikub.util.Pair;

import org.junit.Before;
import org.junit.Test;

/**
 * Tests for {@link StoneSet}
 */
public class StoneSetTest {
	private GameSettings defaultSettings = new GameSettings();
	private GameSettings moreColorSettings = new GameSettings();
	private GameSettings lessColorSettings = new GameSettings();
	private GameSettings higherValueSettings = new GameSettings();
	private GameSettings lowerValueSettings = new GameSettings();
	private GameSettings noLimitsSettings = new GameSettings();
	private GameSettings noLimitsLowerValueSettings = new GameSettings();
	private GameSettings moreColorLowerValueSettings = new GameSettings();

	/** */
	@Before
	public void setUpSettings() {
		moreColorSettings.setStoneColors(EnumSet.allOf(StoneColor.class));
		lessColorSettings.setStoneColors(new HashSet<StoneColor>(Arrays.asList(
				StoneColor.BLUE, StoneColor.RED, StoneColor.BLACK)));
		higherValueSettings.setHighestValue(17);
		lowerValueSettings.setHighestValue(10);
		noLimitsSettings.setNoLimits(true);
		noLimitsLowerValueSettings.setHighestValue(10);
		noLimitsLowerValueSettings.setNoLimits(true);
		moreColorLowerValueSettings.setStoneColors(EnumSet
				.allOf(StoneColor.class));
		moreColorLowerValueSettings.setHighestValue(5);
	}

	private void assertSet(StoneSet.Type expectedType, Integer expectedValue,
			List<Stone> stones, GameSettings settings) {
		StoneSet set = new StoneSet(stones);
		assertSame(expectedType, set.classify(settings).getFirst());
		assertEquals(expectedValue, set.classify(settings).getSecond());
	}

	// valid+point count
	/** */
	@Test
	public void doubleJokerValid() {
		// 3 Stones
		// JJZ
		assertSet(GROUP, 3, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, BLACK)), defaultSettings);
		// JZJ
		assertSet(GROUP, 3, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(BLACK)), defaultSettings);
		// ZJJ
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(BLACK)), defaultSettings);
		assertSet(RUN, 33, Arrays.asList(new Stone(10, RED), new Stone(RED),
				new Stone(BLACK)), defaultSettings);
		assertSet(GROUP, 30, Arrays.asList(new Stone(10, RED), new Stone(RED),
				new Stone(BLACK)), lowerValueSettings);
		assertSet(GROUP, 39, Arrays.asList(new Stone(13, RED), new Stone(RED),
				new Stone(BLACK)), defaultSettings);
		assertSet(RUN, 42, Arrays.asList(new Stone(13, RED), new Stone(RED),
				new Stone(BLACK)), higherValueSettings);
		// 4 Stones
		// JJZZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, BLACK), new Stone(1, RED)), defaultSettings);
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(3, RED), new Stone(4, RED)), defaultSettings);
		// ZZJJ
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(BLACK), new Stone(RED)), defaultSettings);
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(BLACK), new Stone(RED)),
				defaultSettings);
		// ZJZJ
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED), new Stone(BLACK),
				new Stone(1, BLACK), new Stone(RED)), defaultSettings);
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(3, RED), new Stone(BLACK)), defaultSettings);
		// JZJZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(BLACK), new Stone(1, RED)), defaultSettings);
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(2, RED),
				new Stone(BLACK), new Stone(4, RED)), defaultSettings);
		// JZZJ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(1, RED), new Stone(BLACK)), defaultSettings);
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(2, RED),
				new Stone(3, RED), new Stone(BLACK)), defaultSettings);
		// ZJJZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED), new Stone(BLACK),
				new Stone(RED), new Stone(1, BLACK)), defaultSettings);
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(BLACK), new Stone(4, RED)), defaultSettings);
		// More than 4 stones
		assertSet(GROUP, 6, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLUE), new Stone(1, GREEN), new Stone(1, BLACK),
				new Stone(BLACK), new Stone(RED)), moreColorSettings);
	}

	/** */
	@Test
	public void groups() {
		assertSet(GROUP, 3, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(1, BLUE)), defaultSettings);
		assertSet(GROUP, 3, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(1, BLUE)), lessColorSettings);
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(1, BLUE), new Stone(1, ORANGE)),
				defaultSettings);
		assertSet(GROUP, 6, Arrays.asList(new Stone(1, RED),
				new Stone(1, GREEN), new Stone(1, GRAY), new Stone(1, BLACK),
				new Stone(1, BLUE), new Stone(1, ORANGE)), moreColorSettings);
	}

	/** */
	@Test
	public void runs() {
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(3, RED)), defaultSettings);
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(3, RED)), lowerValueSettings);
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(3, RED)), higherValueSettings);
		assertSet(RUN, 22, Arrays.asList(new Stone(4, BLUE),
				new Stone(5, BLUE), new Stone(6, BLUE), new Stone(7, BLUE)),
				defaultSettings);
		assertSet(RUN, 42, Arrays.asList(new Stone(13, RED),
				new Stone(14, RED), new Stone(15, RED)), higherValueSettings);
	}

	/** */
	@Test
	public void singleJoker() {
		// ZJZ
		assertSet(GROUP, 3, Arrays.asList(new Stone(1, RED), new Stone(BLACK),
				new Stone(1, BLACK)), defaultSettings);
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(3, RED)), defaultSettings);
		// JZZ
		assertSet(GROUP, 3, Arrays.asList(new Stone(RED), new Stone(1, RED),
				new Stone(1, BLACK)), defaultSettings);
		assertSet(RUN, 6, Arrays.asList(new Stone(RED), new Stone(2, RED),
				new Stone(3, RED)), defaultSettings);
		// ZZJ
		assertSet(GROUP, 3, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(BLACK)), defaultSettings);
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(RED)), defaultSettings);
		assertSet(RUN, 39, Arrays.asList(new Stone(12, RED),
				new Stone(13, RED), new Stone(RED)), higherValueSettings);
	}

	@Test
	public void noLimitsTest() {
		assertSet(RUN, 16, Arrays.asList(new Stone(RED), new Stone(1, RED),
				new Stone(2, RED)), noLimitsSettings);
		assertSet(RUN, 26, Arrays.asList(new Stone(12, RED),
				new Stone(13, RED), new Stone(RED)), noLimitsSettings);
		assertSet(RUN, 20, Arrays.asList(new Stone(9, RED), new Stone(10, RED),
				new Stone(RED)), noLimitsLowerValueSettings);
		assertSet(RUN, 28, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, RED), new Stone(2, RED)), noLimitsSettings);
		assertSet(RUN, 26, Arrays.asList(new Stone(12, RED),
				new Stone(13, RED), new Stone(1, RED)), noLimitsSettings);
		assertSet(RUN, 16, Arrays.asList(new Stone(13, RED), new Stone(1, RED),
				new Stone(2, RED)), noLimitsSettings);

		assertSet(INVALID, 0, Arrays.asList(new Stone(13, RED), new Stone(RED),
				new Stone(1, RED)), noLimitsSettings);
	}

	// invalid
	/** */
	@Test
	public void doubleJokerInvalid() {
		assertSet(INVALID, 0, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, BLACK), new Stone(1, RED)), lessColorSettings);
		// More than 4 stones
		assertSet(INVALID, 0, Arrays.asList(new Stone(1, RED), new Stone(1,
				BLUE), new Stone(1, GREEN), new Stone(1, BLACK), new Stone(1,
				VIOLET), new Stone(1, ORANGE), new Stone(1, AQUA), new Stone(
				BLACK), new Stone(RED)), moreColorSettings);
	}

	/** */
	@Test
	public void outOfBounds() {
		assertSet(INVALID, 0, Arrays.asList(new Stone(RED), new Stone(1, RED),
				new Stone(2, RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(12, RED), new Stone(13,
				RED), new Stone(RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(9, RED), new Stone(10,
				RED), new Stone(RED)), lowerValueSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, RED), new Stone(2, RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(12, RED), new Stone(13,
				RED), new Stone(1, RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(13, RED), new Stone(1,
				RED), new Stone(2, RED)), defaultSettings);
	}

	/** */
	@Test
	public void sameColor() {
		assertSet(INVALID, 0, Arrays.asList(new Stone(1, RED),
				new Stone(1, RED), new Stone(1, BLUE)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(1, RED), new Stone(1,
				BLUE), new Stone(1, BLACK), new Stone(1, ORANGE),
				new Stone(RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(1, RED), new Stone(1,
				BLUE), new Stone(1, GREEN), new Stone(1, BLACK), new Stone(1,
				VIOLET), new Stone(1, GRAY), new Stone(1, ORANGE), new Stone(1,
				AQUA), new Stone(RED)), moreColorSettings);
	}

	/** */
	@Test
	public void incorrectOrder() {
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED),
				new Stone(6, RED), new Stone(5, RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED),
				new Stone(6, RED), new Stone(RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED), new Stone(RED),
				new Stone(5, RED)), defaultSettings);
	}

	/** */
	@Test
	public void otherInvalid() {
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED),
				new Stone(5, RED), new Stone(7, RED)), defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED), new Stone(5,
				BLUE), new Stone(6, RED)), defaultSettings);
		assertSet(INVALID, 0,
				Arrays.asList(new Stone(4, RED), new Stone(5, RED)),
				defaultSettings);
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, BLUE), new Stone(5,
				RED), new Stone(6, RED)), defaultSettings);
		// Regression test:
		assertSet(INVALID, 0, Arrays.asList(new Stone(12, ORANGE), new Stone(
				12, BLACK), new Stone(7, BLUE)), defaultSettings);
	}

	/** */
	@Test
	public void manyJokersValid() {
		assertSet(GROUP, 3 * 13,
				Arrays.asList(new Stone(RED), new Stone(RED), new Stone(RED)),
				defaultSettings);
		assertSet(GROUP, 4 * 13, Arrays.asList(new Stone(RED), new Stone(RED),
				new Stone(RED), new Stone(RED)), defaultSettings);
		assertSet(RUN, 13 + 12 + 11 + 10 + 9,
				Arrays.asList(new Stone(RED), new Stone(RED), new Stone(RED),
						new Stone(RED), new Stone(RED)), defaultSettings);
		assertSet(RUN, 5 * 10, Arrays.asList(new Stone(RED), new Stone(RED),
				new Stone(RED), new Stone(RED), new Stone(12, RED)),
				defaultSettings);
		assertSet(GROUP, 6 * 5,
				Arrays.asList(new Stone(RED), new Stone(RED), new Stone(RED),
						new Stone(RED), new Stone(RED), new Stone(AQUA)),
				moreColorLowerValueSettings);
	}

	/** */
	@Test
	public void manyJokersInvalid() {
		List<Stone> stones = new ArrayList<Stone>();
		for (int i = 0; i < 14; i++) {
			stones.add(new Stone(RED));
		}
		assertSet(INVALID, 0, stones, defaultSettings);
	}

	// invalid Split
	/** */
	@Test
	public void testSplitInvalidLow() {
		StoneSet testSet = createTestSet();
		assertNull(testSet.splitAt(0).getFirst());

	}

	/** */
	@Test
	public void testSplitInvalidHigh() {
		StoneSet testSet = createTestSet();
		assertNull(testSet.splitAt(3).getSecond());
	}

	// valid Split
	/** */
	@Test
	public void testSplitValid() {
		StoneSet testSet = createTestSet();
		Pair<StoneSet, StoneSet> newSets = testSet.splitAt(1);
		// Sets have right size
		assertEquals(1, newSets.getFirst().getSize());
		assertEquals(2, newSets.getSecond().getSize());
		// Set have right Stones
		assertSame(testSet.get(0), newSets.getFirst().get(0));
		assertSame(testSet.get(1), newSets.getSecond().get(0));
		assertSame(testSet.get(2), newSets.getSecond().get(1));
	}

	private StoneSet createTestSet() {
		List<Stone> stones = new ArrayList<Stone>();
		stones.add(new Stone(1, BLUE));
		stones.add(new Stone(1, RED));
		stones.add(new Stone(1, BLACK));
		StoneSet testSet = new StoneSet(stones);
		return testSet;
	}

	// join
	/** */
	@Test
	public void testJoin() {
		StoneSet testSet = createTestSet();
		StoneSet secondSet = new StoneSet(new Stone(2, BLUE));
		StoneSet joinedSet = testSet.join(secondSet);
		// Sets have right size
		assertEquals(4, joinedSet.getSize());
		// Set have right Stones
		assertSame(testSet.get(0), joinedSet.get(0));
		assertSame(testSet.get(1), joinedSet.get(1));
		assertSame(testSet.get(2), joinedSet.get(2));
		assertSame(secondSet.get(0), joinedSet.get(3));
	}

	// iterator
	/** */
	@Test
	public void testIterator() {
		StoneSet testSet = createTestSet();
		int i = 0;

		for (Stone stone : testSet) {
			assertSame(stone, testSet.get(i));
			i++;
		}

		assertEquals(i, testSet.getSize());
	}

	/**
	 * String for Testing
	 */
	@Test
	public void testToString() {
		StoneSet testSet = new StoneSet(Arrays.asList(new Stone(2, BLUE),
				new Stone(3, BLUE), new Stone(4, BLUE)));

		assertEquals(
				"StoneSet[Stone[value=2,color=BLUE],Stone[value=3,color=BLUE],Stone[value=4,color=BLUE]]",
				testSet.toString());
	}
}