#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 "quakedef.h"

int noconinput = 0;

char *basedir = ".";
char *cachedir = "/tmp";

cvar_t sys_linerefresh = { "sys_linerefresh", "0" };	// set for entity display
cvar_t sys_nostdout = { "sys_nostdout", "0" };

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

void Sys_DebugNumber(int y, int val)
{
}

/*
void Sys_Printf (char *fmt, ...)
{
	va_list		argptr;
	char		text[1024];
	
	va_start (argptr,fmt);
	vsprintf (text,fmt,argptr);
	va_end (argptr);
	fprintf(stderr, "%s", text);
	
	Con_Print (text);
}
*/

void Sys_Printf(char *fmt, ...)
{

    va_list argptr;
    char text[1024], *t_p;
    int l, r;

    if (sys_nostdout.value)
	return;

    va_start(argptr, fmt);
    vsprintf(text, fmt, argptr);
    va_end(argptr);

    l = strlen(text);
    t_p = text;

// make sure everything goes through, even though we are non-blocking
    while (l) {
	r = write(1, text, l);
	if (r != l)
	    sleep(0);
	if (r > 0) {
	    t_p += r;
	    l -= r;
	}
    }

}

void Sys_Quit(void)
{
    Host_Shutdown();
    fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY);
    exit(0);
}

void Sys_Init(void)
{

}

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);

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

    Host_Shutdown();
    exit(1);

}

void Sys_Warn(char *warning, ...)
{
    va_list argptr;
    char string[1024];

    va_start(argptr, warning);
    vsprintf(string, warning, argptr);
    va_end(argptr);
    fprintf(stderr, "Warning: %s", string);
}

/*
============
Sys_FileTime

returns -1 if not present
============
*/
int Sys_FileTime(char *path)
{
    struct stat buf;

    if (stat(path, &buf) == -1)
	return -1;

    return buf.st_mtime;
}


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

int Sys_FileOpenRead(char *path, int *handle)
{
    int h;
    struct stat fileinfo;


    h = open(path, O_RDONLY, 0666);
    *handle = h;
    if (h == -1)
	return -1;

    if (fstat(h, &fileinfo) == -1)
	Sys_Error("Error fstating %s", path);

    return fileinfo.st_size;
}

int Sys_FileOpenWrite(char *path)
{
    int handle;

    umask(0);

    handle = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);

    if (handle == -1)
	Sys_Error("Error opening %s: %s", path, strerror(errno));

    return handle;
}

int Sys_FileWrite(int handle, void *src, int count)
{
    return write(handle, src, count);
}

void Sys_FileClose(int handle)
{
    close(handle);
}

void Sys_FileSeek(int handle, int position)
{
    lseek(handle, position, SEEK_SET);
}

int Sys_FileRead(int handle, void *dest, int count)
{
    return read(handle, dest, count);
}

void Sys_DebugLog(char *file, char *fmt, ...)
{
    va_list argptr;
    static char data[1024];
    int fd;

    va_start(argptr, fmt);
    vsprintf(data, fmt, argptr);
    va_end(argptr);
//    fd = open(file, O_WRONLY | O_BINARY | O_CREAT | O_APPEND, 0666);
    fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0666);
    write(fd, data, strlen(data));
    close(fd);
}

void Sys_EditFile(char *filename)
{

    char cmd[256];
    char *term;
    char *editor;

    term = getenv("TERM");
    if (term && !strcmp(term, "xterm")) {
	editor = getenv("VISUAL");
	if (!editor)
	    editor = getenv("EDITOR");
	if (!editor)
	    editor = getenv("EDIT");
	if (!editor)
	    editor = "vi";
	sprintf(cmd, "xterm -e %s %s", editor, filename);
	system(cmd);
    }

}

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

    gettimeofday(&tp, &tzp);

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

    return (tp.tv_sec - secbase) + tp.tv_usec / 1000000.0;
}

// =======================================================================
// Sleeps for microseconds
// =======================================================================

static volatile int oktogo;

void alarm_handler(int x)
{
    oktogo = 1;
}

byte *Sys_ZoneBase(int *size)
{

    char *QUAKEOPT = getenv("QUAKEOPT");

    *size = 0xc00000;
    if (QUAKEOPT) {
	while (*QUAKEOPT)
	    if (tolower(*QUAKEOPT++) == 'm') {
		*size = atof(QUAKEOPT) * 1024 * 1024;
		break;
	    }
    }
    return malloc(*size);

}

void Sys_LineRefresh(void)
{
}

void Sys_Sleep(void)
{
    usleep(10);
}

void floating_point_exception_handler(int whatever)
{
//      Sys_Warn("floating point exception\n");
    signal(SIGFPE, floating_point_exception_handler);
}

/*
char *Sys_ConsoleInput(void)
{
    static char text[256];
    int     len;

    len = read (0, text, sizeof(text));
    if (len < 1)
        return NULL;
    text[len-1] = 0;    // rip off the /n and terminate

    return text;
}
*/

void moncontrol(int x)
{
}

int main(int c, char **v)
{

    double time, oldtime, newtime;
    quakeparms_t parms;
    extern int vcrFile;
    extern int recording;
    static int frame;

//      static char cwd[1024];

    moncontrol(0);

/*	signal(SIGFPE, floating_point_exception_handler); */
    signal(SIGFPE, SIG_IGN);

    noconinput = COM_CheckParm("-noconinput");
    if (!noconinput)
	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | FNDELAY);

//      getcwd(cwd);
//      if (cwd[Q_strlen(cwd)-1] == '/') cwd[Q_strlen(cwd)-1] = 0;
//      parms.basedir = cwd;
    parms.memsize = 8 * 1024 * 1024;
    parms.membase = malloc(parms.memsize);
    parms.basedir = basedir;
    parms.cachedir = cachedir;

    COM_InitArgv(c, v);
    parms.argc = com_argc;
    parms.argv = com_argv;

    Sys_Init();

    Host_Init(&parms);

    Cvar_RegisterVariable(&sys_nostdout);

    oldtime = Sys_FloatTime() - 0.1;
    while (1) {
	usleep(1000);
// find time spent rendering last frame
	newtime = Sys_FloatTime();
	time = newtime - oldtime;

	if (cls.state == ca_dedicated) {	// play vcrfiles at max speed
	    if (time < sys_ticrate.value && (vcrFile == -1 || recording)) {
		usleep(1);
		continue;	// not time to run a server only tic yet
	    }
	    time = sys_ticrate.value;
	}

	if (time > sys_ticrate.value * 2)
	    oldtime = newtime;
	else
	    oldtime += time;

	if (++frame > 10)
	    moncontrol(1);	// profile only while we do each Quake frame
	Host_Frame(time);
	moncontrol(0);

// graphic debugging aids
	if (sys_linerefresh.value)
	    Sys_LineRefresh();
    }

}


/*
================
Sys_MakeCodeWriteable
================
*/
void Sys_MakeCodeWriteable(unsigned long startaddr, unsigned long length)
{

    int r;
    unsigned long addr;
    int psize = getpagesize();

    fprintf(stderr, "writable code %lx-%lx\n", startaddr,
	    startaddr + length);

    addr = startaddr & ~(psize - 1);

    r = mprotect((char *) addr, length + startaddr - addr, 7);

    if (r < 0)
	Sys_Error("Protection change failed\n");

}
