/*
 *  nextpnr -- Next Generation Place and Route
 *
 *  Copyright (C) 2018  Serge Bazanski <q3k@q3k.org>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#ifndef QUADTREE_H
#define QUADTREE_H

// This file implements a quad tree used for committing 2D axis aligned
// bounding boxes and then retrieving them by 2D point.

NEXTPNR_NAMESPACE_BEGIN

// A node of a QuadTree. Internal.
template <typename CoordinateT, typename ElementT> class QuadTreeNode
{
  public:
    class BoundingBox
    {
        friend class QuadTreeNode;

      private:
        CoordinateT x0_, y0_, x1_, y1_;

        static constexpr float pinf = std::numeric_limits<CoordinateT>::infinity();
        static constexpr float ninf = -std::numeric_limits<CoordinateT>::infinity();

      public:
        // Standard constructor for a given (x0,y0), (x1,y1) bounding box
        //
        // @param x0 x coordinate of top-left corner of box
        // @param y0 y coordinate of top-left corner of box
        // @param x1 x coordinate of bottom-right corner of box
        // @param y1 y coordinate of bottom-right corner of box
        BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), y0_(y0), x1_(x1), y1_(y1)
        {
        }

        BoundingBox() : x0_(pinf), y0_(pinf), x1_(ninf), y1_(ninf) {}

        // Whether a bounding box contains a given points.
        // A point is defined to be in a bounding box when it's not lesser than
        // the lower coordinate or greater than the higher coordinate, eg:
        // A BoundingBox of x0: 20, y0: 30, x1: 100, y1: 130 fits the following
        // points:
        //   [ (50, 50), (20, 50), (20, 30), (100, 130) ]
        inline bool contains(CoordinateT x, CoordinateT y) const
        {
            if (x < x0_ || x > x1_)
                return false;
            if (y < y0_ || y > y1_)
                return false;
            return true;
        }

        // Sort the bounding box coordinates.
        void fixup()
        {
            if (x1_ < x0_)
                std::swap(x0_, x1_);
            if (y1_ < y0_)
                std::swap(y0_, y1_);
        }

        CoordinateT x0() const { return x0_; }
        CoordinateT y0() const { return y0_; }
        CoordinateT x1() const { return x1_; }
        CoordinateT y1() const { return y1_; }

        void setX0(CoordinateT v) { x0_ = v; }
        void setY0(CoordinateT v) { y0_ = v; }
        void setX1(CoordinateT v) { x1_ = v; }
        void setY1(CoordinateT v) { y1_ = v; }

        void clear()
        {
            x0_ = pinf;
            y0_ = pinf;
            x1_ = ninf;
            y1_ = ninf;
        }

        CoordinateT w() const { return x1_ - x0_; }
        CoordinateT h() const { return y1_ - y0_; }
    };

  private:
    // A pair of Element and BoundingBox that contains it.
    class BoundElement
    {
        friend class QuadTreeNode;

      private:
        BoundingBox bb_;
        ElementT elem_;

      public:
        BoundElement(BoundingBox bb, ElementT elem) : bb_(bb), elem_(elem) {}
    };

    // The bounding box that this node describes.
    BoundingBox bound_;
    // How many elements should be contained in this node until it splits into
    // sub-nodes.
    size_t max_elems_;
    // Four sub-nodes or nullptr if it hasn't split yet.
    std::unique_ptr<QuadTreeNode<CoordinateT, ElementT>[]> children_ = nullptr;
    // Coordinates of the split.
    // Anything < split_x is west.
    CoordinateT splitx_;
    // Anything < split_y is north.
    CoordinateT splity_;

    // Elements contained directly within this node and not part of children
    // nodes.
    std::vector<BoundElement> elems_;
    // Depth at which this node is - root is at 0, first level at 1, etc.
    int depth_;

    // Checks whether a given bounding box fits within this node - used for
    // sanity checking on insertion.
    // @param b bounding box to check
    // @returns whether b fits in this node entirely
    bool fits(const BoundingBox &b) const
    {
        if (b.x0_ < bound_.x0_ || b.x0_ > bound_.x1_) {
            return false;
        } else if (b.x1_ < bound_.x0_ || b.x1_ > bound_.x1_) {
            return false;
        } else if (b.y0_ < bound_.y0_ || b.y0_ > bound_.y1_) {
            return false;
        } else if (b.y1_ < bound_.y0_ || b.y1_ > bound_.y1_) {
            return false;
        }
        return true;
    }

    // Used to describe one of 5 possible places an element can exist:
    //  - the node itself (THIS)
    //  - any of the 4 children nodes.
    enum Quadrant
    {
        THIS_NODE = -1,
        NW = 0,
        NE = 1,
        SW = 2,
        SE = 3
    };

    // Finds the quadrant to which a bounding box should go (if the node
    // is / were to be split).
    // @param b bounding box to check
    // @returns quadrant in which b belongs to if the node is were to be split
    Quadrant quadrant(const BoundingBox &b) const
    {
        if (children_ == nullptr) {
            return THIS_NODE;
        }

        bool west0 = b.x0_ < splitx_;
        bool west1 = b.x1_ < splitx_;
        bool north0 = b.y0_ < splity_;
        bool north1 = b.y1_ < splity_;

        if (west0 && west1 && north0 && north1)
            return NW;
        if (!west0 && !west1 && north0 && north1)
            return NE;
        if (west0 && west1 && !north0 && !north1)
            return SW;
        if (!west0 && !west1 && !north0 && !north1)
            return SE;
        return THIS_NODE;
    }

    // Checks whether this node should split.
    bool should_split() const
    {
        // The node shouldn't split if it's not large enough to merit it.
        if (elems_.size() < max_elems_)
            return false;

        // The node shouldn't split if its' level is too deep (this is true for
        // 100k+ entries, where the amount of splits causes us to lose
        // significant CPU time on traversing the tree, or worse yet causes a
        // stack overflow).
        if (depth_ > 5)
            return false;

        return true;
    }

  public:
    // Standard constructor for node.
    // @param b BoundingBox this node covers.
    // @param depth depth at which this node is in the tree
    // @max_elems how many elements should this node contain before it splits
    QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : bound_(b), max_elems_(max_elems), depth_(depth) {}
    // Disallow copies.
    QuadTreeNode(const QuadTreeNode &other) = delete;
    QuadTreeNode &operator=(const QuadTreeNode &other) = delete;
    // Allow moves.
    QuadTreeNode(QuadTreeNode &&other)
            : bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)),
              splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_)
    {
        other.children_ = nullptr;
    }
    QuadTreeNode &operator=(QuadTreeNode &&other)
    {
        if (this == &other)
            return *this;
        bound_ = other.bound_;
        max_elems_ = other.max_elems_;
        // children_ = other.max_children_;
        children_ = other.children_;
        splitx_ = other.splitx_;
        splity_ = other.splity_;
        elems_ = std::move(other.elems_);
        depth_ = other.depth_;
        other.children_ = nullptr;
        return *this;
    }

    // Insert an element at a given bounding box.
    bool insert(const BoundingBox &k, ElementT v)
    {
        // Fail early if this BB doesn't fit us at all.
        if (!fits(k)) {
            return false;
        }

        // Do we have children?
        if (children_ != nullptr) {
            // Put the element either recursively into a child if it fits
            // entirely or keep it for ourselves if not.
            auto quad = quadrant(k);
            if (quad == THIS_NODE) {
                elems_.push_back(BoundElement(k, std::move(v)));
            } else {
                return children_[quad].insert(k, std::move(v));
            }
        } else {
            // No children and not about to have any.
            if (!should_split()) {
                elems_.push_back(BoundElement(k, std::move(v)));
                return true;
            }
            // Splitting. Calculate the split point.
            splitx_ = (bound_.x1_ - bound_.x0_) / 2 + bound_.x0_;
            splity_ = (bound_.y1_ - bound_.y0_) / 2 + bound_.y0_;
            // Create the new children.
            children_ = decltype(children_)(new QuadTreeNode<CoordinateT, ElementT>[4]{
                    // Note: not using [NW] = QuadTreeNode because that seems to
                    //       crash g++ 7.3.0.
                    /* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_),
                                                                 depth_ + 1, max_elems_),
                    /* NE */
                    QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_),
                                                        depth_ + 1, max_elems_),
                    /* SW */
                    QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_),
                                                        depth_ + 1, max_elems_),
                    /* SE */
                    QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_),
                                                        depth_ + 1, max_elems_),
            });
            // Move all elements to where they belong.
            auto it = elems_.begin();
            while (it != elems_.end()) {
                auto quad = quadrant(it->bb_);
                if (quad != THIS_NODE) {
                    // Move to one of the children.
                    if (!children_[quad].insert(it->bb_, std::move(it->elem_)))
                        return false;
                    // Delete from ourselves.
                    it = elems_.erase(it);
                } else {
                    // Keep for ourselves.
                    it++;
                }
            }
            // Insert the actual element now that we've split.
            return insert(k, std::move(v));
        }
        return true;
    }

    // Dump a human-readable representation of the tree to stdout.
    void dump(int level) const
    {
        for (int i = 0; i < level; i++)
            printf("  ");
        printf("loc: % 3d % 3d % 3d % 3d\n", bound_.x0_, bound_.y0_, bound_.x1_, bound_.y1_);
        if (elems_.size() != 0) {
            for (int i = 0; i < level; i++)
                printf("  ");
            printf("elems: %zu\n", elems_.size());
        }

        if (children_ != nullptr) {
            for (int i = 0; i < level; i++)
                printf("  ");
            printf("children:\n");
            children_[NW].dump(level + 1);
            children_[NE].dump(level + 1);
            children_[SW].dump(level + 1);
            children_[SE].dump(level + 1);
        }
    }

    // Return count of BoundingBoxes/Elements contained.
    // @returns count of elements contained.
    size_t size() const
    {
        size_t res = elems_.size();
        if (children_ != nullptr) {
            res += children_[NW].size();
            res += children_[NE].size();
            res += children_[SW].size();
            res += children_[SE].size();
        }
        return res;
    }

    // Retrieve elements whose bounding boxes cover the given coordinates.
    //
    // @param x X coordinate of points to query.
    // @param y Y coordinate of points to query.
    // @returns vector of found bounding boxes
    void get(CoordinateT x, CoordinateT y, std::vector<ElementT> &res) const
    {
        if (!bound_.contains(x, y))
            return;

        for (const auto &elem : elems_) {
            const auto &bb = elem.bb_;
            if (bb.contains(x, y)) {
                res.push_back(elem.elem_);
            }
        }
        if (children_ != nullptr) {
            children_[NW].get(x, y, res);
            children_[NE].get(x, y, res);
            children_[SW].get(x, y, res);
            children_[SE].get(x, y, res);
        }
    }
};

// User facing method to manage a quad tree.
//
// @param CoodinateT scalar type of the coordinate system - int, float, ...
// @param ElementT type of the contained element. Must be movable or copiable.
template <typename CoordinateT, typename ElementT> class QuadTree
{
  private:
    // Root of the tree.
    QuadTreeNode<CoordinateT, ElementT> root_;

  public:
    // To let user create bounding boxes of the correct type.
    // Bounding boxes are composed of two 2D points, which designate their
    // top-left and bottom-right corners. All its' edges are axis aligned.
    using BoundingBox = typename QuadTreeNode<CoordinateT, ElementT>::BoundingBox;

    // Standard constructor.
    //
    // @param b Bounding box of the entire tree - all committed elements must
    //          fit within in.
    QuadTree(BoundingBox b) : root_(b, 0) {}

    // Inserts a new value at a given bounding box.e
    // BoundingBoxes are not deduplicated - if two are pushed with the same
    // coordinates, the first one will take precedence.
    //
    // @param k Bounding box at which to store value.
    // @param v Value at a given bounding box.
    // @returns Whether the insert was successful.
    bool insert(BoundingBox k, ElementT v)
    {
        k.fixup();
        return root_.insert(k, v);
    }

    // Dump a human-readable representation of the tree to stdout.
    void dump() const { root_.dump(0); }

    // Return count of BoundingBoxes/Elements contained.
    // @returns count of elements contained.
    size_t size() const { return root_.size(); }

    // Retrieve elements whose bounding boxes cover the given coordinates.
    //
    // @param x X coordinate of points to query.
    // @param y Y coordinate of points to query.
    // @returns vector of found bounding boxes
    std::vector<ElementT> get(CoordinateT x, CoordinateT y) const
    {
        std::vector<ElementT> res;
        root_.get(x, y, res);
        return res;
    }
};

NEXTPNR_NAMESPACE_END

#endif
