#pragma once

#include "QuadtreeOccupant.h"

#include <memory>
#include <array>
#include <unordered_set>

namespace ltbl {
    class QuadtreeNode : public sf::NonCopyable {

        friend class QuadtreeOccupant;
        friend class Quadtree;
        friend class DynamicQuadtree;

    private:
        QuadtreeNode* _pParent;
        class Quadtree* _pQuadtree;

        bool _hasChildren = false;

        std::array<std::unique_ptr<QuadtreeNode>, 4> _children;
        std::unordered_set<QuadtreeOccupant*> _occupants;

        sf::FloatRect _region;

        int _level;

        int _numOccupantsBelow = 0;

        void getPossibleOccupantPosition(QuadtreeOccupant* oc, sf::Vector2i &point);

        void addToThisLevel(QuadtreeOccupant* oc);

        // Returns true if occupant was added to children
        bool addToChildren(QuadtreeOccupant* oc);

        void destroyChildren() {
            for (int i = 0; i < 4; i++)
                _children[i].reset();

            _hasChildren = false;
        }

        void getOccupants(std::unordered_set<QuadtreeOccupant*> &occupants);

        void partition();

        void merge();

        void update(QuadtreeOccupant* oc);
        void remove(QuadtreeOccupant* oc);

        void removeForDeletion(std::unordered_set<QuadtreeOccupant*> &occupants);

    public:
        QuadtreeNode()
            : _hasChildren(false), _numOccupantsBelow(0)
        {}

        QuadtreeNode(const sf::FloatRect &region, int level, QuadtreeNode* pParent, class Quadtree* pQuadtree);

        // For use after using default constructor
        void create(const sf::FloatRect &region, int level, QuadtreeNode* pParent, class Quadtree* pQuadtree);

        class Quadtree* getTree() const {
            return _pQuadtree;
        }

        void add(QuadtreeOccupant* oc);

        const sf::FloatRect &getRegion() const {
            return _region;
        }

        void getAllOccupantsBelow(std::vector<QuadtreeOccupant*> &occupants);
        void getAllOccupantsBelow(std::unordered_set<QuadtreeOccupant*> &occupants);

        int getNumOccupantsBelow() const {
            return _numOccupantsBelow;
        }
    };
}
