/*
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 <dirent.h>
#include <dlfcn.h>
#include <termios.h>
#ifndef __linux__
#include <machine/param.h>
#endif

#include "qcommon.h"
#include "q_list.h"
#include "net.h"
#include "q_field.h"
#include "prompt.h"
#include "version.h"

#ifndef DEDICATED_ONLY
#include <SDL.h>
#include <SDL_syswm.h>
#endif

cvar_t *sys_nostdout;
cvar_t *sys_nostdin;
cvar_t *sys_debugdir;
cvar_t *sys_ttycon;

sysAPI_t sys;

unsigned	sys_frame_time;

uid_t saved_euid;

static char	curpath[MAX_OSPATH];

static qboolean         tty_enabled;
static struct termios   tty_orig;
static int              tty_erase;
static commandPrompt_t	sys_con;
static int				sys_hidden;

void Sys_ListModules_f( void );
void Sys_Printf( const char *fmt, ... );

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

TERMINAL SUPPORT

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

static void Sys_HideInput( void ) {
	int i;

    if( !tty_enabled ) {
        return;
    }

	if( !sys_hidden ) {
		for( i = 0; i <= sys_con.inputLine.cursorPos; i++ ) {
			write( 1, "\b \b", 3 );	
		}
	}
	sys_hidden++;
}

static void Sys_ShowInput( void ) {
	int i;
    
    if( !tty_enabled ) {
        return;
    }

	if( !sys_hidden ) {
		Com_EPrintf( "Sys_ShowInput: not hidden\n" );
		return;
	}

	sys_hidden--;
	if( !sys_hidden ) {
        i = ']';
		write( 1, &i, 1 );	
		for( i = 0; i < sys_con.inputLine.cursorPos; i++ ) {
			write( 1, &sys_con.inputLine.text[i], 1 );	
		}
	}
}

static void Sys_InitTTY( void ) {
    struct termios tty;

    /* change stdin to non-blocking */
    fcntl( 0, F_SETFL, fcntl( 0, F_GETFL, 0 ) | FNDELAY );

    sys_ttycon = Cvar_Get( "sys_ttycon", "2", CVAR_NOSET );
    if( sys_ttycon->integer < 1 ) {
        return;
    }

    if( isatty( 0 ) != 1 ) {
        Com_Printf( "stdin in not a tty, tty input disabled.\n" );
        Cvar_SetInteger( "sys_ttycon", 0 );
        return;
    }
    
    Com_Printf( "Initializing tty console...\n" );
    tcgetattr( 0, &tty_orig );
    tty = tty_orig;
    tty.c_lflag &= ~( ECHO | ICANON | INPCK | ISTRIP );
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 0;
    tty_erase = tty.c_cc[VERASE];
    tcsetattr( 0, TCSADRAIN, &tty );
    sys_con.widthInChars = 80;
    sys_con.Printf = Sys_Printf;
    
    tty_enabled = qtrue;
}

static void Sys_ShutdownTTY( void ) {
    /* revert to blocking stdin */
    fcntl( 0, F_SETFL, fcntl( 0, F_GETFL, 0 ) & ~FNDELAY );

    if( !tty_enabled ) {
        return;
    }
    Com_Printf( "Shutting down tty console...\n" );
    Sys_HideInput();
    tcsetattr( 0, TCSADRAIN, &tty_orig );
}

/*
=================
Sys_ConsoleOutput
=================
*/
void Sys_ConsoleOutput( const char *string ) {
	static char color_to_ansi[8] = { '0', '1', '2', '3', '4', '6', '5', '7' };
	char	text[MAXPRINTMSG];
	char    *maxp, *p;
    int     length;
    qboolean    hasColor;

	if( sys_nostdout && sys_nostdout->integer )
		return;

	if( !sys_ttycon || sys_ttycon->integer < 2 ) {
		p = text;
		maxp = text + sizeof( text );
		while( *string ) {
			if( Q_IsColorString( string ) ) {
				string += 2;
				continue;
			}
			*p++ = *string++ & 127;
			if( p == maxp ) {
				break;
			}
		}

		length = p - text;
		write( 1, text, length );
		return;
	}

    Sys_HideInput();
    
	p = text;
	maxp = text + sizeof( text );
    hasColor = qfalse;
	while( *string ) {
		if( Q_IsColorString( string ) ) {
            if( p + 5 > maxp ) {
                break;
            }
            p[0] = '\033';
            p[1] = '[';
			string++;
			if( *string == COLOR_RESET ) {
                p[2] = '0';
                p[3] = 'm';
                p += 4;
            } else if( *string == COLOR_ALT ) {
                p[2] = '3';
                p[3] = '2';
                p[4] = 'm';
                p += 5;
			} else {
                p[2] = '3';
                p[3] = color_to_ansi[ ColorIndex( *string ) ];
                p[4] = 'm';
                p += 5;
			}
            hasColor = qtrue;
		} else {
            *p++ = *string & 127;
			if( p == maxp ) {
				break;
			}
		}
		string++;
	}

    if( hasColor ) {
        if( p + 4 > maxp ) {
            p = maxp - 4;
        }
        p[0] = '\033';
        p[1] = '[';
        p[2] = '0';
        p[3] = 'm';
        p += 4;
    }
    
	length = p - text;
	write( 1, text, length );
	
    Sys_ShowInput();
}

/*
=================
Sys_ConsoleInput
=================
*/
char *Sys_ConsoleInput( void ) {
    static char text[MAX_STRING_CHARS];
    int     len;
	fd_set	fdset;
    struct timeval timeout;
	inputField_t *f;
	char *s;
    byte key;

    if( sys_nostdin && sys_nostdin->integer ) {
        return NULL;
    }

	if( !tty_enabled ) {    
        FD_ZERO( &fdset );
        FD_SET( 0, &fdset ); // stdin
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;
        if( select( 1, &fdset, NULL, NULL, &timeout ) == -1 ) {
            return NULL;
        }
            
        if( !FD_ISSET( 0, &fdset ) ) {
            return NULL;
        }
        
        len = read( 0, text, sizeof( text ) );
        if( len < 1 ) {
            Com_Error( ERR_FATAL, "Sys_ConsoleInput: %d bytes read", len );
        }
        
        text[ len - 1 ] = 0;    // rip off the /n and terminate

        return text;
	}

	f = &sys_con.inputLine;
    if( read( 0, &key, 1 ) != 1 ) {
        return NULL;
    }

    if( key == tty_erase || key == 127 || key == 8 ) {
        if( f->cursorPos ) {
            f->cursorPos--;
            f->text[f->cursorPos] = 0;
            write( 1, "\b \b", 3 );	
        }
        return NULL;
    }

    if( !key ) {
        return NULL;
    }

    if( key >= 32 ) {
        if( f->cursorPos < sizeof( f->text ) - 1 ) {
            write( 1, &key, 1 );
            f->text[f->cursorPos] = key;
            f->text[f->cursorPos+1] = 0;
            f->cursorPos++;
        }
        return NULL;
    }
    
    if( key == '\n' ) {
        Sys_HideInput();
        s = Prompt_Action( &sys_con );
        if( s ) {
            if( *s == '\\' || *s == '/' ) {
                s++;
            }
            Sys_Printf( "]%s\n", s );
        } else {
            write( 1, "]\n", 2 );	
        }
        Sys_ShowInput();
        return s;
    }

    if( key == '\t' ) {
        Sys_HideInput();
        Prompt_CompleteCommand( &sys_con, qfalse );
        f->cursorPos = strlen( f->text );
        Sys_ShowInput();
        return NULL;
    }
    
    if( read( 0, &key, 1 ) == 1 ) {
        if( key == '[' || key == 'O' ) {
            if( read( 0, &key, 1 ) == 1 ) {
                if( key == 'A' ) {
                    Sys_HideInput();
                    Prompt_HistoryUp( &sys_con );
                    Sys_ShowInput();
                } else if( key == 'B' ) {
                    Sys_HideInput();
                    Prompt_HistoryDown( &sys_con );
                    Sys_ShowInput();
                }
            }
        }

        while( read( 0, &key, 1 ) == 1 )
            ;
    }

	return NULL;
}

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

HUNK

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

void Hunk_Begin( mempool_t *pool, int maxsize ) {
    byte *buf;

	// reserve a huge chunk of memory, but don't commit any yet
	pool->maxsize = ( maxsize + 4095 ) & ~4095;
	pool->cursize = 0;
    buf = mmap( NULL, pool->maxsize, PROT_READ|PROT_WRITE,
		MAP_PRIVATE|MAP_ANON, -1, 0 );
	if( buf == NULL || buf == ( byte * )-1 ) {
		Com_Error( ERR_FATAL, "Hunk_Begin: unable to virtual allocate %d bytes",
                pool->maxsize );
    }
    pool->base = buf;
    pool->mapped = pool->maxsize;
}

void *Hunk_Alloc( mempool_t *pool, int size ) {
	byte *buf;

	// round to cacheline
	size = ( size + 31 ) & ~31;
	if( pool->cursize + size > pool->maxsize ) {
		Com_Error( ERR_FATAL,
            "Hunk_Alloc: unable to allocate %d bytes out of %d",
                size, pool->maxsize );
    }
	buf = pool->base + pool->cursize;
	pool->cursize += size;
	return buf;
}

void Hunk_End( mempool_t *pool ) {
	byte *n;

#ifndef __linux__
	size_t old_size = pool->maxsize;
	size_t new_size = pool->cursize;
	void * unmap_base;
	size_t unmap_len;

	new_size = round_page(new_size);
	old_size = round_page(old_size);
	if (new_size > old_size) {
		Com_Error( ERR_FATAL, "Hunk_End: new_size > old_size" );
    }
	if (new_size < old_size) {
		unmap_base = (caddr_t)(pool->base + new_size);
		unmap_len = old_size - new_size;
		n = munmap(unmap_base, unmap_len) + pool->base;
	}
#else
	n = mremap( pool->base, pool->maxsize, pool->cursize, 0 );
#endif
	if( n != pool->base ) {
		Com_Error( ERR_FATAL, "Hunk_End: could not remap virtual block: %s",
                strerror( errno ) );
    }
    pool->mapped = pool->cursize;
}

void Hunk_Free( mempool_t *pool ) {
	if( pool->base ) {
		if( munmap( pool->base, pool->mapped ) ) {
			Com_Error( ERR_FATAL, "Hunk_Free: munmap failed: %s",
                strerror( errno ) );
        }
	}
    memset( pool, 0, sizeof( *pool ) );
}


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

GENERAL ROUTINES

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

void Sys_DebugBreak( void ) {
}

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

int Sys_Milliseconds( void ) {
	struct timeval tp;
	struct timezone tzp;
	static int		secbase;

	gettimeofday( &tp, &tzp );

	if( !secbase ) {
		secbase = tp.tv_sec;
		return tp.tv_usec / 1000;
	}

	curtime = ( tp.tv_sec - secbase ) * 1000 + tp.tv_usec / 1000;

	return curtime;
}

uint32 Sys_Realtime( void ) {
	struct timeval tp;
	struct timezone tzp;
    uint32 time;
    
	gettimeofday( &tp, &tzp );
	time = tp.tv_sec * 1000 + tp.tv_usec / 1000;
	return time;
}

void Sys_Sleep( uint32 msec ) {
    NET_Sleep( msec );
}

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

qboolean Sys_RemoveFile( const char *path ) {
	if( remove( path ) ) {
		return qfalse;
	}
	return qtrue;
}

qboolean Sys_RenameFile( const char *from, const char *to ) {
	if( rename( from, to ) ) {
		return qfalse;
	}
	return qtrue;
}

/*
================
Sys_GetFileInfo
================
*/
qboolean Sys_GetFileInfo( const char *path, fsFileInfo_t *info ) {
	struct stat		st;
    qtime_t *ctime, *mtime;

	if( stat( path, &st ) ) {
		return qfalse;
	}

	info->fileSize = st.st_size;
	ctime = localtime( &st.st_ctime );
    mtime = localtime( &st.st_mtime );
	info->timeCreate = *ctime;
    info->timeModify = *mtime;

	return qtrue;
}

/*
=================
Sys_Quit
=================
*/
void Sys_Quit( void ) {
    if( !sys_nostdin || !sys_nostdin->integer ) {
        Sys_ShutdownTTY();

    }
	fflush( stdout );
	exit( 0 );
}

/*
=================
Sys_GetClipboardData
=================
*/
char *Sys_GetClipboardData( void ) {
#ifdef DEDICATED_ONLY
	return NULL;
#else
	SDL_SysWMinfo	info;
    Display *dpy;
	Window sowner, win;
	Atom type, property;
	unsigned long len, bytes_left;
	unsigned char *data;
	int format, result;
	char *ret;

    if( SDL_WasInit( SDL_INIT_VIDEO ) != SDL_INIT_VIDEO ) {
        return NULL;
    }
	SDL_VERSION( &info.version );
	if( !SDL_GetWMInfo( &info ) ) {
        return NULL;
    }
    if( info.subsystem != SDL_SYSWM_X11 ) {
        return NULL;
    }

    dpy = info.info.x11.display;
    win = info.info.x11.window;
	
	sowner = XGetSelectionOwner( dpy, XA_PRIMARY );
	if( sowner == None ) {
        return NULL;
    }

    property = XInternAtom( dpy, "GETCLIPBOARDDATA_PROP", False );
		                       
    XConvertSelection( dpy, XA_PRIMARY, XA_STRING, property, win, CurrentTime );
		
    XSync( dpy, False );
		
    result = XGetWindowProperty( dpy, win, property, 0, 0, False,
        AnyPropertyType, &type, &format, &len, &bytes_left, &data );
								   
    if( result != Success ) {
        return NULL;
    }

    ret = NULL;
    if( bytes_left ) {
        result = XGetWindowProperty( dpy, win, property, 0, bytes_left, True,
            AnyPropertyType, &type, &format, &len, &bytes_left, &data );
        if( result == Success ) {
            ret = Z_CopyString( ( char * )data );
        }
    }

	XFree( data );

    return ret;
#endif
}

/*
=================
Sys_SetClipboardData
=================
*/
void Sys_SetClipboardData( const char *data ) {
}

/*
=================
Sys_GetCurrentDirectory
=================
*/
char *Sys_GetCurrentDirectory( void ) {
	return curpath;
}

/*
================
Sys_FillAPI
================
*/
void Sys_FillAPI( sysAPI_t *api ) {
	api->Milliseconds = Sys_Milliseconds;
	api->GetClipboardData = Sys_GetClipboardData;
	api->SetClipboardData = Sys_SetClipboardData;
    api->HunkBegin = Hunk_Begin;
    api->HunkAlloc = Hunk_Alloc;
    api->HunkEnd = Hunk_End;
    api->HunkFree = Hunk_Free;
}

/*
=================
Sys_Kill
=================
*/
static void Sys_Kill( int signum ) {
	signal( SIGTERM, SIG_DFL );
	signal( SIGINT, SIG_DFL );
	signal( SIGSEGV, SIG_DFL );
	
	Com_Printf( "Received signal %d, exiting\n", signum );
	Com_Quit();
}

/*
=================
Sys_Segv
=================
*/
static void Sys_Segv( int signum ) {
	signal( SIGTERM, SIG_DFL );
	signal( SIGINT, SIG_DFL );
	signal( SIGSEGV, SIG_DFL );

    if( tty_enabled ) {
        Sys_HideInput();
        tcsetattr( 0, TCSADRAIN, &tty_orig );
    }

#ifndef DEDICATED_ONLY
    SDL_ShowCursor( SDL_ENABLE );
    SDL_WM_GrabInput( SDL_GRAB_OFF );
	SDL_Quit();
#endif

	fprintf( stderr, "Received signal SIGSEGV, segmentation fault\n" );

    exit( 1 );
}


/*
=================
Sys_Init
=================
*/
void Sys_Init( void ) {
	signal( SIGTERM, Sys_Kill );
	signal( SIGINT, Sys_Kill );
	signal( SIGSEGV, Sys_Segv );
	signal( SIGTTIN, SIG_IGN );
	signal( SIGTTOU, SIG_IGN );

	sys_nostdout = Cvar_Get( "sys_nostdout", "0", CVAR_NOSET );
	sys_nostdin = Cvar_Get( "sys_nostdin", "0", CVAR_NOSET );
	sys_debugdir = Cvar_Get( "sys_debugdir", "", CVAR_NOSET );
	
	Cmd_AddCommand( "listmodules", Sys_ListModules_f );

    if( !sys_nostdin->integer ) {
        Sys_InitTTY(); 
    }

	Sys_FillAPI( &sys );
}

/*
================
Sys_Printf
================
*/
void Sys_Printf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );

    Sys_ConsoleOutput( msg );
}

/*
=================
Sys_Error
=================
*/
void Sys_Error( const char *error, ... ) {
    va_list     argptr;
    char        text[MAXPRINTMSG];

    if( !sys_nostdin || !sys_nostdin->integer ) {
        Sys_ShutdownTTY();
    }

    va_start( argptr, error );
    Q_vsnprintf( text, sizeof( text ), error, argptr );
    va_end( argptr );
    
    fprintf( stderr, "********************\n"
                     "FATAL: %s\n"
                     "********************\n", text );

    exit( 1 );

}


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


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

DLL LOADING

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

typedef struct sysModule_s {
	listElem_t	elem;
	void *handle;
	moduleEntry_t entry;
	int refCount;
	char name[1];
} sysModule_t;

static list_t sys_moduleList;

/*
=================
Sys_FreeModule
=================
*/
void Sys_FreeModule( void *handle ) {
	sysModule_t *module = ( sysModule_t * )handle;

	if( module ) {
		if( --module->refCount < 1 ) {
			if( module->refCount < 0 ) {
				Com_Error( ERR_FATAL, "Sys_FreeModule: freed a freed module" );
			}
			if( dlclose( module->handle ) ) {
				Com_Error( ERR_FATAL, "Sys_FreeModule: FreeLibrary failed" );
			}
			List_DeleteElem( ( listElem_t * )module );
			Z_Free( module );
		}
	}
}

/*
=================
Sys_LoadModule
=================
*/
moduleEntry_t Sys_LoadModule( const char *name, void **handle ) {
	void		*hModule;
	moduleEntry_t moduleEntry;
	char	fullname[MAX_OSPATH];
	sysModule_t *module;
    listElem_t *elem;
	char *base;

	for( elem = sys_moduleList.first; elem; elem = elem->next ) {
		module = ( sysModule_t * )elem;
		if( !strcmp( module->name, name ) ) {
			break;
		}
	}

	if( elem != NULL ) {
		module->refCount++;
		*handle = ( void * )module;
		return module->entry;
	}
    
	*handle = NULL;

	if( sys_debugdir->string[0] ) {
		base = sys_debugdir->string;
	} else {
		base = LIBDIR;
	}

	Com_sprintf( fullname, sizeof( fullname ), "%s/%s.so", base, name );
	hModule = dlopen( fullname, RTLD_NOW );

	if( !hModule ) {
		Com_DPrintf( "Sys_LoadModule( '%s' ) failed: %s\n",
            fullname, dlerror() );
		return NULL;
	}

	moduleEntry = ( void * )dlsym( hModule, "moduleEntry" );
	if( !moduleEntry ) {
		Com_DPrintf( "Sys_LoadModule( '%s' ) failed: %s\n",
            fullname, dlerror() );
		dlclose( hModule );
		return NULL;
	}

	Com_DPrintf( "Sys_LoadModule( '%s' ) succeeded\n", fullname );

	module = Z_Malloc( sizeof( *module ) + strlen( name ) );
	module->refCount = 1;
	module->handle = hModule;
	module->entry = moduleEntry;
	strcpy( module->name, name );
	List_Append( &sys_moduleList, module );

	*handle = module;

	return moduleEntry;
}

/*
=================
Sys_ListModules_f
=================
*/
void Sys_ListModules_f( void ) {
	listElem_t *elem;
	sysModule_t *module;
	moduleInfo_t info;
    int count;

    count = 0;
	for( elem = sys_moduleList.first; elem; elem = elem->next ) {
		module = ( sysModule_t * )elem;

		Com_Printf( "Module  : %s\n", module->name );
		Com_Printf( "RefCount: %i\n", module->refCount );

		if( !module->entry( MQ_GETINFO, &info ) ) {
			Com_EPrintf( "Couldn't get module info\n" );
			continue;
		}

		Com_Printf( "Name    : %s\n", info.fullname );
		Com_Printf( "Author  : %s\n", info.author );
		Com_Printf( "\n" );
        count++;
	}

	Com_Printf( "----------------\n" );
	Com_Printf( "%i total modules\n", count );
	
}

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

static void	*gameLibrary;

#define	GAMELIB		"gamei386.so"

/*
=================
Sys_FreeGameLibrary
=================
*/
void Sys_FreeGameLibrary( void ) {
	if( !gameLibrary ) {
		return;
	}
	if( dlclose( gameLibrary ) ) {
		Com_Error( ERR_FATAL, "Sys_FreeLibrary failed!" );
	}
	gameLibrary = NULL;
}

/*
=================
Sys_LoadGameLibrary
=================
*/
void *Sys_LoadGameLibrary( void *parms ) {
	void	*(*GetAPI)( void * );
	char	name[MAX_OSPATH];
	char	*path;
#ifdef NDEBUG
	const char *debugdir = "releasei386";
#else
	const char *debugdir = "debugi386";
#endif

	if( gameLibrary ) {
		Sys_FreeGameLibrary();
	}

	// check the current debug directory first for development purposes
    if( sys_debugdir->string[0] ) {
        Com_sprintf( name, sizeof( name ), "%s/"GAMELIB,
                sys_debugdir->string );
    } else {
    	Com_sprintf( name, sizeof( name ), "%s/%s/"GAMELIB,
                Sys_GetCurrentDirectory(), debugdir );
    }
	gameLibrary = dlopen( name, RTLD_NOW );
	if( !gameLibrary ) {
		Com_DPrintf( "Sys_LoadLibrary( '%s' ) failed: %s\n",
                name, dlerror() );
		// now run through the search paths
		path = NULL;
		while( 1 ) {
			path = FS_NextPath( path );
			if( !path ) {
				break;		// couldn't find one anywhere
			}
			Com_sprintf( name, sizeof( name ), "%s/"GAMELIB, path );
			gameLibrary = dlopen( name, RTLD_NOW );
			if( gameLibrary ) {
				break;
			}
			Com_DPrintf( "Sys_LoadLibrary( '%s' ) failed: %s\n",
                    name, dlerror() );
		}
	}

	if( !gameLibrary ) {
		return NULL;
	}

	GetAPI = ( void * )dlsym( gameLibrary, "GetGameAPI" );
	if( !GetAPI ) {
		Com_DPrintf( "Sys_LoadLibrary( '%s' ) failed: %s\n",
                name, dlerror() );
		Sys_FreeGameLibrary();
		return NULL;
	}

	Com_DPrintf( "Sys_LoadLibrary( '%s' ) succeeded\n", name );

	return GetAPI( parms );
}


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

MISC

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

static qboolean Sys_FindInfoToExtraInfo( struct stat *st, fsFileInfo_t *info ) {
    qtime_t *ctime, *mtime;

	info->fileSize = st->st_size;
	ctime = localtime( &st->st_ctime );
    mtime = localtime( &st->st_mtime );
	info->timeCreate = *ctime;
    info->timeModify = *mtime;

	return qtrue;
}


/*
=================
Sys_ListFilteredFiles
=================
*/
static void Sys_ListFilteredFiles( char **listedFiles, int *count,
        const char *path, const char *filter, uint flags, int length )
{
	struct dirent *findInfo;
	DIR		*findHandle;
    struct  stat st;
	char	findPath[MAX_OSPATH];
	fsFileInfo_t	info;
	char	*name;

	if( *count >= MAX_LISTED_FILES ) {
		return;
	}

    if( ( findHandle = opendir( path ) ) == NULL ) {
        return;
    }

    while( ( findInfo = readdir( findHandle ) ) != NULL ) {
        if( !strcmp( findInfo->d_name, "." ) ) {
            continue;
        }
        if( !strcmp( findInfo->d_name, ".." ) ) {
            continue;
        }
        Com_sprintf( findPath, sizeof( findPath ), "%s/%s",
                path, findInfo->d_name );

        if( stat( findPath, &st ) == -1 ) {
            continue;
        }

		if( st.st_mode & S_IFDIR ) {
			Sys_ListFilteredFiles( listedFiles, count, findPath,
                    filter, flags, length );
		}

		if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) {
			if( !( st.st_mode & S_IFDIR ) ) {
				continue;
			}
		} else if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_NO ) {
			if( st.st_mode & S_IFDIR ) {
				continue;
			}
		}

		if( !FS_WildCmp( filter, findPath + length ) ) {
			continue;
		}
        if( flags & FS_SEARCH_SAVEPATH ) {
    		name = findPath + length;
        } else {
            name = findInfo->d_name;
        }

		if( flags & FS_SEARCH_EXTRAINFO ) {
			Sys_FindInfoToExtraInfo( &st, &info );
			listedFiles[( *count )++] = FS_CopyExtraInfo( name, &info );
		} else {
			listedFiles[( *count )++] = Z_CopyString( name );
		}
        if( *count >= MAX_LISTED_FILES ) {
            break;
        }

	}

	closedir( findHandle );
}


/*
=================
Sys_ListFiles
=================
*/
char **Sys_ListFiles( const char *path, const char *extension,
        uint32 flags, int *numFiles )
{
	struct dirent *findInfo;
	DIR		*findHandle;
	struct stat st;
	char	findPath[MAX_OSPATH];
	char	*listedFiles[MAX_LISTED_FILES];
	fsFileInfo_t	info;
	int		count;
	char	**list;
    char    *s;
	int		i, length;

	count = 0;

	if( numFiles ) {
		*numFiles = 0;
	}

	if( flags & FS_SEARCH_BYFILTER ) {
		length = strlen( path );
        if( !length ) {
            return NULL;
        }
		Sys_ListFilteredFiles( listedFiles, &count, path,
                extension, flags, length + 1 );
	} else {
		if( ( findHandle = opendir( path ) ) == NULL ) {
			return NULL;
		}

		while( ( findInfo = readdir( findHandle ) ) != NULL ) {
			if( !strcmp( findInfo->d_name, "." ) ) {
				continue;
			}
			if( !strcmp( findInfo->d_name, ".." ) ) {
				continue;
			}

			Com_sprintf( findPath, sizeof( findPath ), "%s/%s",
                    path, findInfo->d_name );

			if( stat( findPath, &st ) == -1 ) {
				continue;
			}
			if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) {
				if( !( st.st_mode & S_IFDIR ) ) {
					continue;
				}
			} else if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_NO ) {
				if( st.st_mode & S_IFDIR ) {
					continue;
				}
			}

			if( extension ) {
                s = COM_FileExtension( findInfo->d_name );
                if( strcmp( s, extension ) ) {
    				continue;
                }
			}
            
			if( flags & FS_SEARCH_SAVEPATH ) {
				s = findPath;
			} else {
				s = findInfo->d_name;
			}
			if( flags & FS_SEARCH_EXTRAINFO ) {
				Sys_FindInfoToExtraInfo( &st, &info );
				listedFiles[count++] = FS_CopyExtraInfo( s, &info );
			} else {
				listedFiles[count++] = Z_CopyString( s );
			}


			if( count >= MAX_LISTED_FILES ) {
				break;
			}
		}

		closedir( findHandle );
	}

	if( !count ) {
		return NULL;
	}

	qsort( listedFiles, count, sizeof( listedFiles[0] ), SortStrcmp );

	list = Z_Malloc( sizeof( char * ) * ( count + 1 ) );
	for( i = 0; i < count; i++ ) {
		list[i] = listedFiles[i];
	}
	list[count] = NULL;

	if( numFiles ) {
		*numFiles = count;
	}

	return list;
}

/*
=================
Sys_FreeFileList
=================
*/
void Sys_FreeFileList( char **list ) {
	char **p;

	if( !list ) {
		Com_Error( ERR_FATAL, "Sys_FreeFileList: NULL" );
	}

	p = list;
	while( *p ) {
		Z_Free( *p++ );
	}

	Z_Free( list );
}

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

NETWORKING

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

/*
====================
Sys_NetErrorString
====================
*/
char *Sys_NetErrorString( void ) {
	int		code;

	code = errno;
	return strerror( code );
}

/*
====================
Sys_IPSocket
====================
*/
SOCKET Sys_IPSocket( const char *net_interface, int port ) {
	SOCKET				newsocket;
	struct sockaddr_in	address;
	u_long              _true = 1;
	int					i = 1;

	if( !net_interface || !net_interface[0] ) {
		net_interface = "localhost";
	}

	Com_Printf( "Opening IP socket: %s:%i\n", net_interface, port );

	if( ( newsocket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) ==
            INVALID_SOCKET )
    {
		Com_WPrintf( "UDP_OpenSocket: socket: %s", Sys_NetErrorString() );
		return INVALID_SOCKET;
	}

	// make it non-blocking
	if( ioctl( newsocket, FIONBIO, (u_long *)&_true ) == SOCKET_ERROR ) {
		Com_WPrintf( "UDP_OpenSocket: ioctl FIONBIO: %s\n",
                Sys_NetErrorString() );
		close( newsocket );
		return INVALID_SOCKET;
	}

	// make it broadcast capable
	if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i,
                sizeof( i ) ) == SOCKET_ERROR )
    {
		Com_WPrintf( "UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n",
                Sys_NetErrorString() );
		close( newsocket );
		return INVALID_SOCKET;
	}

	if( !Q_stricmp( net_interface, "localhost" ) )
		address.sin_addr.s_addr = INADDR_ANY;
	else
		NET_StringToSockaddr( net_interface, (struct sockaddr *)&address );

	if( port == PORT_ANY )
		address.sin_port = 0;
	else
		address.sin_port = htons( (u_short)port );

	address.sin_family = AF_INET;

	if( bind( newsocket, (void *)&address, sizeof( address ) ) ==
            SOCKET_ERROR )
    {
		Com_WPrintf( "UDP_OpenSocket: bind: %s\n", Sys_NetErrorString() );
		close( newsocket );
		return INVALID_SOCKET;
	}

	return newsocket;
}

/*
====================
Sys_CloseSocket
====================
*/
void Sys_CloseSocket( SOCKET sock ) {
	close( sock );
}

/*
====================
Sys_InitNetworking
====================
*/
qboolean Sys_InitNetworking( void ) {
	return qtrue;
}

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

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

MAIN

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

static void _Qcommon_Init_ArgcArgv( int argc, char **argv ) {
    int i, length;
    char *cmdline;

    if( argc < 2 ) {
	    Qcommon_Init( NULL );
        return;
    }

    length = 0;
    for( i = 1; i < argc; i++ ) {
        length += strlen( argv[i] ) + 1;
    }
    
    cmdline = alloca( length );
    if( cmdline != NULL ) {
        cmdline[0] = 0;
        for( i = 1; i < argc - 1; i++ ) {
            strcat( cmdline, argv[i] );
            strcat( cmdline, " " );
        }
        strcat( cmdline, argv[i] );
    }
    
    Qcommon_Init( cmdline );
}

/*
=================
main
=================
*/
int main( int argc, char **argv ) {
    int i;

	if ( !getuid() || !geteuid() ) {
		Sys_Error(  "You cannot run " APPLICATION " as superuser "
                    "for security reasons" );
	}

    for( i = 1; i < argc; i++ ) {
        if( !strcmp( argv[1], "-v" ) || !strcmp( argv[1], "--version" ) ) {
            printf( APPLICATION " " VERSION " " __DATE__ " " BUILDSTRING "-"
                    CPUSTRING "\n" );
            return 0;
        }
    }
		
	// go back to real user for config loads
	saved_euid = geteuid();
	seteuid( getuid() );

	getcwd( curpath, sizeof( curpath ) );
    
    _Qcommon_Init_ArgcArgv( argc, argv );
    while( 1 ) {
        Qcommon_Frame();
    }

}

