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

using System;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;

namespace BansheeGz.BGDatabase
{
    /// <summary>Load an asset from addressables system and fires BGAssetLoadedEvent event when asset is loaded</summary>
    [UnitCategory("BansheeGz")]
    public class BGAddressablesGetAssetViaEvent : Unit
    {
        public enum EntitySourceEnum
        {
            Index,
            Id,
            Name
        }


        [DoNotSerialize] public ControlInput enter;
        [DoNotSerialize] public ControlOutput exit;

        [DoNotSerialize]
        [NullMeansSelf]
        public ValueInput target { get; private set; }

        [DoNotSerialize] public ValueInput eventName;
        [DoNotSerialize] public ValueInput eventData;
        [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 = ControlInput("enter", StartLoading);
            exit = ControlOutput("exit");

            target = ValueInput<GameObject>("target", (GameObject) null).NullMeansSelf();
            eventName = ValueInput<string>("Event name", "");
            eventData = ValueInput<object>("Event data").AllowsNull();
            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", "");

            this.Requirement(this.target, this.enter);
            this.Succession(this.enter, this.exit);
        }

        private ControlOutput 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 fieldWithLoader)) throw new Exception("Type of field " + tableFieldName + " is not Unity asset! meta=" + tableName);
            if (!(fieldWithLoader is BGAddressablesAssetI fieldAddressables)) throw new Exception("Type of field " + tableFieldName + " is not Unity asset! meta=" + tableName);

            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");
            }

            var address = fieldAddressables.GetAddressablesAddress(entity.Index);
            if (!string.IsNullOrEmpty(address))
            {
                var gameObject = flow.GetValue<GameObject>(this.target);
                var info = new BGAssetLoadedEventArgs(
                    flow.GetValue<string>(eventName),
                    eventData.hasValidConnection ? flow.GetValue<object>(eventData) : null,
                    tableName,
                    tableFieldName,
                    entity.Index
                );
                var valueType = field.ValueType;
                if (valueType == typeof(Sprite))
                {
                    var operation = Addressables.LoadAssetAsync<Sprite>(address);
                    if (operation.Result == null) operation.Completed += handle => OnLoadSprite(field, entity.Id, handle, gameObject, info);
                    else OnLoadSprite(field, entity.Id, operation, gameObject, info);
                }
                else
                {
                    var operation = Addressables.LoadAssetAsync<Object>(address);
                    if (operation.Result == null) operation.Completed += handle => OnLoad(field, entity.Id, handle, gameObject, info);
                    else OnLoad(field, entity.Id, operation, gameObject, info);
                }
            }

            return exit;
        }

        private static void OnLoadSprite(BGField field, BGId eId, AsyncOperationHandle<Sprite> handle, GameObject gameObject, BGAssetLoadedEventArgs info)
        {
            info.asset = handle.Result;
            if(BGAddressablesMonitor.Enabled) BGAddressablesMonitor.AssetWasLoaded(field, eId);
            BGAssetLoadedEvent.Trigger(gameObject, info);
        }

        private static void OnLoad(BGField field, BGId eId, AsyncOperationHandle<Object> handle, GameObject gameObject, BGAssetLoadedEventArgs info)
        {
            info.asset = handle.Result;
            if(BGAddressablesMonitor.Enabled) BGAddressablesMonitor.AssetWasLoaded(field, eId);
            BGAssetLoadedEvent.Trigger(gameObject, info);
        }
    }
}