using Spine;
using Spine.Unity;
using Spine.Unity.AttachmentTools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;

public enum EquipType { None, Armor, Weapon, Accessory }

[System.Serializable]
public class EquipData
{
    public EquipType type;
    public string type2;
    public Sprite sprite;
    [Space(10)]
    public ItemData item;

    public Attachment attachment;
    public Texture texture;
    public Material matOrigin;
    public Material matHit;
    public Material matResist;
}

[System.Serializable]
public class EquipHook
{
    public EquipType type;
    public string type2;
    [Space(10)]
    [SpineSlot]
    public string slot;
    [SpineAttachment(skinField: "templateSkin")]
    public string templateAttachment;
}

public class Equipment : ResourcePool
{
    void Start()
    {
        Skin(skinName);
        InstallApply();

        Install(ItemData.Copy(DataManager.Instance.dataItem.Search($"spear1")));
        //Install(ItemData.Copy(DataManager.Instance.dataItem.Search($"gold_01")));
    }

    public Character character;
    [Space(10)]
    public SkeletonAnimation skeletonAnimation;
    [Space(10)]
    public SkeletonDataAsset skeletonDataAsset;
    public Material sourceMaterial;
    public bool applyPMA = true;
    /// <summary>
    /// 캐릭터의 아틀라스(스킨 및 장비아이템)를 갱신합니다.
    /// </summary>
    void RefreshSkeletonAttachments()
    {
        skeletonAnimation.Skeleton.SetSlotsToSetupPose();
        skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); //skeletonAnimation.Update(0);
    }
    Dictionary<string, Attachment> attachments = new Dictionary<string, Attachment>();
    /// <summary>
    /// 장비의 이미지를 적용합니다.
    /// </summary>
    /// <param name="key">풀링 키</param>
    /// <param name="sprite">장비의 이미지</param>
    /// <param name="slotIndex">슬롯 번호</param>
    /// <param name="templateSkinName">현재 스킨 이름</param>
    /// <param name="templateAttachmentName">바꿔야할 어태치먼트의 이름</param>
    /// <returns></returns>
    Attachment AtlasAttachment(string key, Sprite sprite, int slotIndex, string templateSkinName, string templateAttachmentName)
    {
        Attachment attachment = null;

        if (attachments.ContainsKey(key))
        {
            attachment = attachments[key];
        }
        else
        {
            var templateSkin = skeletonDataAsset.GetSkeletonData(true).FindSkin(templateSkinName);
            attachment = templateSkin.GetAttachment(slotIndex, templateAttachmentName).GetRemappedClone(sprite, sourceMaterial, premultiplyAlpha: this.applyPMA);

            attachments.Add(key, attachment);
        }

        return attachment;
    }

    [Space(10)]
    [SpineSkin]
    public string skinName;//현재 선택된 스킨(아머)
    Skin equipsSkin;//현재 장착하고 있는 스킨(아머)
    Dictionary<string, Skin> skins = new Dictionary<string, Skin>();
    /// <summary>
    /// 캐릭터의 스킨을 변경 합니다.
    /// </summary>
    /// <param name="name"></param>
    public void Skin(string name)
    {
        skinName = name;

        if (skins.ContainsKey($"{skinName}"))
        {
            equipsSkin = skins[$"{skinName}"];
        }
        else
        {
            equipsSkin = new Skin($"{skinName}");

            var templateSkin = skeletonAnimation.Skeleton.Data.FindSkin(skinName);
            if (templateSkin != null)
                equipsSkin.AddAttachments(templateSkin);

            skins.Add($"{skinName}", equipsSkin);
        }

        skeletonAnimation.Skeleton.Skin = equipsSkin;
        RefreshSkeletonAttachments();
    }

    [Space(10)]
    public List<EquipHook> equips;//스파인 내의 장비 목록
    /// <summary>
    /// 장착 아이템을 장착합니다.
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public bool Install(ItemData item, bool EQUIP = true)
    {
        EquipData data = new EquipData();

        if (item.type.CompareTo($"{EquipType.Armor}") == 0)
        {
            character.ec.Remove(OnChanged_ArmorHeart);

            data.type = Util.EnumUtil<EquipType>.Parse($"{item.type}");
            data.item = item;

            Skin(item.idx);

            //character.ec.Add(OnChanged_ArmorHeart);
        }
        else if (item.type.CompareTo($"{EquipType.Weapon}") == 0 || item.type.CompareTo($"{EquipType.Accessory}") == 0)
        {
            data.sprite = SpriteManager.Instance.SpriteGet($"{item.idx}");

            data.type = Util.EnumUtil<EquipType>.Parse($"{item.type}");
            data.item = item;
        }
        else
            return false;

        bool INSTALL = InstallEquip(data);
        if (INSTALL && EQUIP)
        {
            INSTALL = InstallApply();

            EquipHook hook = equips.Find(a => a.type == data.type && data.item.idx.Contains(a.type2));
            if (hook != null)
                data.type2 = hook.type2;

            if (data.type == EquipType.Weapon)
                onWeaponChanged.Invoke(data);//바뀐 무기의 연동이 필요
            else if (data.type == EquipType.Armor)
                character.ec.Add(OnChanged_ArmorHeart);
        }

        return INSTALL;
    }
    Dictionary<EquipType, EquipData> installs = new Dictionary<EquipType, EquipData>();
    public System.Action<Transform, ItemData> OnUnInstall;
    /// <summary>
    /// 해당 아이템 의 부위를 확인
    /// 이미 장착되어 있는 부위 라면 장착하고 잇던 아이템을 드랍 처리합니다.
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    public bool InstallEquip(EquipData data)
    {
        if (installs.ContainsKey(data.type))
        {
            EquipData before = installs[data.type];

            if (data.type == EquipType.Armor || before.item.idx != data.item.idx)
            {
                installs[data.type] = data;
                //이전 장착 아이템 드랍 처리
                //character.map.blkCurrent.OnDropItem_Create(transform, before.item);
                //장비가 보유중인 스킬 해제
                character.skill.UnInstall(before.item);
            }
            else
                return false;
        }
        else
            installs.Add(data.type, data);//빈 슬롯에 장착

        //장비가 보유중인 스킬 적용
        character.skill.Install(data.item);

        return true;
    }
    /// <summary>
    /// 착용한 장비 중 같은 아이템이 있는지 확인합니다.
    /// </summary>
    /// <param name="data">확인할 아이템</param>
    /// <returns></returns>
    public bool InstallCheck_Item(ItemData data)
    {
        EquipType type = Util.EnumUtil<EquipType>.Parse($"{data.type}");
        if (installs.ContainsKey(type))
        {
            EquipData before = installs[type];

            if (type == EquipType.Armor || before.item.idx != data.idx)
                return true;
            else
                return false;
        }

        return true;
    }
    /// <summary>
    /// 해당 부위에 착용중인 아이템이 있는지 확인
    /// </summary>
    /// <param name="type">확인할 부위</param>
    /// <returns></returns>
    public bool InstallCheck_Part(EquipType type)
    {
        return installs.ContainsKey(type);
    }
    [Space(10)]
    public Sprite spriteAlpha;
    /// <summary>
    /// 장착 부위를 적용합니다.
    /// </summary>
    bool InstallApply()
    {
        for (int i = 0; i < equips.Count; i++)
        {
            var skeletonData = skeletonDataAsset.GetSkeletonData(true);
            var slotIndex = skeletonData.FindSlotIndex(equips[i].slot);
            //var attachment = AtlasAttachment($"{slotIndex}{spriteAlpha}", spriteAlpha, slotIndex, skinName, equips[i].templateAttachment);

            //Equip(slotIndex, equips[i].templateAttachment, attachment);
            equipsSkin.RemoveAttachment(slotIndex, equips[i].templateAttachment);
        }

        EquipData data = null;
        var list = new List<EquipData>(installs.Values);
        for (int h = 0; h < list.Count; h++)
        {
            data = list[h];
            if (data.type == EquipType.Armor)
                continue;

            List<EquipHook> hooks = equips.FindAll(a => a.type == data.type && data.item.idx.Contains(a.type2));
            //if (hooks.Count == 0)
            //    return false;

            EquipHook equip = null;
            for (int i = 0; i < hooks.Count; i++)
            {
                equip = hooks[i];

                var skeletonData = skeletonDataAsset.GetSkeletonData(true);
                var slotIndex = skeletonData.FindSlotIndex(equip.slot);
                var attachment = AtlasAttachment($"{slotIndex}{equip.slot}{data.sprite}", data.sprite, slotIndex, skinName, equip.templateAttachment);

                if (attachment != null)
                {
                    data.attachment = attachment;
                    data.matOrigin = (Material)data.attachment.GetRegion().page.rendererObject;
                    data.texture = data.matOrigin.mainTexture;

                    data.matHit = new Material(matHit);
                    data.matHit.mainTexture = data.texture;

                    data.matResist = new Material(matResist);
                    data.matResist.mainTexture = data.texture;
                }

                Equip(slotIndex, equip.templateAttachment, attachment);
            }
        }

        return true;
    }
    /// <summary>
    /// 장비의 이미지를 적용합니다.
    /// </summary>
    /// <param name="slotIndex"></param>
    /// <param name="attachmentName"></param>
    /// <param name="attachment"></param>
    void Equip(int slotIndex, string attachmentName, Attachment attachment)
    {
        equipsSkin.SetAttachment(slotIndex, attachmentName, attachment);

        skeletonAnimation.Skeleton.SetSkin(equipsSkin);
        RefreshSkeletonAttachments();
    }

    [System.Serializable]
    public class WeaponChangEvent : UnityEvent<EquipData> { }
    [FormerlySerializedAs("onWeaponChanged")]
    [SerializeField]
    [Space(10)]
    private WeaponChangEvent onWeaponChanged = new WeaponChangEvent();

    [Space(10)]
    public TileCameraEvent tiltEvent;
    public GameObject particleArmorDestroy;
    public Transform targetArmorDestroy;
    /// <summary>
    /// 캐릭터의 AH 가 변경되어 이를 적용합니다.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="value"></param>
    public void OnChanged_ArmorHeart(int type, object value)
    {
        if (installs.ContainsKey(EquipType.Armor))
        {
            ItemData item = installs[EquipType.Armor].item;
            SkillData skill = item.skills.Find(a => a.work.Equals("AH"));            
            skill.value = character.AH;

            //AH 가 0 이 되면 착용중인 갑옷을 파괴
            if (skill.value < 1)
            {
                //장비가 보유중인 스킬 해제
                for (int i = 0; i < item.skills.Count; i++)
                    character.skill.skills.Remove(item.skills[i]);
                character.skill.items.Remove(item);

                Skin("base_01");
                installs.Remove(EquipType.Armor);
                InstallApply();

                tiltEvent.Shake();
                ObjectCreateLocalPos(particleArmorDestroy, targetArmorDestroy, Vector3.one, Vector3.zero);
            }
        }
    }
    
    /// <summary>
    /// 캐릭터의 히트 등의 효과로 스파인에 Material 이 변경 될 경우, 장비의 이미지는 따로 적용해야함(어태치먼트 적용시 장비템 마다 아틀라스가 새로생성됨)
    /// </summary>
    [Space(5)]
    public Material matHit;
    public void Hit_Start(float duration)
    {
        if (0 == installs.Count || duration == 0)
            return;

        CancelInvoke("Hit_End");

        foreach (KeyValuePair<EquipType, EquipData> value in installs)
        {
            if (value.Value.matHit != null)
                value.Value.attachment.GetRegion().page.rendererObject = value.Value.matHit;
        }

        Invoke("Hit_End", duration);
    }
    void Hit_End()
    {
        foreach (KeyValuePair<EquipType, EquipData> value in installs)
        {
            if (value.Value.matHit != null)
                value.Value.attachment.GetRegion().page.rendererObject = value.Value.matOrigin;
        }
    }

    public Material matResist;
    public void Resist_Start(float duration)
    {
        if (0 == installs.Count || duration == 0)
            return;

        CancelInvoke("Hit_End");

        foreach (KeyValuePair<EquipType, EquipData> value in installs)
        {
            if (value.Value.matResist != null)
                value.Value.attachment.GetRegion().page.rendererObject = value.Value.matResist;
        }

        Invoke("Hit_End", duration);
    }
    void Resist_End()
    {
        foreach (KeyValuePair<EquipType, EquipData> value in installs)
        {
            if (value.Value.matResist != null)
                value.Value.attachment.GetRegion().page.rendererObject = value.Value.matOrigin;
        }
    }
}