﻿using System.Collections.Generic;
using UnityEngine;

namespace AC.Downloads.ArrangingPuzzle
{

	[DefaultExecutionOrder (1)]
	public class ArrangingPuzzlePiece : MonoBehaviour
	{

		#region Variables

		[SerializeField] private Hotspot correctSlot = null;
		[SerializeField] private Hotspot initialSlot = null;
		[SerializeField] private ActionList actionListOnClick = null;
		[SerializeField] private int categoryID = -1;
		private int associatedItemID = -1;

		private Hotspot currentSlot;
		private Hotspot ownHotspot;
		private Vector3 originalPosition;
		private bool isSelected;
		private float cameraDepth;
		private bool inPreDrag;
		private Vector2 dragStartPosition;
		private float timeSelected;

		private ArrangingPuzzleManager manager;
		private static List<int> dynamicItemIDs;

		#endregion


		#region UnityStandards

		private void OnEnable ()
		{
			ownHotspot = GetComponent <Hotspot>();
			Register (ArrangingPuzzleManager.Get (this));
		}


		private void OnDisable ()
		{
			Unregister ();

			if (dynamicItemIDs != null && AssociatedItemID >= 0 && KickStarter.inventoryManager.GetItem (AssociatedItemID) == null)
			{
				dynamicItemIDs.Remove (AssociatedItemID);
			}
		}


		private void Update ()
		{
			if (manager == null)
			{
				ACDebug.LogWarning("Puzzle piece " + gameObject.name + " has no Manager", this);
				return;
			}
			
			if (inPreDrag)
			{
				MouseState mouseState = KickStarter.playerInput.GetMouseState ();
				if (mouseState == MouseState.LetGo)
				{
					inPreDrag = false;
					if (actionListOnClick) actionListOnClick.Interact ();
					return;
				}

				Vector2 dragVector = KickStarter.playerInput.GetMousePosition () - dragStartPosition;
				if ((dragVector.magnitude / ACScreen.LongestDimension) >= manager.dragThreshold)
				{
					Select ();
				}
			}

			if (isSelected)
			{
				// Follow the cursor if it should
				manager.UpdateSelectedPiecePosition (transform, cameraDepth, timeSelected);
			}
			else if (manager.LerpsBack ())
			{
				// Lerp back
				if (currentSlot)
				{
					manager.UpdateDeselectedPiecePosition (transform, currentSlot.transform.position);
				}
				else if (manager.revertPositionWhenRelease)
				{
					manager.UpdateDeselectedPiecePosition (transform, originalPosition);
				}
			}
		}

		#endregion


		#region PublicFunctions

		public void Initialise (ArrangingPuzzleManager _manager, Hotspot _correctSlot, Hotspot _initialSlot)
		{
			correctSlot = _correctSlot;
			initialSlot = _initialSlot;

			Register (_manager);
			OnInitialiseScene ();
		}


		public bool HasAssociatedID (int itemID)
		{
			return AssociatedItemID == itemID;
		}


		public bool HasCorrectSlot ()
		{
			return correctSlot;
		}


		public bool IsInCorrectSlot ()
		{
			return correctSlot && currentSlot == correctSlot;
		}


		public ArrangingPuzzlePieceData SaveData (ArrangingPuzzlePieceData data)
		{
			data.currentSlot = currentSlot ? Serializer.GetConstantID (currentSlot.gameObject) : 0;

			data.originalPositionX = originalPosition.x;
			data.originalPositionY = originalPosition.y;
			data.originalPositionZ = originalPosition.z;

			return data;
		}


		public void LoadData (ArrangingPuzzlePieceData data)
		{
			Vector3 savedPosition = new Vector3 (data.originalPositionX, data.originalPositionY, data.originalPositionZ);
			originalPosition = savedPosition;
			SetCurrentSlot ((data.currentSlot != 0) ? Serializer.returnComponent <Hotspot> (data.currentSlot) : null);
			if (currentSlot) transform.position = currentSlot.transform.position;
			else transform.position = savedPosition;
		}


		public void Select ()
		{
			inPreDrag = false;
			
			BackupPosition ();
			var invInstance = CreateInventoryItem ();

			if (KickStarter.runtimeInventory.SelectedItem == null || KickStarter.runtimeInventory.SelectedItem.id != AssociatedItemID)
			{
				KickStarter.runtimeInventory.SelectItem (invInstance);
			}

			if (currentSlot)
			{
				currentSlot.TurnOn ();

				if (!manager.revertPositionWhenRelease)
				{
					SetCurrentSlot (null);
				}
			}

			isSelected = true;
			timeSelected = Time.time;
			ownHotspot.TurnOff ();
		}

		#endregion


		#region CustomEvents

		private void OnHotspotInteract (Hotspot hotspot, AC.Button button)
		{
			// Get the inventory item selected when the Hotspot was used
			InvItem selectedItem = KickStarter.runtimeInventory.SelectedItem;
			if (selectedItem == null)
			{
				if (hotspot == ownHotspot && button != null && hotspot.GetButtonInteractionType (button) == HotspotInteractionType.Use)
				{
					inPreDrag = true;
					dragStartPosition = KickStarter.playerInput.GetMousePosition ();
				}
				return;
			}

			// Only interested in unhandled inventory interactions
			if (button == null || hotspot.GetButtonInteractionType (button) == HotspotInteractionType.UnhandledInventory)
			{
				if (manager.HotspotIsSlot (hotspot) && selectedItem.id == AssociatedItemID)
				{
					// Used this piece's associated inventory item on a slot Hotspot, so place it over it and check for the win state
					if (manager.onlyCorrectAccepted)
					{
						if (correctSlot && hotspot != correctSlot)
						{
							return;
						}
					}

					SetCurrentSlot (hotspot);
					manager.CheckForWin (true);
				}
				else if (hotspot == ownHotspot)
				{
					ArrangingPuzzlePiece piece = manager.GetPiece (selectedItem.id);
					if (piece != null)
					{
						if (manager.onlyCorrectAccepted)
						{
							if (piece.correctSlot && ownHotspot && ownHotspot != piece.correctSlot)
							{
								return;
							}
						}

						// Another item was used on this GameObject's own Hotspot, so swap the two around
						Vector3 tempPosition = originalPosition;
						originalPosition = piece.originalPosition;
						piece.originalPosition = tempPosition;

						Hotspot tempSlot = currentSlot;
						SetCurrentSlot (piece.currentSlot);
						piece.SetCurrentSlot (tempSlot);

						manager.CheckForWin (true);
					}
				}
			}
		}


		private void OnInventorySelect (InvItem item)
		{
			if (KickStarter.playerMenus.IsMouseOverMenu ()) return;

			if (item.id == AssociatedItemID)
			{
				// If this piece's associated Inventory item is selected, backup the position, turn on it\s current slot's slot Hotspot, and hide if appropriate
				Select ();				
			}
		}


		private void OnInventoryDeselect (InvItem item)
		{
			if (item.id == AssociatedItemID)
			{
				// If this piece's associated Inventory item is deselected, update it's position and remove from the player's inventory
				OnInventoryDeselect ();

				if (actionListOnClick && Time.time < timeSelected + KickStarter.playerInput.clickDelay)
				{
					actionListOnClick.Interact ();
				}
			}
		}


		private void OnInventoryAdd (InvCollection invCollection, InvInstance invInstance, int amount)
		{
			if (invInstance.ItemID == AssociatedItemID)
			{
				// If this piece's associated Inventory item is deselected, update it's position and remove from the player's inventory
				OnInventoryDeselect ();
				Destroy (gameObject);
			}
		}


		private void OnInitialiseScene ()
		{
			if (initialSlot != null)
			{
				SetCurrentSlot (initialSlot);
				transform.position = currentSlot.transform.position;
			}
		}


		private void OnTeleport (GameObject teleportedObject)
		{
			if (teleportedObject == gameObject)
			{
				BackupPosition ();
			}
		}

		#endregion


		#region PrivateFunctions
	   
		private void Register (ArrangingPuzzleManager _manager)
		{
			Unregister ();

			manager = _manager;
			
			if (manager == null)
			{
				ACDebug.LogWarning ("Arranging puzzle piece " + gameObject.name + " has no associated Manager - create an Arranging Puzzle Manager and assign it in its Pieces array", this);
				return;
			}

			// Create event hooks
			manager.AddPiece (this);
			EventManager.OnHotspotInteract += OnHotspotInteract;
			EventManager.OnInventoryAdd_Alt += OnInventoryAdd;
			EventManager.OnInventorySelect += OnInventorySelect;
			EventManager.OnInventoryDeselect += OnInventoryDeselect;
			EventManager.OnInitialiseScene += OnInitialiseScene;
			EventManager.OnTeleport += OnTeleport;

			// Record the current position
			BackupPosition ();
		}


		private void Unregister ()
		{
			if (manager)
			{
				manager.RemovePiece (this);
			}

			// Clear event hooks
			EventManager.OnHotspotInteract -= OnHotspotInteract;
			EventManager.OnInventoryAdd_Alt -= OnInventoryAdd;
			EventManager.OnInventorySelect -= OnInventorySelect;
			EventManager.OnInventoryDeselect -= OnInventoryDeselect;
			EventManager.OnInitialiseScene -= OnInitialiseScene;
			EventManager.OnTeleport -= OnTeleport;
		}


		private InvInstance CreateInventoryItem ()
		{
			InvItem invItem = KickStarter.inventoryManager.GetItem (AssociatedItemID);
			if (invItem == null)
			{
				invItem = new InvItem(AssociatedItemID);
				if (categoryID >= 0) invItem.binID = categoryID;
			}

			var invInstance = new InvInstance (invItem);
			invInstance.Tex = GetComponent<SpriteRenderer> ().sprite.texture;

			return invInstance;
		}


		private int GenerateNewID ()
		{
			if (dynamicItemIDs == null)
			{
				dynamicItemIDs = new List<int> ();
				foreach (InvItem item in KickStarter.inventoryManager.items)
				{
					dynamicItemIDs.Add (item.id);
				}
				dynamicItemIDs.Sort ();
			}

			int newID = dynamicItemIDs.Count > 0 ? dynamicItemIDs[dynamicItemIDs.Count-1] + 1 : 0;
			dynamicItemIDs.Add (newID);
			return newID;
		}


		private void OnInventoryDeselect ()
		{
			if (currentSlot)
			{
				if (!manager.LerpsBack ())
				{
					transform.position = currentSlot.transform.position;
				}
				currentSlot.TurnOff ();
			}
			else if (manager.revertPositionWhenRelease)
			{
				if (!manager.LerpsBack ())
				{
					transform.position = originalPosition;
				}
			}
			else
			{
				Vector3 mousePos = KickStarter.playerInput.GetMousePosition ();
				mousePos.z = transform.position.z - Camera.main.transform.position.z;
				Vector3 newPosition = Camera.main.ScreenToWorldPoint (mousePos);

				if (manager.boundingBox)
				{
					newPosition = manager.boundingBox.bounds.ClosestPoint (newPosition);
				}

				transform.position = newPosition;
			}

			isSelected = false;

			if (currentSlot && currentSlot == correctSlot && manager.lockWhenCorrect == LockWhenCorrect.AfterEachCorrectPlacement)
			{
				// Don't turn on
			}
			else if (!manager.CheckForWin (false) || manager.lockWhenCorrect == LockWhenCorrect.Never)
			{
				ownHotspot.TurnOn ();
			}
		}


		private void BackupPosition ()
		{
			originalPosition = transform.position;
			cameraDepth = Vector3.Dot (transform.position - Camera.main.transform.position, Camera.main.transform.forward);
		}


		private void SetCurrentSlot (Hotspot slotHotspot)
		{
			currentSlot = slotHotspot;
			OnInventoryDeselect ();

			if (currentSlot)
			{
				currentSlot.TurnOff ();
			}

			if (currentSlot && currentSlot == correctSlot && manager.lockWhenCorrect == LockWhenCorrect.AfterEachCorrectPlacement)
			{
				ownHotspot.TurnOff ();
			}
		}

		#endregion


		#region GetSet

		public Hotspot OwnHotspot => ownHotspot;
		public Hotspot CurrentSlot => currentSlot;
		public bool IsSelected => isSelected;

		public int AssociatedItemID
		{
			get
			{
				if (associatedItemID < 0)
				{
					associatedItemID = GenerateNewID ();
				}
				return associatedItemID;
			}
		}

		#endregion

	}

}