package jrummikub.model;

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

import jrummikub.util.Pair;
import static jrummikub.model.StoneColor.*;
import static jrummikub.model.StoneSet.Type.*;

import org.junit.*;
import static org.junit.Assert.*;

/**
 * Tests for {@link StoneSet}
 */
public class StoneSetTest {

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

	// valid+point count
	/** */
	@Test
	public void doubleJoker() {
		// 3 Stones
		// JJZ
		assertSet(GROUP, 3, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, BLACK)));
		// JZJ
		assertSet(GROUP, 3, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(BLACK)));
		// ZJJ
		assertSet(RUN, 6, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(BLACK)));
		// 4 Stones
		// JJZZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(1, BLACK), new Stone(1, RED)));
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(BLACK),
				new Stone(3, RED), new Stone(4, RED)));
		// ZZJJ
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(BLACK), new Stone(RED)));
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED),
				new Stone(1, BLACK), new Stone(BLACK), new Stone(RED)));
		// ZJZJ
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED), new Stone(BLACK),
				new Stone(1, BLACK), new Stone(RED)));
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(3, RED), new Stone(BLACK)));
		// JZJZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(BLACK), new Stone(1, RED)));
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(2, RED),
				new Stone(BLACK), new Stone(4, RED)));
		// JZZJ
		assertSet(GROUP, 4, Arrays.asList(new Stone(RED), new Stone(1, BLACK),
				new Stone(1, RED), new Stone(BLACK)));
		assertSet(RUN, 10, Arrays.asList(new Stone(RED), new Stone(2, RED),
				new Stone(3, RED), new Stone(BLACK)));
		// ZJJZ
		assertSet(GROUP, 4, Arrays.asList(new Stone(1, RED), new Stone(BLACK),
				new Stone(RED), new Stone(1, BLACK)));
		assertSet(RUN, 10, Arrays.asList(new Stone(1, RED), new Stone(RED),
				new Stone(BLACK), new Stone(4, RED)));
	}

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

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

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

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

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

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

	/** */
	@Test
	public void otherInvalid() {

		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED),
				new Stone(5, RED), new Stone(7, RED)));
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, RED), new Stone(5,
				BLUE), new Stone(6, RED)));
		assertSet(INVALID, 0,
				Arrays.asList(new Stone(4, RED), new Stone(5, RED)));
		assertSet(INVALID, 0, Arrays.asList(new Stone(4, BLUE), new Stone(5,
				RED), new Stone(6, RED)));
		// Regression test:
		assertSet(INVALID, 0, Arrays.asList(new Stone(12, ORANGE), new Stone(
				12, BLACK), new Stone(7, BLUE)));
	}

	// 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().size());
		assertEquals(2, newSets.getSecond().size());
		// 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.size());
		// 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.size());
	}

	/**
	 * 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());
	}
}
