package it.polimi.ingsw.gc14.Model.GamePackage;

import it.polimi.ingsw.gc14.Model.Cards.BuildingCard;
import it.polimi.ingsw.gc14.Model.Cards.TribeCard;
import it.polimi.ingsw.gc14.Model.Cards.TribeCards.EventCard;
import it.polimi.ingsw.gc14.Model.DecksCreator;
import it.polimi.ingsw.gc14.Model.Player;
import it.polimi.ingsw.gc14.Model.Slot;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

class BoardTest {

    @Test
    @DisplayName("Slot initialization")
    void getSlotList() {
        // 0 players
        Board b0 = new Board(0);
        assertEquals('B', b0.getSlotList().get(0).getSlotId());
        assertEquals('C', b0.getSlotList().get(1).getSlotId());
        assertEquals('E', b0.getSlotList().get(2).getSlotId());
        assertEquals('F', b0.getSlotList().get(3).getSlotId());

        // 1 players
        Board b1 = new Board(1);
        assertEquals('B', b1.getSlotList().get(0).getSlotId());
        assertEquals('C', b1.getSlotList().get(1).getSlotId());
        assertEquals('E', b1.getSlotList().get(2).getSlotId());
        assertEquals('F', b1.getSlotList().get(3).getSlotId());

        // 2 players
        Board b2 = new Board(2);
        assertEquals('B', b2.getSlotList().get(0).getSlotId());
        assertEquals('C', b2.getSlotList().get(1).getSlotId());
        assertEquals('E', b2.getSlotList().get(2).getSlotId());
        assertEquals('F', b2.getSlotList().get(3).getSlotId());

        // 3 players
        Board b3 = new Board(3);
        assertEquals('B', b3.getSlotList().get(0).getSlotId());
        assertEquals('C', b3.getSlotList().get(1).getSlotId());
        assertEquals('D', b3.getSlotList().get(2).getSlotId());
        assertEquals('E', b3.getSlotList().get(3).getSlotId());
        assertEquals('F', b3.getSlotList().get(4).getSlotId());

        // 4 players
        Board b4 = new Board(4);
        assertEquals('B', b4.getSlotList().get(0).getSlotId());
        assertEquals('C', b4.getSlotList().get(1).getSlotId());
        assertEquals('D', b4.getSlotList().get(2).getSlotId());
        assertEquals('E', b4.getSlotList().get(3).getSlotId());
        assertEquals('F', b4.getSlotList().get(4).getSlotId());
        assertEquals('G', b4.getSlotList().get(5).getSlotId());

        // 5 players
        Board b5 = new Board(5);
        assertEquals('A', b5.getSlotList().get(0).getSlotId());
        assertEquals('B', b5.getSlotList().get(1).getSlotId());
        assertEquals('C', b5.getSlotList().get(2).getSlotId());
        assertEquals('D', b5.getSlotList().get(3).getSlotId());
        assertEquals('E', b5.getSlotList().get(4).getSlotId());
        assertEquals('F', b5.getSlotList().get(5).getSlotId());
        assertEquals('G', b5.getSlotList().get(6).getSlotId());
    }

    @Test
    @DisplayName("Testing TribeDeck size with different player number")
    void getTribeDeckSize() {
        Board bd2 = new Board(2);
        Board bd3 = new Board(3);
        Board bd4 = new Board(4);
        Board bd5 = new Board(5);

        assertEquals(61 + 2 - (2+4) - (2+1), bd2.getTribeDeckSize());
        assertEquals(72 + 2 - (3+4) - (3+1), bd3.getTribeDeckSize());
        assertEquals(83 + 2 - (4+4) - (4+1), bd4.getTribeDeckSize());
        assertEquals(94 + 2 - (5+4) - (5+1), bd5.getTribeDeckSize());
    }

    @Test
    @DisplayName("Testing era getter")
    void getEra() {
        Board bd = new Board(3);
        assertEquals(1, bd.getEra());
    }

    @Test
    @DisplayName("Testing constructor")
    void testConstructor() {
        int numPlayer = 5;
        Board bd = new Board(numPlayer);

        assertEquals(96 - (numPlayer+4) - (numPlayer+1), bd.getTribeDeckSize());  // Verifica che dal mazzo TribeDeck venga pescato il numero corretto di carte

        assertEquals(numPlayer+1, bd.lowerListTribe.size());    // Verifica che la dimensione della lista inferiore sia corretta
        assertTrue(bd.lowerListTribe.stream().noneMatch(card -> card.IsEventCard()));   // Verifica che non ci siano EventCard dentro alla lista inferiore

        assertEquals(numPlayer+4, bd.upperListTribe.size());    // Verifica che la dimensione della lista superiore sia corretta

        assertEquals(2, bd.upperListBuilding.size());   // Verifica che ci sia il giusto numero di buildings (2 edifici per 3+ giocatori)
    }

    @Test
    @DisplayName("get events testing")
    void getPendingEvents() {
        Board bd = new Board(0);

        Queue<EventCard> eventQueue = new LinkedList<>();
        for (TribeCard card : bd.lowerListTribe) {
            if (card.IsEventCard()) {
                eventQueue.add((EventCard) card);
            }
        }

        assertEquals(eventQueue, bd.getPendingEvents());
    }

    @Test
    @DisplayName("Removing a card from upper row of tribe cards")
    void removeUpperTribeCard() {
        Board bd = new Board(3);

        List<TribeCard> before = bd.upperListTribe;
        TribeCard cardToRemove = before.get(2);

        before.remove(2);
        bd.removeUpperTribeCard(cardToRemove);

        assertEquals(before, bd.upperListTribe);
    }

    @Test
    @DisplayName("Removing a card from lower row of tribe cards")
    void removeLowerTribeCard() {
        Board bd = new Board(3);

        List<TribeCard> before = bd.lowerListTribe;
        TribeCard cardToRemove = before.get(2);

        before.remove(2);
        bd.removeLowerTribeCard(cardToRemove);

        assertEquals(before, bd.lowerListTribe);
    }

    @Test
    @DisplayName("RemgetTribeDeckSizeoving a card from upper row of building cards")
    void removeUpperBuildingCard() {
        Board bd = new Board(3);

        List<BuildingCard> before = bd.upperListBuilding;
        BuildingCard cardToRemove = before.get(0);

        before.remove(0);
        bd.removeUpperBuildingCard(cardToRemove);

        assertEquals(before, bd.upperListBuilding);
    }

    @Test
    @DisplayName("Removing a card from lower row of building cards")
    void removeLowerBuildingCard() {
        Board bd = new Board(3);

        for (int i=0;i<3;i++) {
            bd.nextRound();    // Skip to era 2
        }

        List<BuildingCard> before = bd.lowerListBuilding;
        BuildingCard cardToRemove = before.get(0);

        before.remove(0);
        bd.removeLowerBuildingCard(cardToRemove);

        assertEquals(before, bd.lowerListBuilding);
    }

    @Test
    @DisplayName("Testing next round")
    void nextRound() {
        int numPlayer = 3;
        Board bd = new Board(numPlayer);
        assertTrue(!bd.lowerListTribe.isEmpty());
        List<TribeCard> upperListBefore = new ArrayList<>(bd.upperListTribe);

        bd.nextRound();
        assertEquals(upperListBefore, bd.lowerListTribe);   // Verifica che la lista superiore è stata spostata sotto
        assertNotEquals(upperListBefore, bd.upperListTribe);    // Verifica che la lista superiore sia stata cambiata
        assertEquals(numPlayer+4, bd.upperListTribe.size());    // Verifica che la nuova dimensione della lista superiore sia corretta
    }

    @Test
    @DisplayName("Testing nextEra")
    void nextEra() {
        // Since nextEra is private, it must be tested through nextRound.
        // In this test we simulate multiple rounds to trigger the next era activation.
        // NOTE: after 3 rounds we are in era 2. After 6 we are in era 3.
        int numPlayer;
        List<BuildingCard> upperListBefore;

        // Era 2, nTotem <= 3
        numPlayer = 3;
        Board bd1 = new Board(numPlayer);
        upperListBefore = new ArrayList<>(bd1.upperListBuilding);
        for (int i=0;i<3;i++) {
            bd1.nextRound();    // Skip to era 2
        }
        assertEquals(2, bd1.getEra());
        assertEquals(upperListBefore, bd1.lowerListBuilding);
        assertNotEquals(upperListBefore, bd1.upperListBuilding);
        assertEquals(2, bd1.upperListBuilding.size());


        // Era 2, nTotem > 3
        numPlayer = 4;
        Board bd2 = new Board(numPlayer);
        upperListBefore = new ArrayList<>(bd2.upperListBuilding);
        for (int i=0;i<3;i++) {
            bd2.nextRound();    // Skip to era 2
        }
        assertEquals(2, bd2.getEra());
        assertEquals(upperListBefore, bd2.lowerListBuilding);
        assertNotEquals(upperListBefore, bd2.upperListBuilding);
        assertEquals(3, bd2.upperListBuilding.size());


        // Era 3, nTotem == 2
        numPlayer = 2;
        Board bd3 = new Board(numPlayer);
        for (int i=0;i<3;i++) {
            bd3.nextRound();    // Skip to era 2
        }
        upperListBefore = new ArrayList<>(bd3.upperListBuilding);   // We need to take the list of the era 2
        for (int i=0;i<3;i++) {
            bd3.nextRound();    // Skip to era 3
        }
        assertEquals(3, bd3.getEra());
        assertEquals(upperListBefore, bd3.lowerListBuilding);
        assertNotEquals(upperListBefore, bd3.upperListBuilding);
        assertEquals(3, bd3.upperListBuilding.size());


        // Era 3, nTotem == 5
        numPlayer = 5;
        Board bd4 = new Board(numPlayer);
        for (int i=0;i<3;i++) {
            bd4.nextRound();    // Skip to era 2
        }
        upperListBefore = new ArrayList<>(bd4.upperListBuilding);   // We need to take the list of the era 2
        for (int i=0;i<3;i++) {
            bd4.nextRound();    // Skip to era 3
        }
        assertEquals(3, bd4.getEra());
        assertEquals(upperListBefore, bd4.lowerListBuilding);
        assertNotEquals(upperListBefore, bd4.upperListBuilding);
        assertEquals(5, bd4.upperListBuilding.size());


        // Era 3, nTotem != 2, 5
        numPlayer = 3;
        Board bd5 = new Board(numPlayer);
        for (int i=0;i<3;i++) {
            bd5.nextRound();    // Skip to era 2
        }
        upperListBefore = new ArrayList<>(bd5.upperListBuilding);   // We need to take the list of the era 2
        for (int i=0;i<3;i++) {
            bd5.nextRound();    // Skip to era 3
        }
        assertEquals(3, bd5.getEra());
        assertEquals(upperListBefore, bd5.lowerListBuilding);
        assertNotEquals(upperListBefore, bd5.upperListBuilding);
        assertEquals(4, bd5.upperListBuilding.size());


    }

}