#ifndef _xbSHAREDMEM_
#define _xbSHAREDMEM_
/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbSharedMem
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/

#ifndef _xbPTR_
#	include "xbPtr.h"
#endif

#ifndef _xbSYNC_
#	include "xbSync.h"
#endif

#ifndef _xbSECURITY_
#	include "xbSecurity.h"
#endif

//CAUTION: file format used are different in debug mode and release mode.
// so define different GUID for GetFileID() virtual
//no precaution to take with filename: '-d' is added to it in debug mode
#ifdef _DEBUG
#	define _dbgMEM
#endif
/*
Memory Management and the Debug Heap

NoMansLand (0xFD)

The NoMansLand buffers on either side of the memory used by an application 
are currently filled with 0xFD.

Freed blocks (0xDD)

  The freed blocks kept unused in the debug heap’s linked list when the 
  _CRTDBG_DELAY_FREE_MEM_DF flag is set are currently filled with 0xDD.

New objects (0xCD)

New objects are filled with 0xCD when they are allocated.
*/


#define xbSHAREDHEAP_CBYTESECT		64
#define xbSHAREDHEAP_CBYTEPAGE		16384
#define xbSHAREDHEAP_CBYTELOADEDMAX	524288UL

struct xbEXPORT xbSharedHandle
{
	xbSharedHandle()
	{
		m_handle = 0;
	}
	bool IsValid() const {return m_handle != 0;}
	bool IsEmpty() const {return m_handle == 0;}
	ulong m_handle;
};


class __declspec(uuid("{6A9968D6-8FEC-4ac3-8A44-10FE162FBBA2}")) xbSharedHeap;
struct xbSharedPageHdr;
struct xbSharedMemHdr;

class xbEXPORT __declspec(uuid("{6A9968D6-8FEC-4ac3-8A44-10FE162FBBA2}"))
	xbSharedMem:public xbObject
{
public:
	xbSharedHandle	GetHandle() const;
	
	void *LockMem();
	void UnlockMem();

	void LockUse();
	void UnlockUse();

	ulong GetLength() const
	{
		xbSectLock lck(&m_sectData);
		if (!m_pHeap)
			xbThrowEsys("xbSharedMem::GetLength() : void memory");
		return m_cByte;
	}
protected:
	mutable xbSectShare m_sectData;
	virtual void onBeforeDestruction(bool bInDestroy);
	void destroy();
	xbSharedMem(xbSharedHeap *pHeap, ulong pageNo, ulong sectNo, ulong cByte, bool bLoaded);

	class xbEXPORT HeapPtr:public xbObsPtrExt<xbSharedHeap>
	{
	public:
		HeapPtr(xbSharedMem *pMem)
			:xbObsPtrExt<xbSharedHeap>(&pMem->m_sectData)
		{
			m_pMem = pMem;
		}
		HeapPtr &operator=(xbSharedHeap* pObj)
		{
			xbObsPtrExt<xbSharedHeap>::operator =(pObj);
			return *this;
		}
	protected:
		virtual void onUnlink(sIxbPtrNotif*pPtrNotif)
		{
			eProtectTrace(m_pMem->destroy());
			m_pMem = NULL;
		}
		xbSharedMem	*m_pMem;
	};
	friend HeapPtr;


	HeapPtr			m_poHeap;
	xbSharedHeap	*m_pHeap;

	ulong		m_pageNo,m_sectNo,m_cByte,m_cLockMem,m_cLockUse;
private:
	xbSharedMem(const xbSharedMem &heap);
	xbSharedMem &operator=(const xbSharedMem &heap);

	friend xbSharedHeap;
};

class xbEXPORT xbSharedHeap:public xbObject, public sIxbLock
{
public:
	xbSharedHeap(uint64 cByteMax,LPCSTR pFilePath = NULL,bool bCreate = false,ulong cByteLoadedMax = xbSHAREDHEAP_CBYTELOADEDMAX,ulong cByteSect = xbSHAREDHEAP_CBYTESECT,ulong cBytePage = xbSHAREDHEAP_CBYTEPAGE);
	xbSharedHeap(const xbSecurityAttr &attr,uint64 cByteMax,LPCSTR pFilePath = NULL,bool bCreate = false,ulong cByteLoadedMax = xbSHAREDHEAP_CBYTELOADEDMAX,ulong cByteSect = xbSHAREDHEAP_CBYTESECT,ulong cBytePage = xbSHAREDHEAP_CBYTEPAGE);

	virtual ~xbSharedHeap()
	{
		eProtect(if (m_bCreated) Destroy());
	}

	xbImplObject();

	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(xbSharedHeap);
		xbImplInterface(sIxbLock);
		return xbObject::GetInterface(riid,ppInterf);
	}

	virtual void Create();
	virtual void Destroy();
	virtual const GUID &GetFileID() const;

	bool IsValid(const xbSharedHandle &handle,ulong cByte = 0) const;

	virtual bool Alloc(xbRefPtr<xbSharedMem> *pprMem,ulong cByte,xbOpMode opm =xbOp_Required); 
	virtual bool Load(xbRefPtr<xbSharedMem> *pprMem,const xbSharedHandle &handle,ulong cByteForCheck = 0,xbOpMode opm =xbOp_Required);

	virtual void				Lock() const
	{
		m_mutex.Lock();
	}
	virtual void				Unlock() const
	{
		m_mutex.Unlock();
	}

	inline ulong GetSysGranularity() const {return g_xbEngineCenter.GetSysPageGranularity();}

	int Check();

	virtual bool	IsMemLocked() const
	{
		xbLock lck(&m_mutex);
		return m_cMemLock > 0;
	}
	virtual bool	EndMemLocks()
	{
		xbLock lck(&m_mutex);
		m_bNoMemLocks = true;
		return m_cMemLock <= 0;
	}
	
	bool getMainBlock(xbRefPtr<xbSharedMem> *pprMem,ulong cByteForCreate = 0,ulong cByteForCheck = 0);
	bool delMainBlock();

	//These functions doesn't substract blocks and pages headers sizes
	int64 GetBusyByteCount() const;
	int64 GetFreeByteCount() const;

	int64 GetUsedFreeByteCount() const;
	int64 GetUnusedFreeByteCount() const;
	int64 GetMaxByteCount() const {return m_cByteMax;}
protected:
	void *lockMem(ulong pageNo,ulong sectNo);
	void unlockMem(ulong pageNo,ulong sectNo);
	void lockUse(ulong pageNo,ulong sectNo);
	void unlockUse(ulong pageNo,ulong sectNo);
	void free(ulong pageNo,ulong sectNo,ulong cByte);

	xbMutex				m_mutex;
	xbString			m_filepath,m_name;
	bool				m_bHeapCreated,m_bPermanentShared;
	ulong				m_cAskedByteSect,m_cAskedBytePage,m_cAskedByteLoadedMax;
	uint64				m_cAskedByteMax;
	ulong				m_cSectPage,m_cByteSect,m_cBytePage,m_cPageLoadedMax;
	uint64				m_cByteMax;
	ulong				m_cByteHdr,m_cPageMax;
	long				m_cMemLock;
	bool				m_bNoMemLocks;

	HANDLE				m_hFile,m_hMap;

	xbSharedMemHdr		*m_pHdr;

public:
	struct xbEXPORT Page
	{
		xbSharedHeap	*m_pHeap;
		BYTE			*m_pMem;			
		Page			*m_pLoadedNext,*m_pLoadedPrev;
		bool			m_bLoadedLinked;
		uint64			m_offset;
		ulong			m_cByte;
		ulong			m_cMemLock;
		ulong				m_pageNo;
		xbSharedPageHdr *m_pHdr;

		Page(xbSharedHeap *pHeap,uint64 offset,ulong cByte,ulong no,xbSharedPageHdr *pHdr)
		{
			m_pLoadedNext = m_pLoadedPrev = NULL;
			m_pMem = NULL;
			m_pHeap = pHeap;
			m_offset = offset;
			m_cByte = cByte;
			m_pageNo = no;
			m_cMemLock = 0;
			m_bLoadedLinked = false;
			m_pHdr = pHdr;
		}
		~Page()
		{
			eProtect(unload());
		}


		BYTE *load();
		void unload();

		void loadedLink();
		void loadedUnlink();

		void *alloc(ulong cSect,ulong cByte,ulong *pSectNo);
		void free(ulong sectNo,ulong cByte);

		xbHeapNewImpl(Page);
	};
protected:

	xbPtrArray<Page>	m_arrPage;
	Page				*m_pOldestLoadedPage,*m_pNewestLoadedPage;
	ulong				m_cPageLoaded;

	friend Page;

	Page *newPage(ulong sz,xbOpMode opm);
	Page *getPage(ulong ix,xbOpMode opm =xbOp_Required);
	void cleanLoadedPages(bool bToLoad);

	xbSecurityAttr	m_attr;
private:
	xbSharedHeap(const xbSharedHeap &heap);
	xbSharedHeap &operator=(const xbSharedHeap &heap);

	friend xbSharedMem;
};


class xbMemLock
{
public:
	xbMemLock()
	{
		m_pObj = NULL;
	}
	xbMemLock(xbSharedMem *pObj)
	{
		m_pObj = NULL;
		if (pObj) Lock(pObj);
	}
	~xbMemLock()
	{
		eProtect(Unlock());
	}
	void Lock(xbSharedMem *pObj)
	{
		if (m_pObj) Unlock();
		m_pMem = pObj->LockMem();
		m_pObj = pObj;
	}
	void Unlock()
	{
		if (!m_pObj) return;
		xbSharedMem	*pObj = m_pObj;
		m_pObj = NULL;
		m_pMem = NULL;
		pObj->UnlockMem();
	}
	bool IsLocked() const {return m_pObj != NULL;}

	operator char*()
	{
		if (!m_pMem) xbThrowEsys("xbMemLock::operator char*() : lock not valid");
		return (char*)m_pMem;
	}
	operator void*()
	{
		if (!m_pMem) xbThrowEsys("xbMemLock::operator void*() : lock not valid");
		return m_pMem;
	}
	void *GetPtr()
	{
		if (!m_pMem) xbThrowEsys("xbMemLock::operator void*() : lock not valid");
		return m_pMem;
	}
protected:
	xbSharedMem	*m_pObj;
	void		*m_pMem;
};

#define	SHAREDMEMHDR_VER	0x10002
struct SharedBlockHdr
{
	ulong	m_offsNext;	
	ulong	m_offsPrev;
	ulong	m_cByte;
	ulong	m_cBytePrev;
	long	m_cLock;
	SharedBlockHdr()
	{
		xbAssert(sizeof(SharedBlockHdr) == 20);
	}
};

struct xbSharedPageHdr
{
	/*000*/ ulong	m_cSect;
	/*004*/ ulong	m_cFreeSect;
	/*008*/ ulong	m_offsFirstBusyBlock;
	/*012*/ ulong	m_offsFirstFreeBlock;
	/*016*/ uint64	m_offset;
	/*024*/ uint64	m_rsrv;
	xbSharedPageHdr()
	{
		xbAssert(sizeof(xbSharedPageHdr) == 32);
		m_cSect = 0;
		m_cFreeSect = 0;
		m_offsFirstBusyBlock = -1;
		m_offsFirstFreeBlock = -1;
		m_offset = 0;
		m_rsrv = 0;
	}
};/*032*/ 

#ifdef _dbgMEM
struct __declspec(uuid("{F081628D-A651-4e9a-A598-E78D81A1DC0C}"))
#else
struct __declspec(uuid("{A63F4759-0D39-4e80-A6C9-EE5F8B62F20E}"))
#endif
	xbSharedMemHdr
{
	/*000*/ GUID			m_id;
	/*016*/	ulong			m_ver;
	/*024*/ ulong			m_cPage;
	/*028*/ ulong			m_cPageMax;
	/*032*/ ulong			m_cByteSect;
	/*036*/ ulong			m_cSectPage;
	/*040*/ uint64			m_cByteMax;
	/*048*/	xbSharedHandle	m_mainBlockId; 
	/*052*/ char			m_rsrv[12];
	/*064*/ xbSharedPageHdr	m_pages[1];
	xbSharedMemHdr(const GUID &id)
	{
		xbAssert(sizeof(xbSharedMemHdr) == 96);
		m_id = id;
		m_ver = SHAREDMEMHDR_VER;
		m_cPage = m_cPageMax = 0;
		m_cByteSect = m_cSectPage = 0;
		m_cByteMax = 0;
		memset(m_rsrv,0,sizeof(m_rsrv));
	}
	void Check(const xbString &file,const GUID &id)
	{
		if (m_id != id
			|| m_cByteSect == 0 
			|| m_cSectPage == 0
			|| m_cPageMax == 0)
			xbThrowEsys("Invalid header for file '"+file+"'");
		if ((m_ver & 0xFFFF0000) != (SHAREDMEMHDR_VER&0xFFFF0000))
			xbThrowEsys("Invalid header version for file '"+file+"'");
	}
}; /*064*/

#endif
