using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using UnityEditor.Graphs;
using UnityEngine;

public class DanMachiCharacterContainer : ObjectContainer, IAnimated
{
    protected override System.Type _serializeType => typeof(DanMachiCharacterSerializable);
    protected override System.Type _keyframeType => typeof(DanMachiCharacterKeyframeData);

    public CharacterAsset Data;
    public string AnimationSet;
    public string Animation;

    public GameObject Body;
    public MorphPanel MorphPanel;
    public CharacterPanel CharacterPanel;
    public List<AnimationClip> Animations;
    private Animator _animator;
    private AnimatorOverrideController _animatorController;
    private Dictionary<HumanBodyBones, Transform> _humanBones = new Dictionary<HumanBodyBones, Transform>();
    public List<Transform> Bones = new List<Transform>();

    public void Init()
    {
        _animator = GetComponentInChildren<Animator>();
        if(_animator != null)
        {
            _animator.applyRootMotion = false;
            _animator.runtimeAnimatorController = _animatorController = Instantiate(SharedResources.Instance.GenericAnimatorController);
        }
        else
        {
            Error.Log(Color.red, $"{name} has no animator");
        }
        CreateMorphPanel();
        CreateCharacterPanel();
        SetBones();
    }

    public void Rebuild(CharacterAsset model)
    {
        DestroyImmediate(Body);
        Build(model);
    }

    public void Build(CharacterAsset model)
    {
        Data = model;
        var assetPath = Path.Combine(DanMachiAssetLibrary.Instance.LocalFilesPath, model.FilePath);
        var characterAsset = AssetLibrary.LoadFromFile(assetPath);
        var assetNames = characterAsset.GetAllAssetNames();
        var characterModel = assetNames.FirstOrDefault(a => Path.GetFileName(a).StartsWith("CP_") && a.EndsWith(".prefab"));

        if(!string.IsNullOrEmpty(characterModel))
        {
            Body = Instantiate(characterAsset.LoadAsset(characterModel), transform) as GameObject;
        }
        else
        {
            Body = new GameObject();
            Body.transform.SetParent(transform);
            foreach (var go in characterAsset.LoadAllAssets<GameObject>())
            {
                Instantiate(go, Body.transform);
            }
        }

        LoadedAssets = new List<AssetBundleEntry>() { new AssetBundleEntry(model.AssetName, assetPath, characterAsset) };
        AssetLibrary.UnloadAsset(assetPath);

        SetDefaultMaterials(transform);

        Init();
    }

    public void LoadAnimationSet(CharacterAsset animationSet)
    {
        if (animationSet != null)
        {
            AnimationSet = animationSet.Costume;
            Animation = "";
            var animationAsset = AssetBundle.LoadFromFile(Path.Combine(DanMachiAssetLibrary.Instance.LocalFilesPath, animationSet.FilePath));
            Animations = animationAsset.LoadAllAssets<AnimationClip>().ToList();
            animationAsset.Unload(false);
        }
    }

    public void PlayAnimation(string clipName)
    {
        var anim = Animations.FirstOrDefault(a=>a.name == clipName);
        PlayAnimation(anim);
    }

    public void PlayAnimation(AnimationClip anim)
    {
        if(_animator == null)
        {
            Error.Log(Color.red, $"{name} has no animator");
            return;
        }

        if(anim == null)
        {
            Animation = "";
            _animator.enabled = false;
        }
        else
        {
            Animation = anim.name;
            _animator.enabled = true;
            _animatorController["Anim"] = anim;
        }
    }

    public void SetDefaultMaterials(Transform t)
    {
        List<Material> materials = new List<Material>();
        foreach (var renderer in t.GetComponentsInChildren<Renderer>())
        {
            foreach (var material in renderer.sharedMaterials)
            {
                if (materials.Contains(material)) continue;
                materials.Add(material);
                material.shader = Shader.Find("DanMachiUnlit");
            }
        }
    }

    public void SetBones()
    {
        _humanBones.Clear();
        Bones.Clear();

        if (_animator == null) return;

        var humanBones = new Dictionary<HumanBodyBones, Transform>();
        var allBones = new Dictionary<string, Transform>();
        foreach(var bone in _animator.transform.GetComponentsInChildren<Transform>())
        {
            if (!allBones.ContainsKey(bone.name))
            {
                allBones[bone.name] = bone;
            }
        }
        humanBones.Add(HumanBodyBones.Hips,                     TryGet(allBones,"Hips"));
        humanBones.Add(HumanBodyBones.Spine,                    TryGet(allBones,"Spine"));
        humanBones.Add(HumanBodyBones.RightUpperLeg,            TryGet(allBones,"RightUpLeg"));
        humanBones.Add(HumanBodyBones.RightLowerLeg,            TryGet(allBones,"RightLeg"));
        humanBones.Add(HumanBodyBones.RightFoot,                TryGet(allBones,"RightFoot"));
        humanBones.Add(HumanBodyBones.RightToes,                TryGet(allBones,"RightToeBase"));
        humanBones.Add(HumanBodyBones.LeftUpperLeg,             TryGet(allBones,"LeftUpLeg"));
        humanBones.Add(HumanBodyBones.LeftLowerLeg,             TryGet(allBones,"LeftLeg"));
        humanBones.Add(HumanBodyBones.LeftFoot,                 TryGet(allBones,"LeftFoot"));
        humanBones.Add(HumanBodyBones.LeftToes,                 TryGet(allBones,"LeftToeBase"));
        humanBones.Add(HumanBodyBones.Chest,                    TryGet(allBones,"Spine1"));
        humanBones.Add(HumanBodyBones.UpperChest,               TryGet(allBones,"Spine2"));
        humanBones.Add(HumanBodyBones.Neck,                     TryGet(allBones,"Neck"));
        humanBones.Add(HumanBodyBones.Head,                     TryGet(allBones,"Head"));
        humanBones.Add(HumanBodyBones.RightShoulder,            TryGet(allBones,"RightShoulder"));
        humanBones.Add(HumanBodyBones.RightUpperArm,            TryGet(allBones,"RightArm"));
        humanBones.Add(HumanBodyBones.RightLowerArm,            TryGet(allBones,"RightForeArm"));
        humanBones.Add(HumanBodyBones.RightHand,                TryGet(allBones,"RightHand"));
        humanBones.Add(HumanBodyBones.RightIndexProximal,       TryGet(allBones,"RightHandIndex1"));
        humanBones.Add(HumanBodyBones.RightIndexIntermediate,   TryGet(allBones,"RightHandIndex2"));
        humanBones.Add(HumanBodyBones.RightMiddleProximal,      TryGet(allBones,"RightHandMiddle1"));
        humanBones.Add(HumanBodyBones.RightMiddleIntermediate,  TryGet(allBones,"RightHandMiddle2"));
        humanBones.Add(HumanBodyBones.RightRingProximal,        TryGet(allBones,"RightHandRing1"));
        humanBones.Add(HumanBodyBones.RightRingIntermediate,    TryGet(allBones,"RightHandRing2"));
        humanBones.Add(HumanBodyBones.RightLittleProximal,      TryGet(allBones,"RightHandPinky1"));
        humanBones.Add(HumanBodyBones.RightLittleIntermediate,  TryGet(allBones,"RightHandPinky2"));
        humanBones.Add(HumanBodyBones.RightThumbProximal,       TryGet(allBones,"RightHandThumb1"));
        humanBones.Add(HumanBodyBones.RightThumbIntermediate,   TryGet(allBones,"RightHandThumb2"));
        humanBones.Add(HumanBodyBones.LeftShoulder,             TryGet(allBones,"LeftShoulder"));
        humanBones.Add(HumanBodyBones.LeftUpperArm,             TryGet(allBones,"LeftArm"));
        humanBones.Add(HumanBodyBones.LeftLowerArm,             TryGet(allBones,"LeftForeArm"));
        humanBones.Add(HumanBodyBones.LeftHand,                 TryGet(allBones,"LeftHand"));
        humanBones.Add(HumanBodyBones.LeftIndexProximal,        TryGet(allBones,"LeftHandIndex1"));
        humanBones.Add(HumanBodyBones.LeftIndexIntermediate,    TryGet(allBones,"LeftHandIndex2"));
        humanBones.Add(HumanBodyBones.LeftMiddleProximal,       TryGet(allBones,"LeftHandMiddle1"));
        humanBones.Add(HumanBodyBones.LeftMiddleIntermediate,   TryGet(allBones,"LeftHandMiddle2"));
        humanBones.Add(HumanBodyBones.LeftRingProximal,         TryGet(allBones,"LeftHandRing1"));
        humanBones.Add(HumanBodyBones.LeftRingIntermediate,     TryGet(allBones,"LeftHandRing2"));
        humanBones.Add(HumanBodyBones.LeftLittleProximal,       TryGet(allBones,"LeftHandPinky1"));
        humanBones.Add(HumanBodyBones.LeftLittleIntermediate,   TryGet(allBones,"LeftHandPinky2"));
        humanBones.Add(HumanBodyBones.LeftThumbProximal,        TryGet(allBones,"LeftHandThumb1"));
        humanBones.Add(HumanBodyBones.LeftThumbIntermediate,    TryGet(allBones,"LeftHandThumb2"));
        foreach(var bone in humanBones)
        {
            if(bone.Value != null)
            {
                var tags = new List<SerializableBone.BoneTags>() { SerializableBone.BoneTags.Humanoid };
                if (bone.Value.name.Contains("Left"))
                {
                    tags.Add(SerializableBone.BoneTags.Left);
                }
                else if (bone.Value.name.Contains("Right"))
                {
                    tags.Add(SerializableBone.BoneTags.Right);
                }
                if((bone.Key >= HumanBodyBones.LeftThumbProximal && bone.Key <= HumanBodyBones.LeftLittleDistal)
                    || (bone.Key >= HumanBodyBones.RightThumbProximal && bone.Key <= HumanBodyBones.RightLittleDistal))
                {
                    tags.Add(SerializableBone.BoneTags.Finger);
                }
                UIHandle.CreateAsChild<UIHandleBone>(bone.Value).Init(this, tags).WithLineRenderer();
            }
        }
        foreach(var bone in allBones.Values.Except(humanBones.Values))
        {
            var tags = new List<SerializableBone.BoneTags>();
            if (bone.GetComponent<Unity.Animations.SpringBones.SpringBone>() != null)
            {
                tags.Add(SerializableBone.BoneTags.Dynamic);
            }
            UIHandle.CreateAsChild<UIHandleBone>(bone).Init(this, tags).WithLineRenderer();
        }

        _humanBones = humanBones;
        Bones = allBones.Values.ToList();
    }

    public T TryGet<T>(Dictionary<string, T> dict, string key)
    {
        if (dict.ContainsKey(key))
        {
            return dict[key];
        }
        return default(T);
    }

    public void CreateMorphPanel()
    {
        var morphRenderers = new Dictionary<SkinnedMeshRenderer, List<MorphHelper>>();
        foreach (var rend in gameObject.GetComponentsInChildren<SkinnedMeshRenderer>())
        {
            if(rend.sharedMesh.blendShapeCount > 0)
            {
                morphRenderers.Add(rend, new List<MorphHelper>());
            }
        }

        if (morphRenderers.Count == 0)
        {
            if(MorphPanel != null)
            {
                Destroy(MorphPanel.gameObject);
                MorphPanel = null;
                InstantiatedObjects.Remove(MorphPanel.gameObject);
            }
            return;
        }

        if(MorphPanel != null)
        {
            MorphPanel.Init(morphRenderers.Select(mr => mr.Key.sharedMesh.name).ToList());
        }
        else
        {
            MorphPanel = MorphPanel.Create(morphRenderers.Select(mr => mr.Key.sharedMesh.name).ToList());
            MorphPanel.name = $"Chr_{Data.Id} Morphs";
            InstantiatedObjects.Add(MorphPanel.gameObject);
            DanMachiInterface.Instance.SelectedObjectToolbar.AttachToThis(MorphPanel.GetComponent<UIElementDragger>());
        }

        foreach (var rend in morphRenderers)
        {
            var mesh = rend.Key.sharedMesh;
            for(int i = 0; i < mesh.blendShapeCount; i++)
            {
                var panel = MorphPanel.CreateMorph(mesh.name, this, rend.Key, i, mesh.GetBlendShapeName(i).Replace("blendShape1.",""));
                var helper = panel.MorphHelper; 
                if (morphRenderers.ContainsKey(rend.Key))
                    morphRenderers[rend.Key].Add(helper);
                else
                    morphRenderers.Add(rend.Key, new List<MorphHelper>() { helper });
            }
        }

        Morphs = morphRenderers;
    }

    public void CreateCharacterPanel()
    {
        if(CharacterPanel != null)
        {
            CharacterPanel.Init(this);
        }
        else
        {
            CharacterPanel = CharacterPanel.Create(this);
            CharacterPanel.name = $"Chr_{Data.Id} Settings";
            InstantiatedObjects.Add(CharacterPanel.gameObject);
            DanMachiInterface.Instance.SelectedObjectToolbar.AttachToThis(CharacterPanel.GetComponent<UIElementDragger>());
        }
    }

    public override Vector3 GetCenter()
    {
        if (_humanBones.ContainsKey(HumanBodyBones.Hips))
        {
            return _humanBones[HumanBodyBones.Hips].position;
        }
        return base.GetCenter();
    }

    public Animator GetAnimator()
    {
        return _animator;
    }

    public override void PastePose(KeyframeData frame, PoseLoadOptions pasteParams)
    {
        PlayAnimation("");

        if (pasteParams.Root)
        {
            transform.SetTransform(frame.Root);
        }

        var frameData = frame as DanMachiCharacterKeyframeData;
        if (frameData == null) return;

        foreach (var bone in frameData.Bones.Bones)
        {
            var targetBone = Bones.FirstOrDefault(b => b.name == bone.Name);
            if (targetBone == null) continue;

            bone.Transform.ApplyTo(targetBone.transform);
        }

        foreach(var morph in frameData.Morphs)
        {
            var renderer = Morphs.Keys.FirstOrDefault(k => k.name == morph.Key);
            if (renderer == null) continue;

            foreach(var value in morph.Value)
            {
                var helper = Morphs[renderer].FirstOrDefault(h => h.Name == value.Name);
                if (helper == null) continue;
                helper.Value = value.Value;
                helper.UpdateMorph(value.Strength);
            }
        }

        SetKeyframe();
    }
}