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

public partial class Robot : Node3D
{
	private const float EnergyUsePerSecond = 0.2f;
	private const float HeatGainPerSecond = 7.5f;
	private const float ActiveHeatLossPerSecond = 18f;
	private const float IdleHeatLossPerSecond = 9f;
	private const float CooldownTarget = 35f;
	private const float MaintenanceLossPerSecond = 0.04f;

	private bool isExecuting = false;
	private ProgramNode currentNode;

	public string currentProgram;
	public string currentMessage = "";
	public float heat = 0f;
	public float maintenance = 100f;
	public bool isCoolingDown = false;
	public bool isBroken = false;
	public string robotType = "iron_robot";

	private RobotTypeStats TypeStats =>
		GameData.robotStats.RobotTypes.TryGetValue(robotType, out RobotTypeStats stats)
			? stats
			: GameData.robotStats.RobotTypes["stone_robot"];

	public override void _Process(double delta)
	{
		if (isExecuting)
		{
			if (CanExecute(delta))
			{
				switch (currentNode.Execute(this, delta))
				{
					case NodeResult.SUCCESS:
						currentNode = currentNode.nextNode;
						if (currentNode == null)
						{
							isExecuting = false;
						}
						break;

					case NodeResult.FAILURE:
						isExecuting = false;
						currentMessage = "(FAILED)" + currentNode.lastExecutionMessage;
						break;

					case NodeResult.RUNNING:
						currentMessage = "";
						break;
				}
			}
		}
		else if (currentMessage.Length <= 0)
		{
			CoolDown(
				delta,
				GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
				* TypeStats.CoolingMultiplier
			);

			currentMessage = "No script executing";
		}

		Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
	}

	public void SetupExecution(List<ProgramNode> nodes)
	{
		if (nodes.Count <= 0) return;

		isExecuting = true;
		currentNode = nodes[0];
		currentMessage = "";
	}

	public float GetMovementSpeed()
	{
		return GameData.robotStats.GetMovementSpeed(GameData.robotSpeed)
			* TypeStats.SpeedMultiplier
			* GetWorkEfficiency();
	}

	public float GetWorkEfficiency()
	{
		if (isBroken) return 0f;
		if (maintenance >= 50f) return 1f;

		float minimumEfficiency =
			GameData.robotStats.GetMinimumEfficiency()
			+ TypeStats.MinimumEfficiencyBonus;

		minimumEfficiency = Math.Clamp(minimumEfficiency, 0f, 1f);

		return Math.Clamp(
			minimumEfficiency + maintenance / 100f,
			minimumEfficiency,
			1f
		);
	}

	public void Maintain()
	{
		maintenance = 100f;
		isBroken = false;
		currentMessage = "";
	}

	private bool CanExecute(double delta)
	{
		if (GameData.survival.isDead)
		{
			currentMessage = "Survival failed";
			return false;
		}

		if (isBroken)
		{
			currentMessage = "Maintenance required";
			return false;
		}

		if (isCoolingDown)
		{
			CoolDown(
				delta,
				GameData.robotStats.GetCoolingRate(ActiveHeatLossPerSecond)
				* TypeStats.CoolingMultiplier
			);

			currentMessage = $"Cooling down ({heat:0}%)";
			return false;
		}

		float energyUse =
			GameData.robotStats.GetEnergyUse(EnergyUsePerSecond)
			* TypeStats.EnergyUseMultiplier
			* (float)delta;

		if (!GameData.survival.TryConsumeEnergy(energyUse))
		{
			currentMessage = "Not enough energy";
			return false;
		}

		heat = Math.Clamp(
			heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond)
				* TypeStats.HeatGainMultiplier
				* (float)delta,
			0f,
			100f
		);

		maintenance = Math.Clamp(
			maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond)
				* TypeStats.MaintenanceLossMultiplier
				* (float)delta,
			0f,
			100f
		);

		if (heat >= 100f)
		{
			isCoolingDown = true;
			currentMessage = "Overheated";
			return false;
		}

		if (maintenance <= 0f)
		{
			isBroken = true;
			currentMessage = "Maintenance required";
			return false;
		}

		return true;
	}

	private void CoolDown(double delta, float heatLossPerSecond)
	{
		heat = Math.Clamp(
			heat - heatLossPerSecond * (float)delta,
			0f,
			100f
		);

		if (heat <= CooldownTarget)
		{
			isCoolingDown = false;
		}
	}
}