using System;
using System.IO;
using System.Linq;
using System.Threading;
using UniVRM10.ClothWarp.Components;
using UniGLTF;
using UnityEngine;
using UnityEngine.UI;


namespace UniVRM10.Cloth.Viewer
{
    public class ClothViewerUI : MonoBehaviour
    {
        [SerializeField] Text m_version = default;

        [Header("Model")]
        [SerializeField] Toggle m_useAsync = default;
        [SerializeField] Button m_openModel = default;
        [SerializeField] Toggle m_showBoxMan = default;

        [Header("Cloth")]
        [SerializeField] Toggle m_useJob = default;
        [SerializeField] Toggle m_addClothToHips = default;
        [SerializeField] Button m_reconstructSprngBone = default;
        [SerializeField] Button m_resetSpringBone = default;
        [SerializeField] Toggle m_pauseSpringBone = default;

        [Header("Motion")]
        [SerializeField] Button m_openMotion = default;
        [SerializeField] Button m_pastePose = default;
        [SerializeField] Toggle ToggleMotionTPose = default;
        [SerializeField] Toggle ToggleMotionBVH = default;
        [SerializeField] ToggleGroup ToggleMotion = default;
        public bool IsTPose
        {
            get => ToggleMotion.ActiveToggles().FirstOrDefault() == ToggleMotionTPose;
            set
            {
                ToggleMotionTPose.isOn = value;
                ToggleMotionBVH.isOn = !value;
            }
        }

        [Header("Expression")]
        [SerializeField] Toggle m_enableLipSync = default;
        [SerializeField] Toggle m_enableAutoBlink = default;
        [SerializeField] Toggle m_enableAutoExpression = default;

        [SerializeField] GameObject m_target = default;
        [SerializeField] TextAsset m_motion;
        [SerializeField] TextFields m_texts = default;

        private void Reset()
        {
            var map = new ObjectMap(gameObject);

            m_version = map.Get<Text>("VrmVersion");

            m_useAsync = map.Get<Toggle>("UseAsync");
            m_openModel = map.Get<Button>("OpenModel");
            m_showBoxMan = map.Get<Toggle>("ShowBoxMan");

            m_useJob = map.Get<Toggle>("UseJob");
            m_addClothToHips = map.Get<Toggle>("AddClothToHips");
            m_reconstructSprngBone = map.Get<Button>("ReconstcutSpringBone");
            m_resetSpringBone = map.Get<Button>("ResetSpringBone");
            m_pauseSpringBone = map.Get<Toggle>("PauseSpringBone");

            m_openMotion = map.Get<Button>("OpenMotion");
            m_pastePose = map.Get<Button>("PastePose");
            ToggleMotionTPose = map.Get<Toggle>("TPose");
            ToggleMotionBVH = map.Get<Toggle>("BVH");
            ToggleMotion = map.Get<ToggleGroup>("_Motion_");

            m_enableLipSync = map.Get<Toggle>("EnableLipSync");
            m_enableAutoBlink = map.Get<Toggle>("EnableAutoBlink");
            m_enableAutoExpression = map.Get<Toggle>("EnableAutoExpression");
            m_texts.Reset(map);
            m_target = GameObject.FindObjectOfType<ClothTargetMover>().gameObject;
        }


        // Runtime
        GameObject m_root = default;
        IVrm10Animation m_src = default;
        public IVrm10Animation Motion
        {
            get { return m_src; }
            set
            {
                if (m_src != null)
                {
                    m_src.Dispose();
                }
                m_src = value;

                TPose = new Vrm10TPose(m_src.ControlRig.Item1.GetRawHipsPosition());
            }
        }

        public IVrm10Animation TPose;

        private CancellationTokenSource _cancellationTokenSource;

        Loaded m_loaded;
        ClothWarp.HumanoidPose m_init;

        static class ArgumentChecker
        {
            static string[] Supported = {
                ".gltf",
                ".glb",
                ".vrm",
                ".zip",
            };

            static string UnityHubPath => System.Environment.GetEnvironmentVariable("ProgramFiles") + "\\Unity\\Hub";

            public static bool IsLoadable(string path)
            {
                if (!File.Exists(path))
                {
                    // not exists
                    return false;
                }

                if (Application.isEditor)
                {
                    // skip editor argument
                    // {UnityHub_Resources}\PackageManager\ProjectTemplates\com.unity.template.3d-5.0.4.tgz
                    if (path.StartsWith(UnityHubPath))
                    {
                        return false;
                    }
                }

                var ext = Path.GetExtension(path).ToLower();
                if (!Supported.Contains(ext))
                {
                    // unknown extension
                    return false;
                }

                return true;
            }

            public static bool TryGetFirstLoadable(out string cmd)
            {
                foreach (var arg in System.Environment.GetCommandLineArgs())
                {
                    if (ArgumentChecker.IsLoadable(arg))
                    {
                        cmd = arg;
                        return true;
                    }
                }

                cmd = default;
                return false;
            }
        }

        [SerializeField]
        public int Iteration = 32;

        private void Start()
        {
            m_version.text = string.Format("VRMViewer {0}.{1}",
                    VRM10SpecVersion.MAJOR, VRM10SpecVersion.MINOR);

            m_openModel.onClick.AddListener(OnOpenModelClicked);
            m_openMotion.onClick.AddListener(OnOpenMotionClicked);
            m_pastePose.onClick.AddListener(OnPastePoseClicked);
            m_reconstructSprngBone.onClick.AddListener(OnReconstruct);
            m_resetSpringBone.onClick.AddListener(OnReset);

            // load initial bvh
            if (m_motion != null)
            {
                Motion = BvhMotion.LoadBvhFromText(m_motion.text);
            }

            if (ArgumentChecker.TryGetFirstLoadable(out var cmd))
            {
                LoadModel(cmd);
            }

            m_texts.Start();
        }

        private void OnDestroy()
        {
            _cancellationTokenSource?.Dispose();
        }

        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.Tab))
            {
                if (m_root != null) m_root.SetActive(!m_root.activeSelf);
            }

            if (Input.GetKeyDown(KeyCode.Escape))
            {
                if (_cancellationTokenSource != null)
                {
                    _cancellationTokenSource.Cancel();
                }
            }

            if (Motion != null)
            {
                Motion.ShowBoxMan(m_showBoxMan.isOn);
            }

            if (m_loaded != null)
            {
                m_loaded.EnableLipSyncValue = m_enableLipSync.isOn;
                m_loaded.EnableBlinkValue = m_enableAutoBlink.isOn;
                m_loaded.EnableAutoExpressionValue = m_enableAutoExpression.isOn;

                if (IsTPose)
                {
                    m_loaded.Runtime.VrmAnimation = TPose;
                }
                else if (Motion != null)
                {
                    // Automatically retarget in Vrm10Runtime.Process
                    m_loaded.Runtime.VrmAnimation = Motion;
                }
            }
        }

        void OnOpenModelClicked()
        {
#if UNITY_EDITOR
            var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm");
#elif UNITY_STANDALONE_WIN
            var path = ClothFileDialogForWindows.FileDialog("open VRM", "vrm");
#else
            var path = Application.dataPath + "/default.vrm";
#endif
            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            var ext = Path.GetExtension(path).ToLower();
            if (ext != ".vrm")
            {
                Debug.LogWarning($"{path} is not vrm");
                return;
            }

            LoadModel(path);
        }

        async void OnOpenMotionClicked()
        {
#if UNITY_EDITOR
            var path = UnityEditor.EditorUtility.OpenFilePanel("Open Motion", "", "bvh");
#elif UNITY_STANDALONE_WIN
            var path = ClothFileDialogForWindows.FileDialog("open Motion", "bvh", "gltf", "glb", "vrma");
#else
            var path = Application.dataPath + "/default.bvh";
#endif
            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            var ext = Path.GetExtension(path).ToLower();
            if (ext == ".bvh")
            {
                Motion = BvhMotion.LoadBvhFromPath(path);
                return;
            }

            // gltf, glb etc...
            using GltfData data = new AutoGltfFileParser(path).Parse();
            using var loader = new VrmAnimationImporter(data);
            var instance = await loader.LoadAsync(new ImmediateCaller());
            Motion = instance.GetComponent<Vrm10AnimationInstance>();
            instance.GetComponent<Animation>().Play();
        }

        async void OnPastePoseClicked()
        {
            var text = GUIUtility.systemCopyBuffer;
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            try
            {
                Motion = await Vrm10PoseLoader.LoadVrmAnimationPose(text);
            }
            catch (UniJSON.ParserException)
            {
                Debug.LogWarning("UniJSON.ParserException");
            }
            catch (UniJSON.DeserializationException)
            {
                Debug.LogWarning("UniJSON.DeserializationException");
            }
        }

        void OnReconstruct()
        {
            if (m_loaded == null)
            {
                return;
            }
            m_loaded.Runtime.SpringBone.ReconstructSpringBone();
            // var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
            // system.ResetParticle();
        }

        void OnReset()
        {
            if (m_loaded == null)
            {
                return;
            }
            m_loaded.Runtime.SpringBone.RestoreInitialTransform();
            // ResetStrandPose();
        }

        // Action<float> MakeSetPose()
        // {
        //     var start = m_init;
        //     var animator = m_loaded.Instance.GetComponent<Animator>();
        //     var end = new ClothWarp.HumanoidPose(animator);
        //     return (float t) =>
        //     {
        //         ClothWarp.HumanoidPose.ApplyLerp(animator, start, end, t);
        //     };
        // }

        // void ResetStrandPose()
        // {
        //     ResetStrandPose(MakeSetPose(), 32, 1.0f / 30, 60);
        // }

        // void ResetStrandPose(Action<float> setPose, int iteration, float timeDelta, int finish)
        // {
        //     var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();

        //     // init
        //     setPose(0);
        //     system.ResetParticle();

        //     // lerp
        //     var t = 0.0f;
        //     var d = 1.0f / iteration;
        //     for (int i = 0; i < iteration; ++i, t += d)
        //     {
        //         setPose(t);
        //         system.Process(timeDelta);
        //     }

        //     // finish
        //     setPose(1.0f);
        //     for (int i = 0; i < finish; ++i)
        //     {
        //         system.Process(timeDelta);
        //     }
        // }

        static IMaterialDescriptorGenerator GetVrmMaterialDescriptorGenerator(bool useUrp)
        {
            if (useUrp)
            {
                return new UrpVrm10MaterialDescriptorGenerator();
            }
            else
            {
                return new BuiltInVrm10MaterialDescriptorGenerator();
            }
        }

        void OnInit(Vrm10Instance vrm)
        {
            var animator = vrm.GetComponent<Animator>();

            try
            {
                if (vrm.SpringBone.Springs.Count == 0)
                {
                    ClothGuess.Guess(animator);
                    if (vrm.SpringBone.ColliderGroups.Count == 0)
                    {
                        HumanoidCollider.AddColliders(animator);
                        var warps = animator.GetComponentsInChildren<ClothWarpRoot>();
                        var colliderGroups = animator.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
                        foreach (var warp in warps)
                        {
                            warp.ColliderGroups = colliderGroups.ToList();
                        }
                    }
                }
                else
                {
                    ClothWarpRuntimeProvider.FromVrm10(vrm,
                        go => go.AddComponent<ClothWarpRoot>());
                }

                if (m_addClothToHips.isOn)
                {
                    if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
                    {
                        var cloth = hips.GetComponent<ClothGrid>();
                        if (cloth == null)
                        {
                            cloth = hips.gameObject.AddComponent<ClothGrid>();
                            cloth.Reset();
                            cloth.LoopIsClosed = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
        }

        async void LoadModel(string path)
        {
            // cleanup
            m_loaded?.Dispose();
            m_loaded = null;
            _cancellationTokenSource?.Dispose();
            _cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken = _cancellationTokenSource.Token;

            try
            {
                Debug.LogFormat("{0}", path);
                var vrm10Instance = await Vrm10.LoadPathAsync(path,
                    canLoadVrm0X: true,
                    showMeshes: false,
                    awaitCaller: m_useAsync.isOn
                        ? new RuntimeOnlyAwaitCaller()
                        : new ImmediateCaller(),
                    materialGenerator: GetVrmMaterialDescriptorGenerator(true),
                    vrmMetaInformationCallback: m_texts.UpdateMeta,
                    springboneRuntime: m_useJob.isOn
                        ? new UniVRM10.ClothWarp.Jobs.ClothWarpJobRuntime(OnInit)
                        : new UniVRM10.ClothWarp.ClothWarpRuntime(OnInit)
                        );
                if (cancellationToken.IsCancellationRequested)
                {
                    UnityObjectDestroyer.DestroyRuntimeOrEditor(vrm10Instance.gameObject);
                    cancellationToken.ThrowIfCancellationRequested();
                }

                if (vrm10Instance == null)
                {
                    Debug.LogWarning("LoadPathAsync is null");
                    return;
                }

                var instance = vrm10Instance.GetComponent<RuntimeGltfInstance>();
                instance.ShowMeshes();
                instance.EnableUpdateWhenOffscreen();
                m_loaded = new Loaded(instance, m_target.transform);
                m_init = new ClothWarp.HumanoidPose(vrm10Instance.GetComponent<Animator>());
                m_showBoxMan.isOn = false;
            }
            catch (Exception ex)
            {
                if (ex is OperationCanceledException)
                {
                    Debug.LogWarning($"Canceled to Load: {path}");
                }
                else
                {
                    Debug.LogError($"Failed to Load: {path}");
                    Debug.LogException(ex);
                }
            }
        }
    }
}