using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Serialization;
using VRC;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using VRC.SDKBase;
using YamlDotNet.Core.Tokens;

public class DeckImporter : EditorWindow
{
    [SerializeField] private TextAsset deckJson;
    
    [SerializeField] private VRCExpressionsMenu menuRoot;

    [SerializeField] private AnimatorController outputController;

    [MenuItem("Window/Deck Importer")]
    static void ShowWindow()
    {
        GetWindow<DeckImporter>().Show();
    }
    
    private void OnGUI()
    {
        var so = new SerializedObject(this);

        EditorGUILayout.PropertyField(so.FindProperty(nameof(deckJson)));
        EditorGUILayout.PropertyField(so.FindProperty(nameof(menuRoot)));
        EditorGUILayout.PropertyField(so.FindProperty(nameof(outputController)));

        if (so.hasModifiedProperties)
            so.ApplyModifiedProperties();
        
        if (GUILayout.Button("Generate"))
        {
            Generate();
        }
    }

    void Generate()
    {
        var json = deckJson.text;
        var deck = JsonConvert.DeserializeObject<Deck>(json);

        var controller = new AnimatorController();

        var targetPath = AssetDatabase.GetAssetPath(outputController);
        foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(targetPath))
        {
            if (AssetDatabase.IsSubAsset(obj))
            {
                Debug.Log("Removing: " + obj);
                AssetDatabase.RemoveObjectFromAsset(obj);
            }
        }

        var displayStateMachine = new AnimatorStateMachine
        {
            name = "Display"
        };

        var displayLayer = new AnimatorControllerLayer
        {
            name = "Display",
            stateMachine = displayStateMachine,
            defaultWeight = 1f
        };
        
        AssetDatabase.AddObjectToAsset(displayStateMachine, outputController);
        controller.AddLayer(displayLayer);

        var drawStateMachine = new AnimatorStateMachine
        {
            name = "Draw"
        };

        var drawLayer = new AnimatorControllerLayer
        {
            name = "Draw",
            stateMachine = drawStateMachine,
            defaultWeight = 1f
        };

        AssetDatabase.AddObjectToAsset(drawStateMachine, outputController);
        controller.AddLayer(drawLayer);

        var drawIdleState = drawStateMachine.AddState("Idle", 250 * Vector3.left);

        var delayClip = new AnimationClip();
        delayClip.name = "Delay - 0.1 seconds";
        delayClip.SetCurve("Fake Object", typeof(GameObject), "activeSelf", AnimationCurve.Constant(0f, 0.1f, 0f));
        AssetDatabase.AddObjectToAsset(delayClip, outputController);

        controller.AddParameter("Control/Selected Card", AnimatorControllerParameterType.Int);
        controller.AddParameter("Control/Draw", AnimatorControllerParameterType.Int);
        controller.AddParameter("Internal/RNG", AnimatorControllerParameterType.Int);

        int id = 0;

        Dictionary<string, List<Card>> sets = new();
        Dictionary<Card, int> cardIDMap = new();
        Dictionary<string, int> setIDMap = new();
        Dictionary<Card, AnimatorState> cardStateMap = new();
        
        foreach (var card in deck.cards)
        {
            Debug.Log(card);
            
            var cardState = displayLayer.stateMachine.AddState(card.name);

            var cardClip = new AnimationClip();
            cardClip.name = "Display - " + card.name;
            
            var cardUVXCurve = AnimationCurve.Constant(0f, 1f, card.uv.x);
            var cardUVYCurve = AnimationCurve.Constant(0f, 1f, card.uv.y);
            var cardUVZCurve = AnimationCurve.Constant(0f, 1f, card.uv.z);
            var cardUVWCurve = AnimationCurve.Constant(0f, 1f, card.uv.w);

            cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.x", cardUVXCurve);
            cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.y", cardUVYCurve);
            cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.z", cardUVZCurve);
            cardClip.SetCurve("Pivot Offset/Card", typeof(MeshRenderer), "material._MainTex_ST.w", cardUVWCurve);
            
            cardState.motion = cardClip;

            AssetDatabase.AddObjectToAsset(cardClip, outputController);

            var cardTransition = displayStateMachine.AddAnyStateTransition(cardState);
            cardTransition.AddCondition(AnimatorConditionMode.Equals, id, "Control/Selected Card");
            cardTransition.hasExitTime = false;
            cardTransition.duration = 0f;
            cardTransition.canTransitionToSelf = false;
            
            foreach (var tag in card.tags)
            {
                if (!sets.TryGetValue(tag, out var list))
                {
                    list = new();
                    sets[tag] = list;
                }

                list.Add(card);
            }

            var setCardState = drawStateMachine.AddState(card.name, 250 * Vector3.right + 50 * id * Vector3.down);
            setCardState.motion = delayClip;
                
            var returnTransition = setCardState.AddExitTransition();
            returnTransition.hasExitTime = true;
            returnTransition.exitTime = 1f;
            returnTransition.duration = 0f;
                
            var setCardDriver =
                setCardState.AddStateMachineBehaviour<VRCAvatarParameterDriver>();
            
            setCardDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter
            {
                name = "Control/Selected Card",
                type = VRC_AvatarParameterDriver.ChangeType.Set,
                value = id
            });

            setCardDriver.MarkDirty();

            cardStateMap[card] = setCardState;
            
            cardIDMap[card] = id;
            ++id;
        }

        int setID = 0;
        
        foreach (var (tag, set) in sets)
        {
            var setState = drawStateMachine.AddState(tag, 50 * setID * Vector3.down);
            setState.motion = delayClip;
            
            var setTransition = drawIdleState.AddTransition(setState);
            setTransition.hasExitTime = false;
            setTransition.duration = 0;
            setTransition.AddCondition(AnimatorConditionMode.Equals, setID + 1, "Control/Draw");
            
            var setStateDriver =
                setState.AddStateMachineBehaviour<VRCAvatarParameterDriver>();

            setStateDriver.localOnly = true;
            
            setStateDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter()
            {
                name = "Control/Draw",
                type = VRC_AvatarParameterDriver.ChangeType.Set,
                value = 0
            });
            
            setStateDriver.parameters.Add(new VRC_AvatarParameterDriver.Parameter()
            {
                name = "Internal/RNG",
                type = VRC_AvatarParameterDriver.ChangeType.Random,
                valueMin = 0,
                valueMax = set.Count - 1,
            });

            setStateDriver.MarkDirty();

            int randomID = 0;

            foreach (var card in set)
            {
                var setCardState = cardStateMap[card];

                var enterTransition = setState.AddTransition(setCardState);
                enterTransition.hasExitTime = true;
                enterTransition.exitTime = 1;
                enterTransition.duration = 0;
                enterTransition.AddCondition(AnimatorConditionMode.Equals, randomID, "Internal/RNG");

                ++randomID;
            }

            setIDMap[tag] = setID;
            ++setID;
        }
        
        string oldName = outputController.name;
        EditorUtility.CopySerialized(controller, outputController);
        outputController.name = oldName;

        AssetDatabase.SaveAssetIfDirty(outputController);

        var menuPath = AssetDatabase.GetAssetPath(menuRoot);
        foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(menuPath))
        {
            if (AssetDatabase.IsSubAsset(obj))
            {
                Debug.Log("Removing: " + obj);
                AssetDatabase.RemoveObjectFromAsset(obj);
            }
        }

        VRCExpressionsMenu menu = CreateInstance<VRCExpressionsMenu>();
        menu.Parameters = menuRoot.Parameters;

        VRCExpressionsMenu selectMenu = CreateInstance<VRCExpressionsMenu>();
        selectMenu.name = "Select Card";
        selectMenu.Parameters = menuRoot.Parameters;

        menu.controls.Add(new VRCExpressionsMenu.Control
        {
            name = "Select Card",
            type = VRCExpressionsMenu.Control.ControlType.SubMenu,
            subMenu = selectMenu
        });
        AssetDatabase.AddObjectToAsset(selectMenu, menuRoot);

        foreach (var (key, set) in sets)
        {
            List<Card> sorted = set.OrderBy(card => card.name).ToList();
            VRCExpressionsMenu selectSetMenu = CreateInstance<VRCExpressionsMenu>();
            selectSetMenu.name = "Set - " + key;
            selectSetMenu.Parameters = menuRoot.Parameters;

            selectMenu.controls.Add(new VRCExpressionsMenu.Control
            {
                name = key,
                type = VRCExpressionsMenu.Control.ControlType.SubMenu,
                subMenu = selectSetMenu
            });

            AssetDatabase.AddObjectToAsset(selectSetMenu, menuRoot);

            for (int i = 0; i < sorted.Count; i += 8)
            {
                int amount = Mathf.Min(sorted.Count, i + 8) - i;
                
                var range = sorted.GetRange(i, amount);

                VRCExpressionsMenu cardMenu = CreateInstance<VRCExpressionsMenu>();
                cardMenu.name = range[0].name + "-" + range[^1].name;
                cardMenu.Parameters = menuRoot.Parameters;

                foreach (var card in sorted.GetRange(i, amount))
                {
                    cardMenu.controls.Add(new VRCExpressionsMenu.Control
                    {
                        name = card.name,
                        type = VRCExpressionsMenu.Control.ControlType.Toggle,
                        parameter = new VRCExpressionsMenu.Control.Parameter
                        {
                            name = "Control/Selected Card"
                        },
                        value = cardIDMap[card]
                    });
                }

                selectSetMenu.controls.Add(new VRCExpressionsMenu.Control
                {
                    name = range[0].name + "-" + range[^1].name,
                    type = VRCExpressionsMenu.Control.ControlType.SubMenu,
                    subMenu = cardMenu
                });

                AssetDatabase.AddObjectToAsset(cardMenu, menuRoot);
            }
        }

        VRCExpressionsMenu drawMenu = CreateInstance<VRCExpressionsMenu>();
        drawMenu.name = "Draw Card";
        drawMenu.Parameters = menuRoot.Parameters;              

        menu.controls.Add(new VRCExpressionsMenu.Control
        {
            name = "Draw Card",
            type = VRCExpressionsMenu.Control.ControlType.SubMenu,
            subMenu = drawMenu
        });
        AssetDatabase.AddObjectToAsset(drawMenu, menuRoot);
        
        foreach (var (tag, set) in sets)
        {
            drawMenu.controls.Add(new VRCExpressionsMenu.Control
            {
                name = tag,
                type = VRCExpressionsMenu.Control.ControlType.Toggle,
                parameter = new VRCExpressionsMenu.Control.Parameter()
                {
                    name = "Control/Draw"
                },
                value = setIDMap[tag] + 1
            });
        }

        oldName = menuRoot.name;
        EditorUtility.CopySerialized(menu, menuRoot);
        menuRoot.name = oldName;

        AssetDatabase.SaveAssetIfDirty(menuRoot);

    }
}