﻿using UnityEngine;

namespace AC.Downloads.ArrangingPuzzle
{

	public enum LockWhenCorrect { Never, AfterEachCorrectPlacement, OnceAllCorrect };


	public class ArrangingPuzzleManager : MonoBehaviour
	{

		#region Variables

		[Header ("Objects")]
		[SerializeField] private Hotspot[] slots = new Hotspot[0];
		[SerializeField] private ArrangingPuzzlePiece[] pieces = new ArrangingPuzzlePiece[0];

		[Header ("Settings")]
		[SerializeField] private ActionList actionListOnWin = null;
		[SerializeField] private SelectedPiecePosition selectedPiecePosition = SelectedPiecePosition.Hidden;

		private enum SelectedPiecePosition { Hidden, RemainInPlace, FollowCursor };
		[SerializeField] private float followCursorReleaseSpeed = 0f;
		public BoxCollider2D boundingBox = null;
		private InventoryHandling backupInventoryHandline;
		public ArrangingPuzzlePiece SelectedPiece { get; private set; }

		public LockWhenCorrect lockWhenCorrect = LockWhenCorrect.AfterEachCorrectPlacement;
		public bool onlyCorrectAccepted = false;
		public bool revertPositionWhenRelease = true;
		[Range (0f, 0.02f)] public float dragThreshold = 0f;

		#endregion


		#region UnityStandards

		private void OnEnable ()
		{
			EventManager.OnInventorySelect += OnInventorySelect;
			EventManager.OnInventoryDeselect += OnInventoryDeselect;

			backupInventoryHandline = KickStarter.cursorManager.inventoryHandling;
		}


		private void OnDisable ()
		{
			EventManager.OnInventorySelect -= OnInventorySelect;
			EventManager.OnInventoryDeselect -= OnInventoryDeselect;

			KickStarter.cursorManager.inventoryHandling = backupInventoryHandline;
		}

		#endregion


		#region PublicFunctions

		public bool CheckForWin (bool runActionListIfTrue)
		{
			foreach (ArrangingPuzzlePiece piece in pieces)
			{
				if (piece && piece.HasCorrectSlot () && !piece.IsInCorrectSlot ())
				{
					return false;
				}
			}

			switch (lockWhenCorrect)
			{
				case LockWhenCorrect.Never:
					break;	

				default:
					foreach (ArrangingPuzzlePiece piece in pieces)
					{
						if (piece && piece.OwnHotspot) piece.OwnHotspot.TurnOff ();
					} 
					break;
			}

			foreach (Hotspot slot in slots)
			{
				slot.TurnOff ();
			} 

			if (actionListOnWin && runActionListIfTrue)
			{
				actionListOnWin.Interact ();
			}
			return true;
		}


		public bool HotspotIsSlot (Hotspot hotspot)
		{
			foreach (Hotspot slot in slots)
			{
				if (slot && slot == hotspot) return true;
			}
			return false;
		}


		public ArrangingPuzzlePiece GetPiece (int itemID)
		{
			foreach (ArrangingPuzzlePiece piece in pieces)
			{
				if (piece && piece.HasAssociatedID (itemID)) return piece;
			}
			return null;
		}


		public void UpdateSelectedPiecePosition (Transform pieceTransform, float cameraDepth, float timeSelected)
		{
			Vector3 newPosition = pieceTransform.position;

			switch (selectedPiecePosition)
			{
				case SelectedPiecePosition.Hidden:
					newPosition = new Vector2(1000f, 0f);
					break;

				case SelectedPiecePosition.FollowCursor:
					Vector3 screenPosition = new Vector3 (KickStarter.playerInput.GetMousePosition ().x, KickStarter.playerInput.GetMousePosition ().y, cameraDepth);
					Vector3 worldPosition = Camera.main.ScreenToWorldPoint (screenPosition);

					float p = 1f;
					if (LerpsBack ())
					{
						p = Mathf.Clamp01 (Time.time - timeSelected);
					}
					newPosition = Vector3.Lerp (newPosition, worldPosition, p);
					break;

				default:
					break;
			}

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


		public void UpdateDeselectedPiecePosition (Transform pieceTransform, Vector2 newPosition)
		{
			if (boundingBox)
			{
				newPosition = boundingBox.bounds.ClosestPoint (newPosition);
			}

			switch (selectedPiecePosition)
			{
				case SelectedPiecePosition.FollowCursor:
					pieceTransform.position = Vector3.Lerp (pieceTransform.position, newPosition, Time.deltaTime * followCursorReleaseSpeed);
					break;

				default:
					break;
			}
		}


		public bool LerpsBack ()
		{
			return followCursorReleaseSpeed > 0f && selectedPiecePosition == SelectedPiecePosition.FollowCursor;
		}


		public void AddPiece (ArrangingPuzzlePiece piece)
		{
			for (int i = 0; i < pieces.Length; i++)
			{
				if (pieces[i] == piece)
				{
					return;
				}
			}

			System.Array.Resize (ref pieces, pieces.Length + 1);
			pieces[pieces.Length - 1] = piece;
		}


		public void RemovePiece (ArrangingPuzzlePiece piece)
		{
			for (int i = 0; i < pieces.Length; i++)
			{
				if (pieces[i] == piece)
				{
					pieces[i] = null;
					return;
				}
			}
		}

		#endregion


		#region CustomEvents

		private void OnInventorySelect (InvItem invItem)
		{
			foreach (ArrangingPuzzlePiece piece in pieces)
			{
				if (piece && piece.AssociatedItemID == invItem.id)
				{
					KickStarter.cursorManager.inventoryHandling = InventoryHandling.DoNothing;
					SelectedPiece = piece;
					return;
				}
			}
		}


		private void OnInventoryDeselect (InvItem invItem)
		{
			foreach (ArrangingPuzzlePiece piece in pieces)
			{
				if (piece && piece.AssociatedItemID == invItem.id)
				{
					KickStarter.cursorManager.inventoryHandling = backupInventoryHandline;
					SelectedPiece = null;
					return;
				}
			}
		}

		#endregion


		#region StaticFunctions

		public static ArrangingPuzzleManager Get (ArrangingPuzzlePiece _piece)
		{
			if (_piece == null) return null;

			#if UNITY_2022_3_OR_NEWER
			ArrangingPuzzleManager[] managers = UnityEngine.Object.FindObjectsByType <ArrangingPuzzleManager> (FindObjectsSortMode.None);
			#else
			ArrangingPuzzleManager[] managers = UnityEngine.Object.FindObjectsOfType<ArrangingPuzzleManager> ();
			#endif
			foreach (ArrangingPuzzleManager manager in managers)
			{
				foreach (ArrangingPuzzlePiece piece in manager.pieces)
				{
					if (piece == _piece)
					{
						return manager;
					}
				}
			}
			return null;
		}

		#endregion


		#region GetSet

		public Hotspot[] Slots { get { return slots; }}
		public ArrangingPuzzlePiece[] Pieces { get { return pieces; }}

		#endregion

	}

}