#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"

#include <Bitmap.h>
#include <TranslationUtils.h>
#include <Application.h>
#include <Alert.h>
#include <Window.h>
#include <View.h>
#include <Path.h>
#include <Entry.h>
#include <Roster.h>
#include <Screen.h>

qboolean	app_quitting = qfalse;
thread_id	app_tid;		// thread id

int		arg_c = 0;
char	**arg_v = NULL;

int noconinput = 0;
int nostdout = 0;
qboolean isDedicated = qfalse;

char basedir[B_PATH_NAME_LENGTH];
char *cachedir = "/tmp";

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

//
// Splashscreen Class and routines
//

extern "C" void OpenSplash(void){}
extern "C" void CloseSplash(void){}

// =======================================================================
// 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 (nostdout)
        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_Printf (char *fmt, ...)
{
	va_list		argptr;
	char		text[2048];
	unsigned char		*p;
//	BAlert		*alert;

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

//	alert = new BAlert("Printf", text, "Okay");
//	alert->Go();
//	return;

	if (strlen(text) > sizeof(text))
		Sys_Error("memory overwrite in Sys_Printf");

    if (nostdout)
        return;

	for (p = (unsigned char *)text; *p; p++)
		if ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9)
			printf("[%02x]", *p);
		else
			putc(*p, stdout);
}

void Sys_Quit (void)
{
	status_t ret;
	app_quitting = qtrue;
	Host_Shutdown();
	be_app->PostMessage(B_QUIT_REQUESTED);
//    fcntl (STDIN_FILENO, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY);
	wait_for_thread(app_tid, &ret);
	exit(0); // exit_thread(B_OK);
}

void Sys_Init(void)
{
#if id386
	Sys_SetFPCW();
#endif
}

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

// change stdin to non blocking
 //   fcntl (STDIN_FILENO, 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);
	alert = new BAlert("Error", string, "Okay", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
	alert->Go();
	Host_Shutdown ();
	exit (1);

} 

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

    va_start (argptr,warning);
    vsprintf (string,warning,argptr);
    va_end (argptr);
//	fprintf(stderr, "Warning: %s", string);
	alert = new BAlert("Warning", string, "Okay", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
	alert->Go();
} 

/*
============
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);
	}
}

extern "C" 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;
}

void Sys_LineRefresh(void)
{
}

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

char *Sys_ConsoleInput(void)
{
#if 0
    static char text[256];
    int     len;

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

		return text;
	}
#endif
	return NULL;
}

#if !id386
void Sys_HighFPPrecision (void)
{
}

void Sys_LowFPPrecision (void)
{
}
#endif

int		skipframes;

class QApp : public BApplication
{
public:
	QApp(thread_func q_func);
	~QApp();

	virtual void ReadyToRun(void);
	virtual	bool QuitRequested(void);
};

thread_id	tid;

QApp::QApp(thread_func q_func)
	: BApplication("application/x-vnd.aixplosive-quake")
{
	app_quitting = qfalse;
}

QApp::~QApp()
{
	status_t	status;
	BWindow		*win;
	app_quitting = qtrue;
//	if(tid >= 0)
//		wait_for_thread(tid, &status);
	while(NULL!=(win=WindowAt(0)))
	{
		if(win->Lock())
			win->Quit();
	}
}

void QApp::ReadyToRun(void)
{
//	if(tid>=0)	resume_thread(tid);
//	else		PostMessage(B_QUIT_REQUESTED);
}

bool QApp::QuitRequested(void)
{
	if(app_quitting)
		return true;
	else
		return false;
//	app_quitting = qtrue;
//	return true;
}

int32	quake_func(void *data)
{
	quakeparms_t parms;
	int j;
	extern int vcrFile;
	extern int recording;
	double		time, oldtime, newtime;
	app_info	appinfo;

//	static char cwd[1024];

	OpenSplash();

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

	memset(&parms, 0, sizeof(parms));

	COM_InitArgv(arg_c, arg_v);
	parms.argc = com_argc;
	parms.argv = com_argv;

#ifdef GLQUAKE
	parms.memsize = 16*1024*1024;
#else
	parms.memsize = 8*1024*1024;
#endif

	j = COM_CheckParm("-mem");
	if (j)
		parms.memsize = (int) (Q_atof(com_argv[j+1]) * 1024 * 1024);
	parms.membase = malloc (parms.memsize);

	if(B_OK == be_app->GetAppInfo(&appinfo))
	{
		BEntry	entry(&appinfo.ref);
		BPath	path(&entry);
		if((entry.InitCheck()==B_OK)&&(path.InitCheck()==B_OK))
		{
			path.GetParent(&path);
			strcpy(basedir, path.Path());
			parms.basedir = basedir;
		}
	}
	else
	{
		parms.basedir = ".";
	}
// caching is disabled by default, use -cachedir to enable
//	parms.cachedir = cachedir;

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

	if (COM_CheckParm("-nostdout"))
		nostdout = 1;

    Host_Init(&parms);

	Sys_Init();

    oldtime = Sys_FloatTime () - 0.1;
    while (!app_quitting)
    {
// 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) )
            {
				snooze(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;

		Host_Frame(time);
    }
    return B_OK;
}

sem_id	app_wait;

int32	app_func(void *data)
{
	QApp q_app(quake_func);
	release_sem(app_wait);
	q_app.Run();
	return 0;
}

int main (int c, char **v)
{
#if 0
	arg_c = c;
	arg_v = v;

//	tid = spawn_thread(quake_func, "quake thread", B_NORMAL_PRIORITY, NULL);
//	QApp q_app(quake_func);
	QApp q_app(NULL);
	q_app.Run();
#else
	status_t ret;

	arg_c = c;
	arg_v = v;
	app_wait = create_sem(0, "quake_app_wait");

	app_tid = spawn_thread(app_func, "quake_app thread", B_NORMAL_PRIORITY, NULL);
	resume_thread(app_tid);

	acquire_sem(app_wait);
	quake_func(NULL);
	// thread will never reach here!!	(see Sys_Quit())
#endif
	return 0;
}


/*
================
Sys_MakeCodeWriteable
================
*/
void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length)
{
#ifdef USE_ASM
	// HERE: take "length" into account
	area_id id = area_for((void*)startaddr);
	if(id >= 0)
	{
		set_area_protection(id, B_READ_AREA|B_WRITE_AREA);
	}
#endif
}

byte scantokey[128] =
{
// NOT YET BOUND: PRINT, SCRLCK, PAUSE, 0x69=<>| 0x22=NUM_LOCK, 
// 0x23=KP_/ 0x24=KP_* 0x25=KP_- 0x3A=KP_+ 0x5D=KP_ENTER

// Keypad:
// 0x22 0x23 0x24 0x25
// 0x37 0x38 0x39 0x3A
// 0x48 0x49 0x4A
// 0x58 0x59 0x5A 0x5D
//   0x64    0x65

//	0		1		2		3		4		5		6		7
//	8		9		A		B		C		D		E		F
	0,		K_ESCAPE,K_F1,	K_F2,	K_F3,	K_F4,	K_F5,	K_F6,		// 0
	K_F7,	K_F8,	K_F9,	K_F10,	K_F10,	K_F11,	K_F12,	0,
	0,		/*'~'*/'`',	'1',	'2',	'3',	'4',	'5',	'6',		// 1
	'7',	'8',	'9',	'0',	'-',	'=',	K_BACKSPACE,K_INS,
	K_HOME,	K_PGUP,	0,		0,		0,		0,		K_TAB,	'q',		// 2
	'w',	'e',	'r',	't',	'y',	'u',	'i',	'o',
	'p',	'[',	']',	'\\',	0,		0,		0,		0,			// 3
	0,		0,		0,		0,		'a',	's',	'd',	'f',
	'g',	'h',	'j',	'k',	'l',	';',	'\'',	K_ENTER,	// 4
	0,		0,		0,		K_SHIFT,'z',	'x',	'c',	'v',
	'b',	'n',	'm',	',',	'.',	'/',	K_SHIFT,K_UPARROW,	// 5
	0,		0,		0,		0,		K_CTRL,	K_ALT,	K_SPACE,K_ALT,
	K_CTRL,	K_LEFTARROW,K_DOWNARROW,K_RIGHTARROW,0,		0,		0,		0,			// 6
	0,		0,		0,		0,		0,		0,		0,		0,
	0,		0,		0,		0,		0,		0,		0,		0,			// 7
	0,		0,		0,		0,		0,		0,		0,		0,

};

void Sys_SendKeyEvents(void)
{
	static	key_info keys_old;
			key_info keys_new;
	int		key;

	#define KEY_CHANGED(a) ((keys_old.key_states[(a)>>3]^keys_new.key_states[(a)>>3])&(0x80>>((a)&0x07)))
	#define KEY_PRESSED(a) (keys_new.key_states[(a)>>3]&(0x80>>((a)&0x07)))

	if(get_key_info(&keys_new) == B_OK)
	{
		for(key = 0; key < 128; key++)
		{
			if(KEY_CHANGED(key))
			{
				if(KEY_PRESSED(key))
					Key_Event(scantokey[key], qtrue);
				else
					Key_Event(scantokey[key], qfalse);
			}
		}
		memcpy(&keys_old, &keys_new, sizeof(key_info));
	}
}

char *cb_data = NULL;

extern "C" char *Sys_GetClipboardData(void)
{
	BMessage *clip;
	char	 *text;
	ssize_t	 textlen;

	if(be_clipboard->Lock())
	{
		if(NULL!=(clip = be_clipboard->Data()))
		{
			clip->FindData("text/plain", B_MIME_TYPE, (const void**)&text, &textlen);
			if(cb_data != NULL)
				free(cb_data);
			cb_data = (char*)malloc(textlen+1);
			memcpy(cb_data, text, textlen);
			cb_data[textlen]=0;
		}
		be_clipboard->Unlock();
	}
	return cb_data;
}

extern "C" int Sys_modifiers(void) { return modifiers(); }
