#include "BFPlatform.h"

#ifdef BF_WWISE_ENABLED

//////////////////////////////////////////////////////////////////////
//
// AkDefaultIOHookBlocking.cpp
//
// Default blocking low level IO hook (AK::StreamMgr::IAkIOHookBlocking) 
// and file system (AK::StreamMgr::IAkFileLocationResolver) implementation 
// on Windows. It can be used as a standalone implementation of the 
// Low-Level I/O system.
// 
// AK::StreamMgr::IAkFileLocationResolver: 
// Resolves file location using simple path concatenation logic 
// (implemented in ../Common/CAkFileLocationBase). It can be used as a 
// standalone Low-Level IO system, or as part of a multi device system. 
// In the latter case, you should manage multiple devices by implementing 
// AK::StreamMgr::IAkFileLocationResolver elsewhere (you may take a look 
// at class CAkDefaultLowLevelIODispatcher).
//
// AK::StreamMgr::IAkIOHookBlocking: 
// Uses platform API for I/O. Calls to ::ReadFile() and ::WriteFile() 
// block because files are opened without the FILE_FLAG_OVERLAPPED flag. 
// The AK::StreamMgr::IAkIOHookBlocking interface is meant to be used with
// AK_SCHEDULER_BLOCKING streaming devices. 
//
// Init() creates a streaming device (by calling AK::StreamMgr::CreateDevice()).
// AkDeviceSettings::uSchedulerTypeFlags is set inside to AK_SCHEDULER_BLOCKING.
// If there was no AK::StreamMgr::IAkFileLocationResolver previously registered 
// to the Stream Manager, this object registers itself as the File Location Resolver.
//
// Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved
//
//////////////////////////////////////////////////////////////////////

#include "Common.h"
#include "AkDefaultIOHookBlocking.h"
#include "AkFileHelpers.h"


#define WIN32_BLOCKING_DEVICE_NAME		(AKTEXT("Win32 Blocking"))	// Default blocking device name.

CAkDefaultIOHookBlocking::CAkDefaultIOHookBlocking()
: m_deviceID( AK_INVALID_DEVICE_ID )
, m_bAsyncOpen( false )
{
}

CAkDefaultIOHookBlocking::~CAkDefaultIOHookBlocking()
{
}

// Initialization/termination. Init() registers this object as the one and 
// only File Location Resolver if none were registered before. Then 
// it creates a streaming device with scheduler type AK_SCHEDULER_BLOCKING.
AKRESULT CAkDefaultIOHookBlocking::Init(
	const AkDeviceSettings &	in_deviceSettings,		// Device settings.
	bool						in_bAsyncOpen/*=false*/	// If true, files are opened asynchronously when possible.
	)
{
	if ( in_deviceSettings.uSchedulerTypeFlags != AK_SCHEDULER_BLOCKING )
	{
		AKASSERT( !"CAkDefaultIOHookBlocking I/O hook only works with AK_SCHEDULER_BLOCKING devices" );
		return AK_Fail;
	}

	m_bAsyncOpen = in_bAsyncOpen;
	
	// If the Stream Manager's File Location Resolver was not set yet, set this object as the 
	// File Location Resolver (this I/O hook is also able to resolve file location).
	if ( !AK::StreamMgr::GetFileLocationResolver() )
		AK::StreamMgr::SetFileLocationResolver( this );

	// Create a device in the Stream Manager, specifying this as the hook.
	m_deviceID = AK::StreamMgr::CreateDevice( in_deviceSettings, this );
	if ( m_deviceID != AK_INVALID_DEVICE_ID )
		return AK_Success;

	return AK_Fail;
}

void CAkDefaultIOHookBlocking::Term()
{
	if ( AK::StreamMgr::GetFileLocationResolver() == this )
		AK::StreamMgr::SetFileLocationResolver( NULL );
	AK::StreamMgr::DestroyDevice( m_deviceID );
}

//
// IAkFileLocationAware interface.
//-----------------------------------------------------------------------------

// Returns a file descriptor for a given file name (string).
AKRESULT CAkDefaultIOHookBlocking::Open( 
    const AkOSChar* in_pszFileName,     // File name.
    AkOpenMode      in_eOpenMode,       // Open mode.
    AkFileSystemFlags * in_pFlags,      // Special flags. Can pass NULL.
	bool &			io_bSyncOpen,		// If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
    AkFileDesc &    out_fileDesc        // Returned file descriptor.
    )
{
	// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
	// client thread. If you want files to be opened asynchronously when it is possible, this device should 
	// be initialized with the flag in_bAsyncOpen set to true.
	if ( io_bSyncOpen || !m_bAsyncOpen )
	{
		io_bSyncOpen = true;

		// Get the full file path, using path concatenation logic.
		AkOSChar szFullFilePath[AK_MAX_PATH];
		if ( GetFullFilePath( in_pszFileName, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
		{
			// Open the file without FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
			AKRESULT eResult = CAkFileHelpers::OpenFile( 
				szFullFilePath,
				in_eOpenMode,
				false,
				false,
				out_fileDesc.hFile );
			if ( eResult == AK_Success )
			{
#ifdef AK_USE_METRO_API
				FILE_STANDARD_INFO info;
				::GetFileInformationByHandleEx( out_fileDesc.hFile, FileStandardInfo, &info, sizeof(info) );
				out_fileDesc.iFileSize = info.EndOfFile.QuadPart;
#else
				ULARGE_INTEGER Temp;
				Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
				out_fileDesc.iFileSize			= Temp.QuadPart;
#endif
				out_fileDesc.uSector			= 0;
				out_fileDesc.deviceID			= m_deviceID;
				out_fileDesc.pCustomParam		= NULL;
				out_fileDesc.uCustomParamSize	= 0;
			}
			return eResult;
		}

		return AK_Fail;    
	}
	else
	{
		// The client allows us to perform asynchronous opening.
		// We only need to specify the deviceID, and leave the boolean to false.
		out_fileDesc.iFileSize			= 0;
		out_fileDesc.uSector			= 0;
		out_fileDesc.deviceID			= m_deviceID;
		out_fileDesc.pCustomParam		= NULL;
		out_fileDesc.uCustomParamSize	= 0;
		return AK_Success;
	}
}

// Returns a file descriptor for a given file ID.
AKRESULT CAkDefaultIOHookBlocking::Open( 
    AkFileID        in_fileID,          // File ID.
    AkOpenMode      in_eOpenMode,       // Open mode.
    AkFileSystemFlags * in_pFlags,      // Special flags. Can pass NULL.
	bool &			io_bSyncOpen,		// If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
    AkFileDesc &    out_fileDesc        // Returned file descriptor.
    )
{
	// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
	// client thread. If you want files to be opened asynchronously when it is possible, this device should 
	// be initialized with the flag in_bAsyncOpen set to true.
	if ( io_bSyncOpen || !m_bAsyncOpen )
	{
		io_bSyncOpen = true;

		// Get the full file path, using path concatenation logic.
		AkOSChar szFullFilePath[AK_MAX_PATH];
		if ( GetFullFilePath( in_fileID, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
		{
			// Open the file without FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
			AKRESULT eResult = CAkFileHelpers::OpenFile( 
				szFullFilePath,
				in_eOpenMode,
				false,
				false,
				out_fileDesc.hFile );
			if ( eResult == AK_Success )
			{
#ifdef AK_USE_METRO_API
				FILE_STANDARD_INFO info;
				::GetFileInformationByHandleEx( out_fileDesc.hFile, FileStandardInfo, &info, sizeof(info) );
				out_fileDesc.iFileSize = info.EndOfFile.QuadPart;
#else
				ULARGE_INTEGER Temp;
				Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
				out_fileDesc.iFileSize			= Temp.QuadPart;
#endif
				out_fileDesc.uSector			= 0;
				out_fileDesc.deviceID			= m_deviceID;
				out_fileDesc.pCustomParam		= NULL;
				out_fileDesc.uCustomParamSize	= 0;
			}
			return eResult;
		}

		return AK_Fail;
	}
	else
	{
		// The client allows us to perform asynchronous opening.
		// We only need to specify the deviceID, and leave the boolean to false.
		out_fileDesc.iFileSize			= 0;
		out_fileDesc.uSector			= 0;
		out_fileDesc.deviceID			= m_deviceID;
		out_fileDesc.pCustomParam		= NULL;
		out_fileDesc.uCustomParamSize	= 0;
		return AK_Success;
	}
}

//
// IAkIOHookBlocking implementation.
//-----------------------------------------------------------------------------

// Reads data from a file (synchronous). 
AKRESULT CAkDefaultIOHookBlocking::Read(
    AkFileDesc &			in_fileDesc,        // File descriptor.
	const AkIoHeuristics & /*in_heuristics*/,	// Heuristics for this data transfer (not used in this implementation).
    void *					out_pBuffer,        // Buffer to be filled with data.
    AkIOTransferInfo &		io_transferInfo		// Synchronous data transfer info. 
    )
{
    AKASSERT( out_pBuffer &&
            in_fileDesc.hFile != INVALID_HANDLE_VALUE );

	OVERLAPPED overlapped;
	overlapped.Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
	overlapped.OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
	overlapped.hEvent = NULL;

	AkUInt32 uSizeTransferred;

	if ( ::ReadFile( 
			in_fileDesc.hFile,
			out_pBuffer,
			io_transferInfo.uRequestedSize,
			&uSizeTransferred,
			&overlapped ) )
	{
		AKASSERT( uSizeTransferred == io_transferInfo.uRequestedSize );
		return AK_Success;
	}
    return AK_Fail;
}

// Writes data to a file (synchronous). 
AKRESULT CAkDefaultIOHookBlocking::Write(
	AkFileDesc &			in_fileDesc,        // File descriptor.
	const AkIoHeuristics & /*in_heuristics*/,	// Heuristics for this data transfer (not used in this implementation).
    void *					in_pData,           // Data to be written.
    AkIOTransferInfo &		io_transferInfo		// Synchronous data transfer info. 
    )
{
    AKASSERT( in_pData &&
            in_fileDesc.hFile != INVALID_HANDLE_VALUE );

	OVERLAPPED overlapped;
	overlapped.Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
	overlapped.OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
	overlapped.hEvent = NULL;

	AkUInt32 uSizeTransferred;

	if ( ::WriteFile( 
			in_fileDesc.hFile,
			in_pData,
			io_transferInfo.uRequestedSize,
			&uSizeTransferred,
			&overlapped ) )
	{
		AKASSERT( uSizeTransferred == io_transferInfo.uRequestedSize );
		return AK_Success;
	}
	return AK_Fail;
}

// Cleans up a file.
AKRESULT CAkDefaultIOHookBlocking::Close(
    AkFileDesc & in_fileDesc      // File descriptor.
    )
{
	return CAkFileHelpers::CloseFile( in_fileDesc.hFile );
}

// Returns the block size for the file or its storage device. 
AkUInt32 CAkDefaultIOHookBlocking::GetBlockSize(
    AkFileDesc &  /*in_fileDesc*/     // File descriptor.
    )
{
	// No constraint on block size (file seeking).
    return 1;
}


// Returns a description for the streaming device above this low-level hook.
void CAkDefaultIOHookBlocking::GetDeviceDesc(
    AkDeviceDesc &  
#ifndef AK_OPTIMIZED
	out_deviceDesc      // Description of associated low-level I/O device.
#endif
    )
{
#ifndef AK_OPTIMIZED
	AKASSERT( m_deviceID != AK_INVALID_DEVICE_ID || !"Low-Level device was not initialized" );
	out_deviceDesc.deviceID       = m_deviceID;
	out_deviceDesc.bCanRead       = true;
	out_deviceDesc.bCanWrite      = true;
	AKPLATFORM::SafeStrCpy( out_deviceDesc.szDeviceName, WIN32_BLOCKING_DEVICE_NAME, AK_MONITOR_DEVICENAME_MAXLENGTH );
	out_deviceDesc.uStringSize   = (AkUInt32)wcslen( out_deviceDesc.szDeviceName ) + 1;
#endif
}

// Returns custom profiling data: 1 if file opens are asynchronous, 0 otherwise.
AkUInt32 CAkDefaultIOHookBlocking::GetDeviceData()
{
	return ( m_bAsyncOpen ) ? 1 : 0;
}

#endif