using System;
using System.Collections;
using System.Collections.Generic;
using com.deeperbeige.lib.runtime;
using UnityEngine;

namespace com.deeperbeige.lib
{
	public static class Events
	{
		// The following events are all fired automatically, once you've initialised this class.
		// This persists across scene changes automatically too.
		// Initialise by simply using one of the static methods. Eg, listen for an event.
		//
		// It is up to you whether to listen for a payload in an event. Even if an event is
		// firing with a payload, it is entirely up to you whether to receive it or not.

		// Frame events
		public const string EVENT_UPDATE            = "events_update";               // Triggered every monobehaviour update phase. (int)payload=frame_number
		public const string EVENT_FIXED_UPDATE      = "events_fixed_update";         // Triggered once per fixed-update phase.      (int)payload=frame_number
		public const string EVENT_LATE_UPDATE       = "events_late_update";          // Triggered during late-update phase.         (int)payload=frame_number

		// Scene changes
		public const string EVENT_SCENE_CHANGED     = "events_scene_changed";        // Triggered after a scene change
		
		// Resize events
		public const string EVENT_RESIZE            = "resize";                      // Triggered when the stage size changes
		public const string EVENT_RESIZE_LATE       = "resize_late";                 // Triggered in the late-update after screen is resized
		public const string EVENT_RESIZE_NEXT_FRAME = "resize_next_frame";           // Triggered on the frame after the resize - good for NGUI auto layout style things
		
		// App events
		public const string EVENT_APP_PAUSE         = "events_application_pause";    // App paused
		public const string EVENT_APP_RESUME        = "events_application_resume";   // App resumed from pause
		public const string EVENT_APP_FOCUS         = "events_application_focus";    // App brought back from background
		public const string EVENT_APP_DEFOCUS       = "events_application_defocus";  // App has been backgrounded
		public const string EVENT_APP_QUIT          = "events_application_quit";     // App quit (not reliable on iOS, will often suspend instead)
		
		// UI events
		public const string EVENT_UI_BACK           = "events_ui_back";              // Android back button
		public const string EVENT_UI_DOUBLE_BACK    = "events_ui_double_back";       // Android back button tapped twice quickly
		public const string EVENT_UI_SETTINGS       = "events_ui_settings";          // Android settings button
		
		// Variables
		private static bool                                  initialised = false;
		private static Dictionary<string, List<EventAction>> actions;
		private static GameObject                            runtime;
		private static EventsComponent                       component;

		// EventAction class to hold data about each event
		class EventAction
		{
			public Action         action;
			public Action<object> payloadAction;
			public bool           autoCleanup;

			public EventAction(Action action, Action<object> payloadAction, bool autoCleanup)
			{
				this.action        = action;
				this.payloadAction = payloadAction;
				this.autoCleanup   = autoCleanup;
			}
		}

		// Called automatically from public methods
		private static void Init()
		{
			actions = new Dictionary<string, List<EventAction>>();
			initialised = true;

			Listen(Events.EVENT_SCENE_CHANGED, EventsOnSceneChanged, false);
			EventsOnSceneChanged();
			component.Init();
		}

		// Reconnect to new listener on scene change
		// Can be called twice in the same scene without harm
		private static void EventsOnSceneChanged()
		{
			if (! initialised) Init ();

			runtime = GameObjects.FindAnywhere("EventsRuntime", true);
			if (runtime == null) runtime = new GameObject("EventsRuntime");

			component = runtime.GetComponent<EventsComponent>();
			if (component == null) component = runtime.AddComponent<EventsComponent>();
			
			component.ForceResizeEvent();

			UnityEngine.Object.DontDestroyOnLoad(runtime);
		}

		// Add event listener with optional payload for handler
		// The autoCleanup flag indicates whether this listener should be automatically
		// cleaned up by the ForgetAll methods. This is good for persisting events across
		// scene changes when using the Transition class to help clean up events
		public  static void Listen(string eventType, Action handler, bool autoCleanup)         { Listen(eventType, handler, null, autoCleanup); }
		public  static void Listen(string eventType, Action<object> handler, bool autoCleanup) { Listen(eventType, null, handler, autoCleanup); }
		private static void Listen(string eventType, Action handler, Action<object> payloadHandler, bool autoCleanup)
		{
			if (! initialised) Init ();
			if (! actions.ContainsKey(eventType)) actions.Add(eventType, new List<EventAction>());

			List<EventAction> eventActions = actions[eventType];
			Forget(eventType, handler);
			eventActions.Add(new EventAction(handler, payloadHandler, autoCleanup));
		}

		// Remove specific event listener (ignores auto cleanup flag)
		public  static void Forget(string eventType, Action handler)         { Forget(eventType, handler, null); }
		public  static void Forget(string eventType, Action<object> handler) { Forget(eventType, null, handler); }
		private static void Forget(string eventType, Action handler, Action<object> payloadHandler)
		{
			if (! initialised) Init ();
			if (! actions.ContainsKey(eventType)) return;
			
			List<EventAction> eventActions = actions[eventType];
			for (int j=0; j < eventActions.Count; j++)
			{
				if (eventActions[j].action != handler) continue;

				eventActions.RemoveAt(j);
				j--;
			}
		}

		// Remove all auto-cleanup listeners for a given event type
		public static void ForgetAll(string eventType)
		{
			if (! initialised) Init ();
			if (! actions.ContainsKey(eventType)) return;

			List<EventAction> eventActions = actions[eventType];
			for (int j=0; j < eventActions.Count; j++)
			{
				if (! eventActions[j].autoCleanup) continue;

				eventActions.RemoveAt(j);
				j--;
			}
		}

		// Removes all auto-cleanup listeners (eg, when changing scene)
		public static void ForgetAll()
		{
			foreach (string eventType in actions.Keys) ForgetAll(eventType);
		}

		// Remove all listeners for a given event type, including those not marked for auto cleanup
		public static void ForceForgetAll(string eventType)
		{
			if (! initialised) Init ();
			if (! actions.ContainsKey(eventType)) return;
			actions[eventType] = new List<EventAction>();
		}

		// Removes all events entirely, including those not marked for auto cleanup
		public static void ForceForgetAll()
		{
			Init();
		}

		// Send event to registered listeners
		public static void Send(string eventType) { Send(eventType, null); }
		public static void Send(string eventType, object payload)
		{
			//DevPanel.Log ("Sent: " + eventType);

			if (! initialised) Init ();
			if (! actions.ContainsKey(eventType)) return;

			List<EventAction> eventActions = actions[eventType];
			foreach (EventAction action in eventActions)
			{
				if (action.action        != null) action.action();
				if (action.payloadAction != null) action.payloadAction(payload);
			}
		}
		
		// List out events currently in use
		// Handy for debugging
		// Pass true for more detail
		public static void Report() { Report(false); }
		public static void Report(bool verbose)
		{
			if (! initialised) Init ();
			
			int count = 0;
			foreach (string eventType in actions.Keys)
			{
				if (actions[eventType] == null) continue;
				count += actions[eventType].Count;
			}
			
			DevPanel.Log(count + " active listeners:");
			foreach (string eventType in actions.Keys)
			{
				List<EventAction> eventActions = actions[eventType];
				
				if (eventActions == null)    continue;
				if (eventActions.Count == 0) continue;
				
				DevPanel.Log("  " + eventActions.Count + "x " + eventType);
				if (! verbose) continue;
				
				foreach (EventAction eventAction in eventActions)
				{
					string msg = "      ";
					if (eventAction.action != null)
					{
						if (eventAction.action.Target != null)
						{
							msg += "(" + eventAction.action.Target.ToString() + ").";
						} else {
							msg += "(static/anonymous).";
						}
						
						if (eventAction.action.Method != null) msg += eventAction.action.Method.Name + "() ";
						if (eventAction.autoCleanup) msg += " [autoCleanup]";
					    DevPanel.Log(msg);
					}
					
					if (eventAction.payloadAction != null)
					{
						if (eventAction.payloadAction.Target != null)
						{
							msg += "(" + eventAction.payloadAction.Target.ToString() + ").";
						} else {
							msg += "(static/anonymous).";
						}
						
						if (eventAction.payloadAction.Method != null) msg += eventAction.payloadAction.Method.Name + "(object) ";
						if (eventAction.autoCleanup) msg += " [autoCleanup]";
						DevPanel.Log(msg);
					}
				}
			}
		}
	}
}

