/**
 * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
 */

/**
 * This class is responsible for mapping properties in an OnlineGameSettings
 * object to something that the UI system can consume.
 */
class UIDataProvider_OnlinePlayerStorage extends UIDataProvider_OnlinePlayerDataBase
	native(inherit)
	config(Game)
	dependson(OnlineSubsystem)
	transient;

/** The storage settings that are used to load/save with the online subsystem */
var OnlinePlayerStorage Profile;

/** For displaying in the provider tree */
var const name ProviderName;

/**
 * If there was an error, it was possible the read was already in progress. This
 * indicates to re-read upon a good completion
 */
var bool bWasErrorLastRead;

/** Keeps a list of providers for each storage settings id */
struct native PlayerStorageArrayProvider
{
	/** The storage settings id that this provider is for */
	var int PlayerStorageId;
	/** The provider object to expose the data with */
	var UIDataProvider_OnlinePlayerStorageArray Provider;
};

/** The list of mappings from settings id to their provider */
var array<PlayerStorageArrayProvider> PlayerStorageArrayProviders;

/** The amount of storage needed for this game */
var config int DeviceStorageSizeNeeded;

/** Whether the UI external to the game is open or not */
var bool bIsExternalUIOpen;

/** Whether we need to refresh our data upon external UI closing or not */
var bool bNeedsDeferredRefresh;

/**
 * Reads the data
 *
 * @param PlayerInterface is the OnlinePlayerInterface used
 * @param LocalUserNum the user that we are reading the data for
 * @param DeviceId device for local read of player data (-1 for no device)
 * @param PlayerStorage the object to copy the results to and contains the list of items to read
 *
 * @return true if the call succeeds, false otherwise
 */
function bool ReadData(OnlinePlayerInterface PlayerInterface, byte LocalUserNum, int DeviceId, OnlinePlayerStorage PlayerStorage)
{
	return PlayerInterface.ReadPlayerStorage(LocalUserNum, PlayerStorage, DeviceId);
}

/**
 * Writes the online  data for a given local user to the online data store
 *
 * @param PlayerInterface is the OnlinePlayerInterface used
 * @param LocalUserNum the user that we are writing the data for
 * @param DeviceId device for local write of player data (-1 for no device)
 * @param PlayerStorage the object that contains the list of items to write
 *
 * @return true if the call succeeds, false otherwise
 */
function bool WriteData(OnlinePlayerInterface PlayerInterface, byte LocalUserNum, int DeviceId, OnlinePlayerStorage PlayerStorage)
{
	return PlayerInterface.WritePlayerStorage(LocalUserNum,PlayerStorage, DeviceId);
}

/**
 * Fetches the requested object from the online layer's cache
 *
 * @param PlayerInterface is the OnlinePlayerInterface used
 * @param LocalUserNum the user that we are writing the data for
 *
 * @return true if the call succeeds, false otherwise
 */
function bool GetData(OnlinePlayerInterface PlayerInterface,byte LocalUserNum)
{
	local OnlinePlayerStorage CachedStorage;

	CachedStorage = PlayerInterface.GetPlayerStorage(LocalUserNum);
	if (CachedStorage != None)
	{
		// Use the existing object instead of the new one
		Profile = CachedStorage;
		// This read will return immediately
		PlayerInterface.ReadPlayerStorage(LocalUserNum,Profile);
		return true;
	}
	return false;
}

/**
 * Sets the delegate used to notify the gameplay code that the last read request has completed 
 *
 * @param PlayerInterface is the OnlinePlayerInterface used
 * @param LocalUserNum which user to watch for read complete notifications
 */
function AddReadCompleteDelegate(OnlinePlayerInterface PlayerInterface, byte LocalUserNum)
{
	PlayerInterface.AddReadPlayerStorageCompleteDelegate(LocalUserNum,OnReadStorageComplete);
}

/**
 * Clears the delegate used to notify the gameplay code that the last read request has completed 
 *
 * @param PlayerInterface is the OnlinePlayerInterface used
 * @param LocalUserNum which user to stop watching for read complete notifications
 */
function ClearReadCompleteDelegate(OnlinePlayerInterface PlayerInterface, byte LocalUserNum)
{
	PlayerInterface.ClearReadPlayerStorageCompleteDelegate(LocalUserNum,OnReadStorageComplete);
}

/**
 * Binds the player to this provider. Starts the async friends list gathering
 *
 * @param InPlayer the player that we are retrieving friends for
 */
event OnRegister(LocalPlayer InPlayer)
{
	local OnlineSubsystem OnlineSub;
	local OnlinePlayerInterface PlayerInterface;

	Super.OnRegister(InPlayer);

	// If the player is None, we are in the editor
	if (PlayerControllerId != -1)
	{
		// Figure out if we have an online subsystem registered
		OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
		if (OnlineSub != None)
		{
			// Grab the player interface to verify the subsystem supports it
			PlayerInterface = OnlineSub.PlayerInterface;
			if (PlayerInterface != None)
			{
				// Register that we are interested in any sign in change for this player
				PlayerInterface.AddLoginChangeDelegate(OnLoginChange);
				// Set our callback function per player
				AddReadCompleteDelegate(PlayerInterface,PlayerControllerId);
				// Swap our object if this is one cached for this user
				GetData(PlayerInterface,PlayerControllerId);
				// Refresh our data
				RefreshStorageData();
			}
		}
		// Request notifications of device removal
		if (OnlineSub.SystemInterface != None)
		{
			OnlineSub.SystemInterface.AddStorageDeviceChangeDelegate(OnStorageDeviceChange);
			OnlineSub.SystemInterface.AddExternalUIChangeDelegate(OnExternalUIChange);
		}
	}
}

/**
 * Clears our delegate for getting login change notifications
 */
event OnUnregister()
{
	local OnlineSubsystem OnlineSub;
	local OnlinePlayerInterface PlayerInterface;
	
	// Figure out if we have an online subsystem registered
	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	if (OnlineSub != None)
	{
		// Grab the player interface to verify the subsystem supports it
		PlayerInterface = OnlineSub.PlayerInterface;
		if (PlayerInterface != None)
		{
			// Clear our delegate
			PlayerInterface.ClearLoginChangeDelegate(OnLoginChange);
			ClearReadCompleteDelegate(PlayerInterface,PlayerControllerId);
		}
		// Request notifications of device removal
		if (OnlineSub.SystemInterface != None)
		{
			OnlineSub.SystemInterface.ClearStorageDeviceChangeDelegate(OnStorageDeviceChange);
			OnlineSub.SystemInterface.ClearExternalUIChangeDelegate(OnExternalUIChange);
		}
	}
	Super.OnUnregister();
}

/**
 * Handles the notification that the async read of the storage data is done
 *
 * @param bWasSuccessful whether the call succeeded or not
 */
function OnReadStorageComplete(byte LocalUserNum,bool bWasSuccessful)
{
	local OnlineSubsystem OnlineSub;
	local OnlinePlayerInterface PlayerInterface;

	if (bWasSuccessful == true)
	{
		if (!bWasErrorLastRead)
		{
		}
		else
		{
			// Figure out if we have an online subsystem registered
			OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
			if (OnlineSub != None)
			{
				// Grab the player interface to verify the subsystem supports it
				PlayerInterface = OnlineSub.PlayerInterface;
				if (PlayerInterface != None)
				{
					bWasErrorLastRead = false;
					// Read again to copy any data from a read in progress
					if (ReadData(PlayerInterface,PlayerControllerId,Profile.DeviceId,Profile) == false)
					{
						bWasErrorLastRead = true;
					}
				}
			}
		}
	}
	else
	{
		bWasErrorLastRead = true;
		`Log("Failed to read online storage data",,'DevOnline');
	}
}

/**
 * Executes a refetching of the storage data when the login for this player changes
 *
 * @param LocalUserNum the player that logged in/out
 */
function OnLoginChange(byte LocalUserNum)
{
	local OnlineSubsystem OnlineSub;
	local OnlinePlayerInterface PlayerInterface;
	local ELoginStatus LoginStatus;
	local UniqueNetId NetId;

	if (LocalUserNum == PlayerControllerId)
	{
		// Figure out if we have an online subsystem registered
		OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
		if (OnlineSub != None)
		{
			// Grab the player interface to verify the subsystem supports it
			PlayerInterface = OnlineSub.PlayerInterface;
			if (PlayerInterface != None)
			{
				LoginStatus = PlayerInterface.GetLoginStatus(PlayerControllerId);
				PlayerInterface.GetUniquePlayerId(PlayerControllerId,NetId);
				if (LoginStatus == LS_NotLoggedIn)
				{
					// Reset the profile only when they've signed out
					Profile.SetToDefaults();
				}
			}
		}
		RefreshStorageData();
	}
}

/**
 * Reads this user's storage data from the online subsystem.
 */
function RefreshStorageData()
{
	local OnlineSubsystem OnlineSub;
	local bool bFoundCachedData;

	if (!bIsExternalUIOpen)
	{
		// Figure out if we have an online subsystem registered
		OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
		if (OnlineSub != None &&
			OnlineSub.PlayerInterface != None &&
			OnlineSub.PlayerInterfaceEx != None)
		{
			// Determine if the cached data is present
			bFoundCachedData = GetData(OnlineSub.PlayerInterface,PlayerControllerId);
			if (!bFoundCachedData ||
				// If they have cached data and that device is valid, skip the prompt
				(bFoundCachedData &&
				// If they already have a valid device, don't prompt
				!OnlineSub.PlayerInterfaceEx.IsDeviceValid(Profile.DeviceId,DeviceStorageSizeNeeded)))
			{
				ShowDeviceSelection();
			}
		}
	}
	else
	{
		// Do the refresh when the UI closes
		bNeedsDeferredRefresh = true;
	}
}

/**
 * Shows the device selection UI if possible
 */
function ShowDeviceSelection()
{
	local OnlineSubsystem OnlineSub;

	// Figure out if we have an online subsystem registered
	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	if (OnlineSub != None &&
		OnlineSub.PlayerInterface != None &&
		OnlineSub.PlayerInterfaceEx != None)
	{
		OnlineSub.PlayerInterfaceEx.AddDeviceSelectionDoneDelegate(PlayerControllerId,OnDeviceSelectionComplete);
		// Get the device that their data may be stored on locally
		OnlineSub.PlayerInterfaceEx.ShowDeviceSelectionUI(PlayerControllerId,DeviceStorageSizeNeeded);
	}
}

/**
 * Called once the user has selected their device
 *
 * @param bWasSuccessful true if the async action completed without error, false if there was an error
 */
function OnDeviceSelectionComplete(bool bWasSuccessful)
{
	local OnlineSubsystem OnlineSub;
	local string Ignored;

	// We know we have one, because this event was called
	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	OnlineSub.PlayerInterfaceEx.ClearDeviceSelectionDoneDelegate(PlayerControllerId,OnDeviceSelectionComplete);
	// Get the latest device id if this worked
	if (bWasSuccessful)
	{
		// Get the device that was selected
		Profile.DeviceId = OnlineSub.PlayerInterfaceEx.GetDeviceSelectionResults(PlayerControllerId,Ignored);
		`Log("OnDeviceSelectionComplete("$bWasSuccessful$") for ControllerId ("$PlayerControllerId$") with DeviceId ("$Profile.DeviceId$")",,'DevOnline');
		// Start the async task
		if (ReadData(OnlineSub.PlayerInterface,PlayerControllerId,Profile.DeviceId,Profile) == false)
		{
		}
	}
	else
	{
		// Failed, so default to no storage
		Profile.DeviceId = -1;
	}
}

/**
 * Verifies that the device for all of the installed DLC is still valid and reboots the game if not
 */
function OnStorageDeviceChange()
{
	local OnlineSubsystem OnlineSub;

	// Figure out if we have an online subsystem registered
	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	if (OnlineSub != None &&
		OnlineSub.SystemInterface != None)
	{
		// If our current device is no longer valid, re-request one
		if (!OnlineSub.PlayerInterfaceEx.IsDeviceValid(Profile.DeviceId,DeviceStorageSizeNeeded))
		{
			Profile.DeviceId = -1;
			RefreshStorageData();
		}
	}
}

/**
 * Used to check for an external UI being open when attempting to show other UI. That will fail
 * so this allows the code to call the show upon closing
 *
 * @param bIsOpening whether the external UI is opening or closing
 */
function OnExternalUIChange(bool bIsOpening)
{
	bIsExternalUIOpen = bIsOpening;
	// If we have a deferred update pending, kick it off now that the external UI is gone
	if (!bIsOpening && bNeedsDeferredRefresh)
	{
		RefreshStorageData();
	}
}

defaultproperties
{
	ProviderName=PlayerStorageData
}