/* sim_vt.c: VT2xx compatible terminal emulator

   Copyright (c) 2002, Robert M Supnik
   Written by Fischer Franz and used with his gracious permission

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Robert M Supnik shall not
   be used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   13-Apr-02	FF	Added Hold-Screen on Pause-Key
                	Corrected scrolling
			Added additional Esc-sequences Erase-Insert-Delete-Char 
			Corrected decoding 
			(ESC [ H, ESC [ x H, ESC [ ; y H , ESC [ x ; y H
			 are all valid sequences)
   15-Mar-02	FF	Original version Fischer Franz
*/

#include <windows.h>
#include "sim_defs.h"

#define HOLD   0x1
#define INSERT 0x2
#define SCRMAX 64

static HANDLE kbdHdl;
static HANDLE scrHdl;
static INPUT_RECORD inBuf;
static DWORD act, mode;
static CONSOLE_SCREEN_BUFFER_INFO screen;
static COORD margin;
static WORD attrib;

static char *kbd_ptr;
static char *scr_ptr;
static char scr_buf[SCRMAX];
static uint8 scr_mode;

struct keyEntry {
   uint8 asciiCode;
   uint8 scanCode;
   char *escSeq;
};
typedef struct keyEntry KEY;

typedef void (*scr_func) ();
struct scrEntry {
   char last;
   scr_func interpret;
};
typedef struct scrEntry SCR;

static KEY keyTab[] = {
    {0  ,0x3B,"[31~"},	/* F1, mapped to F17 on VT320 */
	{0  ,0x3C,"[32~"},	/* F2, mapped to F18 on VT320 */
	{0  ,0x3D,"[33~"},	/* F3, mapped to F19 on VT320 */
	{0  ,0x3E,"[34~"},	/* F4, mapped to F20 on VT320 */
	{0  ,0x3F,"[17~"},	/* F5, mapped to F6  on VT320 */
	{0  ,0x40,"[18~"},	/* F6, mapped to F7  on VT320 */
	{0  ,0x41,"[19~"},	/* F7, mapped to F8  on VT320 */
	{0  ,0x42,"[20~"},	/* F8, mapped to F9  on VT320 */
	{0  ,0x43,"[23~"},	/* F9, mapped to F11 on VT320 */
	{0  ,0x44,"[24~"},	/* F10,mapped to F12 on VT320 */
	{0  ,0x57,"[25~"},	/* F11,mapped to F13 on VT320 */
	{0  ,0x58,"[26~"},	/* F12,mapped to F14 on VT320 */

	{0xE0,0x52,"[2~"},  /* INS,mapped to INSERT on VT320 */
	{0xE0,0x53,"[3~"},  /* DEL,mapped to REMOVE in VT320 */
	{0xE0,0x47,"[1~"},  /* HOME, mapped to FIND on VT320 */
	{0xE0,0x4F,"[4~"},  /* END,mapped to SELECT on VT320 */
	{0xE0,0x49,"[5~"},  /* PAGE UP, mapped to PREV on VT320   */
	{0xE0,0x51,"[6~"},  /* PAGE DOWN, mapped to NEXT on VT320 */

	{0xE0,0x48,"[A"},/* UP */
	{0xE0,0x50,"[B"},/* DOWN */
	{0xE0,0x4D,"[C"},/* RIGHT */
	{0xE0,0x4B,"[D"},/* LEFT */

    {0xE0,0x45,"OP"},/* NUM, mapped to PF1 on VT320 */
	{0xE0,0x35,"OQ"},/* /  , mapped to PF2 on VT320 */
	{'*' ,0x37,"OR"},/* *  , mapped to PF3 on VT320 */
	{'-', 0x4A,"OS"},/* /  , mapped to PF4 on VT320 */

    /* The following Keys send Application Keypad-Mode sequences */
	{0  ,0x52,"Op"},	/* KP0 */
	{'0',0x52,"Op"},	/* KP0 */
	{0  ,0x4F,"Oq"},	/* KP1 */
	{'1',0x4F,"Oq"},	/* KP1 */
	{0  ,0x50,"Or"},	/* KP2 */
	{'2',0x50,"Or"},	/* KP2 */
	{0  ,0x51,"Os"},	/* KP3 */
	{'3',0x51,"Os"},	/* KP3 */
	{0  ,0x4B,"Ot"},	/* KP4 */
	{'4',0x4B,"Ot"},	/* KP4 */
	{0  ,0x4C,"Ou"},	/* KP5 */
	{'5',0x4C,"Ou"},	/* KP5 */
	{0  ,0x4D,"Ov"},	/* KP6 */
	{'6',0x4D,"Ov"},	/* KP6 */
	{0  ,0x47,"Ow"},	/* KP7 */
	{'7',0x47,"Ow"},	/* KP7 */
	{0  ,0x48,"Ox"},	/* KP8 */
	{'8',0x48,"Ox"},	/* KP8 */
	{0  ,0x49,"Oy"},	/* KP9 */
	{'9',0x49,"Oy"},    /* KP9 */
	{0  ,0x53,"On"},	/* PERIOD */
	{'.',0x53,"On"},    /* PERIOD */
	{0xE0,0x1C,"OM"},	/* ENTER */
	{'+',0x4E,"Ol"},	/* COMMA */
	{0}	                /* End of List */
};

int vt_read() {
   DWORD cnt = 0;
   uint8 sCode, aCode;
   KEY *key;
 
   if (kbd_ptr && *kbd_ptr) {
      return (*kbd_ptr++ & 0177);
   }

   GetNumberOfConsoleInputEvents(kbdHdl, &cnt);
   if (!cnt) return -1;

   ReadConsoleInput(kbdHdl, &inBuf, 1, &act);

   if (inBuf.EventType != KEY_EVENT) return -1;
   if (!inBuf.Event.KeyEvent.bKeyDown) return -1;
   if (inBuf.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) inBuf.Event.KeyEvent.uChar.AsciiChar = (uint8)0xE0;

   sCode = (uint8)inBuf.Event.KeyEvent.wVirtualScanCode;
   aCode = inBuf.Event.KeyEvent.uChar.AsciiChar;

   if (scr_mode & HOLD) {
      scr_mode &= ~HOLD;
      return 0x11;
   }
   if (sCode < 0x37 && aCode != 0 && aCode != 0xE0) return aCode;
   if (sCode == 0x45 && aCode == 0) {
      scr_mode |= HOLD;
      return 0x13;
   }
   
   for (key = keyTab; key->scanCode; key++) {
      if (key->asciiCode != aCode || key->scanCode != sCode) continue;
      kbd_ptr = key->escSeq;
      return 0x1b;
   }

   if (aCode != 0 && aCode != 0xE0) return aCode;
   return -1;
}

static void scr_nop () {};

static void scr_scroll_up(int from, int to, int lines) {
   SMALL_RECT src;
   COORD dst;
   CHAR_INFO fill;

   dst.X=0; dst.Y=from;
   src.Top=from+lines; src.Left=0; src.Bottom=to; src.Right=screen.dwSize.X;
   fill.Char.AsciiChar = ' ';
   fill.Attributes = attrib;
   ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill);
};
static void scr_scroll_down(int from, int to, int lines) {
   SMALL_RECT src;
   COORD dst;
   CHAR_INFO fill;

   dst.X=0; dst.Y=from+lines;
   src.Top=from; src.Left=0; src.Bottom=to-lines; src.Right=screen.dwSize.X;
   fill.Char.AsciiChar = ' ';
   fill.Attributes = attrib;
   ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill);
};
static void scr_insert_char() {
   int nr = atoi(&scr_buf[1]);
   SMALL_RECT src;
   COORD dst;
   CHAR_INFO fill;

   if (!nr) nr = 1;
   src.Top=screen.dwCursorPosition.Y; src.Left=screen.dwCursorPosition.X-nr; 
   src.Bottom=src.Top; src.Right=screen.dwSize.X;
   dst.X=src.Left+nr; dst.Y=src.Top;
   fill.Char.AsciiChar = ' ';
   fill.Attributes = attrib;
   ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill);
};
static void scr_pos_up() {
   int nr = atoi(&scr_buf[1]);
   if (!nr) nr = 1;
   if (screen.dwCursorPosition.Y > nr) screen.dwCursorPosition.Y -= nr;
   else                                screen.dwCursorPosition.Y = 0;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};
static void scr_pos_down() {
   int nr = atoi(&scr_buf[1]);
   if (!nr) nr = 1;
   screen.dwCursorPosition.Y += nr;
   if (screen.dwCursorPosition.Y >= margin.Y) screen.dwCursorPosition.Y = margin.Y-1;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};
static void scr_pos_right() {
   int nr = atoi(&scr_buf[1]);
   if (!nr) nr = 1;
   screen.dwCursorPosition.X += nr;
   if (screen.dwCursorPosition.X >= screen.dwSize.X) screen.dwCursorPosition.X = screen.dwSize.X-1;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};
static void scr_pos_left() {
   int nr = atoi(&scr_buf[1]);
   if (!nr) nr = 1;
   if (screen.dwCursorPosition.X > nr) screen.dwCursorPosition.X -= nr;
   else                                screen.dwCursorPosition.X = 0;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};
static void scr_pos_cursor() {
   int x, y;
   if (scr_buf[0] == 'H') return;
   x = 1; y = 1;
   if (!sscanf(scr_buf,"[%d;%d",&y,&x)) sscanf(scr_buf,"[;%d",&x);
   screen.dwCursorPosition.X = x-1;
   screen.dwCursorPosition.Y = y-1;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};
static void scr_prev_line() {
   scr_scroll_down(margin.X,margin.Y,1);
};
static void scr_next_line() {
   scr_scroll_up(margin.X,margin.Y,1);
};
static void scr_erase_display() {
   int nr = atoi(&scr_buf[1]);
   COORD pos;
   int len;

   if (nr == 0) {
      pos = screen.dwCursorPosition;
      len = (screen.dwSize.Y-screen.dwCursorPosition.Y-1)*screen.dwSize.X +
            (screen.dwSize.X-screen.dwCursorPosition.X);
   } else if (nr == 1) {
      pos.X = 0; pos.Y = 0;
      len = (screen.dwCursorPosition.Y-1)*screen.dwSize.X + screen.dwCursorPosition.X;
   } else if (nr == 2) {
      pos.X = 0; pos.Y = 0;
      len = screen.dwSize.X*screen.dwSize.Y;
   } else {
      return;
   }
      
   FillConsoleOutputAttribute(scrHdl,screen.wAttributes,len,pos,&act);
   FillConsoleOutputCharacter(scrHdl,' ',len,pos,&act);
};
static void scr_erase_line() {
   int nr = atoi(&scr_buf[1]);
   COORD pos;
   int len;

   if (nr == 0) {
      pos = screen.dwCursorPosition;
      len = screen.dwSize.X-screen.dwCursorPosition.X;
   } else if (nr == 1) {
      pos.X = 0; pos.Y = screen.dwCursorPosition.Y;
      len = screen.dwCursorPosition.X;
   } else if (nr == 2) {
      pos.X = 0; pos.Y = screen.dwCursorPosition.Y;
      len = screen.dwSize.X;
   } else {
      return;
   }

   FillConsoleOutputAttribute(scrHdl,screen.wAttributes,len,pos,&act);
   FillConsoleOutputCharacter(scrHdl,' ',len,pos,&act);
};
static void scr_delete_line() {
   if (scr_buf[0] == 'M') {
      scr_prev_line();
   } else {
      int nr = atoi(&scr_buf[1]);
      if (!nr) nr = 1;
      scr_scroll_up(screen.dwCursorPosition.Y,margin.Y,nr);
   }
};
static void scr_insert_line() {
   int nr = atoi(&scr_buf[1]);
   if (!nr) nr = 1;
   scr_scroll_down(screen.dwCursorPosition.Y,margin.Y,nr);
};
static void scr_delete_char() {
   int nr = atoi(&scr_buf[1]);
   SMALL_RECT src;
   COORD dst;
   CHAR_INFO fill;

   if (!nr) nr = 1;
   dst = screen.dwCursorPosition;
   src.Top=dst.Y; src.Left=dst.X+nr; src.Bottom=dst.Y; src.Right=screen.dwSize.X;
   fill.Char.AsciiChar = ' ';
   fill.Attributes = attrib;
   ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill);
};
static void scr_erase_char() {
   int nr = atoi(&scr_buf[1]);
   COORD pos;

   if (!nr) nr = 1;
   pos = screen.dwCursorPosition;

   FillConsoleOutputAttribute(scrHdl,screen.wAttributes,nr,pos,&act);
   FillConsoleOutputCharacter(scrHdl,' ',nr,pos,&act);
};
static void scr_request() {
   kbd_ptr = "\033[?6c";
};
static void scr_set() {
   int nr = atoi(&scr_buf[1]);
   if (nr == 4) scr_mode |= INSERT;
};
static void scr_reset() {
   int nr = atoi(&scr_buf[1]);
   if (nr == 4) scr_mode &= ~INSERT;
};
static void scr_attrib() {
   int nr = atoi(&scr_buf[1]);
   switch (nr) {
      case 0: 
         attrib = screen.wAttributes;
         break;
      case 1: 
         attrib = screen.wAttributes | 0x80;
         break;
      case 7: 
         attrib = ((screen.wAttributes & 0xf) << 4) | ((screen.wAttributes & 0x7) >> 4);
         break;
   }
};
static void scr_report() {};
static void scr_margin() {
   int top, bot;
   top = 1; bot = screen.dwSize.Y+1;
   if (!sscanf(scr_buf,"[%d;%d",&top,&bot)) sscanf(scr_buf,"[;%d",&bot);
   margin.X = top-1;
   if (bot != 24) margin.Y = bot-1;
   else           margin.Y = screen.dwSize.Y;
   
   screen.dwCursorPosition.X = 0;
   screen.dwCursorPosition.Y = 0;
   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
};

static SCR scrTab[] = {
    {'@', scr_insert_char},
    {'A', scr_pos_up},
    {'B', scr_pos_down},
    {'C', scr_pos_right},
    {'D', scr_pos_left},
    {'E', scr_next_line},
    {'H', scr_pos_cursor},
    {'I', scr_prev_line},
    {'J', scr_erase_display},
    {'K', scr_erase_line},
    {'L', scr_insert_line},
    {'M', scr_delete_line},
    {'P', scr_delete_char},
    {'X', scr_erase_char},
    {'Z', scr_request},

    {'c', scr_request},/* Device attribute request */
    {'f', scr_pos_cursor},
    {'h', scr_set},    /* Set   mode */
    {'l', scr_reset},  /* Reset mode */
    {'m', scr_attrib}, /* Display attributes */
    {'n', scr_report}, /* Device report */
    {'r', scr_margin},
    {'=', scr_nop},    /* Set applikation keypad mode */
    {'>', scr_nop},    /* Set numeric keypad mode */
    {0}
};

static void scr_char(char c) {
   
   if (c == 0x8) {         /* BS */
      if (screen.dwCursorPosition.X > 0) screen.dwCursorPosition.X--;
   } else if (c == 0xa) {  /* LF */
      if (screen.dwCursorPosition.Y < margin.Y-1) screen.dwCursorPosition.Y++;
      else scr_scroll_up(margin.X,margin.Y,1);
   } else if (c == 0xd) {  /* CR */
      screen.dwCursorPosition.X = 0;
   } else if (c == 0x9) {  /* Tab */
      screen.dwCursorPosition.X = (screen.dwCursorPosition.X + 8) & ~7;
      if (screen.dwCursorPosition.X >= screen.dwSize.X) screen.dwCursorPosition.X = screen.dwSize.X-1;
   } else if (c < ' ') {
      return;
   } else if (!(scr_mode & INSERT)) {
      WriteConsoleOutputCharacter(scrHdl,&c,1,screen.dwCursorPosition,&act);
      WriteConsoleOutputAttribute(scrHdl,&attrib,1,screen.dwCursorPosition,&act);

      if (screen.dwCursorPosition.X < screen.dwSize.X) screen.dwCursorPosition.X++;
      else if (screen.dwCursorPosition.Y < margin.Y-1) {
         screen.dwCursorPosition.X = 0;
         screen.dwCursorPosition.Y++;
      } else {
         scr_scroll_up(margin.X,margin.Y,1);
         screen.dwCursorPosition.X = 0;
      }
   } else {
      SMALL_RECT src;
      COORD dst;
      CHAR_INFO fill;

      src.Top=screen.dwCursorPosition.Y; src.Left=screen.dwCursorPosition.X; 
      src.Bottom=src.Top; src.Right=screen.dwSize.X;
      dst.X=src.Left+1; dst.Y=src.Top;
      fill.Char.AsciiChar = ' ';
      fill.Attributes = attrib;
      ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill);
      WriteConsoleOutputCharacter(scrHdl,&c,1,screen.dwCursorPosition,&act);

      if (screen.dwCursorPosition.X < screen.dwSize.X) screen.dwCursorPosition.X++;
      else if (screen.dwCursorPosition.Y < margin.Y-1) {
         screen.dwCursorPosition.X = 0;
         screen.dwCursorPosition.Y++;
      } else {
         scr_scroll_up(margin.X,margin.Y,1);
         screen.dwCursorPosition.X = 0;
      }
   }

   SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition);
}

static void check_esc (char c) {
   SCR *scr;

   *scr_ptr = c;
   if ((scr_ptr - scr_buf) < SCRMAX) scr_ptr++;
   for (scr = scrTab; scr->last; scr++) {
      if (scr->last != c) continue;

      *scr_ptr = 0;
      scr->interpret();
      scr_ptr = 0;
      return;
   }
}

void vt_write(char c) {

   if (c != 0x1b && !scr_ptr) {
      scr_char(c);
   } else if (c == 0x1b) {
      scr_ptr = scr_buf;
   } else if (c < ' ') {
      scr_ptr = 0;
      scr_char(c);
   } else if (scr_ptr == scr_buf) {
      check_esc(c);
   } else if (c >= '@') {
      check_esc(c);
      scr_ptr = 0;
   } else  {
      *scr_ptr = c;
      if ((scr_ptr - scr_buf) < SCRMAX) scr_ptr++;
   }
}

void vt_init() {
   kbdHdl = GetStdHandle(STD_INPUT_HANDLE);
   scrHdl = GetStdHandle(STD_OUTPUT_HANDLE);

   GetConsoleMode(kbdHdl, &mode);
   GetConsoleScreenBufferInfo(scrHdl, &screen);
   margin.X = 0; margin.Y = screen.dwSize.Y;
   attrib = screen.wAttributes;
   scr_mode = 0;
}

void vt_cmd() {
   SetConsoleMode(kbdHdl, mode); 
}

void vt_run() {
   kbd_ptr = 0;
   SetConsoleMode(kbdHdl, 0); 
   GetConsoleScreenBufferInfo(scrHdl, &screen);
   margin.X = 0; margin.Y = screen.dwSize.Y;
   attrib = screen.wAttributes;
   scr_mode = 0;
}