#include "PlanetData.h"
#include <iostream>
#include <numeric>
#include <cmath>
#include <algorithm>

namespace ZL {

    Matrix3f GetRotationForTriangle(const Triangle& tri);

    const float PlanetData::PLANET_RADIUS = 20000.f;
    const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f };

    // --- Константы диапазонов (перенесены из PlanetObject.cpp) ---

    VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) {
        return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1;
    }

    // Вспомогательная функция для проекции (локальная)
    static Vector3f projectPointOnPlane(const Vector3f& P, const Vector3f& A, const Vector3f& B, const Vector3f& C) {
        Vector3f AB = B + A * (-1.0f);
        Vector3f AC = C + A * (-1.0f);
        Vector3f N = AB.cross(AC).normalized();
        Vector3f AP = P + A * (-1.0f);
        float distance = N.dot(AP);
        return P + N * (-distance);
    }

    PlanetData::PlanetData()
        : perlin(77777)
        , colorPerlin(123123)
        //, currentLod(0)
    {
       // currentLod = planetMeshLods.size() - 1; // Start with max LOD
        /*
        initialVertexMap = {
            {{ 0.0f, 1.0f, 0.0f}, "A"},
            {{ 0.0f, -1.0f, 0.0f}, "B"},
            {{ 1.0f, 0.0f, 0.0f}, "C"},
            {{-1.0f, 0.0f, 0.0f}, "D"},
            {{ 0.0f, 0.0f, 1.0f}, "E"},
            {{ 0.0f, 0.0f, -1.0f}, "F"}
        };*/
    }

    void PlanetData::init() {
        for (int i = 0; i < planetMeshLods.size(); i++) {
            //planetMeshLods[i] = generateSphere(i, 0.01f);
            planetMeshLods[i] = generateSphere(i, 0.f);
            planetMeshLods[i].Scale(PLANET_RADIUS);
            planetMeshLods[i].Move(PLANET_CENTER_OFFSET);
        }

        planetAtmosphereLod = generateSphere(5, 0);
        planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
        planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);

        const auto& lodLevel = getLodLevel();

        for (size_t i = 0; i < lodLevel.triangles.size(); i++)
        {
            if (i % 100 == 0)
            {
                PlanetCampObject campObject;

                campObject.position = (lodLevel.triangles[i].data[0] +
                    lodLevel.triangles[i].data[1] +
                    lodLevel.triangles[i].data[2]) / 3.0f;

                auto newM = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitX())).toRotationMatrix();

                campObject.rotation = GetRotationForTriangle(lodLevel.triangles[i]).inverse() * newM;
                campObjects.push_back(campObject);
            }
        }
    }

    const LodLevel& PlanetData::getLodLevel() const {
        return planetMeshLods.at(MAX_LOD_LEVELS-1);
    }


    const LodLevel& PlanetData::getAtmosphereLod() const {
        return planetAtmosphereLod;
    }

    /*
    int PlanetData::getCurrentLodIndex() const {
        return currentLod;
    }

    int PlanetData::getMaxLodIndex() const {
        return static_cast<int>(planetMeshLods.size() - 1);
    }*/

    std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) {
        
        
        float currentZNear;
        float currentZFar;
        float alpha;

        if (dToPlanetSurface > 2000)
        {
            currentZNear = 1000;
            currentZFar = currentZNear * 100;
        }
        else if (dToPlanetSurface > 1200)
        {
            currentZNear = 500;
            currentZFar = currentZNear * 100;
        }
        else if (dToPlanetSurface > 650)
        {
            currentZNear = 250;
            currentZFar = currentZNear * 100;
        }
        else if (dToPlanetSurface > 160)
        {
            currentZNear = 125;
            currentZFar = currentZNear * 150;
        }
        else if (dToPlanetSurface > 100)
        {
            currentZNear = 65;
            currentZFar = currentZNear * 170;
        }
        else if (dToPlanetSurface > 40)
        {
            currentZNear = 32;
            currentZFar = 10000.f;
        }
        else if (dToPlanetSurface > 20)
        {
            currentZNear = 16;
            currentZFar = 5000.f;
        }
        else if (dToPlanetSurface > 5)
        {
            currentZNear = 8;
            currentZFar = 2500.f;
        }
        else
        {
            currentZNear = 4;
            currentZFar = 1250.f;
        }

        return { currentZNear, currentZFar };
    }

    float PlanetData::distanceToPlanetSurfaceFast(const Vector3f& viewerPosition)
    {
        Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET;

        return sqrt(shipLocalPosition.squaredNorm()) - PLANET_RADIUS;
    }


    std::vector<int> PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) {
        const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1]; // Работаем с текущим активным LOD
        Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized();

        int bestTriangle = -1;
        float maxDot = -1.0f;

        // Шаг 1: Быстрый поиск ближайшего треугольника по "центроиду"
        // Чтобы не проверять все, можно проверять каждый N-й или использовать 
        // предварительно вычисленные центры для LOD0, чтобы сузить круг.
        // Но для надежности пройдемся по массиву (для 5-6 подразделений это быстро)
        for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
            // Вычисляем примерное направление на треугольник
            Vector3f triDir = (finalLod.triangles[i].data[0] +
                finalLod.triangles[i].data[1] +
                finalLod.triangles[i].data[2]).normalized();

            float dot = targetDir.dot(triDir);
            if (dot > maxDot) {
                maxDot = dot;
                bestTriangle = i;
            }
        }

        if (bestTriangle == -1) return {};

        return { bestTriangle };
    }

    std::vector<int> PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) {
        const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1];
        Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET;
        float currentDist = shipLocal.norm();
        Vector3f targetDir = shipLocal.normalized();

        // Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка)
        // Подбери это значение так, чтобы камни вокруг корабля всегда были видны.
        const float desiredCoverageRadius = 3000.0f;

        // Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния.
        // Чем мы дальше (currentDist больше), тем меньше должен быть угол отклонения 
        // от нормали, чтобы захватить ту же площадь.
        float angle = atan2(desiredCoverageRadius, currentDist);
        float searchThreshold = cos(angle);

        // Ограничитель, чтобы не захватить всю планету или вообще ничего
        searchThreshold = std::clamp(searchThreshold, 0.90f, 0.9999f);

        std::vector<int> result;
        for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
            // Используем центроид (можно кэшировать в LodLevel для скорости)
            Vector3f triDir = (finalLod.triangles[i].data[0] +
                finalLod.triangles[i].data[1] +
                finalLod.triangles[i].data[2]).normalized();

            if (targetDir.dot(triDir) > searchThreshold) {
                result.push_back(i);
            }
        }

        if (result.empty()) return getBestTriangleUnderCamera(viewerPosition);
        return result;
    }
    
    std::vector<Triangle> PlanetData::subdivideTriangles(const std::vector<Triangle>& input, float noiseCoeff) {
        std::vector<Triangle> output;

        for (const auto& t : input) {
            // Вершины и их ID
            const Vector3f& a = t.data[0];
            const Vector3f& b = t.data[1];
            const Vector3f& c = t.data[2];
            const VertexID& id_a = t.ids[0];
            const VertexID& id_b = t.ids[1];
            const VertexID& id_c = t.ids[2];

            // 1. Вычисляем середины (координаты)
            Vector3f m_ab = ((a + b) * 0.5f).normalized();
            Vector3f m_bc = ((b + c) * 0.5f).normalized();
            Vector3f m_ac = ((a + c) * 0.5f).normalized();

            //Vector3f pm_ab = m_ab * perlin.getSurfaceHeight(m_ab, noiseCoeff);
            //Vector3f pm_bc = m_bc * perlin.getSurfaceHeight(m_bc, noiseCoeff);
            //Vector3f pm_ac = m_ac * perlin.getSurfaceHeight(m_ac, noiseCoeff);

            Vector3f pm_ab = m_ab;
            Vector3f pm_bc = m_bc;
            Vector3f pm_ac = m_ac;

            // 2. Вычисляем ID новых вершин
            VertexID id_mab = generateEdgeID(id_a, id_b);
            VertexID id_mbc = generateEdgeID(id_b, id_c);
            VertexID id_mac = generateEdgeID(id_a, id_c);

            // 3. Формируем 4 новых треугольника
            output.emplace_back(Triangle{ {a, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0
            output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1
            output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2
            output.emplace_back(Triangle{ {pm_ab, pm_bc, pm_ac}, {id_mab, id_mbc, id_mac} }); // 3
        }
        return output;
    }

    LodLevel PlanetData::createLodLevel(const std::vector<Triangle>& geometry) {
        LodLevel result;
        result.triangles = geometry;

        size_t vertexCount = geometry.size() * 3;

        result.VertexIDs.reserve(vertexCount);

        for (const auto& t : geometry) {
            for (int i = 0; i < 3; ++i) {
                result.VertexIDs.push_back(t.ids[i]);
            }
        }
        return result;
    }

    void PlanetData::recalculateMeshAttributes(LodLevel& lod)
    {
        size_t vertexCount = lod.triangles.size() * 3;

        lod.vertexData.PositionData.clear();
        lod.vertexData.NormalData.clear();
        lod.vertexData.TexCoordData.clear();
        lod.vertexData.TangentData.clear();
        lod.vertexData.BinormalData.clear();
        lod.vertexData.ColorData.clear();

        lod.vertexData.PositionData.reserve(vertexCount);
        lod.vertexData.NormalData.reserve(vertexCount);
        lod.vertexData.TexCoordData.reserve(vertexCount);
        lod.vertexData.TangentData.reserve(vertexCount);
        lod.vertexData.BinormalData.reserve(vertexCount);
        lod.vertexData.ColorData.reserve(vertexCount);



        const std::array<Vector2f, 3> triangleUVs = {
            Vector2f(0.5f, 1.0f),
            Vector2f(0.0f, 0.0f),
            Vector2f(1.0f, 0.0f)
        };

        const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый
        const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый

        const float colorFrequency = 4.0f; // Масштаб пятен


        for (const auto& t : lod.triangles) {
            // --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) ---
            Vector3f vA = t.data[0];
            Vector3f vB = t.data[1];
            Vector3f vC = t.data[2];

            Vector3f x_axis = (vC - vB).normalized(); // Направление U

            Vector3f edge1 = vB - vA;
            Vector3f edge2 = vC - vA;
            Vector3f z_axis = edge1.cross(edge2).normalized(); // Нормаль плоскости

            // Проверка направления нормали наружу (от центра планеты)
            Vector3f centerToTri = (vA + vB + vC).normalized();
            if (z_axis.dot(centerToTri) < 0) {
                z_axis = z_axis * -1.0f;
            }

            Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Направление V

            for (int i = 0; i < 3; ++i) {
                lod.vertexData.PositionData.push_back(t.data[i]);
                lod.vertexData.NormalData.push_back(z_axis);
                lod.vertexData.TexCoordData.push_back(triangleUVs[i]);
                lod.vertexData.TangentData.push_back(x_axis);
                lod.vertexData.BinormalData.push_back(y_axis);

                // Используем один шум для выбора между розовым и желтым
                Vector3f dir = t.data[i].normalized();
                float blendFactor = colorPerlin.noise(
                    dir(0) * colorFrequency,
                    dir(1) * colorFrequency,
                    dir(2) * colorFrequency
                );

                // Приводим шум из диапазона [-1, 1] в [0, 1]
                blendFactor = blendFactor * 0.5f + 0.5f;

                // Линейная интерполяция между двумя цветами
                Vector3f finalColor;

                finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish);

                lod.vertexData.ColorData.push_back(finalColor);
            }
        }
    }

    LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) {
        const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;

        // 12 базовых вершин икосаэдра
        std::vector<Vector3f> icosaVertices = {
            {-1,  t,  0}, { 1,  t,  0}, {-1, -t,  0}, { 1, -t,  0},
            { 0, -1,  t}, { 0,  1,  t}, { 0, -1, -t}, { 0,  1, -t},
            { t,  0, -1}, { t,  0,  1}, {-t,  0, -1}, {-t,  0,  1}
        };

        // Нормализуем вершины
        for (auto& v : icosaVertices) v = v.normalized();

        // 20 граней икосаэдра
        struct IndexedTri { int v1, v2, v3; };
        std::vector<IndexedTri> faces = {
            {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
            {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
            {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
            {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
        };

        std::vector<Triangle> geometry;
        for (auto& f : faces) {
            Triangle tri;
            tri.data[0] = icosaVertices[f.v1];
            tri.data[1] = icosaVertices[f.v2];
            tri.data[2] = icosaVertices[f.v3];

            // Генерируем ID для базовых вершин (можно использовать их координаты)
            for (int i = 0; i < 3; ++i) {
                tri.ids[i] = std::to_string(tri.data[i](0)) + "_" +
                    std::to_string(tri.data[i](1)) + "_" +
                    std::to_string(tri.data[i](2));
            }
            geometry.push_back(tri);
        }

        // 3. Разбиваем N раз
        for (int i = 0; i < subdivisions; i++) {
            geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем
        }

        // 4. Создаем LodLevel и заполняем топологию (v2tMap)
        LodLevel lodLevel = createLodLevel(geometry);

        // Пересобираем v2tMap (она критична для релаксации)
        lodLevel.v2tMap.clear();
        for (size_t i = 0; i < geometry.size(); ++i) {
            for (int j = 0; j < 3; ++j) {
                lodLevel.v2tMap[geometry[i].ids[j]].push_back((int)i);
            }
        }

        // 5. Применяем итеративную релаксацию (Lloyd-like)
        // 5-10 итераций достаточно для отличной сетки
        applySphericalRelaxation(lodLevel, 8);

        // 6. Накладываем шум и обновляем атрибуты
        // ... (твой код наложения шума через Perlin)

        recalculateMeshAttributes(lodLevel);
        return lodLevel;
    }



    void PlanetData::applySphericalRelaxation(LodLevel& lod, int iterations) {
        for (int iter = 0; iter < iterations; ++iter) {
            std::map<VertexID, Vector3f> newPositions;

            for (auto const& [vID, connectedTris] : lod.v2tMap) {
                Vector3f centroid(0, 0, 0);

                // Находим среднюю точку среди центров всех соседних треугольников
                for (int triIdx : connectedTris) {
                    const auto& tri = lod.triangles[triIdx];
                    Vector3f faceCenter = (tri.data[0] + tri.data[1] + tri.data[2]) * (1.0f / 3.0f);
                    centroid = centroid + faceCenter;
                }

                centroid = centroid * (1.0f / (float)connectedTris.size());

                // Проецируем обратно на единичную сферу
                newPositions[vID] = centroid.normalized();
            }

            // Синхронизируем данные в треугольниках
            for (auto& tri : lod.triangles) {
                for (int i = 0; i < 3; ++i) {
                    tri.data[i] = newPositions[tri.ids[i]];
                }
            }
        }
    }
}