using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
using VOID.Editor.Utils;
using VOID.Generic.World;

namespace VOID.Editor.EditorWindow.World
{
    public class ComplexAreaRotateVariantGenerator : OdinEditorWindow
    {
        [MenuItem("VOID RPG/World/Complex Area Rotate Variant Generator")]
        private static void OpenWindow() => GetWindow<ComplexAreaRotateVariantGenerator>().Show();

        // Input and Output for user
        [ShowInInspector, AssetsOnly] private List<ComplexArea> sourceComplexAreas = new();
        [ShowInInspector, ReadOnly] private List<ComplexArea> generatedComplexAreas = new();

        // Rotation variants to create
        private static readonly float[] Rotations = { 90, 180, 270 };
        
        // Temporary variables
        private ComplexArea _targetComplexArea;
        private string _sourcePrefabPath;
        private string _sourceFolder;
        private float _variantRotation;
        private string _targetPrefabPath;
        private string _targetFolder;

        public static void GenerateRotatedVariants(ComplexArea complexArea)
        {
            // Only ASSETS for this generation!
            var prefab = complexArea.gameObject.GetPrefabAsset();
            if (prefab == null)
            {
                Debug.LogError("Only prefab asset is valid to variant generation!");
                return;
            }

            // Just to be sure - save given prefab for user before generation
            PrefabUtility.SavePrefabAsset(complexArea.gameObject);

            var isOpen = HasOpenInstances<ComplexAreaRotateVariantGenerator>();
            var variantGenerator = GetWindow<ComplexAreaRotateVariantGenerator>(false, null, false);
            variantGenerator.sourceComplexAreas = new List<ComplexArea> { prefab.GetComponent<ComplexArea>() };
            variantGenerator.GenerateRotatedVariants();
            if (!isOpen) variantGenerator.Close();
        }
        
        [Button]
        private void GenerateRotatedVariants()
        {
            if (sourceComplexAreas.IsNullOrEmpty())
            {
                Debug.LogWarning("ComplexArea not selected");
                return;
            }
            
            generatedComplexAreas.Clear();
            sourceComplexAreas.ForEach(sourceComplexArea =>
            {
                var variants = Rotations.Select(rotation => GenerateRotatedVariant(sourceComplexArea, rotation)).ToList();
                sourceComplexArea.isVariant = false;
                sourceComplexArea.variants = variants;
                sourceComplexArea.original = null;
                PrefabUtility.SavePrefabAsset(sourceComplexArea.gameObject);
                generatedComplexAreas.AddRange(variants);
            });
            sourceComplexAreas.Clear();
        }

        private ComplexArea GenerateRotatedVariant(ComplexArea sourceComplexArea, float rotation)
        {
            _sourcePrefabPath = AssetDatabase.GetAssetPath(sourceComplexArea.gameObject);
            _sourceFolder = Path.GetDirectoryName(_sourcePrefabPath)?.Replace("\\", "/") ?? "";
            _variantRotation = rotation;
            _targetFolder = $"{_sourceFolder}/{sourceComplexArea.gameObject.name}_v{_variantRotation}";

            if (Directory.Exists(_targetFolder)) Directory.Delete(_targetFolder, true);
            Directory.CreateDirectory(_targetFolder);
            
            DuplicatePrefab();
            DuplicateTerrains();
            RotatePrefab();
            UpdateTerrainsHeight();
            UpdateTerrainsAlphas();
            UpdateTerrainsDetails();
            UpdateTerrainsTrees();
            UpdateComplexArea();
            UpdateComplexAreaOriginalVariant(sourceComplexArea);

            PrefabUtility.SaveAsPrefabAsset(_targetComplexArea.gameObject, _targetPrefabPath);
            PrefabUtility.UnloadPrefabContents(_targetComplexArea.gameObject);

            return AssetDatabase.LoadAssetAtPath<GameObject>(_targetPrefabPath).GetComponent<ComplexArea>();
        }
        
        private void DuplicatePrefab()
        {
            // Duplicate prefab asset in sub directory
            _targetPrefabPath = $"{_targetFolder}/{Path.GetFileNameWithoutExtension(_sourcePrefabPath)}_v{_variantRotation}.prefab";
            AssetDatabase.CopyAsset(_sourcePrefabPath, _targetPrefabPath);
            
            // "Open" duplicated prefab for editing
            _targetComplexArea = PrefabUtility.LoadPrefabContents(_targetPrefabPath).GetComponent<ComplexArea>();
        }

        private void DuplicateTerrains()
        {
            // Duplicate TerrainData and place them in relative directiory (if not possible put them next to prefab)
            _targetComplexArea.GetComponentsInChildren<Terrain>().ForEach(terrain =>
            {
                var dataPath = AssetDatabase.GetAssetPath(terrain.terrainData);
                var dataPathNew = GetNewAssetPath(dataPath);
                Directory.CreateDirectory(_targetFolder);
                AssetDatabase.CopyAsset(dataPath, dataPathNew);
                var terrainData = AssetDatabase.LoadAssetAtPath<TerrainData>(dataPathNew);
                terrain.GetComponent<Terrain>().terrainData = terrainData;
                terrain.GetComponent<TerrainCollider>().terrainData = terrainData;
            });
                
            // Update asset database
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        private void RotatePrefab()
        {
            // Terrain rotating FIX - instead we will rotate dummies
            var terrainPositionDummyList = new Dictionary<GameObject, Terrain>();
            _targetComplexArea.gameObject.GetComponentsInChildren<Terrain>().ForEach(terrain =>
                {
                    var dummy = new GameObject();
                    var sizeOffset = Vector3.Scale(new Vector3(.5f, 0, .5f), terrain.terrainData.size);
                    dummy.transform.parent = terrain.transform.parent;
                    dummy.transform.position = terrain.transform.position + sizeOffset;
                    terrainPositionDummyList.Add(dummy, terrain);
                });
            
            // All children of prefab
            var prefabChildren = new List<Transform>();
            for (var i = 0; i < _targetComplexArea.gameObject.transform.childCount; i++)
                prefabChildren.Add(_targetComplexArea.gameObject.transform.GetChild(i));
            
            // Put empty dummy
            var dummyRotate = new GameObject();
            dummyRotate.transform.parent = _targetComplexArea.gameObject.transform;
            dummyRotate.transform.position = Vector3.zero;

            // Put all children into dummy
            prefabChildren.ForEach(child => child.parent = dummyRotate.transform);
            
            // Rotate dummy with all children
            dummyRotate.transform.Rotate(Vector3.up, _variantRotation);
            
            // Take out all children from dummy
            prefabChildren.ForEach(child => child.parent = _targetComplexArea.gameObject.transform);
            
            // Remove dummy
            DestroyImmediate(dummyRotate);
            
            // Terrain rotating FIX - Put terrains in good positions
            terrainPositionDummyList.ForEach(pair =>
            {
                var dummy = pair.Key;
                var terrain = pair.Value;
                var sizeOffset = Vector3.Scale(new Vector3(.5f, 0, .5f), terrain.terrainData.size);
                terrain.transform.position = pair.Key.transform.position - sizeOffset;
                terrain.transform.rotation = Quaternion.identity;
                DestroyImmediate(pair.Key);
            });
                
            // Update asset database
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        private void UpdateTerrainsHeight()
        {
            _targetComplexArea.gameObject.GetComponentsInChildren<Terrain>().ForEach(terrain =>
            {
                var rotationTimes = Mathf.RoundToInt(_variantRotation / 90);
                var terrainData = terrain.terrainData;
                var heightMapSize = terrainData.heightmapResolution;
                var heights = terrainData.GetHeights(0, 0, heightMapSize, heightMapSize);
                var rotatedHeights = new float[heightMapSize, heightMapSize];

                // Perform rotating N times
                for (var i = 0; i < rotationTimes; i++)
                {
                    // Rotating whole two-dimensional height array by 90 degree
                    for (var y = 0; y < heightMapSize; y++)
                    for (var x = 0; x < heightMapSize; x++)
                        rotatedHeights[y, x] = heights[x, heightMapSize - y - 1];
                    
                    // Rotated by 90 degree, prepare starting point for next rotating
                    heights = (float[,])rotatedHeights.Clone();
                }
                
                // Update terrain
                terrainData.SetHeights(0, 0, rotatedHeights);
            });
        }

        private void UpdateTerrainsAlphas()
        {
            _targetComplexArea.gameObject.GetComponentsInChildren<Terrain>().ForEach(terrain =>
            {
                var rotationTimes = Mathf.RoundToInt(_variantRotation / 90);
                var terrainData = terrain.terrainData;
                var alphaSize = terrainData.alphamapResolution;
                var alphaCount = terrainData.alphamapLayers;
                var alphas = terrainData.GetAlphamaps(0, 0, alphaSize, alphaSize);
                var rotatedAlphas = new float[alphaSize, alphaSize, alphaCount];

                // Perform rotating N times
                for (var i = 0; i < rotationTimes; i++)
                {
                    // Rotating whole two-dimensional array for each alpha map by 90 degree
                    for (var y = 0; y < alphaSize; y++)
                    for (var x = 0; x < alphaSize; x++)
                    for (var a = 0; a < alphaCount; a++)
                        rotatedAlphas[y, x, a] = alphas[x, alphaSize - y - 1, a];
                    
                    // Rotated by 90 degree, prepare starting point for next rotating
                    alphas = (float[,,])rotatedAlphas.Clone();
                }

                // Update terrain
                terrainData.SetAlphamaps(0, 0, rotatedAlphas);
            });
        }

        private void UpdateTerrainsDetails()
        {
            _targetComplexArea.gameObject.GetComponentsInChildren<Terrain>().ForEach(terrain =>
            {
                var rotationTimes = Mathf.RoundToInt(_variantRotation / 90);
                var terrainData = terrain.terrainData;
                var detailSize = terrainData.detailResolution;
                var detailLayerCount = terrainData.detailPrototypes.Length;
                var rotatedDetails = new int[detailSize, detailSize];
                
                // Rotate ALL detail layers
                for (var a = 0; a < detailLayerCount; a++)
                {
                    // Get current detail layer at index
                    var details = terrainData.GetDetailLayer(0, 0, detailSize, detailSize, a);
                    
                    // Perform rotating N times
                    for (var i = 0; i < rotationTimes; i++)
                    {
                        // Rotating whole dimensional array by 90 degree
                        for (var y = 0; y < detailSize; y++)
                        for (var x = 0; x < detailSize; x++)
                            rotatedDetails[y, x] = details[x, detailSize - y - 1];
                        
                        // Rotated by 90 degree, prepare starting point for next rotating
                        details = (int[,])rotatedDetails.Clone();
                    }
                    
                    // Update terrain
                    terrainData.SetDetailLayer(0, 0, a, rotatedDetails);
                }
            });
        }

        private void UpdateTerrainsTrees()
        {
            _targetComplexArea.gameObject.GetComponentsInChildren<Terrain>().ForEach(terrain =>
            {
                var terrainData = terrain.terrainData;
                
                // New empty GameObject for further rotating trees
                var terrainDummy = new GameObject();
                terrainDummy.transform.position = terrain.transform.position + terrainData.size/2;
                
                // Map dummy GameObjects to each tree for rotating purposes, by making it child to terrain dummy
                var treeList = terrainData.treeInstances;
                var dummyList = new GameObject[treeList.Length];
                for (var i = 0; i < treeList.Length; i++)
                {
                    dummyList[i] = new GameObject();
                    dummyList[i].transform.parent = terrainDummy.transform;
                    dummyList[i].transform.position = terrain.transform.position + Vector3.Scale(treeList[i].position, terrainData.size);
                }
                
                // Rotate dummy terrain (and all tree dummies)
                terrainDummy.transform.Rotate(Vector3.up, _variantRotation);

                // Corrent positions and rotations of all trees, by using created dummies
                for (var i = 0; i < treeList.Length; i++)
                {
                    var relativePosition = dummyList[i].transform.position - terrain.transform.position; 
                    treeList[i].position = new Vector3(relativePosition.x / terrainData.size.x, 0, relativePosition.z / terrainData.size.z);
                    treeList[i].rotation = dummyList[i].transform.rotation.eulerAngles.y;
                }

                // Update Terrain
                terrainData.SetTreeInstances(treeList, true);

                // Remove dummies
                dummyList.ForEach(DestroyImmediate);
                DestroyImmediate(terrainDummy);
            });
        }
        
        private void UpdateComplexArea()
        {
            _targetComplexArea.areas.ForEach(area =>
            {
                // Areas should not be rotated
                area.transform.rotation = Quaternion.identity;
                
                // Swap connection clockwise - N times of needed rotation
                var rotationTimes = Mathf.RoundToInt(_variantRotation / 90);
                for (var i = 0; i < rotationTimes; i++)
                {
                    var tempConnection = area.forwardConnection;
                    area.forwardConnection = area.leftConnection;
                    area.leftConnection = area.backConnection;
                    area.backConnection = area.rightConnection;
                    area.rightConnection = tempConnection;
                }
            });

            // Run all inside updates and fixes
            _targetComplexArea.UpdateAll();
            _targetComplexArea.AutoFixAll();
        }

        private void UpdateComplexAreaOriginalVariant(ComplexArea sourceComplexArea)
        {
            _targetComplexArea.isVariant = true;
            _targetComplexArea.variants.Clear();
            _targetComplexArea.original = sourceComplexArea;
        }
        
        private string GetNewAssetPath(string path)
        {
            if (path.StartsWith(_sourceFolder) == false) return $"{_targetFolder}/{Path.GetFileName(path)}";
            var relativePath = path.Substring(_sourceFolder.Length).TrimStart('/');
            return $"{_targetFolder}/{relativePath}";
        }
    }
}
