/*
Copyright (C) 1997-2001 Id Software, Inc.

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 2
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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <errno.h>
#include <dlfcn.h>
#include <dirent.h>

#ifdef RTCTIMER_LINUX
#include <linux/rtc.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#endif

#if defined(__FreeBSD__)
#include <machine/param.h>
#endif

#include "../qcommon/qcommon.h"
#include "glob.h"

cvar_t *nostdout;

unsigned	sys_frame_time;

uid_t saved_euid;
qboolean stdin_active = qtrue;

#ifdef RTCTIMER_LINUX
/* needed for RTC timer */
static int rtc_rate = -1;
static int rtc_fd = 0;  /* file descriptor for rtc device */
#endif

// =======================================================================
// General routines
// =======================================================================

void Sys_ConsoleOutput (char *string)
{
	if (nostdout && nostdout->integer)
		return;

	fputs (string, stdout);
}

/*
=================
Sys_Quit
=================
*/
void Sys_Quit (void)
{
	fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY);

	Qcommon_Shutdown ();
#ifdef RTCTIMER_LINUX
	if (rtc_fd) {
		close(rtc_fd);
		rtc_fd = 0;
	}
#endif

	_exit(0);
}

/*
=================
Sys_Init
=================
*/
void Sys_Init(void)
{
}

/*
=================
Sys_Error
=================
*/
void Sys_Error (char *error, ...)
{ 
	va_list     argptr;
	char        string[1024];

// change stdin to non blocking
	fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY);

	CL_Shutdown ();
	
	va_start (argptr, error);
	vsnprintf (string, 1024, error, argptr);
	va_end (argptr);
	fprintf (stderr, "Error: %s\n", string);

	Qcommon_Shutdown ();
#ifdef RTCTIMER_LINUX
	if (rtc_fd) {
		close(rtc_fd);
		rtc_fd = 0;
	}
#endif

	_exit (1);
} 

/*
================
Sys_Milliseconds
================
*/
unsigned int curtime;

#ifdef RTCTIMER_LINUX
/* RTC timer code is adapted from EzQuake */
static void Milliseconds_RTC (void)
{
	/* rtc timer vars */
	unsigned long curticks = 0;
	struct pollfd pfd;
	static unsigned long totalticks;

	pfd.fd = rtc_fd;
	pfd.events = POLLIN | POLLERR;

	while (poll(&pfd, 1, 100000) < 0) {
		if ((errno = EINTR))
			continue; /* happens with gdb or signal exiting */
		else
			Sys_Error("Poll call on RTC timer failed!\n");
	}

	read(rtc_fd, &curticks, sizeof(curticks));
	curticks = curticks >> 8; /* knock out info byte */
	totalticks += curticks;
	
	// TODO handle the wrap
	curtime = totalticks / (rtc_rate/1000.0);
}
#endif

#ifdef RTCTIMER_LINUX
static void Milliseconds_GTOD (void)
{
#else
unsigned int Sys_Milliseconds (void)
{
#endif
	struct timeval tp;
	struct timezone tzp;
	static unsigned int		secbase;

	gettimeofday(&tp, &tzp);
	
	if (!secbase)
	{
		secbase = tp.tv_sec;
		curtime = tp.tv_usec/1000;
		return curtime;
	}

	// TODO handle the wrap
	curtime = (tp.tv_sec - secbase)*1000 + tp.tv_usec/1000;

#ifndef RTCTIMER_LINUX
	return curtime;
#endif
}

#ifdef RTCTIMER_LINUX
unsigned int Sys_Milliseconds (void)
{
	if (rtc_fd)
		Milliseconds_RTC();
	else
		Milliseconds_GTOD();

	return curtime;
}
#endif

void Sys_Mkdir (const char *path)
{
    mkdir (path, 0777);
}

void floating_point_exception_handler (int whatever)
{
	signal (SIGFPE, floating_point_exception_handler);
}

char *Sys_ConsoleInput(void)
{
	static char text[256];
	int     len;
	fd_set	fdset;
	struct timeval timeout;

	if (!dedicated || !dedicated->integer)
		return NULL;

	if (!stdin_active)
		return NULL;

	FD_ZERO (&fdset);
	FD_SET (0, &fdset); // stdin
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	if (select (1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset))
		return NULL;

	len = read (0, text, sizeof(text));
	if (len == 0) { // eof!
		stdin_active = qfalse;
		return NULL;
	}

	if (len < 1)
		return NULL;

	text[len-1] = 0;    // rip off the /n and terminate

	return text;
}


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

DLL

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

/*
=================
Sys_UnloadLibrary
=================
*/
void Sys_UnloadLibrary( void **lib )
{
	if( lib && *lib ) {
		if( dlclose( *lib ) )
			Com_Error( ERR_FATAL, "dlclose failed" );
		*lib = NULL;
	}
}

/*
=================
Sys_LoadLibrary
=================
*/
void *Sys_LoadLibrary( char *name, dllfunc_t *funcs )
{
	void *lib;
	dllfunc_t *func;

	if( !name || !name[0] || !funcs )
		return NULL;

	Com_DPrintf( "LoadLibrary (%s)\n", name );

	lib = dlopen( name, RTLD_NOW );
	if( !lib )
	{
		// wsw : pb : error handling
		Com_Printf( "LoadLibrary (%s):(%s)\n", name, dlerror() );
		return NULL;
	}
	for( func = funcs; func->name; func++ ) {
		*(func->funcPointer) = ( void * )dlsym( lib, func->name );

		if( !(*(func->funcPointer)) ) {
			Sys_UnloadLibrary( &lib );
			Com_Error( ERR_FATAL, "%s: dlsym failed for %s", name, func->name );
		}
	}

	return lib;
}

/*****************************************************************************/

static void *game_library = NULL;
static void *cgame_library = NULL;
static void *ui_library = NULL;

#ifdef __cplusplus
# define EXTERN_API_FUNC	extern "C"
#else
# define EXTERN_API_FUNC	extern
#endif

/*
=================
Sys_UnloadGameLibrary
=================
*/
void Sys_UnloadGameLibrary (gamelib_t gamelib)
{
	// Prevent compiler warning
	void **lib = NULL;

	switch( gamelib ) {
		case LIB_GAME:
			lib = &game_library;
#ifdef GAME_HARD_LINKED
			*lib = NULL;
#endif
			break;
		case LIB_CGAME:
			lib = &cgame_library;
#ifdef CGAME_HARD_LINKED
			*lib = NULL;
#endif
			break;
		case LIB_UI:
			lib = &ui_library;
#ifdef UI_HARD_LINKED
			*lib = NULL;
#endif
			break;
		default: assert( 0 );
	}

	if( *lib ) {
		if( dlclose (*lib) )
			Com_Error (ERR_FATAL, "dlclose failed");
		*lib = NULL;
	}
}

/*
=================
Sys_LoadGameLibrary
=================
*/
void *Sys_LoadGameLibrary (gamelib_t gamelib, void *parms)
{
	char name[MAX_OSPATH];
	char cwd[MAX_OSPATH];
	char *path;
	void *(*APIfunc) (void *);
	void **lib = NULL;
	char *libname = NULL;
	char *apifuncname = NULL;

#if defined __FreeBSD__
#if defined __i386__
#define ARCH "freebsd_i386"

#ifdef NDEBUG
	const char *debugdir = "freebsd_releasei386";
#else
	const char *debugdir = "freebsd_debugi386";
#endif

#elif defined __x86_64__
#define ARCH "xfreebsd_86_64"
#ifdef NDEBUG
	const char *debugdir = "releasex86_64";
#else
	const char *debugdir = "debugx86_64";
#endif
#endif /*__x86_64__ */

#elif defined __i386__
#define ARCH "i386"

#ifdef NDEBUG
	const char *debugdir = "releasei386";
#else
	const char *debugdir = "debugi386";
#endif

#elif defined __x86_64__
#define ARCH "x86_64"
#ifdef NDEBUG
	const char *debugdir = "releasex86_64";
#else
	const char *debugdir = "debugx86_64";
#endif

#elif defined __alpha__
#define ARCH "axp"
#ifdef NDEBUG
	const char *debugdir = "releaseaxp";
#else
	const char *debugdir = "debugaxp";
#endif

#elif defined __powerpc__
#define ARCH "ppc"
#ifdef NDEBUG
	const char *debugdir = "releaseppc";
#else
	const char *debugdir = "debugppc";
#endif
#elif defined __sparc__
#define ARCH "sparc"
#ifdef NDEBUG
	const char *debugdir = "releasepsparc";
#else
	const char *debugdir = "debugpsparc";
#endif
#else
#define ARCH	"UNKNOW"
#ifdef NDEBUG
	const char *debugdir = "release";
#else
	const char *debugdir = "debug";
#endif
#endif

	APIfunc = NULL;
	switch( gamelib ) {
		case LIB_GAME:
		{
#ifdef GAME_HARD_LINKED
			EXTERN_API_FUNC void *GetGameAPI( void * );
			APIfunc = GetGameAPI;
#endif
			lib = &game_library;
			libname = "game_" ARCH ".so";
			apifuncname = "GetGameAPI";
			break;
		}
		case LIB_CGAME:
		{
#ifdef CGAME_HARD_LINKED
			EXTERN_API_FUNC void *GetCGameAPI( void * );
			APIfunc = GetCGameAPI;
#endif
			lib = &cgame_library;
			libname = "cgame_" ARCH ".so";
			apifuncname = "GetCGameAPI";
			break;
		}
		case LIB_UI:
		{
#ifdef UI_HARD_LINKED
			EXTERN_API_FUNC void *GetUIAPI( void * );
			APIfunc = GetUIAPI;
#endif
			lib = &ui_library;
			libname = "ui_" ARCH ".so";
			apifuncname = "GetUIAPI";
			break;
		}
		default: assert( 0 );
	}

	if (*lib)
		Com_Error (ERR_FATAL, "Sys_LoadGameLibrary without Sys_UnloadGameLibrary");

	if (APIfunc)
	{
		*lib = ( void * )1;
		return APIfunc (parms);
	}

	// check the current debug directory first for development purposes
	getcwd (cwd, sizeof(cwd));
	Q_snprintfz (name, sizeof(name), "%s/%s/%s", cwd, debugdir, libname);

	*lib = dlopen (name, RTLD_NOW);

	if (!*lib)
	{
		// now run through the search paths
		path = NULL;

		while (1)
		{
			path = FS_NextPath (path);

			if (!path) 
				return NULL; // couldn't find one anywhere

			Q_snprintfz (name, sizeof(name), "%s/%s", path, libname);
			*lib = dlopen (name, RTLD_NOW);

			if (*lib)
			{
				break;
			}
			// wsw : pb : error handling
			Com_Printf ("LoadLibrary (%s):(%s)\n", name, dlerror() );
		}
		
	}

	APIfunc = (void *)dlsym (*lib, apifuncname);

	if (!APIfunc)
	{
		Sys_UnloadGameLibrary (gamelib);
		return NULL;
	}

	return APIfunc(parms);
}

//===============================================================================

static	char	findbase[MAX_OSPATH];
static	char	findpath[MAX_OSPATH];
static	char	findpattern[MAX_OSPATH];
static	DIR	*fdir;

static qboolean CompareAttributes(char *path, char *name,
	unsigned musthave, unsigned canthave )
{
	struct stat st;
	char fn[MAX_OSPATH];

// . and .. never match
	if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
		return qfalse;

	Q_snprintfz( fn, sizeof(fn), "%s/%s", path, name );

	if (stat(fn, &st) == -1) {
		Com_Printf( "Warning, stat failed: %s\n", fn );
		return qfalse; // shouldn't happen
	}

	if ( ( st.st_mode & S_IFDIR ) && ( canthave & SFF_SUBDIR ) )
		return qfalse;

	if ( ( musthave & SFF_SUBDIR ) && !( st.st_mode & S_IFDIR ) )
		return qfalse;

	return qtrue;
}

char *Sys_FindFirst (char *path, unsigned musthave, unsigned canhave)
{
	struct dirent *d;
	char *p;

	if (fdir)
		Sys_Error ("Sys_BeginFind without close");

//	COM_FilePath (path, findbase);
	Q_strncpyz(findbase, path, sizeof(findbase));

	if ((p = strrchr(findbase, '/')) != NULL) {
		*p = 0;
		Q_strncpyz(findpattern, p + 1, sizeof(findpattern));
	} else
		Q_strncpyz(findpattern, "*", sizeof(findpattern));

	if (strcmp(findpattern, "*.*") == 0)
		Q_strncpyz(findpattern, "*", sizeof(findpattern));
	
	if ((fdir = opendir(findbase)) == NULL)
		return NULL;
	while ((d = readdir(fdir)) != NULL) {
		if (!*findpattern || glob_match(findpattern, d->d_name)) {
//			if (*findpattern)
//				printf("%s matched %s\n", findpattern, d->d_name);
			if (CompareAttributes(findbase, d->d_name, musthave, canhave)) {
				sprintf (findpath, "%s/%s", findbase, d->d_name);
				return findpath;
			}
		}
	}
	return NULL;
}

char *Sys_FindNext (unsigned musthave, unsigned canhave)
{
	struct dirent *d;

	if (fdir == NULL)
		return NULL;
	while ((d = readdir(fdir)) != NULL) {
		if (!*findpattern || glob_match(findpattern, d->d_name)) {
//			if (*findpattern)
//				printf("%s matched %s\n", findpattern, d->d_name);
			if (CompareAttributes(findbase, d->d_name, musthave, canhave)) {
				sprintf (findpath, "%s/%s", findbase, d->d_name);
				return findpath;
			}
		}
	}
	return NULL;
}

void Sys_FindClose (void)
{
	if (fdir != NULL)
		closedir(fdir);
	fdir = NULL;
}

/*
=================
Sys_GetHomeDirectory
=================
*/
char *Sys_GetHomeDirectory (void)
{
	return getenv( "HOME" );
}

//===============================================================================

/*
=================
Sys_GetClipboardData
=================
*/

char *Sys_GetClipboardData(void)
{
#if 0
	Window sowner;
	Atom type, property;
	unsigned long len, bytes_left, tmp;
	unsigned char *data;
	int format, result;
	char *ret = NULL;
		
	if (!x11display.dpy && x11display.win) 
		return NULL;
	
	sowner = XGetSelectionOwner (x11display.dpy, XA_PRIMARY);
			
	if (sowner != None)
	{
		property = XInternAtom(x11display.dpy, "GETCLIPBOARDDATA_PROP", False);
				
		XConvertSelection (x11display.dpy, XA_PRIMARY, XA_STRING, property, x11display.win, myxtime); /* myxtime == time of last X event */
		XFlush (x11display.dpy);

		XGetWindowProperty (x11display.dpy, x11display.win, property,  0, 0, False, AnyPropertyType, &type, &format, &len, &bytes_left, &data);

		if (bytes_left > 0) 
		{
			result = XGetWindowProperty(x11display.dpy, x11display.win, property,
				0, bytes_left, True, AnyPropertyType,  &type, &format, &len, &tmp, &data);

			if (result == Success){
				ret = strdup((char *) data);
			}

			XFree(data);
		}
	}

	return ret;
#endif
	return NULL;
}

/*
=================
Sys_AppActivate
=================
*/
void Sys_AppActivate (void)
{
}

/*
=================
Sys_SendKeyEvents
=================
*/
void Sys_SendKeyEvents (void)
{
	// grab frame time 
	sys_frame_time = Sys_Milliseconds();
}

#ifdef RTCTIMER_LINUX
void rtcinit() {
	int retval;
	unsigned long tmpread;

	/* try accessing rtc */
	rtc_fd = open("/dev/rtc", O_RDONLY);
	if (rtc_fd < 0) {
		Com_Printf("Could not open /dev/rtc\n");
		rtc_fd = 0;
		return;
	}

	/* make sure RTC is set to high enough rate */
	retval = ioctl(rtc_fd, RTC_IRQP_READ, &tmpread);
	if (retval < 0) {
		Com_Printf("Could not check RTC rate\n");
		close(rtc_fd);
		rtc_fd = 0;
		return;
	}
	if (tmpread < 1000) {
		Com_Printf("RTC rate is not set to high enough value\n");
		close(rtc_fd);
		rtc_fd = 0;
		return;
	}
	rtc_rate = tmpread;

	/* take ownership of rtc */
	retval = fcntl(rtc_fd, F_SETOWN, getpid());
	if (retval < 0) {
		Com_Printf("Couldn't set ownership of /dev/rtc\n");
		close(rtc_fd);
		rtc_fd = 0;
		return;
	}

	/* everything is nice - now turn on the RTC's periodic timer */
	retval = ioctl(rtc_fd, RTC_PIE_ON, 0);
	if (retval == -1) {
		Com_Printf("Error activating RTC timer\n");
		close(rtc_fd);
		rtc_fd = 0;
		return;
	}

	Com_Printf("RTC timer enabled, running at %iHz\n", rtc_rate);
}
#endif


/*****************************************************************************/

int main (int argc, char **argv)
{
	unsigned int oldtime, newtime, time;

#ifdef RTCTIMER_LINUX
	rtcinit();
#endif
	Qcommon_Init(argc, argv);

	fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY);

	nostdout = Cvar_Get ("nostdout", "0", 0);
	if (!nostdout->integer) {
		fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY);	
	}

	oldtime = Sys_Milliseconds ();
	while (1)
	{
		// find time spent rendering last frame
		do 
		{
			newtime = Sys_Milliseconds ();
			time = newtime - oldtime; // no warp test needed as unsigned
		} while (time < 1);

		Qcommon_Frame (time);
		oldtime = newtime;
	}
}


