//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Handles the spawning and returning of the ItemPackage
//
//=============================================================================

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	[RequireComponent( typeof( Interactable ) )]
	public class ItemPackageSpawner : MonoBehaviour
	{
		public ItemPackage itemPackage
		{
			get
			{
				return _itemPackage;
			}
			set
			{
				CreatePreviewObject();
			}
		}

		public ItemPackage _itemPackage;

		public bool useItemPackagePreview = true;
        private bool useFadedPreview = false;
		private GameObject previewObject;

		public bool requireGrabActionToTake = false;
		public bool requireReleaseActionToReturn = false;
		public bool showTriggerHint = false;

		[EnumFlags]
		public Hand.AttachmentFlags attachmentFlags = Hand.defaultAttachmentFlags;

		public bool takeBackItem = false; // if a hand enters this trigger and has the item this spawner dispenses at the top of the stack, remove it from the stack

		public bool acceptDifferentItems = false;

		private GameObject spawnedItem;
		private bool itemIsSpawned = false;

		public UnityEvent pickupEvent;
		public UnityEvent dropEvent;

		public bool justPickedUpItem = false;


		//-------------------------------------------------
		private void CreatePreviewObject()
		{
			if ( !useItemPackagePreview )
			{
				return;
			}

			ClearPreview();

			if ( useItemPackagePreview )
			{
				if ( itemPackage == null )
				{
					return;
				}

				if ( useFadedPreview == false ) // if we don't have a spawned item out there, use the regular preview
				{
					if ( itemPackage.previewPrefab != null )
					{
						previewObject = Instantiate( itemPackage.previewPrefab, transform.position, Quaternion.identity ) as GameObject;
						previewObject.transform.parent = transform;
						previewObject.transform.localRotation = Quaternion.identity;
					}
				}
				else // there's a spawned item out there. Use the faded preview
				{
					if ( itemPackage.fadedPreviewPrefab != null )
					{
						previewObject = Instantiate( itemPackage.fadedPreviewPrefab, transform.position, Quaternion.identity ) as GameObject;
						previewObject.transform.parent = transform;
						previewObject.transform.localRotation = Quaternion.identity;
					}
				}
			}
		}


		//-------------------------------------------------
		void Start()
		{
			VerifyItemPackage();
		}


		//-------------------------------------------------
		private void VerifyItemPackage()
		{
			if ( itemPackage == null )
			{
				ItemPackageNotValid();
			}

			if ( itemPackage.itemPrefab == null )
			{
				ItemPackageNotValid();
			}
		}


		//-------------------------------------------------
		private void ItemPackageNotValid()
		{
			Debug.LogError("<b>[SteamVR Interaction]</b> ItemPackage assigned to " + gameObject.name + " is not valid. Destroying this game object." );
			Destroy( gameObject );
		}


		//-------------------------------------------------
		private void ClearPreview()
		{
			foreach ( Transform child in transform )
			{
				if ( Time.time > 0 )
				{
					GameObject.Destroy( child.gameObject );
				}
				else
				{
					GameObject.DestroyImmediate( child.gameObject );
				}
			}
		}


		//-------------------------------------------------
		void Update()
		{
			if ( ( itemIsSpawned == true ) && ( spawnedItem == null ) )
			{
				itemIsSpawned = false;
				useFadedPreview = false;
				dropEvent.Invoke();
				CreatePreviewObject();
			}
		}


		//-------------------------------------------------
		private void OnHandHoverBegin( Hand hand )
		{
			ItemPackage currentAttachedItemPackage = GetAttachedItemPackage( hand );

			if ( currentAttachedItemPackage == itemPackage ) // the item at the top of the hand's stack has an associated ItemPackage
			{
				if ( takeBackItem && !requireReleaseActionToReturn ) // if we want to take back matching items and aren't waiting for a trigger press
				{
					TakeBackItem( hand );
				}
			}

			if (!requireGrabActionToTake) // we don't require trigger press for pickup. Spawn and attach object.
			{
				SpawnAndAttachObject( hand, GrabTypes.Scripted );
			}

			if (requireGrabActionToTake && showTriggerHint )
			{
                hand.ShowGrabHint("PickUp");
			}
		}


		//-------------------------------------------------
		private void TakeBackItem( Hand hand )
		{
			RemoveMatchingItemsFromHandStack( itemPackage, hand );

			if ( itemPackage.packageType == ItemPackage.ItemPackageType.TwoHanded )
			{
				RemoveMatchingItemsFromHandStack( itemPackage, hand.otherHand );
			}
		}


		//-------------------------------------------------
		private ItemPackage GetAttachedItemPackage( Hand hand )
		{
			GameObject currentAttachedObject = hand.currentAttachedObject;

			if ( currentAttachedObject == null ) // verify the hand is holding something
			{
				return null;
			}

			ItemPackageReference packageReference = hand.currentAttachedObject.GetComponent<ItemPackageReference>();
			if ( packageReference == null ) // verify the item in the hand is matchable
			{
				return null;
			}

			ItemPackage attachedItemPackage = packageReference.itemPackage; // return the ItemPackage reference we find.

			return attachedItemPackage;
		}


		//-------------------------------------------------
		private void HandHoverUpdate( Hand hand )
		{
			if ( takeBackItem && requireReleaseActionToReturn )
			{
                if (hand.isActive)
				{
					ItemPackage currentAttachedItemPackage = GetAttachedItemPackage( hand );
                    if (currentAttachedItemPackage == itemPackage && hand.IsGrabEnding(currentAttachedItemPackage.gameObject))
					{
						TakeBackItem( hand );
						return; // So that we don't pick up an ItemPackage the same frame that we return it
					}
				}
			}

			if ( requireGrabActionToTake )
			{
                GrabTypes startingGrab = hand.GetGrabStarting();

				if (startingGrab != GrabTypes.None)
				{
					SpawnAndAttachObject( hand, GrabTypes.Scripted);
				}
			}
		}


		//-------------------------------------------------
		private void OnHandHoverEnd( Hand hand )
		{
			if ( !justPickedUpItem && requireGrabActionToTake && showTriggerHint )
			{
                hand.HideGrabHint();
			}

			justPickedUpItem = false;
		}


		//-------------------------------------------------
		private void RemoveMatchingItemsFromHandStack( ItemPackage package, Hand hand )
		{
            if (hand == null)
                return;

			for ( int i = 0; i < hand.AttachedObjects.Count; i++ )
			{
				ItemPackageReference packageReference = hand.AttachedObjects[i].attachedObject.GetComponent<ItemPackageReference>();
				if ( packageReference != null )
				{
					ItemPackage attachedObjectItemPackage = packageReference.itemPackage;
					if ( ( attachedObjectItemPackage != null ) && ( attachedObjectItemPackage == package ) )
					{
						GameObject detachedItem = hand.AttachedObjects[i].attachedObject;
						hand.DetachObject( detachedItem );
					}
				}
			}
		}


		//-------------------------------------------------
		private void RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType packageType, Hand hand )
		{
			for ( int i = 0; i < hand.AttachedObjects.Count; i++ )
			{
				ItemPackageReference packageReference = hand.AttachedObjects[i].attachedObject.GetComponent<ItemPackageReference>();
				if ( packageReference != null )
				{
					if ( packageReference.itemPackage.packageType == packageType )
					{
						GameObject detachedItem = hand.AttachedObjects[i].attachedObject;
						hand.DetachObject( detachedItem );
					}
				}
			}
		}


		//-------------------------------------------------
		private void SpawnAndAttachObject( Hand hand, GrabTypes grabType )
		{
			if ( hand.otherHand != null )
			{
				//If the other hand has this item package, take it back from the other hand
				ItemPackage otherHandItemPackage = GetAttachedItemPackage( hand.otherHand );
				if ( otherHandItemPackage == itemPackage )
				{
					TakeBackItem( hand.otherHand );
				}
			}

			if ( showTriggerHint )
			{
                hand.HideGrabHint();
			}

			if ( itemPackage.otherHandItemPrefab != null )
			{
				if ( hand.otherHand.hoverLocked )
				{
                    Debug.Log( "<b>[SteamVR Interaction]</b> Not attaching objects because other hand is hoverlocked and we can't deliver both items." );
                    return;
				}
			}

			// if we're trying to spawn a one-handed item, remove one and two-handed items from this hand and two-handed items from both hands
			if ( itemPackage.packageType == ItemPackage.ItemPackageType.OneHanded )
			{
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.OneHanded, hand );
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.TwoHanded, hand );
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.TwoHanded, hand.otherHand );
			}

			// if we're trying to spawn a two-handed item, remove one and two-handed items from both hands
			if ( itemPackage.packageType == ItemPackage.ItemPackageType.TwoHanded )
			{
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.OneHanded, hand );
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.OneHanded, hand.otherHand );
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.TwoHanded, hand );
				RemoveMatchingItemTypesFromHand( ItemPackage.ItemPackageType.TwoHanded, hand.otherHand );
			}

			spawnedItem = GameObject.Instantiate( itemPackage.itemPrefab );
			spawnedItem.SetActive( true );
			hand.AttachObject( spawnedItem, grabType, attachmentFlags );

			if ( ( itemPackage.otherHandItemPrefab != null ) && ( hand.otherHand.isActive ) )
			{
				GameObject otherHandObjectToAttach = GameObject.Instantiate( itemPackage.otherHandItemPrefab );
				otherHandObjectToAttach.SetActive( true );
				hand.otherHand.AttachObject( otherHandObjectToAttach, grabType, attachmentFlags );
			}

			itemIsSpawned = true;

			justPickedUpItem = true;

			if ( takeBackItem )
			{
				useFadedPreview = true;
				pickupEvent.Invoke();
				CreatePreviewObject();
			}
		}
	}
}
