#include "view_zone_logic.h"

#include "../../../settings/cache_settings.h"
#include "../../board/card_item.h"

/**
 * @param _player the player that the cards are revealed to.
 * @param _origZone the zone the cards were revealed from.
 * @param _revealZone if false, the cards will be face down.
 * @param _writeableRevealZone whether the player can interact with the revealed cards.
 */
ZoneViewZoneLogic::ZoneViewZoneLogic(Player *_player,
                                     CardZoneLogic *_origZone,
                                     int _numberCards,
                                     bool _revealZone,
                                     bool _writeableRevealZone,
                                     bool _isReversed,
                                     QObject *parent)
    : CardZoneLogic(_player, _origZone->getName(), false, false, true, parent), origZone(_origZone),
      numberCards(_numberCards), revealZone(_revealZone), writeableRevealZone(_writeableRevealZone),
      isReversed(_isReversed)
{
}

/**
 * Checks if inserting a card at the given position requires an actual new card to be created and added to the view.
 * Also does any cardId updates that would be required if a card is inserted in that position.
 *
 * Note that this method can end up modifying the cardIds despite returning false.
 * (for example, if the card is inserted into a hidden portion of the deck while the view is reversed)
 *
 * Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling
 * addCard() if it's not required.
 *
 * @param x The position to insert the card at.
 * @return Whether to proceed with calling addCard.
 */
bool ZoneViewZoneLogic::prepareAddCard(int x)
{
    bool doInsert = false;
    if (!isReversed) {
        if (x <= cards.size() || cards.size() == -1) {
            doInsert = true;
        }
    } else {
        // map x (which is in origZone indexes) to this viewZone's cardList index
        int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId();
        int insertionIndex = x - firstId;
        if (insertionIndex >= 0) {
            // card was put into a portion of the deck that's in the view
            doInsert = true;
        } else {
            // card was put into a portion of the deck that's not in the view; update ids but don't insert card
            updateCardIds(ADD_CARD);
        }
    }

    // autoclose check is done both here and in removeCard

    if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) {
        emit closeView();
    }

    return doInsert;
}

/**
 * Make sure prepareAddCard() was called before calling addCard().
 * This method assumes we already checked that the card is being inserted into the visible portion
 */
void ZoneViewZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
    if (!isReversed) {
        // if x is negative set it to add at end
        // if x is out-of-bounds then also set it to add at the end
        if (x < 0 || x >= cards.size()) {
            x = cards.size();
        }
        cards.insert(x, card);
    } else {
        // map x (which is in origZone indexes) to this viewZone's cardList index
        int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId();
        int insertionIndex = x - firstId;
        // qMin to prevent out-of-bounds error when bottoming a card that is already in the view
        cards.insert(qMin(insertionIndex, cards.size()), card);
    }

    updateCardIds(ADD_CARD);
    reorganizeCards();
}

void ZoneViewZoneLogic::updateCardIds(CardAction action)
{
    if (origZone->contentsKnown()) {
        return;
    }

    if (cards.isEmpty()) {
        return;
    }

    int cardCount = cards.size();

    auto startId = 0;

    if (isReversed) {
        // the card has not been added to origZone's cardList at this point
        startId = origZone->getCards().size() - cardCount;
        switch (action) {
            case INITIALIZE:
                break;
            case ADD_CARD:
                startId += 1;
                break;
            case REMOVE_CARD:
                startId -= 1;
                break;
        }
    }

    for (int i = 0; i < cardCount; ++i) {
        cards[i]->setId(i + startId);
    }
}

void ZoneViewZoneLogic::removeCard(int position, bool toNewZone)
{
    if (isReversed) {
        position -= cards.first()->getId();
        if (position < 0 || position >= cards.size()) {
            updateCardIds(REMOVE_CARD);
            return;
        }
    }

    if (position >= cards.size()) {
        return;
    }

    CardItem *card = cards.takeAt(position);
    card->deleteLater();

    // The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that
    // card gets dragged within the view.
    // Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an
    // unrevealed portion of the same zone.
    if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) {
        emit closeView();
        return;
    }

    updateCardIds(REMOVE_CARD);
    reorganizeCards();
}

void ZoneViewZoneLogic::setWriteableRevealZone(bool _writeableRevealZone)
{

    if (writeableRevealZone && !_writeableRevealZone) {
        emit addToViews();
    } else if (!writeableRevealZone && _writeableRevealZone) {
        emit removeFromViews();
    }
    writeableRevealZone = _writeableRevealZone;
}