﻿using System;
using System.Collections.Generic;
using System.Linq;
using PleasureArcade.ThiccWater.Editor;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.UIElements;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;

public class ThiccWaterInstaller : EditorWindow
{
    private string generatedAssetsFolder = "Assets/PleasureArcade/ThiccWater/GeneratedAssets"; 
    private ThiccWaterResources resources;
    private VRCAvatarDescriptor avatar;
    private bool[] selectedEmitters;
    private Transform parentBone;
    private bool scaleToggle;
    private VRCExpressionsMenu parentMenu;
    private ObjectField _menuSelectField;
    
    [MenuItem("PleasureArcade/ThiccWater Installer")]
    public static void OpenInstaller()
    {
        // This method is called when the user selects the menu item in the Editor
        EditorWindow wnd = GetWindow<ThiccWaterInstaller>();
        wnd.titleContent = new GUIContent("ThiccWater Installer");

        // Limit size of the window
        wnd.minSize = new Vector2(360, 583);
        wnd.maxSize = new Vector2(360, 583);
    }

    private void CreateGUI()
    {
        // Storing emitter data in a scriptable object so I don't have to do a ton of loading in code
        resources = Resources.Load<ThiccWaterResources>("ThiccWaterResources");
        
        // Add a container later? styling?
        var root = rootVisualElement;

        // Avatar selector
        var avatarSelectorField = new ObjectField("Avatar")
        {
            objectType = typeof(VRCAvatarDescriptor),
            allowSceneObjects = true,
            value = avatar
        };
        avatarSelectorField.RegisterValueChangedCallback(e =>
        {
            avatar = (VRCAvatarDescriptor)e.newValue;
            UpdateMenuSelector();
        });
        root.Add(avatarSelectorField);

        root.Add(new Label(" "));
        
        // Select emitters
        root.Add(new Label("Emitters to install"));
        selectedEmitters = new bool[resources.emitterNames.Length];
        selectedEmitters.Initialize();
        var emitterToggles = new Toggle[resources.emitterNames.Length];
        for (var i = 0; i < resources.emitterNames.Length; i++)
        {
            emitterToggles[i] = new Toggle(resources.emitterNames[i])
            {
                value = selectedEmitters[i]
            };
            emitterToggles[i].RegisterValueChangedCallback(GenerateBoolArrayEventCallback(i));
            root.Add(emitterToggles[i]);
        }
        
        root.Add(new Label(" "));
        
        // Parent bone selector
        var boneField = new ObjectField("Parent Bone")
        {
            objectType = typeof(Transform),
            allowSceneObjects = true,
            value = parentBone
        };
        boneField.RegisterValueChangedCallback(e =>
        {
            parentBone = (Transform)e.newValue;
        });
        root.Add(boneField);
        
        root.Add(new Label(" "));

        // Extra options
        var scalingToggle = new Toggle("Enable Scaling")
        {
            value = scaleToggle,
            tooltip = "Enable this if you want the particles to scale up/down with the parent bone"
        };
        scalingToggle.RegisterValueChangedCallback(e => scaleToggle = e.newValue);
        root.Add(scalingToggle);

        _menuSelectField = new ObjectField("Add to Menu")
        {
            objectType = typeof(VRCExpressionsMenu),
            value = parentMenu
        };
        _menuSelectField.RegisterValueChangedCallback(e =>
        {
            parentMenu = (VRCExpressionsMenu)e.newValue;
        });
        root.Add(_menuSelectField);
        
        root.Add(new Label(" "));
        
        // Finish
        root.Add(new Button(Install)
        {
            text = "Install Emitters"
        });
        
        // Adjustments
        root.Add(new Label(" "));
        root.Add(new Button(RemoveBottles)
        {
            text = "Remove Bottles"
        });
    }

    /**
     * Delegate generator to get around non-scoped variable in closure
     */
    public EventCallback<ChangeEvent<bool>> GenerateBoolArrayEventCallback(int i)
    {
        return delegate(ChangeEvent<bool> evt)
        {
            selectedEmitters[i] = evt.newValue;
        };
    }

    /**
     * Update the menu to be the root menu whenever an avatar is selected
     */
    public void UpdateMenuSelector()
    {
        _menuSelectField.value = avatar?.expressionsMenu;
        parentMenu = avatar?.expressionsMenu;
    }

    /**
     * Do the thing!
     */
    public void Install()
    {
        // Sanity checks
        if (!avatar)
        {
            EditorUtility.DisplayDialog("Error", "Please select an avatar to install emitters onto", "OK");
            return;
        }

        if (selectedEmitters.All(s => !s))
        {
            EditorUtility.DisplayDialog("Error", "Please select at least one emitter to install", "OK");
            return;
        }

        if (!parentBone)
        {
            EditorUtility.DisplayDialog("Error", "Please select a parent bone for emitters to attach to", "OK");
            return;
        }

        // Add container to avatar base
        var container = new GameObject("ThiccWater").transform;
        container.SetParent(avatar.transform);
        container.localPosition = Vector3.zero;
        container.localRotation = Quaternion.identity;

        // Add depth provider
        var depthProvider = Instantiate(resources.depthProvider, Vector3.up, Quaternion.identity, container);
        depthProvider.name = "Depth Provider";

        // Add parent constraint
        var parentConstraint = container.gameObject.AddComponent<ParentConstraint>();
        parentConstraint.SetSources(new List<ConstraintSource>(new []
        {
            new ConstraintSource()
            {
                sourceTransform = parentBone,
                weight = 1.0f
            }
        }));
        parentConstraint.locked = false;
        parentConstraint.SetRotationOffset(0, Vector3.zero);
        parentConstraint.SetTranslationOffset(0, Vector3.zero);
        parentConstraint.locked = true;
        parentConstraint.constraintActive = true;
        
        // Add scale constraint
        if (scaleToggle)
        {
            var scaleConstraint = container.gameObject.AddComponent<ScaleConstraint>();
            scaleConstraint.AddSource(new ConstraintSource()
            {
                sourceTransform = parentBone,
                weight = 1.0f
            });
            scaleConstraint.constraintActive = true;
        }
        
        // Find/Create FX controller
        avatar.customizeAnimationLayers = true;

        var fxAnimIndex = -1;
        for (var i = 0; i < avatar.baseAnimationLayers.Length; i++)
        {
            if (avatar.baseAnimationLayers[i].type == VRCAvatarDescriptor.AnimLayerType.FX) fxAnimIndex = i;
        }
        
        if (avatar.baseAnimationLayers[fxAnimIndex].isDefault || avatar.baseAnimationLayers[fxAnimIndex].animatorController == null)
        {
            InitGeneratedAssetFolder();
            var fxController = AnimatorController.CreateAnimatorControllerAtPath(generatedAssetsFolder + "/ThiccWaterFX.controller");
            avatar.baseAnimationLayers[fxAnimIndex].isDefault = false;
            avatar.baseAnimationLayers[fxAnimIndex].animatorController = fxController;
        }

        // Remove [existing] menu from parent menu
        if (parentMenu)
        {
            parentMenu.controls.RemoveAll(c => c.name == "Thicc Water");
        }

        // Per selected emitter...
        var expressionParams = avatar.expressionParameters.parameters.ToList();
        var fxLayers = ((AnimatorController)avatar.baseAnimationLayers[fxAnimIndex].animatorController).layers.ToList();
        var fxParams = ((AnimatorController)avatar.baseAnimationLayers[fxAnimIndex].animatorController).parameters.ToList();
        var menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
        menu.controls = new List<VRCExpressionsMenu.Control>();
        for (var i = 0; i < resources.emitterNames.Length; i++)
        {
            if (selectedEmitters[i])
            {
                // Add component[s]
                var emitter = Instantiate(resources.emitters[i], Vector3.zero, Quaternion.identity, container);
                emitter.name = resources.emitterNames[i];
                emitter.transform.localPosition = Vector3.zero;
                emitter.transform.localRotation = Quaternion.identity;
                
                // Add (non-existing) animator param
                if (expressionParams.All(param => param.name != emitter.name))
                {
                    expressionParams.Add(new VRCExpressionParameters.Parameter()
                    {
                        name = emitter.name,
                        valueType = VRCExpressionParameters.ValueType.Bool,
                        defaultValue = 0,
                        saved = false
                    });
                }

                if (fxParams.All(param => param.name != emitter.name))
                {
                    fxParams.Add(new AnimatorControllerParameter()
                    {
                        name = emitter.name,
                        type = AnimatorControllerParameterType.Bool,
                        defaultBool = false
                    });
                }
                
                // Clear/Add animator layer
                fxLayers.RemoveAll(layer => layer.name == emitter.name);
                fxLayers.Add(resources.AnimatorControllers[i].layers.First());
                
                // Add control to menu
                menu.controls.Add(resources.menu.controls.Find(c => c.name == emitter.name));
            }
        }

        // Reassign animator arrays
        avatar.expressionParameters.parameters = expressionParams.ToArray();
        ((AnimatorController)avatar.baseAnimationLayers[fxAnimIndex].animatorController).layers = fxLayers.ToArray();
        ((AnimatorController)avatar.baseAnimationLayers[fxAnimIndex].animatorController).parameters = fxParams.ToArray();
        
        // Add menu
        if (parentMenu)
        {
            InitGeneratedAssetFolder();
            AssetDatabase.CreateAsset(menu, generatedAssetsFolder + "/ThiccWaterMenu.asset");
            parentMenu.controls.Add(new VRCExpressionsMenu.Control()
            {
                icon = resources.menuIcon,
                name = "Thicc Water",
                type = VRCExpressionsMenu.Control.ControlType.SubMenu,
                subMenu = menu
            });
        }

        EditorUtility.DisplayDialog("Complete", "Installation complete", "OK");
    }

    /**
     * Remove bottle meshes from root objects
     */
    public void RemoveBottles()
    {
        // Sanity checks
        if (!avatar)
        {
            EditorUtility.DisplayDialog("Error", "Please select an avatar to install emitters onto", "OK");
            return;
        }
        
        var parent = avatar.transform.Find("ThiccWater");

        if (parent)
        {
            for (var i = 0; i < parent.childCount; i++)
            {
                var meshFilter = parent.GetChild(i).gameObject.GetComponent<MeshFilter>();
                if (meshFilter)
                {
                    DestroyImmediate(meshFilter);
                }
                
                var meshRenderer = parent.GetChild(i).gameObject.GetComponent<MeshRenderer>();
                if (meshRenderer)
                {
                    DestroyImmediate(meshRenderer);
                }
            }
        }
    }
    
    /**
     * Ensure GeneratedAssets folder exists; if not, create it
     */
    public void InitGeneratedAssetFolder()
    {
        if (AssetDatabase.IsValidFolder(generatedAssetsFolder)) return;
            
        var i = generatedAssetsFolder.LastIndexOf('/');
        AssetDatabase.CreateFolder(generatedAssetsFolder.Substring(0, i), generatedAssetsFolder.Substring(i+1));
    }
}