﻿using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TMPro;
using AC;

namespace AC.Templates.ChatLogUI
{

	public class ChatLog : MonoBehaviour, IPointerClickHandler
	{

		#region Variables

		[Header ("Components")]
		[SerializeField] private TMPro.TextMeshProUGUI uiText = null;
		[SerializeField] private CanvasGroup canvasGroup = null;
		[SerializeField] private Canvas canvas = null;
		[SerializeField] private ScrollRect autoScrollRect = null;
		
		[Header ("Properties")]
		[SerializeField] private ColorOption colorOption = ColorOption.Speaker;
		[SerializeField] private bool showConversationOptions = true;
		[SerializeField] private bool showChosenOptions;
		[SerializeField] private bool visibleByDefault = true;
		[SerializeField] private bool surviveSceneChanges = true;

		[Header ("Input")]
		[SerializeField] private bool directNavigation = false;
		[SerializeField] private string verticalInputAxis = "Vertical";
		[SerializeField] private string submitInputButton = "Submit";

		[Header ("Styling")]
		[SerializeField] private string lineStyle;
		[SerializeField] private string speakerStyle;
		[SerializeField] private string optionStyle;
		[SerializeField] private string optionHoverStyle;

		private enum ColorOption { None, Speaker, Subtitle, Full };
		private enum ConversationOption { None, ShowChosen, ShowChoices };
		private readonly HashSet<LineBase> allLines = new HashSet<LineBase> ();
		
		private int hoverLinkID = -1;
		private bool canAcceptDirectNavInput;

		#endregion


		#region UnityStandards

		private void OnEnable ()
		{
			EventManager.OnStartConversation += OnStartConversation;
			EventManager.OnClickConversation += OnClickConversation;
			EventManager.OnEndConversation += OnEndConversation;
			EventManager.OnStartSpeech_Alt += OnStartSpeech;
			EventManager.OnChangeLanguage += OnChangeLanguage;
			EventManager.OnAfterChangeScene += OnAfterChangeScene;

			RewriteLog ();

			if (visibleByDefault)
			{
				TurnOn ();
			}
			else
			{
				TurnOff ();
			}

			if (surviveSceneChanges)
			{
				DontDestroyOnLoad (gameObject);
			}
		}


		private void Start ()
		{
			if (showConversationOptions)
			{
				if (KickStarter.playerMenus && KickStarter.playerMenus.EventSystem == null)
				{
					KickStarter.playerMenus.CreateEventSystem (true);
				}
				else if (EventSystem.current == null)
				{
					ACDebug.LogWarning ("The ChatLog component requires an EventSystem to be present in the scene.", this);
				}

				if (KickStarter.dialog)
				{
					KickStarter.dialog.conversationDelay = 0f;
				}
			}
		}


		private void OnDisable ()
		{
			EventManager.OnStartConversation -= OnStartConversation;
			EventManager.OnClickConversation -= OnClickConversation;
			EventManager.OnEndConversation += OnEndConversation;
			EventManager.OnChangeLanguage -= OnChangeLanguage;
			EventManager.OnStartSpeech_Alt -= OnStartSpeech;
			EventManager.OnAfterChangeScene -= OnAfterChangeScene;
		}


		private void LateUpdate ()
		{
			HoverLinks ();
		}

		#endregion


		#region PublicFunctions

		public void TurnOn ()
		{
			canvasGroup.alpha = 1f;
			canvasGroup.interactable = true;
			canvasGroup.blocksRaycasts = true;
		}


		public void TurnOff ()
		{
			canvasGroup.alpha = 0f;
			canvasGroup.interactable = true;
			canvasGroup.blocksRaycasts = false;
		}

		#endregion


		#region CustomEvents

		private void OnStartSpeech (Speech speech)
		{
			allLines.Add (new SpeechLine (speech));
			RewriteLog ();
		}


		private void OnChangeLanguage (int language)
		{
			RewriteLog ();
		}


		private void OnClickConversation (Conversation conversation, int optionID)
		{
			if (showChosenOptions)
			{
				allLines.Add (new DialogOption (conversation, optionID));
				RewriteLog ();
			}
		}


		private void OnStartConversation (Conversation conversation)
		{
			hoverLinkID = -1;
			RewriteLog (conversation);
		}


		private void OnEndConversation (Conversation conversation)
		{
			RewriteLog ();
		}


		private void OnAfterChangeScene (LoadingGame loadingGame)
		{
			if (showConversationOptions)
			{
				KickStarter.dialog.conversationDelay = 0f;
			}
		}

		#endregion


		#region PublicFunctions

		public void OnPointerClick (PointerEventData eventData)
		{
			if (directNavigation) return;
			if (KickStarter.playerInput.activeConversation == null || KickStarter.stateHandler.gameState != GameState.DialogOptions) return;
				
			int linkIndex = GetLinkIndex (eventData);
			if (linkIndex != -1)
			{
				var linkInfo = uiText.textInfo.linkInfo[linkIndex];
				if (int.TryParse (linkInfo.GetLinkID (), out int clickedOption))
				{
					KickStarter.playerInput.activeConversation.RunOptionWithID (clickedOption);
				}
			}
		}

		#endregion


		#region PrivateFunctions

		private void HoverLinks ()
		{
			if (directNavigation)
			{
				if (KickStarter.playerInput.activeConversation == null || KickStarter.stateHandler.gameState != GameState.DialogOptions) return;

				float navigationInput = KickStarter.playerInput.InputGetAxis (verticalInputAxis);
				if (navigationInput < -0.1f)
				{
					if (canAcceptDirectNavInput)
					{
						canAcceptDirectNavInput = false;
						hoverLinkID = GetNextLinkID ();
						RewriteLog (KickStarter.playerInput.activeConversation);
					}
				}
				else if (navigationInput > 0.1f)
				{
					if (canAcceptDirectNavInput)
					{
						canAcceptDirectNavInput = false;
						hoverLinkID = GetPreviousLinkID ();
						RewriteLog (KickStarter.playerInput.activeConversation);
					}
				}
				else
				{
					canAcceptDirectNavInput = true;
				}

				if (KickStarter.playerInput.InputGetButtonDown (submitInputButton) && hoverLinkID != -1)
				{
					KickStarter.playerInput.activeConversation.RunOptionWithID (hoverLinkID);
				}
				return;
			}
			
			Vector3 mousePosition = KickStarter.playerInput.GetMousePosition ();

			int linkIndex = GetLinkIndex (mousePosition);
			if (linkIndex != -1)
			{
				var linkInfo = uiText.textInfo.linkInfo[linkIndex];
				if (int.TryParse (linkInfo.GetLinkID (), out int linkID))
				{
					hoverLinkID = linkID;
					RewriteLog (KickStarter.playerInput.activeConversation);
					return;
				}
			}

			if (hoverLinkID != linkIndex)
			{
				hoverLinkID = -1;
				RewriteLog (KickStarter.playerInput.activeConversation);
			}
		}


		private int GetLinkIndex (PointerEventData eventData)
		{
			Vector3 mousePosition = new Vector3 (eventData.position.x, eventData.position.y, 0f);
			return GetLinkIndex (mousePosition);
		}


		private int GetLinkIndex (Vector3 mousePosition)
		{
			Camera camera = (canvas.renderMode == RenderMode.ScreenSpaceOverlay) ? null : Camera.main;
			return TMP_TextUtilities.FindIntersectingLink (uiText, mousePosition, camera);
		}


		private void RewriteLog (Conversation conversation = null)
		{
			if (uiText == null)
			{
				return;
			}

			StringBuilder sb = new StringBuilder ();

			foreach (var line in allLines)
			{
				string lineText = line.GetText (this);
				if (!string.IsNullOrEmpty (lineText))
				{
					if (!string.IsNullOrEmpty (lineStyle))
					{
						lineText = "<style=\"" + lineStyle + "\">" + lineText + "</style>";
					}

					sb.Append (lineText).Append ("\n");
				}
			}

			if (conversation && showConversationOptions)
			{
				int numShown = 1;
				foreach (var option in conversation.options)
				{
					if (!option.isOn) continue;

					if (directNavigation && hoverLinkID == -1)
					{
						hoverLinkID = option.ID;
					}

					string optionText = KickStarter.runtimeLanguages.GetTranslation (option.label, option.lineID, Options.GetLanguage (), AC_TextType.DialogueOption);
					if (hoverLinkID == option.ID && !string.IsNullOrEmpty (optionStyle))
					{
						optionText = "<style=\"" + optionStyle + "\">" + optionText + "</style>";
					}

					string linkText = "<link=\"" + option.ID + "\">" + numShown.ToString () + ". - " + optionText + "</link>";

					if (hoverLinkID == option.ID && !string.IsNullOrEmpty (optionHoverStyle))
					{
						linkText = "<style=\"" + optionHoverStyle + "\">" + linkText + "</style>";
					}

					sb.Append (linkText).Append ("\n");
					numShown++;
				}
			}

			uiText.text = sb.ToString ();

			if (autoScrollRect)
			{
				Canvas.ForceUpdateCanvases ();
				autoScrollRect.verticalNormalizedPosition = 0f;
			}
		}


		private int GetNextLinkID ()
		{
			Conversation conversation = KickStarter.playerInput.activeConversation;
			if (conversation == null) return -1;

			bool isNext = false;
			for (int i = 0; i < conversation.options.Count; i++)
			{
				if (!conversation.options[i].isOn) continue;

				if (hoverLinkID == -1)
				{
					return conversation.options[i].ID;
				}
				else if (hoverLinkID == conversation.options[i].ID)
				{
					isNext = true;
				}
				else if (isNext)
				{
					return conversation.options[i].ID;
				}
			}

			return hoverLinkID;
		}


		private int GetPreviousLinkID ()
		{
			Conversation conversation = KickStarter.playerInput.activeConversation;
			if (conversation == null) return -1;

			bool isNext = false;
			for (int i = conversation.options.Count - 1; i >= 0; i--)
			{
				if (!conversation.options[i].isOn) continue;

				if (hoverLinkID == -1)
				{
					return conversation.options[i].ID;
				}
				else if (hoverLinkID == conversation.options[i].ID)
				{
					isNext = true;
				}
				else if (isNext)
				{
					return conversation.options[i].ID;
				}
			}

			return hoverLinkID;
		}

		#endregion


		#region PrivateClasses

		private abstract class LineBase
		{

			public abstract string GetText (ChatLog chatLog);

			protected string WrapColorTags (string text, Color color)
			{
				string colorHTML = ColorUtility.ToHtmlStringRGBA (color);
				return "<color=#" + colorHTML + ">" + text + "</color>";
			}
		}

		private class SpeechLine : LineBase
		{
			private readonly Speech Speech;

			public SpeechLine (Speech speech)
			{
				Speech = speech;
			}

			public override string GetText (ChatLog chatLog)
			{
				string lineText = KickStarter.runtimeLanguages.GetTranslation (Speech.FullText, Speech.LineID, Options.GetLanguage (), AC_TextType.Speech);
				if (string.IsNullOrEmpty (lineText))
				{
					return lineText;
				}

				if (chatLog.colorOption == ColorOption.Subtitle)
				{
					lineText = WrapColorTags (lineText, Speech.GetColour ());
				}

				string speakerLabel = Speech.GetSpeaker (Options.GetLanguage ());
				if (!string.IsNullOrEmpty (speakerLabel))
				{
					if (!string.IsNullOrEmpty (chatLog.speakerStyle))
					{
						speakerLabel = "<style=\"" + chatLog.speakerStyle + "\">" + speakerLabel + "</style>";
					}
					if (chatLog.colorOption == ColorOption.Speaker)
					{
						speakerLabel = WrapColorTags (speakerLabel, Speech.GetColour ());
					}
					lineText = speakerLabel + " " + lineText;
				}

				if (chatLog.colorOption == ColorOption.Full)
				{
					lineText = WrapColorTags (lineText, Speech.GetColour ());
				}

				return lineText;
			}

		}

		private class DialogOption : LineBase
		{

			private readonly string OriginalLabel;
			private readonly int LineID;

			public DialogOption (Conversation conversation, int optionID)
			{
				OriginalLabel = conversation.GetOptionNameWithID (optionID);
				LineID = conversation.GetOptionWithID (optionID).lineID;
			}

			public override string GetText (ChatLog chatLog)
			{
				string lineText = KickStarter.runtimeLanguages.GetTranslation (OriginalLabel, LineID, Options.GetLanguage (), AC_TextType.DialogueOption);
				if (string.IsNullOrEmpty (lineText))
				{
					return lineText;
				}

				if (KickStarter.player)
				{
					string speakerLabel = KickStarter.player.GetName (Options.GetLanguage ());
					if (!string.IsNullOrEmpty (speakerLabel))
					{
						if (!string.IsNullOrEmpty (chatLog.speakerStyle))
						{
							speakerLabel = "<style=\"" + chatLog.speakerStyle + "\">" + speakerLabel + "</style>";
						}
						if (chatLog.colorOption == ColorOption.Speaker)
						{
							speakerLabel = WrapColorTags (speakerLabel, KickStarter.player.speechColor);
						}
						lineText = speakerLabel + " " + lineText;
					}
				}

				return lineText;
			}
		}

		#endregion

	}

}