

#ifndef tile_h
#define tile_h

#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <vector>

#include "FileSys.h"
#include "Utils.h"
#include "ajson.hpp"
// #include "log.h"
#include "CoordSys.h"
#include "NodeQt.h"
#include "Serialize.h"

namespace jf {

template <typename InNodeIdType, typename InNodeDataType, typename InSerializeDataType>
class Tile {
   public:
    typedef InNodeIdType linkIdType;
    typedef InNodeDataType linkType;
    typedef typename InSerializeDataType::saveType configType;
    Tile(const Rect2D &r, const TileId &intid) : tid(intid) { qt = new NodeQt<InNodeIdType>(MapConfig::qt_max_level, r); }
    Tile(const TileId &intid) : tid(intid) {}

    ~Tile() { delete qt; }

    void PutNode(const Point &p, InNodeIdType nid, const InNodeDataType &n) {
        TileId tid = P2TId(p);
        qt->Insert(nid, p);
        m[nid] = n;
    }

    void UpdateNodesData(const std::function<void(std::map<InNodeIdType, InNodeDataType> &)> &func) { func(m); }

    void Serialize(std::string &s) {
        Rect2D r({Point()}, {Point()});
        std::vector<uint64_t> states;
        std::vector<std::vector<InNodeIdType>> nodes;
        std::vector<std::vector<Point>> ps;
        qt->Serialize(r, states, nodes, ps);

        InSerializeDataType sd(r, m, states, nodes, ps);
        s = DoSerialize(sd, MapConfig::serialize_type_json1_binary2);
    }

    void Deserialize(const std::string &s) {
        // jf_log_info("[Tile::Deserialize]: {} Deserialize Tile lon:{} lat:{}", MapConfig::tag, tid.lonId, tid.latId);
        InSerializeDataType sd;
        DoSeserialize(s, sd, MapConfig::serialize_type_json1_binary2);

        m.clear();
        for (int i = 0; i < sd.m_keys.size(); ++i) {
            m.insert(std::make_pair(sd.m_keys[i], sd.m_values[i]));
        }
        Rect2D &r = sd.r;
        auto &nodes = sd.nodes;
        auto &ps = sd.ps;
        auto &states = sd.states;
        qt = new NodeQt<InNodeIdType>(MapConfig::qt_max_level, r);
        qt->Deserialize(r, states, nodes, ps);
        return;
    }

    Rect2D GetRect() { return qt->GetRect(); }

    void GetNodeIdsInRect(const Rect2D &r, std::set<InNodeIdType> &ns) { return qt->GetNodeIdsInRect(r, ns); }

    void GetNodesInRect(const Rect2D &r, std::set<const InNodeDataType *> &out) {
        Profiler::Start("Tile::GetNodeIdsInRect");
        Profiler::Start("Tile::GetNodeIdsInRect 1");
        std::set<InNodeIdType> ids;
        qt->GetNodeIdsInRect(r, ids);
        Profiler::End("Tile::GetNodeIdsInRect 1");
        Profiler::Start("Tile::GetNodeIdsInRect 2");
        std::set<InNodeDataType> ret;
        for (auto id : ids) {
            out.insert(&m[id]);
        }
        Profiler::End("Tile::GetNodeIdsInRect 2");
        Profiler::End("Tile::GetNodeIdsInRect");
    }

    const InNodeDataType *GetNodeData(const InNodeIdType &nid) const {
        auto i = m.find(nid);
        if (i == m.end()) {
            return nullptr;
        } else {
            return &i->second;
        }
        //   jf_log_error("assert false : Tile::GetNodeData");
        return nullptr;
    }

    TileId GetTId() const { return tid; }

    bool GetNearestNodeId(const Point &p, const Rect2D &inr, InNodeIdType &id, double &distance, bool consider_alt = false) { return qt->GetNearestNodeId(p, inr, id, distance, consider_alt); }

    const std::map<InNodeIdType, InNodeDataType> &GetNodes() { return m; }

   private:
    TileId tid;
    NodeQt<InNodeIdType> *qt;
    std::map<InNodeIdType, InNodeDataType> m;
};
}  // namespace jf

#endif