using RTSEngine.Animation;
using RTSEngine.Attack;
using RTSEngine.BuildingExtension;
using RTSEngine.Demo;
using RTSEngine.Entities;
using RTSEngine.EntityComponent;
using RTSEngine.Health;
using RTSEngine.ResourceExtension;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace RTSEngine.EditorOnly
{
    [System.Serializable]
    public struct TransformList
    {
        public List<Transform> transforms;
    }

    [System.Serializable]
    public struct ProgressObject
    {
        public string comp;
        public Transform obj;
    }

    // Demo

    [System.Serializable]
    public struct CropState
    {
        public List<Transform> show;
        public List<Transform> hide;
    }

    [System.Serializable]
    public class EntityFields
    {
        public string code;
        public GameObject entityModel;

        public List<Transform> destroyStateShow;
        public List<Transform> destroyStateHide;

        public List<TransformList> healthStatesShow;
        public List<TransformList> healthStatesHide;
    }

    [System.Serializable]
    public class ResourceFields : EntityFields
    {
        public List<Transform> workerPositions;

        public List<Transform> collectedStateShow;
        public List<Transform> collectedStateHide;

        // Demo
        public Animator chestAnimator;
    }

    [System.Serializable]
    public class FactionEntityFields : EntityFields
    {
        public List<Renderer> coloredRenderers;

        public Transform attackTargetPosition;
        public Transform attackTargetPositionExternal;

        public Transform dropOffPosition;

        public List<ProgressObject> progressObjects;

        public List<Transform> toggableObjects;
        public List<Transform> attackLaunchPositions;
        public List<Transform> delayParentObjects;

        public List<Transform> carrierAdd;
        public List<Transform> carrierSlots;
        public List<Transform> carrierEject;

        public Transform creatorSpawn;
        public Transform rallypoint;

        // Fog of War
        public List<Transform> sameVisibility;
    }

    [System.Serializable]
    public class BuildingFields : FactionEntityFields
    {
        public List<Transform> workerPositions;

        public List<TransformList> constructionShow;
        public List<TransformList> constructionHide;

        public List<Transform> constructionCompleteShow;
        public List<Transform> constructionCompleteHide;

        // Advanced Placement:
        public List<Renderer> placementRenderers;

        // Demo
        public Transform model;
        public List<CropState> cropState;
        public List<Transform> farmWorkingPositions;
        public Transform infiniteRotTarget;
        public Transform door;
    }

    [System.Serializable]
    public class UnitFields : FactionEntityFields
    {
        public Animator animator;
        public List<Transform> collectObjects;
        public List<Transform> dropOffObjects;

        // Demo
        public Transform model;
    }

    [System.Serializable]
    public class Fields
    {
        public UnitFields[] unitFields;
        public BuildingFields[] buildingFields;
        public ResourceFields[] resourceFields;

        public Fields(UnitFields[] unitFields, BuildingFields[] buildingFields, ResourceFields[] resourceFields)
        {
            this.unitFields = unitFields;
            this.buildingFields = buildingFields;
            this.resourceFields = resourceFields;
        }
    }

    [System.Serializable]
    public class EntityFieldsSet
    {
        public EntityFieldsSet(Entity entity, EntityFields fields)
        {
            if (entity.Code != fields.code)
                return;

            var entitySO = new SerializedObject(entity);
            entitySO.Update();

            entitySO.FindProperty("model").objectReferenceValue = fields.entityModel;
            entitySO.ApplyModifiedProperties();


            var healthSO = new SerializedObject(entity.GetComponent<EntityHealth>());
            healthSO.Update();

            if (true)
            {
                var destroyState = healthSO.FindProperty("destroyState");

                var showList = destroyState.FindPropertyRelative("showChildObjects");
                for (int j = 0; j < showList.arraySize; j++)
                {
                    var objProp = showList.GetArrayElementAtIndex(j);
                    objProp.objectReferenceValue = fields.destroyStateShow[j].gameObject;
                }

                var hideList = destroyState.FindPropertyRelative("hideChildObjects");
                for (int j = 0; j < hideList.arraySize; j++)
                {
                    var objProp = hideList.GetArrayElementAtIndex(j);
                    objProp.objectReferenceValue = fields.destroyStateHide[j].gameObject;
                }
            }

            if(true)
            {
                var states = healthSO.FindProperty("states");
                for (int i = 0; i < states.arraySize; i++)
                {
                    var state = states.GetArrayElementAtIndex(i);
                    var showList = state.FindPropertyRelative("showChildObjects");
                    for (int j = 0; j < showList.arraySize; j++)
                    {
                        var objProp = showList.GetArrayElementAtIndex(j);
                        objProp.objectReferenceValue = fields.healthStatesShow[i].transforms[j].gameObject;
                    }

                    var hideList = state.FindPropertyRelative("hideChildObjects");
                    for (int j = 0; j < hideList.arraySize; j++)
                    {
                        var objProp = hideList.GetArrayElementAtIndex(j);
                        objProp.objectReferenceValue = fields.healthStatesHide[i].transforms[j].gameObject;
                    }
                }
            }

            healthSO.ApplyModifiedProperties();
        }
    }

    [System.Serializable]
    public class ResourceFieldsSet : EntityFieldsSet
    {
        public ResourceFieldsSet(Resource resource, ResourceFields fields) : base(resource, fields)
        {
            var workerSO = new SerializedObject(resource.GetComponentInChildren<ResourceWorkerManager>());
            workerSO.Update();
            var workerProp = workerSO.FindProperty("workerPositions");
            for (int i = 0; i < workerProp.arraySize; i++)
            {
                var objProp = workerProp.GetArrayElementAtIndex(i);
                objProp.objectReferenceValue = fields.workerPositions[i];
            }
            workerSO.ApplyModifiedProperties();

            var healthSO = new SerializedObject(resource.GetComponent<ResourceHealth>());
            healthSO.Update();

            if (true)
            {
                var collectedProp = healthSO.FindProperty("collectedState");

                var showList = collectedProp.FindPropertyRelative("showChildObjects");
                for (int j = 0; j < showList.arraySize; j++)
                {
                    var objProp = showList.GetArrayElementAtIndex(j);
                    objProp.objectReferenceValue = fields.collectedStateShow[j].gameObject;
                }

                var hideList = collectedProp.FindPropertyRelative("hideChildObjects");
                for (int j = 0; j < hideList.arraySize; j++)
                {
                    var objProp = hideList.GetArrayElementAtIndex(j);
                    objProp.objectReferenceValue = fields.collectedStateHide[j].gameObject;
                }
            }
            healthSO.ApplyModifiedProperties();

            // Demo
            if(resource.GetComponentInChildren<OpenTreasureHandler>())
            {
                var openTreasureSO = new SerializedObject(resource.GetComponentInChildren<OpenTreasureHandler>());
                openTreasureSO.Update();
                openTreasureSO.FindProperty("animator").objectReferenceValue = fields.chestAnimator;
                openTreasureSO.ApplyModifiedProperties();
            }
        }
    }

    [System.Serializable]
    public class FactionEntityFieldsSet : EntityFieldsSet
    {
        public FactionEntityFieldsSet(FactionEntity factionEntity, FactionEntityFields fields) : base(factionEntity, fields)
        {
            if (true)
            {
                var entitySO = new SerializedObject(factionEntity);
                entitySO.Update();
                var coloredRenderersProp = entitySO.FindProperty("coloredRenderers");
                for (int i = 0; i < coloredRenderersProp.arraySize; i++)
                {
                    var objProp = coloredRenderersProp.GetArrayElementAtIndex(i).FindPropertyRelative("renderer");
                    objProp.objectReferenceValue = fields.coloredRenderers[i] as Renderer;
                }
                entitySO.ApplyModifiedProperties();
            }

            var healthSO = new SerializedObject(factionEntity.GetComponent<FactionEntityHealth>());
            healthSO.Update();
            healthSO.FindProperty("attackTargetPosition").objectReferenceValue = fields.attackTargetPosition;
            healthSO.ApplyModifiedProperties();
            if (factionEntity.GetComponentInChildren<FactionEntityAttackTargetGetter>())
            {
                var attackTargetPosSO = new SerializedObject(factionEntity.GetComponentInChildren<FactionEntityAttackTargetGetter>());
                attackTargetPosSO.Update();
                attackTargetPosSO.FindProperty("attackTargetPosition").objectReferenceValue = fields.attackTargetPositionExternal;
                attackTargetPosSO.ApplyModifiedProperties();

            }

            if(factionEntity.GetComponentInChildren<DropOffTarget>())
            {
                var dropOffTargetSO = new SerializedObject(factionEntity.GetComponentInChildren<DropOffTarget>());
                dropOffTargetSO.Update();
                dropOffTargetSO.FindProperty("dropOffPosition").objectReferenceValue = fields.dropOffPosition;
                dropOffTargetSO.ApplyModifiedProperties();
            }

            if (true)
            {
                foreach (FactionEntityAttack attackComp in factionEntity.GetComponentsInChildren<FactionEntityAttack>())
                {
                    var attackCompSO = new SerializedObject(attackComp);
                    attackCompSO.Update();
                    var toggableObejctsProp = attackCompSO.FindProperty("weapon.toggableObjects");
                    for (int i = 0; i < toggableObejctsProp.arraySize; i++)
                    {
                        var objProp = toggableObejctsProp.GetArrayElementAtIndex(i);
                        objProp.objectReferenceValue = fields.toggableObjects[i].gameObject;
                    }

                    var prop = attackCompSO.FindProperty("launcher.sources");
                    for (int i = 0; i < prop.arraySize; i++)
                    {
                        var launchPositionProp = prop.GetArrayElementAtIndex(i).FindPropertyRelative("launchPosition");
                        launchPositionProp.objectReferenceValue = fields.attackLaunchPositions[i];

                        var delayParentObjectProp = prop.GetArrayElementAtIndex(i).FindPropertyRelative("delayParentObject");
                        delayParentObjectProp.objectReferenceValue = fields.delayParentObjects[i];
                    }
                    attackCompSO.ApplyModifiedProperties();
                }
            }

            if(true)
            {
                Dictionary<string, GameObject> progressObjDic = fields.progressObjects
                    .GroupBy(elem => elem.comp)
                    .Select(elem => elem.First())
                    .ToDictionary(elem => elem.comp, elem => elem.obj.IsValid() ? elem.obj.gameObject : null);

                foreach(FactionEntityTargetProgressComponent<IEntity> comp in factionEntity.GetComponentsInChildren<FactionEntityTargetProgressComponent<IEntity>>())
                {
                    var SO = new SerializedObject(comp);
                    SO.Update();
                    SO.FindProperty("inProgressObject").objectReferenceValue = progressObjDic[comp.Code];
                    SO.ApplyModifiedProperties();
                }
                foreach(FactionEntityTargetProgressComponent<IFactionEntity> comp in factionEntity.GetComponentsInChildren<FactionEntityTargetProgressComponent<IFactionEntity>>())
                {
                    var SO = new SerializedObject(comp);
                    SO.Update();
                    SO.FindProperty("inProgressObject").objectReferenceValue = progressObjDic[comp.Code];
                    SO.ApplyModifiedProperties();
                }
                foreach(FactionEntityTargetProgressComponent<IResource> comp in factionEntity.GetComponentsInChildren<FactionEntityTargetProgressComponent<IResource>>())
                {
                    var SO = new SerializedObject(comp);
                    SO.Update();
                    SO.FindProperty("inProgressObject").objectReferenceValue = progressObjDic[comp.Code];
                    SO.ApplyModifiedProperties();
                }
                foreach(FactionEntityTargetProgressComponent<IBuilding> comp in factionEntity.GetComponentsInChildren<FactionEntityTargetProgressComponent<IBuilding>>())
                {
                    var SO = new SerializedObject(comp);
                    SO.Update();
                    SO.FindProperty("inProgressObject").objectReferenceValue = progressObjDic[comp.Code];
                    SO.ApplyModifiedProperties();
                }
                foreach(FactionEntityTargetProgressComponent<IResource> comp in factionEntity.GetComponentsInChildren<FactionEntityTargetProgressComponent<IResource>>())
                {
                    var SO = new SerializedObject(comp);
                    SO.Update();
                    SO.FindProperty("inProgressObject").objectReferenceValue = progressObjDic[comp.Code];
                    SO.ApplyModifiedProperties();
                }
            }

            if(factionEntity.GetComponentInChildren<UnitCarrier>())
            {
                var carrierSO = new SerializedObject(factionEntity.GetComponentInChildren<UnitCarrier>());
                carrierSO.Update();
                var addProp = carrierSO.FindProperty("addablePositions");
                for (int i = 0; i < addProp.arraySize; i++)
                {
                    var objProp = addProp.GetArrayElementAtIndex(i);
                    objProp.objectReferenceValue = fields.carrierAdd[i];
                }

                var slotProp = carrierSO.FindProperty("carrierPositions");
                for (int i = 0; i < slotProp.arraySize; i++)
                {
                    var objProp = slotProp.GetArrayElementAtIndex(i);
                    objProp.objectReferenceValue = fields.carrierSlots[i];
                }

                var ejectProp = carrierSO.FindProperty("ejectablePositions");
                for (int i = 0; i < ejectProp.arraySize; i++)
                {
                    var objProp = ejectProp.GetArrayElementAtIndex(i);
                    objProp.objectReferenceValue = fields.carrierEject[i];
                }
                carrierSO.ApplyModifiedProperties();
            }

            if(factionEntity.GetComponentInChildren<UnitCreator>())
            {
                var creatorSO = new SerializedObject(factionEntity.GetComponentInChildren<UnitCreator>());
                creatorSO.Update();
                creatorSO.FindProperty("spawnTransform").objectReferenceValue = fields.creatorSpawn;
                creatorSO.ApplyModifiedProperties();
            }

            if(factionEntity.GetComponentInChildren<Rallypoint>())
            {
                var rallypointSO = new SerializedObject(factionEntity.GetComponentInChildren<Rallypoint>());
                rallypointSO.Update();
                rallypointSO.FindProperty("gotoTransform").objectReferenceValue = fields.rallypoint;
                rallypointSO.ApplyModifiedProperties();
            }

            if(factionEntity.GetComponent("FogOfWarEntity"))
            {
                var SO = new SerializedObject(factionEntity.GetComponent("FogOfWarEntity"));
                SO.Update();
                var sameVisProp = SO.FindProperty("sameVisibilityObjects");
                for (int i = 0; i < sameVisProp.arraySize; i++)
                {
                    var objProp = sameVisProp.GetArrayElementAtIndex(i);
                    objProp.objectReferenceValue = fields.sameVisibility[i].gameObject;
                }
                SO.ApplyModifiedProperties();
            }
        }
    }

    [System.Serializable]
    public class UnitFieldsSet : FactionEntityFieldsSet
    {
        public UnitFieldsSet(Unit unit, UnitFields fields) : base(unit, fields)
        {
            if (true)
            {
                var animatorSO = new SerializedObject(unit.GetComponentInChildren<UnitAnimatorController>());
                animatorSO.Update();
                animatorSO.FindProperty("animator").objectReferenceValue = fields.animator;
                animatorSO.ApplyModifiedProperties();
            }

            if(unit.GetComponentInChildren<ResourceCollector>())
            {
                var collectorSO = new SerializedObject(unit.GetComponentInChildren<ResourceCollector>());
                collectorSO.Update();

                var collectProp = collectorSO.FindProperty("collectableResources");
                for (int i = 0; i < collectProp.arraySize; i++)
                {
                    var objProp = collectProp.GetArrayElementAtIndex(i).FindPropertyRelative("enableObject");
                    if(fields.collectObjects[i].IsValid())
                        objProp.objectReferenceValue = fields.collectObjects[i].gameObject;
                }
                collectorSO.ApplyModifiedProperties();
            }

            if(unit.GetComponentInChildren<DropOffSource>())
            {
                var dropOffSO = new SerializedObject(unit.GetComponentInChildren<DropOffSource>());
                dropOffSO.Update();

                var collectProp = dropOffSO.FindProperty("dropOffResources");
                for (int i = 0; i < collectProp.arraySize; i++)
                {
                    var objProp = collectProp.GetArrayElementAtIndex(i).FindPropertyRelative("enableObject");
                    if(fields.dropOffObjects[i].IsValid())
                        objProp.objectReferenceValue = fields.dropOffObjects[i].gameObject;
                }
                dropOffSO.ApplyModifiedProperties();
            }

            // Demo
            if(unit.GetComponentInChildren<VehicleUnitDestroyHeightModifier>())
            {
                var SO = new SerializedObject(unit.GetComponentInChildren<VehicleUnitDestroyHeightModifier>());
                SO.Update();

                SO.FindProperty("model").objectReferenceValue = fields.model;
                SO.ApplyModifiedProperties();
            }
        }
    }

    [System.Serializable]
    public class BuildingFieldsSet : FactionEntityFieldsSet
    {
        public BuildingFieldsSet(Building building, BuildingFields fields) : base(building, fields)
        {
            var workerSO = new SerializedObject(building.GetComponentInChildren<BuildingWorkerManager>());
            workerSO.Update();
            var workerProp = workerSO.FindProperty("workerPositions");
            for (int i = 0; i < workerProp.arraySize; i++)
            {
                var objProp = workerProp.GetArrayElementAtIndex(i);
                objProp.objectReferenceValue = fields.workerPositions[i];
            }
            workerSO.ApplyModifiedProperties();

            if(true)
            {
                var healthSO = new SerializedObject(building.GetComponent<BuildingHealth>());
                healthSO.Update();

                var states = healthSO.FindProperty("constructionStates");
                for (int i = 0; i < states.arraySize; i++)
                {
                    var state = states.GetArrayElementAtIndex(i);
                    var showList = state.FindPropertyRelative("showChildObjects");
                    for (int j = 0; j < showList.arraySize; j++)
                    {
                        var objProp = showList.GetArrayElementAtIndex(j);
                        objProp.objectReferenceValue = fields.constructionShow[i].transforms[j].gameObject;
                    }

                    var hideList = state.FindPropertyRelative("hideChildObjects");
                    for (int j = 0; j < hideList.arraySize; j++)
                    {
                        var objProp = hideList.GetArrayElementAtIndex(j);
                        objProp.objectReferenceValue = fields.constructionHide[i].transforms[j].gameObject;
                    }
                }

                if (true)
                {
                    var complete = healthSO.FindProperty("constructionCompleteState");

                    var showList = complete.FindPropertyRelative("showChildObjects");
                    for (int i = 0; i < showList.arraySize; i++)
                    {
                        var objProp = showList.GetArrayElementAtIndex(i);
                        objProp.objectReferenceValue = fields.constructionCompleteShow[i].gameObject;
                    }

                    var hideList = complete.FindPropertyRelative("hideChildObjects");
                    for (int i = 0; i < hideList.arraySize; i++)
                    {
                        var objProp = hideList.GetArrayElementAtIndex(i);
                        objProp.objectReferenceValue = fields.constructionCompleteHide[i].gameObject;
                    }
                }

                healthSO.ApplyModifiedProperties();
            }

            if(building.GetComponentInChildren<BuildingPlacer>().gameObject.GetComponent("BuildingPlacementMaterialHandler"))
            {
                var SO = new SerializedObject(building.GetComponentInChildren<BuildingPlacer>().gameObject.GetComponent("BuildingPlacementMaterialHandler"));
                SO.Update();

                var placementProp = SO.FindProperty("placementMaterials");
                for (int i = 0; i < placementProp.arraySize; i++)
                {
                    var objProp = placementProp.GetArrayElementAtIndex(i).FindPropertyRelative("renderer");
                    objProp.objectReferenceValue = fields.placementRenderers[i];
                }
                SO.ApplyModifiedProperties();
            }

            // Demo
            if(building.GetComponentInChildren<BuildingModelHeightModifier>())
            {
                var SO = new SerializedObject(building.GetComponentInChildren<BuildingModelHeightModifier>());
                SO.Update();
                SO.FindProperty("model").objectReferenceValue = fields.model;
                SO.ApplyModifiedProperties();
            }

            if(building.GetComponentInChildren<FarmStateHandler>())
            {
                var SO = new SerializedObject(building.GetComponentInChildren<FarmStateHandler>());
                SO.Update();
                var states = SO.FindProperty("farmStates");
                for (int i = 0; i < states.arraySize; i++)
                {
                    var state = states.GetArrayElementAtIndex(i).FindPropertyRelative("crops");
                    for (int n = 0; n < state.arraySize; n++)
                    {
                        var showList = state.GetArrayElementAtIndex(n).FindPropertyRelative("show");
                        for (int j = 0; j < showList.arraySize; j++)
                        {
                            var objProp = showList.GetArrayElementAtIndex(j);
                            objProp.objectReferenceValue = fields.cropState[i].show[j];
                        }

                        var hideList = state.GetArrayElementAtIndex(n).FindPropertyRelative("hide");
                        for (int j = 0; j < hideList.arraySize; j++)
                        {
                            var objProp = hideList.GetArrayElementAtIndex(j);
                            objProp.objectReferenceValue = fields.cropState[i].hide[j];
                        }
                    }
                }

                var workingPositionsProp = SO.FindProperty("workingPositions");
                for (int i = 0; i < workingPositionsProp.arraySize; i++)
                {
                    var objProp = workingPositionsProp.GetArrayElementAtIndex(i);
                    objProp.objectReferenceValue = fields.farmWorkingPositions[i];
                }

                SO.ApplyModifiedProperties();
            }

            if(building.GetComponentInChildren<InfiniteRotation>())
            {
                var SO = new SerializedObject(building.GetComponentInChildren<InfiniteRotation>());
                SO.Update();
                SO.FindProperty("target").objectReferenceValue = fields.infiniteRotTarget;
                SO.ApplyModifiedProperties();
            }

            if(building.GetComponentInChildren<BarracksDoorSystem>())
            {
                var SO = new SerializedObject(building.GetComponentInChildren<BarracksDoorSystem>());
                SO.Update();
                SO.FindProperty("door").objectReferenceValue = fields.door;
                SO.ApplyModifiedProperties();
            }
        }
    }

    [System.Serializable]
    public class FieldsSet
    {
        public UnitFieldsSet[] unitFields;
        public BuildingFieldsSet[] buildingFields;
        public ResourceFieldsSet[] resourceFields;

        public FieldsSet(UnitFieldsSet[] unitFields, BuildingFieldsSet[] buildingFields, ResourceFieldsSet[] resourceFields)
        {
            this.unitFields = unitFields;
            this.buildingFields = buildingFields;
            this.resourceFields = resourceFields;
        }
    }

    public class EntityFieldAssigner
    {
        [MenuItem("RTS Engine/Update/2023.0.0/Step 2: Assign Entity Fields", false, 1502)]
        private static void AssignFields()
        {
            Fields fields;

            string saveFolder = "RTS Engine Update Handling/2023.0.0/Json";
            string saveName = "fields";
            string saveFormat = "txt";

            try
            {
                saveFolder = "RTS Engine Update Handling/2023.0.0/Json";
                saveFolder = $"{Application.dataPath}/{saveFolder.Trim('/')}/";

                saveFormat = $".{saveFormat.Trim('.')}";

                if (!Directory.Exists(saveFolder))
                {
                    Directory.CreateDirectory(saveFolder);
                }

                string filePath = $@"{saveFolder}{saveName}{saveFormat}";
                string fieldsJSON = File.ReadAllText(filePath);
                fields = JsonUtility.FromJson<Fields>(fieldsJSON);
            }
            catch(Exception e)
            {
                Debug.LogError($"[EntityFieldsExtractor - 2023.0.0 Update] Exception raised when attempting to save extracted fields {e}");
                return;
            }

            var entities = RTSEditorHelper.GetEntities().Values.Where(e => e.IsValid()).Select(e => e.gameObject.GetComponent<Entity>());
            var units = entities.Where(e => e.IsValid() && e.IsUnit()).ToList();
            var buildings = entities.Where(e => e.IsValid() && e.IsBuilding()).ToList();
            var resources = entities.Where(e => e.IsValid() && e.IsResourceOnly()).ToList();
            var fieldsSet = new FieldsSet(
                units.Select(unit => new UnitFieldsSet(unit as Unit, fields.unitFields.First(r => r.code == unit.Code))).ToArray(),
                buildings.Select(building => new BuildingFieldsSet(building as Building, fields.buildingFields.First(r => r.code == building.Code))).ToArray(),
                resources.Select(resource => new ResourceFieldsSet(resource as Resource, fields.resourceFields.First(r => r.code == resource.Code))).ToArray()
            );

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
        }
    }
}
