#include "GoogleTile.h"
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <fstream>
#include <cmath>
#include <filesystem>

using std::string;
#if (__cplusplus < 201703L)
    #include <experimental/filesystem>
    namespace fs = std::experimental::filesystem;
#else
    namespace fs = std::filesystem;
#endif


#define M_PI 3.1415926

googleTile::googleTile()
{
}

googleTile::~googleTile()
{
}

void googleTile::ExportGeoPng(cv::Mat _pan, TileInfo info, std::string dir)
{
    // 创建目录
    if (!fs::exists(dir))
    {
        fs::create_directories(dir);
    }

    // 保存全景图
    string png_path = dir + "/" + info.tileName + ".png";
    cv::imwrite(png_path, _pan);

    // 写入kml
    string kml_path = dir + "/" + info.tileName + ".kml";

    vector<TileInfo> taskTiles;
    taskTiles.push_back(info);
    WriteKml(taskTiles, kml_path);

}

void googleTile::ExportTileSet(cv::Mat _pan, cv::Mat _panMask, vector<TileInfo> taskTilesVec, std::string dir,std::string fileString)
{

    int panWidth = _pan.cols;
    int panHeight = _pan.rows;

    int zoom = taskTilesVec[0].ind.z;

    // 过滤掉无效瓦片
    vector<TileInfo> vaildTilesVec;

    for (size_t i = 0; i < taskTilesVec.size(); i++)
    {
        TileInfo tile = taskTilesVec[i];

        TileBox tilebox = tile.boxLatLon;

        RECT32S tileBoxInPan = tile.boxInPan;

        int x1 = tileBoxInPan.x;
        int x2 = tileBoxInPan.x + tileBoxInPan.w;
        int y1 = tileBoxInPan.y;
        int y2 = tileBoxInPan.y + tileBoxInPan.h;

        // 有不完整瓦片，不保存
        if (x1 < 0 || y1 < 0 || x2 > panWidth - 1 || y2 > panHeight - 1)
        {
            continue;
        }

        // 确保坐标在图像范围内
        x1 = std::max(0, std::min(x1, panWidth - 1));
        y1 = std::max(0, std::min(y1, panHeight - 1));
        x2 = std::max(0, std::min(x2, panWidth - 1));
        y2 = std::max(0, std::min(y2, panHeight - 1));

        // 提取瓦片
        cv::Rect tileRect(x1, y1, x2 - x1, y2 - y1);
        cv::Mat tileImg = _pan(tileRect);

        cv::Mat tileMask = _panMask(tileRect);


        // 瓦片有内容
        if(cv::countNonZero(tileMask) > 0)
        {
            vaildTilesVec.push_back(tile);
        }
        else
        {
            continue;
        }

        // 标准web瓦片尺寸
        if (tileImg.cols != 256 || tileImg.rows != 256)
        {
            cv::resize(tileImg, tileImg, cv::Size(256, 256));
        }

        // 生成文件名
        std::string tileDir = dir + "/" + std::to_string(zoom);
        std::string fileName = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
        // 创建目录
        if (!fs::exists(tileDir))
        {
            fs::create_directories(tileDir);
        }

        // 保存瓦片为图像文件
        cv::imwrite(fileName, tileImg);

    }


    // 输出KML
    WriteKml(vaildTilesVec,dir + "/" + fileString + "_z" + std::to_string(zoom) + ".kml");

}

void googleTile::ExportOneTile(cv::Mat _pan, TileInfo tile, std::string dir, std::string fileString)
{

    int panWidth = _pan.cols;
    int panHeight = _pan.rows;
    int zoom = tile.ind.z;

    TileBox tilebox = tile.boxLatLon;

    RECT32S tileBoxInPan = tile.boxInPan;

    int x1 = tileBoxInPan.x;
    int x2 = tileBoxInPan.x + tileBoxInPan.w;
    int y1 = tileBoxInPan.y;
    int y2 = tileBoxInPan.y + tileBoxInPan.h;

    // 有不完整瓦片，不保存
    if (x1 < 0 || y1 < 0 || x2 > panWidth - 1 || y2 > panHeight - 1)
    {
        return;
    }

    // 确保坐标在图像范围内
    x1 = std::max(0, std::min(x1, panWidth - 1));
    y1 = std::max(0, std::min(y1, panHeight - 1));
    x2 = std::max(0, std::min(x2, panWidth - 1));
    y2 = std::max(0, std::min(y2, panHeight - 1));

    // 提取瓦片
    cv::Rect tileRect(x1, y1, x2 - x1, y2 - y1);
    cv::Mat tileImg = _pan(tileRect);

    // 标准web瓦片尺寸
    if (tileImg.cols != 256 || tileImg.rows != 256)
    {
        cv::resize(tileImg, tileImg, cv::Size(256, 256));
    }

    // 生成文件名
    std::string tileDir = dir + "/" + std::to_string(zoom);
    std::string fileName = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
    std::string kmlPath = tileDir + "/" + std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".kml";
    
    tile.href = std::to_string(tile.ind.x) + "_" + std::to_string(tile.ind.y) + ".png";
    
    // 创建目录
    if (!fs::exists(tileDir))
    {
        fs::create_directories(tileDir);
    }

    // 保存瓦片为图像文件
    cv::imwrite(fileName, tileImg);

    vector<TileInfo> info;
    info.push_back(tile);
    WriteKml(info, kmlPath);
}



vector<TileInfo> googleTile::CalcTileOverlayVec(TileInfo areaInfo,int panWidth,int panHeight)
{
    int zoom = areaInfo.ind.z;
    TileBox panBox = areaInfo.boxLatLon;

    // 计算四至的瓦片编号
    TileIndex WN = LatLonToTile(panBox.north, panBox.west, zoom);
    TileIndex WS = LatLonToTile(panBox.south, panBox.west, zoom);
    TileIndex EN = LatLonToTile(panBox.north, panBox.east, zoom);
    TileIndex ES = LatLonToTile(panBox.south, panBox.east, zoom);

    int xStart = WN.x;
    int xEnd = EN.x;

    int yStart = EN.y;
    int yEnd = ES.y;


    vector<TileInfo> taskTiles;

    for (size_t i = yStart; i < yEnd; i++)
    {
        for (size_t j = xStart; j < xEnd; j++)
        {
            TileIndex id = { j,i,zoom };
            TileBox tilebox = GetTileBox(id);

            // 瓦片不完整覆盖则不输出
            if (tilebox.west < panBox.west|| panBox.north < tilebox.north
                || tilebox.east > panBox.east || panBox.south > tilebox.south)
            {
                continue;
            }


            // 计算瓦片在全景图中的像素位置
            RECT32S rect = { 0 };
            rect.x = (tilebox.west - panBox.west) / (panBox.east - panBox.west) * panWidth;
            rect.y = (panBox.north - tilebox.north) / (panBox.north - panBox.south) * panHeight;
            rect.w = (tilebox.east - panBox.west) / (panBox.east - panBox.west) * panWidth - rect.x;
            rect.h = (panBox.north - tilebox.south) / (panBox.north - panBox.south) * panHeight - rect.y;



            // 填充
            TileInfo info = { 0 };
            info.boxLatLon = tilebox;
            info.ind = id;
            info.boxInPan = rect;

            // 补充瓦片信息
            info.tileName = std::to_string(j) + "_" + std::to_string(i) + "_" + std::to_string(zoom);

            info.href = std::to_string(zoom) + "/" + std::to_string(j) + "_" + std::to_string(i) + ".png";

            taskTiles.push_back(info);

        }

    }

    return taskTiles;
}



int googleTile::ZoomLevel(float mp)
{
    int nLev = 0;
    // 计算单位瓦片的实际覆盖
    int meters_cover = TILE_SIZE * 1.0/mp;

    int earthLen = 40075017;

    nLev = std::floor(std::log2(earthLen / meters_cover));

    return nLev;
}


TileIndex googleTile::LatLonToTile(double latitude, double longitude, int zoomLevel)
{
    // 将纬度转换为墨卡托投影
    double latRad = latitude * M_PI / 180.0;
    double sinLat = sin(latRad);

    // 计算经度的瓦片编号（0-2^zoomLevel）
    int xTile = static_cast<int>((longitude + 180.0) / 360.0 * pow(2.0, zoomLevel));

    // 计算纬度的瓦片编号（0-2^zoomLevel）
    int yTile = static_cast<int>((1.0 - log((1.0 + sinLat) / (1.0 - sinLat)) / (2.0 * M_PI)) / 2.0 * pow(2.0, zoomLevel));

    return TileIndex{ xTile, yTile, zoomLevel };
}


// 将瓦片编号转换为经纬度
void TileToLatLon(int x, int y, int z, double& lat, double& lon) 
{
    double n = std::pow(2.0, z);
    lon = x / n * 360.0 - 180.0;
    double lat_rad = std::atan(std::sinh(M_PI * (1 - 2 * y / n)));
    lat = lat_rad * 180.0 / M_PI;
}

TileBox googleTile::GetTileBox(TileIndex ind)
{
    TileBox box = {0};

    // 计算瓦片的西北角经纬度
    TileToLatLon(ind.x, ind.y, ind.z, box.north, box.west);

    // 计算瓦片的东南角经纬度
    TileToLatLon(ind.x + 1, ind.y + 1, ind.z, box.south, box.east);

    return box;
}

void googleTile::WriteKml(vector<TileInfo> tiles, string filePath)
{
    // Open the KML file for writing
    std::ofstream kml_file(filePath);
    if (!kml_file.is_open()) {
        std::cerr << "Failed to open KML file for writing: " << filePath << std::endl;
        return;
    }

    // Write the KML header
    kml_file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
    kml_file << "<kml xmlns=\"http://www.opengis.net/kml/2.2\">" << std::endl;
    kml_file << "<Document>" << std::endl;

    // Loop through each tile and write a GroundOverlay element
    for (const auto& tile : tiles) 
    {
        kml_file << "<GroundOverlay>" << std::endl;
        kml_file << "  <name>" << tile.tileName << "</name>" << std::endl;
        kml_file << "  <Icon>" << std::endl;
        kml_file << "    <href>" << tile.href << "</href>" << std::endl;
        kml_file << "  </Icon>" << std::endl;
        kml_file << "  <LatLonBox>" << std::endl;
        kml_file << "    <north>" << std::fixed << std::setprecision(6) << tile.boxLatLon.north << "</north>" << std::endl;
        kml_file << "    <south>" << std::fixed << std::setprecision(6) << tile.boxLatLon.south << "</south>" << std::endl;
        kml_file << "    <east>" << std::fixed << std::setprecision(6) << tile.boxLatLon.east << "</east>" << std::endl;
        kml_file << "    <west>" << std::fixed << std::setprecision(6) << tile.boxLatLon.west << "</west>" << std::endl;
        kml_file << "  </LatLonBox>" << std::endl;
        kml_file << "</GroundOverlay>" << std::endl;
    }

    // Write the KML footer
    kml_file << "</Document>" << std::endl;
    kml_file << "</kml>" << std::endl;

    // Close the file
    kml_file.close();
}


