using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using MindPowerSdk;

public static class TerrainGenerator
{
    /// <summary>
    /// 利用 MPMap 数据生成 Terrain,采用自定义材质实现贴图混合
    /// </summary>
    /// <param name="map">地图数据</param>
    /// <param name="chunkSize">每块区域的瓦片数(例如 64)</param>
    /// <param name="terrainMaterial">基于自定义Shader(例如 Custom/TerrainUVBlendShader)的材质</param>
    /// <param name="parent">生成的 Terrain 父节点</param>
    /// <param name="isSmoothing"></param>
    /// <param name="entryMapName"></param>
    public static List<Texture> GenTerrain(MPMap  map, int chunkSize, Material terrainMaterial, Transform parent,
                                           string entryMapName, bool isSmoothing = true)
    {
        List<Texture> textureList = new List<Texture>();
        // 计算全局高度范围(归一化用)
        (float globalMin, float globalMax) = ComputeGlobalHeightRange(map);

        int xCnt       = Mathf.CeilToInt((float)map.Width  / chunkSize);
        int yCnt       = Mathf.CeilToInt((float)map.Height / chunkSize);
        var terrainMap = new GameObject(entryMapName);
        terrainMap.transform.parent = parent;
        for (int i = 0; i < yCnt; i++)
        {
            for (int j = 0; j < xCnt; j++)
            {
                int startX = j * chunkSize;
                int startY = i * chunkSize;
                // 为保证边界内不越界,右侧和上侧减1
                int endX = Mathf.Min(startX + chunkSize, map.Width  - 1);
                int endY = Mathf.Min(startY + chunkSize, map.Height - 1);
                int resX = (endX - startX) + 1; // 横向瓦片数
                int resY = (endY - startY) + 1; // 纵向瓦片数

                // 为了保证生成的 Terrain 与原始地图高度一致,并且位置正确,需要使用固定分辨率
                int newRes = 4097;

                // 创建 TerrainData,设置高度图和混合图分辨率以及尺寸
                TerrainData terrainData = new TerrainData();
                terrainData.heightmapResolution = newRes;
                terrainData.alphamapResolution  = newRes;
                terrainData.size                = new Vector3((resX - 1), globalMax - globalMin, (resY - 1));

                // 生成高度图(归一化到 [0,1])
                float[,] heights = GenerateHeightMap(map, startX, startY, resX, resY, newRes, globalMin, globalMax,
                                                     isSmoothing);
                terrainData.SetHeights(0, 0, heights);

                // ---------------------- 新增部分 -------------------------
                // 生成贴图编号和遮罩贴图(调用你已有的 GenTxtNoTexture 方法)
                Debug.LogWarning($"{startX} {startY} {chunkSize}");
                (Texture2D texNo, Texture2D maskNo) = map.GenTxtNoTexture((short)startX, (short)startY, chunkSize);

                textureList.Add(texNo);
                textureList.Add(maskNo);
                // 设置每个 tile 在辅助 baked UV 贴图中希望的像素尺寸(建议不小于 4)
                int cellPixelSize = 7;


                // 创建材质实例,并设置贴图
                Material matInstance = new Material(terrainMaterial);
                matInstance.SetTexture("_TexNo",  texNo);
                matInstance.SetTexture("_MaskNo", maskNo);


                // 计算每个 tile 在 UV 空间内的尺寸(通常为 1/chunkSize)
                Vector2 mainTileScale = new Vector2(1.0f / chunkSize, 1.0f / chunkSize);
                matInstance.SetVector("_MainTileOffset", new Vector4(mainTileScale.x, mainTileScale.y, 0, 0));

                // 设置 Terrain 尺寸参数,Shader 内部可根据该参数计算全局 UV0(X:宽度,Z:高度)
                matInstance.SetVector("_TerrainSize", new Vector4((resX - 1), (resY - 1), 0, 0));

                matInstance.SetFloat("_Repeat", (resX - 1));
                // ---------------------- 新增部分结束 -------------------------

                // 生成 Terrain 游戏对象并设置位置(Y 坐标偏移 globalMin)
                GameObject terrainGO = Terrain.CreateTerrainGameObject(terrainData);
                terrainGO.name               = $"terrain_{i}_{j}";
                terrainGO.transform.parent   = terrainMap.transform;
                terrainGO.transform.position = new Vector3(startX, globalMin, startY - chunkSize + 1);
                Debug.Log($"terrainGO: {terrainGO.name} | pos: {terrainGO.transform.position}");
                // 设置 Terrain 为自定义材质模式,并赋予生成的材质实例
                Terrain terrainComponent = terrainGO.GetComponent<Terrain>();
                terrainComponent.materialType     = Terrain.MaterialType.Custom;
                terrainComponent.materialTemplate = matInstance;
            }
        }

        return textureList;
    }


    // 以下方法与原有代码一致

    private static float[,] GenerateHeightMap(MPMap map,       int   startX, int startY, int resX, int resY, int newRes,
                                              float globalMin, float globalMax, bool isSmoothing)
    {
        // 采集原始高度数据,并归一化到 [0,1]
        float[,] fullHeights = new float[resY, resX];
        for (int z = 0; z < resY; z++)
        {
            for (int x = 0; x < resX; x++)
            {
                int    worldX = startX + x;
                int    worldY = startY + z;
                MPTile tile   = map.GetTile(worldX, worldY);
                float  norm   = (globalMax - globalMin) > 0 ? (tile.Height - globalMin) / (globalMax - globalMin) : 0f;
                fullHeights[resY - z - 1, x] = norm;
            }
        }

        // 利用双三次插值生成新分辨率高度图
        float[,] resampled = new float[newRes, newRes];
        float    scaleX    = (float)(resX - 1) / (newRes - 1);
        float    scaleY    = (float)(resY - 1) / (newRes - 1);
        for (int z = 0; z < newRes; z++)
        {
            float origZ = z * scaleY;
            for (int x = 0; x < newRes; x++)
            {
                float origX = x * scaleX;
                resampled[z, x] = BicubicInterpolate(fullHeights, origX, origZ, resX, resY);
            }
        }

        if (isSmoothing)
        {
            // 后处理:高斯模糊平滑
            float[,] smoothed = ApplyGaussianBlur(resampled, newRes, newRes);
            return smoothed;
        }
        else
        {
            return resampled;
        }
    }

    private static float BicubicInterpolate(float[,] data, float x, float z, int resX, int resY)
    {
        int   xInt = Mathf.FloorToInt(x);
        int   zInt = Mathf.FloorToInt(z);
        float s    = x - xInt;
        float t    = z - zInt;

        float[] arr = new float[4];
        for (int m = -1; m <= 2; m++)
        {
            int   sampleZ = Mathf.Clamp(zInt + m, 0, resY - 1);
            float p0      = data[sampleZ, Mathf.Clamp(xInt - 1, 0, resX - 1)];
            float p1      = data[sampleZ, Mathf.Clamp(xInt,     0, resX - 1)];
            float p2      = data[sampleZ, Mathf.Clamp(xInt              + 1, 0, resX - 1)];
            float p3      = data[sampleZ, Mathf.Clamp(xInt              + 2, 0, resX - 1)];
            arr[m + 1] = CubicInterpolate(p0, p1, p2, p3, s);
        }

        return CubicInterpolate(arr[0], arr[1], arr[2], arr[3], t);
    }

    private static float CubicInterpolate(float p0, float p1, float p2, float p3, float t)
    {
        float a0 = p3 - p2 - p0 + p1;
        float a1 = p0           - p1 - a0;
        float a2 = p2           - p0;
        float a3 = p1;
        return ((a0 * t + a1) * t + a2) * t + a3;
    }

    private static float[,] ApplyGaussianBlur(float[,] data, int width, int height)
    {
        float[,] result       = new float[height, width];
        int      kernelSize   = 3;
        int      kernelRadius = kernelSize / 2;
        float[,] kernel = new float[,]
        {
            { 1f, 2f, 1f },
            { 2f, 4f, 2f },
            { 1f, 2f, 1f }
        };
        float kernelSum = 16f;

        for (int z = 0; z < height; z++)
        {
            for (int x = 0; x < width; x++)
            {
                float sum = 0f;
                for (int kz = -kernelRadius; kz <= kernelRadius; kz++)
                {
                    int sampleZ = Mathf.Clamp(z + kz, 0, height - 1);
                    for (int kx = -kernelRadius; kx <= kernelRadius; kx++)
                    {
                        int   sampleX = Mathf.Clamp(x + kx, 0, width     - 1);
                        float weight  = kernel[kz     + kernelRadius, kx + kernelRadius];
                        sum += data[sampleZ, sampleX] * weight;
                    }
                }

                result[z, x] = sum / kernelSum;
            }
        }

        return result;
    }

    private static (float, float) ComputeGlobalHeightRange(MPMap map)
    {
        float min = float.MaxValue;
        float max = float.MinValue;
        for (int y = 0; y < map.Height; y++)
        {
            for (int x = 0; x < map.Width; x++)
            {
                float h          = map.GetTile(x, y).Height;
                if (h < min) min = h;
                if (h > max) max = h;
            }
        }

        return (min, max);
    }
}