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

namespace JLGames.RocketDriver.Actions.Extensions
{
    public static class ExtBounds
    {
        public struct LocalBounds
        {
            /// <summary>
            /// (-∞, +∞)
            /// </summary>
            public Point3Int Index;

            /// <summary>
            /// Min=[0,0]
            /// Max=[+∞,+∞]
            /// </summary>
            public Bounds Bounds;

            public int IndexX
            {
                get { return Index.X; }
                set { Index.X = value; }
            }

            public int IndexY
            {
                get { return Index.Y; }
                set { Index.Y = value; }
            }

            public bool Contains(Vector3 point)
            {
                var center = Bounds.center;
                var size = Bounds.size;
                var offset = new Vector3(size.x * Index.X, size.y * Index.Y, size.z * Index.Z);
                var b = new Bounds(new Vector3(center.x + offset.x, center.y + offset.y, center.z + offset.z),
                    Bounds.size);
                return b.Contains(point);
            }

            public override string ToString()
            {
                return $"{{MapLoc={Index},Bound={Bounds}}}";
            }
        }

        public static Vector3[] Get8Points(this Bounds bounds)
        {
            var min = bounds.min;
            var max = bounds.max;
            var points = new[]
            {
                min, new Vector3(max.x, min.y, min.z), new Vector3(max.x, max.y, min.z),
                new Vector3(min.x, max.y, min.z),
                new Vector3(min.x, min.y, max.z), new Vector3(max.x, min.y, max.z), max,
                new Vector3(min.x, max.y, max.z)
            };
            return points;
        }

        public static bool Contains(this Bounds bound, Vector2 point)
        {
            return bound.Contains(point.x, point.y);
        }

        public static bool Contains(this Bounds bound, Vector3 point)
        {
            return bound.Contains(point.x, point.y, point.z);
        }

        public static bool Contains(this Bounds bound, float x, float y)
        {
            return bound.ContainsX(x) && bound.ContainsY(y);
        }

        public static bool Contains(this Bounds bound, float x, float y, float z)
        {
            return bound.ContainsX(x) && bound.ContainsY(y) && bound.ContainsZ(z);
        }

        public static bool ContainsX(this Bounds bound, float x)
        {
            return x >= bound.min.x && x < bound.max.x;
        }

        public static bool ContainsY(this Bounds bound, float y)
        {
            return y >= bound.min.y && y < bound.max.y;
        }

        public static bool ContainsZ(this Bounds bound, float z)
        {
            return z >= bound.min.z && z < bound.max.z;
        }

        public static float GetArea(this Bounds bound)
        {
            var size = bound.size;
            return size.x * size.y * size.z;
        }

        public static Bounds? FixBound(this Bounds bounds, float cutMinX, float cutMinY, float cutMaxX,
            float cutMaxY)
        {
            return bounds.FixBound(cutMinX, cutMinY, bounds.min.z, cutMaxX, cutMaxY, bounds.max.z);
        }

        public static Bounds? FixBound(this Bounds bounds, float cutMinX, float cutMinY, float cutMinZ, float cutMaxX,
            float cutMaxY, float cutMaxZ)
        {
            if (cutMinX > cutMaxX || cutMinY > cutMaxY || cutMinZ > cutMaxZ)
            {
                return null;
            }

            var min = bounds.min;
            var max = bounds.max;

            if (cutMinX >= max.x || cutMinY >= max.y || cutMinZ >= max.z || cutMaxX <= min.x || cutMaxY <= min.y ||
                cutMaxZ <= min.z)
            {
                return null;
            }

            var newMinX = Math.Max(min.x, cutMinX);
            var newMinY = Math.Max(min.y, cutMinY);
            var newMinZ = Math.Max(min.z, cutMinZ);
            var newMaxX = Math.Min(max.x, cutMaxX);
            var newMaxY = Math.Min(max.y, cutMaxY);
            var newMaxZ = Math.Min(max.z, cutMaxZ);

            if (newMinX > newMaxX || newMinY > newMaxY || newMinZ > newMaxZ)
            {
                return null;
            }

            var center = new Vector3
                {x = (newMinX + newMaxX) * 0.5f, y = (newMinY + newMaxY) * 0.5f, z = (newMinZ + newMaxZ) * 0.5f};
            var size = new Vector3
                {x = (newMaxX - newMinX), y = (newMaxY - newMinY), z = (newMaxZ - newMinZ)};

            return new Bounds(center, size);
        }

        public static Bounds? FixBound(this Bounds bounds, Bounds cutBounds)
        {
            var min = cutBounds.min;
            var max = cutBounds.max;
            return bounds.FixBound(min.x, min.y, min.z, max.x, max.y, max.z);
        }

        public static LocalBounds[][] SliceBoundByCycleZero(this Bounds bounds, ulong sizeX, ulong sizeY)
        {
            var min = bounds.min;
            min.z = 0;
            var max = bounds.max;
            max.z = 0.9f;
            bounds.min = min;
            bounds.max = max;
            var rs3 = bounds.SliceBoundByCycleZero(sizeX, sizeY, 1);
            return rs3?[0];
        }

        public static LocalBounds[][][] SliceBoundByCycleZero(this Bounds bounds, ulong sizeX, ulong sizeY,
            ulong sizeZ)
        {
            var min = bounds.min;
            var max = bounds.max;
            var xLine = new Line1 {Start = min.x, End = max.x};
            var yLine = new Line1 {Start = min.y, End = max.y};
            var zLine = new Line1 {Start = min.z, End = max.z};
            var xLines = xLine.SliceAtZero(sizeX);
            var yLines = yLine.SliceAtZero(sizeY);
            var zLines = zLine.SliceAtZero(sizeZ);
            if (xLines == null || yLines == null || zLines == null || xLines.Length == 0 || yLines.Length == 0 ||
                zLines.Length == 0)
            {
                return null;
            }

//            DebugUtil.Log("看看：", bounds, sizeX, sizeY, sizeZ);
//            DebugUtil.Log("看看X：", string.Join(",", xLines));
//            DebugUtil.Log("看看Y：", string.Join(",", yLines));
//            DebugUtil.Log("看看Z：", string.Join(",", zLines));

            var rs = new LocalBounds[zLines.Length][][];
            for (var zIndex = zLines.Length - 1; zIndex >= 0; zIndex--)
            {
                rs[zIndex] = new LocalBounds[yLines.Length][];
                var startZ = GlobalIndexToLocal(zLines[zIndex].Start, sizeZ);
                var endZ = GlobalIndexToLocal(zLines[zIndex].End, sizeZ);
                var endZRemainder = endZ.Remainder.Equals(0) ? sizeZ : endZ.Remainder;
                var cZ = (float) (startZ.Remainder + endZRemainder) * 0.5f;
                var sZ = (float) (endZRemainder - startZ.Remainder);
                for (var yIndex = yLines.Length - 1; yIndex >= 0; yIndex--)
                {
                    rs[zIndex][yIndex] = new LocalBounds[xLines.Length];
                    var startY = GlobalIndexToLocal(yLines[yIndex].Start, sizeY);
                    var endY = GlobalIndexToLocal(yLines[yIndex].End, sizeY);
                    var endYRemainder = endY.Remainder.Equals(0) ? sizeY : endY.Remainder;
                    var cY = (float) (startY.Remainder + endYRemainder) * 0.5f;
                    var sY = (float) (endYRemainder - startY.Remainder);
                    for (var xIndex = xLines.Length - 1; xIndex >= 0; xIndex--)
                    {
                        var startX = GlobalIndexToLocal(xLines[xIndex].Start, sizeX);
                        var endX = GlobalIndexToLocal(xLines[xIndex].End, sizeX);
                        var endXRemainder = endX.Remainder.Equals(0) ? sizeX : endX.Remainder;
                        var cX = (float) (startX.Remainder + endXRemainder) * 0.5f;
                        var sX = (float) (endXRemainder - startX.Remainder);

                        var index = new Point3Int((int) startX.AreaIndex, (int) startY.AreaIndex,
                            (int) startZ.AreaIndex);
                        var b = new Bounds(new Vector3(cX, cY, cZ), new Vector3(sX, sY, sZ));

//                        DebugUtil.Log("子块:", index, $"(XMin={b.min.x},XMax={b.max.x},YMin={b.min.y},YMax={b.max.y})");

                        var lb = new LocalBounds {Index = index, Bounds = b};
                        rs[zIndex][yIndex][xIndex] = lb;
                    }
                }
            }

            return rs;
        }

        private static Point1.AreaPoint1 GlobalIndexToLocal(double global, ulong size)
        {
            return Point1.ToAreaPoint1(global, size);
        }

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

        public static Bounds NewCenterBounds(float xCenter, float yCenter, float zCenter, float xSize, float ySize,
            float zSize)
        {
            var center = new Vector3(xCenter, yCenter, zCenter);
            var size = new Vector3(xSize, ySize, zSize);
            return new Bounds(center, size);
        }

        public static Bounds NewCenterBounds(float xCenter, float yCenter, float xSize, float ySize)
        {
            return NewCenterBounds(xCenter, yCenter, 0, xSize, ySize, 1);
        }
    }
}