/* $Id: ANSITerminalStream,v 1.50 2009/02/28 18:36:50 kiesling Exp $ -*-c-*-*/

/*
  This file is part of Ctalk.
  Copyright  2005 - 2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

/*
 *    ANSITerminalStream class.
 *
 *    Provides ansi, vt100, xterm, dtterm, etc terminal input and 
 *    output.
 *
 *    Note that this class needs backward compatibility work
 *    with Solaris < 10 and/or GCC < 3.
 */

#ifdef DJGPP
#error ANSITerminalStream is only implemented for UNIX Systems.  Use Win32TerminalStream instead.
#endif

/* ANSI/VT100/Xterm escape sequences. */
#define VT_REV    "\033[7m"     /* Reverse video.                    */
#define VT_NORM   "\033[0m"     /* Normal video.                     */
#define VT_CPOS   "\033[%d;%df" /* Set cursor position: row, column. */
#define VT_CLS    "\033[2J"     /* Clear the screen.                 */
#define VT_RESET  "\033[!p"     /* Soft reset.                       */
#define VT_SGR    "\033[%cm"    /* Set graphics.                     */
#define VT_SCS_GRAPHICS    "\033(0"    /* Set graphics characters.   */
#define VT_SCS_ASCII       "\033(B"    /* Set ASCII characters.      */

#define CTRLC   0x03
#define CTRLD   0x04
#define CTRLH   0x08
#define CTRLZ   0x1a
#define ESC     0x1b
#define CSI2    0x5b     /* ESC [ sequence is CSI */
#define DEL     0x7f

#ifndef KBDCHAR                 /* These are also defined in */
#define KBDCHAR      (1 << 0)   /* TerminalStream class.     */
#endif
#ifndef KBDCUR   
#define KBDCUR       (1 << 1)
#endif

TerminalStream class ANSITerminalStream;
ANSITerminalStream instanceVariable inputHandle ReadFileStream 0x0;
ANSITerminalStream instanceVariable outputHandle WriteFileStream 0x0;
ANSITerminalStream instanceVariable ttyDevice String NULL;
ANSITerminalStream instanceVariable rawModeFlag Integer FALSE;
ANSITerminalStream instanceVariable termioCIFlag Integer 0x0;
ANSITerminalStream instanceVariable termioCOFlag Integer 0x0;
ANSITerminalStream instanceVariable termioCLFlag Integer 0x0;
ANSITerminalStream instanceVariable termioCCFlag Integer 0x0;
ANSITerminalStream instanceVariable queueInput Integer 0x0;

#include <stdio.h>
#if defined(__sparc__) && defined(__svr4__)
#include <sys/time.h>
#else /* Linux */
#include <sys/select.h>
#endif
#include <termios.h>

ANSITerminalStream instanceMethod readChar (void) {
  Integer new c;
  returnObjectClass Character;
  c = self inputHandle readChar;
  methodReturnObject(c);
}

ANSITerminalStream instanceMethod readLine (void) {
  String new s;
  returnObjectClass String;
  s = self inputHandle readLine;
  methodReturnObject(s);
}

ANSITerminalStream instanceMethod new (char *__ttyName) {
  OBJECT *__stdinStreamVar, *__stdoutStreamVar;
  struct termios termios_s;
  char buf[MAXLABEL];
  ANSITerminalStream super new __ttyName;

  __ctalkInstanceVarsFromClassObject (__ttyName);

  if ((__stdinStreamVar = 
       __ctalkGetInstanceVariable (__ttyName, "inputHandle",
				   FALSE)) != NULL) {
    __ctalkInstanceVarsFromClassObject (__stdinStreamVar);
#if defined (__linux__)
  sprintf (buf, "%#x", _IO_stdin);
#else
  sprintf (buf, "%#x", stdin);
#endif
  __ctalkSetObjectValueVar (__stdinStreamVar, buf);
  }

  if ((__stdoutStreamVar = 
       __ctalkGetInstanceVariable (__ttyName, "outputHandle",
				   FALSE)) != NULL) {
    __ctalkInstanceVarsFromClassObject (__stdoutStreamVar);
#if defined (__linux__)
  sprintf (buf, "%#x", _IO_stdout);
#else
  sprintf (buf, "%#x", stdout);
#endif
  __ctalkSetObjectValueVar (__stdoutStreamVar, buf);
  }

  tcgetattr (0, &termios_s);
  __ttyName termioCIFlag = termios_s.c_iflag;
  __ttyName termioCOFlag = termios_s.c_oflag;
  __ttyName termioCCFlag = termios_s.c_cflag;
  __ttyName termioCLFlag = termios_s.c_lflag;

  methodReturnObject(__ttyName) 
}

ANSITerminalStream instanceMethod clear (void) {
  self printOn "%s", VT_CLS;
  methodReturnNULL;
}

/*
 *  Only one attribute at a time.
 *
 *  0  - Attributes off.
 *  1  - Bold
 *  4  - Underscore
 *  5  - Blink
 *  7  - Reverse
 *
 */
ANSITerminalStream instanceMethod setGraphics (char __Ps) {
  self printOn VT_SGR, __Ps;
  methodReturnNULL;
}

ANSITerminalStream instanceMethod cursorPos (int __Prow, int __Pcol) {
  self printOn VT_CPOS, __Prow, __Pcol;
  methodReturnNULL;
}

ANSITerminalStream instanceMethod gotoXY (int __Prow, int __Pcol) {
  self printOn VT_CPOS, __Prow, __Pcol;
  methodReturnNULL;
}

ANSITerminalStream instanceMethod printOn (char *__fmt, ...) {
  __ctalkObjectPrintOn (self outputHandle);
  if (self rawModeFlag == TRUE) 
    fflush (self outputHandle value);
  methodReturnSelf;
}

ANSITerminalStream instanceMethod restoreTerm (void) {
  struct termios termios_s;
  tcgetattr (0, &termios_s);
  termios_s.c_iflag = self termioCIFlag value;
  termios_s.c_oflag = self termioCOFlag value;
  termios_s.c_lflag = self termioCLFlag value;
  termios_s.c_cflag = self termioCCFlag value;
  tcsetattr (0, TCSADRAIN, &termios_s);
  methodReturnNULL;
}

ANSITerminalStream instanceMethod rawMode (void) {
  struct termios old_termios_s, new_termios_s;
  tcgetattr (0, &old_termios_s);
  memcpy ((void *)&new_termios_s, (void *)&old_termios_s, 
	  sizeof (struct termios));
#if defined(__sparc__) && defined(__svr4__)
   new_termios_s.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
 			  | INLCR | IGNCR | ICRNL | IXON);
   new_termios_s.c_oflag &= ~OPOST;
   new_termios_s.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
   new_termios_s.c_cflag &= ~(CSIZE | PARENB);
   new_termios_s.c_cflag |= CS8;
   new_termios_s.c_cc[VMIN] = 0;
   new_termios_s.c_cc[VTIME] = 0;
#else /* Linux */
   cfmakeraw (&new_termios_s);
#endif
  tcsetattr (0, TCSADRAIN, &new_termios_s);
  self rawModeFlag = TRUE;
  methodReturnNULL;
}

ANSITerminalStream instanceMethod openInputQueue (void) {
  self queueInput = TRUE;
  methodReturnSelf;
}

ANSITerminalStream instanceMethod getCh (void) {
  int self_input_stream_no;
  fd_set rfds;
  int r;
  int c, c1, c2;
  char buf[2];
  returnObjectClass Integer;

  self_input_stream_no = fileno (self inputHandle value);

  FD_ZERO(&rfds);
  FD_SET(self_input_stream_no, &rfds);

  self rawMode;

  if ((r = select (self_input_stream_no + 1, &rfds, NULL, NULL, NULL)) < 0) {
    fprintf (stderr, "select () error on standard input\n");
    fflush (stderr);
    exit (1);
  }
  if (FD_ISSET (self_input_stream_no, &rfds)) {
    read (self_input_stream_no, (void *)buf, 1 * sizeof (char));
    c = (int)buf[0];
    switch (c)
      {
      case CTRLC:
      case CTRLD:
      case CTRLZ:
	self restoreTerm;
	__ctalk_exitFn(1);
	exit (1);
	break;
       case ESC:
	 read (0, (void *)buf, 1 * sizeof (char));
	 c1 = buf[0];
	 switch (c1)
	   {
	   case CSI2:
	     read (0, (void *)buf, 1 * sizeof (char));
	     c2 = buf[0];
	     switch (c2)
	       {
	       case 'A':   /* Cursor up */
	       case 'B':   /* Cursor down */
	       case 'C':   /* Cursor right */
	       case 'D':   /* Cursor left */
		 c = c2;
		 if (self queueInput)
		   self queueInputEvent KBDCUR, c;
		 break;
	       }
	     break;
	   default:
	     c = c1;        /* Any other escaped character. */
	     if (self queueInput)
	       self queueInputEvent KBDCUR, c;
	     break;
	   }
	 break;
      case CTRLH:
      case DEL:
      default:
   	if (self queueInput)
   	  self queueInputEvent KBDCHAR, c;
	break;
      }
    methodReturnInteger (c)
  } else {
    methodReturnInteger (0);
  }
}

ANSITerminalStream instanceMethod openOn (char *__deviceName) {

  FILE *f;
  OBJECT *self_inputHandle_value;
  OBJECT *self_outputHandle_value;
  SystemErrnoException new s;
  Exception new e;

  if ((f = fopen (__deviceName, "r+")) == NULL) {
    s raiseCriticalException __deviceName;
    methodReturnNULL;
  }

  self ttyDevice = __deviceName;

  self_inputHandle_value = self inputHandle value;
  self_outputHandle_value = self outputHandle value;
  sprintf (self_inputHandle_value -> __o_value, "%#x", f);
  sprintf (self_outputHandle_value -> __o_value, "%#x", f);

  if (!self isATty)
    e raiseException NOT_A_TTY_X, __deviceName;
  methodReturnNULL;
}

ANSITerminalStream instanceMethod setTty (int __speed, int __dataBits, 
					  char __parity, int __stopBits) {
  Exception new e;
  struct termios termios_s, termios_s_c;
  int ospeed, rval;

  if (!self isATty)
    e raiseException NOT_A_TTY_X, self ttyDevice;

  tcgetattr (fileno(self inputHandle), &termios_s);

#if defined(__sparc__) && defined(__svr4__)
  switch (__speed)
    {
    case 0:
      ospeed = B0;
      break;
    case 50:
      ospeed = B50;
      break;
    case 75:
      ospeed = B75;
      break;
    case 110:
      ospeed = B110;
      break;
    case 134:
      ospeed = B134;
      break;
    case 150:
      ospeed = B150;
      break;
    case 200:
      ospeed = B200;
      break;
    case 300:
      ospeed = B300;
      break;
    case 600:
      ospeed = B600;
      break;
    case 1200:
      ospeed = B1200;
      break;
    case 1800:
      ospeed = B1800;
      break;
    case 2400:
      ospeed = B2400;
      break;
    case 4800:
      ospeed = B4800;
      break;
    case 9600:
      ospeed = B9600;
      break;
    case 19200:
      ospeed = B19200;
      break;
    case 38400:
      ospeed = B38400;
      break;
    case 57600:
      ospeed = B57600;
      break;
    case 76800:
      ospeed = B76800;
      break;
    case 115200:
      ospeed = B115200;
      break;
    case 153600:
      ospeed = B153600;
      break;
    case 230400:
      ospeed = B230400;
      break;
    case 307200:
      ospeed = B307200;
      break;
    case 460800:
      ospeed = B460800;
      break;
    }
   termios_s.c_cflag &= ~(CIBAUDEXT|CIBAUD);
   if (ospeed > CBAUD) {
     termios_s.c_cflag |= CBAUDEXT;
     ospeed -= (CBAUD + 1);
   } else {
     termios_s.c_cflag &= ~CBAUDEXT;
   }
   termios_s.c_cflag =
     (termios_s.c_cflag & ~CBAUD) | (ospeed & CBAUD);
   termios_s.c_cflag = 
     (termios_s.c_cflag & ~CBAUD) | (ospeed & CBAUD);
#else
  switch (__speed)
    {
    case 0:
      cfsetspeed (&termios_s, B0);
      break;
    case 50:
      cfsetspeed (&termios_s, B50);
      break;
    case 75:
      cfsetspeed (&termios_s, B75);
      break;
    case 110:
      cfsetspeed (&termios_s, B110);
      break;
    case 134:
      cfsetspeed (&termios_s, B134);
      break;
    case 150:
      cfsetspeed (&termios_s, B150);
      break;
    case 200:
      cfsetspeed (&termios_s, B200);
      break;
    case 300:
      cfsetspeed (&termios_s, B300);
      break;
    case 600:
      cfsetspeed (&termios_s, B600);
      break;
    case 1200:
      cfsetspeed (&termios_s, B1200);
      break;
    case 1800:
      cfsetspeed (&termios_s, B1800);
      break;
    case 2400:
      cfsetspeed (&termios_s, B2400);
      break;
    case 4800:
      cfsetspeed (&termios_s, B4800);
      break;
    case 9600:
      cfsetspeed (&termios_s, B9600);
      break;
    case 19200:
      cfsetspeed (&termios_s, B19200);
      break;
    case 38400:
      cfsetspeed (&termios_s, B38400);
      break;
    case 57600:
      cfsetspeed (&termios_s, B57600);
      break;
    case 115200:
      cfsetspeed (&termios_s, B115200);
      break;
    case 230400:
      cfsetspeed (&termios_s, B230400);
      break;
    }
#endif /* defined(__sparc__) && defined(__svr4__) */

   switch (__dataBits)
     {
     case 5:
       termios_s.c_cflag = (termios_s.c_cflag & ~CSIZE) | (CS5 & CSIZE);
       break;
     case 6:
       termios_s.c_cflag = (termios_s.c_cflag & ~CSIZE) | (CS6 & CSIZE);
       break;
     case 7:
       termios_s.c_cflag = (termios_s.c_cflag & ~CSIZE) | (CS7 & CSIZE);
       break;
     case 8:
       termios_s.c_cflag = (termios_s.c_cflag & ~CSIZE) | (CS8 & CSIZE);
       break;
     }
   switch (__parity)
     {
     case 'N':
     case 'n':
       termios_s.c_cflag &= ~(PARODD|PARENB);
       break;
     case 'E':
     case 'e':
       termios_s.c_cflag &= ~PARODD;
       termios_s.c_cflag |= PARENB;
       break;
     case 'O':
     case 'o':
       termios_s.c_cflag |= (PARENB|PARODD);
       break;
     }
   switch (__stopBits)
     {
     case 1:
       termios_s.c_cflag &= ~CSTOPB;
       break;
     case 2:
       termios_s.c_cflag |= CSTOPB;
       break;
     }
   rval = tcsetattr (fileno(self inputHandle), TCSANOW, &termios_s);
   tcgetattr (fileno(self inputHandle), &termios_s_c);
   methodReturnNULL;
}

ANSITerminalStream instanceMethod closeStream (void) {
  FILE *f;
  OBJECT *selfval;
  int r;
  SystemErrnoException new e;

  selfval = self inputHandle value;
  sscanf (selfval -> __o_value, "0x%p", &f);

  r = fclose (f);
  selfval -> __o_value[0] = '\0';

  if (r)
    e raiseException NULL;

  return NULL;
}
