using Godot;
using System;
using System.Collections.Generic;
using System.Net;

public partial class GameManager : Node
{
	public static GameManager Instance { get; private set; }

	#region Signals

	// Res
	public Dictionary<string, int> Res { get; private set; } = new();
	[Signal] public delegate void ResModifiedEventHandler(string key, int newValue);
	[Signal] public delegate void ResAddedEventHandler(string key, int newValue);
	public Dictionary<string, string> ResDescription { get; private set; } = new();
	[Signal] public delegate void ResDescriptionModifiedEventHandler(string key, string newValue);

	// Sim Res
	public Dictionary<string, int> SimRes { get; private set; } = new();
	[Signal] public delegate void SimResModifiedEventHandler(string key, int newValue);
	[Signal] public delegate void SimResAddedEventHandler(string key, int newValue);
	[Signal] public delegate void SimActionEventHandler(TurnAction turnAction);
	[Signal] public delegate void UnSimActionEventHandler(TurnAction turnAction);

	// Actions
	public List<TurnAction> ActionQueue { get; private set; } = new();
	public List<TurnAction> ActionHistory { get; private set; } = new();
	public List<int> UsedIDs { get; private set; } = new();
	private readonly Random rnd = new();
	[Signal] public delegate void ActionQueuedEventHandler(TurnAction turnAction);
	[Signal] public delegate void ActionDequeuedEventHandler(TurnAction turnAction);
	[Signal] public delegate void ActionAppliedEventHandler(TurnAction turnAction);
	[Signal] public delegate void InvalidResActionQueuedEventHandler(TurnAction turnAction, string invalidRes, int invalidAmount);

	// Turn Phases
	[Signal] public delegate void AllActionsAppliedEventHandler(); // == turn finished

	[Signal] public delegate void ButtonQueuedEventHandler(InteractionButtonResource res, Node reference);
	[Signal] public delegate void ButtonDeQueuedEventHandler(Node reference);

	// Flags
	public Dictionary<string, bool> Flags { get; private set; } = new(); // flag not there == false
	[Signal] public delegate void FlagModifiedEventHandler(string key, bool newValue);
	[Signal] public delegate void FlagAddedEventHandler(string key, bool newValue);
	[Signal] public delegate void FlagClearedEventHandler(string key, bool newValue);

	// Object Interaction
	[Signal] public delegate void InteractedEventHandler(InteractableObject interactableObject, string id, string title, InteractionContentResource content);
	[Signal] public delegate void ClearGUISignalEventHandler();

	// other
	public int TurnCounter { get; private set; } = 1;
	[Signal] public delegate void TurnCounterModifiedEventHandler(int newTurnCounter);
	public int MaxThreads { get; private set; } = 0;
	[Signal] public delegate void MaxThreadsModifiedEventHandler(int newMaxThreads);
	[Signal] public delegate void GameLostEventHandler();
	[Signal] public delegate void ShowMonologEventHandler(string monolog);

	#endregion

	#region NewSignals

	//NEW STUFF
	public Dictionary<string, GameResource> newRes { get; private set; } = new();
	private string path = "res://resources/game_resources/";
	private int actionCounter = 0;
	public Dictionary<int, Action> newQueue { get; private set; } = new();
	[Signal] public delegate void NewResModifiedEventHandler(GameResource res);
	[Signal] public delegate void NewResAddedEventHandler(GameResource res);
	[Signal] public delegate void InvalidQueueEventHandler(string key);
	[Signal] public delegate void QueuedEventHandler(string str, Node reference);
	[Signal] public delegate void DeQueuedEventHandler(Node reference);
	//END NEW STUFF ---------

	#endregion

	public override void _Ready()
	{
		// prevent duplicates
		if (Instance != null && Instance != this)
		{
			QueueFree();
		}
		else
		{
			Instance = this;
		}

		// set initial Res
		Reset();
	}

	public void Reset()
	{
		MaxThreads = 1;
		TurnCounter = 1;
		actionCounter = 0;
		EmitSignal(SignalName.TurnCounterModified, TurnCounter);

		//Clear containers
		Res.Clear();
		newRes.Clear();
		newQueue.Clear();
		ResDescription.Clear();
		SimRes.Clear();
		ActionQueue.Clear();
		ActionHistory.Clear();
		UsedIDs.Clear();
		Flags.Clear();

		EmitSignal(SignalName.AllActionsApplied);
	}

	#region New

	public int NewGetRes(string id)
	{
		return newRes.TryGetValue(id, out GameResource res) ? res.value : 0;
	}

	public int NewGetResMax(string id)
	{
		return newRes.TryGetValue(id, out GameResource res) ? res.max : 0;
	}

	public void NewSetRes(string id, int val)
	{
		GD.Print("Called NewSetRes on res [" + id + "]");
		if (!newRes.ContainsKey(id))
		{
			GD.Print("Looking at path: " + path + id + ".tres");
			GameResource res = ResourceLoader.Load<GameResource>(path + id + ".tres");
			newRes.Add(id, res);
			EmitSignal(SignalName.NewResAdded, res);
		}

		newRes[id].SetValue(val);
		EmitSignal(SignalName.NewResModified, newRes[id]);
	}

	public void NewModifyRes(string id, int delta)
	{
		GD.Print("Called NewModifyRes on res [" + id + "]");
		if (!newRes.ContainsKey(id))
		{
			GameResource res = ResourceLoader.Load<GameResource>(path + id + ".tres");
			newRes.Add(id, res);
			EmitSignal(SignalName.NewResAdded, res);
		}

		newRes[id].ModValue(delta);
		GD.Print("After delta [" + newRes[id].value.ToString() + "," + newRes[id].proxy.ToString() + "," + newRes[id].max.ToString() + "]");
		EmitSignal(SignalName.NewResModified, newRes[id]);
	}

	public void NewSetResMax(string id, int val)
	{
		if (!newRes.ContainsKey(id))
		{
			GameResource res = ResourceLoader.Load<GameResource>(path + id + ".tres");
			newRes.Add(id, res);
			EmitSignal(SignalName.NewResAdded, res);
		}

		newRes[id].SetMax(val);
		EmitSignal(SignalName.NewResModified, newRes[id]);
	}

	public void NewModifyResMax(string id, int delta)
	{
		if (!newRes.ContainsKey(id))
		{
			GameResource res = ResourceLoader.Load<GameResource>(path + id + ".tres");
			newRes.Add(id, res);
			EmitSignal(SignalName.NewResAdded, res);
		}

		newRes[id].ModMax(delta);
		EmitSignal(SignalName.NewResModified, newRes[id]);
	}

	public void NewPayRes(string id, int cost)
	{
		if (newRes.ContainsKey(id))
		{
			newRes[id].ModProxy(cost);
			EmitSignal(SignalName.NewResModified, newRes[id]);
		}
	}

	public void NewRefundRes(string id, int cost)
	{
		if (newRes.ContainsKey(id))
		{
			newRes[id].ModProxy(-cost);
			EmitSignal(SignalName.NewResModified, newRes[id]);
		}
	}

	public bool TestResCost(string id, int cost)
	{
		foreach ((string _id, GameResource res) in newRes) {
			GD.Print("[id:" + _id + "| Title: " + res.title + "| Value: " + res.value.ToString() + "]");
		}
		GD.Print("Checking contain Key on res: " + id);
		if (newRes.ContainsKey(id))
		{
			GD.Print("Executing TestCost on res: " + id);
			return newRes[id].TestCost(cost);
		}
		return false;
	}

	public void AlertInvalidQueue(string id)
	{
		GD.Print("Emitting Invalid Cost signal on resource " + id);
		EmitSignal(SignalName.InvalidQueue);
		//EmitSignal stuff
	}

	public void AlertQueued(string name, Node @ref)
	{
		EmitSignal(SignalName.Queued, name, @ref);
	}

	public void AlertDeQueued(Node @ref)
	{
		EmitSignal(SignalName.DeQueued, @ref);
	}

	public int NewQueueAction(Action act)
	{
		actionCounter++;
		newQueue.Add(actionCounter, act);

		return actionCounter;
	}

	public bool NewDequeueAction(int id)
	{
		bool dequeuedAction = false;

		if (newQueue.ContainsKey(id)) {
			dequeuedAction = true;
			newQueue.Remove(id);
		}

		return dequeuedAction;
	}

	public void NewEndTurn()
	{
		GD.Print("EndTurn called at turn [" + TurnCounter.ToString() + "]");
		//POST PLAYER ACTIONS
		Excecute();
		Cleanup();
		NewCheckLooseGame(); //Termination Check on flowchart
		EndStep();


		//PRE PLAYER ACTIONS
		// ---Turn Start---
		TurnCounter++;
		EmitSignal(SignalName.TurnCounterModified, TurnCounter);

		// ---Upkeep---
		Upkeep(); // TODO maybe call before first turn

		// ---Scene Setup---
		SceneSetup(); // TODO maybe call before first turn
	}

	public void NewCheckLooseGame() // was AfterAllActionsAppliedBeforeNextTurn() before
	{
		if (NewGetRes("thrown_exceptions") == NewGetResMax("thrown_exceptions"))
						LooseGame();
		if (GetRes("Engine Fuel") == 0 && GetFlag("active_Generator"))
						LooseGame();
	}

	private void Excecute()
	{
		//pay all costs
		foreach ((string id, GameResource res) in newRes)
		{
			res.Pay();
		}
		// execute actions
		foreach ((int id, Action act) in newQueue)
		{
			act?.Invoke();
		}
		//Compatibility
	}

	private void Cleanup()
	{
		ActionQueue.Clear();
		newQueue.Clear();
		actionCounter = 0;

		{
			//After reconnecting to the other signals "AllActionsApplied" will no longer be needed
			GD.Print("Emitting Signal All actions Applied for turn [" + TurnCounter.ToString() + "]");
			EmitSignal(SignalName.AllActionsApplied);

			SimRes = new Dictionary<string, int>(Res);

			//SUBSTITUTE THIS WITH JUST A "Threads" int, the res are now their own form and not just an int but we don't need the whole thing for threads
			Res["Threads"] = MaxThreads;
		}
		//SEND SIGNAL "Cleanup" or "CleanupStep"
	}

	private void EndStep()
	{
		// ---Play Events---
		// SEND SIGNAL "EndStep"
	}

	public void Upkeep()
	{
		// This is where turn over turn effects happen, resource resetting, flags checking, invoking dialogues ecc...
		// SEND SIGNAL "Upkeep" that Node "StateBasedActions" can then connect to
	}

	public void SceneSetup()
	{
		// Here is where the visibility check for each node is done to setup how the scene will look this turn
		// SEND SIGNAL "SceneSetup" that every Node "VisibilityControll" can connect to
	}

	#endregion

	#region Res

	// getters and setters for Res
	public int GetRes(string key)
	{
		return Res.TryGetValue(key, out int value) ? value : 0;
	}

	public void QueueButton(InteractionButtonResource res, Node @ref)
	{
		EmitSignal(SignalName.ButtonQueued, res, @ref);
	}

	public void DeQueueButton(Node @ref)
	{
		EmitSignal(SignalName.ButtonDeQueued, @ref);
	}

	public void SetRes(string key, int value)
	{
		if (!Res.TryAdd(key, value))
		{
			if (Res[key] != value)
			{
				Res[key] = value;
				EmitSignal(SignalName.ResModified, key, value);
			}
		}
		else
		{
			EmitSignal(SignalName.ResAdded, key, value);
			if (value != 0)
			{
				EmitSignal(SignalName.ResModified, key, value);
			}
		}
	}

	public void ModifyRes(string key, int delta)
	{
		if (!Res.TryAdd(key, delta))
		{
			if (delta != 0)
			{
				Res[key] += delta;
				EmitSignal(SignalName.ResModified, key, Res[key]);
			}
		}
		else
		{
			EmitSignal(SignalName.ResAdded, key, delta);
			if (delta != 0)
			{
				EmitSignal(SignalName.ResModified, key, delta);
			}
		}
	}

	public void ModifyResDescription(string key, string newValue = "")
	{
		if (!ResDescription.TryAdd(key, newValue))
		{
			if (newValue != null)
			{
				ResDescription[key] = newValue;
				EmitSignal(SignalName.ResDescriptionModified, key, newValue);
			}
		}
		else
		{
			if (newValue != null)
			{
				ResDescription[key] = newValue;
				EmitSignal(SignalName.ResDescriptionModified, key, newValue);
			}
		}
	}

	#endregion

	#region SimRes

	// getters and setters for Sim Res
	public int SimGetRes(string key)
	{
		return SimRes.TryGetValue(key, out int value) ? value : 0;
	}

	public void SimSetRes(string key, int value)
	{
		GD.Print("Calles Sim Set Res for key:" + key + "setting value: " + value.ToString());
		if (!SimRes.TryAdd(key, value))
		{
			if (SimRes[key] != value)
			{
				SimRes[key] = value;
				EmitSignal(SignalName.SimResModified, key, value);
			}
		}
		else
		{
			EmitSignal(SignalName.SimResAdded, key, value);
			if (value != 0)
			{
				EmitSignal(SignalName.SimResModified, key, value);
			}
		}
	}

	public void SimModifyRes(string key, int delta)
	{
		if (!SimRes.TryAdd(key, delta))
		{
			if (delta != 0)
			{
				SimRes[key] += delta;
				EmitSignal(SignalName.SimResModified, key, SimRes[key]);
			}
		}
		else
		{
			EmitSignal(SignalName.SimResAdded, key, delta);
			if (delta != 0)
			{
				EmitSignal(SignalName.SimResModified, key, delta);
			}
		}
	}

	#endregion

	#region Turn System

	// turn system
	public partial class TurnAction : RefCounted
	{
		public List<Action> Execute;
		public string Description;
		public int ID;
		public List<Action> SimExecute;
		public List<Action> UnSimExecute;

		public TurnAction(List<Action> execute, string description, int id, List<Action> simExecute = null, List<Action> unSimExecute = null)
		{
			Execute = execute;
			Description = description;
			ID = id;
			SimExecute = simExecute;
			UnSimExecute = unSimExecute;
		}

		public void Apply()
		{
			if (Execute == null || Execute.Count <= 0) { return; }
			foreach (Action action in Execute)
			{
				action?.Invoke();
			}
			//Execute?.Invoke();
		}

		public void SimApply()
		{
			if (SimExecute == null || SimExecute.Count <= 0) { return; }
			foreach (Action action in SimExecute)
			{
				action?.Invoke();
			}
			//SimExecute?.Invoke();
		}

		public void UnSimApply()
		{
			if (UnSimExecute == null || UnSimExecute.Count <= 0) { return; }
			foreach (Action action in UnSimExecute)
			{
				GD.Print("Calling UnSimExecute");
				action?.Invoke();
			}
			//UnSimExecute?.Invoke();
		}
	}

	// queues action and returns id used for reference (also applies Sim if provided)
	public int QueueAction(List<Action> execute, string description = "", int id = -1, List<Action> simExecute = null, List<Action> unSimExecute = null)
	{
		// create random id if id was not provided
		if (id == -1)
		{
			id = rnd.Next();
			while (UsedIDs.Contains(id))
			{
				id = rnd.Next();
			}
		}

		// queue action
		UsedIDs.Add(id);
		TurnAction turnAction = new TurnAction(execute, description, id, simExecute, unSimExecute);
		ActionQueue.Add(turnAction);
		EmitSignal(SignalName.ActionQueued, turnAction);

		// Sim
		turnAction.SimApply();
		EmitSignal(SignalName.SimAction, turnAction);

		// test if action was queued right or would result in negative res
		if (simExecute != null && unSimExecute != null) // can only check if sim methods are provided
		{
			bool isSomeResInvalid = false;
			foreach (string res in SimRes.Keys)
			{
				if (SimRes[res] < 0)
				{
					isSomeResInvalid = true;
					// send signal for every invalid res
					EmitSignal(SignalName.InvalidResActionQueued, turnAction, res, SimRes[res]);
					break;
				}
			}
			// undo queueing action if one res ends up invalid
			if (isSomeResInvalid)
			{
				ActionQueue.Remove(turnAction);
				EmitSignal(SignalName.ActionDequeued, turnAction);

				turnAction.UnSimApply();
				EmitSignal(SignalName.UnSimAction, turnAction);

			}
		}

		return id;
	}

	// dequeues action and returns if at least one action was removed (also applies UnSim if provided)
	public bool DequeueAction(int id)
	{
		bool dequeuedAction = false;

		int index = 0;
		foreach (TurnAction turnAction in ActionQueue)
		{
			if (turnAction.ID == id)
			{
				dequeuedAction = true;
				break;
			}
			index++;
		}

		if (dequeuedAction)
		{
			TurnAction tmp = ActionQueue[index];
			ActionQueue.RemoveAt(index);
			EmitSignal(SignalName.ActionDequeued, tmp);

			// UnSim
			tmp.UnSimApply();
			EmitSignal(SignalName.UnSimAction, tmp);
		}

		return dequeuedAction;
	}

	public void EndTurn()
	{
		GD.Print("EndTurn called at turn [" + TurnCounter.ToString() + "]");

		// increase turn counter
		TurnCounter++;
		EmitSignal(SignalName.TurnCounterModified, TurnCounter);

		// carry out turn actions
		foreach (TurnAction turnAction in ActionQueue)
		{
			turnAction.Apply();
			ActionHistory.Add(turnAction);
			EmitSignal(SignalName.ActionApplied, turnAction);
		}

		foreach ((int id, Action act) in newQueue)
		{
			act?.Invoke();
		}

		// call turn effects
		AfterAllActionsAppliedBeforeNextTurn();

		// clear turn action queue
		ActionQueue.Clear();
		newQueue.Clear();
		GD.Print("Emitting Signal All actions Applied for turn [" + TurnCounter.ToString() + "]");
		EmitSignal(SignalName.AllActionsApplied);

		// clear Sim
		SimRes = new Dictionary<string, int>(Res);

		// reset threads
		Res["Threads"] = MaxThreads;
	}

	#endregion

	#region Flags

	// getters and setters for Flags
	public bool GetFlag(string key)
	{
		return Flags.TryGetValue(key, out bool value) && value;
	}

	public bool HasFlag(string key)
	{
		return Flags.ContainsKey(key);
	}

	public void SetFlag(string key, bool value = true)
	{
		if (!Flags.TryAdd(key, value))
		{
			if (Flags[key] != value)
			{
				Flags[key] = value;
				EmitSignal(SignalName.FlagModified, key, value);
			}
		}
		else
		{
			EmitSignal(SignalName.FlagAdded, key, value);
			if (value)
			{
				EmitSignal(SignalName.FlagModified, key, true);
			}
		}
	}

	public bool ClearFlag(string key)
	{
		if (Flags.Remove(key, out bool value))
		{
			EmitSignal(SignalName.FlagCleared, key, value);
			if (value)
			{
				EmitSignal(SignalName.FlagModified, key, true);
			}

			return value;
		}

		return false;
	}

	#endregion

	#region Object Interaction

	public void EmitInteractedSignal(InteractableObject interactableObject, string id, string title, InteractionContentResource content)
	{
		EmitSignal(SignalName.Interacted, interactableObject, id, title, content);
	}

	public void EmitClearGUISignal()
	{
		EmitSignal(SignalName.ClearGUISignal);
	}

	#endregion

	#region Other

	public void SetMaxThreads(int newMaxThreads)
	{
		if (MaxThreads != newMaxThreads)
		{
			MaxThreads = newMaxThreads;
			EmitSignal(SignalName.MaxThreadsModified, MaxThreads);
		}
	}

	public void ModifyMaxThreads(int delta)
	{
		if (delta != 0)
		{
			MaxThreads += delta;
			EmitSignal(SignalName.MaxThreadsModified, MaxThreads);
		}
	}

	public void LooseGame()
	{
		EmitSignal(SignalName.GameLost);
		GD.Print("Game Lost");
	}

	public void AfterAllActionsAppliedBeforeNextTurn()
	{
		
		//EmitSignal(SignalName.ShowMonolog, "you will loose soon"); // listen to this on ui

		if (NewGetRes("thrown_exceptions") == NewGetResMax("thrown_exceptions"))
						LooseGame();
		if (GetRes("Engine Fuel") == 0 && GetFlag("active_Generator"))
						LooseGame();
	}

	#endregion
}
