#include <signal.h>
#include <stdlib.h>
#include <ioctl.h>
#include <ctype.h>
#include <basepage.h>
#include <string.h>
#include <osbind.h>
#include <xbra.h>
#include <sysvars.h>
#include <linea.h>
#include <macros.h>

#ifdef bool
#undef bool
#endif

typedef short	bool;

BASEPAGE	*master = (BASEPAGE *) 0;
char	*master_var;

#define kByte	* 1024L

extern long	_stksize = 2 kByte;
extern char	*_ltoa();
extern long	modul(const char *);
extern long	set_x(const char *);
extern long	set_y(const char *);
extern long	log_x(const char *);
extern long	log_y(const char *);

typedef struct {
	char	*master_name;
	short	function_type;
	long	(*function_entry)(const char *cmdline);
} func;

func	module_entries[] = {
	{ "SCREEN", 0, modul }
};

#define _MODUL_MAGIC	0x5343524EL	/* 'SCRN' */
#define _MMX_MAGIC	0x4D4D5845L	/* 'MMXE' */

struct head {
	char		*module_description;
	short		module_version;
	short		number_of_functions;
	func		*jump_table;
	long		module_magic;	/* 'SCRN' */
	long		xbra_magic;	/* 'XBRA' */
	long		mmx_magic;	/* 'MMXE' */
	struct head	*next;
	short		jump;
	long		(*this)();
} module_header = {
	"	Master-Module-eXtension for screen setting\r\n"
	"\n"
	"	    Usage		|	Explanation\r\n"
	"	------------------------+--------------------------\r\n"
	"	screen			| show screen parameters\r\n"
	"	screen help		| print this help message\r\n"
	"	screen min_x <num>	| set minimal screen column\r\n"
	"	screen min_y <num>	| set minimal screen line\r\n"
	"	screen max_x <num>	| set maximal screen column\r\n"
	"	screen max_y <num>	| set maximal screen line\r\n",
	1,
	1,
	module_entries,
	_MODUL_MAGIC,
	_XBRA_MAGIC,
	_MMX_MAGIC,
	(struct head *) 0,
	_JMP_OPCODE,
	(long (*)()) 0
};

#define WRITE(f,text)	write(f, text, strlen(text))

static short	initial_max_x;
static short	initial_max_y;
static bool	is_color;
static short	screen_x_offset = 0;
static short	screen_y_offset = 0;
static short	font_x_offset;
static short	font_y_offset;

long
set_x(const char *cmd)
{
	short	x;
	short	old_x;

	old_x = V_CEL_MX;
	if(!*cmd) {
		return(old_x);
	} else {
		x = atoi(cmd);
		x = min(initial_max_x - screen_x_offset,x);
		x = max(0,x);
		V_CEL_MX = x;
		return(0);
	}
}

long
set_y(const char *cmd)
{
	short	y;
	short	old_y;

	old_y = V_CEL_MY;
	if(!*cmd) {
		return(old_y);
	} else {
		y = atoi(cmd);
		y = min(initial_max_y - screen_y_offset,y);
		y = min(initial_max_y - (screen_x_offset != 0),y);
		y = max(0,y);
		V_CEL_MY = y;
		return(0);
	}
}

long
log_x(const char *cmd)
{
	short	x;
	short	old_x;

	old_x = screen_x_offset;
	if(!*cmd) {
		return(old_x);
	} else {
		is_color = (VPLANES > 1);
		x = atoi(cmd);
		if(is_color && (x & 1)) WRITE(2,"Sorry only even values allowed on color screens\r\n");
		x = (x + is_color) & ~is_color;
		x = min(x,initial_max_x & ~is_color);
		x = max(x,0);
		screen_x_offset = x;
		old_x = min(V_CEL_MX + old_x,initial_max_x) - x;
		V_CEL_MX = max(old_x,0);
		V_CEL_MY = min(V_CEL_MY,initial_max_y - (screen_x_offset != 0));
		write(2, "\33H", 2);
		Setscreen(Physbase()+screen_y_offset*VWRAP*font_y_offset+screen_x_offset*font_x_offset,-1,-1);
		Vsync();
		return(0);
	}
}

long
log_y(const char *cmd)
{
	short	y;
	short	old_y;

	old_y = screen_y_offset;
	if(!*cmd) {
		return(old_y);
	} else {
		y = atoi(cmd);
		y = min(y,initial_max_y);
		y = max(y,0);
		V_CEL_MY = min(V_CEL_MY + old_y,initial_max_y - (screen_x_offset != 0)) - y;
		screen_y_offset = y;
		write(2, "\33H", 2);
		Setscreen(Physbase()+screen_y_offset*VWRAP*font_y_offset+screen_x_offset*font_x_offset,-1,-1);
		Vsync();
		return(0);
	}
}

long
modul(const char *cmdlin)
{
	char	output[30];

	if(!*cmdlin) {
		_ltoa(log_x(""), output, 10);
		WRITE(1,output);
		write(1, " ", 1);
		_ltoa(log_y(""), output, 10);
		WRITE(1,output);
		write(1, " ", 1);
		_ltoa(set_x(""), output, 10);
		WRITE(1,output);
		write(1, " ", 1);
		_ltoa(set_y(""), output, 10);
		WRITE(1,output);
		write(1, "\r\n", 2);
		return(0);
	} else if(!strcmp(cmdlin,"help")) {
		WRITE(1,module_header.module_description);
	} else if(cmdlin[5] && cmdlin[5] != ' ') {
		return(1);
	} else if(!strncmp(cmdlin,"min_x",5)) {
		log_x(cmdlin+5);
	} else if(!strncmp(cmdlin,"min_y",5)) {
		log_y(cmdlin+5);
	} else if(!strncmp(cmdlin,"max_x",5)) {
		set_x(cmdlin+5);
	} else if(!strncmp(cmdlin,"max_y",5)) {
		set_y(cmdlin+5);
	}
	strcpy(output,"setenv LINES ");
	_ltoa(V_CEL_MY+1, output+13, 10);
	WRITE(2,output);
	system(output);
	return(0);
}

struct head	*current;

static long	my_cookies[16] = {
	_MODUL_MAGIC, (long) &module_header,
	0L, 8L
};

void
remove_cookies()
{
	set_sysvar_to_long(_p_cookies,0L);
}

void
remove_cookie()
{
	long	*cp;

	if(cp = (long *) get_sysvar(_p_cookies)) {
		while(*cp && *cp != _MODUL_MAGIC) cp += 2;
		while(*cp) {
			cp[0] = cp[2];
			cp[1] = cp[3];
			cp += 2;
		}
	}
}

void
install_cookie(int resident)
{
	long	*cp;
	long	cookies_used = 0;
	long	tmp;

	initial_max_x = V_CEL_MX;
	initial_max_y = V_CEL_MY;
	font_x_offset = VWRAP / (initial_max_x + 1);
	font_y_offset = (V_Y_MAX + 1) / (initial_max_y + 1);
	if(!(cp = (long *) get_sysvar(_p_cookies))) {
		if(!resident) {
			write(2, "Unable to install cookie!\r\n", 27);
			exit(1);
		}
		signal(SIGRESET,remove_cookies);
		set_sysvar_to_long(_p_cookies,(long)(cp = my_cookies));
	}
	while(*cp && *cp != _MODUL_MAGIC) {
		cp += 2;
		cookies_used++;
	}
	if(*cp) {
		current = (struct head *) cp[1];
		return;
	}
	if(!(cp[1] - cookies_used - 1)) {
		write(2, "No room for cookie!\r\n", 21);
		exit(1);
	}
	*cp++ = _MODUL_MAGIC;
	tmp = *cp;
	*cp++ = (long) (current = &module_header);
	*cp++ = 0L;
	*cp = tmp;
	return;
}

void
install_module(int resident, char *firstcmd)
{
	long	keep;

	install_cookie(resident);
	if(master) {
		strcpy(master_var, "Master-Module ");
		_ltoa(current, master_var+14, 10);
	}
	if(current != &module_header) {
		(*(current->jump_table[0].function_entry))(firstcmd);
		exit(0);
	}
	if(!*firstcmd) modul("help");
	else modul(firstcmd);
	if(!resident) return;
	keep = _base->p_tlen + _base->p_dlen + _base->p_blen
		+ sizeof(BASEPAGE);
	_base = _base->p_parent;
	Ptermres(keep, 0);
}

void
remove_module()
{
	remove_cookie();
}

void
usage(char *prog)
{
	write(2, "Usage: [ module ] ", 18);
	WRITE(2, prog);
	write(2, " [ <cmd> ]\r\n", 12);
	exit(2);
}

main(int argc, char *argv[])
{
	char	*mmx;
	BASEPAGE	*caller;
	char	*prog;
	char	*firstcmd;

	if(**argv) prog = *argv;
	else prog = "screen";
	argc--;
	argv++;
	if(argc) {
		firstcmd = *argv;
		argc--;
		argv++;
	} else firstcmd = "";
	if(argc) usage(prog);
	if(mmx = getenv("MMX")) {
		if(strncmp(mmx, "Master-Call", 11)) exit(1);
		mmx += 11;
		if(!(master = (BASEPAGE *) strtol(mmx, &mmx, 10)) ||
		   !(master_var = (char *) strtol(mmx, &mmx, 10)) ||
		   !(caller = (BASEPAGE *) strtol(mmx, &mmx, 10)) ||
		   (caller != _base->p_parent))
			exit(1);
		install_module(1, firstcmd);
	} else if(!system(NULL)) install_module(1, firstcmd);
	install_module(0, firstcmd);
	system("-i");
	remove_module();
}
