#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 <sys/shm.h>
#include <linux/limits.h>
#endif	//	__linux__
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "defines.h"
#include "sema.h"

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

#define _IS_VALID_SEMID(id)				((id) >= 0)

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

CSemaphore::CSemaphore(void) :	m_nSemID(_INVALID_ID)
{
}

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

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

int CSemaphore::Create(const char *pszFilename, int nsems, unsigned short *pInitArr)
{
	int nFdSema, nRet = -1;

	if((nFdSema = ::open(pszFilename, O_RDWR | O_CREAT | O_TRUNC, _NUMBER_OF_THE_BEAST)) >= 0)
	{
		if(!::flock(nFdSema, LOCK_EX))
		{
			key_t semKey;

			if((semKey = ::ftok(pszFilename, 'R')) >= 0)
			{
				if((m_nSemID = ::semget(semKey, nsems, IPC_CREAT | _NUMBER_OF_THE_BEAST)) >= 0)
				{
					struct semid_ds sds;
					::memset(&sds, 0, sizeof(sds));

					if(!IpcStat(&sds))
					{
						if(!sds.sem_otime)
						{
							bool fCreateArr = !pInitArr;

							if(fCreateArr)
							{
								pInitArr = new unsigned short[sds.sem_nsems];

								for(unsigned long i = 0; i < sds.sem_nsems; i++)
								{
									pInitArr[i] = 0;
								}
							}

							SetAll(pInitArr);

							if(fCreateArr)
								delete [] pInitArr;

							struct sembuf sb[2] = {{0, 1, IPC_NOWAIT}, {0, -1, IPC_NOWAIT}};
							Op(sb, 2);

							TRACE("CSemaphore::Create: Created new Semaphore!\n");
							nRet = 1;
						}
						else
						{
							TRACE("CSemaphore::Create: Opened existing Semaphore!\n");
							nRet = 0;
						}
					}
					else
					{
						TRACE("CSemaphore::Create: IpcStat failed - errno: %d!\n", errno);
					}
				}
				else
				{
					TRACE("CSemaphore::Create: Failed to create Semaphore - errno: %d!\n", errno);
				}
			}
			else
			{
				TRACE("CSemaphore::Create: Failed to create semKey - errno: %d!\n", errno);
			}

			::flock(nFdSema, LOCK_UN);
		}
		else
		{
			TRACE("CSemaphore::Create: Failed to create Lock on '%s' - errno: %d!\n", pszFilename, errno);
		}

		::close(nFdSema);
	}
	else
	{
		TRACE("CSemaphore::Create: Failed to create '%s' - errno: %d!\n", pszFilename, errno);
	}

	return nRet;
}

void CSemaphore::Release(void)
{
	if(_IS_VALID_SEMID(m_nSemID))
	{
		m_nSemID = _INVALID_ID;
		TRACE("CSemaphore::Release\n");
	}
}

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

int CSemaphore::Ctl(int cmd)
{
	union semun su;
	su.val = 0;
	return Ctl(0, cmd, su);
}

int CSemaphore::Ctl(int semnum, int cmd)
{
	union semun su;
	su.val = 0;
	return Ctl(semnum, cmd, su);
}

int CSemaphore::Ctl(int semnum, int cmd, int val)
{
	union semun su;
	su.val = val;
	return Ctl(semnum, cmd, su);
}

int CSemaphore::Ctl(int cmd, struct semid_ds *buf)
{
	union semun su;
	su.buf = buf;
	return Ctl(0, cmd, su);
}

int CSemaphore::Ctl(int cmd, unsigned short  *arr)
{
	union semun su;
	su.array = arr;
	return Ctl(0, cmd, su);
}

int CSemaphore::Ctl(int cmd, struct seminfo *si)
{
	union semun su;
	su.__buf = si;
	return Ctl(0, cmd, su);
}

int CSemaphore::Ctl(int semnum, int cmd, union semun su)
{
	if(!_IS_VALID_SEMID(m_nSemID))
	{
//		TRACE("CSemaphore::Ctl: Invalid SemID: %d!\n", m_nSemID);
		errno = EINVAL;
		return -1;
	}

	return ::semctl(m_nSemID, semnum, cmd, su);
}

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

int CSemaphore::IpcStat(struct semid_ds *buf)
{
	return Ctl(IPC_STAT, buf);
}

int CSemaphore::IpcSet(struct semid_ds *buf)
{
	return Ctl(IPC_SET, buf);
}

int CSemaphore::IpcRmID(void)
{
	int nRet = Ctl(IPC_RMID);
	Release();
	return nRet;
}

int CSemaphore::IpcInfo(struct seminfo *si)
{
	return Ctl(IPC_INFO, si);
}

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

int CSemaphore::SemStat(int index, struct semid_ds *buf)
{
	union semun su;
	su.buf = buf;
	return ::semctl(index, 0, SEM_STAT, su);
}

int CSemaphore::SemInfo(struct seminfo *si)
{
	return Ctl(SEM_INFO, si);
}

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

int CSemaphore::GetVal(int semnum)
{
	return Ctl(semnum, GETVAL);
}

int CSemaphore::SetVal(int semnum, int val)
{
	return Ctl(semnum, SETVAL, val);
}

int CSemaphore::GetAll(unsigned short *arr)
{
	return Ctl(GETALL, arr);
}

int CSemaphore::SetAll(unsigned short *arr)
{
	return Ctl(SETALL, arr);
}

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

int CSemaphore::GetPid(int semnum)
{
	return Ctl(semnum, GETPID);
}

int CSemaphore::GetNCnt(int semnum)
{
	return Ctl(semnum, GETNCNT);
}

int CSemaphore::GetZCnt(int semnum)
{
	return Ctl(semnum, GETZCNT);
}

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

bool CSemaphore::Op(struct sembuf *sops, size_t nsops)
{
	if(!_IS_VALID_SEMID(m_nSemID))
	{
//		TRACE("CSemaphore::Op: Invalid SemID: %d!\n", m_nSemID);
		errno = EINVAL;
		return false;
	}

	return !::semop(m_nSemID, sops, nsops);
}

bool CSemaphore::TimedOp(struct sembuf *sops, size_t nsops, const struct timespec *timeout)
{
	if(!_IS_VALID_SEMID(m_nSemID))
	{
		TRACE("CSemaphore::TimedOp: Invalid SemID: %d!\n", m_nSemID);
		errno = EINVAL;
		return false;
	}

	return !::semtimedop(m_nSemID, sops, nsops, timeout);
}

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

bool CSemaphore::Lock(int semnum)
{
	struct sembuf sb;
	sb.sem_num	= (unsigned short)semnum;
	sb.sem_op	= -1;
	sb.sem_flg	= SEM_UNDO;
	return Op(&sb, 1);
}

bool CSemaphore::LockTimeout(const struct timespec *timeout, int semnum)
{
	struct sembuf sb;
	sb.sem_num	= (unsigned short)semnum;
	sb.sem_op	= -1;
	sb.sem_flg	= SEM_UNDO;
	return TimedOp(&sb, 1, timeout);
}

bool CSemaphore::LockTimeout(unsigned long msec, int semnum)
{
	struct timespec to;
	to.tv_sec	= msec / 1000;
	to.tv_nsec	= (msec % 1000) * 1000000;
	return LockTimeout(&to, semnum);
}

bool CSemaphore::Unlock(int semnum)
{
	struct sembuf sb;
	sb.sem_num	= (unsigned short)semnum;
	sb.sem_op	= 1;
	sb.sem_flg	= SEM_UNDO;
	return Op(&sb, 1);
}

bool CSemaphore::WaitZero(int semnum)
{
	struct sembuf sb;
	sb.sem_num	= (unsigned short)semnum;
	sb.sem_op	= 0;
	sb.sem_flg	= SEM_UNDO;
	return Op(&sb, 1);
}

bool CSemaphore::WaitZeroTimeout(const struct timespec *timeout, int semnum)
{
	struct sembuf sb;
	sb.sem_num	= (unsigned short)semnum;
	sb.sem_op	= 0;
	sb.sem_flg	= SEM_UNDO;
	return TimedOp(&sb, 1, timeout);
}

bool CSemaphore::WaitZeroTimeout(unsigned long msec, int semnum)
{
	struct timespec to;
	to.tv_sec	= msec / 1000;
	to.tv_nsec	= (msec % 1000) * 1000000;
	return WaitZeroTimeout(&to, semnum);
}

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

void CSemaphore::DumpIpcPerm(struct ipc_perm *ip)
{
	printf("__key: 0x%X\n", ip->__key);
	printf("uid:   %d\n", ip->uid);
	printf("gid:   %d\n", ip->gid);
	printf("cuid:  %d\n", ip->cuid);
	printf("cgid:  %d\n", ip->cgid);
	printf("mode:  %hu\n", ip->mode);
	printf("__seq: %hu\n", ip->__seq);
}

void CSemaphore::DumpSemidDs(struct semid_ds *sid)
{
	CSemaphore::DumpIpcPerm(&sid->sem_perm);
	printf("sem_otime: %lu\n", sid->sem_otime);
	printf("sem_ctime: %lu\n", sid->sem_ctime);
	printf("sem_nsems: %lu\n", sid->sem_nsems);
}

void CSemaphore::DumpSeminfo(struct seminfo *psi)
{
	printf("semmap: %d\n", psi->semmap);
	printf("semmni: %d\n", psi->semmni);
	printf("semmns: %d\n", psi->semmns);
	printf("semmnu: %d\n", psi->semmnu);
	printf("semmsl: %d\n", psi->semmsl);
	printf("semopm: %d\n", psi->semopm);
	printf("semume: %d\n", psi->semume);
	printf("semusz: %d\n", psi->semusz);
	printf("semvmx: %d\n", psi->semvmx);
	printf("semaem: %d\n\n", psi->semaem);
}

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

CShmSemaphore::CShmSemaphore(void)
{
}

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

int CShmSemaphore::Create(const char *pszFilename)
{
	int nRet;
	unsigned short nVals[2] = {1, 0};
	if((nRet = CSemaphore::Create(pszFilename, 2, nVals)) < 0)
		return nRet;
	Unlock(1);
	TRACE("CShmSemaphore::Create\n");
	return nRet;
}

int CShmSemaphore::Create(const uuid_t &ruuid, const char *pszDir)
{
	if(!pszDir)
		pszDir = "";

	size_t nLenReq = strlen(pszDir) + _UUID_STRING_LEN + 2;

	if(nLenReq > PATH_MAX)
	{
		TRACE("CShmSemaphore::Create: directory name too long\n");
		errno = EINVAL;
		return -1;
	}

	char szUuid[_UUID_STRING_LEN + 1];
	char szSema[PATH_MAX];
	_uuid_unparse(&ruuid, szUuid, sizeof(szUuid));
	sprintf(szSema, "%s/%s.sem", pszDir, szUuid);
	return Create(szSema);
}

void CShmSemaphore::Release(void)
{
	if(_IS_VALID_SEMID(m_nSemID))
	{
		int nRefs = GetVal(1);

		if(nRefs > 1)
		{
			TRACE("CShmSemaphore::Release: remaining References: %d\n", nRefs - 1);
			Lock(1);
		}
		else if(nRefs == 1)
		{
			TRACE("CShmSemaphore::Release: remaining References: %d - Semaphore removed!\n", nRefs - 1);
			IpcRmID();
		}

		CSemaphore::Release();
	}
}
