﻿/*
<copyright file="BGAddressablesGetAsset.cs" company="BansheeGz">
    Copyright (c) 2018-2020 All Rights Reserved
</copyright>
*/

using System;
using System.Collections;
using Bolt;
using Ludiq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;

namespace BansheeGz.BGDatabase
{
    /// <summary>Load an asset from addressables system in synchronous way</summary>
    [UnitCategory("BansheeGz")]
    public class BGAddressablesGetAsset : Unit
    {
        public enum EntitySourceEnum
        {
            Index,
            Id,
            Name
        }


        [DoNotSerialize] public ControlInput enter;
        [DoNotSerialize] public ControlOutput exitNoWait;
        [DoNotSerialize] [PortLabel("Loaded")] public ControlOutput exit;

        [DoNotSerialize] public ValueOutput asset;
        [DoNotSerialize] public ValueInput metaName;
        [DoNotSerialize] public ValueInput fieldName;
        [DoNotSerialize] public ValueInput entitySource;
        [DoNotSerialize] public ValueInput entityIndex;
        [DoNotSerialize] public ValueInput entityId;
        [DoNotSerialize] public ValueInput entityName;

        protected override void Definition()
        {
            enter = ControlInputCoroutine("enter", StartLoading);
            exitNoWait = ControlOutput("exitNoWait");
            exit = ControlOutput("exit");

            metaName = ValueInput<string>("Meta name", "");
            fieldName = ValueInput<string>("Field name", "");
            entitySource = ValueInput<EntitySourceEnum>("Entity source", EntitySourceEnum.Index);
            entityIndex = ValueInput<int>("Entity index", -1);
            entityId = ValueInput<string>("Entity ID", "");
            entityName = ValueInput<string>("Entity name", "");

            asset = ValueOutput<UnityEngine.Object>("asset");

            this.Succession(this.enter, this.exit);
            this.Succession(this.enter, this.exitNoWait);
        }

        private IEnumerator StartLoading(Flow flow)
        {
            var tableName = flow.GetValue<string>(metaName);
            if (string.IsNullOrEmpty(tableName)) throw new Exception("Meta name is not set!");
            var meta = BGRepo.I[tableName];
            if (meta == null) throw new Exception("Can not find meta with name " + tableName + "!");
            var tableFieldName = flow.GetValue<string>(fieldName);
            if (string.IsNullOrEmpty(tableFieldName)) throw new Exception("Field name is not set! meta=" + tableName);
            var field = meta.GetField(tableFieldName, false);
            if (field == null) throw new Exception("Can not find field with name " + tableFieldName + "! meta=" + tableName);
            if (!(field is BGAssetLoaderA.WithLoaderI)) throw new Exception("Type of field " + tableFieldName + " is not Unity asset! meta=" + tableName);
            if (!(field is BGAddressablesAssetI)) throw new Exception("Type of field " + tableFieldName + " is not Unity asset! meta=" + tableName);

            var fieldWithLoader = field as BGAssetLoaderA.WithLoaderI;
            if (!(fieldWithLoader.AssetLoader is BGAssetLoaderAddressables)) throw new Exception("Field " + tableFieldName + " does not set to have Addressables loader! meta=" + tableName);

            var entitySourceValue = flow.GetValue<EntitySourceEnum>(entitySource);
            BGEntity entity;
            switch (entitySourceValue)
            {
                case EntitySourceEnum.Index:
                    var entityIndexValue = flow.GetValue<int>(entityIndex);
                    if (entityIndexValue < 0 || entityIndexValue >= meta.CountEntities)
                        throw new Exception("Can not get entity with index " + entityIndexValue +
                                            ": index less than 0 or more than entities count! meta=" + tableName + " index=" + entityIndexValue);
                    entity = meta.GetEntity(entityIndexValue);
                    break;
                case EntitySourceEnum.Id:
                    var entityIdValue = flow.GetValue<string>(entityId);
                    var entityIdValueTyped = BGId.Parse(entityIdValue);
                    if (entityIdValueTyped.IsEmpty) throw new Exception("Can not get entity by ID: ID is not set or invalid! meta=" + tableName + " ID=" + entityIdValue);

                    entity = meta.GetEntity(entityIdValueTyped);
                    if (entity == null) throw new Exception("Can not get entity by ID: entity is not found! meta=" + tableName + " ID=" + entityIdValue);
                    break;
                case EntitySourceEnum.Name:
                    var entityNameValue = flow.GetValue<string>(entityName);
                    entity = meta.GetEntity(entityNameValue);
                    if (entity == null) throw new Exception("Can not get entity by name: entity is not found! meta=" + tableName + " name=" + entityNameValue);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("entitySourceValue");
            }

            yield return exitNoWait;
            var fieldAddressables = field as BGAddressablesAssetI;
            var address = fieldAddressables.GetAddressablesAddress(entity.Index);
            if (string.IsNullOrEmpty(address))
            {
                flow.SetValue(asset, null);
                yield return exit;
            }
            else
            {
                var info = new LoadingInfo(){entityIndex = entity.Index};
                var valueType = field.ValueType;
                if (valueType == typeof(Sprite))
                {
                    var operation = Addressables.LoadAssetAsync<Sprite>(address);
                    if (operation.Result == null) operation.Completed += handle => OnLoadSprite(flow, handle, info);
                    else OnLoadSprite(flow, operation, info);
                }
                else
                {
                    var operation = Addressables.LoadAssetAsync<Object>(address);
                    if (operation.Result == null) operation.Completed += handle => OnLoad(flow, handle, info);
                    else OnLoad(flow, operation, info);
                }

                if (!info.loaded) yield return new WaitUntil(() => info.loaded);
                yield return exit;
            }
        }

        private void OnLoadSprite(Flow flow, AsyncOperationHandle<Sprite> handle, LoadingInfo info)
        {
            info.loaded = true;
            flow.SetValue(asset, handle.Result);
        }

        private void OnLoad(Flow flow, AsyncOperationHandle<Object> handle, LoadingInfo info)
        {
            info.loaded = true;
            flow.SetValue(asset, handle.Result);
        }

        private class LoadingInfo
        {
            public int entityIndex;
            public bool loaded;
        }
    }
}