#include "Core.h"

namespace Upp {

StaticCriticalSection log_mutex;

#ifdef PLATFORM_WINCE
const char *FromSysChrSet(const wchar *s)
{
	static char out[256];
	FromUnicode(out, s, wstrlen(s), CHARSET_DEFAULT);
	return out;
}

const wchar *ToSysChrSet(const char *s)
{
	static wchar out[1024];
	ToUnicode(out, s, strlen(s), CHARSET_DEFAULT);
	return out;
}
#endif

struct LogOut {
	dword options;
	int   sizelimit;

#ifdef PLATFORM_WIN32
	HANDLE hfile;
#endif
#ifdef PLATFORM_POSIX
	enum { INVALID_HANDLE_VALUE = -1 };
	int   hfile;
#endif

	char  filepath[512];
	int   filesize;

	int   part;
	
	bool  line_begin;
	
	void  Create(bool append);
	void  Create()                                     { Create(options & LOG_APPEND); }
	void  Close();
	void  Line(const char *buffer, int len, int depth);
	bool  IsOpen() const;
	void  Rotate();
};

bool LogOut::IsOpen() const
{
#ifdef PLATFORM_POSIX
	return hfile >= 0;
#else
	return hfile != INVALID_HANDLE_VALUE;
#endif
}

void LogOut::Rotate()
{
}

void LogOut::Create(bool append)
{
	Close();
	
	line_begin = true;
	
	int rotn = options >> 24;
	if(rotn) {
		char next[512];
		for(int rot = rotn; rot >= 0; rot--) {
			char current[512];
			if(rot == 0)
				strcpy(current, filepath);
			else
				sprintf(current, rot > 1 && (options & LOG_ROTATE_GZIP) ? "%s.%d.gz" : "%s.%d",
				        filepath, rot);
			if(FileExists(current)) {
				if(rot == rotn)
					FileDelete(current);
				else
				if((options & LOG_ROTATE_GZIP) && rot == 1 && !IsPanicMode()) {
					GZCompressFile(next, current); // Should be OK to use heap in Create...
				}
				else
					FileMove(current, next);
			}
			strcpy(next, current);
		}
	}
	
	filesize = 0;

#ifdef PLATFORM_WIN32
	hfile = CreateFile(ToSysChrSet(filepath),
		GENERIC_READ|GENERIC_WRITE,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		NULL,
		append ? OPEN_ALWAYS : CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);
	if(append)
		filesize = (int)SetFilePointer(hfile, 0, NULL, FILE_END);
#else
	hfile = open(filepath, append ? O_CREAT|O_RDWR|O_APPEND : O_CREAT|O_RDWR|O_TRUNC, 0644);
	if(append)
		filesize = (int)lseek(hfile, 0, SEEK_END);
#endif
	Time t = GetSysTime();
#ifdef PLATFORM_WINCE
	wchar exe[512];
#else
	char exe[512];
#endif
	char user[500];
	*user = 0;

#ifdef PLATFORM_WIN32
	GetModuleFileName(AppGetHandle(), exe, 512);
#ifndef PLATFORM_WINCE
	dword w = 2048;
	::GetUserNameA(user, &w);
#endif
#else //#
	const char *procexepath_(void);
	strcpy(exe, procexepath_());
	const char *uenv = getenv("USER");
	strcpy(user, uenv ? uenv : "boot");
#endif

	char h[1000];
	sprintf(h, "* %s %02d.%02d.%04d %02d:%02d:%02d, user: %s\n",
	           FromSysChrSet(exe),
	           t.day, t.month, t.year, t.hour, t.minute, t.second, user);
#ifdef PLATFORM_WIN32
	dword n;
	WriteFile(hfile, h, (dword)strlen(h), &n, NULL);
	if(part) {
		sprintf(h, ", #%d", part);
		WriteFile(hfile, h, (dword)strlen(h) , &n, NULL);
	}
	WriteFile(hfile, "\r\n", 2, &n, NULL);
#else
	IGNORE_RESULT(
		write(hfile, h, strlen(h))
	);
	if(part) {
		sprintf(h, ", #%d", part);
		IGNORE_RESULT(
			write(hfile, h, strlen(h))
		);
	}
	IGNORE_RESULT(
		write(hfile, "\r\n", 2)
	);
#endif
}

void LogOut::Close()
{
#ifdef PLATFORM_POSIX
	if(hfile >= 0)
		close(hfile);
	hfile = -1;
#else
	if(hfile != INVALID_HANDLE_VALUE)
		CloseHandle(hfile);
	hfile = INVALID_HANDLE_VALUE;
#endif
}

void LogOut::Line(const char *s, int len, int depth)
{
	Mutex::Lock __(log_mutex);
	
	ASSERT(len < 600);

	char h[600];
	char *p = h;
	int   ll = 0;
	if((options & (LOG_TIMESTAMP|LOG_TIMESTAMP_UTC)) && line_begin) {
		Time t = (options & LOG_TIMESTAMP_UTC) ? GetUtcTime() : GetSysTime();
		ll = sprintf(h, "%02d.%02d.%04d %02d:%02d:%02d ",
		                t.day, t.month, t.year, t.hour, t.minute, t.second);
		if(ll < 0)
			return;
		p += ll;
	}
	char *beg = p;
	for(int q = min(depth, 99); q--;)
		*p++ = '\t';
	line_begin = len && s[len - 1] == '\n';
	memcpy(p, s, len);
	p += len;
	*p = '\0';
	int count = (int)(p - h);
	if(count == 0) return;
	if(options & LOG_COUT)
		Cout().Put(h, count);
	if(options & LOG_CERR)
		Cerr().Put(h, count);
#ifdef PLATFORM_WIN32
	if(options & LOG_FILE)
		if(hfile != INVALID_HANDLE_VALUE) {
			dword n;
			WriteFile(hfile, h, count, &n, NULL);
		}
	if(options & LOG_DBG) {
		*p = 0;
		::OutputDebugString((LPCSTR)h);
	}
#else
	if(options & LOG_FILE)
		if(hfile >= 0)
			IGNORE_RESULT(
				write(hfile, h, count)
			);
	if(options & LOG_DBG)
		Cerr().Put(h, count);
	if(options & LOG_SYS)
		syslog(LOG_INFO|LOG_USER, "%s", beg);
#endif
	filesize += count;
	if(sizelimit > 0 && filesize > sizelimit)
		Create(false);
}

#ifdef PLATFORM_POSIX
static LogOut sLog = { LOG_FILE, 10 * 1024 * 1024, -1 };
#else
static LogOut sLog = { LOG_FILE, 10 * 1024 * 1024 };
#endif

struct ThreadLog {
	char  buffer[512];
	int   len;
	int   line_depth;
	int   depth;
	
	void  Put(int w);
};

static thread__ ThreadLog sTh;

void ThreadLog::Put(int w)
{
	if(w == LOG_BEGIN)
		depth = min(depth + 1, 20);
	else
	if(w == LOG_END)
		depth = max(depth - 1, 0);
	else {
		buffer[len++] = w;
		if(w == '\n' || len > 500) {
			sLog.Line(buffer, len, line_depth);
			len = 0;
		}
		if(w != '\r')
			line_depth = depth;
	}
}

class LogStream : public Stream {
protected:
	virtual void    _Put(int w);
	virtual void    _Put(const void *data, dword size);
	virtual int64   GetSize() const;

public:
	virtual   bool  IsOpen() const;
};

int64 LogStream::GetSize() const
{
	return sLog.filesize;
}

bool LogStream::IsOpen() const
{
	return sLog.IsOpen();
}

void LogStream::_Put(int w)
{
	sTh.Put(w);
}

void  LogStream::_Put(const void *data, dword size)
{
	const byte *q = (byte *)data;
	while(size--)
		sTh.Put(*q++);
}

String AsString(const MemoryProfile& mem)
{
	String text;
#ifdef UPP_HEAP
	int acount = 0;
	size_t asize = 0;
	int fcount = 0;
	size_t fsize = 0;
	for(int i = 0; i < 1024; i++)
		if(mem.allocated[i]) {
			int sz = 4 * i;
			text << Format("%4d B, %6d allocated (%5d KB), %6d fragmented (%5d KB)\n",
			              sz, mem.allocated[i], (mem.allocated[i] * sz) >> 10,
			              mem.fragmented[i], (mem.fragmented[i] * sz) >> 10);
			acount += mem.allocated[i];
			asize += mem.allocated[i] * sz;
			fcount += mem.fragmented[i];
			fsize += mem.fragmented[i] * sz;
		}
	text << Format(" TOTAL, %6d allocated (%5d KB), %6d fragmented (%5d KB)\n",
	              acount, int(asize >> 10), fcount, int(fsize >> 10));
	text << "Free 4KB pages " << mem.freepages << " (" << mem.freepages * 4 << " KB)\n";
	text << "Large block count " << mem.large_count
	     << ", total size " << (mem.large_total >> 10) << " KB\n";
	text << "Large fragments count " << mem.large_free_count
	     << ", total size " << (mem.large_free_total >> 10) << " KB\n";
	text << "Large free 64KB pages " << mem.large_empty
	     << ", total size " << 64 * mem.large_empty << " KB\n";
	text << "Big block count " << mem.big_count
	     << ", total size " << int(mem.big_size >> 10) << " KB\n";
#endif
	return text;
}

int64 GetAllocatedBytes(const MemoryProfile& mem)
{
	int64 asize = 0;
#ifdef UPP_HEAP
	for(int i = 0; i < 1024; i++)
		if(mem.allocated[i]) {
			int sz = 4 * i;
			asize += mem.allocated[i] * sz;
		}
	asize += mem.large_total;
	asize += mem.big_size;
#endif
	return asize;
}

#ifdef flagCHECKINIT

void InitBlockBegin__(const char *fn, int line) {
	RLOG(fn << " " << line << " init block");
#ifdef HEAPDBG
	MemoryCheckDebug();
#else
	MemoryCheck();
#endif
}

void InitBlockEnd__(const char *fn, int line) {
	RLOG(fn << " " << line << " init block finished");
#ifdef HEAPDBG
	MemoryCheckDebug();
#else
	MemoryCheck();
#endif
}

#endif

#ifdef PLATFORM_WIN32
static void sLogFile(char *fn, const char *app = ".log")
{
#ifdef PLATFORM_WINCE
	wchar wfn[256];
	::GetModuleFileName(NULL, wfn, 512);
	strcpy(fn, FromSysChrSet(wfn));
#else
	::GetModuleFileName(NULL, fn, 512);
#endif
	char *e = fn + strlen(fn), *s = e;
	while(s > fn && *--s != '\\' && *s != '.')
		;
	strcpy(*s == '.' ? s : e, app);
}
#endif

#ifdef PLATFORM_POSIX
const char *procexepath_();
extern char Argv0__[_MAX_PATH + 1];

static void sLogFile(char *fn, const char *app = ".log")
{
	char *path = fn;
	const char *ehome = getenv("HOME");
	strcpy(fn, ehome ? ehome : "/root");
	if(!*fn || (fn += strlen(fn))[-1] != '/')
		*fn++ = '/';
	*fn = '\0';
	strcat(path, ".upp/");
	const char *exe = procexepath_();
	if(!*exe)
		exe = Argv0__;
	const char *q = strrchr(exe, '/');
	if(q)
		exe = q + 1;
	if(!*exe)
		exe = "upp";
	strcat(path, exe);
	mkdir(path, 0755);
	strcat(path, "/");
	strcat(path, exe);
	strcat(path, app);
}
#endif

static Stream *__logstream;

void SetVppLogSizeLimit(int limit) { sLog.sizelimit = limit; }
void SetVppLogNoDeleteOnStartup()  { sLog.options |= LOG_APPEND; }

LogStream& StdLogStream()
{
	static LogStream *s;
	ONCELOCK {
		static byte lb[sizeof(LogStream)];
		LogStream *strm = new(lb) LogStream;
		if(*sLog.filepath == '\0')
			sLogFile(sLog.filepath);
		sLog.Create();
		s = strm;
	}
	return *s;
}

void CloseStdLog()
{
	StdLogStream().Close();
}

void ReopenLog()
{
	if(sLog.IsOpen()) {
		sLog.Close();
		sLog.Create();
	}
}

void StdLogSetup(dword options, const char *filepath, int filesize_limit)
{
	sLog.options = options;
	sLog.sizelimit = filesize_limit;
	if(filepath)
		strcpy(sLog.filepath, filepath);
	ReopenLog();
}

String GetStdLogPath()
{
	return sLog.filepath;
}

Stream& StdLog()
{
	return StdLogStream();
}

void SetVppLog(Stream& log) {
	__logstream = &log;
}

void SetUppLog(Stream& log) {
	__logstream = &log;
}

Stream& UppLog() {
	if(!__logstream) __logstream = &StdLog();
	return *__logstream;
}

Stream& VppLog() {
	return UppLog();
}

void SetVppLogName(const String& file) {
	strcpy(sLog.filepath, file);
	ReopenLog();
}

namespace Ini {
	INI_BOOL(user_log, false, "Activates logging of user actions");
};

}
