/*
sys_con.c - stdout and log
Copyright (C) 2007 Uncle Mike

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

#include "common.h"
#if XASH_WIN32
#define STDOUT_FILENO 1
#include <io.h>
#elif XASH_ANDROID
#include <android/log.h>
#endif
#include <string.h>
#include <errno.h>
#if XASH_IRIX
#include <sys/time.h>
#endif

// do not waste precious CPU cycles on mobiles or low memory devices
#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY
#define XASH_COLORIZE_CONSOLE true
// use with caution, running engine in Qt Creator may cause a freeze in read() call
// I have never encountered this bug anywhere else, so still enable by default
#define XASH_USE_SELECT 1
#else
#define XASH_COLORIZE_CONSOLE false
#endif

#if XASH_USE_SELECT
// non-blocking console input
#include <sys/select.h>
#endif

typedef struct {
	char		title[64];
	qboolean		log_active;
	char		log_path[MAX_SYSPATH];
	FILE		*logfile;
	int 		logfileno;
} LogData;

static LogData s_ld;

char *Sys_Input( void )
{
#if XASH_USE_SELECT
	if( Host_IsDedicated( ))
	{
		fd_set rfds;
		static char line[1024];
		static int len;
		struct timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 0;
		FD_ZERO( &rfds );
		FD_SET( 0, &rfds); // stdin
		while( select( 1, &rfds, NULL, NULL, &tv ) > 0 )
		{
			if( read( 0, &line[len], 1 ) != 1 )
				break;
			if( line[len] == '\n' || len > 1022 )
			{
				line[ ++len ] = 0;
				len = 0;
				return line;
			}
			len++;
			tv.tv_sec = 0;
			tv.tv_usec = 0;
		}
	}
#endif
#if XASH_WIN32
	return Wcon_Input();
#endif
	return NULL;
}

void Sys_DestroyConsole( void )
{
	// last text message into console or log
	Con_Reportf( "Sys_DestroyConsole: Exiting!\n" );
#if XASH_WIN32
	Wcon_DestroyConsole();
#endif
}

/*
===============================================================================

SYSTEM LOG

===============================================================================
*/
int Sys_LogFileNo( void )
{
	return s_ld.logfileno;
}

static void Sys_FlushStdout( void )
{
	// never printing anything to stdout on mobiles
#if !XASH_MOBILE_PLATFORM
	fflush( stdout );
#endif
}

static void Sys_FlushLogfile( void )
{
	if( s_ld.logfile )
		fflush( s_ld.logfile );
}

void Sys_InitLog( void )
{
	const char	*mode;

	if( Sys_CheckParm( "-log" ) && host.allow_console != 0 )
	{
		s_ld.log_active = true;
		Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path ));
	}

	if( host.change_game && host.type != HOST_DEDICATED )
		mode = "a";
	else mode = "w";

	if( Host_IsDedicated( ))
		Q_strncpy( s_ld.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_ld.title ));
	else Q_strncpy( s_ld.title, XASH_ENGINE_NAME " " XASH_VERSION, sizeof( s_ld.title ));

	// create log if needed
	if( s_ld.log_active )
	{
		s_ld.logfile = fopen( s_ld.log_path, mode );

		if ( !s_ld.logfile )
		{
			Con_Reportf( S_ERROR  "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ));
			return;
		}

		s_ld.logfileno = fileno( s_ld.logfile );

		// fit to 80 columns for easier read on standard terminal
		fputs( "================================================================================\n", s_ld.logfile );
		fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
		fprintf( s_ld.logfile, "Game started at %s\n", Q_timestamp( TIME_FULL ));
		fputs( "================================================================================\n", s_ld.logfile );
		fflush( s_ld.logfile );
	}
}

void Sys_CloseLog( void )
{
	char	event_name[64];

	// continue logged
	switch( host.status )
	{
	case HOST_CRASHED:
		Q_strncpy( event_name, "crashed", sizeof( event_name ));
		break;
	case HOST_ERR_FATAL:
		Q_strncpy( event_name, "stopped with error", sizeof( event_name ));
		break;
	default:
		if( !host.change_game ) Q_strncpy( event_name, "stopped", sizeof( event_name ));
		else Q_strncpy( event_name, host.finalmsg, sizeof( event_name ));
		break;
	}

	Sys_FlushStdout(); // flush to stdout to ensure all data was written

	if( s_ld.logfile )
	{
		fputc( '\n', s_ld.logfile );
		fputs( "================================================================================\n", s_ld.logfile );
		fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
		fprintf( s_ld.logfile, "Stopped with reason \"%s\" at %s\n", event_name, Q_timestamp( TIME_FULL ));
		fputs( "================================================================================\n", s_ld.logfile );
		fclose( s_ld.logfile );
		s_ld.logfile = NULL;
	}
}

#if XASH_COLORIZE_CONSOLE == true
static void Sys_WriteEscapeSequenceForColorcode( int fd, int c )
{
	static const char *q3ToAnsi[ 8 ] =
	{
		"\033[30m", // COLOR_BLACK
		"\033[31m", // COLOR_RED
		"\033[32m", // COLOR_GREEN
		"\033[33m", // COLOR_YELLOW
		"\033[34m", // COLOR_BLUE
		"\033[36m", // COLOR_CYAN
		"\033[35m", // COLOR_MAGENTA
		"\033[0m", // COLOR_WHITE
	};
	const char *esc = q3ToAnsi[c];

	if( c == 7 )
		write( fd, esc, 4 );
	else write( fd, esc, 5 );
}
#else
static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {}
#endif

static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize )
{
	const char *p = msg;

	write( fd, logtime, Q_strlen( logtime ) );

	while( p && *p )
	{
		p = Q_strchr( msg, '^' );

		if( p == NULL )
		{
			write( fd, msg, Q_strlen( msg ));
			break;
		}
		else if( IsColorString( p ))
		{
			if( p != msg )
				write( fd, msg, p - msg );
			msg = p + 2;

			if( colorize )
				Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] ));
		}
		else
		{
			write( fd, msg, p - msg + 1 );
			msg = p + 1;
		}
	}

	// flush the color
	if( colorize )
		Sys_WriteEscapeSequenceForColorcode( fd, 7 );
}

static void Sys_PrintStdout( const char *logtime, const char *msg )
{
#if XASH_MOBILE_PLATFORM
	static char buf[MAX_PRINT_MSG];

	// strip color codes
	COM_StripColors( msg, buf );

	// platform-specific output
#if XASH_ANDROID && !XASH_DEDICATED
	__android_log_write( ANDROID_LOG_DEBUG, "Xash", buf );
#endif // XASH_ANDROID && !XASH_DEDICATED

#if TARGET_OS_IOS
	void IOS_Log( const char * );
	IOS_Log( buf );
#endif // TARGET_OS_IOS

#if XASH_NSWITCH && NSWITCH_DEBUG
	// just spew it to stderr normally in debug mode
	fprintf( stderr, "%s %s", logtime, buf );
#endif // XASH_NSWITCH && NSWITCH_DEBUG

#if XASH_PSVITA
	// spew to stderr only in developer mode
	if( host_developer.value )
	{
		fprintf( stderr, "%s %s", logtime, buf );
	}
#endif

#elif !XASH_WIN32 // Wcon does the job
	Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE );
	Sys_FlushStdout();
#endif
}

void Sys_PrintLog( const char *pMsg )
{
	time_t crt_time;
	const struct tm	*crt_tm;
	char logtime[32] = "";
	static char lastchar;

	time( &crt_time );
	crt_tm = localtime( &crt_time );

	if( !lastchar || lastchar == '\n')
		strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time

	// spew to stdout
	Sys_PrintStdout( logtime, pMsg );

	if( !s_ld.logfile )
	{
		// save last char to detect when line was not ended
		lastchar = pMsg[Q_strlen( pMsg ) - 1];
		return;
	}

	if( !lastchar || lastchar == '\n')
		strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time
	
	// save last char to detect when line was not ended
	lastchar = pMsg[Q_strlen( pMsg ) - 1];

	Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false );
	Sys_FlushLogfile();
}

/*
=============================================================================

CONSOLE PRINT

=============================================================================
*/
/*
=============
Con_Printf

=============
*/
void GAME_EXPORT Con_Printf( const char *szFmt, ... )
{
	static char	buffer[MAX_PRINT_MSG];
	va_list		args;
	qboolean add_newline;

	if( !host.allow_console )
		return;

	va_start( args, szFmt );
	add_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0;
	va_end( args );

	Sys_Print( buffer );
	if( add_newline )
		Sys_Print( "\n" );
}

/*
=============
Con_DPrintf

=============
*/
void GAME_EXPORT Con_DPrintf( const char *szFmt, ... )
{
	static char	buffer[MAX_PRINT_MSG];
	va_list		args;
	qboolean add_newline;

	if( host_developer.value < DEV_NORMAL )
		return;

	va_start( args, szFmt );
	add_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0;
	va_end( args );

	if( buffer[0] == '0' && buffer[1] == '\n' && buffer[2] == '\0' )
		return; // hlrally spam

	Sys_Print( buffer );
	if( add_newline )
		Sys_Print( "\n" );
}

/*
=============
Con_Reportf

=============
*/
void Con_Reportf( const char *szFmt, ... )
{
	static char	buffer[MAX_PRINT_MSG];
	va_list		args;
	qboolean add_newline;

	if( host_developer.value < DEV_EXTENDED )
		return;

	va_start( args, szFmt );
	add_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0;
	va_end( args );

	Sys_Print( buffer );
	if( add_newline )
		Sys_Print( "\n" );
}


#if XASH_MESSAGEBOX == MSGBOX_STDERR
void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )
{
	fprintf( stderr,
		 "======================================\n"
		 "%s: %s\n"
		 "======================================\n", title, message );
}
#endif

