#include "Log.h"
#include <stdarg.h>

#if defined(_LINUX)
#include <syslog.h>
#elif defined(_WINDOWS)
#include <windows.h>
#include "Strings.h"
#include "Messages.h"
#endif

class Log::PrivateData {
public:
	PrivateData() : logOutput(Log::System), debug(false) {}

	Log::LogOutput logOutput;
	bool debug;

	static Log *instance;
#ifdef _WINDOWS
	HANDLE eventSource;
#endif
};

Log *Log::PrivateData::instance = 0;

Log::Log()
	:d(new PrivateData)
{
#if defined(_LINUX)
	setlogmask(LOG_UPTO(LOG_INFO));
	openlog("telldusd", LOG_CONS, LOG_USER);
#elif defined(_WINDOWS)
	//Add ourselves to the registy
	HKEY hRegKey = NULL;
	DWORD dwError = 0;
	TCHAR filePath[MAX_PATH];

	std::wstring path(L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\TelldusService");
	dwError = RegCreateKey( HKEY_LOCAL_MACHINE, path.c_str(), &hRegKey );

	GetModuleFileName( NULL, filePath, MAX_PATH );
	dwError = RegSetValueEx( hRegKey, L"EventMessageFile", 0,
                           REG_EXPAND_SZ, (PBYTE) filePath,
                           (DWORD)(wcslen(filePath) + 1) * sizeof TCHAR );

	DWORD dwTypes = LOG_DEBUG | LOG_NOTICE | LOG_WARNING | LOG_ERR;
    dwError = RegSetValueEx( hRegKey, L"TypesSupported",
              0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes );

	RegCloseKey(hRegKey);

	d->eventSource = RegisterEventSource(NULL, L"TelldusService");
#endif
}

Log::~Log() {
#if defined(_LINUX)
	closelog();
#elif defined(_WINDOWS)
	if (d->eventSource != NULL) {
		DeregisterEventSource(d->eventSource);
	}
#endif
	delete d;
}

void Log::destroy() {
	if (PrivateData::instance == 0) {
		return;
	}
	delete PrivateData::instance;
	PrivateData::instance = 0;
}

void Log::debug(const char *fmt, ...) {
	Log *log = Log::instance();
	va_list ap;
	va_start(ap, fmt);
	log->message(Debug, fmt, ap);
	va_end(ap);
}

void Log::notice(const char *fmt, ...) {
	Log *log = Log::instance();
	va_list ap;
	va_start(ap, fmt);
	log->message(Notice, fmt, ap);
	va_end(ap);
}

void Log::warning(const char *fmt, ...) {
	Log *log = Log::instance();
	va_list ap;
	va_start(ap, fmt);
	log->message(Warning, fmt, ap);
	va_end(ap);
}

void Log::error(const char *fmt, ...) {
	Log *log = Log::instance();
	va_list ap;
	va_start(ap, fmt);
	log->message(Error, fmt, ap);
	va_end(ap);
}

void Log::setDebug() {
	Log *log = Log::instance();
	log->d->debug = true;
}

void Log::setLogOutput(LogOutput logOutput) {
	Log *log = Log::instance();
	log->d->logOutput = logOutput;
}

void Log::message(Log::LogLevel logLevel, const char *format, va_list ap) const {
	if (logLevel == Debug && d->debug == false) {
		return;
	}
	if (d->logOutput == StdOut) {
		FILE *stream = stdout;
		if (logLevel == Warning || logLevel == Error) {
			stream = stderr;
		}
		vfprintf(stream, format, ap);
		fprintf(stream, "\n");
		fflush(stream);
	} else {
#if defined(_LINUX)
		switch (logLevel) {
			case Debug:
				vsyslog(LOG_DEBUG, format, ap);
				break;
			case Notice:
				vsyslog(LOG_NOTICE, format, ap);
				break;
			case Warning:
				vsyslog(LOG_WARNING, format, ap);
				break;
			case Error:
				vsyslog(LOG_ERR, format, ap);
				break;
		}
#elif defined(_WINDOWS)
		LPWSTR pInsertStrings[2] = {NULL, NULL};
		std::wstring str = TelldusCore::charToWstring(format);
		pInsertStrings[0] = (LPWSTR)str.c_str();

		switch (logLevel) {
			case Debug:
				ReportEvent(d->eventSource, EVENTLOG_SUCCESS, NULL, LOG_DEBUG, NULL, 1, 0, (LPCWSTR*)pInsertStrings, NULL);
				break;
			case Notice:
				ReportEvent(d->eventSource, EVENTLOG_INFORMATION_TYPE, NULL, LOG_NOTICE, NULL, 1, 0, (LPCWSTR*)pInsertStrings, NULL);
				break;
			case Warning:
				ReportEvent(d->eventSource, EVENTLOG_WARNING_TYPE, NULL, LOG_WARNING, NULL, 1, 0, (LPCWSTR*)pInsertStrings, NULL);
				break;
			case Error:
				ReportEvent(d->eventSource, EVENTLOG_ERROR_TYPE, NULL, LOG_ERR, NULL, 1, 0, (LPCWSTR*)pInsertStrings, NULL);
				break;
		}
#endif
	}
}

Log *Log::instance() {
	if (PrivateData::instance == 0) {
		PrivateData::instance = new Log();
	}
	return PrivateData::instance;
}
