/*  

    YABASIC ---  a simple Basic Interpreter
    written by Marc-Oliver Ihm 1995-2004
    homepage: www.yabasic.de
    
    io.c --- code for screen and file i/o
    
    This file is part of yabasic and  may be copied only 
    under the terms of either the Artistic License or 
    the GNU General Public License (GPL), both of which 
    can be found at www.yabasic.de

*/


/* ------------- defines ---------------- */

#ifdef WINDOWS
#define WM_WANTKEY (WM_APP+7)
#endif

/* ------------- includes ---------------- */

#ifndef YABASIC_INCLUDED
#include "yabasic.h"       /* all prototypes and structures */
#endif


/* ------------- local defines ----------------- */

#define YC_BLACK 0
#define YC_WHITE 1
#define YC_RED 2
#define YC_BLUE 3
#define YC_GREEN 4
#define YC_YELLOW 5
#define YC_CYAN 6
#define YC_MAGENTA 7

#ifndef COLOR_BLACK
#define COLOR_BLACK	0
#define COLOR_RED	1
#define COLOR_GREEN	2
#define COLOR_YELLOW	3
#define COLOR_BLUE	4
#define COLOR_MAGENTA	5
#define COLOR_CYAN	6
#define COLOR_WHITE	7
#endif
#ifndef A_COLOR
#define A_COLOR 0xff00
#endif

/* ------------- external references ---------------- */

extern int mylineno;   /* current line number */
extern int yyparse();  /* call bison parser */


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

static int onechar(void); /* read one char from currentinstream */
static void backchar(int); /* put char back into stream */
static void readline(void); /* read one line from current stream */
static void curinit(void); /* initialize curses */
static void initcol(void); /* initialize curses colors */
void myswitch(int); /* switch to specified stream */
int checkstream(void);  /* test if currst is still valid */
#ifdef WINDOWS
static DWORD keythread(LPWORD); /* wait for key input from console */
static int is_valid_key(INPUT_RECORD *); /* check if input rec is valid key */
#endif
int name2yc(char *); /* convert a color name to an integer */
int yc2oc(int,int); /* convert a yabasic color to operating system color */
char *yc2short(int); /* convert yabasic colours to short colour name */
#ifdef UNIX
int oc2yc(int); /* convert an operating system color to yabasic color */
#endif

/* ------------- global variables ---------------- */

static int prompted; /* TRUE, if prompt is fresh */
int read_controls; /* TRUE, if input should read control characters */
FILE *streams[FOPEN_MAX]; /* file streams */
int stream_modes[FOPEN_MAX]; /* modes for streams */
int lprstream=-1; /* stream associated with lineprinter */
static int currstr=STDIO_STREAM; /* currently switched stream */
static FILE *cinstr; /* current stream for input */
static FILE *coutstr; /* current stream for output */
static char linebuffer[INBUFFLEN]; /* buffer for one line of input */
int curinized=FALSE; /* true, if curses has been initialized */
static char *currchar; /* current char to read */
static short stdfc; /* standard foreground color of window */
static short stdbc; /* standard background color of window */
#ifdef UNIX
int winpid=-1; /* pid of process waiting for window keys */
int termpid=-1; /* pid of process waiting for terminal keys */
FILE *lineprinter=NULL; /* handle for line printer */
#ifndef BUILD_NCURSES
int COLS = 80;
int LINES = 25;
#endif 
#else
HANDLE wantkey=INVALID_HANDLE_VALUE; /* mutex to signal key desire */
HANDLE gotkey=INVALID_HANDLE_VALUE; /* mutex to signal key reception */
HANDLE wthandle=INVALID_HANDLE_VALUE; /* handle of win thread */
HANDLE kthandle=INVALID_HANDLE_VALUE; /* handle of inkey thread */
DWORD ktid; /* id of inkey thread */
int LINES=0; /* number of lines on screen */
int COLS=0; /* number of columns on screen */
HANDLE ConsoleInput; /* handle for console input */
HANDLE ConsoleOutput; /* handle for console output */
HANDLE lineprinter=INVALID_HANDLE_VALUE; /* handle for line printer */
#endif


/* ------------- functions ---------------- */

void create_print(char type) /* create command 'print' */
{
  struct command *cmd;
  
  cmd=add_command(cPRINT,NULL);
  cmd->pointer=my_malloc(sizeof(int));
  /* store type of print  */
  cmd->tag=type;
}


void print(struct command *cmd) /* print on screen */
{
  int type;
  struct stackentry *p,*q,*r;
  static int last='n';
  char *s;
  int x,y;
  long int n;
  double d;
#ifdef WINDOWS
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  SMALL_RECT screen;
  COORD to;
  CHAR_INFO ci;
#endif
  
  r=NULL;
  type=cmd->tag;
  if (!checkstream()) return;
  switch(type) {
  case 'n':  /* print newline */
    if (curinized && coutstr==stdout) {
#ifdef WINDOWS
      GetConsoleScreenBufferInfo(ConsoleOutput,&csbi);
      x=csbi.dwCursorPosition.X;
      y=csbi.dwCursorPosition.Y;
      if (y>=LINES-1) {
	screen.Left=0;
	screen.Right=COLS;
	screen.Top=1;
	screen.Bottom=LINES;
	to.X=0;
	to.Y=0;
	ci.Char.AsciiChar=' ';
	ci.Attributes=FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED;
	ScrollConsoleScreenBuffer(ConsoleOutput,&screen,NULL,to,&ci);
	to.X=0;
	to.Y=y;
      } else {
	to.X=0;
	to.Y=y+1;
      }
      SetConsoleCursorPosition(ConsoleOutput,to);
      break;
#else
#ifdef BUILD_NCURSES
      getyx(stdscr,y,x);
      if (y>=LINES-1) {
        scrl(1);
        y=y-1;
      }
      move(y+1,0);
      refresh();
#endif
      break;
#endif
    } else {
      string[0]='\n';
      if (abs(currstr)==lprstream) {
	string[1]='\r';
	string[2]='\0';
      } else {
	string[1]='\0';
      }
    }
    onestring(string);
    break;
  case 't':  /* print tab */
    string[0]='\t';
    string[1]='\0';
    onestring(string);
    break;
  case 'd':  /* print double value */
    p=pop(stNUMBER);
    d=p->value;
    n=(int)d;
    if (n==d && d<LONG_MAX && d>LONG_MIN)
      sprintf(string,"%s%ld",(last=='d')?" ":"",n);
    else
      sprintf(string,"%s%g",(last=='d')?" ":"",d);
    onestring(string);
    break;
  case 'U':
    r=pop(stSTRING);    
  case 'u':  /* print using */
    p=pop(stSTRING);
    q=pop(stNUMBER);
    type='d';
    s=string;
    if (last=='d') {
      *s=' ';
      s++;
    }
    if (!myformat(s,q->value,p->pointer,r?r->pointer:NULL)) {
      sprintf(string,"'%s' is not a valid format",(char *)p->pointer);
      error(ERROR,string);
      break;
    }
    onestring(string);
    break;
  case 's': 
    p=pop(stSTRING);
    onestring((char *)p->pointer);
    break;
  }
  last=type;
}


void mymove() /* move to specific position on screen */
{
#ifdef BUILD_NCURSES
  int x,y;
#ifdef WINDOWS
  COORD coord;
#endif
  
  y=(int)pop(stNUMBER)->value;
  if (y<0) y=0;
  if (y>LINES-1) y=LINES-1;
  x=(int)pop(stNUMBER)->value;
  if (x<0) x=0;
  if (x>COLS-1) x=COLS-1;
  if (!curinized) {
    error(ERROR,"need to call 'clear screen' first");
    return;
  }
#ifdef UNIX
  move(y,x);
  refresh();
#else
  coord.X=x;
  coord.Y=y;
  SetConsoleCursorPosition(ConsoleOutput,coord);
#endif
#endif
}


void clearscreen() /* clear entire screen */
{
#ifdef WINDOWS
  DWORD written; /* number of chars actually written */
  COORD coord; /* coordinates to start writing */
#endif

  if (!curinized) curinit();
#ifdef UNIX
#ifdef BUILD_NCURSES
  clear();
  refresh();
#else
    error(ERROR,"ncurses support was not compiled!");
#endif
#else
  coord.X=0;
  coord.Y=0;
  FillConsoleOutputCharacter(ConsoleOutput,' ',LINES*COLS,coord,&written);
  FillConsoleOutputAttribute(ConsoleOutput,
			     FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE,
			     LINES*COLS,
			     coord,&written);
  SetConsoleCursorPosition(ConsoleOutput,coord);
#endif
}


static void curinit(void) /* initialize curses */
{
#ifdef WINDOWS
  CONSOLE_SCREEN_BUFFER_INFO coninfo; /* receives console size */
#endif
  
#ifdef UNIX
#ifdef BUILD_NCURSES
  initscr();
  initcol();
  setscrreg(0,LINES);
  scrollok(stdscr,TRUE);
  leaveok(stdscr,TRUE);
  keypad(stdscr,TRUE);
  intrflush(stdscr,FALSE);
  curs_set(0);
  def_prog_mode();
#endif
#else
  GetConsoleScreenBufferInfo(ConsoleOutput,&coninfo);
  COLS=coninfo.dwSize.X;
  LINES=coninfo.dwSize.Y;
#endif
  curinized=TRUE;
}


char *inkey(double maxtime) /* get char from keyboard */
{
  char *skey;
#ifdef WINDOWS
  int ms; /* number of milliseconds to wait*/
  DWORD oflags; /* saves normal state of console input buffer */
  DWORD flags; /* new input mode for console input buffer */
  static char conkeybuff[100],winkeybuff[100];
#else
  fd_set readfds;
  struct timeval tv;
  int maxfd;
  int winfd[2],termfd[2];
  char retkey[100];
  int status,ret;
#endif
  
  
  if (maxtime>=0.0 && maxtime<0.01) maxtime=0.01;
  if (!curinized) {
    error(ERROR,"need to call 'clear screen' first");
    return my_strdup("");
  }
#ifdef UNIX
#ifdef BUILD_NCURSES
  retkey[0]='\0';
  winfd[0]=winfd[1]=termfd[0]=termfd[1]=-1;
  winpid=termpid=-1;
  if (pipe(winfd) || pipe(termfd)) {
    error(ERROR,"Couldn't open pipes");
    goto sane_state;
  }
  
  winpid=fork();
  if (winpid==0) {
    /* this is the child */
    signal(SIGINT,SIG_DFL);
    signal(SIGFPE,SIG_DFL);
    signal(SIGSEGV,SIG_DFL);
    retkey[0]='\0';
#ifndef BEOS
    if (winopened) getwinkey(retkey);
#endif
    if (*retkey) write(winfd[1],retkey,strlen(retkey));
    wait(&status);
    exit(0);
  } else if (winpid==-1) {
    error(ERROR,"couldn't fork child");
    goto sane_state;
  }
  
  termpid=fork();
  if (termpid==0) {
    /* this is the child */
    signal(SIGINT,SIG_DFL);
    signal(SIGFPE,SIG_DFL);
    signal(SIGSEGV,SIG_DFL);
    retkey[0]='\0';
    noecho();
    cbreak();
    timeout(-1);
    gettermkey(retkey);
    if (*retkey) write(termfd[1],retkey,strlen(retkey));
    wait(&status);
    exit(0);
  } else if (termpid==-1) {
    error(ERROR,"Couldn't fork child");
    goto sane_state;
  }
    
  FD_ZERO(&readfds);
  FD_SET(termfd[0],&readfds);
  FD_SET(winfd[0],&readfds);
  fflush(stdout);
  maxfd=(termfd[0]>winfd[0])?termfd[0]:winfd[0];
  if (maxtime>=0) {
    tv.tv_sec=(int)maxtime;
    tv.tv_usec=(maxtime-(int)maxtime)*1000000.;
    ret=select(maxfd+1,&readfds,NULL,NULL,&tv);
  } else {
    ret=select(maxfd+1,&readfds,NULL,NULL,NULL);
  }
  if (ret==-1) {
    error(ERROR,"select failed");
    goto sane_state;
  } 
  
  if (FD_ISSET(termfd[0],&readfds)) {
    ret=read(termfd[0],retkey,100);
  } else if (FD_ISSET(winfd[0],&readfds)) {
    ret=read(winfd[0],retkey,100);
    // if (!strncmp("MB",retkey,2)) getmousexybm(retkey,&mousex,&mousey,&mouseb,&mousemod);
  }
  else ret=0;
  retkey[ret]='\0';
 sane_state:
  skey=retkey;
  if (winfd[0]>0) close(winfd[0]);
  if (winfd[1]>0) close(winfd[1]);
  if (termfd[0]>0) close(termfd[0]);
  if (termfd[1]>0) close(termfd[1]);
  if (termpid>0) {
    kill(termpid,SIGTERM);
    waitpid(termpid,&status,0);
    termpid=-1;
  }
  if (winpid>0) {
    kill(winpid,SIGTERM);
    waitpid(winpid,&status,0);
    winpid=-1;
  }
  reset_prog_mode(); /* prepare for input afterwards */
#endif  
#elif WINDOWS
  /* create event to signal key */
  if (gotkey==INVALID_HANDLE_VALUE) gotkey=CreateEvent(NULL,FALSE,FALSE,NULL);
  if (wantkey==INVALID_HANDLE_VALUE) wantkey=CreateEvent(NULL,TRUE,FALSE,NULL);
  
  conkeybuff[0]=winkeybuff[0]='\0';
  
  GetConsoleMode(ConsoleInput,&oflags);
  flags=oflags&~(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT);
  SetConsoleMode(ConsoleInput,flags);
  
  /* create thread to observe console */
  if (kthandle==INVALID_HANDLE_VALUE) {
    kthandle=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)keythread,
			  (LPVOID)conkeybuff,0,(LPDWORD)&ktid);
  }
  /* signal, that we want a key */
    if (winopened) {
      SendMessage(window,WM_WANTKEY,0,(LPARAM)winkeybuff);
    }
    SetEvent(wantkey);
    ms=(int)(1000*maxtime);
    if (ms<0)
      WaitForSingleObject(gotkey,INFINITE);
    else 
      WaitForSingleObject(gotkey,ms);
    
    ResetEvent(wantkey);
    SetConsoleMode(ConsoleInput,oflags);
    if (*winkeybuff) 
      skey=winkeybuff;
    else
      skey=conkeybuff;
#endif
    return my_strdup(skey);
}


#ifdef WINDOWS
static DWORD keythread(LPWORD lparam) /* wait for key input from console */
{
  INPUT_RECORD inrec; /* for reading key-event */
  int key,skey;
  int num;
  char *keybuff;
  HANDLE evn[2];
  
  keybuff=(char *)lparam;
  keybuff[0]='\0';
  evn[0]=wantkey;
  evn[1]=ConsoleInput;
  do {
    do {
      do {
        WaitForMultipleObjects(2,evn,TRUE,INFINITE);
        ReadConsoleInput(ConsoleInput,&inrec,1,&num);
      } while(!is_valid_key(&inrec));	
      if (isprint(inrec.Event.KeyEvent.uChar.AsciiChar)) {
        keybuff[0]=inrec.Event.KeyEvent.uChar.AsciiChar;
        keybuff[1]='\0';
      } else {
        key=inrec.Event.KeyEvent.wVirtualKeyCode;
        skey=-1;
        switch(key) {
        case 0x1b: skey=kESC;break;
        case 0x0d: skey=kENTER;break;
        case 0x09: skey=kTAB;break;
        case 0x21: skey=kSCRNUP;break;
        case 0x22: skey=kSCRNDOWN;break;
        case 0x70: skey=kF1;break;
        case 0x71: skey=kF2;break;
        case 0x72: skey=kF3;break;
        case 0x73: skey=kF4;break;
        case 0x74: skey=kF5;break;
        case 0x75: skey=kF6;break;
        case 0x76: skey=kF7;break;
        case 0x77: skey=kF8;break;
        case 0x78: skey=kF9;break;
        case 0x79: skey=kF10;break;
        case 0x7a: skey=kF11;break;
        case 0x7b: skey=kF12;break;
        case 0x24: skey=kHOME;break;
        case 0x23: skey=kEND;break;
        case 0x2d: skey=kINS;break;
        case 0x2e: skey=kDEL;break;
        case 0x08: skey=kBACKSPACE;break;
        case 0x27: skey=kRIGHT;break;
        case 0x25: skey=kLEFT;break;
        case 0x28: skey=kDOWN;break;
        case 0x26: skey=kUP;break;
        default:
          sprintf(keybuff,"key%x",key);
        }
        if (skey>0) strcpy(keybuff,ykey[skey]);
      }
      if (!keybuff[0]) printf("Loop !\n");
    } while(!keybuff[0]);
    ResetEvent(wantkey);
    SetEvent(gotkey);
  }while(TRUE);
  
  return 0;
}


static int is_valid_key(INPUT_RECORD *rec) /* check if input rec contains valid key */
{
  if (rec->EventType!=KEY_EVENT || 
    !rec->Event.KeyEvent.bKeyDown ||
    rec->Event.KeyEvent.wVirtualKeyCode==VK_SHIFT ||
    rec->Event.KeyEvent.wVirtualKeyCode==VK_CONTROL) {
    return FALSE;
  }
  return TRUE;
}
#endif


char *replace(char *string) /* replace \n,\a, etc. */
{
  char *from,*to;
  char *p;
  int val;
  static char *hexdigits="0123456789abcdef";

  from=to=string;
  while(*from) {
    if (*from=='\\') {
      from++;
      switch(*from) {
      case 'n':	*to='\n';break;
      case 't':	*to='\t';break;
      case 'v':	*to='\v';break;
      case 'b':	*to='\b';break;
      case 'r':	*to='\r';break;
      case 'f':	*to='\f';break;
      case 'a':	*to='\a';break;
      case '\\': *to='\\';break;
      case '\?': *to='\?';break;
      case '\'': *to='\'';break;
      case '\"': *to='\"';break;
      case 'x':
	val=0;
	if ((p=strchr(hexdigits,tolower(*(from+1)))) && p-hexdigits<16) {
	  from++;
	  val=p-hexdigits;
	  if ((p=strchr(hexdigits,tolower(*(from+1)))) && p-hexdigits<16) {
	    from++;
	    val*=16;
	    val+=p-hexdigits;
	  }
	}
	*to=(char)val;
	break;
      default:
        *to='\\';
        to++;
        *to=*from;
      }
    }
    else
      *to=*from;
    from++;
    to++;
  }
  *to='\0';
  return string;
}


void create_myopen(int num) /* create command 'myopen' */
{
  struct command *cmd;
  
  cmd=add_command(cOPEN,NULL);
  cmd->tag=num;
}


void myopen(struct command *cmd) /* open specified file for given name */
{
#ifdef WINDOWS
  char PrinterName[200]; /* Name of default Printer */
  char *n; /* points into PrinterName */  
  DOC_INFO_1 di;
#endif
  FILE *handle=NULL;
  int stream,i;
  char *name=NULL;
  char *mode=NULL;
  char **pmode;
  static char *valid_modes[]={"r+","r","w","a","rb","wb","ab",""};
  static int smodes[]={smREADWRITE,smREAD,smWRITE,smWRITE,smREAD,smWRITE,smWRITE,smREAD};
  int smode;
  
  struct stackentry *p;
  int has_mode,has_stream,printer=0;
  /* decode cmd->tag */
  has_stream=cmd->tag&OPEN_HAS_STREAM;
  has_mode=cmd->tag&OPEN_HAS_MODE;
  /* printer=cmd->tag&OPEN_PRINTER;*/
	
  if (has_mode)
    mode=my_strdup(pop(stSTRING)->pointer);
  else 
    mode=printer ? my_strdup("w") : my_strdup("r");
  if (printer) 
    name=my_strdup("/usr/bin/lpr");
  else 
    name=my_strdup(pop(stSTRING)->pointer);

  if (has_stream) {
    stream=(int)pop(stNUMBER)->value;
  } else { 
    stream=0;
    for(i=1;i<FOPEN_MAX-4;i++) {
      if (stream_modes[i]==smCLOSED) {
	stream=i;
	break;
      }
    }
    if (!stream) {
      sprintf(errorstring,"reached maximum number of open files");
      errorcode=5;
      goto open_done;
    }
  }

  p=push();
  p->value=0.;
  p->type=stNUMBER;

/*
  if (printer && print_to_file) {
    sprintf(errorstring,"cannot open printer: already printing grafics");
    errorcode=6;
    goto open_done;
  }*/

  if (badstream(stream,1)) {
    sprintf(errorstring,"invalid stream number %d",stream);
    errorcode=9;
    goto open_done;
  }
  if (stream_modes[stream]!=smCLOSED) {
    sprintf(errorstring,"stream already in use");
    errorcode=2;
    goto open_done;
  }

  smode=0;
  for(pmode=valid_modes;**pmode;pmode++) {
    if (!strcmp(*pmode,mode)) break;
    smode++;
  }
  if (!**pmode) {
    sprintf(errorstring,"\'%s\' is not a valid filemode",mode);
    errorcode=3;
    goto open_done;
  }

  if (printer) {
#ifdef UNIX
    lineprinter=popen(name,"w");
    if (!lineprinter) {
      sprintf(errorstring,"could not open line printer");
      errorcode=7;
      goto open_done;
    }
#else
    /* query win.ini for default printer */
    GetProfileString("windows","device",",,,",PrinterName,200);
    
    /* truncate printer name */
    n=PrinterName;
    while(*n && *n!=',') n++;
    *n='\0';
    
    OpenPrinter(PrinterName,&lineprinter,NULL);  
    di.pDocName="yabasic text";
    di.pOutputFile=(LPTSTR)NULL;
    di.pDatatype="RAW";
    if (!StartDocPrinter(lineprinter,1,(LPBYTE)&di)) {
      sprintf(errorstring,"could not open line printer");
      errorcode=7;
      goto open_done;
    }
    StartPagePrinter(lineprinter);
#endif
    lprstream=stream;
  } else {
    handle=fopen(name,mode);
    if (handle==NULL) {
      sprintf(errorstring,"could not open '%s': %s",name,my_strerror(errno));
      errorcode=4;
      goto open_done;
    }
    streams[stream]=handle;
  }
  stream_modes[stream]=smodes[smode];
  errorcode=0;
  p->value=stream;
 open_done:
  if (name) my_free(name);
  if (mode) my_free(mode);
}


void checkopen(void) /* check, if open has been sucessfull */
{
  double result;
  
  result=pop(stNUMBER)->value;
  if (result<=0) {
    error(ERROR,errorstring);
  } 
}


void myclose(void) /* close the specified stream */
{
  int s;
#ifdef WINDOWS
  DWORD written;
#endif
  
  s=(int)pop(stNUMBER)->value;
  if (abs(s)==STDIO_STREAM || badstream(s,0)) return;
  if (stream_modes[s]==smCLOSED) {
    sprintf(string,"stream %d already closed",s);
    error(WARNING,string);
    return;
  }
  if (s==lprstream) {
#ifdef UNIX
    pclose(lineprinter);
#else
    WritePrinter(lineprinter,"\f",2,&written);
    EndPagePrinter(lineprinter);
    EndDocPrinter(lineprinter);
    ClosePrinter(lineprinter);
    lineprinter=INVALID_HANDLE_VALUE;
#endif
    lprstream=-1;
  } else {
    fclose(streams[s]);
  }
  streams[s]=NULL;
  stream_modes[s]=smCLOSED;
}


void myseek(struct command *cmd) /* reposition file pointer */
{
  int s,p,m,i;
  struct stackentry *pp;
  char *mode;

  if (cmd->type==cSEEK2) 
    mode=(char *)my_strdup(pop(stSTRING)->pointer);
  else
    mode=my_strdup("begin");
  p=(int)pop(stNUMBER)->value;
  s=(int)pop(stNUMBER)->value;
  pp=push();
  pp->value=0.;
  pp->type=stNUMBER;
  for(i=0;mode[i];i++) mode[i]=tolower(mode[i]);
  if (!strcmp(mode,"begin")) {
    m=SEEK_SET;
  } else if (!strcmp(mode,"end")) {
    m=SEEK_END;
  } else if (!strcmp(mode,"here")) {
    m=SEEK_CUR;
  } else {
    sprintf(errorstring,"seek mode '%s' is none of begin,end,here",mode);
    errorcode=12;
    my_free(mode);
    return;
  }
  my_free(mode);
  if (abs(s)==STDIO_STREAM || badstream(s,0)) return;
  if (!(stream_modes[s] & (smREAD | smWRITE | smREADWRITE))) {
    sprintf(errorstring,"stream %d not open",s);
    errorcode=11;
    return;
  }
  if (fseek(streams[s],(long)p,m)) {
    sprintf(errorstring,"could not position stream %d to byte %d",s,p);
    errorcode=10;
    return;
  }
  pp->value=1.0;
}


void create_pps(int type,int input) /* create command pushswitch or popswitch */
{
  struct command *cmd;
  
  cmd=add_command(type,NULL);
  cmd->args=input;
}


void push_switch(struct command *cmd) /* push current stream on stack and switch to new one */
{
  static int oldstream=STDIO_STREAM;
  struct stackentry *s;
  int stream;

  stream=(int)pop(stNUMBER)->value;
  if (badstream(stream,0)) return;
  if (!cmd->args) stream=-stream;

  s=push();
  s->type=stNUMBER;
  s->value=oldstream;
  if (infolevel>=DEBUG) {
    sprintf(string,"pushing %d on stack, switching to %d",oldstream,stream);
    error(DEBUG,string);
  }
  oldstream=stream;
  myswitch(stream);
}


void pop_switch(void) /* pop current stream from stack and switch to it */
{
  int stream;

  stream=(int)pop(stNUMBER)->value;
  if (infolevel>=DEBUG) {
    sprintf(string,"popping %d from stack, switching to it",stream);
    error(DEBUG,string);
  }
  myswitch(stream);
}


void myswitch(int stream) /* switch to specified stream */
{
  int stdio,input;

  stdio=(abs(stream)==STDIO_STREAM);
  input=(stream>0);
  currstr=stream;
  if (stream<0) stream=-stream;
  if (badstream(stream,0)) return;

  if (stdio) {
    cinstr=stdin;
    coutstr=stdout;
  } else {
    cinstr=coutstr=NULL;
    if (input) 
      cinstr=streams[stream];
    else
      coutstr=streams[stream];
  }
}


int checkstream(void)  /* test if currst is still valid */
{
  int stdio,input;

  stdio=(abs(currstr)==STDIO_STREAM);
  input=(currstr>0);

  if (!stdio) {
    if (input && !(stream_modes[abs(currstr)] & smREAD| smREADWRITE)) {
      sprintf(string,"stream %d not open for reading",abs(currstr));
      error(ERROR,string);
      return FALSE;
    }
    if (!input && !(stream_modes[abs(currstr)] & (smWRITE | smPRINT))) {
      sprintf(string,"stream %d not open for writing or printing",abs(currstr));
      error(ERROR,string);
      return FALSE;
    }
  }
  return TRUE;
}


void testeof(struct command *cmd) /* close the specified stream */
{
  int s,c;
  struct stackentry *result;
  
  s=(int)pop(stNUMBER)->value;
  if (s!=STDIO_STREAM && badstream(s,0)) return;
  result=push();
  result->type=stNUMBER;
  if (s && !(stream_modes[s] & smREAD)) {
    result->value=1.;
    return;
  }
  if (!s) {
    result->value=0.;
    return;
  }
  c=getc(streams[s]);
  if (c==EOF) {
    result->value=1.;
    return;
  }
  
  result->value=0.;
  ungetc(c,streams[s]);
  return;
}


int badstream(int stream,int errcode) /* test for valid stream id */
{
  if (stream!=STDIO_STREAM && (stream>FOPEN_MAX-4 || stream<=0)) {
    sprintf(errcode?errorstring:string,"invalid stream: %d (can handle only streams from 1 to %d)",stream,FOPEN_MAX-4);
    if (errcode) 
      errorcode=errcode;
    else
      error(ERROR,string);
    return TRUE;
  }
  return FALSE;
}


void create_myread(char type,int tileol) /* create command 'read' */
{
  struct command *cmd;
  
  cmd=add_command(cREAD,NULL);
  cmd->args=tileol; /* true, if read should go til eol */
  cmd->tag=type; /* can be 'd' or 's' */
}


void myread(struct command *cmd) /* read string or double */
{
  double d;
  static char buffer[INBUFFLEN]; /* buffer with current input */
  int numread; /* number of bytes read */
  int tileol; /* true, if read should go til end of line */
  struct stackentry *s;
  int currch; /* current character */
  
  numread=0; /* no chars read'til now */
  buffer[0]='\0';
  tileol=cmd->args;
  
  /* skip leading whitespace */
  if (!tileol) {
    do {
      currch=onechar();
    } while(currch==' ' || currch=='\t');
    /* put back last char */
    if (currch!=EOF && currch!='\0') backchar(currch);
    if (currch=='\0' || currch==EOF) goto done;
  }      
  
  /* read chars */
  do {
    currch=onechar();
    buffer[numread]=currch;
    numread++;
  } while(((tileol && currch!='\0') ||
	   (!tileol && currch!=' ' &&
	    currch!='\t' && currch!='\0')) &&
	  currch!=EOF && numread<INBUFFLEN);
  
  /* put back last char */
  if (currch!=EOF && currch!='\0') backchar(currch);
  /* and remove it from buff */
  if (currch!=EOF) numread--;
  buffer[numread]='\0';
  if (currch=='\0' || currch==EOF) goto done;
  
  /* skip trailing whitespace */
  if (!tileol) {
    do {
      currch=onechar();
    } while(currch==' ' || currch=='\t');
    if (currch!=EOF && currch!='\0') backchar(currch);
  }
  
done:
  if (cmd->tag=='s') { /* read string */
    s=push();
    s->type=stSTRING;
    s->pointer=my_strdup(buffer);}
  else { /* read double */
    s=push();
    s->type=stNUMBER;
    s->value=0.0;
    if (buffer[0] && (sscanf(buffer,"%lf",&d)==1)) s->value=d;
  }
}


static void readline(void) /* read one line from current stream */
{  
#ifdef UNIX
  char *nl; /* position of newline */
  int x,y;
#else
  int read;
#endif
  
  if (!checkstream()) return;
  linebuffer[0]='\0';
#ifdef UNIX
  if (curinized && cinstr==stdin) {
  	;
  #ifdef BUILD_NCURSES
    getyx(stdscr,y,x);
#ifdef HAVE_GETNSTR
    getnstr(linebuffer,INBUFFLEN);
#else
    getstr(linebuffer);
#endif
    if ((nl=strchr(linebuffer,'\0'))) {
      *nl='\n';
      *(nl+1)='\0';
    }
    if (y>=LINES-1) scrl(1);
    refresh();
  #endif
  }
#else
  if (curinized && cinstr==stdin) {
  	;
  #ifdef BUILD_NCURSES
    FlushConsoleInputBuffer(ConsoleInput);
    ReadConsole(ConsoleInput,linebuffer,INBUFFLEN,&read,NULL);
    if (read>=2) {
      linebuffer[read-2]='\n';
      linebuffer[read-1]='\0';
    }
  #endif
  }
#endif
  else {
    fgets(linebuffer,INBUFFLEN,cinstr);
  }
  currchar=linebuffer;
  prompted=FALSE;
}


static int onechar() /* read one char from cinstr */
{
  int ch;
  
  if (!checkstream()) return '\0';
  if (cinstr==stdin) {
    if (!currchar || !*currchar) {
      readline();
    }
    do {
      ch=*currchar;
      currchar++;
    } while(! (!iscntrl(ch) || strchr(" \t\n",ch) || read_controls || ch=='\0'));
  } else {
    do {
      ch=fgetc(cinstr);
    } while(! (!iscntrl(ch) || strchr(" \t\n",ch) || read_controls || ch=='\0' || ch==EOF));
  }
  
  if (ch=='\n' || ch==EOF)
    return '\0';
  else
    return ch;
}


static void backchar(int ch) /* put char back into stream */
{
  if (!checkstream()) return;
  if (cinstr==stdin) {
    if (currchar>linebuffer) currchar--;
  }
  else {
    ungetc(ch,cinstr);
  }
}


void chkprompt() /* print an intermediate prompt if necessary */
{
  if (cinstr==stdin && (!currchar || !*currchar) && !prompted) onestring("?");
}


void create_onestring(char *str) /* create command 'onestring' */
{
  struct command *cmd;
  
  cmd=add_command(cONESTRING,NULL);
  cmd->pointer=my_strdup(str);
}


void onestring(char *s) /* write string to file */
{
#ifdef WINDOWS
  DWORD len,written;
#endif

  if (!checkstream()) return;
  if (curinized && abs(currstr)==STDIO_STREAM) {
  	;
  #ifdef BUILD_NCURSES
#ifdef UNIX
    addstr(s);
    refresh();
#else
    len=strlen(s);
    WriteConsole(ConsoleOutput,s,len,&written,NULL);
#endif
#endif
  } else if (abs(currstr)==lprstream) {
#ifdef UNIX
    fprintf(lineprinter,"%s",s);
    fflush(lineprinter);
#else
    len=strlen(s);
    WritePrinter(lineprinter,s,len,&written);
#endif    
  } else {
    fprintf(coutstr,"%s",s);
    fflush(coutstr);
  }
  prompted=TRUE;
}


void create_colour(int flag) /* create command 'colour' */
{
  struct command *c;
  
  c=add_command(cCOLOUR,NULL);
  c->args=flag;
}


void colour(struct command *cmd) /* switch on colour */
{
  char *fore=NULL,*back=NULL,*p;
  int fc,bc;

  if (cmd->args && !curinized) {
    error(ERROR,"need to call 'clear screen' first");
    return;
  }
  if (cmd->args==0) {
    if (!curinized) return;
#ifdef UNIX
  ;
  #ifdef BUILD_NCURSES
    if (has_colors()) 
      attrset(A_NORMAL|COLOR_PAIR(stdfc*8+stdbc));
    else
      attrset(A_NORMAL);
    return;
  #endif
#else
    SetConsoleTextAttribute(ConsoleOutput,FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
    return;
#endif
  } else if (cmd->args==1) {
#ifdef UNIX
  ;
  #ifdef BUILD_NCURSES
    if (has_colors()) 
      attrset(A_NORMAL|COLOR_PAIR(stdbc*8+stdfc));
    else {
      attrset(A_REVERSE);
      return;
    }
  #endif
#else
    SetConsoleTextAttribute(ConsoleOutput,BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE);
    return;
#endif
  } else { /* decode colours */
#ifdef UNIX
  ;
  #ifdef BUILD_NCURSES
    if (!has_colors()) {
      pop(stSTRING);
      if (cmd->args==3) pop(stSTRING);
      attrset(A_REVERSE);
      return;
    }
  #endif
#endif
    if (cmd->args==2) {
      back=NULL;
      fore=pop(stSTRING)->pointer;
      for(p=fore;*p;p++) *p=tolower(*p);
    } else {
      back=pop(stSTRING)->pointer;
      for(p=back;*p;p++) *p=tolower(*p);
      fore=pop(stSTRING)->pointer;
      for(p=fore;*p;p++) *p=tolower(*p);
    }
    fc=name2yc(fore);
    if (fc<0) {
      sprintf(string,"unknown foreground colour: '%s'",fore);
      error(ERROR,string);
    }
    bc=stdbc;
    if (back) {
      bc=name2yc(back);
      if (fc<0) {
	sprintf(string,"unknown background colour: '%s'",back);
	error(ERROR,string);
      }
    }
#ifdef UNIX
  #ifdef BUILD_NCURSES
    attrset(COLOR_PAIR(fc*8+bc));
  #endif
#else
    SetConsoleTextAttribute(ConsoleOutput,(WORD)(yc2oc(fc,TRUE)|yc2oc(bc,FALSE)));
#endif
  }
}


static void initcol(void) /* initialize curses colors */
{
  static int first=TRUE;
#ifdef UNIX
  int i,j,col;
  short f,b;
#else
  CONSOLE_SCREEN_BUFFER_INFO csbi;
#endif

  if (!first) return;
  first=FALSE;
#ifdef UNIX
  #ifdef BUILD_NCURSES
  if (!has_colors()) return;
  start_color();
  
  for(i=0;i<8;i++) {
    for(j=0;j<8;j++) {
      if (!i && !j) continue;
      init_pair(i*8+j,yc2oc(i,TRUE),yc2oc(j,FALSE));
    }
  }
  init_color(COLOR_YELLOW,1000,1000,0);

  col=inch()&A_COLOR;
  pair_content(col,&f,&b);
  stdfc=oc2yc(f);
  stdbc=oc2yc(b);
  bkgdset(COLOR_PAIR(stdfc*8+stdbc));
#endif
#else
  GetConsoleScreenBufferInfo(ConsoleOutput,&csbi);
  stdfc=csbi.wAttributes & (FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED);
  stdbc=csbi.wAttributes & (BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED);
#endif
}


int name2yc(char *name) /* convert a color name to an integer */
{
  char *c;

  for(c=name;*c;c++) *c=tolower(*c);
  if (!strcmp(name,"black") || !strcmp(name,"bla")) return YC_BLACK;
  if (!strcmp(name,"white") || !strcmp(name,"whi")) return YC_WHITE;
  if (!strcmp(name,"red") || !strcmp(name,"red")) return YC_RED;
  if (!strcmp(name,"blue") || !strcmp(name,"blu")) return YC_BLUE;
  if (!strcmp(name,"green") || !strcmp(name,"gre")) return YC_GREEN;
  if (!strcmp(name,"yellow") || !strcmp(name,"yel")) return YC_YELLOW;
  if (!strcmp(name,"cyan") || !strcmp(name,"cya")) return YC_CYAN;
  if (!strcmp(name,"magenta") || !strcmp(name,"mag")) return YC_MAGENTA;
  return -1;
}


int yc2oc(int yc,int fore) /* convert a yabasic color to operating system color */
{
#ifdef UNIX
  fore=0; /* stop gcc from complaining */
  if (yc==YC_BLACK) return COLOR_BLACK;
  if (yc==YC_WHITE) return COLOR_WHITE;
  if (yc==YC_RED) return COLOR_RED;
  if (yc==YC_BLUE) return COLOR_BLUE;
  if (yc==YC_GREEN) return COLOR_GREEN;
  if (yc==YC_YELLOW) return COLOR_YELLOW;
  if (yc==YC_CYAN) return COLOR_CYAN;
  if (yc==YC_MAGENTA) return COLOR_MAGENTA;
#else
  if (fore) { 
    if (yc==YC_BLACK) return 0;
    if (yc==YC_WHITE) return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
    if (yc==YC_RED) return FOREGROUND_RED;
    if (yc==YC_BLUE) return FOREGROUND_BLUE;
    if (yc==YC_GREEN) return FOREGROUND_GREEN;
    if (yc==YC_YELLOW) return FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
    if (yc==YC_CYAN) return FOREGROUND_GREEN | FOREGROUND_BLUE;
    if (yc==YC_MAGENTA) return FOREGROUND_BLUE | FOREGROUND_RED;
  } else {
    if (yc==YC_BLACK) return 0;
    if (yc==YC_WHITE) return BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
    if (yc==YC_RED) return BACKGROUND_RED;
    if (yc==YC_BLUE) return BACKGROUND_BLUE;
    if (yc==YC_GREEN) return BACKGROUND_GREEN;
    if (yc==YC_YELLOW) return BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY;
    if (yc==YC_CYAN) return BACKGROUND_GREEN | BACKGROUND_BLUE;
    if (yc==YC_MAGENTA) return BACKGROUND_BLUE | BACKGROUND_RED;
  }
#endif
  return -1; 
}


int oc2yc(int oc) /* convert an operating system color to yabasic color */
{
#ifdef UNIX
  if (oc==COLOR_BLACK) return YC_BLACK;
  if (oc==COLOR_WHITE) return YC_WHITE;
  if (oc==COLOR_RED) return YC_RED;
  if (oc==COLOR_BLUE) return YC_BLUE;
  if (oc==COLOR_GREEN) return YC_GREEN;
  if (oc==COLOR_YELLOW) return YC_YELLOW;
  if (oc==COLOR_CYAN) return YC_CYAN;
  if (oc==COLOR_MAGENTA) return YC_MAGENTA;
#else
  if (oc==0) return YC_BLACK;
  if (oc==(FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN) || 
      oc==(BACKGROUND_RED|BACKGROUND_BLUE|BACKGROUND_GREEN)) return YC_WHITE;
  if (oc==(FOREGROUND_RED) || 
      oc==(BACKGROUND_RED)) return YC_RED;
  if (oc==(FOREGROUND_BLUE) || 
      oc==(BACKGROUND_BLUE)) return YC_BLUE;
  if (oc==(FOREGROUND_GREEN) || 
      oc==(BACKGROUND_GREEN)) return YC_GREEN;
  if (oc==(FOREGROUND_RED|FOREGROUND_GREEN) || 
      oc==(BACKGROUND_RED|BACKGROUND_GREEN) ||
      oc==(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY) || 
      oc==(BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_INTENSITY)) return YC_YELLOW;
  if (oc==(FOREGROUND_BLUE|FOREGROUND_GREEN) || 
      oc==(BACKGROUND_BLUE|BACKGROUND_GREEN)) return YC_CYAN;
  if (oc==(FOREGROUND_RED|FOREGROUND_BLUE) || 
      oc==(BACKGROUND_RED|BACKGROUND_BLUE)) return YC_MAGENTA;
#endif
  return -1;
}


void putchars(void) /* put rect onto screen */
{
  char *ch,text,fore[4],back[4];
  int n,sx,sy,x,y,f,b;
  int tox,toy;
  int oldx,oldy;
#ifdef WINDOWS
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  COORD cp;
  char buff[2];
  int written;
#endif

  toy=(int)(pop(stNUMBER)->value);
  tox=(int)(pop(stNUMBER)->value);
  ch=pop(stSTRING)->pointer;
#ifdef UNIX
#ifdef BUILD_NCURSES
  getyx(stdscr,oldy,oldx);
#endif
#else
  GetConsoleScreenBufferInfo(ConsoleOutput,&csbi);
  oldx=csbi.dwCursorPosition.X;
  oldy=csbi.dwCursorPosition.Y;
#endif
  if (sscanf(ch,"%d,%d:%n",&sx,&sy,&n)!=2) {
    error(ERROR,"illegal screen string");
    return;
  }
  ch+=n;
  for(x=tox;x<tox+sx;x++) {
    if (x<0 || x>=COLS) continue;
    for(y=toy;y<toy+sy;y++) {
      if (x<0 || x>=COLS || y<0 || y>=LINES) {
        for(n=0;n<10;n++) if (*ch) ch++;
        continue;
      }
      if (!*ch) {
        text=' ';
        f=YC_BLACK;
        b=YC_BLACK;
      } else {
        text=*ch;
        strncpy(fore,ch+2,3);
        fore[3]='\0';
        strncpy(back,ch+6,3);
        back[3]='\0';
        for(n=0;n<10;n++) if (*ch) ch++;
        f=name2yc(fore);
        if (f<0) f=YC_WHITE;
        b=name2yc(back);
        if (b<0) b=YC_WHITE;
      }
#ifdef UNIX
  #ifdef BUILD_NCURSES
      if (has_colors()) attrset(COLOR_PAIR(f*8+b));
      mvaddch(y,x,text);
  #endif
#else
      cp.X=x;
      cp.Y=y;
      SetConsoleCursorPosition(ConsoleOutput,cp);
      SetConsoleTextAttribute(ConsoleOutput,(WORD)(yc2oc(f,TRUE)|yc2oc(b,FALSE)));
      buff[0]=text;
      buff[1]='\0';
      WriteConsole(ConsoleOutput,buff,1,&written,NULL);
#endif
    }
  }
#ifdef UNIX
  #ifdef BUILD_NCURSES
    if (has_colors()) 
      attrset(A_NORMAL|COLOR_PAIR(stdfc*8+stdbc));
    else
      attrset(A_NORMAL);
    move(y,x);
    refresh();
  #endif
#else
    cp.X=oldx;
    cp.Y=oldy;
    SetConsoleCursorPosition(ConsoleOutput,cp);
    SetConsoleTextAttribute(ConsoleOutput,(WORD)(stdfc|stdbc));
#endif

    return;
}


char *getchars(int xf,int yf,int xt,int yt) /* get rect from screen */
{
  int x,y;
#ifdef UNIX
  int c,ct,cc;
#endif
  int cf,cb;
  int oldx,oldy;
  char *res;
  char cols[20];
#ifdef WINDOWS
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  COORD cp;
  char charbuff[2];
  WORD attrbuff[2];
  int read;
#endif

  if (xf>xt) {x=xf;xf=xt;xt=x;}
  if (yf>yt) {y=yf;yf=yt;yt=y;}

  res=my_malloc(12+(xt-xf+1)*(yt-yf+1)*12);
  sprintf(res,"%d,%d:",xt-xf+1,yt-yf+1);
#ifdef UNIX
#ifdef BUILD_NCURSES
  getyx(stdscr,oldy,oldx);
#endif
#else
  GetConsoleScreenBufferInfo(ConsoleOutput,&csbi);
  oldx=csbi.dwCursorPosition.X;
  oldy=csbi.dwCursorPosition.Y;
#endif

  for(x=xf;x<=xt;x++) {
    for(y=yf;y<=yt;y++) {
      if (y<0 || y>=LINES || x<0 || x>=COLS) {
        strcat(res," blbl");
      } else {
#ifdef UNIX
#ifdef BUILD_NCURSES
        c=mvinch(y,x);
        ct=c&A_CHARTEXT;
        if (!isprint(ct)) ct=' ';
        cc=PAIR_NUMBER(c&A_COLOR);
        cb=cc&7;
        cf=(cc-cb)/8;
        if (has_colors()) {
          sprintf(cols,"%c:%s:%s,",ct,yc2short(cf),yc2short(cb));
        } else 
	{

          sprintf(cols,"%c:???:???,",ct);
        }
#endif
#else
        cp.X=x;
        cp.Y=y;
        ReadConsoleOutputCharacter(ConsoleOutput,charbuff,1,cp,&read);
        charbuff[1]='\0';
        ReadConsoleOutputAttribute(ConsoleOutput,attrbuff,1,cp,&read);
        cf=oc2yc(attrbuff[0]&(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE));
        cb=oc2yc(attrbuff[0]&(BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE));
        sprintf(cols,"%c:%s:%s,",charbuff[0],yc2short(cf),yc2short(cb));
#endif
        strcat(res,cols);
      }
    }
  }
#ifdef UNIX
#ifdef BUILD_NCURSES
  move(oldy,oldx);
#endif
#endif
  res[strlen(res)-1]='\0';
  return res;
}


char *yc2short(int col) /* convert yabasic colours to short colour name */
{
  static char r1[4],r2[4];
  static char *pr=r1;

  if (pr==r1) 
    pr=r2;
  else
    pr=r1;
  
  strcpy(pr,"***");
  if (col==YC_BLACK) strcpy(pr,"Bla");
  if (col==YC_WHITE) strcpy(pr,"Whi");
  if (col==YC_RED) strcpy(pr,"Red");
  if (col==YC_BLUE) strcpy(pr,"Blu");
  if (col==YC_GREEN) strcpy(pr,"Gre");
  if (col==YC_YELLOW) strcpy(pr,"Yel");
  if (col==YC_CYAN) strcpy(pr,"Cya");
  if (col==YC_MAGENTA) strcpy(pr,"Mag");

  return pr;
}

