#ifndef _JFXMAP_COORDSYS_H_
#define _JFXMAP_COORDSYS_H_

#include <cmath>
#include <iostream>
#include <istream>
#include <map>
#include <ostream>
#include <set>
#include <vector>

#include "MapConfig.h"
#include "MapTypes.hpp"
#include "Serialize.h"
#include "Utils.h"
#include "ajson.hpp"

namespace jf {

static const double PI = 3.1415926535897932384626;

double Rad2Degree(double rad);
inline double Dot(const XYZ &from, const XYZ &to) { return from.x * to.x + from.y * to.y; }

inline double Cross(const XYZ &from, const XYZ &to) { return from.x * to.y - from.y * to.x; }

// 右手坐标系
struct Rot {
    Rot() : angle(0) {}
    Rot(double in_angle) : angle(in_angle) {}
    Rot(const XYZ &from, const XYZ &to) : angle(0) {
        if (from == to) {
            angle = 0;
        } else {
            XYZ toward = to - from;
            XYZ norm_toward = norm(toward);
            XYZ x0(0, 1, 0);
            double cos = Dot(x0, norm_toward);
            angle = acos(cos) * 180 / PI;
            if (Cross(x0, norm_toward) < 0) {
                angle = -angle;
            }
        }
    }
    double angle;
    // 右手坐标系逆时针正，顺时针负
    Rot operator+(const Rot &o) const {
        Rot ret;
        ret.angle = angle + o.angle;
        return ret;
    }
    // 新方向 - 旧方向 : 旧方向到新方向要至少旋转多少？ 两个旋转方向取angle值小的
    Rot operator-(const Rot &o) const {
        Rot ret;
        Rot new_rot = *this;
        Rot old_rot = o;
        if (new_rot.angle < 0) {
            new_rot.angle = new_rot.angle + 360.0;
        }
        if (old_rot.angle < 0) {
            old_rot.angle = old_rot.angle + 360.0;
        }
        ret.angle = new_rot.angle - old_rot.angle;
        return ret;
    }
};

inline Rect2D TId2Rect(const TileId &tid) {
    double lonlb = tid.nLonId * MapConfig::tile_lon_interval;
    double latlb = tid.nLatId * MapConfig::tile_lat_interval;
    double lonrt = (tid.nLonId + 1) * MapConfig::tile_lon_interval;
    double latrt = (tid.nLatId + 1) * MapConfig::tile_lat_interval;
    return Rect2D(Point(lonlb, latlb, 0), Point(lonrt, latrt, 0));
}

// point到tile左下角点
inline Point P2TLBP(const Point &p) {
    double lon = int(p.dLon / MapConfig::tile_lon_interval) * MapConfig::tile_lon_interval;
    double lat = int(p.dLat / MapConfig::tile_lat_interval) * MapConfig::tile_lat_interval;
    return Point(lon, lat, p.dAlt);
}

inline Point P2TRTP(const Point &p) {
    double lon = int(p.dLon / MapConfig::tile_lon_interval) * MapConfig::tile_lon_interval + MapConfig::tile_lon_interval;
    double lat = int(p.dLat / MapConfig::tile_lat_interval) * MapConfig::tile_lat_interval + MapConfig::tile_lat_interval;
    return Point(lon, lat, p.dAlt);
}

inline Rect2D P2TRect(const Point &p) {
    Point tlbp = P2TLBP(p);
    Point trtp = P2TRTP(p);
    return Rect2D(tlbp, trtp);
}

inline TileId String2TId(const std::string &s) {
    std::vector<int> tid;
    jf::Utils::SplitToArray(s, ',', tid);
    if (tid.size() >= 2)
        return TileId(tid[0], tid[1]);
    else
        return TileId(0, 0);
}

inline TileId P2TId(const Point &p, float tile_lon_interval, float tile_lat_interval) {
    int lonId = Utils::DoubleToInt(p.dLon / tile_lon_interval);
    int latId = Utils::DoubleToInt(p.dLat / tile_lat_interval);
    return TileId(lonId, latId);
}

inline std::set<TileId> R2TIds(const Rect2D &r) {
    Point fromlb = P2TLBP(r.lb);
    Point tort = P2TRTP(r.rt);
    std::vector<double> lons;
    std::vector<double> lats;
    std::set<TileId> ret;
    for (double lon = fromlb.dLon; lon < tort.dLon; lon += MapConfig::tile_lon_interval) {
        lons.push_back(lon);
    }
    for (double lat = fromlb.dLat; lat < tort.dLat; lat += MapConfig::tile_lat_interval) {
        lats.push_back(lat);
    }
    for (auto lon : lons)
        for (auto lat : lats) ret.insert(P2TId(Point(lon, lat, 0)));
    return ret;
}

Point TId2PointLB(const TileId &tid);

void GaussProjCal(double longitude, double latitude, double &X, double &Y);
void GaussProjInvCal(double X, double Y, double &longitude, double &latitude);

inline std::vector<Point> InterP(const Point &p1, const Point &p2, float interval) {
    XYZ xyz1 = P2XYZ(p1);
    XYZ xyz2 = P2XYZ(p2);
    XYZ dir = xyz2 - xyz1;
    float len = dir.length();
    XYZ unit = dir / len;
    std::vector<Point> ret;
    ret.push_back(p1);
    for (float l = interval; l < len; l += interval) {
        XYZ now = xyz1 + unit * l;
        ret.push_back(XYZ2P(now));
    }
    ret.push_back(p2);
    return ret;
}

/**
 * @brief 笛卡尔坐标系下距离平方
 *
 * @param l
 * @param r
 * @return double
 */
double D2XYZ(const XYZ &l, const XYZ &r, bool consider_alt = false);
double D2XYZ(const Point &l, const Point &r, bool consider_alt = false);
/**
 * @brief 笛卡尔坐标系下距离
 *
 * @param l
 * @param r
 * @return double
 */
double DXYZ(const XYZ &l, const XYZ &r, bool consider_alt = false);
double DXYZ(const Point &l, const Point &r, bool consider_alt = false);

/**
 * @brief 84坐标系距离平方
 *
 * @param l 点a
 * @param r 点b
 * @return double
 */
double D2_84(const Point &l, const Point &r);
/**
 * @brief 84坐标系距离
 *
 * @param l 点a
 * @param r 点b
 * @return double
 */
double D_84(const Point &l, const Point &r);

inline bool test_near(const Point &p1, const Point &p2) {
    if (std::abs(p1.dLon - p2.dLon) < 0.001 && std::abs(p1.dLat - p2.dLat) < 0.001) {
        return true;
    }
    return false;
}

}  // namespace jf

#ifndef jfxmap_serialize_override_tileId
jfxmap_serialize(jf::TileId, nLonId, nLatId);
#endif

#ifndef jfxmap_serialize_override_point
jfxmap_serialize(jf::Point, dLon, dLat, dAlt);
#endif

#ifndef jfxmap_serialize_override_rot
jfxmap_serialize(jf::Rot, angle);
#endif

#ifndef jfxmap_serialize_override_rect2d
jfxmap_serialize(jf::Rect2D, lb, rt);
#endif

#endif