#pragma once

#include "Position.h"

#include <vector>
#include <set>
#include <iostream>
#include <array>
#include <bitset>


/**
 * A mathematical object consisting of touching squares on a 2D grid
 */
class Polyomino {
    public:
        static const std::size_t MAX_LENGTH = 16;
        using PolyominoData = std::bitset<MAX_LENGTH * MAX_LENGTH>;

        class ConstIterator {
            public:
                using iterator_category = std::forward_iterator_tag;
                using difference_type   = std::ptrdiff_t;
                using value_type        = Position;
                using const_pointer     = const Position*;  // or also value_type*
                using const_reference   = const Position&;  // or also value_type&
            public:
                ConstIterator(const Polyomino& polyomino, std::size_t index) : m_Polyomino(polyomino), m_Index(index) {
                    updatePosition();
                }

                const_reference operator*() const { return m_Position; }
                const_pointer operator->() { return &m_Position; }

                // Prefix increment
                ConstIterator& operator++() { 
                    m_Index = m_Polyomino.getPositionsData()._Find_next(m_Index);
                    updatePosition();
                    return *this; 
                }  


                friend bool operator== (const ConstIterator& a, const ConstIterator& b) { return a.m_Index == b.m_Index; };
                friend bool operator!= (const ConstIterator& a, const ConstIterator& b) { return a.m_Index != b.m_Index; };  

            private:
                void updatePosition() {
                    m_Position = Position(m_Index % m_Polyomino.getLength(), m_Index / m_Polyomino.getLength());
                }

                const Polyomino& m_Polyomino;
                std::size_t m_Index;
                Position m_Position;
        };

    private:
        PolyominoData positions; // the squares composing the polyomino, stored in binary. MSB is downleft
        std::int8_t length;      // the size of the smallest square box in which the polyomino can fit on any rotation
    
    public:
        /**
         * Creates a polyomino with the specified positions and length, wheter it is actually a polyonimo of this length is not checked
         */
        Polyomino(PolyominoData&& positions, std::int8_t length);

        /**
         * Creates a polyomino with the specified positions and normalizes it, wheter it is actually a polyonimo is not checked
         */
        Polyomino(std::set<Position>&& positions);

        /**
         * Translates the polyomino to the lowest unsigned values (lower row on y = 0, and left-most column on x = 0)
         */
        void normalize();

        /**
         * Rotates the polyomino 90° clockwise, the center of rotation being the middle of the box going from (0,0) to (length-1, length-1)
         */
        void rotateCW();

        /**
         * Rotates the polyomino 180°, the center of rotation being the middle of the box going from (0,0) to (length-1, length-1)
         */
        void rotate180();

        /**
         * Rotates the polyomino 90° counter-clockwise, the center of rotation being the middle of the box going from (0,0) to (length-1, length-1)
         */
        void rotateCCW();

        /**
         * Set the polyomino to a normalized default spawn position
         */
        void goToSpawnPosition();

    private:
        /**
         * Auxiliary method of goToSpawnPosition()
         */
        void checkForFlattestSide(const std::vector<std::vector<int>>& linesCompleteness, bool currentFlattestSides[4], int& sideToBeOn, bool checkLeftSide) const;

    public:
        /**
         * Check if the polyomino is convex, that is if every line and column has at most one continuous line of positions
         * @return If the polyomino is convex
         */
        bool isConvex() const;

        /**
         * Check if the polyomino has at least one hole
         * @return If the polyomino has at least one hole
         */
        bool hasHole() const;
    
    private:
        /**
         * Auxiliary method of hasHole()
         */
        void tryToInsertPosition(Polyomino& emptypositions, const Position& candidate) const;

    public:
        /**
         * @return The positions data of the polyomino
         */
        const PolyominoData& getPositionsData() const;

        /**
         * @return The length of the polyomino
         */
        int getLength() const;

        /**
         * 
         */
        int getSize() const;

        /**
         * 
         */
        std::size_t getBitIndex(const Position& position) const;

        /**
         * @return The number of squares in the polyomino
         */
        int getPolyominoSize() const;

        /**
         * Strict inferiority operator, a polyomino is inferior than another if it has a smaller length, or if they are the same length,
         * while checking from left to right and top to bottom, is the first which has a square while the other don't
         * @return If the polyomino is inferior than another
         */
        bool operator<(const Polyomino& other) const;

        /**
         * Equality operator, two polyominoes are equal if their positions are the same, that means two polyominoes of the same shape at different places will not be equal
         * @return If the polyomino is equal to another
         */
        bool operator==(const Polyomino& other) const;

        /**
         * Stream output operator, adds a 2D grid representing the polyomino
         * @return A reference to the output stream
         */
        friend std::ostream& operator<<(std::ostream& os, const Polyomino& polyomino);

        /**
         * @return True if it contains the position
         */
        bool contains(const Position& position) const;

        /**
         * @brief Insert a square at the position
         */
        void insert(const Position& position);

        /**
         * @brief Removes a square at the position
         */
        void erase(const Position& position);

        ConstIterator begin() const;

        ConstIterator end() const;
};
