#include "Generator.h"

#include "Polyomino.h"

#include <vector>
#include <set>
#include <map>
#include <algorithm>


Generator::Generator() {
}

std::vector<Polyomino>&& Generator::generatePolyominoes(int polyominoSize) {
    this->validPolyominoes.clear();
    this->currentTestedShape.clear();

    // a polyomino has at least 1 square
    if (polyominoSize < 1) return std::move(this->validPolyominoes);

    // always place the first cell at (0, 0)
    this->currentTestedShape.insert(Position(0, 0));
    
    std::map<Position, int> candidatePositions;
    this->generate(polyominoSize, 0, 1, candidatePositions);
    return std::move(this->validPolyominoes);
}

void Generator::generate(unsigned polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions) {
    // recursion stop
    if (polyominoSize == this->currentTestedShape.size()) {
        Polyomino candidate(std::move(this->currentTestedShape));

        // we sort the rotations of the polyominoes
        std::vector<Polyomino> candidateRotations;
        candidateRotations.reserve(4);
        for (int i = 0; i < 4; i++) {
            candidate.normalize();
            candidateRotations.push_back(candidate);
            if (!(i==3)) candidate.rotateCW();
        }
        std::sort(candidateRotations.begin(), candidateRotations.end());

        // we keep the polyomino only if it was generated in its lowest rotation
        if (candidate == candidateRotations.at(0)) {
            this->validPolyominoes.push_back(candidate);
        }

        return;
    }

    // generate the list of candidate positions
    for (const Position position : this->currentTestedShape) {
        this->tryToAddCandidatePosition(Position(position.x, position.y + 1), nextAvaibleNumber, candidatePositions);
        this->tryToAddCandidatePosition(Position(position.x + 1, position.y), nextAvaibleNumber, candidatePositions);
        this->tryToAddCandidatePosition(Position(position.x, position.y - 1), nextAvaibleNumber, candidatePositions);
        this->tryToAddCandidatePosition(Position(position.x - 1, position.y), nextAvaibleNumber, candidatePositions);
    }
    
    // try adding a square only to positions with a higher number than the last one
    for (const auto& [key, val] : candidatePositions) {
        if (val > lastAddedPositionNumber) {
            this->currentTestedShape.insert(key);
            this->generate(polyominoSize, val, nextAvaibleNumber, (polyominoSize == this->currentTestedShape.size()) ? std::map<Position, int>() : candidatePositions);
            this->currentTestedShape.erase(key);
        }
    }
}

void Generator::tryToAddCandidatePosition(const Position& candidate, int& nextAvaibleNumber, std::map<Position, int>& candidatepositions) {
    // we declared the first position as the lower-left square, since we always start with a monomino at (0,0) we can test with 0 directly
    if (candidate.y < 0 || (candidate.y == 0 && candidate.x < 0)) return;

    // if the position was already marked then we should not mark it again
    if (candidatepositions.contains(candidate)) return;

    // if the candidate overlaps with the shape there is no reason to add it
    if (this->currentTestedShape.contains(candidate)) return;

    // once all tests passed we can add the position
    candidatepositions.insert({candidate, nextAvaibleNumber});
    nextAvaibleNumber++;
}
