#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NONSTDC_NO_WARNINGS
#include <windows.h>
#include <io.h>
#include "../Win32/w32def.h"
#endif	//	_WIN32

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/file.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <linux/limits.h>
#include <sys/shm.h>
#endif	//	__linux__
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "defines.h"
#include "shmrot.h"

/////////////////////////////////////////////////////////////////////////////

#define _FILES_DIRECTORY				"/tmp"

/////////////////////////////////////////////////////////////////////////////

#define _SHMROT_ENTRY_FLAG_VALID		0x00000001

/////////////////////////////////////////////////////////////////////////////

// {99df2b86-f904-425d-9fcc-c83c329d0937}
DEFINE_UUID(GUID_SHMROT, 0x99df2b86, 0xf904, 0x425d, 0x9f, 0xcc, 0xc8, 0x3c, 0x32, 0x9d, 0x09, 0x37);

/////////////////////////////////////////////////////////////////////////////

CShmROT::CShmROT(void)	: m_pTable(NULL)
{
}

CShmROT::~CShmROT(void)
{
	Release();
}

bool CShmROT::Create(void)
{
	TRACE("CShmROT::Create!\n");

	int nRet = m_shmRot.Create(GUID_SHMROT, sizeof(SHMROT));

	if(	nRet >= 0 &&
		(m_pTable = (LPSHMROT)m_shmRot.Attach()))
	{
		if(nRet == 1)
		{
			m_pTable->version		= _ROT_CURRENT_VERSION;
			m_pTable->maxEntries	= _ROT_MAX_TABLE_ENTRIES;
			m_pTable->entrySize		= (uint32_t)sizeof(SHMROT_ENTRY);

		}
		else if(nRet == 0)
		{
			if(m_pTable->version != _ROT_CURRENT_VERSION)
			{
				// ...
			}
		}

		return true;
	}

	return false;
}

void CShmROT::Release(void)
{
	if(m_pTable)
	{
		TRACE("CShmROT::Release!\n");
		m_shmRot.Detach(m_pTable);
		m_shmRot.Release();
		m_pTable = NULL;
	}
}

CShm* CShmROT::AcquireShm(const char *pszUuid, size_t nSizeElement, size_t nCntElements, const char *pszDescName)
{
	if(!m_pTable)
	{
		TRACE("CShmROT::AcquireShm: ROT not initialized!\n");
		return NULL;
	}

	uuid_t uuid;
	CShm *pShm = NULL;

	if(!_uuid_parse(pszUuid, &uuid))
	{
		TRACE("CShmROT::AcquireShm: invalid uuid!\n");
		return NULL;
	}

	int nIndexFound			= -1;
	int nFirstUnusedIndex	= -1;

	for(int i = 0; i < (int)m_pTable->maxEntries; i++)
	{
		SHMROT_ENTRY &re = m_pTable->rot[i];

		if(re.flags & _SHMROT_ENTRY_FLAG_VALID)
		{
			if(!_uuid_compare(&re.uuid, &uuid))
			{
				nIndexFound = i;
				break;
			}
		}
		else if(nFirstUnusedIndex == -1)
		{
			nFirstUnusedIndex = i;
		}
	}

	if(nIndexFound >= 0) // found existing entry
	{
		SHMROT_ENTRY &re = m_pTable->rot[nIndexFound];
		bool bMatch = (re.nSizeElement == nSizeElement) && (re.nCntElements == nCntElements);

		if(bMatch)
		{
			TRACE("CShmROT::AcquireShm: Found matching SHM entry!\n");

			if((pShm = new CShm))
			{
				if(pShm->Create(uuid, nSizeElement * nCntElements) < 0)
				{
					pShm->Release();
					delete pShm;
					pShm = NULL;
					TRACE("CShmROT::AcquireShm: Failed to create new CShm!\n");
				}
				else
				{
					re.nCntHandles++;
				}
			}
			else
			{
				TRACE("CShmROT::AcquireShm: Failed to allocate new CShm!\n");
			}
		}
		else
		{
			TRACE("CShmROT::AcquireShm: SHM uuid exists, but size does not match!\n");
		}
	}
	else if(nFirstUnusedIndex >= 0) // next free slot
	{
		TRACE("CShmROT::AcquireShm: Create new SHM entry!\n");

		SHMROT_ENTRY &re = m_pTable->rot[nFirstUnusedIndex];

		if((pShm = new CShm))
		{
			if(pShm->Create(uuid, nSizeElement * nCntElements) < 0)
			{
				pShm->Release();
				delete pShm;
				pShm = NULL;
				TRACE("CShmROT::AcquireShm: Failed to create new CShm!\n");
			}
			else
			{
				re.flags		= _SHMROT_ENTRY_FLAG_VALID;
				re.nSizeElement	= (uint32_t)nSizeElement;
				re.nCntElements	= (uint32_t)nCntElements;
				re.nCntHandles	= 1;
				_uuid_copy(&re.uuid, &uuid);
				memset(re.szDescName, 0, sizeof(re.szDescName));

				if(pszDescName)
				{
					size_t nLen = strlen(pszDescName);

					if(nLen > 0)
					{
						if(nLen >= _ROT_MAX_ENTRY_NAME)
							nLen = _ROT_MAX_ENTRY_NAME - 1;
						memcpy(re.szDescName, pszDescName, nLen);
						re.szDescName[nLen] = '\0';
					}
				}
			}
		}
		else
		{
			TRACE("CShmROT::AcquireShm: Failed to allocate new CShm!\n");
		}
	}
	else
	{
		TRACE("CShmROT::AcquireShm: SHM uuid not found and no slot available!\n");
	}

	return pShm;
}

void CShmROT::ReleaseShm(CShm *pShm)
{
	if(pShm)
	{
		const uuid_t &ruuid = pShm->Uuid();

		for(int i = 0; i < (int)m_pTable->maxEntries; i++)
		{
			SHMROT_ENTRY &re = m_pTable->rot[i];

			if(!_uuid_compare(&re.uuid, &ruuid))
			{
				if(re.flags & _SHMROT_ENTRY_FLAG_VALID)
				{
					if(re.nCntHandles > 0)
						--re.nCntHandles;
						
					if(re.nCntHandles == 0)
						re.flags &= ~_SHMROT_ENTRY_FLAG_VALID;
				}

				break;
			}
		}

		pShm->Release();
		delete pShm;
	}
}

int CShmROT::Lock(void)
{
	return m_shmRot.Lock();
}

int CShmROT::Unlock(void)
{
	return m_shmRot.Unlock();
}

void CShmROT::DumpEntries(CShmHandleMap &map)
{
	if(!m_pTable)
	{
		printf("CShmROT::DumpEntries: ROT not initialized!\n");
		return;
	}

	char szUuid[_UUID_STRING_LEN + 1];

	Lock();

	for(int i = 0; i < (int)m_pTable->maxEntries; i++)
	{
		SHMROT_ENTRY &re = m_pTable->rot[i];

		if(re.flags & _SHMROT_ENTRY_FLAG_VALID)
		{
			unsigned long nAttached = 0;
			_uuid_unparse(&re.uuid, szUuid, sizeof(szUuid));

			if(re.nCntHandles > 0)
			{
				CShm *pShm = map.LookupShm(re.uuid);
				if(pShm)
					nAttached = pShm->GetNumAttached();
			}

			printf(	"uuid: '%s',\n"						\
					"    flags:              0x%08x,\n"	\
					"    size of element:    %u,\n"		\
					"    number of elements: %u,\n"		\
					"    name:               '%s',\n"	\
					"    number of handles:  %u,\n"		\
					"    number of pointers: %lu\n",
					szUuid,
					re.flags,
					re.nSizeElement,
					re.nCntElements,
					re.szDescName,
					re.nCntHandles,
					nAttached);
		}
	}

	Unlock();
}
