
#ifndef nodeQt_h
#define nodeQt_h

#include <string>
#include <tuple>
#include <vector>

#include "CoordSys.h"
#include "Profiler.h"
#include "Utils.h"

namespace jf {

struct Point;
struct Rect2D;

template <typename InNodeIdType>
class NodeQt {
   private:
    class qt {
       public:
        ~qt() {
            std::vector<qt *> ds{d.i.lt, d.i.rt, d.i.lb, d.i.rb};
            for (qt *t : ds) {
                if (t) {
                    delete (t);
                }
            }
        }
        struct inner {
            inner() : lt(nullptr), lb(nullptr), rt(nullptr), rb(nullptr) {}
            qt *lt;
            qt *lb;
            qt *rb;
            qt *rt;
        };

        struct outer {
            std::vector<InNodeIdType> ns;
            std::vector<Point> ps;
            void put(InNodeIdType n, const Point &p) {
                ns.push_back(n);
                ps.push_back(p);
            }

            struct retDataType {
                typedef std::set<InNodeIdType> retDataInSet;
                typedef std::vector<std::tuple<InNodeIdType, Point>> retDataInTuple;
            };

            void GetData(const Rect2D &r, typename retDataType::retDataInTuple &ret) {
                for (int i = 0; i < ns.size(); ++i) {
                    if (r.Contains(ps[i])) {
                        ret.push_back(std::make_tuple(ns[i], ps[i]));
                    }
                }
            }

            void GetData(typename retDataType::retDataInTuple &ret) {
                for (int i = 0; i < ns.size(); ++i) {
                    ret.push_back(std::make_tuple(ns[i], ps[i]));
                }
            }
            void GetData(const Rect2D &r, typename retDataType::retDataInSet &ret) {
                for (int i = 0; i < ns.size(); ++i) {
                    auto &id = ns[i];
                    auto &p = ps[i];
                    if (r.Contains(p)) {
                        ret.insert(id);
                    }
                }
            }
            void GetData(typename retDataType::retDataInSet &ret) {
                for (auto &id : ns) {
                    ret.insert(id);
                }
            }
        };
        struct data {
            inner i;
            outer o;
            data(inner ini) { i = ini; }
            data(outer ino) { o = ino; }
            ~data() {}
        };
        data d;

        qt(inner i) : d(i) {}
        qt(outer o) : d(o) {}

        template <typename retType>
        void GetNodeIds(const Rect2D &target, const Rect2D &thisTree, int level, int maxLevel, retType &ret) {
            // std::vector<std::tuple<Rect2D, qt *>> tests{{thisTree.getLb(), d.i.lb}, {thisTree.getRt(), d.i.rt}, {thisTree.getLt(), d.i.lt}, {thisTree.getRb(), d.i.rb}};
            std::tuple<Rect2D, qt *> test1 = std::make_tuple(thisTree.getLb(), d.i.lb);
            std::tuple<Rect2D, qt *> test2 = std::make_tuple(thisTree.getRt(), d.i.rt);
            std::tuple<Rect2D, qt *> test3 = std::make_tuple(thisTree.getLt(), d.i.lt);
            std::tuple<Rect2D, qt *> test4 = std::make_tuple(thisTree.getRb(), d.i.rb);
            std::vector<std::tuple<Rect2D, qt *>> tests{test1, test2, test3, test4};
            if (level == maxLevel) {
                retType ds;
                d.o.GetData(target, ds);
                ret = jf::Utils::ContainerUnion(ret, ds);
            } else {
                for (auto &test : tests) {
                    auto &subQt = std::get<1>(test);
                    auto &subRect = std::get<0>(test);
                    if (subRect.intersect(target)) {
                        if (subQt != nullptr) {
                            subQt->GetNodeIds(target, subRect, level + 1, maxLevel, ret);
                        }
                    }
                }
            }
        }

        void insert(int level, const Rect2D &r, const Point &p, InNodeIdType nid, int maxLevel) {
            // std::vector<std::tuple<Rect2D, qt **>> tests{{r.getLb(), &d.i.lb}, {r.getRt(), &d.i.rt}, {r.getLt(), &d.i.lt}, {r.getRb(), &d.i.rb}};
            std::tuple<Rect2D, qt **> test1 = std::make_tuple(r.getLb(), &d.i.lb);
            std::tuple<Rect2D, qt **> test2 = std::make_tuple(r.getRt(), &d.i.rt);
            std::tuple<Rect2D, qt **> test3 = std::make_tuple(r.getLt(), &d.i.lt);
            std::tuple<Rect2D, qt **> test4 = std::make_tuple(r.getRb(), &d.i.rb);
            std::vector<std::tuple<Rect2D, qt **>> tests{test1, test2, test3, test4};
            if (!r.Contains(p)) {
                return;
            }
            for (auto &test : tests) {
                auto &subRect = std::get<0>(test);
                auto &pSubQt = std::get<1>(test);
                if (subRect.Contains(p)) {
                    if (level == maxLevel) {
                        d.o.put(nid, p);
                    } else {
                        *pSubQt = *pSubQt == nullptr ? new qt(typename qt::inner()) : *pSubQt;
                        (*pSubQt)->insert(level + 1, subRect, p, nid, maxLevel);
                    }
                }
            }
        }

        void Serialize(int level, uint64_t state, std::vector<uint64_t> &states, std::vector<std::vector<InNodeIdType>> &nodes, std::vector<std::vector<Point>> &ps, int max_level) {
            if (level == max_level) {
                nodes.push_back(d.o.ns);
                ps.push_back(d.o.ps);
                states.push_back(state);
            } else {
                // std::vector<std::tuple<qt *, int>> tests{{d.i.lb, 1}, {d.i.rt, 2}, {d.i.lt, 4}, {d.i.rb, 8}};
                std::tuple<qt *, int> test1 = std::make_tuple(d.i.lb, 1);
                std::tuple<qt *, int> test2 = std::make_tuple(d.i.rt, 2);
                std::tuple<qt *, int> test3 = std::make_tuple(d.i.lt, 4);
                std::tuple<qt *, int> test4 = std::make_tuple(d.i.rb, 8);
                std::vector<std::tuple<qt *, int>> tests{test1, test2, test3, test4};
                for (auto &test : tests) {
                    auto pSubQt = std::get<0>(test);
                    if (pSubQt != nullptr) {
                        int s = std::get<1>(test);
                        uint64_t st = state << 4;
                        st |= s;
                        pSubQt->Serialize(level + 1, st, states, nodes, ps, max_level);
                    }
                }
            }
        }

        void Deserialize(int level, const Rect2D &r, uint64_t state, const std::vector<InNodeIdType> &ns, const std::vector<Point> &ps, int max_level) {
            if (level == max_level) {
                d.o.ns = ns;
                d.o.ps = ps;
            } else {
                // std::vector<std::tuple<Rect2D, qt **, int>> tests{{r.getLb(), &d.i.lb, 1}, {r.getRt(), &d.i.rt, 2}, {r.getLt(), &d.i.lt, 4}, {r.getRb(), &d.i.rb, 8}};
                std::tuple<Rect2D, qt **, int> test1 = std::make_tuple(r.getLb(), &d.i.lb, 1);
                std::tuple<Rect2D, qt **, int> test2 = std::make_tuple(r.getRt(), &d.i.rt, 2);
                std::tuple<Rect2D, qt **, int> test4 = std::make_tuple(r.getLt(), &d.i.lt, 4);
                std::tuple<Rect2D, qt **, int> test8 = std::make_tuple(r.getRb(), &d.i.rb, 8);
                std::vector<std::tuple<Rect2D, qt **, int>> tests = {test1, test2, test4, test8};
                for (auto &test : tests) {
                    auto &s = std::get<2>(test);
                    // 序列化时每次左移，所以这里需要计算右移位数
                    int rightMoveBit = (max_level - level - 1) * 4;
                    uint64_t st = state >> rightMoveBit;
                    if ((s & st) != 0) {
                        auto &subRect = std::get<0>(test);
                        auto &pSubQt = std::get<1>(test);
                        *pSubQt = *pSubQt == nullptr ? new qt(typename qt::inner()) : *pSubQt;
                        (*pSubQt)->Deserialize(level + 1, subRect, state, ns, ps, max_level);
                    }
                }
            }
        }
    };

   public:
    NodeQt(int inMaxLevel, const Rect2D &inR) : maxLevel(inMaxLevel), r(inR), root(new qt(typename qt::inner())) {}
    ~NodeQt() { delete root; }

    void Insert(const InNodeIdType id, const Point &p) { root->insert(0, r, p, id, maxLevel); }

    void Serialize(Rect2D &inoutr, std::vector<uint64_t> &states, std::vector<std::vector<InNodeIdType>> &nodes, std::vector<std::vector<Point>> &ps) {
        inoutr = r;
        root->Serialize(0, 0, states, nodes, ps, maxLevel);
    }

    void Deserialize(const Rect2D &r, const std::vector<uint64_t> &states, const std::vector<std::vector<InNodeIdType>> &in_ns, const std::vector<std::vector<Point>> &in_ps) {
        for (int i = 0; i < states.size(); ++i) {
            int64_t s = states[i];
            const std::vector<InNodeIdType> &ns = in_ns[i];
            const std::vector<Point> &ps = in_ps[i];
            root->Deserialize(0, r, s, ns, ps, maxLevel);
        }
    }

    void GetNodeIdsInRect(const Rect2D &inr, std::set<InNodeIdType> &out) {
        Profiler::Start("NodeQt::GetNodeIdsInRect");
        root->GetNodeIds(inr, r, 0, maxLevel, out);
        Profiler::End("NodeQt::GetNodeIdsInRect");
    }

    bool GetNearestNodeId(const Point &inp, const Rect2D &inr, InNodeIdType &outid, double &outd, bool consider_alt = false) {
        typename qt::outer::retDataType::retDataInTuple testNodes;
        root->GetNodeIds(inr, r, 0, maxLevel, testNodes);
        if (testNodes.size() == 0) {
            return false;
        }
        double outd2 = std::numeric_limits<double>::max();
        for (auto node : testNodes) {
            auto &id = std::get<0>(node);
            auto &p = std::get<1>(node);
            double d2 = D2XYZ(p, inp, consider_alt);
            if (d2 < outd2) {
                outd2 = d2;
                outid = id;
            }
        }
        outd = std::sqrt(outd2);
        return true;
    }

   private:
    int maxLevel;
    Rect2D r;
    qt *root;
};

}  // namespace jf
#endif