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

public partial class InteractableObject : StaticBody3D
{
	[Export] public InteractableObjectResource Data { get; set; }
	public List<int> QueuedActionsFromThisInteractable { get; private set; } = new();
	public List<int> NewQueuedActionsIDs { get; private set; } = new();

	// Child Nodes
	private Node3D pivot;
	private Camera3D cam;
	private MeshInstance3D mesh;
	private ObjectInteractionGUIManager ui;

	private bool isQueued = false;
	private BoxShape3D box;

	public override void _Ready()
	{
		if (Data == null) { GD.PrintErr($"{Name} needs interaction data resource!"); return; }
		pivot = GetNode<Node3D>("Pivot");
		mesh = GetNode<MeshInstance3D>("MeshInstance3D");
		ui = GetNode<ObjectInteractionGUIManager>("ObjectInteractionGUIManager");
		cam = GetViewport().GetCamera3D();

		MouseEntered += ui.Expand;
		MouseExited += ui.Minimize;

		ui.setProperties(Data);

		// content stuff
		//UpdateCurrentContent(); // update content on game load to initial one
		GameManager.Instance.AllActionsApplied += TurnUpkeep; // update content on turn end
		GameManager.Instance.InvalidResActionQueued += forcedDequeue;
		 // remove other actions from this interactable that have been queued together with an invalid one
		//GameManager.Instance.InvalidResActionQueued += (GameManager.TurnAction turnAction, string invalidRes, int invalidAmount) => dequeueElement();
	}

	public override void _ExitTree()
	{
		GameManager.Instance.AllActionsApplied -= TurnUpkeep; // update content on turn end
		GameManager.Instance.InvalidResActionQueued -= forcedDequeue;
	}


	public void HideUI()
	{
		ui.Disable();
	}

	public void ShowUI()
	{
		ui.Enable();
	}

	public override void _Process(double delta)
	{
		Vector3 g_pos = pivot.GlobalPosition;
		ui.setDetectionBoxSize(CalcBBProjectedSize());
		ui.setScreenPosition(g_pos, cam);
	}

	private Vector2 CalcBBProjectedSize()
	{
		Aabb bb = Transform * mesh.Mesh.GetAabb();
		return maxmin(bb.Position, bb.End, bb.Size);
	}

	private Vector2 maxmin(Vector3 p1, Vector3 p2, Vector3 s)
	{
		Vector2 bottom_left = cam.UnprojectPosition(p1);
		Vector2 top_right = bottom_left;
		List<Vector3> points = new List<Vector3>
			{ p1 + new Vector3(s.X, 0, 0), p1 + new Vector3(0, s.Y, 0), p1 + new Vector3(0, 0, s.Z),
			p2, p2 - new Vector3(s.X, 0, 0), p2 - new Vector3(0, s.Y, 0), p2 - new Vector3(0, 0, s.Z) };

		foreach (Vector3 vertex in points)
		{
			Vector2 point = cam.UnprojectPosition(vertex);
			for (int i = 0; i < 1; i++)
			{
				if (bottom_left.X > point.X)
					bottom_left.X = point.X;
				if (bottom_left.Y > point.Y)
					bottom_left.Y = point.Y;
				if (top_right.X < point.X)
					top_right.X = point.X;
				if (top_right.Y < point.Y)
					top_right.Y = point.Y;
			}
		}

		return top_right - bottom_left;
	}

	//INPUT HANDLING
	public override void _Input(InputEvent @event)
	{
		if (ui.isHighlighted && @event.IsActionPressed("interact"))
		{
			if (isQueued)
				dequeueElement();
			else
				queueElement();
		}
	}

	private void queueElement()
	{
		if (isQueued) { return; }
		GD.Print("Queuing Element [" + Data.Name + "::" + Data.Status + "]");

		//NEW STUFF -------------------------------------------
		//Test the costs
		foreach (InteractionActionResource cost in Data.Costs)
		{
			if (!GameManager.Instance.TestResCost(cost.Key, int.Parse(cost.Value)))
			{
				//Don't go forward if the test fails and abort queuing, while launching a signal to the UI
				GameManager.Instance.AlertInvalidQueue(cost.Key);
				return;
			}
		}

		//Apply The Costs
		foreach (InteractionActionResource cost in Data.Costs)
		{
			GD.Print("Calling PayRes on: " + cost.Key + " for " + cost.Value);
			GameManager.Instance.NewPayRes(cost.Key, int.Parse(cost.Value));
		}

		//QueueActions
		foreach (InteractionActionResource effect in Data.Effects)
		{
			NewTakeActionBuildForQueue(effect);
		}

		GameManager.Instance.AlertQueued(Data.Name, this);
		isQueued = true;
		//END OF NEW STUFF -------------------------

		//GameManager.Instance.QueueButton(CurrentContent.Buttons[0], this);

		// check if the current content is even clickable
		//if (CurrentContent.Buttons.Count == 0 || CurrentContent.Buttons[0].Actions.Count == 0)
		//{
			//return;
		//}

		
		// currently only 1 button per content
		// will always take first
		//foreach (InteractionActionResource action in CurrentContent.Buttons[0].Actions)
		//{
			//GD.Print("Queuing Action [" + i.ToString() + "] :: " + action.Key);
			//TakeActionsAndBuildThemOntoTheQueue(action);
		//}
	}

	private void dequeueElement()
	{
		if (!isQueued) { return; }
		GD.Print("Dequeuing Elements [" + Data.Name + "::" + Data.Status + "]");

		//NEW STUFF -----------------------

		//Deapply the costs
		foreach (InteractionActionResource cost in Data.Costs)
		{
			GameManager.Instance.NewRefundRes(cost.Key, int.Parse(cost.Value));
		}

		//Dequeue Actions
		foreach (int ID in NewQueuedActionsIDs)
		{
			GameManager.Instance.NewDequeueAction(ID);
		}
		NewQueuedActionsIDs.Clear();
		GameManager.Instance.AlertDeQueued(this);

		//END NEW STUFF -------------------

		// takes every id from QueuedActionsFromThisInteractable and dequques the action corresponding to it
		foreach (int id in QueuedActionsFromThisInteractable)
		{
			GameManager.Instance.DequeueAction(id);
		}

		GameManager.Instance.DeQueueButton(this);
		QueuedActionsFromThisInteractable.Clear();
		isQueued = false;
	}

	private void forcedDequeue(GameManager.TurnAction turnAction, string invalidRes, int invalidAmount)
	{
		if (!isQueued) { return; }

		GD.Print("Force Dequeuing Elements [" + Data.Name + "::" + Data.Status + "]");
		GameManager.Instance.DeQueueButton(this);
		QueuedActionsFromThisInteractable.Clear();
		isQueued = false;
	}

	public void TurnUpkeep()
	{
		//UpdateCurrentContent();
		isQueued = false;
	}

	/*
	//public void UpdateCurrentContent()
	//{
	// clear old queued action ids from old turns
	QueuedActionsFromThisInteractable = [];

		// find and set current content
		InteractionContentResource possibleContent = null;
		foreach (InteractionContentResource content in Data.Contents)
		{
			bool allConditionsMet = true;
			foreach (string flag in content.Conditions.Keys)
			{
				if (!GameManager.Instance.GetFlag(flag) != content.Conditions[flag])
				{
					allConditionsMet = false;
					break;
				}
			}

			if (allConditionsMet)
			{
				possibleContent = content;
			}
		}
		if (possibleContent == null)
		{
			GD.PrintErr("no content with current flag combination!");
			return;
		}

		CurrentContent = possibleContent;
	}
	*/

	public void NewTakeActionBuildForQueue(InteractionActionResource action)
{
	int actionID = -1;
	switch (action.Type)
	{
		case InteractionActionResource.ActionType.Default:
			GD.PrintErr($"no action type was set in resource:{Data.Name}");
			break;
		case InteractionActionResource.ActionType.SetFlag:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.SetFlag(action.Key, bool.Parse(action.Value))
			);
			break;
		case InteractionActionResource.ActionType.SetRes:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.NewSetRes(action.Key, int.Parse(action.Value))
			);
			break;
		case InteractionActionResource.ActionType.ModifyRes:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.NewModifyRes(action.Key, int.Parse(action.Value))
			);
			break;
		case InteractionActionResource.ActionType.SetResMax:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.NewSetResMax(action.Key, int.Parse(action.Value))
			);
			break;
		case InteractionActionResource.ActionType.ModifyResMax:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.NewModifyResMax(action.Key, int.Parse(action.Value))
			);
			break;
		case InteractionActionResource.ActionType.ModifyMaxThreads:
			actionID = GameManager.Instance.NewQueueAction(
				() => GameManager.Instance.ModifyMaxThreads(int.Parse(action.Value))
			);
			break;
		default:
			GD.Print("Cannot queue action of unsupported type");
			break;
	}
	if (actionID != -1)
	{
		NewQueuedActionsIDs.Add(actionID);
	}
}
	/*
	public void TakeActionsAndBuildThemOntoTheQueue(InteractionActionResource action)
	{
		// for later tracking
		int actionID = action.ID;

		// build action onto queue using action type presets
		switch (action.Type)
		{
			case InteractionActionResource.ActionType.Default:
				GD.PrintErr($"no action type was set in resource:{Data.Name}");
				break;

			case InteractionActionResource.ActionType.SetFlag:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.SetFlag(action.Key, bool.Parse(action.Value))
					},
					action.Description,
					actionID
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.SetRes:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.SetRes(action.Key, int.Parse(action.Value))
					},
					action.Description,
					actionID,
					new List<Action> {
						() => GameManager.Instance.SimSetRes(action.Key, int.Parse(action.Value))
					},
					new List<Action> {
						() => GameManager.Instance.SimSetRes(action.Key, GameManager.Instance.SimRes[action.Key])
					}
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.ModifyRes:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.ModifyRes(action.Key, int.Parse(action.Value))
					},
					action.Description,
					actionID,
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, int.Parse(action.Value))
					},
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, -int.Parse(action.Value))
					}
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.SetMaxThreads:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.SetMaxThreads(int.Parse(action.Value))
					},
					action.Description,
					actionID
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.ModifyMaxThreads:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.ModifyMaxThreads(int.Parse(action.Value))
					},
					action.Description,
					actionID
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.SetResUsingThread:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.SetRes(action.Key, int.Parse(action.Value)),
					},
					action.Description,
					actionID,
					new List<Action> {
						() => GameManager.Instance.SimSetRes(action.Key, int.Parse(action.Value)),
						() => GameManager.Instance.SimModifyRes("Threads", -1),
					},
					new List<Action> {
						() => GameManager.Instance.SimSetRes(action.Key, GameManager.Instance.SimRes[action.Key]),
						() => GameManager.Instance.SimModifyRes("Threads", +1),
					}
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			case InteractionActionResource.ActionType.ModifyResUsingThread:
				actionID = GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.ModifyRes(action.Key, int.Parse(action.Value)),
					},
					action.Description,
					actionID,
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, int.Parse(action.Value)),
						() => GameManager.Instance.SimModifyRes("Threads", -1),
					},
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, -int.Parse(action.Value)),
						() => GameManager.Instance.SimModifyRes("Threads", +1),
					}
				);
				QueuedActionsFromThisInteractable.Add(actionID);
				break;

			default:
				GD.PrintErr($"Action type {action.Type} is unknown!");
				break;
		}
	}
	*/

	/*
	public void Interact()
	{
		var content = GetCurrentContent();
		if (content == null)
		{
			GD.PrintErr("no content with current flag combination!");
			return;
		}

		GameManager.Instance.EmitInteractedSignal(this, Data.ID, Data.Title, content);
	}

	private InteractionContentResource GetCurrentContent()
	{
		InteractionContentResource possibleContent = null;
		foreach (var content in Data.Contents)
		{
			bool allConditionsMet = true;
			foreach (string condition in content.Conditions)
			{
				if (!GameManager.Instance.GetFlag(condition))
				{
					allConditionsMet = false;
					break;
				}
			}

			if (allConditionsMet)
			{
				possibleContent = content;
			}
		}

		return possibleContent;
	}

	public void ExecuteAction(InteractionActionResource action)
	{
		switch (action.Type) // TODO
		{
			case "SetFlag":
				GameManager.Instance.SetFlag(action.Key, bool.Parse(action.Value));
				break;
			case "ModifyResource":
				GameManager.Instance.QueueAction(
					new List<Action> {
						() => GameManager.Instance.ModifyRes(action.Key, int.Parse(action.Value))
					},
					action.Description,
					123, // TODO keep empty
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, int.Parse(action.Value))
					},
					new List<Action> {
						() => GameManager.Instance.SimModifyRes(action.Key, int.Parse(action.Value))
					}
					);
				break;
			default:
				GD.PrintErr($"Action type {action.Type} is unknown!");
				break;
		}
	}

	public void Test()
	{
		Dictionary<string, int> res = GameManager.Instance.Res;
		int energy = GameManager.Instance.GetRes("Energy");
		int simEnergy = GameManager.Instance.SimGetRes("Energy");
		int numberOfThreads = GameManager.Instance.GetRes("Threads");
		int numberOfRemainingThreads = GameManager.Instance.SimGetRes("Threads");
		// same as numberOfThreads but
		// will set the resource after the next turn to it
		// as player does not get threads in between turns
		// and only cause of queued input that then gets executed
		int MaxThreads = GameManager.Instance.MaxThreads;
		List<GameManager.TurnAction> actionQueue = GameManager.Instance.ActionQueue;
		// queue action
		int id = GameManager.Instance.QueueAction(
			new List<Action> {
				() => GameManager.Instance.ModifyRes("Energy", -5),
			},
			"spend 5 energy to increase max threads",
			-1, // will generate random id and return it
			new List<Action> {
				() => GameManager.Instance.SimModifyRes("Energy", -5),
				// still costs 1 current thread to queue this action
				() => GameManager.Instance.SimModifyRes("Threads", -1),
				() => GameManager.Instance.ModifyMaxThreads(+1)
			},
			new List<Action> {
				() => GameManager.Instance.SimModifyRes("Energy", +5),
				() => GameManager.Instance.SimModifyRes("Threads", +1),
				() => GameManager.Instance.ModifyMaxThreads(-1)
			}
			);
		// remove action from queue (everything gets undone)
		GameManager.Instance.DequeueAction(id);
		int turnCounter = GameManager.Instance.TurnCounter;
		// carry out turn
		GameManager.Instance.EndTurn();
		// yourButtonSignal += GameManager.Instance.EndTurn();
	}
	*/
}
