// This is the prototypes of interfaces that will be used for asynchronous
// I/O (streaming).
// THIS IS NOT FINAL AND IS SUBJECT TO CHANGE WITHOUT NOTICE

// Some excerpts explaining basic ideas behind streaming design here:

/*
 * The idea is that the data loaded is ready for usage and ideally doesn't need further transformation,
 * therefore the client allocates the buffer (to avoid extra copy). All the data transformations should take place in the Resource Compiler. If you have to allocate a lot of small memory objects, you should revise this strategy in favor of one big allocation (again, that will be read directly from the compiled file).
 * Anyway, we can negotiate that the streaming engine allocates this memory.
 * In the end, it could make use of a memory pool, and copying data is not the bottleneck in our engine
 *
 * The client should take care of all fast operations. Looking up file size should be fast on the virtual
 * file system in a pak file, because the directory should be preloaded in memory
 */
#ifndef _CRY_COMMON_STREAM_ENGINE_HDR_
#define _CRY_COMMON_STREAM_ENGINE_HDR_

#include "smartptr.h"

struct StreamEngineParams;
class IStreamCallback;
class ICrySizer;

enum
{
	ERROR_UNKNOWN_ERROR          = 0xF0000000,
	ERROR_UNEXPECTED_DESTRUCTION = 0xF0000001,
	ERROR_INVALID_CALL           = 0xF0000002,
	ERROR_CANT_OPEN_FILE         = 0xF0000003,
	ERROR_REFSTREAM_ERROR        = 0xF0000004,
	ERROR_OFFSET_OUT_OF_RANGE    = 0xF0000005,
	ERROR_REGION_OUT_OF_RANGE    = 0xF0000006,
	ERROR_SIZE_OUT_OF_RANGE      = 0xF0000007,
	ERROR_CANT_START_READING     = 0xF0000008,
	ERROR_OUT_OF_MEMORY          = 0xF0000009,
	ERROR_ABORTED_ON_SHUTDOWN    = 0xF000000A,
	ERROR_OUT_OF_MEMORY_QUOTA    = 0xF000000B,
	ERROR_ZIP_CACHE_FAILURE      = 0xF000000C,
	ERROR_USER_ABORT             = 0xF000000D
};


// these are the flags for the StreamReadParams structure
enum StreamReadParamsFlagEnum
{
	// if this flag is set, the callback can be called from within stream engine's worker thread
	// WARNING: Use with care
	SRP_FLAGS_ASYNC_CALLBACK = 1,

	// If this flag is set, the file will be read synchronously
	// NOTE: Not implemented yet
//	SRP_FLAGS_SYNC_READ = 1 << 1,

	// if this flag is set, the stream will be treated as "permanent" and the file handle will
	// be cached. This is needed for files which are accessed frequently, e.g. Resource files.
	SRP_FLAGS_MAKE_PERMANENT = 1<<2,

	// if this flag is set and the stream was made permanent before (either explicitly because of
	// the SRP_FLAGS_MAKE_PERMANENT flag, or implicitly because of the policy of the StreamEngine),
	// the stream will be removed as soon as the last proxy will be released.
	SRP_FLAGS_MAKE_TRANSIENT = 1 << 3,

	// with this flag, the progress routine will be called asynchronously
	SRP_FLAGS_ASYNC_PROGRESS = 1 << 4,

	// this means that the path passed to StartRead is real path, and shouldn't undergo
	// adjustments through mod searching mechanics
	SRP_FLAGS_PATH_REAL      = 1 << 5,

	// if this is set, it is adviced that Update(0) be called during startread, which
	// can effectively call the callback before StartRead returns
	// SRP_IMMEDIATE_UPDATE and SRP_QUICK_RETURN are mutually exclusive
//	SRP_IMMEDIATE_UPDATE     = 1 << 6,

	// if this is set, it is adviced that Update(0) not be called during StartRead,
	// which yields quicker response.
	// SRP_IMMEDIATE_UPDATE and SRP_QUICK_RETURN are mutually exclusive
	SRP_QUICK_STARTREAD      = 1 << 7
};

//////////////////////////////////////////////////////////////////////////
// this is used as parameter to the asynchronous read function
// all the unnecessary parameters go here, because there are many of them
struct StreamReadParams
{
	StreamReadParams (
		//const char* _szFile,
		//IStreamCallback* _pCallback,
		DWORD_PTR _dwUserData = 0,
		int _nPriority = 0,
		unsigned _nLoadTime = 0,
		unsigned _nMaxLoadTime = 0,
		unsigned _nOffset = 0,
		unsigned _nSize = 0,
		void* _pBuffer = NULL,
		unsigned _nFlags = 0
	):
		//szFile (_szFile),
		//pCallback(_pCallback),
		dwUserData (_dwUserData),
		nPriority(_nPriority),
		nLoadTime(_nLoadTime),
		nMaxLoadTime(_nMaxLoadTime),
		pBuffer (_pBuffer),
		nOffset (_nOffset),
		nSize (_nSize),
		nFlags (_nFlags)
	{
	}

	// file name
	//const char* szFile;
	// the callback
	//IStreamCallback* pCallback;
	// the user data that'll be used to call the callback
	DWORD_PTR dwUserData;

	// the priority of this read; INT_MIN is the idle, INT_MAX is the highest, 0 is the average
	int nPriority;

	// the desirable loading time, in milliseconds, from the time of call
	// 0 means as fast as possible (desirably in this frame)
	unsigned nLoadTime;

	// the maximum load time, in milliseconds. 0 means forever. If the read lasts longer, it can be discarded.
	// WARNING: avoid too small max times, like 1-10 ms, because many loads will be discarded in this case.
	unsigned nMaxLoadTime;

	// the buffer into which to read the file or the file piece
	// if this is NULL, the streaming engine will supply the buffer
	// DO NOT USE THIS BUFFER during read operation! DO NOT READ from it, it can lead to memory corruption!
	void* pBuffer;

	// offset in the file to read; if this is not 0, then the file read
	// occurs beginning with the specified offset in bytes.
	// the callback interface receives the size of already read data as nSize
	// and generally behaves as if the piece of file would be a file of its own.
	unsigned nOffset;

	// number of bytes to read; if this is 0, then the whole file is read	
	// if nSize == 0 && nOffset != 0, then the file from the offset to the end is read
	// If nSize != 0, then the file piece from nOffset is read, at most nSize bytes
	// (if less, an error is reported). So, from nOffset byte to nOffset + nSize - 1 byte in the file
	unsigned nSize;

	// the combination of one or several flags from StreamReadParamsFlagEnum
	unsigned nFlags;
};

class IReadStream;
//typedef IReadStream_AutoPtr auto ptr wrapper
TYPEDEF_AUTOPTR(IReadStream);
typedef IReadStream_AutoPtr IReadStreamPtr;


//////////////////////////////////////////////////////////////////////////
// The highest level. THere is only one StreamingEngine in the application
// and it controls all I/O streams.
struct IStreamEngine
{
public:
	// general purpose flags
	enum
	{
		// if this is set in the call to Update, the time quota for callbacks is ignored,
		// and all the callbacks for which the data is available are called
		FLAGS_DISABLE_CALLBACK_TIME_QUOTA = 1
	};

	// Starts asynchronous read from the specified file (the file may be on a
	// virtual file system, in pak or zip file or wherever).
	// Reads the file contents into the given buffer, up to the given size.
	// Upon success, calls success callback. If the file is truncated or for other 
	// reason can not be read, calls error callback. THe callback can be NULL (in this case, the client should poll
	// the returned IReadStream object; the returned object must be locked for that)
	// NOTE: the error/success/ progress callbacks can also be called from INSIDE this function.
	// pParams - PLACEHOLDER for the future additional parameters (like priority), or really
	//  a pointer to a structure that will hold the parameters if there are too many of them
	//
	// IReadStream is reference-counted and will be automatically deleted if you don't refer to it;
	// If you don't store it immediately in an auto-pointer, it may be deleted as soon as on the next line of code,
	// because the read operation may complete immediately inside StartRead() and the object is self-disposed
	// as soon as the callback is called
	//
	// in some implementations disposal of the old pointers happen synchronously
	// (in the main thread) outside StartRead() (it happens in the entity update),
	// so you're guaranteed that it won't trash inside the calling function. However, this may change in the future
	// and you'll be required to assign it to IReadStream immediately (StartRead will return IReadStream_AutoPtr then)
	virtual IReadStreamPtr StartRead (const char* szSource, const char* szFile, IStreamCallback* pCallback = NULL, StreamReadParams* pParams = NULL) = 0;

	// returns the size of the file; returns 0 if there's no such file.
	// nCryPakFlags is the flag set as in ICryPak
	virtual unsigned GetFileSize (const char* szFile, unsigned nCryPakFlags = 0) = 0;

	// waits at most the specified number of milliseconds, or until at least one pending operation is completed
	// nFlags: may have the following flag set: 
	//    FLAGS_DISABLE_CALLBACK_TIME_QUOTA
	virtual void Update (unsigned nFlags = 0) = 0;

	// wait at most the specified time for the IO jobs to be completed.
	// Returns the number of jobs that actually were completed (finalized) during the call.
	// It may be different from the number of executed jobs.
	virtual unsigned Wait(unsigned nMilliseconds, unsigned nFlags = 0) = 0;

	//! Puts the memory statistics into the given sizer object
	//! According to the specifications in interface ICrySizer
	virtual void GetMemoryStatistics(ICrySizer *pSizer) = 0;

	//! Enables or disables callback time quota per frame
	virtual void SuspendCallbackTimeQuota(){}
	virtual void ResumeCallbackTimeQuota(){}

	virtual ~IStreamEngine() {}
};


class AutoSuspendTimeQuota
{
public:
	AutoSuspendTimeQuota(IStreamEngine* pStreamEngine)
	{
		m_pStreamEngine = pStreamEngine;
		pStreamEngine->SuspendCallbackTimeQuota();
	}

	~AutoSuspendTimeQuota()
	{
		m_pStreamEngine->ResumeCallbackTimeQuota();
	}
protected:
	IStreamEngine* m_pStreamEngine;
};

/*
class AutoResumeTimeQuota
{
public:
	AutoResumeTimeQuota(IStreamEngine* pStreamEngine)
	{
		m_pStreamEngine = pStreamEngine;
		pStreamEngine->ResumeCallbackTimeQuota();
	}

	~AutoResumeTimeQuota()
	{
		m_pStreamEngine->SuspendCallbackTimeQuota();
	}
protected:
	IStreamEngine* m_pStreamEngine;
};
*/

//////////////////////////////////////////////////////////////////////////
// This is the file "handle" that can be used to query the status
// of the asynchronous operation on the file. The same object may be returned
// for the same file to multiple clients.
//
// It will actually represent the asynchronous object in memory, and will be
// thread-safe reference-counted (both AddRef() and Release() will be virtual
// and thread-safe, just like the others)
// USE:
//   IReadStream_AutoPtr pReadStream = pStreamEngine->StartRead ("bla.xxx", this);
// OR:
//   pStreamEngine->StartRead ("MusicSystem","bla.xxx", this);
class IReadStream
{
public:
	virtual void AddRef() = 0;
	virtual void Release() = 0;

	// returns true if the file read was not successful.
	virtual bool IsError() = 0;
  // returns true if the file read was completed successfully
	// check IsError to check if the whole requested file (piece) was read
	virtual bool IsFinished() = 0;
	// returns the number of bytes read so far (the whole buffer size if IsFinished())
	// if bWait == true, then waits until the pending I/O operation completes
	// returns the total number of bytes read (if it completes successfully, returns the size of block being read)
	virtual unsigned int GetBytesRead(bool bWait=false) = 0;
	// returns the buffer into which the data has been or will be read
	// at least GetBytesRead() bytes in this buffer are guaranteed to be already read
	// DO NOT USE THIS BUFFER during read operation! DO NOT READ from it, it can lead to memory corruption!
	virtual const void* GetBuffer () = 0;

	// tries to stop reading the stream; this is advisory and may have no effect
	// but the callback	will not be called after this. If you just destructing object,
	// dereference this object and it will automatically abort and release all associated resources.
	virtual void Abort() {}

	// tries to raise the priority of the read; this is advisory and may have no effect
	virtual void RaisePriority (unsigned int nPriority) {}

	// Returns the transparent DWORD that was passed in the StreamReadParams::dwUserData field
	// of the structure passed in the call to IStreamEngine::StartRead
	virtual DWORD_PTR GetUserData() = 0;

	// unconditionally waits until the callback is called
	// i.e. if the stream hasn't yet finish, it's guaranteed that the user-supplied callback
	// is called before return from this function (unless no callback was specified)
  virtual void Wait() = 0;
protected:
	// the clients are not allowed to destroy this object directly; only via Release()
	virtual ~IReadStream() {}
};


// Description:
//  CryPak supports asynchronous reading through this interface. The callback 
//  is called from the main thread in the frame update loop.
//
//  The callback receives packets through StreamOnComplete() and 
//  StreamOnProgress(). The second one can be used to update the asset based 
//  on the partial data that arrived. the callback that will be called by the 
//  streaming engine must be implemented by all clients that want to use 
//  StreamingEngine services
//
// Remarks:
//  the pStream interface is guaranteed to be locked (have reference count > 0)
//  while inside the function, but can vanish any time outside the function.
//  If you need it, keep it from the beginning (after call to StartRead())
//  some or all callbacks MAY be called from inside IStreamEngine::StartRead()
// 
// Example:
// <code>
// IStreamEngine *pStreamEngine = g_pISystem->GetStreamEngine();	 // get streaming engine
// IStreamCallback *pCallback = &MyClass;									// user
// 
// StreamReadParams params;
// 
// params.dwUserData = 0;
// params.nSize = 0;
// params.pBuffer = NULL;
// params.nLoadTime = 10000;
// params.nMaxLoadTime = 10000;
// params.nFlags |= SRP_FLAGS_ASYNC_PROGRESS;
// 
// pStreamEngine->StartRead(  .. pCallback .. params .. );			 // registers callback
// </code>
class IStreamCallback
{
public:
	// For some applications, even partially loaded data can be useful.
	// To have this callback possibly called, specify the corresponding flag in IStreamEngine::StartRead()
	// This callback signals about finish of reading of nSize bytes to the specified buffer pData
	// so the client can read that much data inside this callback
	// NOTE:
	//   The default implementation is {} so the clients need not implement it if they don't care
	//   This callback is not guaranteed to be called.
	//   If this callback is called, it doesn't mean the data load will finish successfully;
	//     it still may not finish (then the OnError is called)
	//   nSize is always LESS than the requested data size; when it's equal, StreamOnComplete is called instead
	virtual void StreamOnProgress (IReadStream* pStream) {}

	// signals that reading the requested data has completed (with or without error).
	// this callback is always called, whether an error occurs or not
	// pStream will signal either IsFinished() or IsError() and will hold the (perhaps partially) read data until this interface is released
	// GetBytesRead() will return the size of the file (the completely read buffer) in case of successful operation end
	// or the size of partially read data in case of error (0 if nothing was read)
	// Pending status is true during this callback, because the callback itself is the part of IO operation
	// nError == 0 : Success
	// nError != 0 : Error code
	virtual void StreamOnComplete (IReadStream* pStream, unsigned nError) = 0;
};



enum StreamingStrategy
{
	// First in-first out, the strategy which ignores priorities
	SS_FIFO,
	// Random order, ignoring priorities, but the sequence is chosen to read as fast as possible
	SS_FAST,
	// Inside the same priority class, first in - first out. 
	// The highest priorities get serviced first
	SS_PRIORITIZED,
	// attempt to make read smooth, taking priorities into account
	SS_MULTISTREAM
};


//////////////////////////////////////////////////////////////////////////
// this is the approximate parameter block for the streaming engine 
// set up
struct StreamEngineParams
{
	// the strategy to use when queuing clients
	StreamingStrategy nStrategy;
	// the stream capacity to use, at most N bytes per second
	// if read requests come faster than that, delay loading
	unsigned nMaxBytesPerSecond;
	// the maximum number of ticks (TICK is yet to be defined - 
	// CPU clock, or mcs, or ms, or whatever) to spend inside
	// the callbacks per SECOND. If callbacks spend more than that, 
	// delay callback execution until the next frame
	// The actual limit is per-frame, the streaming engine uses estimated
	// FPS to calculate the per-frame max callback time.
	unsigned nMaxCallbackTicksPerSecond;
	// the maximum allowable simultaneously open streams
	unsigned nMaxStreams;
};


#endif //_CRY_COMMON_STREAM_ENGINE_HDR_
