#ifndef __HCM4A_CalcFS4A_dtGIS_h_
#define __HCM4A_CalcFS4A_dtGIS_h_


#include <iomanip>


/// Converts geographical coordinates to a formatted string.
//     * point (Pointf): The coordinates to convert.
String CoordinateToString(Pointf point) {
    std::string coo = "               ";
    double lo = point.x;
    double la = point.y;
    if (lo >= 0.0) {
        coo[3] = 'E';
    } else {
        coo[3] = 'W';
        lo = -lo;
    }
    if (la >= 0.0) {
        coo[10] = 'N';
    } else {
        coo[10] = 'S';
        la = -la;
    }
    int i = static_cast<int>(lo);
    lo = (lo - i) * 60.0;
    int j = static_cast<int>(lo);
    lo = (lo - j) * 60.0;
    int k = std::round(lo);
    if (k >= 60) {
        k -= 60;
        j += 1;
        if (j >= 60) {
            j -= 60;
            i += 1;
        }
    }
    // Write the longitude part
    std::ostringstream oss;
    oss << std::setw(3) << std::setfill('0') << i;
    coo.replace(0, 3, oss.str());
    oss.str("");
    oss.clear();
    oss << std::setw(2) << std::setfill('0') << j;
    coo.replace(4, 2, oss.str());
    oss.str("");
    oss.clear();
    oss << std::setw(2) << std::setfill('0') << k;
    coo.replace(6, 2, oss.str());

    i = static_cast<int>(la);
    la = (la - i) * 60.0;
    j = static_cast<int>(la);
    la = (la - j) * 60.0;
    k = std::round(la);
    if (k >= 60) {
        k -= 60;
        j += 1;
        if (j >= 60) {
            j -= 60;
            i += 1;
        }
    }
    // Write the latitude part
    oss.str("");
    oss.clear();
    oss << std::setw(2) << std::setfill('0') << i;
    coo.replace(8, 2, oss.str());
    oss.str("");
    oss.clear();
    oss << std::setw(2) << std::setfill('0') << j;
    coo.replace(11, 2, oss.str());
    oss.str("");
    oss.clear();
    oss << std::setw(2) << std::setfill('0') << k;
    coo.replace(13, 2, oss.str());
    return coo;
}

struct CountryGeoJon{
    String countryName;				// countryName: The name of the country.
    Array<Array<Pointf>> border;	// The border coordinates of the country.
};
/// geographical border with associated points
struct GeoBorder{
    String countryName;		// The name of the country.
    String distance;		// The distance related to the border
    Array<Pointf> points;	// The points defining the border
    Array<Pointf> points10;	// Additional points related to the border
    String ToString()const{
		return Format("%s: %s [%d %d]\n", countryName, distance, points.GetCount(), points10.GetCount());
    }
};
/// topographical and morphological data
struct GeoTopoMorpho{
    bool isTopo;		// Indicates if the data is topographical
    Point pointLD;		// A point related to the data
    String fileName;	// The name of the file containing the data
    Array<int16> data;	// The data values
};
     
/// Generates a filename for topographical or morphological data based on coordinates
//    longitude (double): The longitude coordinate.
//    latitude (double): The latitude coordinate.
//    isTopo (bool): Indicates if the data is topographical.
String GetTopoMorphoFileName(double longitude, double latitude, bool isTopo){
    std::string filename;
    filename += (longitude >= 0) ? 'E' : 'W';
    if(std::abs(longitude) >= 100){
        filename += "";
    }else if(std::abs(longitude) >= 10){
        filename += "0";
    }else{
        filename += "00";
    }
    filename += std::to_string(static_cast<int>(std::abs(longitude))).substr(0, 3);
    filename += (latitude > -1) ? 'N' : 'S';
    if(std::abs(latitude) < 10){
        filename += "0";
    }
    filename += std::to_string(static_cast<int>(std::abs(latitude))).substr(0, 2);
    filename += (std::abs(latitude) < 50) ? ".33" : ".63";
    filename += isTopo ? 'E' : 'M';
    return filename;
}

class GeoData{
public:
    Array<CountryGeoJon> countryGeoJson;
    Array<GeoBorder> borders;
    Array<String> listOfCountries;
    Array<GeoTopoMorpho> topoMorpho;
    String pathToTopo;
    String pathToMorpho;
    String pathToGeoJson;
    String pathToBorders;
    String fileWithCountriesCodes = "";
    String lastStatus;
    double GetHeight(Pointf pnt);
    int GetMorpho(Pointf pnt);
    void LoadGeoData(Progress& pi);
private:
    int ERROR_HEIGHT = -9999;
    void LoadGeoJson(Progress& pi);
    void LoadBorders(Progress& pi);
    void LoadListOfCountries(Progress& pi);
    void LoadTopoMorho(Progress& pi);
};
/// Gets the morphological data at a specific point
int GeoData::GetMorpho(Pointf pnt){
    String morpho_filename = GetTopoMorphoFileName(pnt.x, pnt.y, false);
    morpho_filename = pathToMorpho + "//" + morpho_filename.Left(4) + "//" + morpho_filename;
    FileIn morpho_file(morpho_filename);
    if(!morpho_file){
        return 0;
    }
    int lats = static_cast<int>(pnt.y * 3600);  // Change format of latitude into seconds
    int longs = static_cast<int>(pnt.x * 3600); // Change format of longitude into seconds
    double blockx = (longs - floor(pnt.x) * 3600) * 12 / 3600; // Get horizontal block number
    double blocky = (lats - floor(pnt.y) * 3600) * 12 / 3600;   // Get vertical block number
    int block_nr = static_cast<int>(blocky) * 12 + static_cast<int>(blockx); // Block number from beginning
    int stepx = pnt.y < 50 ? 100 : 50; // If latitude > 50, in the block we have 50 points in x direction
    double dotx = (blockx - floor(blockx)) * stepx; // Point x coordinate inside the block
    double doty = (blocky - floor(blocky)) * 100;   // Point y coordinate inside the block
    int dot_nr = static_cast<int>(doty) * (stepx + 1) + static_cast<int>(dotx); // Point number from beginning of the block
    long offset = block_nr * (stepx + 1) * 202 + dot_nr * 2; // Offset of the first point in bytes
    morpho_file.Seek(offset);
    int r = morpho_file.Get16();
    morpho_file.Close();
    return r;
}
/// Gets the height at a specific point.
double GeoData::GetHeight(Pointf pnt){
    String filename = GetTopoMorphoFileName(pnt.x, pnt.y, true);
    filename = pathToTopo + "//" + filename.Left(4) + "//" + filename;
    FileIn topo_file(filename);
    if(!topo_file){
        return 0;
    }
    int lats = static_cast<int>(pnt.y * 3600);  // Change format of latitude into seconds
    int longs = static_cast<int>(pnt.x * 3600); // Change format of longitude into seconds
    double blockx = (longs - floor(pnt.x) * 3600) * 12 / 3600; // Get horizontal block number
    double blocky = (lats - floor(pnt.y) * 3600) * 12 / 3600;   // Get vertical block number
    int block_nr = static_cast<int>(blocky) * 12 + static_cast<int>(blockx); // Block number from beginning
    int stepx = pnt.y < 50 ? 100 : 50; // If latitude > 50, in the block we have 50 points in x direction
    double dotx = (blockx - floor(blockx)) * stepx; // Point x coordinate inside the block
    double doty = (blocky - floor(blocky)) * 100;   // Point y coordinate inside the block
    int dot_nr = static_cast<int>(doty) * (stepx + 1) + static_cast<int>(dotx); // Point number from beginning of the block
    long offset = block_nr * (stepx + 1) * 202 + dot_nr * 2; // Offset of the first point in bytes
    topo_file.Seek(offset);
    short h1, h2, h3, h4;
    h1 = topo_file.Get16();
    h2 = topo_file.Get16();
    topo_file.SeekCur((stepx + 1 - 2) * 2);
    h3 = topo_file.Get16();
    h4 = topo_file.Get16();
    if (h1 == -9999 || h2 == -9999 || h3 == -9999 || h4 == -9999) {
        return 0;
    }
    double h12 = h1 + (h2 - h1) * (dotx - floor(dotx));
    double h34 = h3 + (h4 - h3) * (dotx - floor(dotx));
    double h = h12 + (h34 - h12) * (doty - floor(doty));
    topo_file.Close();
    return h;
}
/// Finds files in a directory matching a given mask
//		directory (const String&): The directory to search in.
//		mask (const String&): The file mask to match.
//		result (Array<String>&): The array to store the matching file names
void FindFiles(const String& directory, const String& mask, Array<String>& result){
    FindFile ff(AppendFileName(directory, "*"));
    while(ff){
        String fullPath = AppendFileName(directory, ff.GetName());
        if(ff.IsFile() && PatternMatchMulti(mask, ff.GetName())){
            result.Add(fullPath);
        } else if(ff.IsFolder() && ff.GetName() != "." && ff.GetName() != ".."){
            FindFiles(fullPath, mask, result);
        }
        ff.Next();
    }
}
/// Loads all geographical data.
void GeoData::LoadGeoData(Progress& pi){
    // LoadGeoJson();
	// RTIMING("LoadBorders");
	pi.SetPos(0);
	pi.Title(t_("Loading country borders..."));
    LoadBorders(pi);
	pi.SetPos(0);
	pi.Title(t_("..."));
    LoadListOfCountries(pi);
	pi.SetPos(0);
	pi.Title(t_("Loading topo, morho..."));
    LoadTopoMorho(pi);
}


/// Loads topographical and morphological data.
void GeoData::LoadTopoMorho(Progress& pi){
    Array<String> files;
    String directory = pathToTopo;
    String mask = "*.33E";
    FindFiles(directory, mask, files);
    mask = "*.63E";
    FindFiles(directory, mask, files);
    directory = pathToMorpho;
    mask = "*.33M";
    FindFiles(directory, mask, files);
    mask = "*.63M";
    FindFiles(directory, mask, files);
    topoMorpho.SetCount(files.GetCount());
	pi.Set(0, files.GetCount());
	pi.Create();
    for(int ifi = 0; ifi < files.GetCount(); ifi++){
		pi.Step();
        topoMorpho[ifi].fileName = files[ifi];
        topoMorpho[ifi].isTopo = files[ifi].Find(".33E") >= 0;
        String ss = GetFileName(files[ifi]);
        ss = ss.Left(ss.FindFirstOf("."));

        bool flagE = true, flagS = true;
        int ewPos = ss.Find("E");
        int snPos = ss.Find("S");
        if(ewPos < 0){
            ewPos = ss.Find("W");
            flagE = false;
        }
        if(snPos < 0){
            snPos = ss.Find("N");
            flagS = false;
        }
        topoMorpho[ifi].pointLD.x = StrInt(ss.Mid(ewPos + 1, snPos - ewPos - 1));
        topoMorpho[ifi].pointLD.y = StrInt(ss.Mid(snPos + 1));
        if(!flagE){
            topoMorpho[ifi].pointLD.x *= -1;
        }
        if(flagS){
            topoMorpho[ifi].pointLD.y *= -1;
        }
    }
}
/// Loads the list of countries.
void GeoData::LoadListOfCountries(Progress& pi){
    listOfCountries.Clear();
    FileIn file(fileWithCountriesCodes);
    if(!file){
        return;
    }
    while(!file.IsEof()){
        String line = file.GetLine();
        if(!line.IsEmpty()){
            listOfCountries.Add(line);
        }
    }
}

/// Loads border data
void GeoData::LoadBorders(Progress& pi){
	String directory = pathToBorders;
    Array<String> files;
    String mask = "*.*";
    borders.Clear();
    FindFiles(directory, mask, files);
	FileIn file;
    int index = 0;
	pi.Set(0, files.GetCount());
	pi.Create();
    for(int ifi = 0; ifi < files.GetCount(); ifi++){
		pi.Step();
        if(!file.Open(files[ifi])){
            continue;
        }
        borders.Add();
        borders[index].countryName = GetFileName(files[ifi]);
        borders[index].distance = GetFileExt(files[ifi]);
        borders[index].countryName.Replace(borders[index].distance, "");
        borders[index].distance.Replace(".", "");

        Pointf point;
        int counter = 0;
        file.Seek(0);
        while(!file.IsEof()){
            if(file.Get(&point.x, sizeof(point.x)) != sizeof(point.x)){
                break;
            }
            point.x = point.x * 57.2957795130;
            if(file.Get(&point.y, sizeof(point.y)) != sizeof(point.y)){
                break;
            }
            point.y = point.y * 57.2957795130;
            counter++;
            counter % 11 != 0 ? borders[index].points.Add(point) : borders[index].points10.Add(point);
        }
        index++;
    }
    file.Close();
}

/// Loads GeoJSON data
void GeoData::LoadGeoJson(Progress& pi){
    String directory = pathToBorders;
    String mask = "*.geojson";
    Array<String> files;
    FindFiles(directory, mask, files);
    countryGeoJson.Clear();
    CountryGeoJon cntr;
    for(int ifi = 0; ifi < files.GetCount(); ifi++){
        String jsonData = LoadFile(files[ifi]);
        if(jsonData.IsEmpty()){
            Cout() << "Error loading GeoJSON.\n";
            return;
        }
        Value json = ParseJSON(jsonData);
        if(json.IsError()){
            Cout() << "Error parsing JSON.\n";
            return;
        }
        if(json["type"] != "FeatureCollection"){
            Cout() << "Error: there are no FeatureCollection.\n";
            return;
        }
        const Value& features = json["features"];
        for(int i = 0; i < features.GetCount(); i++){
            const Value& feature = features[i];
            if(feature["geometry"]["type"] != "Polygon" && feature["geometry"]["type"] != "MultiPolygon"){
                continue;
            }
            String name = feature["properties"]["name"];
            cntr.countryName = name;
            const Value& coordinates = feature["geometry"]["coordinates"];
            if(feature["geometry"]["type"] == "Polygon"){
                cntr.border.Add();
                for(int j = 0; j < coordinates[0].GetCount(); j++){
                    double x = coordinates[0][j][0];
                    double y = coordinates[0][j][1];
                    cntr.border[0].Add(Pointf(x, y));
                }
            }else if(feature["geometry"]["type"] == "MultiPolygon"){
                for(int j = 0; j < coordinates.GetCount(); j++){
                    cntr.border.Add();
                    for(int k = 0; k < coordinates[j][0].GetCount(); k++){
                        double x = coordinates[j][0][k][0];
                        double y = coordinates[j][0][k][1];
                        cntr.border[j].Add(Pointf(x, y));
                    }
                }
            }
        }
    }
}


/*

struct MapsTabDlg : WithMapsLayout<ParentCtrl> {
public:
    GisMap map;
    StatusBar statusBar;
    InfoCtrl infoLongLati, infoZoom, infoTopo, infoMorpho, infoDop;
    Option showTopoTile, showMorphoTile, showDegLines;
    MapsTabDlg();
    void ChangeOptions();
};

   MapsTabDlg tabMap;

*/



#endif
