﻿using AsmodeeDigital.Common.Plugin.Utils;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace AsmodeeDigital.Common.Plugin.Manager.Event
{
    /// <summary>
    /// Manage .Net events on an Unity game
    /// </summary>
    public class EventManager : MonoBehaviour, IEventManager
    {
        /// <summary>
        /// Singleton
        /// </summary>
        public static IEventManager Instance;

        private void OnEnable()
        {
            if (Instance == null)
                Instance = this;
        }

        /// <summary>
        /// Add an event to the queue
        /// </summary>
        /// <param name="action">Event to raise</param>
        public void QueueEvent(Action action)
        {
            lock (_queueLock)
            {
                _queuedEvents.Add(new Tuple<Delegate, object>(action, null));
            }
        }

#if UNITY_IOS
        /// <summary>
        /// Fake method for mono AOT Compiler used for iOS.
        /// Prevent the "Attempting to JIT compile method [..]" error
        /// More informations here : https://docs.unity3d.com/Manual/TroubleShootingIPhone.html
        /// </summary>
        private void DummyMethodToHelpOATCompiler()
        {
            Action<int> action = null;
            QueueEvent(action, default(int));

            PlayReal.Plugin.Domain.Players.IPlayerSeat playerSeat = new PlayReal.Plugin.Domain.Players.LocalPlayerSeat();
            com.daysofwonder.async.PlayerTimeoutRequest.PlayerStatus playerStatus = new com.daysofwonder.async.PlayerTimeoutRequest.PlayerStatus();

            Action<PlayReal.Plugin.Domain.Players.IPlayerSeat, com.daysofwonder.async.PlayerTimeoutRequest.PlayerStatus> action2 = null;

           QueueEvent(action2, playerSeat, playerStatus);
        }
#endif
        /// <summary>
        /// Add a one-parameterized event to the queue
        /// </summary>
        /// <typeparam name="T">Type of the parameter</typeparam>
        /// <param name="action">Event to raise</param>
        /// <param name="parameter">Parameter of the event</param>
        public void QueueEvent<T>(Action<T> action, T parameter)
        {
            lock (_queueLock)
            {
                _queuedEvents.Add(new Tuple<Delegate, object>(action, parameter));
            }
        }

        /// <summary>
        /// Add a two-parameterized event to the queue
        /// </summary>
        /// <typeparam name="T1">Type of the first parameter</typeparam>
        /// <typeparam name="T2">Type of the second parameter</typeparam>
        /// <param name="action">Event to raise</param>
        /// <param name="parameter1">First parameter of the event</param>
        /// <param name="parameter2">Second parameter of the event</param>
        public void QueueEvent<T1, T2>(Action<T1, T2> action, T1 parameter1, T2 parameter2)
        {
            lock (_queueLock)
            {
                _queuedEvents.Add(new Tuple<Delegate, object>(action, new object[] { parameter1, parameter2 }));
            }
        }

        /// <summary>
        /// Called each frame by Unity to raise queued events
        /// </summary>
        void Update()
        {
            MoveQueuedEventsToExecuting();

            while (_executingEvents.Count > 0)
            {
                Tuple<Delegate, object> e = _executingEvents[0];

                _executingEvents.RemoveAt(0);

                try
                {
                    if (e.Item2 != null && e.Item2 is Array)
                    {
                        Array array = e.Item2 as Array;
                        if (array.Length == 2)
                            e.Item1.DynamicInvoke(array.GetValue(0), array.GetValue(1));
                        else if (array.Length == 3)
                            e.Item1.DynamicInvoke(array.GetValue(0), array.GetValue(1), array.GetValue(2));
                    }
                    else if (e.Item2 != null)
                        e.Item1.DynamicInvoke(e.Item2);
                    else
                        e.Item1.DynamicInvoke();
                }
                catch
                {
                    Debug.Log(e.Item1.Method.Name);
                    throw;
                }
            }
        }

        /// <summary>
        /// Move events from queue to executing
        /// </summary>
        private void MoveQueuedEventsToExecuting()
        {
            lock (_queueLock)
            {
                while (_queuedEvents.Count > 0)
                {
                    Tuple<Delegate, object> e = _queuedEvents[0];
                    _executingEvents.Add(e);
                    _queuedEvents.RemoveAt(0);
                }
            }
        }

        /// <summary>
        /// Lock
        /// </summary>
        private System.Object _queueLock = new System.Object();

        /// <summary>
        /// Queue of events
        /// </summary>
        private List<Tuple<Delegate, object>> _queuedEvents = new List<Tuple<Delegate, object>>();

        /// <summary>
        /// List of events currently raised
        /// </summary>
        private List<Tuple<Delegate, object>> _executingEvents = new List<Tuple<Delegate, object>>();
    }
}