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

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

namespace BansheeGz.BGDatabase
{
    [AddComponentMenu("BansheeGz/BGDataBinderUiToolkitGo")]
    [RequireComponent(typeof(UIDocument))]
    [BGPlugin(Version = "1.0")]
    public class BGDataBinderUiToolkitGo : BGDataBinderGoA
    {
        //-------------------- serializable
        [SerializeReference] [SerializeField] [HideInInspector]
        private List<BGDBUiElementBinderA> binders;


        //-------------------- non serializable
        private readonly List<BGDBA.FieldEventHandler> fieldListeners = new List<BGDBA.FieldEventHandler>();
        private int listenersCount;
        private UIDocument doc;

        public List<BGDBUiElementBinderA> Binders => binders;

        public UIDocument Doc
        {
            get
            {
                if (doc != null) return doc;
                return doc = GetComponent<UIDocument>();
            }
        }

        public override string Error
        {
            get
            {
                var document = Doc;
                if (binders != null)
                {
                    for (var i = 0; i < binders.Count; i++)
                    {
                        var binder = binders[i];
                        var error = binder.GetError(document);
                        if (error != null) return "ERROR at binder #" + i + ": " + error;
                    }
                }

                return null;
            }
        }

        public int BindersCount => binders?.Count ?? 0;


        //=======================================================================================================
        //                      BIND
        //=======================================================================================================

        public override void Bind()
        {
            if (!bindedOnce)
            {
                bindedOnce = true;
                FirstBind();
            }
            else
            {
                BindAll();
            }
        }

        public override void ReverseBind()
        {
            if (binders != null)
            {
                foreach (var binder in binders)
                {
                    try
                    {
                        binder.ReverseBind(Doc);
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e);
                    }
                }
            }
        }

        protected override void FirstBind()
        {
            var isRunning = (Application.isPlaying || BGUtil.TestIsRunning);
            listenersCount = 0;
            BindAll(binder =>
            {
                if (!binder.LiveUpdate || !isRunning) return;

                switch (binder)
                {
                    case BGDBUiElementBinderField field:
                    {
                        fieldListeners.Add(new BGDBA.FieldEventHandler(field.Meta.Id, field.Field.Id, field.Entity.Id, () => Bind(Doc, binder)));
                        listenersCount++;
                        break;
                    }
                    case BGDBUiElementBinderLocalized localized:
                        fieldListeners.Add(new BGDBA.FieldEventHandler(localized.Meta.Id, localized.Field.Id, localized.Entity.Id, () => Bind(Doc, binder)));
                        listenersCount++;
                        break;
                    case BGDBUiElementBinderTemplate template:
                    {
                        var templateBinder = template;
                        var fieldsInfo = templateBinder.Binder.Fields;
                        if (fieldsInfo == null || fieldsInfo.Count == 0) return;
                        foreach (var fieldInfo in fieldsInfo)
                        {
                            fieldListeners.Add(new BGDBA.FieldEventHandler(fieldInfo.MetaId, fieldInfo.FieldId, fieldInfo.EntityId, () => Bind(Doc, binder)));
                            listenersCount++;
                        }

                        break;
                    }
                }
            });

            if (listenersCount > 0)
            {
                BGRepo.OnLoad += OnLoad;
                BGRepo.I.Events.OnBatchUpdate += OnBatch;
            }
        }

        private void OnBatch(object sender, BGEventArgsBatch e) => Bind();

        private void OnLoad(bool loaded)
        {
            if (!loaded) return;
            Bind();
        }

        private void BindAll(Action<BGDBUiElementBinderA> postProcess = null)
        {
            if (binders == null) return;
            var document = Doc;
            foreach (var binder in binders)
            {
                if (Bind(document, binder)) postProcess?.Invoke(binder);
            }
        }

        private bool Bind<T>(UIDocument doc, T binder) where T : BGDBUiElementBinderA
        {
            try
            {
                if (binder is BGDBUiElementBinderGraph graphBinder) graphBinder.SetContext(gameObject, doc, listenersCount == 0);
                binder.Bind(doc);
                return true;
            }
            catch (Exception e)
            {
                Debug.LogError("BGDataBinderUiToolkitGo: Error at binder #" + binders.IndexOf(binder) + ": " + e.Message);
                Debug.LogException(e);
                return false;
            }
        }

        //=======================================================================================================
        //                      UNITY CALLBACKS
        //=======================================================================================================

        private void Reset()
        {
            binders = new List<BGDBUiElementBinderA>();
            doc = null;
            listenersCount = 0;
        }

        protected override void OnDestroy()
        {
            if (listenersCount == 0 || (!Application.isPlaying && !BGUtil.TestIsRunning)) return;

            BGRepo.OnLoad -= OnLoad;
            BGRepo.I.Events.OnBatchUpdate -= OnBatch;
            foreach (var fieldListener in fieldListeners) fieldListener.Release();
        }


        //=======================================================================================================
        //                      PUBLIC
        //=======================================================================================================
        public BGDBUiElementBinderA Find(Predicate<BGDBUiElementBinderA> filter)
        {
            for (var i = 0; i < binders.Count; i++)
            {
                var binder = binders[i];
                if (filter == null || filter(binder)) return binder;
            }

            return null;
        }

        public void Add(BGDBUiElementBinderA binder)
        {
            if (binders.Contains(binder)) return;
            binders.Add(binder);
        }

        public void Remove(BGDBUiElementBinderA binder) => binders.RemoveAll(b => b == binder);

        public List<BGDBUiElementBinderA> GetBinders(string elementName)
        {
            var result = new List<BGDBUiElementBinderA>();
            foreach (var binder in binders)
            {
                if (binder.ElementName != elementName) continue;
                result.Add(binder);
            }

            return result;
        }

        //=======================================================================================================
        //                      PRIVATE
        //=======================================================================================================
        /*
        private void AddToDictionary(BGDBUiElementBinderA binder)
        {
            if (string.IsNullOrEmpty(binder.ElementName)) return;
            name2Binder.TryGetValue(binder.ElementName, out var list);
            if (list == null)
            {
                list = new List<BGDBUiElementBinderA>();
                name2Binder[binder.ElementName] = list;
            }

            list.Add(binder);
            binder.OnNameChange += OnBinderNameChange;
        }

        private void RemoveFromDictionary(BGDBUiElementBinderA binder, string binderName = null)
        {
            var binderElementName = binderName ?? binder.ElementName;
            if (string.IsNullOrEmpty(binderElementName)) return;
            name2Binder.TryGetValue(binderElementName, out var list);
            if (list == null) return;
            if (list.RemoveAll(b => b == binder) > 0) binder.OnNameChange -= RemoveFromDictionary;
        }

        private void OnBinderNameChange(BGDBUiElementBinderA binder, string oldName)
        {
            if (name2Binder == null) return;

            //remove by old name
            if (!string.IsNullOrEmpty(oldName)) RemoveFromDictionary(binder, oldName);

            //add by new name
            if (!string.IsNullOrEmpty(binder.ElementName)) AddToDictionary(binder);
        }
    */
    }
}