using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;

using BangerTypes;
using TagLib.Ape;

namespace UITypes
{

	public class GameElement : VisualElement
	{
		[UnityEngine.Scripting.Preserve]
		public new class UxmlFactory : UxmlFactory<GameElement> { }

		public Button Left { get; set; }
		public Button Right { get; set; }
		public Button Up { get; set; }
		public Button Down { get; set; }

		private Visualizer leftVisualizer;
		private Visualizer rightVisualizer;


		private VisualElement leftUpcomingColumn;
		private VisualElement rightUpcomingColumn;
		private VisualElement upUpcomingColumn;
		public VisualElement downUpcomingColumn;

		private bool leftActive = false;
		private bool rightActive = false;
		private bool downActive = false;
		private bool upActive = false;

		private Label score;
		private Label songTime;

		private VisualElement leftBackgroundImage;
		private VisualElement rightBackgroundImage;

		Dictionary<KeyCode, Button> buttonDictionary;

		Game game;

		List<NoteElement> activeNoteElements = new();

		public void InitGame(Game game)
		{
			this.game = game;
			leftVisualizer = this.Q("LeftVisualizer") as Visualizer;
			rightVisualizer = this.Q("RightVisualizer") as Visualizer;

			Left = this.Q("GameLeftButton") as Button;
			Right = this.Q("GameRightButton") as Button;
			Up = this.Q("GameUpButton") as Button;
			Down = this.Q("GameDownButton") as Button;

			score = this.Q("Score") as Label;
			songTime = this.Q("TimeLeft") as Label;

			leftBackgroundImage = this.Q("GameBackgroundImageLeft");
			rightBackgroundImage = this.Q("GameBackgroundImageRight");

			leftUpcomingColumn = this.Q("LeftArrowUpcomingColumn");
			rightUpcomingColumn = this.Q("RightArrowUpcomingColumn");
			upUpcomingColumn = this.Q("UpArrowUpcomingColumn");
			downUpcomingColumn = this.Q("DownArrowUpcomingColumn");

			RegisterCallback<KeyDownEvent>(OnKeyDown, TrickleDown.TrickleDown);
			RegisterCallback<KeyUpEvent>(OnKeyUp, TrickleDown.TrickleDown);

			leftVisualizer.InitVisualizer("LeftBar");
			rightVisualizer.InitVisualizer("RightBar");

			buttonDictionary = new()
			{
				{ KeyCode.LeftArrow, Left },
				{ KeyCode.RightArrow, Right },
				{ KeyCode.UpArrow, Up },
				{ KeyCode.DownArrow, Down },
			};
		}

		public void SetColumnColor(Color32 color)
		{
			leftVisualizer.SetBarColor(color);
			rightVisualizer.SetBarColor(color);
		}

		public void UpdateVisualizers(float[] specData, float bassStrength)
		{
			leftVisualizer.UpdateBars(specData);
			rightVisualizer.UpdateBars(specData);

			leftBackgroundImage.style.opacity = new StyleFloat(bassStrength);
			rightBackgroundImage.style.opacity = new StyleFloat(bassStrength);
		}

		public void UpdateSongTime(string time)
		{
			songTime.text = time;
		}

		public void UpdateNotes(List<Note> notes)
		{
			leftActive = notes.Where(note => note.a == "left").Count() > 0;
			rightActive = notes.Where(note => note.a == "right").Count() > 0;
			upActive = notes.Where(note => note.a == "up").Count() > 0;
			downActive = notes.Where(note => note.a == "down").Count() > 0;

			leftUpcomingColumn.EnableInClassList("ColumnActive", leftActive);
			rightUpcomingColumn.EnableInClassList("ColumnActive", rightActive);
			downUpcomingColumn.EnableInClassList("ColumnActive", downActive);
			upUpcomingColumn.EnableInClassList("ColumnActive", upActive);

			foreach (var note in notes)
			{
				NoteElement el = new();
				el.Init(note);
				activeNoteElements.Add(el);

				if (note.a == "left")
				{
					leftUpcomingColumn.Add(el);
				}
				else if (note.a == "right")
				{
					rightUpcomingColumn.Add(el);
				}
				else if (note.a == "up")
				{
					upUpcomingColumn.Add(el);
				}
				else if (note.a == "down")
				{
					downUpcomingColumn.Add(el);
				}
			}
		}

		public void UpdateMargin(float timestamp)
		{
			foreach (var note in activeNoteElements)
			{
				note.UpdateMargin(timestamp, leftUpcomingColumn.resolvedStyle.height);
			}
		}

		public void CheckNoteLifetime(float timestamp)
		{
			foreach (var note in activeNoteElements.ToList())
			{
				if (note.CheckSelf(timestamp))
				{
					activeNoteElements.Remove(note);

					if (note.note.a == "left")
					{
						leftUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "right")
					{
						rightUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "up")
					{
						upUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "down")
					{
						downUpcomingColumn.Remove(note);
					}
					else
					{
						continue;
					}

					AddScore(-1);
				}
			}
		}

		void OnKeyDown(KeyDownEvent ev)
		{
			if (ev.keyCode == KeyCode.Escape)
			{
				game.EscapeClicked();
				return;
			}

			buttonDictionary.TryGetValue(ev.keyCode, out Button button);

			if (button == null) return;

			button.AddToClassList("GameButtonClicked");

			CheckNotesOnClick(ev.keyCode);
		}

		void CheckNotesOnClick(KeyCode keyCode)
		{
			List<NoteElement> activeNotesInCurrentColumn = activeNoteElements
															.Where(note => note.note.a + "arrow" == keyCode.ToString().ToLower())
															.OrderBy(note => note.note.t)
															.ToList();

			foreach (NoteElement note in activeNotesInCurrentColumn)
			{
				float scoreTime = note.note.t - game.manager.source.time;
				if (scoreTime < 0.1f)
				{
					AddScore(scoreTime);
					activeNoteElements.Remove(note);

					if (note.note.a == "left")
					{
						leftUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "right")
					{
						rightUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "up")
					{
						upUpcomingColumn.Remove(note);
					}
					else if (note.note.a == "down")
					{
						downUpcomingColumn.Remove(note);
					}
					return;
				}
			}
			AddScore(-1);
		}

		void AddScore(float accuracy)
		{
			if (accuracy == -1)
			{
				game.Score -= 10;
			}
			else
			{
				game.Score += 100 - (int) (accuracy * 1000);
			}
			score.text = "SCORE: " + game.Score.ToString();
		}

		void OnKeyUp(KeyUpEvent ev)
		{
			buttonDictionary.TryGetValue(ev.keyCode, out Button button);

			if (button == null) return;

			button.RemoveFromClassList("GameButtonClicked");
		}
	}
}
