// OAL: OS Abstraction Layer
#include "ascript/OAL.h"
#include "ascript/String.h"

#if defined(HAVE_WINDOWS_H)
#else
#include <dlfcn.h>
#include <sys/time.h>
#endif

namespace AScript {
namespace OAL {

const char FileSeparatorWin = '\\';
const char FileSeparatorUnix = '/';

// dirName must be specified as an absolute path.
String MakeAbsPath(char chSeparator, const char *fileName, const char *dirName)
{
	String str;
	String dirNameWk;
	if (dirName == NULL || dirName[0] == '\0' || dirName[0] == '.' &&
					(dirName[1] == '\0' ||
					 IsFileSeparator(dirName[1]) && dirName[2] == '\0')) {
		dirNameWk = GetCurDir();
	} else {
		dirNameWk = dirName;
	}
	if (IsFileSeparator(fileName[0])) {
		if (dirName == NULL || *dirName == '\0') return String(fileName);
		for (const char *p = dirNameWk.c_str(); *p != '\0' && !IsFileSeparator(*p); p++) {
			str += *p;
		}
	} else if (fileName[0] != '\0' && fileName[1] == ':') {
		// absolute path in Windows style.
	} else {
		int lenDirName = static_cast<int>(dirNameWk.size());
		if (lenDirName > 0 && IsFileSeparator(dirNameWk[lenDirName - 1])) {
			lenDirName--;
		}
		for (;;) {
			if (*fileName == '.' &&
					(IsFileSeparator(*(fileName + 1)) || *(fileName + 1) == '\0')) {
				fileName += IsFileSeparator(*(fileName + 2))? 2 : 1;
			} else if (*fileName == '.' && *(fileName + 1) == '.' &&
					(IsFileSeparator(*(fileName + 2)) || *(fileName + 2) == '\0')) {
				int len = lenDirName;
				if (len > 0) len--;
				for ( ; len > 0 && !IsFileSeparator(dirNameWk[len]); len--) ;
				if (len > 0) lenDirName = len;
				fileName += IsFileSeparator(*(fileName + 2))? 3 : 2;
			} else {
				break;
			}
		}
		for (const char *p = dirNameWk.c_str(); lenDirName > 0; p++, lenDirName--) {
			str += IsFileSeparator(*p)? chSeparator : *p;
		}
		str += chSeparator;
	}
	return JoinPathName(chSeparator, str, fileName);
}

String &JoinPathName(char chSeparator, String &pathName, const char *name)
{
	if (*name == '\0') return pathName;
	if (!pathName.empty() && !IsFileSeparator(pathName[pathName.size() - 1])) {
		pathName += chSeparator;
	}
	const char *p = name;
	if (IsFileSeparator(*p)) p++;
	for ( ; *p != '\0'; p++) {
		pathName += IsFileSeparator(*p)? chSeparator : *p;
	}
	return pathName;
}

String &JoinPathName(char chSeparator, String &pathName, const char *name, size_t len)
{
	if (*name == '\0' || len == 0) return pathName;
	if (!pathName.empty() && !IsFileSeparator(pathName[pathName.size() - 1])) {
		pathName += chSeparator;
	}
	const char *p = name;
	if (IsFileSeparator(*p)) {
		p++, len--;
	}
	for ( ; *p != '\0' && len > 0; p++, len--) {
		pathName += IsFileSeparator(*p)? chSeparator : *p;
	}
	return pathName;
}

#if defined(HAVE_WINDOWS_H)
//=============================================================================
// Windows API
//=============================================================================
const char FileSeparator = '\\';

String GetEnv(const char *name)
{
	DWORD len = ::GetEnvironmentVariable(name, NULL, 0);
	if (len == 0) return String("");
	char *buff = new char [len];
	::GetEnvironmentVariable(name, buff, len);
	String rtn(buff);
	delete[] buff;
	return rtn;
}

void PutEnv(const char *name, const char *value)
{
	::SetEnvironmentVariable(name, value);
}

void Rename(const char *src, const char *dst)
{
	::MoveFileEx(src, dst, MOVEFILE_REPLACE_EXISTING);
}

void Remove(const char *pathName)
{
	::DeleteFile(pathName);
}

bool MakeDir(const char *pathName, bool recursiveFlag)
{
	return ::CreateDirectory(pathName, NULL)? true : false;
}

bool ChangeCurDir(const char *pathName)
{
	return ::SetCurrentDirectory(pathName)? true : false;
}

String GetCurDir()
{
	char pathName[MAX_PATH];
	::GetCurrentDirectory(MAX_PATH, pathName);
	return String(pathName);
}

void Sleep(Number delay)	// unit: sec
{
	::Sleep(static_cast<long>(delay * 1000));	// unit: msec
}

Number GetTickTime()
{
	LARGE_INTEGER freq, counter;
	if (::QueryPerformanceFrequency(&freq) && ::QueryPerformanceCounter(&counter)) {
		return static_cast<Number>(counter.QuadPart) / freq.QuadPart;
	} else {
		return static_cast<Number>(::GetTickCount()) / 1000;
	}
}

DateTime GetCurDateTime(bool utcFlag)
{
	SYSTEMTIME st;
	DateTime dateTime;
	if (utcFlag) {
		::GetSystemTime(&st);
		dateTime = ToDateTime(st);
		dateTime.SetTZOffset(0);
	} else {
		::GetLocalTime(&st);
		dateTime = ToDateTime(st);
		TIME_ZONE_INFORMATION tzi;
		::GetTimeZoneInformation(&tzi);
		dateTime.SetTZOffset(-tzi.Bias * 60);
	}
	return dateTime;
}

DateTime ToDateTime(const SYSTEMTIME &st)
{
	return DateTime(
		static_cast<short>(st.wYear),
		static_cast<char>(st.wMonth),
		static_cast<char>(st.wDay),
		static_cast<long>(st.wHour) * 3600 +
					static_cast<long>(st.wMinute) * 60 + st.wSecond,
		static_cast<long>(st.wMilliseconds) * 1000);
}

DateTime ToDateTime(const FILETIME &ft, bool utcFlag)
{
	SYSTEMTIME stUTC;
	::FileTimeToSystemTime(&ft, &stUTC);
	if (!utcFlag) {
		SYSTEMTIME stLocal;
		::SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);
		return ToDateTime(stLocal);
	}
	return ToDateTime(stUTC);
}

String GetBaseDir()
{
	char pathName[512];
	::GetModuleFileName(NULL, pathName, NUMBEROF(pathName)); // Win32 API
	char *p = pathName + ::strlen(pathName);
	for ( ; p >= pathName; p--) {
		if (*p == '\\') {
			*p = '\0';
			break;
		}
	}
	return String(pathName);
}

String GetDataDir()
{
	return GetBaseDir();
}

String GetLibDir()
{
	String dirName(GetBaseDir());
	dirName += FileSeparator;
#if defined(__BORLANDC__)
	dirName += "module.bcc";
#else
	dirName += "module";
#endif
	return dirName;
}

String GetExecutable()
{
	char pathName[512];
	::GetModuleFileName(NULL, pathName, NUMBEROF(pathName)); // Win32 API
	return String(pathName);
}

void SetupModulePath(StringList &strList)
{
	strList.push_back(".");
	strList.push_back(GetBaseDir());
#if defined(__BORLANDC__)
	do {
		String dirName(GetBaseDir());
		dirName += FileSeparator;
		dirName += "module.bcc";
		strList.push_back(dirName);
	} while (0);
	do {
		String dirName(GetBaseDir());
		dirName += FileSeparator;
		dirName += "module.bcc\\site";
		strList.push_back(dirName);
	} while (0);
#endif
	do {
		String dirName(GetBaseDir());
		dirName += FileSeparator;
		dirName += "module";
		strList.push_back(dirName);
	} while (0);
	do {
		String dirName(GetBaseDir());
		dirName += FileSeparator;
		dirName += "module\\site";
		strList.push_back(dirName);
	} while (0);
	String str = GetEnv("ASCRIPTPATH");
	if (!str.empty()) {
		SplitPathList(str.c_str(), strList);
	}
}

void SetupExecutablePath()
{
	String dirBase = GetEnv("ASCRIPTDIR");
	if (dirBase.empty()) {
		char pathName[512];
		::GetModuleFileName(NULL, pathName, NUMBEROF(pathName)); // Win32 API
		char *p = pathName + ::strlen(pathName);
		for ( ; p >= pathName; p--) {
			if (*p == '\\') {
				*p = '\0';
				break;
			}
		}
		dirBase = pathName;
	}
	String path;
	path += dirBase;
	path += "\\extra\\lib.win32";
	path += ";";
	path += dirBase;
	path += "\\extra\\tcl\\bin";
	path += ";";
	path += GetEnv("PATH");
	PutEnv("PATH", path.c_str());
}

//-----------------------------------------------------------------------------
// DynamicLibrary
//-----------------------------------------------------------------------------
DynamicLibrary::DynamicLibrary() : _hModule(NULL)
{
}

bool DynamicLibrary::Open(Signal sig, const char *pathName)
{
	_hModule = ::LoadLibrary(pathName);
	if (_hModule == NULL) {
		sig.SetError(ERR_ImportError, "can't open module file '%s'", pathName);
		return false;
	}
	return true;
}

void *DynamicLibrary::GetEntry(Signal sig, const char *name)
{
	if (_hModule == NULL) {
		sig.SetError(ERR_ImportError, "library has not been opened");
		return NULL;
	}
	FARPROC pFunc = ::GetProcAddress(_hModule, name);
	if (pFunc == NULL) {
		String nameEx = "_";
		nameEx += name;
		pFunc = ::GetProcAddress(_hModule, nameEx.c_str());
	}
	if (pFunc == NULL) {
		sig.SetError(ERR_ImportError, "can't find entry function '%s'", name);
		return NULL;
	}
	return pFunc;
}

//-----------------------------------------------------------------------------
// Memory
//-----------------------------------------------------------------------------
Memory::Memory(size_t bytes) : _bytes(bytes), _buff(NULL)
{
	if (bytes > 0) Allocate(bytes);
}

Memory::~Memory()
{
	::LocalFree(_buff);
}

void *Memory::Allocate(size_t bytes)
{
	if (bytes < _bytes) return _buff;
	::LocalFree(_buff);
	_bytes = bytes;
	_buff = reinterpret_cast<char *>(::LocalAlloc(LMEM_FIXED, _bytes));
	return _buff;
}

void *Memory::Resize(size_t bytes, size_t bytesToCopy)
{
	_bytes = bytes;
	char *buffNew = reinterpret_cast<char *>(::LocalAlloc(LMEM_FIXED, _bytes));
	::memcpy(buffNew, _buff, bytesToCopy);
	::LocalFree(_buff);
	_buff = buffNew;
	return _buff;
}

void Memory::Free()
{
	::LocalFree(_buff);
	_bytes = 0;
	_buff = NULL;
}

void *Memory::GetPointer(size_t offset) const
{
	return _buff + offset;
}

//-----------------------------------------------------------------------------
// Thread
//-----------------------------------------------------------------------------
static DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	reinterpret_cast<Thread *>(lpParameter)->Run();
	return 0;
}

void Thread::Start()
{
	DWORD threadId;
	::CreateThread(NULL, 0, ThreadProc, this, 0, &threadId);
}

//-----------------------------------------------------------------------------
// Semaphore
//-----------------------------------------------------------------------------
Semaphore::Semaphore()
{
	_hMutex = ::CreateMutex(NULL, FALSE, NULL);
}

Semaphore::~Semaphore()
{
	::DeleteObject(_hMutex);
}

void Semaphore::Wait()
{
	::WaitForSingleObject(_hMutex, INFINITE);
}

void Semaphore::Release()
{
	::ReleaseMutex(_hMutex);
}

//-----------------------------------------------------------------------------
// Event
//-----------------------------------------------------------------------------
Event::Event()
{
	_hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}

Event::~Event()
{
	::DeleteObject(_hEvent);
}

void Event::Wait()
{
	::WaitForSingleObject(_hEvent, INFINITE);
}

void Event::Notify()
{
	::SetEvent(_hEvent);
}

#else
//=============================================================================
// POSIX
//=============================================================================
const char FileSeparator = '/';

String GetEnv(const char *name)
{
	const char *str = ::getenv(name);
	if (str == NULL) return String("");
	return String(str);
}

void PutEnv(const char *name, const char *value)
{
	::setenv(name, value, 1);
}

void Rename(const char *src, const char *dst)
{
	::rename(src, dst);
}

void Remove(const char *pathName)
{
	::unlink(pathName);
}

bool MakeDir(const char *pathName, bool recursiveFlag)
{
	return ::mkdir(pathName, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
}

bool ChangeCurDir(const char *pathName)
{
	return ::chdir(pathName) == 0;
}

String GetCurDir()
{
	char *pathName = ::get_current_dir_name();
	String rtn(pathName);
	::free(pathName);
	return rtn;
}

void Sleep(Number delay)	// unit: sec
{
	struct timeval tv;
	tv.tv_sec = 0;
	tv.tv_usec = static_cast<int>(delay * 1000000);
	::select(0, NULL, NULL, NULL, &tv);
}

Number GetTickTime()
{
	timeval tv;
	struct timezone tz;
	::gettimeofday(&tv, &tz);
	Number num = tv.tv_sec;
	num += static_cast<Number>(tv.tv_usec) / 1000000;
	return num;
}

DateTime GetCurDateTime(bool utcFlag)
{
	time_t t;
	::time(&t);
	struct tm tm;
	DateTime dateTime;
	if (utcFlag) {
		gmtime_r(&t, &tm);
		dateTime = ToDateTime(tm);
		dateTime.SetTZOffset(0);
	} else {
		localtime_r(&t, &tm);
		dateTime = ToDateTime(tm);
	}
	return dateTime;
}

DateTime ToDateTime(const struct tm &tm)
{
	return DateTime(
		static_cast<short>(tm.tm_year) + 1900,
		static_cast<char>(tm.tm_mon) + 1,
		static_cast<char>(tm.tm_mday),
		static_cast<long>(tm.tm_hour) * 3600 +
					static_cast<long>(tm.tm_min) * 60 + tm.tm_sec, 0);
}

DateTime ToDateTime(time_t t, bool utcFlag)
{
	struct tm tm;
	if (utcFlag) {
		gmtime_r(&t, &tm);
	} else {
		localtime_r(&t, &tm);
	}
	return ToDateTime(tm);
}

String GetBaseDir()
{
	return String(PKGDATADIR);
}

String GetDataDir()
{
	return GetBaseDir();
}

String GetLibDir()
{
	return String(PKGLIBDIR);
}

String GetExecutable()
{
	return String(BINDIR "/ascript");
}

void SetupModulePath(StringList &strList)
{
	strList.push_back(".");
	strList.push_back(PKGLIBDIR);
	strList.push_back(PKGLIBDIR "/site");
	String str = GetEnv("ASCRIPTPATH");
	if (!str.empty()) {
		SplitPathList(str.c_str(), strList);
	}
}

void SetupExecutablePath()
{
	// nothing to do
}

//-----------------------------------------------------------------------------
// DynamicLibrary
//-----------------------------------------------------------------------------
DynamicLibrary::DynamicLibrary() : _hLibrary(NULL)
{
}

bool DynamicLibrary::Open(Signal sig, const char *pathName)
{
	_hLibrary = dlopen(pathName, RTLD_LAZY);
	if (_hLibrary == NULL) {
		sig.SetError(ERR_ImportError, "can't open module file '%s'", pathName);
		return false;
	}
	return true;
}

void *DynamicLibrary::GetEntry(Signal sig, const char *name)
{
	void *pFunc = dlsym(_hLibrary, name);
	if (pFunc == NULL) {
		sig.SetError(ERR_ImportError, "can't find entry function '%s'", name);
		return NULL;
	}
	return pFunc;
}

//-----------------------------------------------------------------------------
// Memory
//-----------------------------------------------------------------------------
Memory::Memory(size_t bytes) : _bytes(bytes), _buff(NULL)
{
	if (bytes > 0) Allocate(bytes);
}

Memory::~Memory()
{
	::free(_buff);
}

void *Memory::Allocate(size_t bytes)
{
	::free(_buff);
	_bytes = bytes;
	_buff = reinterpret_cast<char *>(::malloc(_bytes));
	return _buff;
}

void *Memory::Resize(size_t bytes, size_t bytesToCopy)
{
	_bytes = bytes;
	char *buffNew = reinterpret_cast<char *>(::malloc(_bytes));
	::memcpy(buffNew, _buff, bytesToCopy);
	::free(_buff);
	_buff = buffNew;
	return _buff;
}

void Memory::Free()
{
	::free(_buff);
	_bytes = 0;
	_buff = NULL;
}

void *Memory::GetPointer(size_t offset) const
{
	return _buff + offset;
}

//-----------------------------------------------------------------------------
// Thread
//-----------------------------------------------------------------------------
void Thread::Start()
{
}

//-----------------------------------------------------------------------------
// Semaphore
//-----------------------------------------------------------------------------
#if defined(HAVE_SEMAPHORE_H)
// should use mutex instead?
Semaphore::Semaphore()
{
	::sem_init(&_sem, 0, 1);
}

Semaphore::~Semaphore()
{
	::sem_destroy(&_sem);
}

void Semaphore::Wait()
{
	::sem_wait(&_sem);
}

void Semaphore::Release()
{
	::sem_post(&_sem);
}
#else
#endif

//-----------------------------------------------------------------------------
// Event
//-----------------------------------------------------------------------------
#if defined(HAVE_SEMAPHORE_H)
Event::Event()
{
}

Event::~Event()
{
}

void Event::Wait()
{
}

void Event::Notify()
{
}
#else
#endif

#endif

}
}
