#include "ehs/system/Semaphore_P.h"
#include "ehs/Log.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <cerrno>

namespace ehs
{
	Semaphore::~Semaphore()
	{
		if (!Semaphore::IsValid())
			return;

		if (sem_destroy(&hdl) == -1)
			EHS_LOG_INT(LogType::ERR, 0, "Failed to release semaphore with error #" + Str_8::FromNum(errno) + ".");

		valid = false;
	}

	Semaphore::Semaphore()
		: hdl{}, valid(false)
	{
	}

	Semaphore::Semaphore(const Str_8& name, const UInt_32 initial)
		: BaseSemaphore(name, initial), hdl{}, valid(false)
	{
		Semaphore::Initialize();
	}

	Semaphore::Semaphore(const UInt_32 initial)
		: BaseSemaphore(initial), hdl{}, valid(false)
	{
		Semaphore::Initialize();
	}

	Semaphore::Semaphore(Semaphore&& sem) noexcept
		: BaseSemaphore((Semaphore&&)sem), hdl(sem.hdl), valid(sem.valid)
	{
		sem.hdl = {};
		sem.valid = false;
	}

	Semaphore::Semaphore(const Semaphore& sem)
		: BaseSemaphore(sem), hdl{}, valid(false)
	{
		Semaphore::Initialize();
	}

	Semaphore& Semaphore::operator=(Semaphore&& sem) noexcept
	{
		if (this == &sem)
			return *this;

		Release();

		BaseSemaphore::operator=((Semaphore&&)sem);

		hdl = sem.hdl;
		valid = sem.valid;

		sem.hdl = {};
		sem.valid = false;

		return *this;
	}

	Semaphore& Semaphore::operator=(const Semaphore& sem)
	{
		if (this == &sem)
			return *this;

		Release();

		BaseSemaphore::operator=(sem);

		hdl = {};
		valid = false;

		Initialize();

		return *this;
	}

	void Semaphore::Initialize()
	{
		if (IsValid())
			return;

		if (GetName().Size())
		{
			sem_t* result = sem_open("/" + GetName(), O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, GetInitial());
			if (!result)
				EHS_LOG_INT(LogType::ERR, 0, "Failed to create semaphore with error #" + Str_8::FromNum(errno) + ".");

			hdl = *result;
		}
		else
		{
			if (sem_init(&hdl, 0, GetInitial()) == -1)
				EHS_LOG_INT(LogType::ERR, 0, "Failed to create semaphore with error #" + Str_8::FromNum(errno) + ".");
		}

		valid = true;
	}

	void Semaphore::Release()
	{
		if (!IsValid())
			return;

		if (sem_destroy(&hdl) == -1)
			EHS_LOG_INT(LogType::ERR, 0, "Failed to release semaphore with error #" + Str_8::FromNum(errno) + ".");

		hdl = {};

		valid = false;
	}

	void Semaphore::Signal(const UInt_32 inc)
	{
		if (!IsValid())
			return;

		if (sem_post(&hdl) == -1)
			EHS_LOG_INT(LogType::ERR, 0, "Failed to signal semaphore with error #" + Str_8::FromNum(errno) + ".");
	}

	bool Semaphore::Wait(const UInt_32 timeout)
	{
		if (!IsValid())
			return false;

		int result;

		if (timeout == EHS_INFINITE)
		{
			result = sem_wait(&hdl);
		}
		else
		{
			timespec time = {timeout / 1000, timeout % 1000 * 1000000};
			result = sem_timedwait(&hdl, &time);
		}

		if (result == -1)
		{
			int code = errno;
			if (code != ETIMEDOUT)
				EHS_LOG_INT(LogType::ERR, 0, "Failed to wait for semaphore with error #" + Str_8::FromNum(errno) + ".");

			return false;
		}

		return true;
	}

	bool Semaphore::IsValid() const
	{
		return valid;
	}
}