#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>

#ifdef		NT_CONSOLE
#include	<../source/PAL/oshead.h>
#endif		/* NT_CONSOLE */

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

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

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

void V_LOCAL V_CALL
con_file_puts_and_flush(char *str) 
{ 
	FILE *out=libvLockGlobal()->log_file;
	fprintf(out,"%s",str);
	fflush(out);
	libvUnlockGlobal();
	return;
}

void V_LOCAL V_CALL
con_con_puts(char *str)
{
#ifdef	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	/* !NT_CONSOLE */
	printf("%s",str);
#endif	/* !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);
	}
	vpsAdjust(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	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	/* !NT_CONSOLE */
	return logAddCallback(con_con_puts);
#endif	/* !NT_CONSOLE */
}

void V_GLOBAL
logStopConsoleLog(void)
{
#ifdef	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	/* NT_CONSOLE */
	logRemoveCallback(con_con_puts);
}

vboolean V_GLOBAL
logStartFileLog(char *name,vboolean flush_mode)
{
	global_data_t *gd;
	gd=libvLockGlobal();
	for(;;) /* My guess, goto's looks better ... */
	{
		if(!name || gd->log_file)
			break;
#ifndef	RIGHT_SLASHED
		{
		char right_name[_MAX_PATH];
		vfsUnix2Native(right_name,name);
		gd->log_file=fopen(right_name,"at");
		}
#else	/* RIGHT_SLASHED */
		gd->log_file=fopen(name,"at");
#endif	/* !RIGHT_SLASHED */
		if(gd->log_file)
		{
			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));
		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)
	{
		fclose(gd->log_file);
		gd->log_file=NULL;
	}
	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 log_fmt_buffer[MAX_FORMAT_BUFFER];
	int len;
	if(!fmt)
		return;
	ptr=log_fmt_buffer;
	va_start(marker,fmt);
	len=vsprintf((char*)ptr,fmt,marker);
	va_end(marker);
	/* 'Post-mortem' check: idea stolen from Quake */
	if(len>=MAX_FORMAT_BUFFER)
	{
#ifdef	CHECKED_BUILD
		libvError(
			"conPrintf(): format buffer overflowed; stack trashed.\n"
			"Increase MAX_FORMAT_BUFFER and recompile.");
#endif	/* CHECKED_BUILD */
		return;
	}
	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,dbs(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
#ifdef	NT_CONSOLE
	wait_for_input=true;
#endif	/* NT_CONSOLE */
	if(prompt)
		printf(prompt);
	fgets(dest,dest_len,stdin);
#ifdef	NT_CONSOLE
	wait_for_input=false;
#endif	/* 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,dbs(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
	for(;;)
	{
#ifdef	NT_CONSOLE
		wait_for_input=true;
#endif	/* NT_CONSOLE */
		if(prompt)
			printf(prompt);
		fgets(buf,128,stdin);
#ifdef	NT_CONSOLE
		wait_for_input=false;
#endif	/* 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(mvec_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,dbs(prompt));
#endif	/* CHECKED_BUILD */
		return false;
	}
	for(;;)
	{
#ifdef	NT_CONSOLE
		wait_for_input=true;
#endif	/* NT_CONSOLE */
		if(prompt)
			printf(prompt);
		fgets(buf,128,stdin);
#ifdef	NT_CONSOLE
		wait_for_input=false;
#endif	/* NT_CONSOLE */
		*dest=(mvec_t)strtod(buf,&endptr);
		if(!*endptr || *endptr==0x0D || *endptr==0x0A)
			break;
		puts("Please, enter correct floating-point number !");
	}
	return endptr > buf;
}
