using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;

namespace Shared.ScriptableVariables {
  // Component to tie GameEvents to Unity's event system
  public class GameEventListener : MonoBehaviour {
    [Tooltip("The game event to listen to")]
    public GameEvent eventToListenTo;

    [Tooltip("The UnityEvent to raise in response to the game event being raised")]
    public UnityEvent response;

    [Tooltip("The delay to have before firing the Unity Events")]
    public float delay = 0.0f;

    //---------------------------------------------------------------------------
    void OnEnable() {
      eventToListenTo.OnRaised += OnEventRaised;
    }

    //---------------------------------------------------------------------------
    void OnDisable() {
      eventToListenTo.OnRaised -= OnEventRaised;
    }

    //---------------------------------------------------------------------------
    public virtual void OnEventRaised() {
      // If there isn't a delay (or it's set wrong), just fire the Unity Event right away
      if (delay <= 0.0f) {
        InvokeUnityEvent();
      }
      // Otherwise, wait for the delay and THEN fire the Unity Event
      else {
        StartCoroutine(DelayInvokeUnityEvent());
      }
    }

    //---------------------------------------------------------------------------
    private IEnumerator DelayInvokeUnityEvent() {
      yield return new WaitForSeconds(delay);
      InvokeUnityEvent();
    }

    //---------------------------------------------------------------------------
    private void InvokeUnityEvent() {
      if (response != null) {
        try {
          response.Invoke();
        }
        catch (Exception exception) {
          Debug.LogError($"{gameObject.name} is throwing the following exception:");
          Debug.LogException(exception);
        }
      }
    }
  }

  //---------------------------------------------------------------------------
  public abstract class GameEventListener<T> : MonoBehaviour {
    [Tooltip("The delay to have before firing the Unity Events")]
    public float delay = 0.0f;

    //---------------------------------------------------------------------------
    protected abstract GameEvent<T> GetGameEvent();

    //---------------------------------------------------------------------------
    protected abstract UnityEvent<T> GetUnityEvent();

    //---------------------------------------------------------------------------
    void OnEnable() {
      GetGameEvent().OnRaised += OnEventRaised;
    }

    //---------------------------------------------------------------------------
    void OnDisable() {
      GetGameEvent().OnRaised -= OnEventRaised;
    }

    //---------------------------------------------------------------------------
    public virtual void OnEventRaised(T value) {
      // If there isn't a delay (or it's set wrong), just fire the Unity Event right away
      if (delay <= 0.0f) {
        InvokeUnityEvent(value);
      }
      // Otherwise, wait for the delay and THEN fire the Unity Event
      else {
        StartCoroutine(DelayInvokeUnityEvent(value));
      }
    }

    //---------------------------------------------------------------------------
    private IEnumerator DelayInvokeUnityEvent(T value) {
      yield return new WaitForSeconds(delay);
      InvokeUnityEvent(value);
    }

    //---------------------------------------------------------------------------
    private void InvokeUnityEvent(T value) {
      var unityEvent = GetUnityEvent();
      if (unityEvent != null) {
        try {
          unityEvent.Invoke(value);
        }
        catch (Exception exception) {
          Debug.LogError($"{gameObject.name} is throwing the following exception:");
          Debug.LogException(exception);
        }
      }
    }
  }
}