﻿using System;
using System.Collections.Generic;
using JLGames.RocketDriver.CSharp.Mathx;
using UnityEngine;

namespace JLGames.RocketDriver.Games.Tiledx
{
    public sealed class TiledMapWorld
    {
        private int m_TileDefineCount;
        private int m_XMapCount; //X方向地图数量
        private int m_YMapCount; //Y方向地图数量
        private int m_MapWidth; //单个地图Width
        private int m_MapHeight; //单个地图Height
        private Map[] m_MapData;
        private Bounds2Int m_TiledWorldBound; //循环矩形
        private Tileset[] m_Tilesets;

        private bool m_WidthCycle;
        private bool m_HeightCycle;

        /// <summary>
        /// 地块类型数量
        /// </summary>
        public int TileDefineCount => m_TileDefineCount;

        /// <summary>
        /// X方向子地图数量
        /// </summary>
        public int XMapCount => m_XMapCount;

        /// <summary>
        /// Y方向子地图数量
        /// </summary>
        public int YMapCount => m_YMapCount;

        /// <summary>
        /// 子地图X方向Width
        /// </summary>
        public int MapWidth => m_MapWidth;

        /// <summary>
        /// 子地图Y方向Height
        /// </summary>
        public int MapHeight => m_MapHeight;

        /// <summary>
        /// X方向循环数据，不循环时返回0
        /// </summary>
        public int CycleXMapCount
        {
            get
            {
                if (m_WidthCycle)
                {
                    return m_XMapCount;
                }

                return 0;
            }
        }

        /// <summary>
        /// Y方向循环数据，不循环时返回0
        /// </summary>
        public int CycleYMapCount
        {
            get
            {
                if (m_HeightCycle)
                {
                    return m_YMapCount;
                }

                return 0;
            }
        }

        /// <summary>
        /// 非循环时世界Size
        /// </summary>
        public Vector2Int UncycleWorldSize => new Vector2Int {x = m_XMapCount * m_MapWidth, y = m_YMapCount * m_MapHeight};

        /// <summary>
        /// 循环模式下Tiled地图矩形
        /// 循环时对应min=int.MinValue, max=int.MaxValue,否则为对应方向下地图个数*地图长度
        /// </summary>
        public Bounds2Int TiledWorldBound => m_TiledWorldBound;

        public TiledMapWorld(uint xMapCount, uint yMapCount, uint mapWidth, uint mapHeight, uint tileDefineCount)
        {
            m_XMapCount = (int) xMapCount;
            m_YMapCount = (int) yMapCount;
            m_MapWidth = (int) mapWidth;
            m_MapHeight = (int) mapHeight;
            m_MapData = new Map[xMapCount * yMapCount];
            m_TileDefineCount = (int) tileDefineCount;
        }

        /// <summary>
        /// 设置循环模式
        /// </summary>
        /// <param name="widthCycle">是否宽度循环(X方向循环)</param>
        /// <param name="heightCycle">是否高度循环(Y方向循环)</param>
        public void SetCycleInfo(bool widthCycle, bool heightCycle)
        {
            m_WidthCycle = widthCycle;
            m_HeightCycle = heightCycle;
            var minX = m_WidthCycle ? int.MinValue : 0;
            var minY = m_HeightCycle ? int.MinValue : 0;
            var maxX = m_WidthCycle ? int.MaxValue : m_XMapCount * m_MapWidth;
            var maxY = m_HeightCycle ? int.MaxValue : m_YMapCount * m_MapHeight;
            m_TiledWorldBound = new Bounds2Int(minX, minY, maxX, maxY);
        }

        /// <summary>
        /// 设置地块定义
        /// 这里会有一个合并行为，因为TiledMapEditor的数据可支持外部引用地块与内嵌地块
        /// </summary>
        /// <param name="tilesets"></param>
        /// <param name="linkTilesets"></param>
        public void SetTilesets(Tileset[] tilesets, Tileset[] linkTilesets)
        {
            m_Tilesets = TiledMapUtil.MergeToCompleteTileset(tilesets, linkTilesets);
        }

        /// <summary>
        /// 设置子地图数据
        /// 同时把记录的地块定义存入到地图数据中
        /// </summary>
        /// <param name="mapIndex"></param>
        /// <param name="data"></param>
        public void SetMapData(int mapIndex, Map data)
        {
            data.SetLnikTilesets(m_Tilesets);
            m_MapData[mapIndex] = data;
        }

        /// <summary>
        /// 设置子地图数据
        /// 同时把记录的地块定义存入到地图数据中
        /// </summary>
        /// <param name="mapX"></param>
        /// <param name="mapY"></param>
        /// <param name="data"></param>
        public void SetMapData(int mapX, int mapY, Map data)
        {
            data.SetLnikTilesets(m_Tilesets);
            m_MapData[InnerGetMapIndex(mapX, mapY)] = data;
        }

        /// <summary>
        /// 设置子地图数据
        /// 同时把记录的地块定义存入到地图数据中
        /// </summary>
        /// <param name="loc"></param>
        /// <param name="data"></param>
        public void SetMapData(Point2Int loc, Map data)
        {
            data.SetLnikTilesets(m_Tilesets);
            m_MapData[InnerGetMapIndex(loc.X, loc.Y)] = data;
        }

        /// <summary>
        /// 清除对就子地图数据
        /// 并清空地块定义的引用
        /// </summary>
        /// <param name="mapX"></param>
        /// <param name="mapY"></param>
        public void ClearMapData(int mapX, int mapY)
        {
            m_MapData[InnerGetMapIndex(mapX, mapY)].SetLnikTilesets(null);
            m_MapData[InnerGetMapIndex(mapX, mapY)] = null;
        }

        /// <summary>
        /// 清除对就子地图数据
        /// 并清空地块定义的引用
        /// </summary>
        /// <param name="loc"></param>
        public void ClearMapData(Point2Int loc)
        {
            m_MapData[InnerGetMapIndex(loc.X, loc.Y)].SetLnikTilesets(null);
            m_MapData[InnerGetMapIndex(loc.X, loc.Y)] = null;
        }

        /// <summary>
        /// 取地图数据
        /// </summary>
        /// <param name="mapX"></param>
        /// <param name="mapY"></param>
        /// <returns></returns>
        public Map GetMap(int mapX, int mapY)
        {
            if (m_WidthCycle)
            {
                mapX = mapX % m_XMapCount;
                if (mapX < 0)
                {
                    mapX += m_XMapCount;
                }
            }

            if (m_HeightCycle)
            {
                mapY = mapY % m_YMapCount;
                if (mapY < 0)
                {
                    mapY += m_YMapCount;
                }
            }

            if (mapX >= 0 && mapX < m_XMapCount && mapY >= 0 && mapY < m_YMapCount)
            {
                return m_MapData[InnerGetMapIndex(mapX, mapY)];
            }

            return null;
        }

        /// <summary>
        /// 取地图数据
        /// </summary>
        /// <param name="loc"></param>
        /// <returns></returns>
        public Map GetMap(Point2Int loc)
        {
            return GetMap(loc.X, loc.Y);
        }

        /// <summary>
        /// 世界环境中子地图索引坐标 映射到 原定义子地图索引坐标
        /// </summary>
        /// <param name="mapLoc"></param>
        /// <returns></returns>
        public Point2Int CycleMapLoc(Point2Int mapLoc)
        {
            var mapX = mapLoc.X;
            var mapY = mapLoc.Y;
            if (m_WidthCycle)
            {
                mapX = mapX % m_XMapCount;
                if (mapX < 0)
                {
                    mapX += m_XMapCount;
                }
            }

            if (m_HeightCycle)
            {
                mapY = mapY % m_YMapCount;
                if (mapY < 0)
                {
                    mapY += m_YMapCount;
                }
            }

            return new Point2Int {X = mapX, Y = mapY};
        }

        /// <summary>
        /// 对TiledMapEditor数据值(GID)对应的扩展属性值
        /// </summary>
        /// <param name="gId"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetTilePropertyInt(int gId, string name)
        {
            return TiledMapUtil.GetTilePropertyInt(m_Tilesets, gId, name);
        }

        /// <summary>
        /// 世界区域 到 子地图区域的 映射
        /// </summary>
        /// <param name="globalTiledX"></param>
        /// <param name="globalTiledY"></param>
        /// <param name="globalSizeX"></param>
        /// <param name="globalSizeY"></param>
        /// <returns></returns>
        public MapLocalBound[][] GetSubBounds(int globalTiledX, int globalTiledY, int globalSizeX, int globalSizeY)
        {
            var bound = m_TiledWorldBound.Crop2(globalTiledX, globalTiledY, globalTiledX + globalSizeX,
                globalTiledY + globalSizeY);
            if (bound.IsNone) return null;
            return InnerGetSliceBounds(bound.XMin, bound.YMin, bound.XSize, bound.YSize);
        }

        /// <summary>
        /// 在世界区域下，取地图单一层的数据集合
        /// 算法核心为：遍历世界坐标 -> 对子地图进行数据查询 -> 然后统计
        /// </summary>
        /// <param name="globalX"></param>
        /// <param name="globalY"></param>
        /// <param name="globalSizeX"></param>
        /// <param name="globalSizeY"></param>
        /// <param name="layerName"></param>
        /// <returns></returns>
        public TileRefSet GetTileRefSetBaseGlobal(int globalX, int globalY, int globalSizeX, int globalSizeY,
            string layerName)
        {
            var bounds = m_TiledWorldBound.Crop2(globalX, globalY, globalX + globalSizeX, globalY + globalSizeY);
//            DebugUtil.Log("Bound:", bound);
            if (bounds.IsNone) return null;
            var rs = new TileRefSet(m_TileDefineCount);
            for (var gy = bounds.YMin; gy < bounds.YMax; gy++)
            {
                for (var gx = bounds.XMin; gx < bounds.XMax; gx++)
                {
                    //取循环本地坐标
                    var local = TiledWorldUtil.GlobalPointToLocal(gx, gy, m_MapWidth, m_MapHeight, m_XMapCount,
                        m_YMapCount);
                    var layer = GetMap(local.MapLoc).FindLayer(layerName);
                    if (null == layer)
                    {
                        continue;
                    }

                    var gId = layer.GetData(local.LocalX, local.LocalY);
//                    DebugUtil.Log("AddGlobalLocation:", gId, gx, gy);
                    rs.AddGlobalLocation((int) gId, new Point2Int(gx, gy));
                }
            }

            return rs;
        }

        /// <summary>
        /// 在世界区域下，取地图单一层的数据集合
        /// 算法核心为：映射到子地图区域 -> 遍历子地图 -> 遍历坐标后转换为世界坐标 -> 统计
        /// </summary>
        /// <param name="globalX"></param>
        /// <param name="globalY"></param>
        /// <param name="globalSizeX"></param>
        /// <param name="globalSizeY"></param>
        /// <param name="layerName"></param>
        /// <returns></returns>
        public TileRefSet GetTileRefSetBaseLocal(int globalX, int globalY, int globalSizeX, int globalSizeY,
            string layerName)
        {
            var localBounds = GetSubBounds(globalX, globalY, globalSizeX, globalSizeY);
            if (null == localBounds)
            {
                return null;
            }

            var rs = new TileRefSet(m_TileDefineCount);
            //取非循环本地坐标
            var startLocal = TiledWorldUtil.GlobalPointToLocal(globalX, globalY, m_MapWidth, m_MapHeight);
            var tempLocal =
                new LocalLocation {MapX = startLocal.MapX, MapY = startLocal.MapY, LocalX = 0, LocalY = 0};
            foreach (MapLocalBound[] localWidthBound in localBounds)
            {
                foreach (var localBound in localWidthBound)
                {
                    var bound = localBound.Bounds2Int;
                    var layer = GetMap(localBound.MapLoc)?.FindLayer(layerName);
                    if (layer == null)
                    {
                        goto TabCointinue;
                    }

                    for (tempLocal.LocalY = bound.YMin; tempLocal.LocalY < bound.YMax; tempLocal.LocalY++)
                    {
                        for (tempLocal.LocalX = bound.XMin; tempLocal.LocalX < bound.XMax; tempLocal.LocalX++)
                        {
                            var tileId = (int) layer.GetData(tempLocal.LocalX, tempLocal.LocalY);
                            var global = TiledWorldUtil.LocalPointToGlobal(localBound.MapLoc, tempLocal.LocalLoc,
                                m_MapWidth, m_MapHeight);
                            rs.AddGlobalLocation(tileId, global);
                        }
                    }

                    TabCointinue:
                    tempLocal.MapX++;
                }

                tempLocal.MapY++;
            }

            return rs;
        }

        /// <summary>
        /// 取世界坐标上对应层的数据
        /// </summary>
        /// <param name="globalX"></param>
        /// <param name="globalY"></param>
        /// <param name="layerName"></param>
        /// <returns></returns>
        public int GetGlobalId(int globalX, int globalY, string layerName)
        {
            var localPoint = TiledWorldUtil.GlobalPointToLocal(globalX, globalY, m_MapWidth, m_MapHeight, CycleXMapCount,
                CycleYMapCount);
            var map = GetMap(localPoint.MapX, localPoint.MapY);
            var layer = GetMap(localPoint.MapX, localPoint.MapY)?.FindLayer(layerName);
            if (null == layer)
            {
                return -1;
            }

            return (int) layer.GetData(localPoint.LocalX, localPoint.LocalY);
        }

        /// <summary>
        /// 统计地图数据
        /// </summary>
        /// <returns></returns>
        public Dictionary<int, int> Statistics()
        {
            var rs = new Dictionary<int, int>();
            for (var mapY = YMapCount - 1; mapY >= 0; mapY--)
            {
                for (var mapX = XMapCount - 1; mapX >= 0; mapX--)
                {
                    var map = GetMap(mapX, mapY);
                    for (var layerIndex = map.layers.Length - 1; layerIndex >= 0; layerIndex--)
                    {
                        var layer = map.layers[layerIndex];
                        if (null == layer)
                        {
                            continue;
                        }

                        for (var localY = MapWidth - 1; localY >= 0; localY--)
                        {
                            for (var localX = MapWidth - 1; localX >= 0; localX--)
                            {
                                var gId = (int) layer.GetData(localX, localY);
                                if (gId == 0)
                                {
                                    continue;
                                }

                                var tId = GetTilePropertyInt(gId, "tid");
                                if (tId == 0)
                                {
                                    continue;
                                }

                                if (!rs.ContainsKey(tId))
                                {
                                    rs[tId] = 1;
                                    continue;
                                }

                                rs[tId] += 1;
                            }
                        }
                    }
                }
            }

            return rs;
        }

        //---------------------------------------

        /// <summary>
        /// 取组合地图区域
        /// 非循环
        /// </summary>
        /// <param name="globalX"></param>
        /// <param name="globalY"></param>
        /// <param name="globalSizeX"></param>
        /// <param name="globalSizeY"></param>
        /// <returns>
        ///     在非Cycle模式下返回的MapLoc信息可能是负数
        /// </returns>
        private MapLocalBound[][] InnerGetSliceBounds(int globalX, int globalY, int globalSizeX, int globalSizeY)
        {
            var xSlice = TiledWorldUtil.SliceGlobalLine(globalX, globalSizeX, m_MapWidth);
            var ySlice = TiledWorldUtil.SliceGlobalLine(globalY, globalSizeY, m_MapHeight);
            var lenX = xSlice.Length - 1;
            var lenY = ySlice.Length - 1;

            var rs = new MapLocalBound[lenY][];
            for (var yIndex = 0; yIndex < lenY; yIndex++)
            {
                rs[yIndex] = new MapLocalBound[lenX];
                var localY = TiledWorldUtil.GlobalIndexToLocal(ySlice[yIndex], m_MapHeight);

                var localSizeY = yIndex == lenY - 1
                    ? Math.Abs(ySlice[yIndex + 1] - ySlice[yIndex])
                    : m_MapHeight - localY.MapLocalIndex;

                for (var xIndex = 0; xIndex < lenX; xIndex++)
                {
                    var localX = TiledWorldUtil.GlobalIndexToLocal(xSlice[xIndex], m_MapWidth);

                    var localSizeX = xIndex == lenX - 1
                        ? Math.Abs(xSlice[xIndex + 1] - xSlice[xIndex])
                        : m_MapWidth - localX.MapLocalIndex;

                    rs[yIndex][xIndex] = new MapLocalBound
                    {
                        MapX = localX.MapIndex,
                        MapY = localY.MapIndex,
                        Bounds2Int = new Bounds2Int
                        {
                            XMin = localX.MapLocalIndex,
                            YMin = localY.MapLocalIndex,
                            XSize = localSizeX,
                            YSize = localSizeY
                        }
                    };
                }
            }

            return rs;
        }

        private int InnerGetMapIndex(int mapX, int mapY)
        {
            return TiledWorldUtil.GetGroupIndex(mapX, mapY, m_XMapCount);
        }
    }
}