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.Cards.TribeCards.Events.ShamanicRitual;
import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Events.Sustenance;
import it.polimi.ingsw.gc14.Model.DecksCreator;
import it.polimi.ingsw.gc14.Model.Slot;

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

/**
 * Board manages all the elements during the game such as decks, upper and lower rows, totems, tiles...
 */
public class Board implements Serializable {
    /**
     * slotList contains the ordered list of slots (tiles). The slots changes based on the number of players.
     * Each slot (tile) has special action as drawing from the upper/lower row or taking food.
     */
    private List<Slot> slotList;

    /** tribeDeck is the deck from where you draw tribe cards as characters and events. */
    private Queue<TribeCard> tribeDeck;

    /** The upper row of tribe cards. There must be (num. of players + 4) character cards + Event cards */
    public List<TribeCard> upperListTribe;

    /**
     * The lower row of the tribe card. During the first round, there will be (num. of players + 1) character cards.
     * During the following rounds, lower row will be emptied and populated with upper row's cards.
     * When an Event card gets in the lower row, the event effect will be activated at the end of the round.
     */
    public List<TribeCard> lowerListTribe;

    /** Contains all the building cards of the upper list. When a new era starts, all its building cards are placed here */
    public List<BuildingCard> upperListBuilding;

    /** Contains all the building cards of the upper list. When a new era starts, the old era's buildings are moved from the upper to the lower list */
    public List<BuildingCard> lowerListBuilding;

    /** Number of players */
    private int nTotem;

    /** Current era of the game */
    private int era;

    /**
     * Creates and returns a new list containing the slots (tiles) of the game
     *
     * @return a copy of the slot list
     */
    public List<Slot> getSlotList(){
        return slotList.stream().map(x->new Slot(x.getSlotId())).collect(Collectors.toList());
    }

    /**
     * @return the size of the TribeDeck
     */
    public int getTribeDeckSize() {
        return this.tribeDeck.size();
    }

    /**
     * @return the game era
     */
    public int getEra() {return this.era;}


    /**
     *  Creates and initializes a new board:
     *      - Generate the slotList using a method of DecksCreator
     *      - Generate the tribeDeck
     *      - Populate the lower list (if there is an event card, it's added to the upper list)
     *      - Populate the upper list
     *      - Generate the buildingDeck using a method of DecksCreator
     *
     * @param nTotem        the number of players
     */
    public Board(int nTotem) {
        this.nTotem = nTotem;
        this.slotList = DecksCreator.loadSlotDeck().stream().filter(x->x.getNMinPlayer()<=nTotem).sorted(Comparator.comparingInt(Slot::getSlotId)).toList();
        upperListTribe=new ArrayList<>();
        lowerListTribe=new ArrayList<>();
        lowerListBuilding=new ArrayList<>();
        tribeDeck=generateTribeDeck(nTotem);
        era=1;

        for(int i=0;i<nTotem+1;i++)
        {
            TribeCard tempCard = tribeDeck.remove();
            if(tempCard.IsEventCard())
            {
                upperListTribe.add(tempCard);
                i--;
            }
            else
            {
                lowerListTribe.add(tempCard);
            }
        }

        for(int i=upperListTribe.size();i<nTotem+4;i++)
        {
            TribeCard tempCard = tribeDeck.remove();
            upperListTribe.add(tempCard);
        }

        ArrayList<BuildingCard> buildingDeck = new ArrayList<>(DecksCreator.loadBuildingDeckByEra(1));
        Collections.shuffle(buildingDeck);
        if(nTotem==2)
            upperListBuilding= buildingDeck.subList(0,1);
        else
            upperListBuilding= buildingDeck.subList(0,2);
    }

    /**
     * Creates the tribeDeck. Loads the cards from each era, shuffle them, and then combine them in the final deck.
     * In the end, two special events (Sustenance and ShamanicRitual) are added.
     *
     * @param nPlayers      the number of players
     * @return a queue containing the cards in this order:
     *      - Era 1
     *      - Era 2
     *      - Era 3
     *      - Special events
     */
    private Queue<TribeCard>  generateTribeDeck(int nPlayers) {
        ArrayList<TribeCard> era1= DecksCreator.loadTribeDeckByEra(1).stream().filter(x->x.getNMin()<=nPlayers).collect(Collectors.toCollection(ArrayList::new));
        ArrayList<TribeCard> era2= DecksCreator.loadTribeDeckByEra(2).stream().filter(x->x.getNMin()<=nPlayers).collect(Collectors.toCollection(ArrayList::new));
        ArrayList<TribeCard> era3= DecksCreator.loadTribeDeckByEra(3).stream().filter(x->x.getNMin()<=nPlayers).collect(Collectors.toCollection(ArrayList::new));
        Collections.shuffle(era1);
        Collections.shuffle(era2);
        Collections.shuffle(era3);

        Queue<TribeCard> tribeDeck_temp=new LinkedList<>();
        tribeDeck_temp.addAll(era1);
        tribeDeck_temp.addAll(era2);
        tribeDeck_temp.addAll(era3);
        tribeDeck_temp.add(new Sustenance(3,3));
        tribeDeck_temp.add(new ShamanicRitual(3,15,7));
        return tribeDeck_temp;
    }

    /** @return the queue of events cards from the lower list that need to be activated */
    public Queue<EventCard> getPendingEvents(){
        return lowerListTribe.stream().filter(TribeCard::IsEventCard).map(x->(EventCard)x).collect(Collectors.toCollection(LinkedList::new));
    }

    /**
     * Take the selected card from the upper tribe list of the board
     *
     * @param tribeCard is the card to remove
     * @return true if the card is succesfully removed
     */
    public boolean removeUpperTribeCard(TribeCard tribeCard) {
        return upperListTribe.remove(tribeCard);
    }

    /**
     * Take the selected card from the lower tribe list of the board
     *
     * @param tribeCard is the card to remove
     * @return true if succeed in removing the card
     */
    public boolean removeLowerTribeCard(TribeCard tribeCard) {
        return lowerListTribe.remove(tribeCard);
    }

    /**
     * Take the selected card from the upper building list of the board
     *
     * @param buildingCard is the card to remove
     * @return true if succeed in removing the card
     */
    public boolean removeUpperBuildingCard(BuildingCard buildingCard) {
        return upperListBuilding.remove(buildingCard);
    }

    /**
     * Take the selected card from the lower building list of the board
     *
     * @param buildingCard is the card to remove
     * @return true if succeed in removing the card
     */
    public boolean removeLowerBuildingCard(BuildingCard buildingCard) {
        return lowerListBuilding.remove(buildingCard);
    }

    /**
     * Goes to the next round.
     *      - Clear the lower tribe row
     *      - Moves the upper tribe row to the lower one
     *      - Populate the upper tribe row
     *
     */
    public int nextRound() {
        lowerListTribe.clear();
        lowerListTribe.addAll(upperListTribe);
        upperListTribe.clear();

        for(int i=0;i<nTotem+4;i++)
        {
            TribeCard tempCard = tribeDeck.remove();
            if(tempCard.getEra()!=era)
            {
                nextEra();
            }
            upperListTribe.add(tempCard);
        }

        return era;
    }

    /**
     * Changes the era of the game:
     *      - Increase the era attribute
     *      - Clear the lower building list and replace it with the upper building list
     *      - Repopulate the upper building list
     */
    private void nextEra() {
        era=era+1;
        lowerListBuilding.clear();
        lowerListBuilding.addAll(upperListBuilding);
        upperListBuilding.clear();

        ArrayList<BuildingCard> buildingCards= new ArrayList<>(DecksCreator.loadBuildingDeckByEra(era));
        Collections.shuffle(buildingCards);
        if(era==2)
        {
            if(nTotem<=3)
            {
                upperListBuilding.addAll(buildingCards.subList(0,2));
            }
            else
            {
                upperListBuilding.addAll( buildingCards.subList(0,3));
            }
        }
        else    // Era 3
        {
            if(nTotem==2)
            {
                upperListBuilding.addAll( buildingCards.subList(0,3));
            }else if(nTotem==5)
            {
                upperListBuilding.addAll( buildingCards.subList(0,5));
            }
            else {
                upperListBuilding.addAll(buildingCards.subList(0,4));
            }
        }
    }
}
