//////////////////////////////////////////////////////////////////////
//
// AkFilePackageLowLevelIO.h
//
// CAkFilePackageLowLevelIO extends a Low-Level I/O device by providing 
// the ability to reference files that are part of a file package.
//
// It can extend either blocking or deferred I/O hooks (both inheriting from
// AK::StreamMgr::IAkLowLevelIOHook), since its base class is templated. 
// In either case, the base class must also implement 
// AK::StreamMgr::IAkFileLocationResolver. This interface defines both overloads
// for Open(), and this is where the package's look-up table is searched.
// If no match is found, then it falls back on the base implementation.
// 
// Clients of devices that use this class' functionnality simply need to call
// LoadFilePackage(), which loads and parses file packages that were created with 
// the AkFilePackager utility app (located in ($WWISESDK)/samples/FilePackager/). 
// The header of these file packages contains look-up tables that describe the 
// internal offset of each file it references, their block size (required alignment), 
// and their language. Each combination of AkFileID and Language ID is unique.
//
// LoadFilePackage() returns a package ID that can be used to unload it. Any number
// of packages can be loaded simultaneously. When Open() is called, the last package 
// loaded is searched first, then the previous one, and so on.
//
// The language ID was created dynamically when the package was created. The header 
// also contains a map of language names (strings) to their ID, so that the proper 
// language-specific version of files can be resolved. The language name that is stored
// matches the name of the directory that is created by the Wwise Bank Manager,
// except for the trailing slash.
//
// The type of package is also a template argument. By default, it is a disk package
// (see AkDiskPackage.h).
//
// Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved
//
//////////////////////////////////////////////////////////////////////

#ifndef _AK_FILE_PACKAGE_LOW_LEVEL_IO_H_
#define _AK_FILE_PACKAGE_LOW_LEVEL_IO_H_

#include <AK/SoundEngine/Common/AkStreamMgrModule.h>
#include "AkFilePackage.h"

//-----------------------------------------------------------------------------
// Name: AkFilePackageReader 
// Desc: This class wraps an AK::IAkStdStream to read a file package.
//-----------------------------------------------------------------------------
class AkFilePackageReader
{
public:
	AkFilePackageReader()
		: m_pStream( NULL ), m_uBlockSize( 0 ) {}
	~AkFilePackageReader() 
	{
		// IMPORTANT: Do not close file. This object can be copied.
	}

	AKRESULT Open(
		const AkOSChar*	in_pszFilePackageName,	// File package name. Location is resolved using base class' Open().
		bool in_bReadFromSFXOnlyDir		// If true, the file package is opened from the language agnostic directory only. Otherwise, it tries to open it 
										// from the current language-specific directory first, and then from the common directory if it fails, similarly to the soundbanks loader of the Sound Engine (Default).
		)
	{
		AkFileSystemFlags flags;
		flags.uCompanyID = AKCOMPANYID_AUDIOKINETIC;
		flags.uCodecID = AKCODECID_FILE_PACKAGE;
		flags.uCustomParamSize = 0;
		flags.pCustomParam = NULL;
		flags.bIsLanguageSpecific = !in_bReadFromSFXOnlyDir;
		flags.bIsFromRSX = false;

		AKRESULT eResult = AK::IAkStreamMgr::Get()->CreateStd(
            in_pszFilePackageName,
            &flags,
			AK_OpenModeRead,
			m_pStream,
			true );

		if ( eResult != AK_Success 
			&& !in_bReadFromSFXOnlyDir )
		{
			// Try again, in SFX-only directory.
			flags.bIsLanguageSpecific = false;
			eResult = AK::IAkStreamMgr::Get()->CreateStd(
				in_pszFilePackageName,
				&flags,
				AK_OpenModeRead,
				m_pStream,
				true );
		}

		return eResult;
	}

	AKRESULT Read(
		void *			in_pBuffer,			// Buffer. Must be aligned with value returned by GetBlockSize().
		AkUInt32		in_uSizeToRead,		// Size to read. Must be a multiple of value returned by GetBlockSize().
		AkUInt32 &		out_uSizeRead,		// Returned size read.
		AkPriority		in_priority = AK_DEFAULT_PRIORITY,	// Priority heuristic.
		AkReal32		in_fThroughput = 0	// Throughput heuristic. 0 means "not set", and results in "immediate".
		)
	{
		AKASSERT( m_pStream );
		AkReal32 fDeadline = ( in_fThroughput > 0 ) ? in_uSizeToRead / in_fThroughput : 0;
		return m_pStream->Read( 
			in_pBuffer, 
			in_uSizeToRead,
			true,
			in_priority,
			fDeadline,
			out_uSizeRead );
	}

	AKRESULT Seek( 
		AkUInt32		in_uPosition,
		AkUInt32 &		out_uRealOffset
		)
	{
		AkInt64 iRealOffset;
		AKRESULT eResult = m_pStream->SetPosition( in_uPosition, AK_MoveBegin, &iRealOffset );
		AKASSERT( eResult == AK_Success || !"Failed changing stream position" );
		out_uRealOffset = (AkUInt32)iRealOffset;
		return eResult;
	}

	void Close()
	{
		if ( m_pStream )
			m_pStream->Destroy();
		m_pStream = NULL;
	}

	void SetName( 
		const AkOSChar* 
#ifndef AK_OPTIMIZED
		in_pszName 
#endif
		)
	{
#ifndef AK_OPTIMIZED
		AKASSERT( m_pStream );
		m_pStream->SetStreamName( in_pszName );
#endif
	}

	AkUInt64 GetSize()
	{
		AKASSERT( m_pStream );
		AkStreamInfo info;
		m_pStream->GetInfo( info );
		return info.uSize;
	}

	AkUInt32 GetBlockSize()
	{
		AKASSERT( m_pStream );
		// AK::IAkStdStream::GetBlockSize() is costly. Cache block size.
		if ( !m_uBlockSize )
			m_uBlockSize = m_pStream->GetBlockSize();
		return m_uBlockSize;
	}

	AkFileHandle GetHandle()
	{
		AKASSERT( m_pStream );
		AkFileDesc * pFileDesc = (AkFileDesc*)m_pStream->GetFileDescriptor();
		AKASSERT( pFileDesc );
		return pFileDesc->hFile;
	}

private:
	AK::IAkStdStream *	m_pStream;
	AkUInt32			m_uBlockSize;
};

//-----------------------------------------------------------------------------
// Name: CAkDiskPackage 
// Desc: This class extends the CAkFilePackage class by providing system handle
// management.
// It keeps a copy of the file package reader that was used to read the file package
// header from disk, and uses it to query and cache its low-level system handle
// (AkFileDesc::hFile). This handle is kept open and used directly to read portions
// of the package from disk, corresponding to read requests for the files it 
// contains. The streaming object / package handle is closed when the package
// is destroyed.
//-----------------------------------------------------------------------------
class CAkDiskPackage : public CAkFilePackage
{
public:
	// Factory for disk package.
	// Instantiates a file package object, queries its file handle once and keep in package.
	// Also keeps a copy of its reader object, which is used to close the file handle on destruction.
	static CAkDiskPackage * Create( 
		AkFilePackageReader & in_reader,		// File package reader.
		const AkOSChar*		in_pszPackageName,	// Name of the file package (for memory monitoring).
		AkMemPoolId			in_memPoolID,		// Memory pool in which the package is created with its lookup table.
		AkUInt32 			in_uHeaderSize,		// File package header size, including the size of the header chunk AKPK_HEADER_CHUNK_DEF_SIZE.
		AkUInt32 &			out_uReservedHeaderSize, // Size reserved for header, taking mem align into account.
		AkUInt8 *&			out_pHeaderBuffer	// Returned address of memory for header.
		)
	{
		CAkDiskPackage * pPackage = CAkFilePackage::Create<CAkDiskPackage>( 
			in_pszPackageName,
			in_memPoolID,
			in_uHeaderSize,
			in_reader.GetBlockSize(),
			out_uReservedHeaderSize,
			out_pHeaderBuffer );
		if ( pPackage )
		{
			pPackage->m_reader = in_reader;				// Copy reader.
			pPackage->m_hFile = in_reader.GetHandle();	// Cache handle.
		}
		return pPackage;
	}

	CAkDiskPackage( AkUInt32 in_uPackageID, AkUInt32 in_uHeaderSize, AkMemPoolId in_poolID, void * in_pToRelease, bool in_bIsInternalPool )
		: CAkFilePackage( in_uPackageID, in_uHeaderSize, in_poolID, in_pToRelease, in_bIsInternalPool )
	{ }

	// Override Destroy(): Close 
	virtual void Destroy()
	{
		m_reader.Close();
		CAkFilePackage::Destroy();
	}

	// Fills an AkFileHandle with a value that will be useable by the low-level I/O hook.
	// Disk packages return the package's system handle: the hook reads from the package file itself, with
	// proper offset, to get the data it needs.
	inline void GetHandleForFileDesc( AkFileHandle & out_hFile ) { out_hFile = m_hFile; }

protected:
	
	AkFilePackageReader	m_reader;	// READER object. Holds the stream used to read the package. Closed only upon package destruction.
	AkFileHandle		m_hFile;	// Platform-independent file handle (cached from READER).
};

//-----------------------------------------------------------------------------
// Name: class CAkFilePackageLowLevelIO.
// Desc: Extends default Low-level IO implementation with packaged file support.
//		 Base class must implement one of the low-level I/O hooks 
//		 (AK::StreamMgr::IAkIOHookBlocking or AK::StreamMgr::IAkIOHookDeferred)
//		 _and_ the AK::StreamMgr::IAkFileLocationResolver interface.
//		 It must also define the following methods:
//			- void Term()
// Note: This class uses AkFileDesc::uCustomParamSize to store the block size 
//		 of files opened from a package, and relies on the fact that it is 0 
//		 when they are not part of the package.
//-----------------------------------------------------------------------------
template <class T_LLIOHOOK_FILELOC, class T_PACKAGE = CAkDiskPackage>
class CAkFilePackageLowLevelIO : public T_LLIOHOOK_FILELOC
{
public:

    CAkFilePackageLowLevelIO();
    virtual ~CAkFilePackageLowLevelIO();

	// File package loading:
    // Opens a package file, parses its header, fills LUT.
    // Overrides of Open() will search files in loaded LUTs first, then use default Low-Level I/O 
	// services if they cannot be found.
	// Any number of packages can be loaded at a time. Each LUT is searched until a match is found.
	// Returns AK_Success if successful, AK_InvalidLanguage if the current language 
	// does not exist in the LUT (not necessarily an error), AK_Fail for any other reason.
	// Also returns a package ID which can be used to unload it (see UnloadFilePackage()).
	// WARNING: This method is not thread safe. Ensure there are no I/O occurring on this device
	// when loading a file package.
    virtual AKRESULT LoadFilePackage(
        const AkOSChar* in_pszFilePackageName,	// File package name. Location is resolved using base class' Open().
		AkUInt32 &		out_uPackageID,			// Returned package ID.
		AkMemPoolId		in_memPoolID = AK_DEFAULT_POOL_ID	// Memory pool in which the LUT is written. Passing AK_DEFAULT_POOL_ID will create a new pool automatically. 
								// Note that the bulk of the package's data is stored in VRAM, which is allocated through the VRAM allocation hook.
        );
	
	// Unload a file package.
	// Returns AK_Success if in_uPackageID exists, AK_Fail otherwise.
	// WARNING: This method is not thread safe. Ensure there are no I/O occurring on this device
	// when unloading a file package.
	virtual AKRESULT UnloadFilePackage( 
		AkUInt32	in_uPackageID			// Returned package ID.
		);

	// Unload all file packages.
	// Returns AK_Success;
	// WARNING: This method is not thread safe. Ensure there are no I/O occurring on this device
	// when unloading a file package.
    virtual AKRESULT UnloadAllFilePackages();


	//
	// Overriden base class policies.
	// ---------------------------------------------------------------

    // Clean up.
    void Term();

protected:

	//
	// IAkFileLocationResolver interface overriden methods.
	// ---------------------------------------------------------------

	// Override Open (string): Search file in each LUT first. If it cannot be found, use base class services.
	// If the file is found in the LUTs, open is always synchronous.
	// Applies to AK soundbanks only.
    virtual AKRESULT 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.
        );

    // Override Open (ID): Search file in each LUT first. If it cannot be found, use base class services.
    // If the file is found in the LUTs, open is always synchronous.
	virtual AKRESULT 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.
        );

    //
	// IAkLowLevelIOHook interface overriden methods.
	// ---------------------------------------------------------------

	// Override Close: Do not close handle if file descriptor is part of the current packaged file.
    virtual AKRESULT Close(
        AkFileDesc &	in_fileDesc			// File descriptor.
        );

	// Override GetBlockSize: Get the block size of the LUT if a file package is loaded.
	virtual AkUInt32 GetBlockSize(
        AkFileDesc &	in_fileDesc			// File descriptor.
        );

protected:

	// Language change handling.
    // ------------------------------------------

	// Handler for global language change.
	static AK_FUNC( void, LanguageChangeHandler )( 
		const AkOSChar * const in_pLanguageName,// New language name.
		void * in_pCookie						// Cookie that was passed to AddLanguageChangeObserver().
		)
	{
		((CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC, T_PACKAGE>*)in_pCookie)->OnLanguageChange( in_pLanguageName );
	}

	// Updates language of all loaded packages. Packages keep a language ID to help them find 
	// language-specific assets quickly.
	void OnLanguageChange( 
		const AkOSChar * const in_pLanguageName	// New language name.
		);


	// File package handling methods.
    // ------------------------------------------

	// Loads a file package, with a given file package reader.
	AKRESULT _LoadFilePackage(
        const AkOSChar*			in_pszFilePackageName,	// File package name. Location is resolved using base class' Open().
		AkFilePackageReader &	in_reader,				// File package reader.
		AkPriority				in_readerPriority,		// File package reader priority heuristic.
		AkMemPoolId				in_memPoolID,			// Memory pool in which the LUT is written. Passing AK_DEFAULT_POOL_ID will create a new pool automatically. 
		T_PACKAGE *&			out_pPackage			// Returned package
        );

    // Searches the LUT to find the file data associated with the FileID.
    // Returns AK_Success if the file is found.
	template <class T_FILEID>
    AKRESULT FindPackagedFile( 
		T_PACKAGE *			in_pPackage,	// Package to search into.
		T_FILEID			in_fileID,		// File ID.
		AkFileSystemFlags * in_pFlags,		// Special flags. Can pass NULL.
		AkFileDesc &		out_fileDesc	// Returned file descriptor.
		);

	// Returns true if file described by in_fileDesc is in a package.
	inline bool IsInPackage( 
		const AkFileDesc & in_fileDesc		// File descriptor.
		)
	{
		return in_fileDesc.uCustomParamSize > 0;
	}

protected:
	// List of loaded packages.
	ListFilePackages	m_packages;
	bool				m_bRegisteredToLangChg;	// True after registering to language change notifications.
};

#include "AkFilePackageLowLevelIO.inl"

#endif //_AK_FILE_PACKAGE_LOW_LEVEL_IO_H_
