
#include "vt.h"
#include "user.h"

#include <stream.h>
#include <ctype.h>


// VT100 emulation
//
// VT100 code representation:
//   Example: the escape code \e[3;14H  (set cursor position)
//     would be represented by
//           code[] = [,_LN,;,_LN,H
//           VTArgn = 2
//           VTArg[]= 3,14
//     _LN is a flag for a numeric argument, which is kept in VTArg.


// Symbol in code[] which signifies a numeric argument.
const int _LN = -1;

int VT100emulator::write (char* buf, int nchars)
{
/* pshuang: This used to be all the code there was for this function. Replaced.
  for (int i = 0; i < nchars; i++) userWindow->addch(buf[i]);
*/
  if (nchars<=0)
    return nchars;
  else
    if (nchars>1)
      {
	int i,count;
	for (i=0; i<nchars; i++)
	  if (write(&buf[i],1)==1) count++;
	return count;
      }

  // Ideally, I would like to use send_last_code here, but since that
  // does not seem to be working, I'm copying the code from the Barton
  // ::write method, stripping out the Barton hacks

  char c = buf[0];
  VT100_code code = filter_escapeCodes(c);

  switch (code)
    {
    case c_NotCode:
      // code converts all EOL conventions (CR, CR+NL, NL) to just NL.
      // This problem didn't show up on Barton because Barton always
      // repositions the cursor (even EOL's) using escape commands.
      // It seems curses happily does a ClearEOL when it receives a CR.

      static char last_char = 0;

      switch (c)
	{
	case '\r':
	  {
	    int x,y,bottom_line;
	    userWindow->getyx(y,x);
	    bottom_line = userWindow->maxy() - 1;
	    
	    if (y>=bottom_line)
	      {
/* pshuang: this works but is slow and looks funny (cursor visibly wanders)
		userWindow->scrollok(1);
		userWindow->scroll();
		userWindow->scrollok(0);
*/
		userWindow->move(0,0);
		userWindow->deleteln();
		userWindow->move(y,x);
		// setting scrollok permanently leads to extra
		// linefeeds, not sure why
		userWindow->addch('\n');
		userWindow->move(bottom_line,0);
	      }
	    else
	      userWindow->addch('\n');
	  }
	  check_if_refresh_needed();
	  break;
	case '\n':
	  if (last_char!='\r')
	    { /* what is inside this bracket should be copy of "case '\r'" */
	      int x,y,bottom_line;
	      userWindow->getyx(y,x);
	      bottom_line = userWindow->maxy() - 1;
	      
	      if (y>=bottom_line)
		{
		  userWindow->scrollok(1);
		  userWindow->scroll();
		  userWindow->scrollok(0);
		  // setting scrollok permanently leads to extra
		  // linefeeds, not sure why
		  userWindow->addch('\n');
		  userWindow->move(bottom_line,0);
		}
	      else
		userWindow->addch('\n');
	    }
	  check_if_refresh_needed();
	  break;
	default:
	  userWindow->addch(c);
	  if (ispunct(c))
	    userWindow->refresh();
	}

/* pshuang: if I could figure out how to convinve g++ to link in clock()...
#define MAX_SECONDS_WITHOUT_REFRESH 2

      long now, last_refresh_time = 0;
      now = clock();
      if (now-last_refresh_time > MAX_SECONDS_WITHOUT_REFRESH)
	{
	  userWindow->refresh();
	  last_refresh_time=now;
	}
*/

      last_char = c;
      break;
    case c_CUP:
      if (VTArgn==2)
	userWindow->move(VTArg[0]-1, VTArg[1]-1);
      else
	userWindow->move(VTArg[0]-1, 0);
      break;
    case c_SGR:
      if (VTArgn>0)
	{
	  int counter=0;
	  while ((counter++)<=VTArgn)
	    {
	      if (VTArg[counter]==0)
		userWindow->standend();
	      else
		userWindow->standout();
	    }
	}
      else userWindow->standend();
      break;
    case c_ED:
      userWindow->clrtobot();
      break;
    case c_EL:
      userWindow->clrtoeol();
      break;
    case c_InCode:
      break;
    case c_UnkOp:
    default:
      userWindow->addstr("[CODE?]");
      return 0;
  }

  return 1;
}

// pshuang: 6-19-91: userWindow->refresh every REFRESH_COUNT times called

void VT100emulator::check_if_refresh_needed(void)
{

#define REFRESH_COUNT 3

  static int times_called = 0;

  if (++times_called>=REFRESH_COUNT)
    {
      userWindow->refresh();
      times_called = 0;
    }
}

// IncrementalWrite calls write for each character in buf.
int VT100emulator::IncrementalWrite (char* buf, int nchars)
{
  int ccount =0;
  for(int i=0;i<nchars;i++) {
    int res = write(&buf[i],1);
    if(res==-1) return -1;
    if(res!=1)  return -1;
    ccount += res;
  }
  return ccount;
}

// Regenerates and prints the code which was just parsed and recognized.
//   Equivalent expressions may be substituted.
void VT100emulator::send_last_code()
{
  switch(last) {
  case c_UnkOp:
    break;
  case c_NotCode:
    break;
  case c_InCode:
    break;
  case c_CUP:
    if(VTArgn==2) userWindow->move(VTArg[0]-1, VTArg[1]-1);
    else userWindow->move(VTArg[0]-1, 0);
    break;
  case c_ED:    // Clear to end of screen
    userWindow->clrtobot();
    break;
  case c_EL:    // Clear to end of line
    userWindow->clrtoeol();
    break;
/* pshuang: replaced, translate SGR's for curses
//  case c_SGR:    for(int i=0;i<VTArgn;i++) printf("[%dm",VTArg[i]);
  case c_SGR:   // Boldface on or off
//    if (VTArgn > 0) {
//      if (VTArg[0] < 10) userWindow->standout();  // turn it on
//      else userWindow->standend();                // turn it off
//    }
    break;
*/
  case c_SGR: // any attribute is represented by standout()
    if (VTArgn>0)
      {
	int counter=0;
	while ((counter++)<=VTArgn)
	  {
	    if (VTArg[counter]==0)
	      userWindow->standend();
	    else
	      userWindow->standout();
	  }
      }
    else userWindow->standend();
    break;
  default: break;
  }
  fflush(stdout);
  return;
}

// Give a visual indication when an unknown code is recieved.
void VT100emulator::encountered_unknown_code()
{
#ifdef DEBUG
 int *x = code;
 printf("<");
  for(int i=0,j=0;i<codeidx;i++)
    if(x[i]==_LN) { printf("%d.",VTArg[j]); j++; }
    else { printf("%c.",x[i]); }
  printf(">");
  fflush(stdout);
  sleep(6);
#endif
}


// ----- Helper functions for escape code recognition -----

// ----- Matching
// match("foo") returns true if \efoo has been parsed.
// match(123)   returns true if the code so far parsed is 123 characters long.

bool VT100emulator::matched (int len)
{
  return len==codeidx;
}
bool VT100emulator::matched (char* x)
{
  const int len = strlen(x);
  if(len!=codeidx) return 0;
  if(len > 0 && x[0]!=code[0]) if(x[0]!='0'||code[0]!=_LN) return 0;
  if(len > 1 && x[1]!=code[1]) if(x[1]!='0'||code[1]!=_LN) return 0;
  if(len > 2 && x[2]!=code[2]) if(x[2]!='0'||code[2]!=_LN) return 0;
  if(len > 3 && x[3]!=code[3]) if(x[3]!='0'||code[3]!=_LN) return 0;
  if(len > 4 && x[4]!=code[4]) if(x[4]!='0'||code[4]!=_LN) return 0;
  return 1;
}

// ----- Parse results (of this step)
// Was(CODE)  Escape code recognized as CODE
// WasUnk()   Escape code recognized as being unknown.
// IsMore()   Beginning of uncompleted but valid code seen.

inline VT100_code VT100emulator::Was (VT100_code cod)
{
  in_code =0;
  last    =cod;
  return cod;
}
inline VT100_code VT100emulator::WasUnk ()
{
  encountered_unknown_code();
  return Was(c_UnkOp);
}
inline VT100_code VT100emulator::IsMore ()
{
  last = c_InCode;
  return c_InCode;
}

// ----- Recognize VT100 escape codes

VT100_code VT100emulator::filter_escapeCodes(int c)
{
  int i,j;
  int *x = code;

  // Look for leading escape character.
  if(!in_code) {
      if(c!='') return Was(c_NotCode);
      else {
	  in_code =1;
	  VTArgn  =0;
	  codeidx =0;
	  in_code_num =0;
	  last=c_InCode;
	  return last;
      }
  }

  // Continue and termininate numeric arguments.
  if(in_code_num) {
    if(isdigit(c)) VTArg[VTArgn] = VTArg[VTArgn] *10 + (c-48);
    else {
      in_code_num =0;
      VTArgn++;
      codeidx++;
      x[codeidx] = c;
      codeidx++;
    }
  }
  else {
    // begin numeric argument
    if(isdigit(c)) {
      in_code_num =1;
      x[codeidx] = _LN;
      VTArg[VTArgn] = (c-48);
    }
    // non-numeric character (which doesnt follow number)
    else {
      x[codeidx] = c;
      codeidx++;
    }
  }

  if( matched("0") ) return WasUnk();
  if( matched("=") ) return Was(c_DECPAM);
  if( matched("#") && in_code_num ) return Was(c_UnkOp);
  if( matched("#") ) return IsMore();
  if( matched("[") ) return IsMore();
  if( matched(1)   ) return WasUnk();
  if( matched("[0") ) return IsMore();
  if( matched("[m") ) { VTArg[0]=0;VTArgn=1;
			 return Was(c_SGR); }
  if( matched("[H") ) { VTArg[0]=1;VTArg[1]=1;VTArgn=2;
			 return Was(c_CUP); }
  if( matched("[J") ) { VTArg[0]=0;VTArgn=1;
			 return Was(c_ED); }
  if( matched("[K") ) { VTArg[0]=0;VTArgn=1;
			 return Was(c_EL); }
  if( matched("[?") ) return IsMore();
  if( matched(2)    ) return WasUnk();
  if( matched("[0m") ) return Was(c_SGR);
  if( matched("[0H") ) { VTArgn=2;VTArg[1]=1; return Was(c_CUP); }
  if( matched("[0J") ) return Was(c_ED);
  if( matched("[0K") ) return Was(c_EL);
  if( matched("[0;") ) return IsMore();
  if( matched("[?0") ) return IsMore();
  if( matched(3)     ) return WasUnk();
  if( matched("[0;0") ) return IsMore();
  if( matched("[?0h") ) return Was(c_DECSET);
  if( matched("[?0l") ) return Was(c_DECRST);
  if( matched(4)      ) return WasUnk();
  if( matched("[0;0H") ) return Was(c_CUP);
  if( matched("[0;0m") ) return Was(c_SGR);
  if( matched(5)       ) return WasUnk();
  return WasUnk();
}
  

//=====================================================



void VT100_filter::set_filter_mode(int m)
{
  switch (m) {
  case 0: mode = echo;          break;
  case 1: mode = parsed_echo;   break;
  case 2: mode = filter;        break;
  }
}

void VT100_filter::set_pass_codes(VT100_code *codelist, int how_many)
{
  passcodepop = (maxpop >= how_many) ? how_many : maxpop;
  for(int i=0;i<maxpop;i++) passcodes[i] = codelist[i];
}

int VT100_filter::write (char* buf, int nchars)
{
  if(nchars>1)  return IncrementalWrite(buf,nchars);
  if(nchars==0) return 0;
  char c = buf[0];

  VT100_code code = filter_escapeCodes(c);

  if(mode==echo) {
    userWindow->addch(c);
    return 1;
  }
//    { printf("%c",(int)c);  fflush(stdout); return 1; }
  if(mode==parsed_echo) {
    if(code==c_NotCode) {
      userWindow->addch(c);
      return 1;
    }
    else {
      send_last_code();
      return 1;
    }
  }
//    if(code==c_NotCode) { printf("%c",(int)c); fflush(stdout); return 1; }
//    else                { send_last_code(); return 1; }
  if(mode==filter) {
    for(int i=0;i<maxpop;i++) 
      if(code==passcodes[i]) { send_last_code(); return 1; }
    return 0;
  }
}

//=====================================================

char sbuf[10];
int VT100_from_VT100barton::write (char* buf, int nchars)
{
  if (nchars>1)  return IncrementalWrite(buf,nchars);
  if (nchars==0) return 0;

  static char send_check_buffer[5] = "";
  static int send_num = 0;

  char c = buf[0];

  VT100_code code = filter_escapeCodes(c);

  if(code==c_NotCode) {
      if(c!='' &&
	 (c!=' ' || !on_line_25)) {

	  int in_send = 0;
	  int x, y;

	  userWindow->getyx(y, x);

	  // pshuang: SEND versus ENTER hack found here below
	  // Needs changes in allowable coords for logon screens?
	  
	  if (y > 18 && x > 60 &&
	      ((send_num == 0 &&
		c == 'S') ||
	       (send_num == 1 &&
		c == 'E') ||
	       (send_num == 2 &&
		c == 'N') ||
	       (send_num == 3 &&
		c == 'D'))) {
	      send_check_buffer[send_num] = c;
	      send_num += 1;
	      in_send = 1;
	  }

	  if (send_num == 4 && in_send)     // found a 'SEND' token
	    {
		userWindow->addch('E');
		userWindow->addch('N');
		userWindow->addch('T');
		userWindow->addch('E');
		userWindow->addch('R');

		send_num = 0;
	    }
	  else if (send_num > 0 && !in_send)  // aborted partial 'SEND' token
	    {
		int i;

		// output partial 'SEND'
		for (i = 0; i < send_num; i++)
		  userWindow->addch(send_check_buffer[i]);
		send_num = 0;
	    }

          if (!in_send) userWindow->addch(c);
	}
    return 1;
  }
  if(code==c_CUP) {
    int ln = VTArg[0];
    // 
    on_line_25 = (ln==25);
    // Compress out line 2 by sliding everything up.
    if(ln > 2) ln--;

/* pshuang: modified code to below 6-19-91
    if(VTArgn==2) userWindow->move(ln - 1, VTArg[1] - 1);
    else userWindow->move(ln - 1, 0);
*/
    // It appears that GEAC sends escape codes at the end of each line.
    // Therefore trapping for end-of-lines cannot be done by looking for
    // '\n\ or '\r', but rather a CUP.  Solution: force the refresh here,
    // rather than futilely trying to catch it at ::write.  This should
    // trap only the repositions to the beginning of line, assuming that
    // GEAC is sufficiently optimized for this to work.

    if (VTArgn==2)
      {
	userWindow->move(ln-1, VTArg[1]-1);

#define POSSIBLY_AT_BEGIN_OF_TEXT_LINE 4

	if (VTArg[1] < POSSIBLY_AT_BEGIN_OF_TEXT_LINE)
	  check_if_refresh_needed();
      }
    else userWindow->move(ln-1,0);

    return 1;
  }
  else if (code==c_SGR) {
/* pshuang: re-enable SGR's
//    if (VTArgn > 0) {
//      if (VTArg[0] < 10) userWindow->standout();
//      else userWindow->standend();
//    }
*/
    // pshuang: this code fragment can also be found in VT100emulator::send_last_code
    if (VTArgn>0)
      {
	int counter;
	for (counter=0; counter<VTArgn; counter++);
	  {
	    if (VTArg[counter]==0)
	      userWindow->standend();
	    else
	      userWindow->standout();
	  }
/*
        { pshuang: DEBUG ### to be removed !!!
	  char console_buffer[1000];
	  sprintf(console_buffer,"<%d:%d,%d,%d>",VTArgn,VTArg[0],VTArg[1],VTArg[2]);
	  userWindow->console_output(console_buffer);
	}
*/
      }
    else userWindow->standend();
    
    return 1;
  }
//  else if (code==c_SGR) return 1; // don't pass boldfacing.
  else if (code==c_ED) {
      userWindow->clrtobot();
      return 1;
  }
  else if (code==c_EL) {
      userWindow->clrtoeol();
      return 1;
  }
  else {
//    send_last_code();
    return 1;
  }
}
