package jrummikub.model;

import static jrummikub.model.StoneColor.BLACK;
import static jrummikub.model.StoneColor.BLUE;
import static jrummikub.model.StoneColor.ORANGE;
import static jrummikub.model.StoneColor.RED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

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

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

/**
 * Test class for {@link Hand}
 */
public class HandTest {

	IHand hand;
	GameSettings settings;

	/** */
	@Before
	public void setUp() {
		hand = new Hand();
		settings = new GameSettings();
	}

	/** */
	@Test
	public void testSimpleDrop() {
		Stone stone1 = new Stone(1, RED);
		Stone stone2 = new Stone(5, RED);
		Stone stone3 = new Stone(2, RED);

		hand.drop(stone1, new Position(2, 0));
		hand.drop(stone2, new Position(3, 0));
		hand.drop(stone3, new Position(2.5f, 0));

		assertEquals(new Position(1.5f, 0), hand.getPosition(stone1));
		assertEquals(new Position(3.5f, 0), hand.getPosition(stone2));
		assertEquals(new Position(2.5f, 0), hand.getPosition(stone3));
	}

	/** */
	@Test
	public void testSingleEdgeDrop() {
		Stone stone1 = new Stone(2, RED);
		Stone stone2 = new Stone(4, RED);

		hand.drop(stone1, new Position(0, 0));
		hand.drop(stone2, new Position(0.5f, 0));

		assertEquals(new Position(0, 0), hand.getPosition(stone1));
		assertEquals(new Position(1, 0), hand.getPosition(stone2));
	}

	/** */
	@Test
	public void testNearEdgeDrop() {
		Stone stone1 = new Stone(2, RED);
		Stone stone2 = new Stone(4, RED);

		hand.drop(stone1, new Position(0.25f, 0));
		hand.drop(stone2, new Position(0.5f, 0));

		assertEquals(new Position(0, 0), hand.getPosition(stone1));
		assertEquals(new Position(1, 0), hand.getPosition(stone2));
	}

	/** */
	@Test
	public void testNearEdgeMiddleDrop() {
		Stone stone1 = new Stone(1, RED);
		Stone stone2 = new Stone(5, RED);
		Stone stone3 = new Stone(2, RED);

		hand.drop(stone1, new Position(0.25f, 0));
		hand.drop(stone2, new Position(1.25f, 0));
		hand.drop(stone3, new Position(0.5f, 0));

		assertEquals(new Position(0, 0), hand.getPosition(stone1));
		assertEquals(new Position(2, 0), hand.getPosition(stone2));
		assertEquals(new Position(1, 0), hand.getPosition(stone3));
	}

	/** */
	@Test
	public void testNearRightEdgeDrop() {
		Stone stone1 = new Stone(2, BLUE);
		Stone stone2 = new Stone(4, BLUE);

		hand.drop(stone1, new Position(12.75f, 1));
		hand.drop(stone2, new Position(12.5f, 1));

		assertEquals(new Position(13, 1), hand.getPosition(stone1));
		assertEquals(new Position(12, 1), hand.getPosition(stone2));
	}

	/** */
	@Test
	public void testFullWrapDrop() {
		List<Stone> rowStones = new ArrayList<Stone>();
		for (int i = 0; i < 14; i++) {
			Stone stone = new Stone(i % 9, BLUE);
			rowStones.add(stone);
			hand.drop(stone, new Position(i, 1));
		}
		Stone newStone = new Stone(RED);
		hand.drop(newStone, new Position(12.5f, 1));

		for (int i = 0; i < 13; i++) {
			assertEquals(new Position(i, 1), hand.getPosition(rowStones.get(i)));
		}
		assertEquals(new Position(13, 1), hand.getPosition(newStone));
		assertEquals(new Position(0, 2), hand.getPosition(rowStones.get(13)));
	}

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

		Stone stone1 = new Stone(2, BLUE);
		Stone stone2 = new Stone(4, BLUE);
		Stone stone3 = new Stone(RED);

		hand.drop(stone1, new Position(0, 0));
		hand.drop(stone2, new Position(0, 0));
		hand.drop(stone3, new Position(0, 0));

		assertEquals(56, hand.getStonePoints(new GameSettings()));
	}

	private void dropStoneList(List<Stone> handStones) {
		hand = new Hand();
		for (Stone stone : handStones) {
			hand.drop(stone, new Position(0, 0));
		}
	}

	private void testInitialMeld(boolean possible, List<Stone> handStones) {
		dropStoneList(handStones);
		assertTrue(possible == hand.isInitialMeldPossible(settings));
	}

	/** */
	@Test
	public void testInvalid() {
		testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
				RED), new Stone(10, RED), new Stone(12, RED),
				new Stone(13, RED)));
		testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(10,
				BLACK), new Stone(11, RED), new Stone(11, BLACK)));
		testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(10,
				RED), new Stone(10, BLACK), new Stone(11, RED), new Stone(11,
				BLACK)));

		testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(11,
				BLACK), new Stone(12, RED)));
	}

	/** */
	@Test
	public void testNotEnoughPoints() {
		testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
				RED), new Stone(10, RED)));
		testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
				RED), new Stone(3, RED), new Stone(4, RED)));
		testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
				RED), new Stone(3, RED), new Stone(1, BLACK), new Stone(2,
				BLACK), new Stone(3, BLACK)));
	}

	/** */
	@Test
	public void testNotEnoughPointsWithJoker() {
		testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
				RED), new Stone(RED), new Stone(3, BLACK)));
		testInitialMeld(false, Arrays.asList(new Stone(4, RED), new Stone(5,
				RED), new Stone(4, BLACK), new Stone(5, BLACK), new Stone(RED)));
	}

	/** */
	@Test
	public void testNotEnoughPointsWithTwoJokers() {
		testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
				BLUE), new Stone(3, BLACK), new Stone(RED), new Stone(BLACK)));
		testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(RED),
				new Stone(BLACK)));
	}

	/** */
	@Test
	public void testValid() {
		testInitialMeld(true, Arrays.asList(new Stone(11, RED), new Stone(12,
				RED), new Stone(13, RED)));
		testInitialMeld(true, Arrays.asList(new Stone(4, RED),
				new Stone(5, RED), new Stone(6, RED), new Stone(5, ORANGE),
				new Stone(5, BLACK), new Stone(5, BLUE)));
		testInitialMeld(true, Arrays.asList(new Stone(10, RED), new Stone(10,
				BLACK), new Stone(10, ORANGE)));
	}

	/** */
	@Test
	public void testValidWithJoker() {
		testInitialMeld(true, Arrays.asList(new Stone(11, RED), new Stone(RED),
				new Stone(13, RED)));
		testInitialMeld(true, Arrays.asList(new Stone(10, RED),
				new Stone(BLACK), new Stone(10, ORANGE)));
		testInitialMeld(true, Arrays.asList(new Stone(4, RED),
				new Stone(5, RED), new Stone(6, RED), new Stone(5, BLACK),
				new Stone(5, BLUE), new Stone(RED)));
	}

	/** */
	@Test
	public void testValidWithTwoJokers() {
		testInitialMeld(true, Arrays.asList(new Stone(9, RED), new Stone(RED),
				new Stone(BLACK)));
	}

	/** */
	@Test
	public void testValidHuge() {
		List<Stone> stones = new ArrayList<Stone>();
		for (int i = 1; i <= 10; i++) {
			stones.add(new Stone(i, RED));
			stones.add(new Stone(i, BLACK));
		}
		testInitialMeld(true, stones);

		testInitialMeld(true, Arrays.asList(new Stone(1, BLACK), new Stone(1,
				BLACK), new Stone(2, BLACK), new Stone(8, BLACK), new Stone(9,
				BLACK), new Stone(10, BLACK), new Stone(11, BLACK), new Stone(
				12, BLACK), new Stone(12, BLACK), new Stone(3, ORANGE),
				new Stone(3, ORANGE), new Stone(5, ORANGE),
				new Stone(7, ORANGE), new Stone(12, ORANGE), new Stone(13,
						ORANGE), new Stone(13, ORANGE), new Stone(2, BLUE),
				new Stone(2, BLUE), new Stone(3, BLUE), new Stone(3, BLUE),
				new Stone(8, BLUE), new Stone(9, BLUE), new Stone(1, RED),
				new Stone(3, RED), new Stone(4, RED), new Stone(4, RED),
				new Stone(5, RED), new Stone(8, RED)));
	}

	/** */
	@Test
	public void testInvalidHuge() {
		testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
				RED), new Stone(4, RED), new Stone(6, RED), new Stone(8, RED),
				new Stone(10, RED), new Stone(13, RED), new Stone(1, BLACK),
				new Stone(2, BLACK), new Stone(4, BLACK), new Stone(5, BLACK),
				new Stone(8, BLACK), new Stone(9, BLACK), new Stone(12, BLACK),
				new Stone(3, BLUE), new Stone(5, BLUE), new Stone(7, BLUE),
				new Stone(11, BLUE), new Stone(RED)));
	}

	/** */
	@Test
	public void testNoLimits() {
		testInitialMeld(false, Arrays.asList(new Stone(12, RED), new Stone(13,
				RED), new Stone(1, RED), new Stone(2, RED), new Stone(3, RED)));
		settings.setNoLimits(true);
		testInitialMeld(true, Arrays.asList(new Stone(12, RED), new Stone(13,
				RED), new Stone(1, RED), new Stone(2, RED), new Stone(3, RED)));
	}

	/** */
	@Test
	public void testCountIdenticalStones() {
		dropStoneList(Arrays.asList(new Stone(1, RED), new Stone(2, RED),
				new Stone(1, BLUE)));
		assertEquals(0, hand.getIdenticalStoneCount());
		dropStoneList(Arrays.asList(new Stone(1, RED), new Stone(1, RED),
				new Stone(1, BLUE)));
		assertEquals(1, hand.getIdenticalStoneCount());
		dropStoneList(Arrays.asList(new Stone(1, RED), new Stone(1, RED),
				new Stone(1, BLUE), new Stone(1, BLUE)));
		assertEquals(2, hand.getIdenticalStoneCount());
		dropStoneList(Arrays.asList(new Stone(1, RED), new Stone(1, RED),
				new Stone(1, RED)));
		assertEquals(1, hand.getIdenticalStoneCount());
		dropStoneList(Arrays.asList(new Stone(1, RED), new Stone(1, RED),
				new Stone(1, RED), new Stone(1, RED)));
		assertEquals(2, hand.getIdenticalStoneCount());
		dropStoneList(Arrays.asList(new Stone(RED), new Stone(RED)));
		assertEquals(0, hand.getIdenticalStoneCount());

	}

}
