﻿using UnityEngine;
using UnityEngine.Animations;
using UnityEditor;
using UnityEditor.Animations;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using System.IO;


namespace CRPen2
{
    public partial class CRPen2Editor : Editor
    {
        private void SetPrefab()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            // Make Particle Pen.
            GameObject penPrefab = script.useGrip ?
                AssetDatabase.LoadAssetAtPath<GameObject>(_rootDir + "/Common/Prefab/ParticlePen_UseGrip.prefab") :
                AssetDatabase.LoadAssetAtPath<GameObject>(_rootDir + "/Common/Prefab/ParticlePen.prefab");

            penPrefab = GameObject.Instantiate(penPrefab, Vector3.zero, Quaternion.Euler(Vector3.zero));
            penPrefab.name = "ParticlePen";
            penPrefab.transform.parent = script.avatar.transform;
            penPrefab.transform.position = Vector3.zero;
            penPrefab.transform.rotation = Quaternion.Euler(Vector3.zero);

            // Pen Position.
            Transform penPosition = penPrefab.transform.Find("PenPosition");
            penPosition.position = script.inkPosition.position;
            penPosition.rotation = script.inkPosition.rotation;

            ParentConstraint penConstraint = penPosition.GetComponent<ParentConstraint>();
            penConstraint.AddSource(new ConstraintSource()
            {
                sourceTransform = script.inkPosition,
                weight = 1
            });

            // Ink Psotion.
            Transform ink = penPrefab.transform.Find("PenPosition/Ink");
            ink.position = _inkTarget.position;

            if (script.useGrip)
            {
                // World Position.
                Transform worldPosition = penPrefab.transform.Find("WorldPosition");
                PositionConstraint worldPositionConstraint = worldPosition.GetComponent<PositionConstraint>();
                worldPositionConstraint.AddSource(new ConstraintSource()
                {
                    sourceTransform = penPrefab.transform,
                    weight = -1
                });

                RotationConstraint worldRotationConstraint = worldPosition.GetComponent<RotationConstraint>();
                worldRotationConstraint.AddSource(new ConstraintSource()
                {
                    sourceTransform = penPrefab.transform,
                    weight = -0.5f
                });

                // Canvas Position.
                Transform canvasPosition = penPrefab.transform.Find("WorldPosition/CanversPosition");
                canvasPosition.position = script.canvasPosition.position;
                canvasPosition.rotation = script.canvasPosition.rotation;

                ParentConstraint canvasConstraint = canvasPosition.GetComponent<ParentConstraint>();
                canvasConstraint.AddSource(new ConstraintSource()
                {
                    sourceTransform = script.canvasPosition,
                    weight = 1
                });
            }

            // Set Velue.
            ParticleSystem normal = penPosition.Find("Ink/InkNormal").GetComponent<ParticleSystem>();
            ParticleSystem grip = penPosition.Find("Ink/InkGrip").GetComponent<ParticleSystem>();

            // Life Time.
            ParticleSystem.MainModule mainModule;
            mainModule = normal.main;
            mainModule.startLifetime = script.lifetime;

            mainModule = grip.main;
            mainModule.startLifetime = script.lifetime;

            // Size.
            normal.transform.localScale = new Vector3(script.size, script.size, script.size);
            grip.transform.localScale = new Vector3(script.size, script.size, script.size);
        }

        private void ApplyDescripter()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            // CustomLayer ON.
            script.avatar.customizeAnimationLayers = true;
            script.avatar.customExpressions = true;
            script.avatar.baseAnimationLayers[2].isDefault = false;
            script.avatar.baseAnimationLayers[4].isDefault = false;

            // Menu ON.
            script.avatar.customExpressions = true;
        }

        private void MakeAnimator()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            // Set CustomLayer.
            VRCAvatarDescriptor.CustomAnimLayer customAnimLayer;

            ///////////////////// Gesture ///////////////////// 
            customAnimLayer = script.avatar.baseAnimationLayers[2];

            AnimatorController newGestureController = null;
            if (script.applyDirectly)
            {
                if (customAnimLayer.animatorController)
                {
                    // Bakup.
                    MakeBackup(customAnimLayer.animatorController, "Pen_Bak");

                    // Select Gesture.
                    newGestureController = (AnimatorController)customAnimLayer.animatorController;
                }
            }
            else
            {
                // Copy Gesture.
                newGestureController = (AnimatorController)CopyAsset(customAnimLayer.animatorController, _dataPath + "/Gesture.controller");
            }

            // Gesture Null. Copy Default.
            if (!newGestureController)
                newGestureController = (AnimatorController)CopyAsset(_rootDir + "/Common/Animator/DefaultAnimator.controller", _dataPath + "/Gesture.controller");

            // Copy penGesture.
            string handGestureLayer = script.useHand == CRPen2Setting.Hand.Right ? "Pen_R" : "Pen_L";
            AnimatorController penGesture = AssetDatabase.LoadAssetAtPath<AnimatorController>(_rootDir + "/Common/Animator/PenGesture.controller");
            AnimatorControllerLayer penGestrueLayer = CR_AnimatorControllerUtility.CopyLayerWithData(newGestureController, penGesture, handGestureLayer, true);

            // Copy Parameter.
            CR_AnimatorControllerUtility.CopyParameter(newGestureController, penGesture, "PenUse");

            // Apply AnimationClip.
            if (script.penGestureClip)
            {
                CR_AnimatorControllerUtility.FindState(penGestrueLayer.stateMachine, "Pen").motion = script.penGestureClip;
            }

            ///////////////////// FX /////////////////////
            customAnimLayer = script.avatar.baseAnimationLayers[4];

            AnimatorController newFxController = null;
            if (script.applyDirectly)
            {
                if (customAnimLayer.animatorController)
                {
                    // Bakup.
                    MakeBackup(customAnimLayer.animatorController, "Pen_Bak");
                 
                    // Select FX.
                    newFxController = (AnimatorController)customAnimLayer.animatorController;
                }
            }
            else
            {
                // Copy FX.
                newFxController = (AnimatorController)CopyAsset(customAnimLayer.animatorController, _dataPath + "/FX.controller");
            }

            // FX Null. Copy Default.
            if (!newFxController)
                newFxController = AnimatorController.CreateAnimatorControllerAtPath(_dataPath + "/FX.controller");

            // Set PenMain Path.
            string penController = script.useGrip == true ? _rootDir + "/Common/Animator/PenFX_Grip.controller" : _rootDir + "/Common/Animator/PenFX.controller";
            string handFXLayer = script.useHand == CRPen2Setting.Hand.Right ? "Pen_R" : "Pen_L";

            // Copy PenFX.
            AnimatorController penFX = AssetDatabase.LoadAssetAtPath<AnimatorController>(penController);
            AnimatorControllerLayer penMainLayer = CR_AnimatorControllerUtility.CopyLayerWithData(newFxController, penFX, handFXLayer, true);
            CR_AnimatorControllerUtility.CopyLayerWithData(newFxController, penFX, "PenErase", true);
            if (script.useGrip) CR_AnimatorControllerUtility.CopyLayerWithData(newFxController, penFX, "PenGrip", true);
            CR_AnimatorControllerUtility.CopyParameters(newFxController, penFX);

            // Set Draw Gesture.
            AnimatorStateTransition transition;
            transition = CR_AnimatorControllerUtility.FindTrsition(penMainLayer.stateMachine, "PenGestureON");
            AnimatorCondition condition = transition.conditions[0];
            transition.AddCondition(condition.mode, (float)script.drawGesture, condition.parameter);
            transition.RemoveCondition(condition);

            transition = CR_AnimatorControllerUtility.FindTrsition(penMainLayer.stateMachine, "PenGestureOFF");
            condition = transition.conditions[0];
            transition.AddCondition(condition.mode, (float)script.drawGesture, condition.parameter);
            transition.RemoveCondition(condition);

            // Set Draw Gesture Grip.
            if (script.useGrip)
            {
                transition = CR_AnimatorControllerUtility.FindTrsition(penMainLayer.stateMachine, "PenGestureONGrip");
                condition = transition.conditions[0];
                transition.AddCondition(condition.mode, (float)script.drawGesture, condition.parameter);
                transition.RemoveCondition(condition);

                transition = CR_AnimatorControllerUtility.FindTrsition(penMainLayer.stateMachine, "PenGestureOFFGrip");
                condition = transition.conditions[0];
                transition.AddCondition(condition.mode, (float)script.drawGesture, condition.parameter);
                transition.RemoveCondition(condition);
            }

            // Copy Color.
            if (script.useColor)
            {
                AnimatorController penFX_Color = AssetDatabase.LoadAssetAtPath<AnimatorController>(_rootDir + "/Common/Animator/PenFX_Color.controller");
                CR_AnimatorControllerUtility.MergeController(newFxController, penFX_Color, true);

                AnimationClip clip = CreateColorAnimation(_dataPath + "/Color.anim");
                AnimatorControllerLayer layer = CR_AnimatorControllerUtility.FindLayer(newFxController, "PenColor");
                for (int i = 0; i < script.colorList.Length; i++)
                {
                    layer.stateMachine.states[i].state.motion = clip;
                }
            }

            // Copy Size.
            if (script.useSize)
            {
                string handSizeLayer = script.useHand == CRPen2Setting.Hand.Right ? "PenSize_R" : "PenSize_L";
                AnimatorController penFX_Size = AssetDatabase.LoadAssetAtPath<AnimatorController>(_rootDir + "/Common/Animator/PenFX_Size.controller");
                CR_AnimatorControllerUtility.CopyLayerWithData(newFxController, penFX_Size, handSizeLayer, true);
                CR_AnimatorControllerUtility.CopyParameters(newFxController, penFX_Size);
            }

            // Copy Emission.
            if (script.useEmission)
            {
                AnimatorController penFX_Emission = AssetDatabase.LoadAssetAtPath<AnimatorController>(_rootDir + "/Common/Animator/PenFX_Emission.controller");
                CR_AnimatorControllerUtility.CopyLayerWithData(newFxController, penFX_Emission, "PenEmission", true);
                CR_AnimatorControllerUtility.CopyParameters(newFxController, penFX_Emission);
            }

            // Apply PlayerLayer.
            script.avatar.baseAnimationLayers[2].animatorController = newGestureController;
            script.avatar.baseAnimationLayers[4].animatorController = newFxController;
        }

        private void MakeMenu()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            VRCExpressionsMenu playerMenu = null;
            if (script.applyDirectly)
            {
                if (script.avatar.expressionsMenu)
                {
                    // Bakup.
                    MakeBackup(script.avatar.expressionsMenu, "Pen_Bak");

                    // Select FX.
                    playerMenu = script.avatar.expressionsMenu;
                }
            }
            else
            {
                // Copy Player Menu.
                playerMenu = (VRCExpressionsMenu)CopyAsset(script.avatar.expressionsMenu, _dataPath + "/Menu.asset");
            }

            // Menu Null. Copy Default.
            if (!playerMenu)
                playerMenu = (VRCExpressionsMenu)CopyAsset(_rootDir + "/Common/Menu/DefatultMenu.asset", _dataPath + "/Menu.asset");

            // Make Pen Menu.
            VRCExpressionsMenu newMenu = (VRCExpressionsMenu)CopyAsset(_rootDir + "/Common/Menu/DefatultMenu.asset", _dataPath + "/PenMenu.asset");

            // Applay Pen Menu.
            VRCExpressionsMenu pen = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(_rootDir + "/Common/Menu/Pen.asset");
            VRCExpressionsMenu.Control penControl = CR_VRCMenuUtility.MenuCopyControl(playerMenu, pen.controls[0]);
            penControl.subMenu = newMenu;

            // Set Pen Menu.
            VRCExpressionsMenu penMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(_rootDir + "/Common/Menu/PenMenu.asset");
            CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Enable");
            CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Erase");

            // Color.
            if (script.useColor)
            {
                CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Color");
                VRCExpressionsMenu.Control control = CR_VRCMenuUtility.MenuFindControl(newMenu, "Color");
                control.subMenu = MakeColorManu();
            }

            // Grip.
            if (script.useGrip)
                CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Grip");

            // Size.
            if (script.useSize)
                CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Size");

            // Emission.
            if (script.useEmission)
                CR_VRCMenuUtility.MenuCopyControl(newMenu, penMenu, "Emission");

            // Applay Player Menu.
            script.avatar.expressionsMenu = playerMenu;
        }
        
        private void MakeParameter()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            VRCExpressionParameters newParam = null;
            if (script.applyDirectly)
            {
                if (script.avatar.expressionParameters)
                {
                    // Bakup.
                    MakeBackup(script.avatar.expressionParameters, "Pen_Bak");

                    // Select FX.
                    newParam = script.avatar.expressionParameters;
                }
            }
            else
            {
                // Copy Player Parameter.
                newParam = (VRCExpressionParameters)CopyAsset(script.avatar.expressionParameters, _dataPath + "/Parameters.asset");
            }

            if (!newParam)
            {
                newParam = (VRCExpressionParameters)CopyAsset(_rootDir + "/Common/Menu/DefaultParam.asset", _dataPath + "/Parameters.asset");
            }

            CR_VRCMenuUtility.AddParameter(newParam, "PenUse", VRCExpressionParameters.ValueType.Bool, 0, false);
            CR_VRCMenuUtility.AddParameter(newParam, "PenErase", VRCExpressionParameters.ValueType.Bool, 0, false);

            if (script.useColor) CR_VRCMenuUtility.AddParameter(newParam, "PenColor", VRCExpressionParameters.ValueType.Int, 0, false);
            if (script.useGrip) CR_VRCMenuUtility.AddParameter(newParam, "PenGrip", VRCExpressionParameters.ValueType.Bool, 0, false);
            if (script.useSize) CR_VRCMenuUtility.AddParameter(newParam, "PenSize", VRCExpressionParameters.ValueType.Float, 0, false);
            if (script.useEmission) CR_VRCMenuUtility.AddParameter(newParam, "PenEmission", VRCExpressionParameters.ValueType.Float, 0, false);

            // Applay Player Param.
            script.avatar.expressionParameters = newParam;
        }

        private void SetTextureColor(Texture2D texture, Color color)
        {
            if (!texture) return;

            for (int x = 0; x < texture.width; x++)
            {
                for (int y = 0; y < texture.height; y++)
                {
                    texture.SetPixel(x, y, texture.GetPixel(x, y) * color);
                }
            }
            texture.Apply();
        }

        private VRCExpressionsMenu MakeColorManu()
        {
            CRPen2Setting script = (CRPen2Setting)target;

            Texture2D colorIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(_rootDir + "/Common/Icon/Icon_ColorDummy.png");

            VRCExpressionsMenu menu = (VRCExpressionsMenu)CopyAsset(_rootDir + "/Common/Menu/DefatultMenu.asset", _dataPath + "/ColorMenu.asset");
            for (int i = 0; i < script.colorCount; i++)
            {
                Color color = script.colorList[i];

                string rgb = "R" + color.r * 255 + "G" + color.g * 255 + "B" + color.b * 255;
                string path = _dataPath + "/" + rgb + ".png";

                Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
                if (texture == null)
                {
                    texture = (Texture2D)CopyAsset(colorIcon, path);
                    SetTextureColor(texture, color);
                    System.IO.File.WriteAllBytes(path, texture.EncodeToPNG());
                    AssetDatabase.ImportAsset(path);
                }

                VRCExpressionsMenu.Control control = new VRCExpressionsMenu.Control()
                {
                    icon = texture,
                    type = VRCExpressionsMenu.Control.ControlType.Toggle,
                    parameter = new VRCExpressionsMenu.Control.Parameter() { name = "PenColor" },
                    value = i
                };
                menu.controls.Add(control);
            }

            EditorUtility.SetDirty(menu);

            return menu;
        }

        private AnimationClip CreateColorAnimation(string assetPath)
        {
            CRPen2Setting script = (CRPen2Setting)target;

            AnimationClip clip = new AnimationClip();
            AssetDatabase.CreateAsset(clip, assetPath);

            AnimationCurve curveR = new AnimationCurve();
            AnimationCurve curveG = new AnimationCurve();
            AnimationCurve curveB = new AnimationCurve();
            for (int i = 0; i < script.colorList.Length; i++)
            {
                curveR.AddKey(new Keyframe(i, script.colorList[i].r, float.PositiveInfinity, float.PositiveInfinity));
                curveG.AddKey(new Keyframe(i, script.colorList[i].g, float.PositiveInfinity, float.PositiveInfinity));
                curveB.AddKey(new Keyframe(i, script.colorList[i].b, float.PositiveInfinity, float.PositiveInfinity));
            }
            curveR.AddKey(new Keyframe(10, 1, float.PositiveInfinity, float.PositiveInfinity));
            curveG.AddKey(new Keyframe(10, 1, float.PositiveInfinity, float.PositiveInfinity));
            curveB.AddKey(new Keyframe(10, 1, float.PositiveInfinity, float.PositiveInfinity));

            EditorCurveBinding binding;
            string path;

            path = "ParticlePen/PenPosition/Ink/InkNormal";
            binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.r");
            AnimationUtility.SetEditorCurve(clip, binding, curveR);
            binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.g");
            AnimationUtility.SetEditorCurve(clip, binding, curveG);
            binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.b");
            AnimationUtility.SetEditorCurve(clip, binding, curveB);

            if (script.useGrip)
            {
                path = "ParticlePen/PenPosition/Ink/InkGrip";
                binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.r");
                AnimationUtility.SetEditorCurve(clip, binding, curveR);
                binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.g");
                AnimationUtility.SetEditorCurve(clip, binding, curveG);
                binding = EditorCurveBinding.FloatCurve(path, typeof(ParticleSystem), "InitialModule.startColor.maxColor.b");
                AnimationUtility.SetEditorCurve(clip, binding, curveB);
            }

            return clip;
        }
    }
}