﻿using UnityEngine;
using System.Collections;

namespace AC.CombatExample
{

	public class PlayerCombat : CombatCharacter
	{

		// The Player's combat script, allowing for them to equip a weapon, attack, and take damage

		#region Variables

		[SerializeField] private Player player = null;
		[SerializeField] private float aimSpeedFactor = 0.5f;
		[SerializeField] private bool autoReload;
		[SerializeField] private ActionListAsset actionListOnDie = null;

		private Weapon heldWeapon;
		private InvInstance heldWeaponInstance;
		private bool isAiming;

		[SerializeField] private int fireRatePropertyID = 1;
		[SerializeField] private int ammoIDPropertyID = 2;
		[SerializeField] private int aimAnimPropertyID = 4;
		[SerializeField] private int attackAnimPropertyID = 5;
		[SerializeField] private int ammoInChamberPropertyID = 3;
		[SerializeField] private int ammoCapacityPropertyID = 0;
		
		[SerializeField] private int useIconID = 4;

		[SerializeField] private string totalAmmoVariableName = "TotalAmmo";
		private GVar ammoCountVariable;
		[SerializeField] private string equippedWeaponVariableName = "EquippedWeaponID";
		private GVar equippedWeaponVariable;

		private const float takeDamageInputDelay = 0.6f;

		#endregion


		#region UnityStandards

		private void Start ()
		{
			player = GetComponent <Player>();

			originalWalkSpeed = player.walkSpeedScale;
			originalRunSpeed = player.runSpeedScale;
			originalTurnSpeed = player.turnSpeed;
		}


		private void OnEnable ()
		{
			EventManager.OnInventoryInteract_Alt += OnInventoryInteract;
			EventManager.OnFinishLoading += OnFinishLoading;
			EventManager.OnExitGameState += OnExitGameState;
			EventManager.OnCharacterHoldObject += OnCharacterHoldObject;
			EventManager.OnCharacterDropObject += OnCharacterDropObject;
		}


		private void OnDisable ()
		{
			EventManager.OnInventoryInteract_Alt -= OnInventoryInteract;
			EventManager.OnFinishLoading -= OnFinishLoading;
			EventManager.OnExitGameState -= OnExitGameState;
			EventManager.OnCharacterHoldObject -= OnCharacterHoldObject;
			EventManager.OnCharacterDropObject -= OnCharacterDropObject;
		}


		private void Update ()
		{
			// Don't move if dead
			if (IsDead () || KickStarter.stateHandler.MovementIsOff)
			{
				SetAimState (false);
				return;
			}

			if (KickStarter.stateHandler.IsInGameplay ())
			{
				if (CanAttack ())
				{
					// Recieve aim/fire input if in gameplay and a weapon is equipped

					SetAimState (KickStarter.playerInput.InputGetButton ("Aim"));

					if (IsAiming ())
					{
						if (KickStarter.playerInput.InputGetButtonDown ("Fire1"))
						{
							Attack ();

							// Prevent Interaction input from being received in the same frame as firing a weapon (so that the two cannot occur simultaneously)
							KickStarter.playerInteraction.IgnoreInputThisFrame ();
						}
						else if (KickStarter.playerInput.InputGetButtonDown ("Reload"))
						{
							Reload (heldWeaponInstance);

							// Prevent Interaction input from being received in the same frame as firing a weapon (so that the two cannot occur simultaneously)
							KickStarter.playerInteraction.IgnoreInputThisFrame ();
						}
					}
				}
			}
		}

		#endregion


		#region PublicFunctions

		public bool IsAiming ()
		{
			return isAiming;
		}

		#endregion


		#region ProtectedFunctions

		protected override void OnDie ()
		{
			AudioSource.PlayClipAtPoint (dieSound, transform.position);
			PlayAnim ("Die", animCrossfadeTime, true, 0);
			player.motionControl = MotionControl.Manual;
			PlayerMenus.GetMenuWithName ("Inventory").isLocked = true;
		
			if (actionListOnDie)
			{
				actionListOnDie.Interact ();
			}
		}


		protected override void OnTakeDamage ()
		{
			AudioSource.PlayClipAtPoint (takeDamageSound, transform.position);
			PlayAnim ("TakeDamage", animCrossfadeTime, true, 0);

			SetAttackDelay (takeDamageInputDelay);
			StartCoroutine (DisableMovement (takeDamageInputDelay));
		}

		
		protected IEnumerator DisableMovement (float duration)
		{
			player.walkSpeedScale = 0f;
			player.runSpeedScale = 0f;
			player.turnSpeed = 0f;
			yield return new WaitForSeconds (duration);
			player.walkSpeedScale = originalWalkSpeed;
			player.runSpeedScale = originalRunSpeed;
			player.turnSpeed = originalTurnSpeed;
		}


		protected override void Attack ()
		{
			if (isAiming && InvInstance.IsValid (heldWeaponInstance))
			{
				float fireRate = heldWeaponInstance.GetProperty (fireRatePropertyID).FloatValue;
				int ammoID = heldWeaponInstance.GetProperty (ammoIDPropertyID).IntegerValue;

				if (ammoID >= 0)
				{
					// Has ammo?
					int ammoInChamber = heldWeaponInstance.GetProperty (ammoInChamberPropertyID).IntegerValue;
					if (ammoInChamber <= 0)
					{
						if (autoReload)
						{
							Reload (heldWeaponInstance);
						}
						return;
					}

					// Deduct ammo
					heldWeaponInstance.GetProperty (ammoInChamberPropertyID).IntegerValue -= 1;
					UpdateAmmoDisplay ();
				}

				// Animate
				string attackState = heldWeaponInstance.GetProperty (attackAnimPropertyID).TextValue;
				PlayAnim (attackState, 0f, false, 1);

				heldWeapon.OnAttack (player);

				SetAttackDelay (fireRate);
			}
		}


		public void Reload (InvInstance weaponInstance)
		{
			if (!InvInstance.IsValid (weaponInstance))
			{
				return;
			}

			int ammoCapacity = weaponInstance.GetProperty (ammoCapacityPropertyID).IntegerValue;
			int ammoInChamber = weaponInstance.GetProperty (ammoInChamberPropertyID).IntegerValue;
			if (ammoCapacity <= 0 || ammoInChamber >= ammoCapacity)
			{
				return;
			}

			int chamberSpace = ammoCapacity - ammoInChamber;

			int ammoID = weaponInstance.GetProperty (ammoIDPropertyID).IntegerValue;
			int ammoLeft = KickStarter.runtimeInventory.PlayerInvCollection.GetCount (ammoID);
			if (ammoLeft <= 0)
			{
				return;
			}

			int ammoToReload = Mathf.Min (ammoLeft, chamberSpace);

			KickStarter.runtimeInventory.PlayerInvCollection.Delete (ammoID, ammoToReload);
			weaponInstance.GetProperty (ammoInChamberPropertyID).IntegerValue += ammoToReload;
			UpdateAmmoDisplay ();

			if (weaponInstance == heldWeaponInstance)
			{
				heldWeapon.OnReload (player, true);
			}
			else
			{
				GameObject heldWeaponGO = weaponInstance.InvItem.linkedPrefab;
				heldWeaponGO.GetComponent<Weapon> ().OnReload (player, false);
			}

			SetAttackDelay (heldWeapon.ReloadTime);
		}

		#endregion


		#region CustomEvents

		private void OnExitGameState (GameState gameState)
		{
			// Stop aiming once exit gameplay
			if (gameState == GameState.Normal)
			{
				SetAimState (false);
			}
		}


		private void OnInventoryInteract (InvInstance invInstance, int iconID)
		{
			// If a Herb item is used, boost health
			if (invInstance.InvItem.label == "Herb" && iconID == useIconID)
			{
				KickStarter.runtimeInventory.PlayerInvCollection.Delete (invInstance);
				TakeDamage (-15);
			}
		}


		private void OnFinishLoading (int saveID)
		{
			if (!IsDead ())
			{
				player.motionControl = MotionControl.Automatic;
			}
		}


		private void OnCharacterHoldObject (Char character, GameObject objectToHold, int attachmentPointID)
		{
			if (character != player) return;

			SceneItem sceneItem = objectToHold.GetComponent<SceneItem> ();
			if (sceneItem == null) return;

			isAiming = false;

			// Now spawn the weapon into the scene.  This is the "Linked prefab" as set in the Weapon Inventory Item's list of properties
			heldWeapon = objectToHold.GetComponent <Weapon>();
			heldWeaponInstance = sceneItem.LinkedInvInstance;
			EquippedWeaponID = InvInstance.IsValid (heldWeaponInstance) ? heldWeaponInstance.ItemID : -1;

			objectToHold.SendMessage ("InitForScene", null, SendMessageOptions.DontRequireReceiver);

			SetAttackDelay (0.3f);
			UpdateAmmoDisplay ();
		}


		private void OnCharacterDropObject (Char character, GameObject objectToHold, int attachmentPointID)
		{
			if (character != player) return;

			SceneItem sceneItem = objectToHold.GetComponent<SceneItem> ();
			if (sceneItem == null) return;

			// Destroy the weapon's spawned prefab
			isAiming = false;

			heldWeapon = null;
			heldWeaponInstance = null;
			EquippedWeaponID = -1;

			SetAttackDelay (0.3f);
			UpdateAmmoDisplay ();
		}

		#endregion


		#region PrivateFunctions

		private void UpdateAmmoDisplay ()
		{
			if (InvInstance.IsValid (heldWeaponInstance))
			{
				int ammoCapacity = heldWeaponInstance.GetProperty (ammoCapacityPropertyID).IntegerValue;
				if (ammoCapacity > 0)
				{
					int ammoInChamber = heldWeaponInstance.GetProperty (ammoInChamberPropertyID).IntegerValue;
					AmmoCount = ammoInChamber.ToString () + " / " + ammoCapacity.ToString ();
					return;
				}
			}
			AmmoCount = string.Empty;
		}


		private void SetAimState (bool value)
		{
			if (isAiming == value) return;

			if (InvInstance.IsValid (heldWeaponInstance))
			{
				isAiming = value;

				if (isAiming)
				{
					// Play the Inventory item's "aim anim" property, and reduce the movement speed

					string aimState = heldWeaponInstance.GetProperty (aimAnimPropertyID).TextValue;
					PlayAnim (aimState, animCrossfadeTime, true, 1);

					player.walkSpeedScale *= aimSpeedFactor;
					player.runSpeedScale *= aimSpeedFactor;
					player.turnSpeed *= aimSpeedFactor;
				}
				else
				{
					PlayAnim ("Empty", animCrossfadeTime, true, 1);

					player.walkSpeedScale = originalWalkSpeed;
					player.runSpeedScale = originalRunSpeed;
					player.turnSpeed = originalTurnSpeed;
				}

				SetAttackDelay (0.15f);
			}
		}


		private void PlayAnim (string animName, float fadeTime, bool inFixedTime, int layer)
		{
			int hash = Animator.StringToHash (animName);
			if (player.GetAnimator ().HasState (layer, hash))
			{
				if (inFixedTime)
				{
					player.GetAnimator ().CrossFadeInFixedTime (hash, fadeTime, layer);
				}
				else
				{
					player.GetAnimator ().CrossFade (hash, fadeTime, layer);
				}
			}
			else
			{
				ACDebug.LogWarning ("Cannot play clip " + animName + " on " + player.GetAnimator ().name, player.GetAnimator ());
			}
		}

		#endregion


		#region GetSet

		protected string AmmoCount
		{
			get
			{
				if (ammoCountVariable == null) ammoCountVariable = variables.GetVariable (totalAmmoVariableName);
				return ammoCountVariable.TextValue;
			}
			set
			{
				if (ammoCountVariable == null) ammoCountVariable = variables.GetVariable (totalAmmoVariableName);
				ammoCountVariable.TextValue = value;
			}
		}


		protected int EquippedWeaponID
		{
			get
			{
				if (equippedWeaponVariable == null) equippedWeaponVariable = variables.GetVariable (equippedWeaponVariableName);
				return equippedWeaponVariable.IntegerValue;
			}
			set
			{
				if (equippedWeaponVariable == null) equippedWeaponVariable = variables.GetVariable (equippedWeaponVariableName);
				equippedWeaponVariable.IntegerValue = value;
			}
		}

		#endregion


		#region Singleton

		private static PlayerCombat instance;

		public static PlayerCombat Instance
		{
			get
			{
				if (instance == null)
				{
					#if UNITY_2022_3_OR_NEWER
					instance = Object.FindFirstObjectByType <PlayerCombat> ();
					#else
					instance = Object.FindObjectOfType <PlayerCombat> ();
					#endif
				}
				return instance;
			}
		}

		#endregion

	}

}