/*
** Falling Tower KAI
**	Copyright (c) 2000	ROYALPANDA	All rights reserved
**
**	May 25, 2000 Ver 1.0
*/

#include<stdio.h>
#include<time.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/syslimits.h>
#include<signal.h>
#include<X11/Intrinsic.h>
#include<X11/StringDefs.h>
#include<X11/Xaw/Label.h>
#include<X11/Xaw/List.h>
#include<X11/Xaw/Form.h>
#include<X11/Xaw/Viewport.h>
#include<X11/Shell.h>
#include<X11/xpm.h>

#include"key.h"

#include"xjump.h"
#include"xjump_xlib.h"
#include"record.h"

#include"picture.xpm"
#include"title.xpm"
#include"icon.xbm"
#include"icon_msk.xbm"

/***********************************************************
**
**	Xt Intrinsic data
**
************************************************************/

typedef struct{
	String up;
	String down;
	String left;
	String right;
	String trig1;
	String trig2;
	String trig3;
	String trig4;
	String pause;
	String quit;

	int private;

	String title_xpm;
	String graphic;
} AppData;

static AppData app_data;

static XtResource Resources[] = {
	{
		"keyUp", "KeyUp", XtRString, sizeof(String),
		XtOffsetOf(AppData,up),
		XtRImmediate,"Up"
	},
	{
		"keyDown", "KeyDown", XtRString, sizeof(String),
		XtOffsetOf(AppData,down),
		XtRImmediate,"Down"
	},
	{
		"keyLeft", "KeyLeft", XtRString, sizeof(String),
		XtOffsetOf(AppData,left),
		XtRImmediate,"Left"
	},
	{
		"keyRight", "KeyRight", XtRString, sizeof(String),
		XtOffsetOf(AppData,right),
		XtRImmediate,"Right"
	},
	{
		"keyTrig1", "KeyTrig1", XtRString, sizeof(String),
		XtOffsetOf(AppData,trig1),
		XtRImmediate,"space"
	},
	{
		"keyTrig2", "KeyTrig2", XtRString, sizeof(String),
		XtOffsetOf(AppData,trig2),
		XtRImmediate,"n"
	},
	{
		"keyTrig3", "KeyTrig3", XtRString, sizeof(String),
		XtOffsetOf(AppData,trig3),
		XtRImmediate,"x"
	},
	{
		"KeyTrig4", "KeyTrig4", XtRString, sizeof(String),
		XtOffsetOf(AppData,trig4),
		XtRImmediate,"c"
	},
	{
		"keyPause", "KeyPause", XtRString, sizeof(String),
		XtOffsetOf(AppData,pause),
		XtRImmediate,"p"
	},
	{
		"keyQuit", "KeyQuit", XtRString, sizeof(String),
		XtOffsetOf(AppData,quit),
		XtRImmediate,"q"
	},
	{
		"private", "Private", XtRBoolean, sizeof(int),
		XtOffsetOf(AppData,private),
		XtRImmediate,(XtPointer)False
	},
	{
		"titleXPM", "TitleXPM", XtRString, sizeof(String),
		XtOffsetOf(AppData,title_xpm),
		XtRImmediate,NULL
	},
	{
		"graphicXPM", "GraphicXPM", XtRString, sizeof(String),
		XtOffsetOf(AppData,graphic),
		XtRImmediate,NULL
	},
};


static XrmOptionDescRec Options[] = {
	{"-up","*keyUp",XrmoptionSepArg,(caddr_t)NULL },
	{"-down","*keyDown",XrmoptionSepArg,(caddr_t)NULL },
	{"-left","*keyLeft",XrmoptionSepArg,(caddr_t)NULL },
	{"-right","*keyRight",XrmoptionSepArg,(caddr_t)NULL },
	{"-trig1","*keyTrig1",XrmoptionSepArg,(caddr_t)NULL },
	{"-trig2","*keyTrig2",XrmoptionSepArg,(caddr_t)NULL },
	{"-trig3","*keyTrig3",XrmoptionSepArg,(caddr_t)NULL },
	{"-trig4","*keyTrig4",XrmoptionSepArg,(caddr_t)NULL },
	{"-pause","*keyPause",XrmoptionSepArg,(caddr_t)NULL },
	{"-quit","*keyQuit",XrmoptionSepArg,(caddr_t)NULL },
	{"-private","*private",XrmoptionNoArg,(caddr_t)NULL },
	{"-title","*titleXPM",XrmoptionSepArg,(caddr_t)NULL },
	{"-graphic","*graphicXPM",XrmoptionSepArg,(caddr_t)NULL },
};

static XtAppContext App;   /* Application context */

static Widget Top;		/* Toplevel */
static Widget Score;		/* score (Label) */
static Widget Scr;		/* main screen (core) */
static Widget Score_v;		/* hiscore frame (viewport) */
static Widget ScoreList;	/* hiscore (label) */
static Widget Gameover;		/* gameover (label) */
static Widget Pause;		/* pause (label) */
static Widget Message;		/* message (label) */

static int IntervalState;	/* Is timer running? */
static XtIntervalId IntervalId; /* timer ID */

extern char *DefaultResources[];


/******************************************************
**
**	Xlib data
**
******************************************************/

static Colormap Cmap;    /* colormap */
static XKeyboardState Keyboard;
static int Repeat_mode = 1;      /* key repeat mode (1:default 0:off) */


Display *Disp;		/* display */
GC Gc_nomask;		/* GC for no masking */
GC Gc_mask;		/* GC for masking */

	/* drawables */
Drawable Scr_d;		/* main screen */
Pixmap Char_p;		/* characters */
Pixmap Char_m;		/* characters(mask) */
Pixmap Floor_p[4];	/* floor */


/*****************************************************
**
**	game data
**
*****************************************************/

static int Sc;		/* score */

static int State;	/* State */

#define STATE_TITLE	0
#define STATE_GAME	1
#define STATE_GAMEOVER	2
#define STATE_PAUSE	3

#define TIC 20		/* tics */

static int Redraw_enable;

	/* recording */
static record_file_t Record;
static record_file_t Record_private;

static record_t My_record;
static int Recording;

static char User_dir[PATH_MAX];
static char Filename[3][PATH_MAX+1];


char *Myname;   /* program name */


/*************************************
**
**	prototypes
**
*************************************/

static void timi( XtPointer c, XtIntervalId id );


/*********************
** message
**	show message
**********************/

void message( char *msg )
{
	 XtVaSetValues(Message,XtNlabel,msg,NULL);
}


/*************************************
**
**	local functions 
**
************************************/


/*******************************
** repeat_off
**	disable key repeat
*******************************/

static void repeat_off( void )
{
	if( Repeat_mode ){
		XGetKeyboardControl(Disp,&Keyboard);
		XAutoRepeatOff(Disp);

		Repeat_mode = 0;
	}
}


/******************************************
** repeat_on
**	reset key repeat mode as default
******************************************/

static void repeat_on( void )
{
	if( !Repeat_mode ){
		if( Keyboard.global_auto_repeat )
			XAutoRepeatOn(Disp);
		else
			XAutoRepeatOff(Disp);

		Repeat_mode = 1;
	}
}


/*************************************
** make_hiscore
**	create hiscore list
*************************************/


static void make_hiscore( void )
{
	struct tm *tm;
	time_t t;
	int i;
	char *p;
	record_t *q;
	char buf[80*(RECORD_ENTRY_PRIVATE+RECORD_ENTRY+7)];

	if( !Recording ){
		XtVaSetValues(ScoreList,XtNlabel,
			"Cannot save record file!",
			NULL);
		return;
	}

	p = buf;

	p += sprintf(p,
		"\n"
		"Rank Floor   << Hi-Score Players >>          Date\n"
		"---- ----- ------------------------------- "
		"----------------\n");

	q = Record.record;
	for( i = 0 ; i < Record.num_record ; i++,q++ ){
		t = q->date;
		tm = localtime(&t);
		p += sprintf(p,
		    "%4d %4d  %-31.31s %04d/%02d/%02d %02d:%02d\n",
		    i+1,q->score,q->name,
		    tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,
		    tm->tm_hour,tm->tm_min);
	}

	p += sprintf(p,
		"\n"
		"\n"
		"Rank Floor   << Your Hi-Score >>             Date\n"
		"---- ----- ------------------------------- "
		"----------------\n");

	q = Record_private.record;
	for( i = 0 ; i < Record_private.num_record ; i++,q++ ){
		t = q->date;
		tm = localtime(&t);
		p += sprintf(p,
		    "%4d %4d  %-31.31s %04d/%02d/%02d %02d:%02d\n",
		    i+1,q->score,q->name,
		    tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,
		    tm->tm_hour,tm->tm_min);
	}

	p--;
	*p = '\0';

	XtVaSetValues(ScoreList,XtNlabel,buf,NULL);
}


/*****************************************
** put_score
**	show score
*****************************************/

static void put_score( void )
{
	char buf[32];

	sprintf(buf,"%04d",Sc);
	XtVaSetValues(Score,XtNlabel,buf,NULL);
}


/*****************************************
** reset_timer
**	disable interval timer
*****************************************/

static void reset_timer( void )
{
	if( IntervalState ){
		XtRemoveTimeOut(IntervalId);
		IntervalState = 0;
	}
}


/*****************************************
** set_timer
**	enable interval timer
*****************************************/

static void set_timer( void )
{
	if( IntervalState )
		reset_timer();
	IntervalId = XtAppAddTimeOut(App,TIC,(XtTimerCallbackProc)timi,NULL);
	IntervalState = 1;
}


/**********************************************************
** gameover
**	transit to STATE_GAMEOVER
**********************************************************/

static void gameover( void )
{
	XtMapWidget(Gameover);

	State = STATE_GAMEOVER;

	XFlush(Disp);

	My_record.score = Sc;
	My_record.date = time(NULL);

	if( Recording ){
		if( RECORD_write(&Record) < 0
				|| RECORD_write(&Record_private) < 0 ){
			Recording = 0;
		}
	}

	make_hiscore();

}


/*********************************
** title
**	transit to STATE_TITLE
*********************************/

static void title( void )
{
	reset_timer();
	XtUnmapWidget(Gameover);
	XtUnmapWidget(Scr);
	State = STATE_TITLE;
	make_hiscore();
	XtMapWidget(Score_v);
}


/********************************
** start_game
**	transit to STATE_GAME
*********************************/

static void start_game( void )
{
	message("");

	XtUnmapWidget(Score_v);
	XtUnmapWidget(Gameover);
	XtMapWidget(Scr);
	GAME_init();
	Sc = 0;
	State = STATE_GAME;
	set_timer();
}


/*******************************************
** pause_game
**	toggle pause state
********************************************/

static void pause_game( void )
{
	switch( State ){
	case STATE_GAME:
		reset_timer();
		XtMapWidget(Pause);
		State = STATE_PAUSE;
		Redraw_enable = 1;
		break;

	case STATE_PAUSE:
		State = STATE_GAME;
		XtUnmapWidget(Pause);
		set_timer();
		Redraw_enable = 0;
		break;
	}
}


/*******************************************
** quit_game
**	quit game
********************************************/

static void quit_game(void )
{
	repeat_on();
	XFlush( Disp );
	exit(0);
}


/********************************
** timi		-- Xt interval timer handler
**	interval timer handler
********************************/

static void timi( XtPointer c,XtIntervalId id )
{
	int floor;
	static int timer;
	int i;

	IntervalState = 0;

	set_timer();

	switch( State ){
	case STATE_GAME:
		floor = GAME_main(1);
		if( floor < 0 ){
			gameover();
			timer = 0;
		}else if( floor > Sc ){
			Sc = floor;
			if( Sc > 9999 )
				Sc = 9999;
			put_score();
		}
		break;
		
	case STATE_GAMEOVER:
		if( timer++ < 1200 / TIC )
			GAME_main(0);
		else
			title();
		break;
	}
}

/***********************************************
** focus	-- Xt event handler
**	change key repeat when focus is changed
***********************************************/

static void focus( Widget w,XtPointer p,XEvent *e )
{
	if( e->type == FocusIn )
		repeat_off();
	else
		repeat_on();
}


/************************************************
** expose	-- Xt event handler
**	redraw screen
***********************************************/

static void expose( Widget w,XtPointer p,XEvent *e )
{
	if( Redraw_enable && e->xexpose.count == 0 )
		GAME_redraw();
}


/*************************************
** key		-- Xt event handler
**	key press/release
*************************************/

static void key( Widget w, XtPointer state, XEvent *e )
{
	int k;

	if( (int)state ){
		k = KEY_change(e->xkey.keycode,1);
		if( k & KEY_QUIT )
			quit_game();
		else if( k & KEY_PAUSE )
			pause_game();
		
	}else{
		k = KEY_change(e->xkey.keycode,0);
		if( State == STATE_TITLE && (k & (KEY_TRIG1|KEY_UP)) ){
			start_game();
		}
	}
}


/*******************************************
** sig_handler		-- signal handler
**	quit game
********************************************/

static void sig_handler( int dummy )
{
	quit_game();
}

/*********************************
** set_icon
**	set icon bitmap
**********************************/

static void set_icon( void )
{
	Pixmap icon,mask;

	icon = XCreateBitmapFromData(Disp,DefaultRootWindow(Disp),
		icon_bits,icon_width,icon_height);

	mask = XCreateBitmapFromData(Disp,DefaultRootWindow(Disp),
		icon_msk_bits,icon_msk_width,icon_msk_height);

	XtVaSetValues(Top,XtNiconPixmap,icon,XtNiconMask,mask,NULL);
}


/**************************
** usage
**	show usage
***************************/


static void usage()
{
	fprintf(stderr,"Usage: %s [options]\n",Myname);
	fprintf(stderr,"\t-toolkitoption ...\n");
	fprintf(stderr,"\t-private\t\tuse private colormap.\n");
	fprintf(stderr,"\t-graphic 'file'\t\tuse your xpm graphic 'file'\n");
}


/*********************************
** init_graphic
**	initialize graphic data
*********************************/

static void init_graphic( Pixmap *back, Pixmap *title, int *tw,int *th )
{
	int i,x;
	XpmAttributes attr;

	attr.valuemask = XpmColormap|XpmCloseness;
	attr.colormap = Cmap;
	attr.closeness = 40000;

	if( app_data.graphic != NULL ){
		i = XpmReadFileToPixmap(Disp,DefaultRootWindow(Disp),
			app_data.graphic,&Char_p,&Char_m,&attr);
	}else{
		i = XpmCreatePixmapFromData(Disp,DefaultRootWindow(Disp),
			picture_xpm,&Char_p,&Char_m,&attr);
	}
	if( i ){
		fprintf(stderr,"%s: %s\n",Myname,XpmGetErrorString(i));
		exit(1);
	}

	if( app_data.title_xpm != NULL ){
		i = XpmReadFileToPixmap(Disp,DefaultRootWindow(Disp),
			app_data.title_xpm,title,NULL,&attr);
	}else{
		i = XpmCreatePixmapFromData(Disp,DefaultRootWindow(Disp),
			title_xpm,title,NULL,&attr);
	}
	if( i ){
		fprintf(stderr,"%s: %s\n",Myname,XpmGetErrorString(i));
		exit(1);
	}
	*tw = attr.width;
	*th = attr.height;

	Gc_mask = XCreateGC(Disp,DefaultRootWindow(Disp),0,NULL);
	XSetClipMask(Disp,Gc_mask,Char_m);

	Gc_nomask = XCreateGC(Disp,DefaultRootWindow(Disp),0,NULL);

	*back = XCreatePixmap(Disp,DefaultRootWindow(Disp),
			 WIDTH*16,16,
			 DefaultDepth(Disp,DefaultScreen(Disp)));

	XCopyArea(Disp,Char_p,*back,Gc_nomask,80,128,16,16,0,0);
	XCopyArea(Disp,Char_p,*back,Gc_nomask,96,128,16,16,WIDTH*16-16,0);

	for( x = 16 ; x < WIDTH*16-16 ; x+=16 )
		XCopyArea(Disp,Char_p,*back,Gc_nomask,0,128,16,16,x,0);

	for( i = 0 ; i < 3 ; i++ ){
		Floor_p[i] = XCreatePixmap(Disp,DefaultRootWindow(Disp),
				WIDTH*16-32,16,
				DefaultDepth(Disp,DefaultScreen(Disp)));

		for( x = 0 ; x < WIDTH*16-32 ; x+=16 ){
			XCopyArea(Disp,Char_p,Floor_p[i],Gc_nomask,
				i*16,128,16,16,x,0);
		}
	}
	Floor_p[3] = XCreatePixmap(Disp,DefaultRootWindow(Disp),
			WIDTH*16-32,16,DefaultDepth(Disp,DefaultScreen(Disp)));

	for( x = 0 ; x < WIDTH*16-32 ; x+=32 ){
		XCopyArea(Disp,Char_p,Floor_p[3],Gc_nomask,48,128,32,16,x,0);
	}
}



/**********
** main
**********/

int main( int argc, char **argv )
{
	Widget game,w;
	Pixmap back_p,title_p;
	int width,height;
	int i;
	int uid,euid;

	Myname = argv[0];
	if( Myname == NULL )
		Myname = "xjumpx";

	uid = getuid();
	euid = geteuid();
	seteuid(uid);

	XtSetLanguageProc(NULL,NULL,NULL);

	Top = XtVaAppInitialize(&App,"XJumpX",Options,XtNumber(Options),
					&argc,argv,DefaultResources,NULL);

	seteuid(euid);

	XtGetApplicationResources(Top,&app_data,
		Resources,XtNumber(Resources),NULL,0);

	Disp = XtDisplay(Top);

	if( app_data.private ){
		Cmap = XCreateColormap(Disp,DefaultRootWindow(Disp),
			   DefaultVisual(Disp,DefaultScreen(Disp)),AllocNone);
		XtVaSetValues(Top,XtNcolormap,Cmap,NULL);
	}else{
		Cmap = DefaultColormap(Disp,DefaultScreen(Disp));
	}

	if( argc > 1 ){
		usage();
		return 0;
	}

	KEY_init();
	KEY_add(Disp,KEY_UP,app_data.up);
	KEY_add(Disp,KEY_LEFT,app_data.left);
	KEY_add(Disp,KEY_RIGHT,app_data.right);
	KEY_add(Disp,KEY_TRIG1,app_data.trig1);
	KEY_add(Disp,KEY_PAUSE,app_data.pause);
	KEY_add(Disp,KEY_QUIT,app_data.quit);

	init_graphic(&back_p,&title_p,&width,&height);

	game = XtVaCreateManagedWidget("game",formWidgetClass,Top,NULL);

	w = XtVaCreateManagedWidget("board",formWidgetClass,game,NULL);

	XtVaCreateManagedWidget("title",widgetClass,w,
				XtNwidth,width,
				XtNheight,height,
				XtNbackgroundPixmap,title_p,
				NULL);

	XtVaCreateManagedWidget("scLabel",labelWidgetClass,w,NULL);

	Score = XtVaCreateManagedWidget("score",labelWidgetClass,w,
					XtNlabel,"0000",
					NULL);

	Gameover = XtVaCreateManagedWidget("gameover",labelWidgetClass,game,
					   XtNmappedWhenManaged,FALSE,
					   NULL);

	Pause = XtVaCreateManagedWidget("pause",labelWidgetClass,game,
					XtNmappedWhenManaged,FALSE,
					NULL);

	Scr = XtVaCreateManagedWidget("scr",widgetClass,game,
				XtNwidth,WIDTH*16,
				XtNheight,HEIGHT*16,
				XtNmappedWhenManaged,FALSE,
				XtNbackgroundPixmap,back_p,
				NULL);

	Score_v = XtVaCreateManagedWidget("record_v",viewportWidgetClass,game,
					  XtNwidth,WIDTH*16,
					  XtNheight,HEIGHT*16,
					  NULL);

	ScoreList = XtVaCreateManagedWidget("record",labelWidgetClass,Score_v,
					    XtNlabel,"",
					    NULL);

	Message = XtVaCreateManagedWidget("message",labelWidgetClass,game,
					  XtNwidth,WIDTH*16,
					  XtNlabel,
						"Copyright (C)2000 ROYALPANDA",
					  NULL);

	XtAddEventHandler(Top,FocusChangeMask,FALSE,(XtEventHandler)focus,NULL);
	XtAddEventHandler(game,KeyPressMask,FALSE,
		(XtEventHandler)key,(XtPointer)1);
	XtAddEventHandler(game,KeyReleaseMask,FALSE,
		(XtEventHandler)key,(XtPointer)0);
	XtAddEventHandler(game,ExposureMask,FALSE,
		(XtEventHandler)expose,(XtPointer)0);

	if( signal(SIGINT,SIG_IGN) != SIG_IGN )
		signal(SIGINT,sig_handler);

	if( signal(SIGTERM,SIG_IGN) != SIG_IGN )
		signal(SIGTERM,sig_handler);

	XtRealizeWidget(Top);
	set_icon();
	Scr_d = XtWindow(Scr);

	XFreePixmap(Disp,back_p);
	XFreePixmap(Disp,title_p);

	get_name(My_record.name,32,uid);
	My_record.uid = uid;

	Record.record_file = RECORD_FILE;
	Record.swap_file = SWAP_FILE;
	Record.lock_file = LOCK_FILE;
	Record.max_record = RECORD_ENTRY;
	Record.new_record = &My_record;
	Record.override = 1;

	sprintf(Filename[0],"%s/%d.dat",RECORD_DIR,uid);
	sprintf(Filename[1],"%s/%d.swap",RECORD_DIR,uid);
	sprintf(Filename[2],"%s/%d.lock",RECORD_DIR,uid);
	Record_private.record_file = Filename[0];
	Record_private.swap_file = Filename[1];
	Record_private.lock_file = Filename[2];
	Record_private.max_record = RECORD_ENTRY_PRIVATE;
	Record_private.new_record = &My_record;
	Record_private.override = 0;

	if( RECORD_init(&Record) != -1 && RECORD_init(&Record_private) != -1 ){
		Recording = 1;
	}else{
		Recording = 0;
	}

	make_hiscore();

	srnd(time(NULL));

	State = STATE_TITLE;
	Redraw_enable = 0;

	XtAppMainLoop(App);

	return 0;
}
