#include <../arch/arch.h> /* under Win/DOS we need conversion routine */
#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>

#define	VOID_CAST(ptr)			(*((void**)&(ptr)))

#ifdef	CONFIG_NT_CONSOLE
/*	Needless to move to global section: there can be only one console */
static	vboolean	wait_for_input = false;
#endif	/* CONFIG_NT_CONSOLE */

void V_LOCAL V_CALL
con_file_puts(char *str) 
{
	vhandle_t out = libvLockGlobal()->log_file;
	filePrintf(out,"%s",str);
	libvUnlockGlobal();
	return;
}

void V_LOCAL V_CALL
con_file_puts_and_flush(char *str) 
{ 
	vhandle_t out = libvLockGlobal()->log_file;
	filePrintf(out,"%s",str);
	libvUnlockGlobal();
	fileControl(out,pctl_flush,NULL);
	return;
}

void V_LOCAL V_CALL
con_con_puts(char *str)
{
#ifdef	CONFIG_NT_CONSOLE
	DWORD wrcount;
	HANDLE out=(HANDLE)libvLockGlobal()->outhandle;
	if(wait_for_input)
	{
		/* TODO: implement edit_buffer_t ... */
		WriteFile(out,str,strlen(str),&wrcount,NULL);
	}
	else
		WriteFile(out,str,strlen(str),&wrcount,NULL);
	libvUnlockGlobal();
#else	/* !CONFIG_NT_CONSOLE */
	printf("%s",str);
#endif	/* !CONFIG_NT_CONSOLE */
}

vboolean V_GLOBAL
logAddCallback(con_puts_t outcallback)
{
	vboolean ret=false;
	vp_stack_t *callbacks;
	if(!outcallback)
		return false;
	callbacks=&libvLockGlobal()->log_callbacks;
	if(vpsFind(callbacks,VOID_CAST(outcallback),NULL))
	{
		/* warning: callback already exists */
	}
	else
	{
		vpsStore(callbacks,VOID_CAST(outcallback));
		ret=true;
	}
	libvUnlockGlobal();
	return ret;
}
  
void V_GLOBAL
logRemoveCallback(con_puts_t outcallback)
{
	u32_t pos;
	vp_stack_t *callbacks;
	if(!outcallback)
		return;
	callbacks=&libvLockGlobal()->log_callbacks;
	/* The interfaces looks pretty nice ... :) */
	if(vpsFind(callbacks,VOID_CAST(outcallback),&pos))
	{
		void *lastcb;
		vpsPop(callbacks,&lastcb);
		if(pos < vpsCount(callbacks))
			vpsSet(callbacks,pos,lastcb);
	}
	vpsTrim(callbacks);
	libvUnlockGlobal();
	return;
}

u32_t	V_GLOBAL
logUpperLevel(u32_t new_level)
{
	u32_t ol;
	u32_t *l;
	l=&(libvLockGlobal()->log_upper_level);
	ol=*l;
	*l=new_level;
	libvUnlockGlobal();
	return ol;
}

vboolean V_GLOBAL
logStartConsoleLog(void)
{
#ifdef	CONFIG_NT_CONSOLE
	global_data_t *gd;
	char *nt_console_out="CONOUT$";
	if(!logAddCallback(con_con_puts))
		return false;
	gd=libvLockGlobal();
	gd->outhandle=CreateFile(nt_console_out,GENERIC_WRITE,FILE_SHARE_WRITE,
		NULL,OPEN_EXISTING,0,NULL);
	if(gd->outhandle==INVALID_HANDLE_VALUE)
	{
		AllocConsole();
		gd->outhandle=CreateFile(nt_console_out,GENERIC_WRITE,
			FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
		gd->own_console=true;
	}
	else
		gd->own_console=false;
	gd->inhandle=CreateFile("CONIN$",GENERIC_WRITE,FILE_SHARE_WRITE,
		NULL,OPEN_EXISTING,0,NULL);
	libvUnlockGlobal();
	return true;
#else	/* !CONFIG_NT_CONSOLE */
	return logAddCallback(con_con_puts);
#endif	/* !CONFIG_NT_CONSOLE */
}

void V_GLOBAL
logStopConsoleLog(void)
{
#ifdef	CONFIG_NT_CONSOLE
	global_data_t *gd;
	gd=libvLockGlobal();
	CloseHandle(gd->inhandle);
	CloseHandle(gd->outhandle);
	gd->inhandle=NULL;
	gd->outhandle=NULL;
	if(gd->own_console)
	{
		FreeConsole();
		gd->own_console=false;
	}
	libvUnlockGlobal();
#endif	/* CONFIG_NT_CONSOLE */
	logRemoveCallback(con_con_puts);
}

vboolean V_GLOBAL
logStartFileLog(char *name,vboolean flush_mode)
{
	global_data_t *gd;
	gd=libvLockGlobal();
	for(;;)
	{
		if(!name || gd->log_file != LIBV_INVALID_HANDLE)
			break;
		gd->log_file = fileOpenWrite(name);
		if(gd->log_file != LIBV_INVALID_HANDLE)
		{
			if(flush_mode)
				logAddCallback(con_file_puts_and_flush);
			else
				logAddCallback(con_file_puts);
			libvUnlockGlobal();
			return true;
		}
		/* Unable to open log file */
#ifdef	CHECKED_BUILD
		{
			char buf[64+_MAX_PATH]="logStartFileLog('";
			strCat(buf,name,64+_MAX_PATH-strlen(buf));
			strCat(buf,"'): unable to open file.\n",64+_MAX_PATH-strlen(buf));
			puts(buf);
			libvError(buf);
		}
#endif
		break;
	}
	libvUnlockGlobal();
	return false;
}

void V_GLOBAL
logStopFileLog(void)
{
	global_data_t *gd;
	logRemoveCallback(con_file_puts_and_flush);
	logRemoveCallback(con_file_puts);
	gd = libvLockGlobal();
	if(gd->log_file != LIBV_INVALID_HANDLE)
	{
		fileClose(gd->log_file);
		gd->log_file = LIBV_INVALID_HANDLE;
	}
	libvUnlockGlobal();
	return;
}

void V_GLOBAL
conPuts(char *str)
{
	u32_t pos, len;
	global_data_t *gd;
	con_puts_t *callbacks;
	if(!str)
		return;
	gd=libvLockGlobal();
	len=vpsCount(&gd->log_callbacks);
	callbacks=(con_puts_t*)vpsGetStorage(&gd->log_callbacks);
	for(pos=0;pos<len;pos++)
		if(callbacks[pos])
			callbacks[pos](str);
	libvUnlockGlobal();
	return;
}

char_p	V_GLOBAL_VA
logFormatString(char *dest,const char *fmt, ...)
{
	va_list marker;
	char *ptr;
	if(!fmt)
		return NULL;
	ptr=dest;
	va_start(marker,fmt);
	vsprintf(ptr,fmt,marker);
	va_end(marker);
	return ptr;
}

void V_GLOBAL_VA
conPrintf(const char *fmt, ...)
{
	va_list marker;
	char *ptr;
	char *buf, log_fmt_buffer[MAX_FORMAT_BUFFER];
	int len, maxlen;
	if(!fmt)
		return;
	len = strlen(fmt);
	/*
	 * TODO: make FMT_ARGS() macro
	 */
	/*
	 * FIXME: go though 'fmt', parse fields; for every '%c' - add 1 byte,
	 * for every '%[u|d|x|f|g]' - add 32 bytes, for every '%s' - read
	 * 'strptr' and add strlen(strptr) bytes.
	 */
	if(len > 128)
	{
		maxlen = len * 128;
		buf = (char*)memAlloc(maxlen);
	}
	else
	{
		maxlen = MAX_FORMAT_BUFFER;
		buf = log_fmt_buffer;
	}
	ptr = buf;
	va_start(marker, fmt);
	len = vsprintf((char*)ptr,fmt,marker);
	va_end(marker);
	/* 'Post-mortem' check: idea stolen from Quake */
	if(len >= maxlen)
	{
#ifdef	CHECKED_BUILD
		libvError(
			"conPrintf(): format buffer overflowed; stack trashed.\n"
			"At least, increase MAX_FORMAT_BUFFER and recompile;\n"
			"better, look and fix the console.c::conPrintf()."
			"The application is now terminates, sorry.");
#endif	/* CHECKED_BUILD */
		return;
	}
	if(buf != log_fmt_buffer)
		memFree(buf);
	conPuts(ptr);
}

void	V_GLOBAL
conlPuts(u32_t level,char *str)
{
	global_data_t *gd;
	if(!str)
		return;
	gd=libvLockGlobal();
	if(level<=gd->log_upper_level)
		conPuts(str);
	libvUnlockGlobal();
	return;
}

void	V_GLOBAL_VA
conlPrintf(u32_t level,const char *fmt, ...)
{
	va_list marker;
	char *ptr;
	global_data_t *gd;
	u32_t cur_lev;
	int len;
	char log_fmt_buffer[MAX_FORMAT_BUFFER];
	gd=libvLockGlobal();
	cur_lev=gd->log_upper_level;
	libvUnlockGlobal();
	if(level>cur_lev || !fmt)
		return;
	ptr=log_fmt_buffer;
	va_start(marker,fmt);
	len=vsprintf(ptr,fmt,marker);
	va_end(marker);
	/* 'Post-mortem' check: idea stolen from Quake */
	if(len>=MAX_FORMAT_BUFFER)
	{
#ifdef	CHECKED_BUILD
		libvError(
			"conlPrintf(): format buffer overflowed; stack trashed.\n"
			"Increase MAX_FORMAT_BUFFER and recompile.");
#endif	/* CHECKED_BUILD */
		return;
	}
	conPuts(ptr);
}

/*
**	Reads string from console.
*/
vboolean	V_GLOBAL
conGets(char *dest,u32_t dest_len,char *prompt)
{
	if(!dest || !dest_len)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"conGets(%X,%d,%s): incorrect parameter(s).\n",
			dest,dest_len,strSafe(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
#ifdef	CONFIG_NT_CONSOLE
	wait_for_input=true;
#endif	/* CONFIG_NT_CONSOLE */
	if(prompt)
		printf(prompt);
	fgets(dest,dest_len,stdin);
#ifdef	CONFIG_NT_CONSOLE
	wait_for_input=false;
#endif	/* CONFIG_NT_CONSOLE */
	return *dest && *dest!=0x0D && *dest==0x0A;
}

/*
 *	TODO: implement nice line editor.
 */

/*
**	Reads integer from console.
*/
vboolean	V_GLOBAL
conGetInt(int *dest,char *prompt)
{
	char buf[128];
	char *endptr;
	if(!dest)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"conGetInt(%X,%s): incorrect 'dest' parameter.\n",
			dest,strSafe(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
	for(;;)
	{
#ifdef	CONFIG_NT_CONSOLE
		wait_for_input=true;
#endif	/* CONFIG_NT_CONSOLE */
		if(prompt)
			printf(prompt);
		fgets(buf,128,stdin);
#ifdef	CONFIG_NT_CONSOLE
		wait_for_input=false;
#endif	/* CONFIG_NT_CONSOLE */
		*dest=strtol(buf,&endptr,0);
		if(!*endptr || *endptr==0x0D || *endptr==0x0A)
			break;
		if(buf[0]!='0')
			puts("Please, enter correct integer number !");
		else
		{
			if(buf[1]!='x' && buf[1]!='X')
				puts("Incorrect octal number.");
			else
				puts("Incorrect hexadecimal number.");
		}
	}
	return endptr > buf;
}

/*
**	Reads floating-point number from console.
*/
vboolean	V_GLOBAL
conGetFloat(vec_t *dest,char *prompt)
{
	char buf[128];
	char *endptr;
	if(!dest)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"conGetFloat(%X,%s): incorrect 'dest' parameter.\n",
			dest,strSafe(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
	for(;;)
	{
#ifdef	CONFIG_NT_CONSOLE
		wait_for_input=true;
#endif	/* CONFIG_NT_CONSOLE */
		if(prompt)
			printf(prompt);
		fgets(buf,128,stdin);
#ifdef	CONFIG_NT_CONSOLE
		wait_for_input=false;
#endif	/* CONFIG_NT_CONSOLE */
		*dest=(vec_t)strtod(buf,&endptr);
		if(!*endptr || *endptr==0x0D || *endptr==0x0A)
			break;
		puts("Please, enter correct floating-point number !");
	}
	return endptr > buf;
}
