#ifndef __HCM4A_CalcFS4A_ctrlGIS_h_
#define __HCM4A_CalcFS4A_ctrlGIS_h_

#include <CtrlLib/CtrlLib.h>
#include <math.h>
#include "dtGIS.h"


struct GisOptions{
	bool showTopo = false;
	bool showMorpho = false;
	bool showDegreeLines = false;
	bool showGoogleHybrid = false;
	bool showGoogle = false;
};
//!---------------------------------------------------------------------------------------------

struct MapStation{
	String name = "";
	double longitude = 0.0;
	double latitude = 0.0;
	double azimut = 0.0;
	double radius = 0.0;
	bool isRef = false;
};
//!---------------------------------------------------------------------------------------------

/// Calculates the distance between two geographical points using the Haversine formula.
//     * LonA (double): Longitude of the first point.
//     * LatA (double): Latitude of the first point.
//     * LonB (double): Longitude of the second point.
//     * LatB (double): Latitude of the second point.
double TopoDistance(double LonA, double LatA, double LonB, double LatB) {
    const double R = 6371.29; // Earth radius in kilometers
    double delta_lat = LatB - LatA;
    double delta_lon = LonB - LonA;
    double a = pow(sin(delta_lat / 2.0 * M_PI / 180.0), 2) +
               cos(LatA * M_PI / 180.0) * cos(LatB * M_PI / 180.0) * pow(sin(delta_lon / 2.0 * M_PI / 180.0), 2);
    return 2.0 * R * atan2(sqrt(a), sqrt(1.0 - a));
}
//!---------------------------------------------------------------------------------------------

/// Calculates a new point given a starting point, azimuth, and distance.
//     * pnt1 (Pointf): The starting point.
//     * azimut (double): The azimuth in degrees.
//     * distance_km (double): The distance in kilometers.
//     * pnt2 (Pointf&): The resulting point.
void TopoGetStep(Pointf pnt1, double azimut, double distance_km, Pointf& pnt2){
    const double cPIdiv180 = M_PI / 180.0;
    const double c180divPI = 180.0 / M_PI;
    const double earthRadiusKm = 6371.0;
    // Convert degrees to radians
    double lat1 = pnt1.y * cPIdiv180;
    double lon1 = pnt1.x * cPIdiv180;
    double azimuth = azimut * cPIdiv180;
    // Convert distance from kilometers to radians
    double angularDistance = distance_km / earthRadiusKm;
    // Calculate the new latitude
    double lat2 = asin(sin(lat1) * cos(angularDistance) +
                       cos(lat1) * sin(angularDistance) * cos(azimuth));
    // Calculate the new longitude
    double lon2 = lon1 + atan2(sin(azimuth) * sin(angularDistance) * cos(lat1),
                               cos(angularDistance) - sin(lat1) * sin(lat2));
    // Convert radians back to degrees
    pnt2.y = lat2 * c180divPI;
    pnt2.x = lon2 * c180divPI;
}
//!---------------------------------------------------------------------------------------------

/// Generates a filename for a map tile.
//     * x (int): The x coordinate of the tile.
//     * y (int): The y coordinate of the tile.
//     * zoom (int): The zoom level.
//     * isHybrid (bool): Indicates if the tile is hybrid.
String GetFilenameToTile(int x, int y, int zoom, bool isHybrid) {
    String prefix = "";
    !isHybrid ? prefix = "Sputnic" : prefix = "Hybrid";
    String fname = GetExeFolder() + "\\Tiles" + prefix + "\\z" + AsString(zoom) +
                                    "\\x" + AsString(trunc(x / 1024)) +
                                    "\\y" + AsString(trunc(y / 1024)) +
                                    "\\" + AsString(x) + "." + AsString(y) + ".png";
    RealizeDirectory(GetFileFolder(fname));
    return fname;
}
//!---------------------------------------------------------------------------------------------

/// Downloads a Google map tile image.
//     * cx (int): The x coordinate of the tile.
//     * cy (int): The y coordinate of the tile.
//     * cz (int): The zoom level.
//     * isHybrid (bool): Indicates if the tile is hybrid.
//     * downloadComplete (Event<>&): Event triggered when the download is complete.


/*

The available Google Maps layers are:
Roadmap
	http://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}
Terrain
	http://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}
Altered roadmap
	http://mt0.google.com/vt/lyrs=r&hl=en&x={x}&y={y}&z={z}
Satellite only
	http://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}
Terrain only
	http://mt0.google.com/vt/lyrs=t&hl=en&x={x}&y={y}&z={z}
Hybrid
	http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}

lyrs=
	h = roads only
	m = standard roadmap
	p = terrain
	r = somehow altered roadmap
	s = satellite only
	t = terrain only
	y = hybrid

	Hybrid Maps URL: http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga
*/

Image GetGooleMapImage(int cx, int cy, int cz, bool isHybrid, Event<>& downloadComplete) {
    String fname = GetFilenameToTile(cx, cy, cz, isHybrid);
    if (FileExists(fname)) {
        return StreamRaster::LoadFileAny(fname);
    }
    Thread().Run([=] {
        String request;
        if (isHybrid) {
            request << "http://mt0.google.com/vt/lyrs=y@169000000&hl=en&x=" << cx << "&y=" << cy << "&z=" << (cz);
        } else {
            request << "http://mt0.google.com/vt/lyrs=p@169000000&hl=en&x=" << cx << "&y=" << cy << "&z=" << (cz);
        }
        try {
            HttpRequest r(request);
            r.Timeout(5000);
            String h = r.Execute();
            if (r.IsSuccess()) {
                String fname = GetFilenameToTile(cx, cy, cz, isHybrid);
                FileOut f(fname);
                f.PutLine(r.GetContent());
                f.Close();
            }
            ~r;
            downloadComplete();
        } catch (...) {
            // Handle exceptions
        }
    });
    return StreamRaster::LoadStringAny("");
}
//!---------------------------------------------------------------------------------------------

/// Converts geographical coordinates to tile coordinates.
//     * pnt (Pointf): The geographical coordinates.
//     * zoom (int): The zoom level.
Point CoordToTile(Pointf pnt, int zoom) {
    Point r;
    int tileSize = 256;
    double initialResolution = 2 * M_PI * 6378137 / tileSize;
    double originShift = 2 * M_PI * 6378137 / 2.0;
    double mx = pnt.x * originShift / 180.0;
    double my = log(tan((90 + pnt.y) * M_PI / 360.0)) / (M_PI / 180.0);
    my = my * originShift / 180.0;
    double res = initialResolution / (pow(2, zoom));
    r.x = (int)((mx + originShift) / res / tileSize);
    r.y = (int)((originShift - my) / res / tileSize);
    return r;
}
//!---------------------------------------------------------------------------------------------

/// Converts tile coordinates to geographical coordinates.
//     * cx (int): The x coordinate of the tile.
//     * cy (int): The y coordinate of the tile.
//     * zoom (int): The zoom level.
Pointf TileToCoord(int cx, int cy, int zoom) {
    Pointf coord;
    int tileSize = 256;
    double initialResolution = 2 * M_PI * 6378137 / tileSize;
    double originShift = 2 * M_PI * 6378137 / 2.0;
    double res = initialResolution / (pow(2, zoom));
    double mx = cx * tileSize * res - originShift;
    double my = originShift - cy * tileSize * res;
    coord.x = (mx / originShift) * 180.0;
    coord.y = (my / originShift) * 180.0;
    coord.y = 180 / M_PI * (2 * atan(exp(coord.y * M_PI / 180.0)) - M_PI / 2.0);
    return coord;
}
//!---------------------------------------------------------------------------------------------

/// Calculates the number of meters per pixel at a given latitude and zoom level.
//     * latitude (double): The latitude.
//     * zoom (int): The zoom level.
double TopoGetMetrsInPixel(double latitude, int zoom) {
    const double equator = 40075.016685578488;
    const double tileSize = equator * 1000 / 256;  //≈ 156543.03
    return tileSize * cos(0.017453292519943 * latitude) / pow(2, zoom);
}
//!---------------------------------------------------------------------------------------------

class GisMap : public Ctrl{
	private:
		const double MAX_LATITUDE = 85.05; // spherical mercator restriction
		const double MAX_LONGITUDE = 180.0; // spherical mercator restriction
		const int MAX_ZOOM = 20;
		bool isDragging = false;
		bool isDistShowing = false;
		Point pointCenterPx = Point(0.0, 0.0);
		Point pointMouseUpPx = Point(0.0, 0.0);
		Point pointMovePx = Point(0.0, 0.0);
		Point pointDist = Point(0.0, 0.0);
		
		int pxInCm = 52; // TODO: need to check this in other systems
		double metresIn1px = 0;
		int width, height;
		
		Event<> downloadComplete;
		Time latTimeUpdate = GetSysTime();
		
		double RoundDigit(double digit, int places);
		
		void Paint(Draw& w);
		void DrawDistance(Draw& w);
		void DrawCountries(Draw& w);
		void DrawClosedBorders(Draw& w);
		void DrawStations(Draw& w);
		
		void DrawTopoRect(Draw& w);
		void DrawTopoRect(bool isTopo, Draw& w);
		void DrawLines(Draw& w);
		void DrawOrder(Draw& w);
		void DrawGoogle(Draw& w);
		
		void MouseWheel(Point p, int zdelta, dword keyflags);
		void LeftDrag(Point pos, dword flags);
		void LeftUp(Point pos, dword flags);
		void MouseMove(Point p, dword keyflag);
			
		Color GetColorForCountry(const String& country);
		Color GetRandomColor60(int Seed = -1);
		void CheckMapCenter();
	public:
		Pointf pointMouseDownPx = Point(0.0, 0.0);
		Pointf pointCenterGrad = Point(0.0, 0.0);
		Pointf pointMoveGrad = Point(0.0, 0.0);
		Array<MapStation> mapStation;
		int zoom = 8;
		int lastTopo = 0;
		int lastMorpho = 0;
		GisOptions options;
		GeoData geodata;
		
		Pointf WebMercatorToXY(Pointf pnt);
		Pointf XyToWebMercator(Pointf pnt);
		Pointf DegToPx(Pointf pnt);
		Pointf PxToDeg(Pointf pnt);
		
		void ZoomChange(int zm);
		
		void ToCenter();
		void LookAt(double longitude, double latitide, int zoom = -1);
		GisMap();
};
/// Converts Web Mercator coordinates to XY coordinates.
Pointf GisMap::WebMercatorToXY(Pointf pnt) {
    Pointf result;
    result.x = pnt.x * 0.017453292519943 * 6378137.0;
    result.y = std::max(std::min(MAX_LATITUDE, pnt.y), -MAX_LATITUDE);
    result.y = log(tan(0.7853981633974 + (0.017453292519943 * result.y) / 2)) * 6378137.0;
    return Point(result.x, result.y);
}
/// Converts XY coordinates to Web Mercator coordinates.
Pointf GisMap::XyToWebMercator(Pointf pnt) {
    Pointf result;
    result.x = pnt.x / 6378137.0 * 57.295779513082323;
    result.y = (2 * atan(exp(pnt.y / 6378137.0)) - 1.570796326794896) * 57.295779513082323;
    return result;
}
/// Converts degrees to pixel coordinates.
Pointf GisMap::DegToPx(Pointf pnt) {
    pnt = Pointf(std::min(MAX_LONGITUDE, std::max(-MAX_LONGITUDE, pnt.x)), std::min(MAX_LATITUDE, std::max(-MAX_LATITUDE, pnt.y)));
    pnt = WebMercatorToXY(pnt);
    Pointf tmp = WebMercatorToXY(pointCenterGrad);
    Pointf result = Pointf((tmp.x - pnt.x) / metresIn1px, (pnt.y - tmp.y) / metresIn1px);
    return Pointf(-(result.x - pointCenterPx.x), (pointCenterPx.y - result.y));
}
/// Converts pixel coordinates to degrees.
Pointf GisMap::PxToDeg(Pointf pnt) {
    Pointf result = Pointf((pnt.x - pointCenterPx.x) * metresIn1px, (pointCenterPx.y - pnt.y) * metresIn1px);
    Pointf tmp2 = WebMercatorToXY(pointCenterGrad);
    tmp2 = XyToWebMercator(Pointf(result.x + tmp2.x, result.y + tmp2.y));
    return Pointf(std::min(MAX_LONGITUDE, std::max(-MAX_LONGITUDE, tmp2.x)), std::min(MAX_LATITUDE, std::max(-MAX_LATITUDE, tmp2.y)));
}
/// Changes the zoom level of the map.
///   * zm (int): The new zoom level.
void GisMap::ZoomChange(int zm) {
    zoom = std::min(MAX_ZOOM, std::max(1, zm));
    metresIn1px = TopoGetMetrsInPixel(0, zoom);
    Refresh();
}
/// Centers the map view.
void GisMap::ToCenter() {
    isDragging = false;
    pointCenterPx = Pointf(width / 2, height / 2);
    pointMouseUpPx = pointCenterPx;
    pointMouseDownPx = Pointf(0, 0);
}
/// Centers the map view on a specific location.
//     * longitude (double): The longitude to center on.
//     * latitude (double): The latitude to center on.
//     * zoom (int): The zoom level.
void GisMap::LookAt(double longitude, double latitude, int zoom) {
    pointCenterGrad = Pointf(longitude, latitude);
    pointCenterPx = Pointf(width / 2, height / 2);
    ZoomChange(zoom);
    ToCenter();
}
/// Handles mouse wheel events to zoom the map.
//     * p (Point): The mouse position.
//     * zdelta (int): The scroll delta.
//     * keyflags (dword): The key flags.
void GisMap::MouseWheel(Point p, int zdelta, dword keyflags) {
    int zoom2 = std::clamp(zoom + sgn(zdelta), 1, 20);
    if(zoom2 != zoom){
        Pointf pnt = pointMoveGrad;
        LookAt(pnt.x, pnt.y, zoom2);
    }
}
/// Handles left mouse button drag events.
//     * pos (Point): The mouse position.
//     * flags (dword): The key flags.
void GisMap::LeftDrag(Point pos, dword flags) {
    isDragging = true;
    pointMouseDownPx = pos;
}
/// Handles left mouse button up events.
//     * pos (Point): The mouse position.
//     * flags (dword): The key flags.
void GisMap::LeftUp(Point pos, dword flags) {
    if(isDragging){
        isDragging = false;
        pointMouseUpPx = Pointf(pos.x, pos.y);
        Pointf tmp = PxToDeg(Pointf(pos.x, pos.y));
        pointCenterPx = pointMouseUpPx;
        pointCenterGrad = tmp;
        pointMouseDownPx = pointMouseUpPx;
        Refresh();
    }
}
///  Handles mouse move events.
//     * p (Point): The mouse position.
//     * keyflag (dword): The key flags.
void GisMap::MouseMove(Point p, dword keyflag) {
    pointMovePx = Pointf(p.x, p.y);
    pointMoveGrad = PxToDeg(pointMovePx);
    if (isDragging) {
        pointCenterPx.x = pointMouseUpPx.x - pointMouseDownPx.x + p.x;
        pointCenterPx.y = pointMouseUpPx.y - pointMouseDownPx.y + p.y;
        pointCenterGrad = PxToDeg(pointCenterPx);
        Refresh();
    }
    if(GetCtrl()){
        Pointf pnt = pointMoveGrad;
        pnt.y = pnt.y - 1;
        lastTopo = geodata.GetHeight(pnt);
        lastMorpho = geodata.GetMorpho(pnt);
    }
    if(GetShift()){
        if(!isDistShowing){
            pointDist = p;
            isDistShowing = true;
        }
        Refresh();
    }else{
        isDistShowing = false;
    }
}
/// Draws the distance between two points on the map.
void GisMap::DrawDistance(Draw& w) {
    if(!isDistShowing) return;
    w.DrawLine(pointDist, pointMovePx, 2, GrayColor(50));
    Pointf pn1, pn2;
    pn1 = PxToDeg(pointDist);
    pn2 = PxToDeg(pointMovePx);
    double d = TopoDistance(pn1.x, pn1.y, pn2.x, pn2.y);
    w.DrawTextA(pointDist.x, pointDist.y, AsString(round(d * 10) / 10) + " km");
}
/// Draws country borders on the map.
void GisMap::DrawCountries(Draw& w) {
    if(geodata.borders.IsEmpty()) return;
    for(int i = 0; i < geodata.borders.GetCount(); i++){
        if(geodata.borders[i].distance != "ALL") continue;
        Vector<Point> vertices;
        vertices.SetCount(geodata.borders[i].points.GetCount());
        for(int j = 0; j < vertices.GetCount(); j++){
            vertices[j] = DegToPx(geodata.borders[i].points[j]);
        }
        // w.DrawPolygon(vertices, GetRandomColor60(i), 1, Black());
        w.DrawPolygon(vertices, GetColorForCountry(geodata.borders[i].countryName), 1, Black());
    }
}
/// Draws latitude and longitude lines on the map.
void GisMap::DrawLines(Draw& w) {
    Pointf pnt1, pnt2;
    for(int lon = -180; lon <= 180; lon += 5){
        pnt1 = DegToPx(Pointf(lon, -MAX_LATITUDE));
        pnt2 = DegToPx(Pointf(lon, MAX_LATITUDE));
        w.DrawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y, 1, Color(200, 200, 200));
    }
    for(int lat = -MAX_LATITUDE; lat <= MAX_LATITUDE; lat += 5){
        pnt1 = DegToPx(Pointf(-MAX_LONGITUDE, lat));
        pnt2 = DegToPx(Pointf(MAX_LONGITUDE, lat));
        w.DrawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y, 1, Color(200, 200, 200));
    }
}
/// Draws rectangles for topographical or morphological data on the map.
//     * isTopo (bool): Indicates if the data is topographical.
void GisMap::DrawTopoRect(bool isTopo, Draw& w) {
    if(geodata.topoMorpho.IsEmpty()){
        return;
    }
    int offset = isTopo ? 0 : 18;
    for(int i = 0; i < geodata.topoMorpho.GetCount(); i++){
        if(geodata.topoMorpho[i].isTopo != isTopo){ continue; }
        Vector<Point> vertices;
        vertices.SetCount(5);
        vertices[0] = Pointf(geodata.topoMorpho[i].pointLD.x, geodata.topoMorpho[i].pointLD.y);
        vertices[1] = Pointf(vertices[0].x + 1, vertices[0].y);
        vertices[2] = Pointf(vertices[0].x + 1, vertices[0].y + 1);
        vertices[3] = Pointf(vertices[0].x, vertices[0].y + 1);
        vertices[4] = vertices[0];

        vertices[0] = DegToPx(vertices[0]);
        vertices[1] = DegToPx(vertices[1]);
        vertices[2] = DegToPx(vertices[2]);
        vertices[3] = DegToPx(vertices[3]);
        vertices[4] = DegToPx(vertices[4]);

        w.DrawLine(vertices[0], vertices[1], Black());
        w.DrawLine(vertices[1], vertices[2], Black());
        w.DrawLine(vertices[2], vertices[3], Black());
        w.DrawLine(vertices[3], vertices[4], Black());
        w.DrawLine(vertices[4], vertices[0], Black());
        if(zoom > 8){
            String txt = GetFileName(geodata.topoMorpho[i].fileName);
            w.DrawTextA(vertices[0].x + round((vertices[2].x - vertices[0].x) / 2 - round(txt.GetCount() * 4 / 2)),
                        vertices[0].y - round((vertices[0].y - vertices[3].y) / 2) - offset, txt);
        }
    }
}
/// Draws Google map tiles on the map.
void GisMap::DrawGoogle(Draw& w) {
    if(width == 0) return;
    if(zoom < 1) return;
    if(!options.showGoogle & !options.showGoogleHybrid) return;
    Pointf pnt1 = PxToDeg(Pointf(0, 0));
    pnt1 = CoordToTile(pnt1, zoom);
    Pointf pnt2 = PxToDeg(Pointf(width, height));
    pnt2 = CoordToTile(pnt2, zoom);
    Pointf pnt;
    Image img;
    for(int x = std::min(pnt1.x, pnt2.x) - 2; x <= std::max(pnt1.x, pnt2.x) + 2; x++){
        for(int y = std::min(pnt1.y, pnt2.y) - 2; y <= std::max(pnt1.y, pnt2.y) + 2; y++){
            img = GetGooleMapImage(x, y, zoom, options.showGoogleHybrid, downloadComplete);
            pnt = TileToCoord(x, y, zoom);
            pnt = DegToPx(pnt);
            if(!IsNull(img)){
                w.DrawImage(pnt.x, pnt.y, img);
                img.Clear();
            }
        }
    }
    ~img;
}
/// Draws stations on the map.
void GisMap::DrawStations(Draw& w) {
    if(width == 0) return;
    if(zoom < 2) return;
    Pointf pnt1, pnt2;
    int sizeObj, maxX, minX, maxY, minY, x;
    zoom < 7 ? sizeObj = 3 : sizeObj = 5;
    Vector<Point> vertices;
    vertices.SetCount(4);
    for(x = 0; x < mapStation.GetCount(); x++){
        pnt1 = Pointf(mapStation[x].longitude, mapStation[x].latitude);
        if(mapStation[x].radius > 0) {
            TopoGetStep(pnt1, 0, mapStation[x].radius, pnt2);
            minY = trunc(DegToPx(pnt2).y);
            TopoGetStep(pnt1, 90, mapStation[x].radius, pnt2);
            maxX = trunc(DegToPx(pnt2).x);
            TopoGetStep(pnt1, 180, mapStation[x].radius, pnt2);
            maxY = trunc(DegToPx(pnt2).y);
            TopoGetStep(pnt1, 270, mapStation[x].radius, pnt2);
            minX = trunc(DegToPx(pnt2).x);
            TopoGetStep(pnt1, 225, mapStation[x].radius * 1.414, pnt2);
            pnt2 = DegToPx(pnt2);
            w.DrawArc(RectC(minX, minY, abs(maxY - minY), abs(maxY - minY)), Point(0, 0), Point(0, 0), 2, Cyan);
        }else{
            TopoGetStep(pnt1, mapStation[x].azimut, 3, pnt2);
            pnt2 = DegToPx(pnt2);
            pnt1 = DegToPx(Pointf(mapStation[x].longitude, mapStation[x].latitude));
            w.DrawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y, 2, Black());
        }
        pnt1 = DegToPx(Pointf(mapStation[x].longitude, mapStation[x].latitude));
        vertices[0] = Pointf(pnt1.x - sizeObj, pnt1.y + sizeObj);
        vertices[1] = Pointf(pnt1.x, pnt1.y - sizeObj);
        vertices[2] = Pointf(pnt1.x + sizeObj, pnt1.y + sizeObj);
        vertices[3] = vertices[0];
        w.DrawPolyline(vertices, 2, mapStation[x].isRef ? Color(192, 0, 50) : Color(50, 0, 192));
        if (zoom > 7) w.DrawTextA(vertices[2].x - mapStation[x].name.GetLength() * 5 / 2, vertices[2].y, mapStation[x].name);
    }
}
/// Draws the map layers in order.
void GisMap::DrawOrder(Draw& w){
    DrawCountries(w);
    if(!isDragging) DrawGoogle(w);
    if(options.showDegreeLines) DrawLines(w);
    if(options.showTopo) DrawTopoRect(true, w);
    if(options.showMorpho) DrawTopoRect(false, w);
    DrawDistance(w);
    DrawStations(w);
}
/// Checks and adjusts the map center if it is out of bounds.
void GisMap::CheckMapCenter() {
    Pointf PntCenter = PxToDeg(Pointf(width / 2, height / 2));
    if(PntCenter.x == MAX_LONGITUDE || PntCenter.x == -MAX_LONGITUDE || PntCenter.y == MAX_LATITUDE || PntCenter.y == -MAX_LATITUDE){
        ToCenter();
    }
}
/// Paints the map on the drawing context.
void GisMap::Paint(Draw& w) {
    Size sz = GetSize();
    width = sz.cx;
    height = sz.cy;
    if(width <= 0) return;
    w.DrawRect(sz, SWhiteGray());
    CheckMapCenter();
    DrawOrder(w);
}
/// Rounds a number to a specified number of decimal places.
//     * digit (double): The number to round.
//     * places (int): The number of decimal places.
double GisMap::RoundDigit(double digit, int places) {
    places = pow(10, std::min(8, std::max(0, places)));
    return round(digit * places) / places;
}
/// Generates a random color with a lightness of 60%.
Color GisMap::GetRandomColor60(int Seed){
    double C, X, M;
    double R = 0, G = 0, B = 0;
    double Hue, Saturation, Lightness;
    if (Seed < 0) {
        Seed = Random(16777214);
    }
    Seed = Seed + 1;
    Seed = Seed + Seed * 15 - 1;
    Hue = fmod(Seed * 6, 360);
    Saturation = 1;
    Lightness = 0.60 + (Seed % 20) * 0.01;
    Hue = Hue / 60;
    C = (1 - fabs(2 * Lightness - 1)) * Saturation;
    X = C * (1 - fabs(Hue - floor(Hue) - 1));
    M = Lightness - C / 2;
    switch (static_cast<int>(Hue)) {
        case 0: R = C; G = X; B = 0; break;
        case 1: R = X; G = C; B = 0; break;
        case 2: R = 0; G = C; B = X; break;
        case 3: R = 0; G = X; B = C; break;
        case 4: R = X; G = 0; B = C; break;
        case 5: R = C; G = 0; B = X; break;
        default: R = 0; G = 0; B = 0; break;
    }
    return Color(
        static_cast<int>((R + M) * 255),
        static_cast<int>((G + M) * 255),
        static_cast<int>((B + M) * 255)
    );
}
Color GisMap::GetColorForCountry(const String& country){
	static const std::unordered_map<int, Color> typeColors = {
		{1, Color(255, 182, 193)},  // Pastel Red
		{2, Color(152, 251, 152)},  // Pastel Green
		{3, Color(255, 209, 255)},  // Pastel Pink
		{4, Color(189, 186, 45)}	// Pastel Yellow
	};
	static const std::unordered_map<std::string, int> countryTypes = {
	    {"GNB", 1}, {"LBR", 1}, {"GHA", 1}, {"NGR", 1}, {"CME", 1}, {"SDN", 1}, {"COD", 1}, {"KEN", 1}, {"MWI", 1}, {"BOT", 1}, {"SWZ", 1},
	    {"ALG", 2}, {"EGY", 2}, {"SEN", 2}, {"TCD", 2}, {"CTI", 2}, {"BEN", 2}, {"ETH", 2}, {"AGL", 2}, {"TZA", 2}, {"ZWE", 2}, {"AO1", 2}, {"TZ1", 2},
	    {"MTN", 3}, {"TUN", 3}, {"GUI", 3}, {"BFA", 3}, {"NIG", 3}, {"SSD", 3}, {"DJI", 3}, {"GNE", 3}, {"COG", 3}, {"RRW", 3}, {"ZMB", 3}, {"AFS", 3}, {"GQ1", 3},
	    {"MRC", 4}, {"LBY", 4}, {"MLI", 4}, {"GMB", 4}, {"ERI", 4}, {"SRL", 4}, {"TGO", 4}, {"CAF", 4}, {"SOM", 4}, {"GAB", 4}, {"UGA", 4}, {"BDI", 4}, {"NMB", 4}, {"MOZ", 4}, {"LSO", 4}, {"ZA1", 4}
	};
	auto it = countryTypes.find(country.ToStd());
	if(it != countryTypes.end()){
		int type = it->second;
	    return typeColors.at(type);
	}
    return GetRandomColor60( country.GetCount() < 2 ? 0 : static_cast<int>(country[0]) * static_cast<int>(country[1]) );
}

GisMap::GisMap(){
    ToCenter();
    ZoomChange(5);
    downloadComplete << [=] {
        Time tt = GetSysTime();
        if(abs(tt.second - latTimeUpdate.second) >= 1){
            Call([=]{
                Refresh();
            });
        }
        latTimeUpdate = tt;
    };
}
//!---------------------------------------------------------------------------------------------

struct MapCoord{
	int id;
    double longitude;
    double latitude;
    int zoom;
};
//!---------------------------------------------------------------------------------------------


struct LayGisMap : WithLayMap<ParentCtrl> {
public:
    GisMap map;
	Vector<MapCoord> mapTreeCoordArray;
	Vector<StationRec> stationsList;
    StatusBar statusBar;
    InfoCtrl infoLongLati;
    InfoCtrl infoZoom;
    InfoCtrl infoTopo;
    InfoCtrl infoMorpho;
    InfoCtrl infoDop;
    Option showTopoTile;
    Option showMorphoTile;
    Option showDegLines;
    void ChangeOptions();

	void PrepareMapTree();
    void RefreshMapSettings();
	void RefreshMapTree();
    LayGisMap();
};
LayGisMap::LayGisMap(){
    typedef LayGisMap CLASSNAME;
    CtrlLayout(*this);
    AddFrame(statusBar);
    statusBar.AddFrame(infoLongLati.Right(250));
    statusBar.AddFrame(infoZoom.Right(70));
    statusBar.AddFrame(infoTopo.Right(90));
    statusBar.AddFrame(infoMorpho.Right(70));
    statusBar.AddFrame(infoDop.Right(300));
    infoDop = "Push [ctrl] to show Height/Morpho, [shift] to Distance";
    SetTimeCallback(-100, [=] {
        String txt = CoordinateToString(map.pointMoveGrad);
        txt.Insert(8, " ");
        txt += "  " + FormatDouble(map.pointMoveGrad.x, 8) << " " << FormatDouble(map.pointMoveGrad.y, 8);
        infoLongLati.Set(txt);
        infoZoom = "Zoom = " + AsString(map.zoom);
        infoTopo = "Height = " + AsString(map.lastTopo);
        infoMorpho = "Morpho = " + AsString(map.lastMorpho);
    });
    optionTopo.WhenAction = optionMorpho.WhenAction = optionGoogle.WhenAction =
        optionDegLines.WhenAction = optionGoogleHybrid.WhenAction = THISBACK(ChangeOptions);

	PrepareMapTree();
	RefreshMapTree();
}
/// Updates the map options based on user selections.
void LayGisMap::ChangeOptions(){
    map.options.showTopo = optionTopo;
    map.options.showMorpho = optionMorpho;
    map.options.showDegreeLines = optionDegLines;
    map.options.showGoogleHybrid = optionGoogleHybrid;
    map.options.showGoogle = optionGoogle;
    if(optionTopo || optionMorpho || optionDegLines){
		RefreshMapSettings();
    }
    else{
        Refresh();
    }
}
///  Refreshes the settings and updates the UI.
void LayGisMap::RefreshMapSettings(){
    bool flag = !PathIsEqual(map.geodata.pathToBorders, Single<AppCFG>().cfgPathRec.GetBorder()) ||
                !PathIsEqual(map.geodata.pathToMorpho, Single<AppCFG>().cfgPathRec.GetMorpho()) ||
                !PathIsEqual(map.geodata.pathToTopo, Single<AppCFG>().cfgPathRec.GetTopo());
    map.geodata.pathToBorders = Single<AppCFG>().cfgPathRec.GetBorder();
    map.geodata.fileWithCountriesCodes = Single<AppCFG>().cfgEditRec.GetLstCountriesName();
    map.geodata.pathToMorpho = Single<AppCFG>().cfgPathRec.GetMorpho();
    map.geodata.pathToTopo = Single<AppCFG>().cfgPathRec.GetTopo();
    if(flag){
		Progress pi;
		//pi.Title(t_("Load map..."));
		map.geodata.LoadGeoData(pi);
		pi.Close();
    }
}

/// Prepares the map tree for displaying stations.
void LayGisMap::PrepareMapTree(){
    mapTree.WhenCursor = [=] {
        int cur = mapTree.GetItemAtLine(mapTree.GetCursor());
        if(cur < 0) return;
        for(int i = 0; i < mapTreeCoordArray.GetCount(); i++){
            if(mapTreeCoordArray[i].id != cur) continue;
            map.LookAt(mapTreeCoordArray[i].longitude, mapTreeCoordArray[i].latitude, mapTreeCoordArray[i].zoom);
            break;
        }
    };
}
/// Refreshes the map tree with station data.
void LayGisMap::RefreshMapTree() {
	if(!stationsList.GetCount()){
	    mapTreeCoordArray.Clear();
		mapTree.Clear();
	    map.mapStation.Clear();
		mapTree.SetRoot(Null, "Stations");
		return;
	}
    MapStation mapStation;
    map.mapStation.Clear();
    for(int i = 0; i < stationsList.GetCount(); i++){
        mapStation.longitude = stationsList[i].longitude;
        mapStation.latitude = stationsList[i].latitude;
        mapStation.azimut = stationsList[i].azimut;
        mapStation.name = stationsList[i].name;
        mapStation.radius = stationsList[i].radius;
        mapStation.isRef = stationsList[i].isRef;
        map.mapStation.Add(mapStation);
    }
    mapTreeCoordArray.Clear();
    mapTree.Clear();
    mapTree.SetRoot(Null, "Stations");
    int refTree = mapTree.Add(0, Null, "Reference list");
    int testTree = mapTree.Add(0, Null, "Test list");
    int id;
    for(int i = 0; i < stationsList.GetCount(); i++){
        if(stationsList[i].isRef){
            id = mapTree.Add(refTree, Null, stationsList[i].name);
        }else{
            id = mapTree.Add(testTree, Null, stationsList[i].name);
        }
        mapTreeCoordArray.Add(MapCoord{ id, stationsList[i].longitude, stationsList[i].latitude, 10 });
    }
    mapTree.Open(0);
    mapTree.Open(refTree);
    mapTree.Open(testTree);
}
//!---------------------------------------------------------------------------------------------

class FormMap : public WithFrmMap<TopWindow> {
public:
	typedef FormMap CLASSNAME;
	LayGisMap layGisMap;
	bool isShow;
	void OnFrmClose();
	void Serialize(Stream& s);
	FormMap();
};
FormMap::FormMap(){
	CtrlLayout(*this, String(t_("")));
	Sizeable().MinimizeBox(false).MaximizeBox(false);
	Add(layGisMap.SizePos());
	WhenClose = THISBACK(OnFrmClose);
	isShow = false;
    //layGisMap.optionTopo.Set(1);
    //layGisMap.optionMorpho.Set(1);
    //layGisMap.optionDegLines.Set(1);
}
void FormMap::Serialize(Stream& s){
	int version = 3;
	s / version;
	SerializePlacement(s);
}
void FormMap::OnFrmClose(){
	isShow = false;
	Close();
}
//!---------------------------------------------------------------------------------------



#endif
