/**@class		ReplaceItemWithNewLambdaBase
 * @brief		base class for transformation operations (creating one item from another)
 **/
class ReplaceItemWithNewLambdaBase
{
	EntityAI m_OldItem;
	string m_NewItemType;
	protected ref InventoryLocation m_OldLocation;
	protected ref InventoryLocation m_NewLocation;
	protected bool m_RemoveFromLocationPassed = false;
	private bool m_RemoveNetworkObjectInfoPassed = false;

	void ReplaceItemWithNewLambdaBase(EntityAI old_item, string new_item_type)
	{
		m_OldItem = old_item;
		m_NewItemType = new_item_type;
	}

	void OverrideNewLocation(InventoryLocation newLocation)
	{
		m_NewLocation = newLocation;
	}
	
	void VerifyItemTypeBySlotType() {}

	protected bool WantCreateNewEntity()
	{
		return m_NewLocation && m_NewItemType != string.Empty;
	}

	protected bool CanExecuteLambda()
	{
		if (m_OldItem)
			if (GameInventory.LocationCanRemoveEntity(m_OldLocation))
				//if (WantCreateNewEntity() GameInventory.LocationTestAddEntityType(m_NewItemType, true, true, false, true))
					return true;
		return false;
	}

	/**@fn		PrepareLocations
	 * @brief	Step A. - prepare inventory locations
	 **/
	protected bool PrepareLocations()
	{
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step A) Prepare inventory locations, old_item=" + m_OldItem);
		m_OldLocation = new InventoryLocation;
		if (m_OldItem.GetInventory().GetCurrentInventoryLocation(m_OldLocation)) // A.1) store old location
		{
			if (m_NewLocation == null)
			{
				m_NewLocation = new InventoryLocation;
				m_NewLocation.CopyLocationFrom(m_OldLocation, true);	// A.2) prepare new location from old
				
				//setting proper location type for ground pos
				if (!m_NewLocation.GetParent())
				{
					vector m4[4];
					Math3D.MatrixIdentity4(m4);
					m4[3] = m_NewLocation.GetPos();
					string path = "" + CFG_VEHICLESPATH + " " + m_NewItemType + " inherit_rotation";
					bool keep_rotation = GetGame().ConfigIsExisting(path) && GetGame().ConfigGetInt(path) > 0;
					
					if (m_OldLocation.GetType() == InventoryLocationType.GROUND && keep_rotation)
					{
						float dir[4];
						m_OldLocation.GetDir(dir);
						m_NewLocation.SetGroundEx(null,m_NewLocation.GetPos(),dir);
					}
					else
					{
						m_NewLocation.SetGround(null,m4);
					}
				}
			}
			return true;
		}
		else
			Error("[inv] ReplaceItemWithNewLambdaBase Step A.1) failed to get old_item inventory location");
		return false;
	}

	/**@fn		RemoveOldItemFromLocation
	 * @brief	Step B. - free location for new item
	 * @NOTE	this operation does not delete the object, only removes it from inventory location
	 **/
	protected void RemoveOldItemFromLocation()
	{
		if (!GameInventory.LocationRemoveEntity(m_OldLocation)) // B) remove entity from old inventory location (making it free for new item)
		{
			Error("[inv] ReplaceItemWithNewLambdaBase Step B) failed to remove old_item rom current inventory location");
			m_RemoveFromLocationPassed = false;
		}
		Print("[inv] ReplaceItemWithNewLambdaBase Step B) remove OK, loc=" + InventoryLocation.DumpToStringNullSafe(m_OldLocation));
		m_RemoveFromLocationPassed = true;
	}
	protected void UndoRemoveOldItemFromLocation()
	{
		if (!GameInventory.LocationAddEntity(m_OldLocation)) // B) undo
			Error("[inv] ReplaceItemWithNewLambdaBase Step B) failed to undo remove");
		Print("[inv] ReplaceItemWithNewLambdaBase Step B) undo remove OK, loc=" + InventoryLocation.DumpToStringNullSafe(m_OldLocation));
	}

	/**@fn		RemoveNetworkObjectInfo
	 * @brief	Step C. - remove network part of the object
	 * @NOTE	this operation does not delete the object, only removes its network part (and deletes it on client)
	 **/
	protected void RemoveNetworkObjectInfo()
	{
		GetGame().RemoteObjectTreeDelete(m_OldItem); // C) this forces server to send DeleteObject Message to client. This is needed for preserving the appearance of network operations on client (so that DeleteObject(old) arrives before CreateVehicle(new)). @NOTE: this does not delete the object on server, only it's network representation.
		// @NOTE: the item is not deleted right now on server, but rather after copying the properties in Step E)
		m_RemoveNetworkObjectInfoPassed = true;
	}
	protected void UndoRemoveNetworkObjectInfo()
	{
		GetGame().RemoteObjectTreeCreate(m_OldItem);
	}

	/**@fn		CreateNewEntity
	 * @brief	Step D. - create new entity (LOCAL) with specified type
	 *
	 * @NOTE: if (!m_NewLocation || m_NewItemType.Empty) ==> this function does not create a new entity
	 **/
	protected EntityAI CreateNewEntity()
	{
		if (WantCreateNewEntity())
		{
			VerifyItemTypeBySlotType();
			EntityAI new_item;
			
			switch (m_NewLocation.GetType())
			{
				case InventoryLocationType.GROUND:
					new_item = EntityAI.Cast(GetGame().CreateObjectEx(m_NewItemType,m_NewLocation.GetPos(),ECE_PLACE_ON_SURFACE|ECE_LOCAL));
					string path = "" + CFG_VEHICLESPATH + " " + m_NewItemType + " inherit_rotation";
					bool keep_rotation = GetGame().ConfigIsExisting(path) && GetGame().ConfigGetInt(path) > 0;
					if (keep_rotation)
					{
						new_item.SetOrientation(m_OldItem.GetOrientation()); //this one actually works...debug InventoryLocation
					}
				break;
				case InventoryLocationType.ATTACHMENT:
					// forces rawlocation in C++ to make location Valid
					m_NewLocation.SetAttachment(m_NewLocation.GetParent(), null, m_NewLocation.GetSlot());
					new_item = GameInventory.LocationCreateEntity(m_NewLocation, m_NewItemType, ECE_OBJECT_SWAP, RF_NONE);
				break;
				default:
					new_item = GameInventory.LocationCreateLocalEntity(m_NewLocation, m_NewItemType, ECE_OBJECT_SWAP, RF_NONE);
				break;
			}
			
			if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step D) Created new new_item=" + new_item);
			if (new_item)
			{
				return new_item;
			}
			else
			{							
				return null;

				/*InventoryLocation backupLocation = new InventoryLocation;
				vector mtx[4];
				Math3D.MatrixIdentity4(mtx);
				mtx[3] = m_OldItem.GetPosition();
				backupLocation.SetGround(null, mtx);
				if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step D) plan B - creating=" + m_NewItemType + " at bkp loc=" + backupLocation.DumpToString() + ", but failed");
				new_item = GameInventory.LocationCreateLocalEntity(backupLocation, m_NewItemType,ECE_OBJECT_SWAP,RF_NONE); // create LOCAL new one on ground
				if (!new_item)
				{
					Error("[inv] ReplaceItemWithNewLambdaBase Step D) plan B - wanted to create=" + m_NewItemType + " at bkp loc=" + backupLocation.DumpToString() + ", but failed");
					return null;
				}
				return new_item;*/
			}
		}

		// user did not asked for new entity
		return null;
	}

	/**@fn		CopyOldPropertiesToNew
	 * @brief	Step E. copy properties from old object to the created one
	 *
	 * @NOTE: This is supposed to be overriden in derived classes
	 **/
	void CopyOldPropertiesToNew(notnull EntityAI old_item, EntityAI new_item)
	{
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step E) Copying props " + old_item + " --> " + new_item);
	}

	/**@fn		DeleteOldEntity
	 * @brief	Step F. - deletes physically old item
	 **/
	protected void DeleteOldEntity()
	{
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step F) delete old item=" + m_OldItem);
		m_OldItem.DeleteSafe();
	}
	
	/**@fn		CreateNetworkObjectInfo
	 * @brief	Step G. - create NetworkObjectInfo for new item
	 *
	 * @NOTE: new_item can be null if the lambda did not create any item (intentionaly)
	 **/
	protected void CreateNetworkObjectInfo(EntityAI new_item)
	{
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step G) CreateNetworkObjectInfo =" + new_item);
		if (new_item)
			GetGame().RemoteObjectTreeCreate(new_item); // G) this forces server to send CreateVehicle Message to client. This is needed for preserving the appearance of network operations on client (so that DeleteObject(old) arrives before CreateVehicle(new)). @NOTE: this does not delete the object on server, only it's network representation.
	}

	/**@fn		OnSuccess
	 * @brief	Step H. - notification on finish
	 *
	 * @NOTE: new_item can be null if the lambda did not create any item (intentionaly)
	 **/
	protected void OnSuccess(EntityAI new_item)
	{
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[inv] ReplaceItemWithNewLambdaBase Step H) OnSuccess=" + new_item);
	}

	/**@fn		OnAbort
	 * @brief	Step Out - notification on abort
	 *
	 * @NOTE: new_item can be null if the lambda did not create any item (intentionaly)
	 **/
	protected void OnAbort()
	{
		Print("Error [inv] ReplaceItemWithNewLambdaBase OnAbort");
	}

	void Execute(HumanInventoryWithFSM fsm_to_notify = null)
	{
		int t = GetGame().GetTime();
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[syncinv] t=" + t + " lambda.Execute start ");

		// A) init
		bool prepared = PrepareLocations();

		if (prepared && CanExecuteLambda())
		{
			// B) rm old (and delete on client)
			RemoveOldItemFromLocation();
			if (!m_RemoveFromLocationPassed)
			{
				Error("[inv] ReplaceItemWithNewLambdaBase Step B) ABORT - failed while rm old item from loc=" + InventoryLocation.DumpToStringNullSafe(m_OldLocation));

				if (fsm_to_notify)
					fsm_to_notify.ProcessHandAbortEvent(new HandEventHumanCommandActionAborted(fsm_to_notify.GetManOwner()));
				OnAbort();
				return;
			}

			// C) rm NetworkObjectInfo from old item (+ send delete object tree to clients)
			RemoveNetworkObjectInfo();

			// D) mk new in place of old
			EntityAI new_item = CreateNewEntity();
			if (WantCreateNewEntity() && new_item == null)
			{
				//Error("[inv] ReplaceItemWithNewLambdaBase Step D) ABORT - wanted to create=" + m_NewItemType + " at loc=" + m_NewLocation.DumpToString() + ", but failed");
				Print("Error [inv] ReplaceItemWithNewLambdaBase Step D) ABORT - wanted to create=" + m_NewItemType + " at loc=" + InventoryLocation.DumpToStringNullSafe(m_NewLocation) + ", but failed");
				if (m_RemoveFromLocationPassed)
					UndoRemoveOldItemFromLocation();
				if (m_RemoveNetworkObjectInfoPassed)
					UndoRemoveNetworkObjectInfo();

				OnAbort();
				if (fsm_to_notify)
					fsm_to_notify.ProcessHandAbortEvent(new HandEventHumanCommandActionAborted(fsm_to_notify.GetManOwner()));
				return;
			}

			// E) cpy props
			CopyOldPropertiesToNew(m_OldItem, new_item);

			// F) del old
			DeleteOldEntity();

			// G) mk new NetworkObjectInfo
			CreateNetworkObjectInfo(new_item);

			// H) notification
			OnSuccess(new_item);
		}
		else
		{
			Print("[syncinv] warning, lambda cannot be executed, skipping!");
			if (fsm_to_notify)
				fsm_to_notify.ProcessHandAbortEvent(new HandEventHumanCommandActionAborted(fsm_to_notify.GetManOwner()));
			OnAbort();
			return;
		}
		int te = GetGame().GetTime();
		int dt = te - t;
		if (LogManager.IsInventoryHFSMLogEnable()) hndDebugPrint("[syncinv] te=" + te + " lambda.Execute end, exec time=" + dt);
	}

	string DumpToString()
	{
		string s = "{ old=" + m_OldItem + " newType=" + m_NewItemType + "}";
		return s;
	}
};


class ItemInventory : GameInventory
{
};
