using System;
using HutongGames.PlayMaker;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;

namespace BansheeGz.BGDatabase
{
    [ActionCategory("BansheeGz")]
    [HutongGames.PlayMaker.Tooltip("Loads an Addressable asset from BGDatabase based on entity info and caches it under a named group. If the asset is already cached, it will be returned immediately. The cache group can later be released manually.")]
 
    public class BGAddressablesGetAssetAutoCached : FsmStateAction
    {
        public enum EntitySourceEnum
        {
            Index,
            Id,
            Name
        }

        [HutongGames.PlayMaker.Tooltip("Meta Id")]
        public string MetaId;

        [HutongGames.PlayMaker.Tooltip("Field Id")]
        public string FieldId;

        [HutongGames.PlayMaker.Tooltip("How to locate the entity (by Index, Id, or Name)")]
        public EntitySourceEnum EntitySource;

        [UIHint(UIHint.FsmInt)]
        [HutongGames.PlayMaker.Tooltip("Entity index if using 'Index' source")]
        public FsmInt EntityIndex;

        [UIHint(UIHint.FsmString)]
        [HutongGames.PlayMaker.Tooltip("Entity ID if using 'Id' source")]
        public FsmString EntityId;

        [UIHint(UIHint.FsmString)]
        [HutongGames.PlayMaker.Tooltip("Entity name if using 'Name' source")]
        public FsmString EntityName;

        [UIHint(UIHint.FsmString)]
        [HutongGames.PlayMaker.Tooltip("Name of the group to cache the loaded asset under. Use this to manage asset lifetime. If left blank, \"Default\" will be used.")]
        public FsmString CacheGroup;

        [UIHint(UIHint.Variable)]
        [HutongGames.PlayMaker.Tooltip("Loaded Addressable asset")]
        public FsmObject Result;

        [HutongGames.PlayMaker.Tooltip("Event to fire once the asset is loaded")]
        public FsmEvent LoadedEvent;

        [HutongGames.PlayMaker.Tooltip("Event to fire if the asset was not found or failed to load")]
        public FsmEvent NotFoundEvent;

        private bool assetFound = false;

        public BGMetaEntity Meta => BGRepo.I[BGId.Parse(MetaId)];

        public BGField Field
        {
            get
            {
                var meta = Meta;
                if (meta == null) return null;
                var field = meta.GetField(BGId.Parse(FieldId), false);
                if (field == null) return null;
                if (!(field is BGAddressablesAssetI) || !(field is BGAssetLoaderA.WithLoaderI withLoaderI)) return null;
                var loader = withLoaderI.AssetLoader;
                if (!(loader is BGAssetLoaderAddressables)) return null;
                return field;
            }
        }

        public override void Reset()
        {
            MetaId = null;
            FieldId = null;
            EntitySource = EntitySourceEnum.Index;
            EntityIndex = 0;
            EntityId = null;
            EntityName = null;
            CacheGroup = null;
            Result = null;
            LoadedEvent = null;
        }

        public override void OnEnter()
        {
            //reset Result value
            if (!Result.IsNone) Result.Value = null;
            assetFound = false;

            var callbackAttached = false;
            var field = Field;
            if (field != null)
            {
                var addressablesField = field as BGAddressablesAssetI;
                string address = null;
                var entityIndex = -1;
                switch (EntitySource)
                {
                    case EntitySourceEnum.Index:
                        if (!EntityIndex.IsNone) entityIndex = EntityIndex.Value;
                        break;
                    case EntitySourceEnum.Id:
                        if (!EntityId.IsNone)
                        {
                            var entity = field.Meta.GetEntity(BGId.Parse(EntityId.Value));
                            if (entity != null) entityIndex = entity.Index;
                        }
                        break;
                    case EntitySourceEnum.Name:
                        if (!EntityName.IsNone)
                        {
                            var entity = field.Meta.GetEntity(EntityName.Value);
                            if (entity != null) entityIndex = entity.Index;
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (entityIndex >= 0)
                {
                    address = addressablesField.GetAddressablesAddress(entityIndex);
                    var group = CacheGroup.IsNone || string.IsNullOrEmpty(CacheGroup.Value) ? "Default" : CacheGroup.Value;
                    if (!string.IsNullOrEmpty(address))
                    {
                        var valueType = field.ValueType;
                        if (BGAddressablesAssetCache.TryGet(group, address, out Object cachedAsset))
                        {
                            Result.Value = cachedAsset;
                            assetFound = true;
                            Fsm.Event(LoadedEvent);
                            Finish();
                            return;
                        }

                        if (valueType == typeof(Sprite))
                        {
                            Addressables.LoadAssetAsync<Sprite>(address).Completed += op => OnLoaded(op, address, group);
                        }
                        else
                        {
                            Addressables.LoadAssetAsync<Object>(address).Completed += op => OnLoaded(op, address, group);
                        }
                        callbackAttached = true;
                    }
                }
            }

            if (!callbackAttached) Done();
        }

        private void OnLoaded<T>(AsyncOperationHandle<T> op, string address, string group) where T : Object
        {
            if (op.Status == AsyncOperationStatus.Succeeded && op.Result != null)
            {
                if (!Result.IsNone) Result.Value = op.Result;
                BGAddressablesAssetCache.Register(group, address, op.Result);
                assetFound = true;
            }
            Done();
        }

        private void Done()
        {
            if (assetFound)
            {
                Fsm.Event(LoadedEvent);
            }
            else
            {
                Fsm.Event(NotFoundEvent);
            }
            Finish();
        }
    }
}
