#include "Bag.h"

#include "../Pieces/Piece.h"
#include "PiecesList.h"

#include <vector>
#include <utility>
#include <cstdlib>

static const double SMALLEST_CONSIDERED_PROPORTION = 0.01; // the smallest a proportion can get before it is considered equal to 0


Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
    piecesList(piecesList) {

    this->highestSize = this->piecesList->getHighestLoadedSize();
    this->selectedPieces = this->piecesList->getSelectedPieces();
    this->distributionMode = this->piecesList->getDistributionMode();
    this->propotionsPerSize = this->piecesList->getProportionsPerSize();

    this->currentBags.clear();
    this->nextBags.clear();
    this->sizesBag.clear();
    this->sizesProgression.clear();

    if (this->distributionMode == DEFAULT) {
        this->currentBags.push_back(this->selectedPieces);
        this->nextBags.push_back({});
    }
    else {
        for (int i = 0; i <= this->highestSize; i++) {
            this->currentBags.push_back(PieceBag());
            this->nextBags.push_back(PieceBag());
            this->sizesProgression.push_back(0);
        }

        for (const auto& piece : this->selectedPieces) {
            int pieceSize = this->piecesList->lookAtPiece(piece).getPositions().getSize();
            this->currentBags.at(pieceSize).push_back(piece);
        }
    }
        
    this->prepareNext();
}

void Bag::jumpToNextBag() {
    for (int i = 0; i < this->currentBags.size(); i++) {
        if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) {
            std::swap(this->currentBags.at(i), this->nextBags.at(i));
        }
    
        for (const auto& piece : this->nextBags.at(i)) {
            this->currentBags.at(i).push_back(piece);
        }
        this->nextBags.at(i).clear();
    }

    this->prepareNext();
}

Piece Bag::lookNext() {
    return this->piecesList->getPiece(this->next);
}

Piece Bag::getNext() {
    std::pair<int, int> nextIndex = this->next;

    this->prepareNext();

    return this->piecesList->getPiece(nextIndex);
}

void Bag::prepareNext() {
    if (this->distributionMode == DEFAULT) {
        this->getNextPieceFromBag(0);
    }
    else {
        if (this->sizesBag.empty()) {
            for (int i = 0; i <= this->highestSize; i++) {
                if (this->propotionsPerSize.at(i) >= SMALLEST_CONSIDERED_PROPORTION
                    && !(this->currentBags.at(i).empty() && this->nextBags.at(i).empty())) {
                    while (this->sizesProgression.at(i) < 1) {
                        this->sizesBag.push_back(i);
                        this->sizesProgression.at(i) += this->propotionsPerSize.at(i);
                    }

                    this->sizesProgression.at(i) -= 1;
                }
            }
        }

        int nextSizeIndex = std::rand() % this->sizesBag.size();
        int nextSize = this->sizesBag.at(nextSizeIndex);
        this->sizesBag.erase(this->sizesBag.begin() + nextSizeIndex);

        this->getNextPieceFromBag(nextSize);
    }
}

void Bag::getNextPieceFromBag(int bagIndex) {
    if (this->currentBags.at(bagIndex).empty()) {
        std::swap(this->currentBags.at(bagIndex), this->nextBags.at(bagIndex));
    }

    int indexIndex = std::rand() % this->currentBags.at(bagIndex).size();
    this->next = this->currentBags.at(bagIndex).at(indexIndex);

    this->nextBags.at(bagIndex).push_back(this->next);
    this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex);
}
