/* os_msdos.c -- interface routines for MS-DOS            */
/* Copyright 1996,97    Robert Masenten                   */
/*                                                        */
/* This is part of the source for the (Mostly) Universal  */
/*       AGT Interpreter                                  */

/* This was written to work under Borland C; it hasn't been
 tested with other compilers. */


/* NOTE: The library functions for screen location put the origin
   at (1,1), but curr_x, curr_y, box_startx, and box_starty have
   origin (0,0). */

#define USE_EDITLINE
#define DEBUG_KEY 0  /* Turns on debugging of key functions */

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <limits.h>

#include "agtread.h"
#include "uagt.h"

#define cleareol clreol

static int colorset[5]={7,14,0,15,1};

/* cs_ stands for "color scheme" */
enum {cs_normal=0, cs_bold, cs_back, cs_statfore, cs_statback} color_scheme;
/* normal/highlight/background/statfore/statback */

int scroll_count; /* Count of how many lines we have printed since
		     last player input. */
int status_height;
bool have_compass;
int top_row; /* The absolute y value of the current top row. */


#define STATUS_STATE 0x3007
#define BOX_STATE 0x2007
#define START_STATE 0x0007

/* For state:
     currently low 4 bits are color.
     bit 12 (0x1000) indicates we are printing the status line, so
                       supress blinking
     bit 13 (0x2000) indicates whether we are in reverse video mode or not
     bit 14 (0x4000) indicates whether we are blinking or not 
     bit 15 (0x8000) indicates whether bold is on or not */

#define BOLD_BIT 0x8000
#define BLINK_BIT 0x4000
#define RVID_BIT 0x2000
#define STAT_BIT 0x1000

typedef unsigned short vstate;
static vstate state_stack[16];
int stateptr=0;  /* Points into state stack */

#define cstate state_stack[stateptr]  /* Current state */

static void set_state(vstate state)
{
  int c; /* Color */
  int bkgd; /* Background and blink bits */

  /* 0=normal/1=highlight/2=background/3=statfore/4=statback */

  c=state & 0xF;  /* Extract color */
  if (state & RVID_BIT) {  /* Reverse video */
    bkgd=colorset[cs_statback];
    if (c==7 || (state & STAT_BIT) ) c=colorset[cs_statfore];
  } else 
    bkgd=colorset[cs_back];
  if (state & BOLD_BIT)
    if (c==colorset[cs_normal]) 
      c=colorset[cs_bold];
    else c^=0x08; /* Toggle bold bit */
  if ( (state & BLINK_BIT) && !(state & STAT_BIT) ) bkgd|=0x08; /* Blinking */
  bkgd=(bkgd<<4)|c;
  textattr(bkgd);
  cstate=state;
}

static void push_state(vstate state)
{
  if (stateptr==15) fatal("State stack overflow!");
  stateptr++;
  set_state(state);
}

static void pop_state(void)
{
  if (stateptr==0) fatal("State stack error: POP without PUSH!");
  stateptr--;
  set_state(cstate);
}

void agt_textcolor(int c)
/* Set text color to color #c, where the colors are as follows: */
/*  0=Black, 1=Blue, 2=Green, 3=Cyan, 4=Red, 5=Magenta, 6=Brown, */
/*  7=White("normal"), 8=Blinking.  */
/*  9= *Just* White (not neccessarily "normal" and no need to turn off */
/*      blinking)  */
/* Also used to set other text attributes: */
/* -1=emphasized text, used (e.g.) for room titles */
/* -2=end emphasized text */
{
  vstate nstate;

  nstate=cstate;
  if (c==-1) 
    nstate=nstate|0x8000;
  else if (c==-2) 
    nstate=nstate & ~0x8000; /* BOLD off */
  else if (c==8) 
    nstate=nstate | 0x4000;  /* BLINK on */
  else if (c==7)  
    nstate=colorset[cs_normal];   /* "Normal" */
  else if (c==9)
    nstate=(nstate & ~0xF) | 7; /* Set color to 7: white */
  else if (c>=0 && c<7)
    nstate=(nstate & ~0xF) | c; /* Set color to c */
  set_state(nstate);
}

void agt_delay(int n)
{
  print_statline();
  sleep(n/2);
}

void agt_tone(int hz,int ms)
/* Produce a hz-Hertz sound for ms milliseconds */
{
  if (!sound_on) return;
  sound(hz);
  delay(ms);
  nosound();
}

int agt_rand(int a,int b)
/* Return random number from a to b inclusive */
{
    return a+(rand()>>2)%(b-a+1);
}


/*
   1000= backspace
   1001= delete
   1002= left
   1003= right
   1004= up
   1005= down
   1006= home
   1007= end
   1008= page up
   1009= page down  
   2000= unknown control character
*/

static char keytrans[]={0x00,0x53,  /* Delete and backspace */
			0x4b,0x4d,0x48,0x50, /* Arrow keys */
			0x47,0x4F,  /* Home and End */
			0x49,0x51}; /* PgUp, PgDown */

	
static int saved_tab=0;

int read_a_key(void)
{
  char c;
  int i;

  scroll_count=0;
  if (saved_tab>0) {
    saved_tab--;
    return ' ';
  }
  c=getch();

  if (c!=0) 
    if (isprint(c)) return c;
    else if (c=='\n' || c=='\r') return '\n';
    else if (c=='\t') {
      saved_tab=4;
      return ' ';
    }
    else if (c=='\001')  /* Ctrl-A ==> Home */
      return 1006;
    else if (c=='\005')  /* Ctrl-E ==> End */
      return 1007;
    else if (c=='\010')  /* Ctrl-H ==> Backspace */
      return 1000;
    else if (c=='\013')  /* Ctrl-K */
      return 1500;
    else if (c=='\031') /* Ctrl-Y */
      return 1501;
    else if (c=='\014')  /* Ctrl-L */
      return 1502;
    else return 2000;

  /* Get extended code */
  c=getch();
  for(i=0;i<10;i++)
    if (c==keytrans[i]) return 1000+i;
  return 2000;
}




#ifdef USE_EDITLINE

void agt_putc(char c)
{
#ifdef UNIX
  fix_loc();  /* Fixup if returning from being suspended */
#endif
  printf("%c",c);
  curr_x+=1;
}

static bool old_yheight=0;
static int base_x,base_y; /* Base X value of first character of input */

/* This assumes the cursor is always positioned on the string
   being edited */
static void update_cursor(int cursor)
{
  int curr_y;

  curr_x=base_x+cursor;
  curr_y=base_y;
  while (curr_x>=screen_width) {
    curr_x-=screen_width;
    curr_y++;
  }
  gotoxy(curr_x+1,curr_y+1);
}

/* static bool save_accum_text=0;*/

static void update_line(int cursor,char *buff)
{
  int yval, yheight, xpos, i, curr_y;
  
  yheight=(base_x+strlen(buff)+screen_width-1)/screen_width; /* Round up */
  /* printf("<%d+%d>",base_y,yheight);*/
  gotoxy(screen_width,screen_height);
  while (base_y+yheight+status_height>screen_height && base_y>1) {
    base_y--;
    /*    save_accum_text=1;*/ 
    cputs("\r\n");
    /* save_accum_text=0;*/
  }
  for(yval=yheight;yval<old_yheight;yval++) {
    gotoxy(1,base_y+yval+1);
    cleareol();
  }
  old_yheight=yheight;
  xpos=cursor-1;
  if (xpos<0) xpos=0;
  update_cursor(xpos);
  curr_x=wherex()-1;
  curr_y=wherey()-1;
  for(yval=curr_y-base_y;yval<yheight;yval++) {
    cleareol();
    for(i=curr_x;i<screen_width;i++) {
      if (buff[xpos]==0) break;
      putchar(buff[xpos++]);
    }
    curr_x=0;
    if (base_y+yval+status_height<screen_height) 
      gotoxy(curr_x+1,base_y+yval+2);
    else break;
  }
  update_cursor(cursor);
}

static void redisplay_line(char *buff)
{
  update_line(0,buff);
}

#define ENABLE_HIST
static char **hist=NULL;
static long histcount=0;
static long histmax=10; 

static char *line_edit(int state)
     /* state=0, input a line.
	state=1, input character with echo
	state=2, input character with no echo */
{
  static char *yank_text=NULL;
  bool editmode;
  int key;
  int buffleng, buffspace;  /* buffspace is space allocated for buffer,
			       buffleng is space really used */
  static int cursor; /* Where the insertion point is in buffer:
		 insert at this location */
  char *buff, *savebuff;
  int i, curr_hist;

  buff=rmalloc(2);
  savebuff=NULL;
  buff[0]=buff[1]=0;
  cursor=buffleng=0;buffspace=2;
  base_x=wherex()-1; base_y=wherey()-1;
  editmode=(state==0);
  saved_tab=0;
  old_yheight=0;
  scroll_count=0;

#ifdef UNIX
  set_term_noncanon();
  keyread=keyptr=0;  /* Clear key-buffer */
#endif

  for(;;) {
    key=read_a_key();
    if (DEBUG_KEY) printf("{%d}",key);
    if (key<1000) { /* Real character entered */
      if (state>0) { /* One key */
	if (state==1) {
	  agt_putc(key);
	  agt_newline();
	}
	buff[0]=key; buff[1]=0;
	break;
      } 

#ifdef ENABLE_HIST
      if (savebuff!=NULL) {
	rfree(savebuff);
	buff=rstrdup(buff);
	buffspace=buffleng+1;
      }
#endif
      if (key=='\n') {
	update_cursor(buffleng);      
	break;
      }
      /* add character to buffer */
      buffleng++;
      if (buffleng+1>buffspace) buffspace+=20;
      buff=rrealloc(buff,buffspace);
      for(i=buffleng;i>cursor;i--)
	buff[i]=buff[i-1];      
      buff[cursor++]=key;
      update_line(cursor,buff);
    } 
    else switch(key-1000) 
      { /* Special key */
      case 0: /* Backspace */
	if (!editmode || cursor==0) break;
        cursor--;
	/* Fall through... */
      case 1: /* Delete */
	if (!editmode || buffleng==cursor) break;
#ifdef ENABLE_HIST
	if (savebuff!=NULL) {
	  rfree(savebuff);
	  buff=rstrdup(buff);
	  buffspace=buffleng+1;
	}
#endif
	for(i=cursor;i<buffleng;i++)
	  buff[i]=buff[i+1];
	buffleng--;
	update_line(cursor,buff);
	break;
      case 2: /* Left arrow */
	if (editmode && cursor>0) 
	  update_cursor(--cursor);
	break;
      case 3: /* Right arrow */
	if (editmode && cursor<buffleng)	  
	  update_cursor(++cursor);
	break;
#ifdef ENABLE_HIST
      case 4: /* Up arrow: Command history */	
	if (!editmode || histcount==0) break;
	if (savebuff==NULL) { /* Save current line */
	  savebuff=buff;
	  curr_hist=histcount;
	}
	curr_hist--;
	if (curr_hist<0) {
	  curr_hist=0;	
	  break;
	}
	buff=hist[curr_hist];
	buffleng=strlen(buff);
	cursor=0;
	redisplay_line(buff);
	break;
      case 5: /* Down arrow: Command history */
	if (!editmode || savebuff==NULL) break;
	curr_hist++;
	if (curr_hist>=histcount) { 
	  /* Reached bottom: restore original line */
	  curr_hist=histcount;
	  buff=savebuff;
	  savebuff=NULL;
	} else
	  buff=hist[curr_hist];
	buffleng=strlen(buff);
	cursor=0;
	redisplay_line(buff);
	break;
#endif
      case 6: /* Home */
	if (!editmode) break;
	cursor=0;
	update_cursor(cursor);
	break;
      case 7: /* End */
	if (!editmode) break;
	cursor=buffleng;
	update_cursor(cursor);
	break;
      case 8: /* Page up : Scroll back */
      case 9: /* Page down */
	break;
      case 500: /* Ctrl-K: Delete to EOL */
	if (!editmode) break;
#ifdef ENABLE_HIST
	if (savebuff!=NULL) {
	  rfree(savebuff);
	  buff=rstrdup(buff);
	  buffspace=buffleng+1;
	}
#endif
	rfree(yank_text);
	yank_text=rstrdup(buff+cursor);
	buffleng=cursor;
	buff[buffleng]=0;
	update_line(cursor,buff);
	break;
      case 501:  /* Ctrl-Y: Yank */
	if (!editmode) break;
	{ 
	  int txtleng;
#ifdef ENABLE_HIST
	  if (savebuff!=NULL) {
	    rfree(savebuff);
	    buff=rstrdup(buff);
	    buffspace=buffleng+1;
	  }
#endif
	  txtleng=strlen(yank_text);
	  buffleng+=txtleng;
	  while(buffleng+1>buffspace) buffspace+=20;
	  buff=rrealloc(buff,buffspace);
	  for(i=buffleng;i>=cursor+txtleng;i--)
	    buff[i]=buff[i-txtleng];
	  for(i=0;i<txtleng;i++)
	    buff[cursor+i]=yank_text[i];
	  update_line(cursor,buff);
	  cursor+=txtleng;
	  update_cursor(cursor);
	}
	break;
      case 502: /* Ctrl-L: Redraw screen */
#ifdef UNIX
	agt_clrscr();
	base_y=curr_y;
	printf("%s",accum_text); /* Redraw prompt */
	if (editmode) 
	  redisplay_line(buff);
#endif
	break;
      default: break;
      }
  }
#ifdef UNIX
  set_term_normal();
#endif
  buff=rrealloc(buff,(buffleng+1));
  if (state==0) {
    if (histmax==0 || histcount<histmax) 
      hist=rrealloc(hist,(++histcount)*sizeof(char*));
    else 
      for(i=0;i<histcount-1;i++)
	hist[i]=hist[i+1];
    hist[histcount-1]=rstrdup(buff);
  }
  return buff;
}
#endif


int tmpcnt=0;

char *agt_input(int in_type)
/* read a line from the keyboard, allocating space for it using malloc */
/* in_type: 0=command, 1=number, 2=question, 3=userstr, 4=filename,*/
/* 5=RESTART,RESTORE,UNDO,QUIT */
/*  Negative values are for internal use by the interface (i.e. this module) */
/*  We completely ignore the value of in_type */
{
  static int input_error=0;
  char *s;

  scroll_count=0;
  print_statline();

#ifdef USE_EDITLINE
  s=line_edit(0);
#else
  s=rmalloc(300);
  s[0]=297;
  s[1]=0;
  if (cgets(s)==NULL) { /* I have no idea if cgets _ever_ returns NULL */
    if (++input_error>10) {
      printf("Console read failure.\n");
      exit(1);
    }
  } input_error=0;

  s=rrealloc(s,s[1]+3);  
  memmove(s,s+2,s[1]+1);
  printf("\n");
#endif

  printf("\n");
  if (DEBUG_OUT)
    fprintf(stderr,"%s\n",s);
  if (script_on) fprintf(scriptfile,"%s\n",s);
  curr_x=wherex()-1;
  return s;
}

char agt_getkey(bool echo_char)
/* Reads a character and returns it, possibly reading in a full line
  depending on the platform */
/* If echo_char=1, echo character. If 0, then the character is not
     required to be echoed (and ideally shouldn't be) */
{
  char c;

  scroll_count=0;
  print_statline();

  if (echo_char) 
    c=getche();    /* Get with echo */
  else c=getch();   /* Get w/o echo */
  if (c==0) getch(); /* Throw away extended character */
  curr_x=wherex()-1;
  return c;
}


static void print_compass(void)
{
  int cwidth, i;

  if (status_width< 9+4*12) return;
  gotoxy(1,2);
  cputs("  EXITS: ");cwidth=9;
  for(i=0;i<12;i++)
    if (compass_rose & (1<<i)) {
      cputs(exitname[i]);
      cputs(" ");
      cwidth+=strlen(exitname[i])+1;
    }
  for(i=cwidth;i<status_width;i++) cputs(" ");
}


void agt_statline(const char *s)
/* Output a string at location (x,y), with attributes attr */
/* 0=normal, 1=background(e.g. status line) */
{
   int savex, savey;

   savex=wherex();savey=wherey()+top_row;  /* Save old postion */
   window(1,1,screen_width,screen_height);  /* Set window to whole screen */
   gotoxy(1,1);
   push_state(STATUS_STATE);
   cputs(s);  /* Console put string */
   if (have_compass) {
      top_row=3;
      print_compass();
   } else top_row=2;
   pop_state();
   window(1,status_height+1,screen_width,screen_height);
   /* Exclude status line from window again */
   gotoxy(savex,savey-top_row); /* Return to old postion */
   return;
}


void agt_clrscr(void)
/* This should clear the screen and put the cursor at the upper left
  corner (or one down if there is a status line) */
{
  clrscr();
  curr_x=0;
  if (DEBUG_OUT) fprintf(stderr,"\n\n<CLRSCR>\n\n");
  if (script_on) fprintf(scriptfile,"\n\n\n\n");
  scroll_count=0;
}

bool cursor_wrap;

void agt_puts(const char *s)
{
  int old_x;
  old_x=wherex()-1;
  cputs(s);
  curr_x=wherex()-1;
  if (curr_x<old_x+strlen(s))  /* We wrapped */
    cursor_wrap=1;
  if (DEBUG_OUT) fprintf(stderr,"%s",s);
  if (script_on) fprintf(scriptfile,"%s",s);
}

void agt_newline(void)
{
  if (!cursor_wrap) cputs("\r\n");
  curr_x=0;cursor_wrap=0;
  if (DEBUG_OUT) fprintf(stderr,"\n");
  if (script_on) fprintf(scriptfile,"\n");
  scroll_count++;
  if (scroll_count>=screen_height-status_height-1) {
    cputs("  --MORE--"); /* Notice: no newline */
    agt_getkey(0);
    gotoxy(1,wherey()); /* Move to beginning of line */
    clreol();  /* Clear to end of line: erase the --MORE-- */
  }
}



static unsigned long boxflags;
static int box_startx; /* Starting x of box; starts from 0 */
static int box_width;
static int delta_scroll; /* Amount we are adding to vertical scroll
			    with the box */

static void box_border(char *c)
{
#ifdef DRAW_BORDER 
  agt_puts(c);
#else
  agt_puts(" ");
#endif
}


#define VLINE_STR "\263"  /* 0xB3 or 0xBA */

static void boxrule(int bottom)
/* Draw line at top or bottom of box */
{ 
  int i;
  
  if (bottom) 
    box_border("\300"); /* 0xC0 or 0xC8 */   
  else 
    box_border("\332"); /* 0xDA or 0xC9 */
  for(i=0;i<box_width+2;i++) box_border("\304");  /* 0xC4 or 0xCD */
  if (bottom) 
    box_border("\311");  /* 0xC9 or 0xBC */
  else 
    box_border("\277"); /* 0xBF or 0xBB */
}


static void boxpos(void)
{
  int curr_y;

  curr_y=wherey(); /* == y location + 1 for status line */
  if (curr_y>screen_height) curr_y=screen_height;
  gotoxy(box_startx+1,curr_y+1);
  curr_x=box_startx;
}

void agt_makebox(int width,int height,unsigned long flags)
/* Flags: TB_TTL, TB_BORDER, TB_NOCENT */
{
  int box_starty;

  boxflags=flags;
  box_width=width;
  if (boxflags&TB_BORDER) {  /* Add space for border */
    width+=4;
    height+=2;
  }
  if (boxflags&TB_NOCENT) box_startx=0;
  else box_startx=(screen_width-width)/2; /* Center the box horizontally */
  if (box_startx<0) box_startx=0;

/* Now we need to compute the vertical position of the box */
  if (flags & TB_TTL) { /* Title: centered horizontally */
    box_starty=(screen_height-height)/2;
    if (box_starty+height+8>screen_height) /* Make room for credits */
      box_starty=screen_height-height-8;
    if (box_starty<1) box_starty=1;
    delta_scroll=0;
    scroll_count=0;
  } else {  /* Compute vertical position of non-title box */
    box_starty=wherey()-1-height;
    if (box_starty<1) {
      delta_scroll=1-box_starty;
      box_starty=1;
    } else delta_scroll=0;
  }

  curr_x=box_startx;
  gotoxy(box_startx+1,box_starty+1);

  if (boxflags&TB_BORDER) {
    push_state(BOX_STATE);
    boxrule(0);
    boxpos();
    box_border(VLINE_STR);
    agt_puts(" ");
  }
}

void agt_qnewline(void)
{
  if (boxflags&TB_BORDER) { 
    agt_puts(" ");
    box_border(VLINE_STR);
  }
  boxpos();
  if (boxflags&TB_BORDER) {
    box_border(VLINE_STR);
    agt_puts(" ");
  }
}

void agt_endbox(void)
{
  if (boxflags&TB_BORDER) {
    agt_puts(" ");
    box_border(VLINE_STR);
    boxpos();
    boxrule(1);
    pop_state();
  }
  scroll_count+=delta_scroll;
  agt_newline(); /* NOT agt_qnewline() */
  if ( (boxflags&TB_TTL) )
     while(wherey()+1<screen_height-8) agt_newline();
}



const char *colorname[16]=
{"black","blue","green","cyan","red","magenta","brown","lightgray",
 "darkgray","lightblue","lightgreen","lightcyan","lightred","lightmagenta",
"yellow","white"};

static int parse_color(int index,char *cname)
{
  int i;
  for(i=0;i<16;i++)
    if(strcasecmp(colorname[i],cname)==0) return i;
  return colorset[index];
}


static void getcolors(int cnum,char *clist[])
{
  int i;

  if (cnum>5) cnum=5;
  for(i=0;i<cnum;i++)
    colorset[i]=parse_color(i,clist[i]);
  colorset[cs_back]&=0x7;
  colorset[cs_statback]&=0x7;
}

#define opt(s) (!strcmp(optstr[0],s))

bool agt_option(int optnum,char *optstr[],bool setflag)
/* If setflag is 0, then the option was prefixed with NO_ */
{
  if (optnum==0) return 1;
  if (strcasecmp(optstr[0],"COMPASS")==0) {
    have_compass=1;
    status_height=2;
    return 1;
  }
  if (strcasecmp(optstr[0],"COLORS")==0) {
    getcolors(optnum-1,optstr+1);
    return 1;}
  if (strcasecmp(optstr[0],"HISTORY")==0) {
    histmax=strtol(optstr[1],NULL,10);
    if (histmax<0) histmax=0;
    return 1;
  }
  return 0;
}

#undef opt

static char *progname;
static const char *cfgname="agil.cfg";

FILE *agt_globalfile(int fid)
{
  char *s,*t;
  FILE *f;

  if (fid==0 && progname!=NULL) {
    s=rstrdup(progname);
    t=s+strlen(s);
    while (t!=s && *t!='\\' && *t!=':') t--; 
    if (*t!=0) t++;
    *t=0;
    s=rrealloc(s,strlen(s)+strlen(cfgname)+1);
    strcat(s,cfgname);
    f=fopen(s,"rb");
    rfree(s);
    return f;
  }
  return NULL;
}


int save_mode;

void init_interface(char *gamename,int argc,char *argv[])
{
  struct text_info term_data;

  if (argc>0) progname=argv[0]; else progname=NULL;
  script_on=0;scriptfile=NULL;
  scroll_count=0;
  cursor_wrap=0;
  have_compass=0;status_height=1;
  center_on=par_fill_on=0;
  DEBUG_OUT=0;debugfile=stderr;
  gettextinfo(&term_data);
  save_mode=term_data.currmode;
  textmode(C80);  /* Change to color, 80 column mode */
  screen_height=25;  /* Assume PC dimensions */
  status_width=screen_width=80;
  clrscr();
  window(1,1,screen_width,screen_height);
  top_row=1;
  gotoxy(1,1); /* Upper-left hand corner of just defined window */
}

void start_interface(void)
{
  srand(time(0));
  clrscr();
  window(1,status_height+1,screen_width,screen_height);
  curr_x=0;scroll_count=0;
  top_row=status_height+1;
  set_state(7);
}

void close_interface(void)
{
  if (scriptfile!=NULL)
    fclose(scriptfile);
  textattr(7);
  textmode(save_mode);
  clrscr();
}

