﻿using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Articy.Unity;
using Articy.Unity.Interfaces;

namespace AC.Downloads.ArticyDraft
{

	public class ArticyFlowPlayer : MonoBehaviour, IArticyFlowPlayerCallbacks
	{

		#region Variables

		[SerializeField] private bool syncLanguages;
		[SerializeField] private string globalVariableString;

		private Conversation conversation;
		private Articy.Unity.ArticyFlowPlayer articyFlowPlayer;

		[SerializeField] private ReferenceCharactersBy referenceCharactersBy = ReferenceCharactersBy.DisplayName;
		private enum ReferenceCharactersBy { DisplayName, ExternalID, TechnicalName };

		private Speech currentSpeech;
		private IList<Branch> conversationBranches;
		private IList<Branch> recordedBranches;

		#endregion


		#region UnityStandards

		private void OnEnable ()
		{
			EventManager.OnClickConversation += OnClickConversation;
			EventManager.OnStopSpeech_Alt += OnStopSpeech;
			EventManager.OnBeforeSaving += OnBeforeSaving;
			EventManager.OnFinishLoading += OnFinishLoading;
			EventManager.OnRestartGame += OnRestartGame;
			EventManager.OnChangeLanguage += OnChangeLanguage;

			OnChangeLanguage (Options.GetLanguage ());

			articyFlowPlayer = GetComponent<Articy.Unity.ArticyFlowPlayer> ();
			if (articyFlowPlayer == null)
			{
				articyFlowPlayer = gameObject.AddComponent<Articy.Unity.ArticyFlowPlayer> ();
				articyFlowPlayer.pauseOn = PausableObjectTypes.DialogueFragments;
				articyFlowPlayer.ignoreInvalidBranches = true;
			}
			IsRunning = false;

			if (conversation == null)
			{
				conversation = GetComponent<Conversation> ();
				if (conversation == null)
				{
					conversation = gameObject.AddComponent<Conversation> ();
				}
			}
			conversation.interactionSource = InteractionSource.CustomScript;
		}


		private void OnDisable ()
		{
			EventManager.OnClickConversation -= OnClickConversation;
			EventManager.OnStopSpeech_Alt -= OnStopSpeech;
			EventManager.OnBeforeSaving -= OnBeforeSaving;
			EventManager.OnFinishLoading -= OnFinishLoading;
			EventManager.OnRestartGame -= OnRestartGame;
			EventManager.OnChangeLanguage -= OnChangeLanguage;
		}

		#endregion


		#region PublicFunctions

		public void RunFlow (IArticyObject articyObject)
		{
			StopRunning ();

			KickStarter.stateHandler.EnforceCutsceneMode = true;

			IsRunning = true;
			articyFlowPlayer.StartOn = articyObject;
		}


		public void OnFlowPlayerPaused (IFlowObject aObject)
		{
			currentSpeech = null;

			var dlgSpeaker = aObject as IObjectWithSpeaker;
			if (dlgSpeaker != null)
			{
				Sprite portraitGraphic = null;
				string displayName = string.Empty;

				if (dlgSpeaker.Speaker != null)
				{
					IAsset speakerAsset = ((dlgSpeaker.Speaker as IObjectWithPreviewImage).PreviewImage.Asset as IAsset);
					if (speakerAsset != null)
					{
						portraitGraphic = speakerAsset.LoadAssetAsSprite ();
					}
				}

				var modelWithDisplayName = aObject as IObjectWithDisplayName;
				if (modelWithDisplayName != null)
				{
					displayName = modelWithDisplayName.DisplayName;
				}

				Char speaker = GetCharacterWithName (dlgSpeaker.Speaker);
				if (speaker != null)
				{
					if (!string.IsNullOrEmpty (displayName))
					{
						speaker.speechLabel = displayName;
					}
					if (portraitGraphic != null && portraitGraphic.texture != null)
					{
						speaker.portraitIcon.texture = portraitGraphic.texture;
					}
				}

				var txtObj = aObject as IObjectWithText;
				if (txtObj != null)
				{
					string speechText = txtObj.Text;
					KickStarter.stateHandler.EnforceCutsceneMode = true;
					currentSpeech = KickStarter.dialog.StartDialog (speaker, speechText);
				}
			}
		}


		public void OnBranchesUpdated (IList<Branch> aBranches)
		{
			if (currentSpeech == null)
			{
				recordedBranches = null;
				ProcessBranches (aBranches);
			}
			else
			{
				recordedBranches = aBranches;
			}
		}

		#endregion


		#region CustomEvents

		private void OnClickConversation (Conversation _conversation, int optionID)
		{
			if (conversation == _conversation && conversationBranches != null)
			{
				KickStarter.playerInput.EndConversation ();
				string optionLabel = conversation.GetOptionNameWithID (optionID);
				foreach (Branch branch in conversationBranches)
				{
					var obj = branch.Target as IObjectWithMenuText;
					if (obj.MenuText == optionLabel)
					{
						articyFlowPlayer.Play (branch);
					}
				}

				conversationBranches = null;
			}
		}


		private void OnStopSpeech (Speech speech)
		{
			if (speech == currentSpeech)
			{
				currentSpeech = null;
				if (recordedBranches != null)
				{
					ProcessBranches (recordedBranches);
				}
			}
		}


		private void OnBeforeSaving (int saveID)
		{
			GVar acVariable = GetStringVariable ();
			if (acVariable == null) return;
			
			StringBuilder sb = new StringBuilder ();
			foreach (KeyValuePair<string, object> kvp in ArticyDatabase.DefaultGlobalVariables.Variables)
			{
				if (sb.Length != 0)
				{
					sb.Append ("|");
				}
				sb.Append (kvp.Key).Append ("#");

				string varValue = string.Empty;
				if (kvp.Value.GetType () == typeof (bool))
				{
					bool boolValue = (bool) kvp.Value;
					varValue = boolValue ? "1" : "0";
				}
				else if (kvp.Value.GetType () == typeof (int))
				{
					int intValue = (int) kvp.Value;
					varValue = intValue.ToString ();
				}
				else
				{
					string stringValue = (string) kvp.Value;
					varValue = stringValue;
				}
				sb.Append (varValue);
			}

			acVariable.TextValue = sb.ToString ();
		}


		private void OnFinishLoading (int saveID)
		{
			GVar acVariable = GetStringVariable ();
			if (acVariable == null || string.IsNullOrEmpty (acVariable.TextValue)) return;

			string[] varChunks = acVariable.TextValue.Split ("|"[0]);
			Dictionary<string, object> newVars = new Dictionary<string, object> ();
			foreach (string varChunk in varChunks)
			{
				string[] varData = varChunk.Split ("#"[0]);
				if (varData == null || varData.Length != 2) continue;

				string varName = varData[0];
				string varValue = varData[1];

				if (!ArticyDatabase.DefaultGlobalVariables.Variables.ContainsKey (varName))
				{
					ACDebug.LogWarning ("No articy variable named " + varName + " was found");
					continue;
				}

				if (ArticyDatabase.DefaultGlobalVariables.Variables[varName].GetType () == typeof (bool))
				{
					if (int.TryParse (varValue, out int intValue))
					{
						newVars.Add (varName, (bool) (intValue == 1));
					}
				}
				else if (ArticyDatabase.DefaultGlobalVariables.Variables[varName].GetType () == typeof (int))
				{
					if (int.TryParse (varValue, out int intValue))
					{
						newVars.Add (varName, (int) intValue);
					}
				}
				else
				{
					newVars.Add (varName, varValue);
				}
			}

			ArticyDatabase.DefaultGlobalVariables.Variables = newVars;
		}


		private void OnRestartGame ()
		{
			ArticyDatabase.DefaultGlobalVariables.ResetVariables ();
		}


		private void OnChangeLanguage (int index)
		{
			if (syncLanguages)
			{
				string languageName = Options.GetLanguageName ();
				ArticyDatabase.Localization.SetLanguage (languageName);
				ACDebug.Log ("Articy:Draft - changed language to " + languageName, this);
			}
		}

		#endregion


		#region PrivateFunctions

		private void ProcessBranches (IList<Branch> aBranches)
		{
			bool dialogueIsFinished = true;
			foreach (var branch in aBranches)
			{
				if (branch.Target is IDialogueFragment)
				{
					dialogueIsFinished = false;
				}
			}
			if (dialogueIsFinished)
			{
				StopRunning ();
				return;
			}

			recordedBranches = null;

			conversationBranches = aBranches;
			conversation.interactionSource = InteractionSource.CustomScript;
			conversation.options.Clear ();
			for (int i = 0; i < aBranches.Count; i++)
			{
				var obj = aBranches[i].Target as IObjectWithMenuText;
				if (obj != null)
				{
					ButtonDialog buttonDialog = new ButtonDialog (i, obj.MenuText, true);
					conversation.options.Add (buttonDialog);
				}
			}

			if (conversation.options.Count > 1)
			{
				conversation.Interact ();
				KickStarter.stateHandler.EnforceCutsceneMode = false;
			}
			else
			{
				articyFlowPlayer.Play (aBranches[0]);
			}
		}


		private Char GetCharacterWithName (ArticyObject speaker)
		{
			if (speaker == null)
			{
				return null;
			}
			
			string _name = string.Empty;
			switch (referenceCharactersBy)
			{
				case ReferenceCharactersBy.DisplayName:
				default:
					_name = (speaker as IObjectWithDisplayName).DisplayName;
					break;

				case ReferenceCharactersBy.ExternalID:
					_name = (speaker as IObjectWithExternalId).ExternalId;
					break;

				case ReferenceCharactersBy.TechnicalName:
					_name = speaker.TechnicalName;
					break;
			}

			if (string.IsNullOrEmpty (_name))
			{
				return null;
			}

			if (KickStarter.player && _name == "Player")
			{
				return KickStarter.player;
			}

			#if UNITY_2022_3_OR_NEWER
			Char[] chars = UnityEngine.Object.FindObjectsByType <Char> (FindObjectsSortMode.None);
			#else
			Char[] chars = UnityEngine.Object.FindObjectsOfType<Char> ();
			#endif
			foreach (Char _char in chars)
			{
				if (_char.gameObject.name == _name)
				{
					return _char;
				}
			}

			ACDebug.LogWarning ("Character '" + _name + "' not found");
			return null;
		}


		private void StopRunning ()
		{
			if (IsRunning)
			{
				articyFlowPlayer.FinishCurrentPausedObject ();
				IsRunning = false;
				conversationBranches = null;
				currentSpeech = null;

				KickStarter.stateHandler.EnforceCutsceneMode = false;
			}
		}


		private GVar GetStringVariable ()
		{
			if (string.IsNullOrEmpty (globalVariableString)) return null;

			GVar acVariable = GlobalVariables.GetVariable (globalVariableString);
			if (acVariable == null)
			{
				ACDebug.LogWarning ("Cannot find AC Global Variable named " + globalVariableString);
				return null;
			}

			if (acVariable.type != VariableType.String)
			{
				ACDebug.LogWarning ("AC Global Variable " + globalVariableString + " must be of the type String");
				return null;
			}

			return acVariable;
		}

		#endregion


		#region GetSet

		public bool IsRunning
		{
			get { return articyFlowPlayer.enabled; }
			set { articyFlowPlayer.enabled = value; }
		}

		#endregion


		#region Singleton

		private static ArticyFlowPlayer instance;
		public static ArticyFlowPlayer Instance
		{
			get
			{
				if (instance == null)
				{
					#if UNITY_2022_3_OR_NEWER
					instance = Object.FindFirstObjectByType <ArticyFlowPlayer> ();
					#else
					instance = Object.FindObjectOfType <ArticyFlowPlayer> ();
					#endif
					if (instance == null)
					{
						GameObject instanceOb = new GameObject ("articy:draft AC integration");
						instance = instanceOb.AddComponent<ArticyFlowPlayer> ();
					}
				}
				return instance;
			}
		}

		#endregion

	}

}