importonly class IMarketSystem extends ScriptableSystem
{
	public import function AddVendorHashMap( key : EntityID, tweakID : TweakDBID, vendor : IScriptable ) : Bool;
	public import function ClearVendorHashMap();
	public import const function GetVendorHashMap( key : EntityID ) : weak< IScriptable >;
	public import const function GetVendorTBIDHashMap( key : TweakDBID ) : weak< IScriptable >;
}

class MarketSystem extends IMarketSystem
{
	private persistent var m_vendors : array< Vendor >;
	private var m_vendingMachinesVendors : array< Vendor >;

	private export override function OnDetach()
	{
		var vendor : Vendor;
		var i : Int32;
		ClearVendorHashMap();
		for( i = m_vendors.Size() - 1; i >= 0; i -= 1 )
		{
			vendor = m_vendors[ i ];
			if( vendor.GetVendorType() == gamedataVendorType.VendingMachine )
			{
				m_vendors.Remove( vendor );
			}
		}
	}

	private export override function OnRestored( saveVersion : Int32, gameVersion : Int32 )
	{
		var i, j, numberOfRipperdocsVisited : Int32;
		var vendor : Vendor;
		var successfullyMapped, forceReinit : Bool;
		var player : GameObject;
		var playerItemsToRemove : array< weak< gameItemData > >;
		var checkedItemData : gameItemData;
		ClearVendorHashMap();
		if( saveVersion < 262 )
		{
			player = GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject();
			if( player )
			{
				GameInstance.GetTransactionSystem( GetGameInstance() ).GetItemList( player, playerItemsToRemove );
			}
		}
		forceReinit = ( ( saveVersion < 249 ) ? ( true ) : ( false ) );
		for( i = m_vendors.Size() - 1; i >= 0; i -= 1 )
		{
			vendor = m_vendors[ i ];
			vendor.OnRestored( GetGameInstance(), forceReinit );
			for( j = 0; j < playerItemsToRemove.Size(); j += 1 )
			{
				checkedItemData = playerItemsToRemove[ j ];
				if( checkedItemData && ItemID.GetStructure( checkedItemData.GetID() ) == gamedataItemStructure.Unique )
				{
					vendor.RemoveItemIDFromStock( checkedItemData.GetID() );
				}
			}
			if( vendor.GetVendorType() == gamedataVendorType.VendingMachine )
			{
				m_vendors.Remove( vendor );
				continue;
			}
			else if( vendor.GetVendorType() == gamedataVendorType.RipperDoc )
			{
				if( m_vendors[ i ].GetLastInteractionTime() != 0.0 )
				{
					numberOfRipperdocsVisited += 1;
				}
			}
			successfullyMapped = AddVendorHashMap( PersistentID.ExtractEntityID( vendor.GetVendorPersistentID() ), vendor.GetVendorTweakID(), vendor );
			if( !( successfullyMapped ) )
			{
				m_vendors.Remove( vendor );
				continue;
			}
		}
		GameInstance.GetQuestsSystem( GetGameInstance() ).SetFact( 'number_of_ripperdocs_visited', numberOfRipperdocsVisited );
	}

	public const function GetVendor( vendorObject : GameObject ) : Vendor
	{
		var vendor : Vendor;
		vendor = ( ( Vendor )( GetVendorHashMap( vendorObject.GetEntityID() ) ) );
		return vendor;
	}

	private function GetOrAddVendor( vendorObject : GameObject ) : Vendor
	{
		var vendor : IScriptable;
		vendor = GetVendorHashMap( vendorObject.GetEntityID() );
		if( vendor )
		{
			return ( ( Vendor )( vendor ) );
		}
		return AddVendor( vendorObject );
	}

	private function GetVendorByTDBID( vendorDataID : TweakDBID ) : Vendor
	{
		var vendor : IScriptable;
		vendor = GetVendorTBIDHashMap( vendorDataID );
		if( vendor )
		{
			return ( ( Vendor )( vendor ) );
		}
		return NULL;
	}

	private function AddVendor( vendorObject : GameObject ) : Vendor
	{
		var vendor : Vendor;
		var vendorRecord : weak< Vendor_Record >;
		var vendorID : TweakDBID;
		vendorID = MarketSystem.GetVendorID( vendorObject );
		vendorRecord = TweakDBInterface.GetVendorRecord( vendorID );
		if( vendorRecord )
		{
			vendor = new Vendor;
			vendor.Initialize( GetGameInstance(), vendorID, vendorObject );
			vendor.SetPersistentID( vendorObject.GetPersistentID() );
			AddVendorHashMap( vendorObject.GetEntityID(), vendor.GetVendorTweakID(), vendor );
			if( vendor.GetVendorType() == gamedataVendorType.VendingMachine )
			{
				m_vendingMachinesVendors.PushBack( vendor );
			}
			else
			{
				m_vendors.PushBack( vendor );
			}
			return vendor;
		}
		return NULL;
	}

	public static function IsAccessible( player : weak< PlayerPuppet >, vendorID : TweakDBID ) : Bool
	{
		var accessPrereqs : array< weak< IPrereq_Record > >;
		TweakDBInterface.GetVendorRecord( vendorID ).AccessPrereqs( accessPrereqs );
		return RPGManager.CheckPrereqs( accessPrereqs, player );
	}

	public static function IsVisibleOnMap( player : weak< PlayerPuppet >, vendorID : TweakDBID ) : Bool
	{
		var mapVisibilityPrereqs : array< weak< IPrereq_Record > >;
		TweakDBInterface.GetVendorRecord( vendorID ).MapVisibilityPrereqs( mapVisibilityPrereqs );
		return RPGManager.CheckPrereqs( mapVisibilityPrereqs, player );
	}

	public static function GetBuyPrice( vendorObject : weak< GameObject >, itemID : ItemID ) : Int32
	{
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		marketSystem = MarketSystem.GetInstance( vendorObject.GetGame() );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return RPGManager.CalculateBuyPrice( vendorObject.GetGame(), vendorObject, itemID, vendor.GetPriceMultiplier() );
	}

	public static function GetBuyPrice( game : GameInstance, vendorID : EntityID, itemID : ItemID ) : Int32
	{
		var vendorObject : weak< GameObject >;
		vendorObject = ( ( GameObject )( GameInstance.FindEntityByID( game, vendorID ) ) );
		return GetBuyPrice( vendorObject, itemID );
	}

	public static function Money() : ItemID
	{
		return ItemID.FromTDBID( T"Items.money" );
	}

	public static function OnVendorMenuOpen( vendorObject : GameObject )
	{
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		marketSystem = MarketSystem.GetInstance( vendorObject.GetGame() );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		vendor.OnVendorMenuOpen();
	}

	public static function GetVendorMoney( vendorObject : weak< GameObject > ) : Int32
	{
		var gameInstance : GameInstance;
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		gameInstance = vendorObject.GetGame();
		marketSystem = MarketSystem.GetInstance( gameInstance );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return vendor.GetMoney();
	}

	public static function GetVendorItemsForSale( vendorObject : weak< GameObject >, checkPlayerCanBuy : Bool ) : array< SItemStack >
	{
		var gameInstance : GameInstance;
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		gameInstance = vendorObject.GetGame();
		marketSystem = MarketSystem.GetInstance( gameInstance );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return vendor.GetItemsForSale( checkPlayerCanBuy );
	}

	public static function GetVendorSoldItems( vendorObject : weak< GameObject > ) : SoldItemsCache
	{
		var gameInstance : GameInstance;
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		gameInstance = vendorObject.GetGame();
		marketSystem = MarketSystem.GetInstance( gameInstance );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return vendor.GetSoldItems();
	}

	public static function GetVendorCyberwareForSale( vendorObject : weak< GameObject >, checkPlayerCanBuy : Bool ) : array< SItemStack >
	{
		var gameInstance : GameInstance;
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		gameInstance = vendorObject.GetGame();
		marketSystem = MarketSystem.GetInstance( gameInstance );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return vendor.GetCyberwareForSale( checkPlayerCanBuy );
	}

	public static function GetItemsPlayerCanSell( vendorObject : weak< GameObject >, allowQuestItems : Bool, excludeEquipped : Bool ) : array< SItemStack >
	{
		var gameInstance : GameInstance;
		var marketSystem : MarketSystem;
		var vendor : Vendor;
		gameInstance = vendorObject.GetGame();
		marketSystem = MarketSystem.GetInstance( gameInstance );
		vendor = marketSystem.GetOrAddVendor( vendorObject );
		return vendor.GetItemsPlayerCanSell( allowQuestItems, excludeEquipped );
	}

	public static function CanPlayerSellItem( vendorObject : weak< GameObject >, itemID : ItemID, allowQuestItems : Bool, excludeEquipped : Bool ) : Bool
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).PlayerCanSell( itemID, allowQuestItems, excludeEquipped );
	}

	public static function IsNewItem( vendorObject : weak< GameObject >, itemTDBID : TweakDBID ) : Bool
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).IsNewItem( itemTDBID );
	}

	public static function ItemInspected( vendorObject : weak< GameObject >, itemTDBID : TweakDBID )
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).ItemInspected( itemTDBID );
	}

	public static function GetNewItems( vendorObject : weak< GameObject > ) : array< TweakDBID >
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).GetNewItems();
	}

	public static function DoesEquipAreaContainNewItems( vendorObject : weak< GameObject >, area : gamedataEquipmentArea, checkPlayerCanBuy : Bool ) : Bool
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).DoesEquipAreaContainNewItems( area, checkPlayerCanBuy );
	}

	public static function IsAttached( vendorObject : weak< GameObject > ) : Bool
	{
		return MarketSystem.GetInstance( vendorObject.GetGame() ).GetOrAddVendor( vendorObject ).IsAttached();
	}

	public static function GetVendorID( vendor : weak< GameObject > ) : TweakDBID
	{
		var puppet : weak< ScriptedPuppet >;
		var vendingMachine : VendingMachine;
		var dropPoint : DropPoint;
		var tweakDBId : TweakDBID;
		var vendorRecord : Character_Record;
		vendingMachine = ( ( VendingMachine )( vendor ) );
		if( vendingMachine )
		{
			return vendingMachine.GetVendorID();
		}
		dropPoint = ( ( DropPoint )( vendor ) );
		if( dropPoint )
		{
			return TDBID.Create( ( ( DropPointControllerPS )( dropPoint.GetDevicePS() ) ).GetVendorRecordPath() );
		}
		else
		{
			puppet = ( ( ScriptedPuppet )( vendor ) );
			if( puppet )
			{
				vendorRecord = TweakDBInterface.GetCharacterRecord( puppet.GetRecordID() );
				if( vendorRecord && vendorRecord.VendorID() )
				{
					return vendorRecord.VendorID().GetID();
				}
			}
		}
		return tweakDBId;
	}

	public static function GetInstance( gameInstance : GameInstance ) : MarketSystem
	{
		return ( ( MarketSystem )( GameInstance.GetScriptableSystemsContainer( gameInstance ).Get( 'MarketSystem' ) ) );
	}

	private export function OnAttachVendorRequest( request : AttachVendorRequest )
	{
		var vendor : Vendor;
		vendor = GetOrAddVendor( request.owner );
		vendor.OnAttach( request.owner );
	}

	private export function OnDeattachVendorRequest( request : DeattachVendorRequest )
	{
		var vendor : Vendor;
		vendor = GetOrAddVendor( request.owner );
		vendor.OnDeattach( request.owner );
	}

	private function OnBuyRequest( request : BuyRequest )
	{
		var itemsStack : array< SItemStack >;
		var itemStack : SItemStack;
		var i, limit : Int32;
		for( i = 0, limit = request.items.Size(); i < limit; i += 1 )
		{
			itemStack.itemID = request.items[ i ].itemID;
			itemStack.quantity = request.items[ i ].quantity;
			itemStack.dynamicTags = RPGManager.GetItemRecord( itemStack.itemID ).Tags();
			itemsStack.PushBack( itemStack );
		}
		GetOrAddVendor( request.owner ).BuyItemsFromVendor( itemsStack, request.requestID );
	}

	private function OnBuybackRequest( request : BuybackRequest )
	{
		var itemStack : SItemStack;
		var itemsStack : array< SItemStack >;
		var i, limit : Int32;
		for( i = 0, limit = request.items.Size(); i < limit; i += 1 )
		{
			itemStack.itemID = request.items[ i ].itemID;
			itemStack.quantity = request.items[ i ].quantity;
			itemsStack.PushBack( itemStack );
		}
		GetOrAddVendor( request.owner ).BuybackItemsFromVendor( itemsStack, request.requestID );
	}

	private function OnSellRequest( request : SellRequest )
	{
		var itemsStack : array< SItemStack >;
		var itemStack : SItemStack;
		var i, limit : Int32;
		for( i = 0, limit = request.items.Size(); i < limit; i += 1 )
		{
			itemStack.itemID = request.items[ i ].itemID;
			itemStack.quantity = request.items[ i ].quantity;
			itemStack.powerLevel = RoundMath( request.items[ i ].powerLevel * 100.0 );
			itemsStack.PushBack( itemStack );
		}
		GetOrAddVendor( request.owner ).SellItemsToVendor( itemsStack, request.requestID );
	}

	private export function OnDispenseRequest( request : DispenseRequest )
	{
		var playerMoney : Int32;
		var buyPrice : Int32;
		var tSystem : TransactionSystem;
		if( request.shouldPay )
		{
			tSystem = GameInstance.GetTransactionSystem( GetGameInstance() );
			playerMoney = tSystem.GetItemQuantity( GetPlayer( GetGameInstance() ), MarketSystem.Money() );
			buyPrice = MarketSystem.GetBuyPrice( request.owner, request.itemID );
			if( playerMoney < buyPrice )
			{
				return;
			}
			else
			{
				tSystem.RemoveItem( GetPlayer( GetGameInstance() ), MarketSystem.Money(), buyPrice );
			}
		}
		GetOrAddVendor( request.owner ).DispenseItemFromVendor( request.position, request.itemID );
	}

	private function OnDispenseStackRequest( request : DispenseStackRequest )
	{
		var playerMoney : Int32;
		var buyPrice : Int32;
		var tSystem : TransactionSystem;
		if( request.shouldPay )
		{
			tSystem = GameInstance.GetTransactionSystem( GetGameInstance() );
			playerMoney = tSystem.GetItemQuantity( GetPlayer( GetGameInstance() ), MarketSystem.Money() );
			buyPrice = MarketSystem.GetBuyPrice( request.owner, request.itemID ) * request.amount;
			if( playerMoney < buyPrice )
			{
				return;
			}
			else
			{
				tSystem.RemoveItem( GetPlayer( GetGameInstance() ), MarketSystem.Money(), buyPrice );
			}
		}
		GetOrAddVendor( request.owner ).DispenseItemStackFromVendor( request.position, request.itemID, request.amount, request.bypassStock );
	}

	private function OnAddItemToStockRequest( request : AddItemToVendorRequest )
	{
		var itemStack : SItemStack;
		var transactionSys : TransactionSystem;
		var vendor : Vendor;
		var i : Int32;
		vendor = GetVendorByTDBID( request.vendorID );
		if( TweakDBInterface.GetItemRecord( request.itemToAddID ).IsSingleInstance() )
		{
			itemStack.itemID = ItemID.FromTDBID( request.itemToAddID );
			itemStack.quantity = request.quantity;
			vendor.AddItemsToStock( itemStack );
			transactionSys.GiveItem( vendor.GetVendorObject(), itemStack.itemID, itemStack.quantity );
		}
		else
		{
			for( i = 0; i < request.quantity; i += 1 )
			{
				itemStack.itemID = ItemID.FromTDBID( request.itemToAddID );
				itemStack.quantity = 1;
				vendor.AddItemsToStock( itemStack );
				transactionSys.GiveItem( vendor.GetVendorObject(), itemStack.itemID, 1 );
			}
		}
	}

	private export function OnSetPriceModifierRequest( request : SetVendorPriceMultiplierRequest )
	{
		var vendor : Vendor;
		vendor = GetVendorByTDBID( request.vendorID );
		vendor.SetPriceMultiplier( request.multiplier );
	}

}

class MarketSystemRequest extends ScriptableSystemRequest
{
	var owner : weak< GameObject >;
	var vendorID : TweakDBID;
}

class AttachVendorRequest extends MarketSystemRequest
{
}

class DeattachVendorRequest extends MarketSystemRequest
{
}

class AddItemToVendorRequest extends ScriptableSystemRequest
{
	[ customEditor = "TweakDBGroupInheritance;Vendor" ]
	editable var vendorID : TweakDBID;
	[ customEditor = "TweakDBGroupInheritance;Item" ]
	editable var itemToAddID : TweakDBID;
	[ rangeMin = "1" ][ rangeMax = "99999" ]
	editable var quantity : Int32;
	default quantity = 1;

	public constexpr export function GetFriendlyDescription() : String
	{
		return "Add Item To Vendor";
	}

}

struct TransactionRequestData
{
	var itemID : ItemID;
	var quantity : Int32;
	default quantity = 1;
	var powerLevel : Float;
	default powerLevel = 0.0f;
}

class TransactionRequest extends MarketSystemRequest
{
	var requestID : Int32;
	var items : array< TransactionRequestData >;
}

class BuyRequest extends TransactionRequest
{
}

class BuybackRequest extends TransactionRequest
{
}

class SellRequest extends TransactionRequest
{
}

class SetVendorPriceMultiplierRequest extends ScriptableSystemRequest
{
	[ customEditor = "TweakDBGroupInheritance;Vendor" ]
	editable var vendorID : TweakDBID;
	[ rangeMin = "0" ][ rangeMax = "10" ]
	editable var multiplier : Float;
	default multiplier = 1;

	public constexpr export function GetFriendlyDescription() : String
	{
		return "Set Vendor Price Modifier";
	}

}

class DispenseRequest extends MarketSystemRequest
{
	var position : Vector4;
	var itemID : ItemID;
	var shouldPay : Bool;
}

class DispenseStackRequest extends MarketSystemRequest
{
	var position : Vector4;
	var itemID : ItemID;
	var amount : Int32;
	var shouldPay : Bool;
	var bypassStock : Bool;
}

struct SItemTransaction
{
	var itemStack : SItemStack;
	var pricePerItem : Int32;
}

importonly struct SItemStack
{
	import persistent var itemID : ItemID;
	import persistent var quantity : Int32;
	import persistent var powerLevel : Int32;
	import persistent var vendorItemID : TweakDBID;
	import var isAvailable : Bool;
	import var requirement : SItemStackRequirementData;
	import var dynamicTags : array< CName >;
}

importonly struct SItemStackRequirementData
{
	import var statType : gamedataStatType;
	import var requiredValue : Float;
}

