package com.packtpublishing.tddjava.ch05connect4;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.List;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyString;
import static org.junit.Assert.*;

public class Connect4TDDSpec {

    @Rule
    public ExpectedException exception = ExpectedException.none();

    private Connect4TDD tested;

    private OutputStream output;

    @Before
    public void beforeEachTest() {
        output = new ByteArrayOutputStream();
        tested = new Connect4TDD(new PrintStream(output));
    }

    /*
     * Plansza ma wymiary 7 (w poziomie) na 6 (w pionie). Początkowo pola są puste.
     */

    @Test
    public void whenTheGameStartsTheBoardIsEmpty() {
        assertThat(tested.getNumberOfDiscs(), is(0));
    }

    /*
	 * Gracze wrzucają od góry krążki w kolumny.
	 * Jeśli kolumna jest pusta, wrzucony krążek spada na dół planszy.
	 * Kolejne krążki wrzucane do tej samej kolumny zajmują pozycje nad wcześniej dodanymi krążkami.
     */

    @Test
    public void whenDiscOutsideBoardThenRuntimeException() {
        int column = -1;
        exception.expect(RuntimeException.class);
        exception.expectMessage("Nieprawidłowa kolumna " + column);
        tested.putDiscInColumn(column);
    }

    @Test
    public void whenFirstDiscInsertedInColumnThenPositionIsZero() {
        int column = 1;
        assertThat(tested.putDiscInColumn(column), is(0));
    }

    @Test
    public void whenSecondDiscInsertedInColumnThenPositionIsOne() {
        int column = 1;
        tested.putDiscInColumn(column);
        assertThat(tested.putDiscInColumn(column), is(1));
    }

    @Test
    public void whenDiscInsertedThenNumberOfDiscsIncreases() {
        int column = 1;
        tested.putDiscInColumn(column);
        assertThat(tested.getNumberOfDiscs(), is(1));
    }

    @Test
    public void whenNoMoreRoomInColumnThenRuntimeException() {
        int column = 1;
        int maxDiscsInColumn = 6; // Liczba rzędów
        for (int times = 0; times < maxDiscsInColumn; ++times) {
            tested.putDiscInColumn(column);
        }
        exception.expect(RuntimeException.class);
        exception.expectMessage("Brak miejsca w kolumnie " + column);
        tested.putDiscInColumn(column);
    }

    /*
	 * Gra jest przeznaczona dla dwóch osób, posługujących się różnymi kolorami.
	 * Jeden gracz używa krążków czerwonych ('C'), natomiast drugi - zielonych ('Z').
	 * Gracze na zmianę wrzucają po jednym krążku.
     */

    @Test
    public void whenFirstPlayerPlaysThenDiscColorIsRed() {
        assertThat(tested.getCurrentPlayer(), is("C"));
    }

    @Test
    public void whenSecondPlayerPlaysThenDiscColorIsRed() {
        int column = 1;
        tested.putDiscInColumn(column);
        assertThat(tested.getCurrentPlayer(), is("Z"));
    }

    /*
	 * Zdarzenia i błędy powinny generować informacje zwrotne.
	 * W danych wyjściowych wyświetlany jest stan planszy po każdym posunięciu.
     */

    @Test
    public void whenAskedForCurrentPlayerTheOutputNotice() {
        tested.getCurrentPlayer();
        assertThat(output.toString(), containsString("Kolejka gracza C"));
    }

    @Test
    public void whenADiscIsIntroducedTheBoardIsPrinted() {
        int column = 1;
        tested.putDiscInColumn(column);
        assertThat(output.toString(), containsString("| |C| | | | | |"));
    }

    /*
	 * Gdy nie można wrzucić więcej krążków, gra kończy się remisem
     */

    @Test
    public void whenTheGameStartsItIsNotFinished() {
        assertFalse("Gra nie może być zakończona", tested.isFinished());
    }

    @Test
    public void whenNoDiscCanBeIntroducedTheGamesIsFinished() {
        for (int row = 0; row < 6; row++)
            for (int column = 0; column < 7; column++)
                tested.putDiscInColumn(column);
        assertTrue("Gra musi być zakończona", tested.isFinished());
    }

    /*
	 * Gdy gracz wrzuci krążek i połączy więcej niż trzy krążki w swoim kolorze
	 * w pionowej linii, wygrywa.
     */

    @Test
    public void when4VerticalDiscsAreConnectedThenThatPlayerWins() {
        for (int row = 0; row < 3; row++) {
            tested.putDiscInColumn(1); // C
            tested.putDiscInColumn(2); // Z
        }
        assertThat(tested.getWinner(), isEmptyString());
        tested.putDiscInColumn(1); // C
        assertThat(tested.getWinner(), is("C"));
    }

    /*
	 * Gdy gracz wrzuci krążek i połączy więcej niż trzy krążki w swoim kolorze
	 * w poziomej linii, wygrywa.
     */

    @Test
    public void when4HorizontalDiscsAreConnectedThenThatPlayerWins() {
        int column;
        for (column = 0; column < 3; column++) {
            tested.putDiscInColumn(column); // C
            tested.putDiscInColumn(column); // Z
        }
        assertThat(tested.getWinner(), isEmptyString());
        tested.putDiscInColumn(column); // C
        assertThat(tested.getWinner(), is("C"));
    }

    /*
	 * Gdy gracz wrzuci krążek i połączy więcej niż trzy krążki w swoim kolorze
	 * po przekątnej, wygrywa.
     */

    @Test
    public void when4Diagonal1DiscsAreConnectedThenThatPlayerWins() {
        int[] gameplay = new int[] {1, 2, 2, 3, 4, 3, 3, 4, 4, 5, 4};
        for (int column : gameplay) {
            tested.putDiscInColumn(column);
        }
        assertThat(tested.getWinner(), is("C"));
    }

    @Test
    public void when4Diagonal2DiscsAreConnectedThenThatPlayerWins() {
        int[] gameplay = new int[] {3, 4, 2, 3, 2, 2, 1, 1, 1, 1};
        for (int column : gameplay) {
            tested.putDiscInColumn(column);
        }
        assertThat(tested.getWinner(), is("Z"));
    }
}
