using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class DanMachiCharacterContainer : DanMachiObjectContainer, IAnimated
{
    public CharacterAsset Data;
    public string AnimationSet;
    public string Animation;

    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 override void Select()
    {
        if(MorphPanel != null)
        {
            MorphPanel.gameObject.SetActive(true);
        }
    }

    public override void Deselect()
    {
        if (MorphPanel != null)
        {
            MorphPanel.gameObject.SetActive(false);
        }
    }

    public void Build(CharacterAsset model)
    {
        Data = model;
        var characterAsset = AssetBundle.LoadFromFile(model.FilePath);
        var animationSets = DanMachiAssetLibrary.Instance.Animations.Where(a => a.Category == model.Category && a.Id == model.Id);

        foreach (var go in characterAsset.LoadAllAssets<GameObject>())
        {
            Instantiate(go, transform);
        }
        characterAsset.Unload(false);

        SetDefaultMaterials(transform);

        Init();

        var defaultAnimSet = animationSets.FirstOrDefault(a => a.Costume == "common");
        LoadAnimationSet(defaultAnimSet);
    }

    public void Init()
    {
        _animator = GetComponentInChildren<Animator>();
        _animator.applyRootMotion = false;
        _animator.runtimeAnimatorController = _animatorController = Instantiate(SharedResources.Instance.GenericAnimatorController);
        CreateMorphPanel();
        CreateCharacterPanel();
        SetBones();
    }

    public void LoadAnimationSet(CharacterAsset animationSet)
    {
        if (animationSet != null)
        {
            AnimationSet = animationSet.Costume;
            Animation = "";
            var animationAsset = AssetBundle.LoadFromFile(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(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()
    {
        var humanBones = new Dictionary<HumanBodyBones, Transform>();
        var allBones = _animator.transform.GetComponentsInChildren<Transform>().ToDictionary(b=>b.name, b=>b);
        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;
    }

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

    public void CreateMorphPanel()
    {
        if(MorphPanel != null)
        {
            Destroy(MorphPanel.gameObject);
        }

        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) return;

        MorphPanel = MorphPanel.Create(morphRenderers.Select(mr=>mr.Key.sharedMesh.name).ToList());
        MorphPanel.name = $"Chr_{Data.Id} Morphs";

        foreach(var rend in morphRenderers)
        {
            var mesh = rend.Key.sharedMesh;
            for(int i = 0; i < mesh.blendShapeCount; i++)
            {
                MorphPanel.CreateMorph(mesh.name, this, rend.Key, i, mesh.GetBlendShapeName(i).Replace("blendShape1.",""));
            }
        }

        Morphs = morphRenderers;
        DanMachiModelViewerInterface.Instance.SelectedObjectToolbar.AttachToThis(MorphPanel.GetComponent<UIElementDragger>());
    }

    public void CreateCharacterPanel()
    {
        CharacterPanel = CharacterPanel.Create(this);
        CharacterPanel.name = $"Chr_{Data.Id} Settings";
        DanMachiModelViewerInterface.Instance.SelectedObjectToolbar.AttachToThis(CharacterPanel.GetComponent<UIElementDragger>());
    }

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

    protected override void OnDestroy()
    {
        if (_applicationQuitting) return;
        if(MorphPanel != null)
        {
            Destroy(MorphPanel.gameObject);
        }
        Destroy(CharacterPanel.gameObject);
        base.OnDestroy();
    }

    public Animator GetAnimator()
    {
        return _animator;
    }
}