using System;
using UnityEngine;
using Debug = UnityEngine.Debug;

namespace BansheeGz.BGDatabase
{
    public class BGDatabaseMTAssetsLoaderAsync : BGDatabaseMTAssetsLoaderI
    {
        private static readonly AsyncAddressablesLoader addressablesLoader;
        private readonly Action fireOnFinished;
        private readonly bool validateLoadedAsset;
        private readonly AssetCacheListener listener;

        private int requestCount;

        static BGDatabaseMTAssetsLoaderAsync()
        {
            var type = BGUtil.GetType("BansheeGz.BGDatabase.BGDatabaseMTAssetsLoaderAsyncAddressables");
            if (type != null) addressablesLoader = Activator.CreateInstance(type) as AsyncAddressablesLoader;
        }

        public BGDatabaseMTAssetsLoaderAsync(bool validateLoadedAsset, Action fireOnFinished, AssetCacheListener listener)
        {
            this.validateLoadedAsset = validateLoadedAsset;
            this.fireOnFinished = fireOnFinished;
            this.listener = listener;
        }

        public void Load()
        {
            BGRepo.I.ForEachMeta(meta =>
            {
                meta.ForEachField(field =>
                {
                    var withLoader = (BGAssetLoaderA.WithLoaderI)field;
                    switch (withLoader.AssetLoader)
                    {
                        case BGAssetLoaderAddressables _:
                            ProcessAddressablesField(field);
                            break;
                        case BGAssetLoaderResources _:
                            ProcessResourcesField(field);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(withLoader.AssetLoader));
                    }
                }, field => field is BGAssetLoaderA.WithLoaderI);
            });
            //in case there is no requests
            CheckRequestCount();
        }

        public int PendingRequests => requestCount;

        private void ProcessAddressablesField(BGField field)
        {
            if (addressablesLoader == null)
                throw new Exception("Unpack Assets/BansheeGz/BGDatabaseReadonlyMT/Scripts/AddressablesSupport.unity package to add support for " +
                                    "asynchronous loading of Addressables assets");
            addressablesLoader.Load(new LoadContext(field, validateLoadedAsset, () => requestCount++, ReduceRequestCount, listener));
        }

        private void ProcessResourcesField(BGField field)
        {
            var meta = field.Meta;
            var valueType = field.ValueType;
            if (field is BGFieldUnityObject fieldUnityObject && fieldUnityObject.AssetType != null) valueType = fieldUnityObject.AssetType;
            var sprite = field is BGFieldUnitySprite spriteField;
            if (sprite && validateLoadedAsset)
            {
                //not clear how to implement it
                // Debug.Log($"Assets preloader WARNING: field={field.FullName}: loading sprites asynchronously from multiple sprites and sprite atlases is not currently supported." +
                          // " Sprites will be loaded synchronously.");
            }
            for (var i = 0; i < meta.CountEntities; i++)
            {
                var path = ((BGStorable<string>)field).GetStoredValue(i);
                if (string.IsNullOrWhiteSpace(path)) continue;

                var entityIndex = i;
                if (sprite)
                {
                    var location = new BGFieldUnitySprite.SpriteLocation(path);
                    switch (location.Location)
                    {
                        case BGFieldUnitySprite.LocationEnum.Multiple:
                        case BGFieldUnitySprite.LocationEnum.SpriteAtlas:
                        {
                            //synchronous fallback
                            var asset = field.GetValue(i);
                            if (validateLoadedAsset && asset == null)
                            {
                                var storedValue = ((BGStorable<string>)field).GetStoredValue(i);
                                if (!string.IsNullOrEmpty(storedValue)) Debug.Log($"Assets preloader WARNING: " +
                                                                                  $"Asset can not be loaded for the entity #={i} field={field.FullName} with invalid path={storedValue}");
                            }
                            continue;
                        }
                    }
                }
                
                requestCount++;
                var handler = Resources.LoadAsync(path, valueType);
                handler.completed += h => ResourceRequestCompleted(entityIndex, field, path, handler);
            }
        }

        private void ResourceRequestCompleted(int entityIndex, BGField field, string path, ResourceRequest handler)
        {
            var asset = handler.asset;
            if (asset == null)
            {
                if (validateLoadedAsset)
                    Debug.Log($"Assets preloader WARNING: Asset can not be loaded for the entity #={entityIndex} field={field.FullName} " +
                              $"using path={path}");
            }
            else
            {
                if (asset is Sprite && asset.name.EndsWith("(Clone)")) asset.name = asset.name.Substring(0, asset.name.Length - 7);
                BGAssetsCache.Add(path, asset);
                listener?.AssetAddedToCache(field, entityIndex, path, asset);
            }

            ReduceRequestCount();
        }

        private void ReduceRequestCount()
        {
            requestCount--;
            CheckRequestCount();
        }

        private void CheckRequestCount()
        {
            if (requestCount == 0) fireOnFinished?.Invoke();
        }

        //this interface is required, cause Addressables are not installed by default
        public interface AsyncAddressablesLoader
        {
            void Load(LoadContext context);
        }

        public class LoadContext
        {
            internal readonly BGField field;
            internal readonly bool validateLoadedAsset;
            internal readonly Action increaseCount;
            internal readonly Action decreaseCount;
            internal readonly AssetCacheListener listener;
            public LoadContext(BGField field, bool validateLoadedAsset, Action increaseCount, Action decreaseCount, AssetCacheListener listener)
            {
                this.field = field;
                this.validateLoadedAsset = validateLoadedAsset;
                this.increaseCount = increaseCount;
                this.decreaseCount = decreaseCount;
                this.listener = listener;
            }
        }
        public interface AssetCacheListener
        {
            void AssetAddedToCache(BGField field, int entityIndex, string path, object asset);
        }
    }
}