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

namespace Hellswipers.Game;

public partial class Game : Control {
	private const int TotalWidth = 1920;
	private const int SpaceBetweenArrows = 8;
	private const long ErrorLengthMs = 400;

	private PackedScene _arrowScene;
	private AnimationPlayer _player;
	private Node2D _arrowsNode;
	private Label _nameLabel;
	private Icons _icons;
	
	private readonly Gem[] _gems = Gem.LoadGems();
	private readonly Random _rand = new();
	private readonly Stopwatch _errorWatch = new();
	
	private readonly Queue<Gem> _nextGems = new();
	private readonly List<Arrow> _arrows = new();
	
	private Gem _current;
	private bool _disabled = false;
	private int _pos = -1;

	public delegate void VoidEventHandler();
	public event VoidEventHandler SwipeOk;
	public event VoidEventHandler SwipeErr;
	public event VoidEventHandler Solved;

	private Gem RandomGem() => _gems[_rand.Next(_gems.Length)];

	private async void StartNewRound() {
		var gem = RandomGem();
		_icons.Update(gem);
		_nextGems.Enqueue(gem);
		
		_current = _nextGems.Dequeue();
		_disabled = true;
		var tween = CreateTween();
		tween.SetParallel();
		if (_pos != -1) {
			_arrows.ForEach(v => {
				var newPos = v.CurrentDir switch {
					Arrow.Dirs.Right => new Vector2(100, 0),
					Arrow.Dirs.Down => new Vector2(0, 100),
					Arrow.Dirs.Left => new Vector2(-100, 0),
					Arrow.Dirs.Up => new Vector2(0, -100),
					_ => new Vector2(0, 0)
				};
				tween.TweenProperty(v, "position", v.Position + newPos, 0.2);
			});
			_player.Play("round-end");
			await ToSignal(_player, AnimationMixer.SignalName.AnimationFinished);
			
			_arrows.ForEach(v => {
				_arrowsNode.RemoveChild(v);
				v.QueueFree();
			});
			_arrows.Clear();
			tween = CreateTween();
			tween.SetParallel();
		}

		_pos = 0;
		_nameLabel.Text = _current.Name;
		
		var arrowCount = _current.Dirs.Length;
		var totalArrowWidth = 64 * arrowCount + SpaceBetweenArrows * (arrowCount - 1);
		var xPos = (TotalWidth - totalArrowWidth) / 2;
		foreach (var dir in _current.Dirs) {
			var arrow = _arrowScene.Instantiate<Arrow>();
			var startPos = dir switch {
				Arrow.Dirs.Right => new Vector2(-100, 0),
				Arrow.Dirs.Down => new Vector2(0, -100),
				Arrow.Dirs.Left => new Vector2(100, 0),
				Arrow.Dirs.Up => new Vector2(0, 100),
				_ => new Vector2(0, 0)
			};
			startPos.X += xPos;
			arrow.Position = startPos;
			tween.TweenProperty(arrow, "position", new Vector2(xPos, 0), 0.2);
			arrow.SetDir(dir);
			_arrows.Add(arrow);
			_arrowsNode.AddChild(arrow);
			xPos += 64 + SpaceBetweenArrows;
		}
		
		_player.Play("round-start");
		await ToSignal(_player, AnimationMixer.SignalName.AnimationFinished);

		_disabled = false;
	}
	
	private void HandleInput(Arrow.Dirs dir) {
		if (_disabled) {
			if (_errorWatch.IsRunning && _errorWatch.ElapsedMilliseconds >= ErrorLengthMs) {
				_disabled = false;
				_errorWatch.Stop();
			} else return;
		}
		
		if (_pos >= _current.Dirs.Length) return;

		if (dir == _current.Dirs[_pos]) {
			_arrows[_pos].Done();
			_pos++;
			SwipeOk!.Invoke();
			if (_pos == _current.Dirs.Length) {
				Solved!.Invoke();
				StartNewRound();
			}
		} else {
			_disabled = true;
			SwipeErr!.Invoke();
			_errorWatch.Start();
			_arrows.ForEach(v => v.Error());
			_pos = 0;
		}
	}

	public override void _Ready() {
		_player = GetNode<AnimationPlayer>("AnimationPlayer");
		_arrowsNode = GetNode<Node2D>("Arrows");
		_nameLabel = GetNode<Label>("Name");
		_icons = GetNode<Icons>("Icons");
		
		var stats = GetNode<Stats>("StatContainer");
		SwipeOk += () => stats.Combo++;
		SwipeErr += () => stats.Combo = 0;
		Solved += () => stats.Solved++;

		var audio = GetNode<Audio>("Audio");
		SwipeOk += audio.Ok;
		SwipeErr += audio.Err;
		
		_arrowScene = ResourceLoader.Load<PackedScene>("res://game/arrow.tscn");
		
		for (int i = 0; i < 7; ++i)
			_nextGems.Enqueue(RandomGem());

		_icons.Init(_nextGems.ToArray());
		StartNewRound();
	}
}
