﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace JLGames.RocketDriver.Actions.Extensions
{
    public static class ExtBoundsInt
    {
        private static readonly BoundsInt m_Empty = new BoundsInt();

        public static bool IsNone(this BoundsInt bounds)
        {
            return bounds.GetArea() == 0;
        }

        public static bool Contains(this BoundsInt bounds, Vector2Int point)
        {
            return bounds.Contains(point.x, point.y);
        }

        public static bool Contains(this BoundsInt bounds, Vector3Int point)
        {
            return bounds.Contains(point.x, point.y, point.z);
        }

        public static bool Contains(this BoundsInt bounds, int x, int y)
        {
            return bounds.ContainsX(x) && bounds.ContainsY(y);
        }

        public static bool Contains(this BoundsInt bounds, int x, int y, int z)
        {
            return bounds.ContainsX(x) && bounds.ContainsY(y) && bounds.ContainsZ(z);
        }

        public static bool ContainsX(this BoundsInt bounds, int x)
        {
            return x >= bounds.xMin && x < bounds.xMax;
        }

        public static bool ContainsY(this BoundsInt bounds, int y)
        {
            return y >= bounds.yMin && y < bounds.yMax;
        }

        public static bool ContainsZ(this BoundsInt bounds, int z)
        {
            return z >= bounds.zMin && z < bounds.zMax;
        }

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

        public static Vector3Int[] ToArray(this BoundsInt bounds)
        {
            var size = bounds.size;
            var rs = new Vector3Int[size.x * size.y * size.z];
            var index = 0;
            for (var z = bounds.zMin; z < bounds.zMax; z++)
            {
                for (var y = bounds.yMin; y < bounds.yMax; y++)
                {
                    for (var x = bounds.xMin; x < bounds.xMax; x++)
                    {
                        rs[index] = new Vector3Int(x, y, z);
                        index++;
                    }
                }
            }

            return rs;
        }

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

        public static BoundsInt Move(this BoundsInt bounds, Vector2Int offset)
        {
            return new BoundsInt
            {
                xMin = bounds.xMin + offset.x,
                yMin = bounds.yMin + offset.y,
                zMin = bounds.zMin,
                xMax = bounds.xMax + offset.x,
                yMax = bounds.yMax + offset.y,
                zMax = bounds.zMax
            };
        }

        public static BoundsInt Move(this BoundsInt bounds, Vector3Int offset)
        {
            return new BoundsInt
            {
                xMin = bounds.xMin + offset.x,
                yMin = bounds.yMin + offset.y,
                zMin = bounds.zMin + offset.z,
                xMax = bounds.xMax + offset.x,
                yMax = bounds.yMax + offset.y,
                zMax = bounds.zMax + offset.z
            };
        }

        public static BoundsInt Crop(this BoundsInt bounds, BoundsInt subCrop)
        {
            return bounds.Crop3(subCrop.min, subCrop.max);
        }

        public static BoundsInt Crop3(this BoundsInt bounds, Vector3Int min, Vector3Int max)
        {
            return bounds.Crop3(min.x, min.y, min.z, max.x, max.y, max.z);
        }

        public static BoundsInt Crop2(this BoundsInt bounds, Vector2Int min, Vector2Int max)
        {
            return bounds.Crop3(min.x, min.y, bounds.min.z, max.x, max.y, bounds.max.z);
        }

        public static BoundsInt Crop2(this BoundsInt bounds, int minX, int minY, int maxX, int maxY)
        {
            return bounds.Crop3(minX, minY, bounds.min.z, maxX, maxY, bounds.max.z);
        }

        public static BoundsInt Crop3(this BoundsInt bounds, int minX, int minY, int minZ, int maxX, int maxY, int maxZ)
        {
            if (minX >= maxX || minY >= maxY || minZ >= maxZ) return m_Empty;

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

            var newMinX = Math.Max(min.x, minX);
            var newMinY = Math.Max(min.y, minY);
            var newMinZ = Math.Max(min.z, minZ);
            var newMaxX = Math.Min(max.x, maxX);
            var newMaxY = Math.Min(max.y, maxY);
            var newMaxZ = Math.Min(max.z, maxZ);

            if (newMinX >= newMaxX || newMinY >= newMaxY || newMinZ >= newMaxZ) return m_Empty;

            var pos = new Vector3Int {x = newMinX, y = newMinY, z = newMinZ};
            var size = new Vector3Int
            {
                x = newMaxX - newMinX,
                y = newMaxY - newMinY,
                z = newMaxZ - newMinZ
            };

            return new BoundsInt(pos, size);
        }

        public static BoundsInt CropX(this BoundsInt bounds, int minX, int maxX)
        {
            var min = bounds.min;
            var max = bounds.max;
            minX = Mathf.Max(minX, min.x);
            maxX = Mathf.Min(maxX, max.x);
            if (minX >= maxX) return m_Empty;
            return new BoundsInt(new Vector3Int(minX, min.y, min.z), new Vector3Int(maxX - minX, max.y - min.y, max.z - min.z));
        }

        public static BoundsInt CropY(this BoundsInt bounds, int minY, int maxY)
        {
            var min = bounds.min;
            var max = bounds.max;
            minY = Mathf.Max(minY, min.y);
            maxY = Mathf.Min(maxY, max.y);
            if (minY >= maxY) return m_Empty;
            return new BoundsInt(new Vector3Int(min.x, maxY, min.z), new Vector3Int(max.x - min.x, maxY - minY, max.z - min.z));
        }

        public static BoundsInt CropZ(this BoundsInt bounds, int minZ, int maxZ)
        {
            var min = bounds.min;
            var max = bounds.max;
            minZ = Mathf.Max(minZ, min.z);
            maxZ = Mathf.Min(maxZ, max.z);
            if (minZ >= maxZ) return m_Empty;
            return new BoundsInt(new Vector3Int(min.x, min.y, minZ), new Vector3Int(max.x - min.x, max.y - min.y, maxZ - minZ));
        }

        public static BoundsInt[] SplitX(this BoundsInt bounds, int x)
        {
            if (!bounds.ContainsX(x))
            {
                return new[] {bounds};
            }

            return new[]
            {
                new BoundsInt(bounds.xMin, bounds.yMin, bounds.zMin, x, bounds.yMax, bounds.zMax),
                new BoundsInt(x, bounds.yMin, bounds.zMin, bounds.xMax, bounds.yMax, bounds.zMax)
            };
        }

        public static BoundsInt[] SplitY(this BoundsInt bounds, int y)
        {
            if (!bounds.ContainsY(y))
            {
                return new[] {bounds};
            }

            return new[]
            {
                new BoundsInt(bounds.xMin, bounds.yMin, bounds.zMin, bounds.xMax, y, bounds.zMax),
                new BoundsInt(bounds.xMin, y, bounds.zMin, bounds.xMax, bounds.yMax, bounds.zMax)
            };
        }

        public static BoundsInt[] SplitZ(this BoundsInt bounds, int z)
        {
            if (!bounds.ContainsZ(z))
            {
                return new[] {bounds};
            }

            return new[]
            {
                new BoundsInt(bounds.xMin, bounds.yMin, bounds.zMin, bounds.xMax, bounds.yMax, z),
                new BoundsInt(bounds.xMin, bounds.yMin, z, bounds.xMax, bounds.yMax, bounds.zMax)
            };
        }

        public static Vector3Int[] Add(this BoundsInt bounds, BoundsInt add, params BoundsInt[] other)
        {
            var list = new List<Vector3Int>();
            list.AddRange(add.ToArray());
            if (other.Length > 0)
            {
                foreach (var o in other)
                {
                    list.AddRange(o.ToArray());
                }
            }

            var adds = list.ToArray();
            list.Clear();
            list.AddRange(bounds.ToArray());
            foreach (var o in adds)
            {
                if (bounds.Contains(o) || list.Contains(o)) continue;
                list.Add(o);
            }

            return list.ToArray();
        }

        public static Vector3Int[] Sub(this BoundsInt bounds, BoundsInt sub, params BoundsInt[] other)
        {
            if (bounds.IsNone()) return null;
            var subList = new List<Vector3Int>();
            subList.AddRange(sub.ToArray());
            if (other.Length > 0)
            {
                foreach (var o in other)
                {
                    subList.AddRange(o.ToArray());
                }
            }

            var rsList = new List<Vector3Int>();
            var olds = bounds.ToArray();
            for (var index = 0; index < olds.Length; index++)
            {
                if (subList.Contains(olds[index])) continue;
                rsList.Add(olds[index]);
            }

            return rsList.ToArray();
        }

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

        public static BoundsInt NewCenterBoundsInt(int xCenter, int yCenter, int zCenter, int xSize, int ySize,
            int zSize)
        {
            var rs = NewCenterBoundsInt(xCenter, yCenter, xSize, ySize);
            var z1 = zCenter + zSize / 2;
            var z2 = zCenter - zSize / 2;
            rs.zMin = Math.Min(z1, z2);
            rs.zMax = Math.Max(z1, z2);
            return rs;
        }

        public static BoundsInt NewCenterBoundsInt(int xCenter, int yCenter, int xSize, int ySize)
        {
            var x1 = xCenter + xSize / 2;
            var x2 = xCenter - xSize / 2;
            var y1 = yCenter + ySize / 2;
            var y2 = yCenter - ySize / 2;

            var xMin = Math.Min(x1, x2);
            var yMin = Math.Min(y1, y2);
            var xMax = Math.Max(x1, x2);
            var yMax = Math.Max(y1, y2);
            return new BoundsInt {xMin = xMin, yMin = yMin, zMin = 0, xMax = xMax, yMax = yMax, zMax = 1};
        }
    }
}