/* This file is part of Zutty.
 * Copyright (C) 2020 Tom Szilagyi
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * See the file LICENSE for the full license.
 */

#include "options.h"
#include "pty.h"
#include "vterm.h"

#include <cstring>

namespace
{
   using namespace zutty;
   using Key = VtKey;
   using InputSpec = Vterm::InputSpec;

   #define ESC "\x1b"
   #define CSI ESC "["
   #define SS3 ESC "O"
   // magic byte to act as a placeholder for the Modifier Code:
   #define MC "\xff"

   const InputSpec is_modOtherKeys2 [] =
   {
      {Key::K2,          CSI "27;" MC ";50~"},
      {Key::K3,          CSI "27;" MC ";51~"},
      {Key::K4,          CSI "27;" MC ";52~"},
      {Key::K5,          CSI "27;" MC ";53~"},
      {Key::K6,          CSI "27;" MC ";54~"},
      {Key::K7,          CSI "27;" MC ";55~"},
      {Key::K8,          CSI "27;" MC ";56~"},
      {Key::Backtick,    CSI "27;" MC ";96~"},
      {Key::Tilde,       CSI "27;" MC ";126~"},
      {Key::Tab,         CSI "27;" MC ";9~"},
      {Key::Return,      CSI "27;" MC ";13~"},
      {Key::Space,       CSI "27;" MC ";32~"},
      {Key::Backspace,   CSI "27;" MC ";127~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Alt [] =
   {                     // UTF-8 encoded:
      {Key::K0,          "\xc2\xb0"},
      {Key::K1,          "\xc2\xb1"},
      {Key::K2,          "\xc2\xb2"},
      {Key::K3,          "\xc2\xb3"},
      {Key::K4,          "\xc2\xb4"},
      {Key::K5,          "\xc2\xb5"},
      {Key::K6,          "\xc2\xb6"},
      {Key::K7,          "\xc2\xb7"},
      {Key::K8,          "\xc2\xb8"},
      {Key::K9,          "\xc2\xb9"},
      {Key::Backtick,    "\xc3\xa0"},
      {Key::Tilde,       "\xc3\xbe"},
      {Key::Backspace,   "\xc3\xbf"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Alt_altSendsEscape [] =
   {
      {Key::K0,          ESC "0"},
      {Key::K1,          ESC "1"},
      {Key::K2,          ESC "2"},
      {Key::K3,          ESC "3"},
      {Key::K4,          ESC "4"},
      {Key::K5,          ESC "5"},
      {Key::K6,          ESC "6"},
      {Key::K7,          ESC "7"},
      {Key::K8,          ESC "8"},
      {Key::K9,          ESC "9"},
      {Key::Backtick,    ESC "`"},
      {Key::Tilde,       ESC "~"},
      {Key::Backspace,   ESC "\x7f"},
      {Key::Space,       ESC " "},
      {Key::Tab,         ESC "\t"},
      {Key::Return,      ESC "\n"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Control_modOtherKeys [] =
   {
      {Key::K0,          CSI "27;" MC ";48~"},
      {Key::K1,          CSI "27;" MC ";49~"},
      {Key::K9,          CSI "27;" MC ";57~"},
      {Key::Tab,         CSI "27;" MC ";9~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_ControlAlt_altSendsEscape [] =
   {
      {Key::K2,          ESC "\x00", 2},
      {Key::K3,          ESC "\x1b", 2},
      {Key::K4,          ESC "\x1c", 2},
      {Key::K5,          ESC "\x1d", 2},
      {Key::K6,          ESC "\x1e", 2},
      {Key::K7,          ESC "\x1f", 2},
      {Key::K8,          ESC "\x7f", 2},
      {Key::Backtick,    ESC "\x00", 2},
      {Key::Tilde,       ESC "\x1e", 2},
      {Key::Space,       ESC "\x00", 2},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Control [] =
   {
      {Key::K2,          "\x00", 1},
      {Key::K3,          "\x1b", 1},
      {Key::K4,          "\x1c", 1},
      {Key::K5,          "\x1d", 1},
      {Key::K6,          "\x1e", 1},
      {Key::K7,          "\x1f", 1},
      {Key::K8,          "\x7f", 1},
      {Key::Backtick,    "\x00", 1},
      {Key::Tilde,       "\x1e", 1},
      {Key::Space,       "\x00", 1},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Shift [] =
   {
      {Key::Tab,         CSI "Z"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_modOtherKeys [] =
   {
      {Key::Return,      CSI "27;" MC ";13~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Ansi [] =
   {
      {Key::K0,          "0"},
      {Key::K1,          "1"},
      {Key::K2,          "2"},
      {Key::K3,          "3"},
      {Key::K4,          "4"},
      {Key::K5,          "5"},
      {Key::K6,          "6"},
      {Key::K7,          "7"},
      {Key::K8,          "8"},
      {Key::K9,          "9"},
      {Key::Backtick,    "`"},
      {Key::Tilde,       "~"},
      {Key::Space,       " "},
      {Key::Backspace,   "\x7f"},
      {Key::Tab,         "\t"},
      {Key::Return,      "\r"},
      {Key::Insert,      CSI "2~"},
      {Key::Delete,      CSI "3~"},
      {Key::PageUp,      CSI "5~"},
      {Key::PageDown,    CSI "6~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Mod_Ansi [] =
   {
      {Key::Insert,      CSI "2;" MC "~"},
      {Key::Delete,      CSI "3;" MC "~"},
      {Key::PageUp,      CSI "5;" MC "~"},
      {Key::PageDown,    CSI "6;" MC "~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Ansi_FunctionKeys [] =
   {
      {Key::F1,          SS3 "P"},     {Key::KP_F1,       SS3 "P"},
      {Key::F2,          SS3 "Q"},     {Key::KP_F2,       SS3 "Q"},
      {Key::F3,          SS3 "R"},     {Key::KP_F3,       SS3 "R"},
      {Key::F4,          SS3 "S"},     {Key::KP_F4,       SS3 "S"},
      {Key::F5,          CSI "15~"},
      {Key::F6,          CSI "17~"},
      {Key::F7,          CSI "18~"},
      {Key::F8,          CSI "19~"},
      {Key::F9,          CSI "20~"},
      {Key::F10,         CSI "21~"},
      {Key::F11,         CSI "23~"},
      {Key::F12,         CSI "24~"},
      {Key::F13,         CSI "25~"},
      {Key::F14,         CSI "26~"},
      {Key::F15,         CSI "28~"},
      {Key::F16,         CSI "29~"},
      {Key::F17,         CSI "31~"},
      {Key::F18,         CSI "32~"},
      {Key::F19,         CSI "33~"},
      {Key::F20,         CSI "34~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Mod_Ansi_FunctionKeys [] =
   {
      {Key::F1,          CSI "1;" MC "P"},   {Key::KP_F1,    CSI "1;" MC "P"},
      {Key::F2,          CSI "1;" MC "Q"},   {Key::KP_F2,    CSI "1;" MC "Q"},
      {Key::F3,          CSI "1;" MC "R"},   {Key::KP_F3,    CSI "1;" MC "R"},
      {Key::F4,          CSI "1;" MC "S"},   {Key::KP_F4,    CSI "1;" MC "S"},
      {Key::F5,          CSI "15;" MC "~"},
      {Key::F6,          CSI "17;" MC "~"},
      {Key::F7,          CSI "18;" MC "~"},
      {Key::F8,          CSI "19;" MC "~"},
      {Key::F9,          CSI "20;" MC "~"},
      {Key::F10,         CSI "21;" MC "~"},
      {Key::F11,         CSI "23;" MC "~"},
      {Key::F12,         CSI "24;" MC "~"},
      {Key::F13,         CSI "25;" MC "~"},
      {Key::F14,         CSI "26;" MC "~"},
      {Key::F15,         CSI "28;" MC "~"},
      {Key::F16,         CSI "29;" MC "~"},
      {Key::F17,         CSI "31;" MC "~"},
      {Key::F18,         CSI "32;" MC "~"},
      {Key::F19,         CSI "33;" MC "~"},
      {Key::F20,         CSI "34;" MC "~"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Ansi_KeypadKeys [] =
   {
      {Key::KP_Space,    " "},
      {Key::KP_Tab,      "\t"},
      {Key::KP_Enter,    "\r"},
      {Key::KP_Star,     "*"},
      {Key::KP_Plus,     "+"},
      {Key::KP_Comma,    ","},
      {Key::KP_Minus,    "-"},
      {Key::KP_Slash,    "/"},
      {Key::KP_Delete,   "."},  {Key::KP_Dot,      "."},
      {Key::KP_Insert,   "0"},  {Key::KP_0,        "0"},
      {Key::KP_End,      "1"},  {Key::KP_1,        "1"},
      {Key::KP_Down,     "2"},  {Key::KP_2,        "2"},
      {Key::KP_PageDown, "3"},  {Key::KP_3,        "3"},
      {Key::KP_Left,     "4"},  {Key::KP_4,        "4"},
      {Key::KP_Begin,    "5"},  {Key::KP_5,        "5"},
      {Key::KP_Right,    "6"},  {Key::KP_6,        "6"},
      {Key::KP_Home,     "7"},  {Key::KP_7,        "7"},
      {Key::KP_Up,       "8"},  {Key::KP_8,        "8"},
      {Key::KP_PageUp,   "9"},  {Key::KP_9,        "9"},
      {Key::KP_Equal,    "="},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Appl_KeypadKeys [] =
   {
      {Key::KP_Space,    SS3 " "},
      {Key::KP_Tab,      SS3 "I"},
      {Key::KP_Enter,    SS3 "M"},
      {Key::KP_Star,     SS3 "j"},
      {Key::KP_Plus,     SS3 "k"},
      {Key::KP_Comma,    SS3 "l"},
      {Key::KP_Minus,    SS3 "m"},
      {Key::KP_Delete,   SS3 "n"},
      {Key::KP_Slash,    SS3 "o"},
      {Key::KP_Insert,   SS3 "p"},
      {Key::KP_End,      SS3 "q"},
      {Key::KP_Down,     SS3 "r"},
      {Key::KP_PageDown, SS3 "s"},
      {Key::KP_Left,     SS3 "t"},
      {Key::KP_Begin,    SS3 "u"},
      {Key::KP_Right,    SS3 "v"},
      {Key::KP_Home,     SS3 "w"},
      {Key::KP_Up,       SS3 "x"},
      {Key::KP_PageUp,   SS3 "y"},
      {Key::KP_Equal,    SS3 "X"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Mod_Appl_KeypadKeys [] =
   {
      {Key::KP_Space,    SS3 MC " "},
      {Key::KP_Tab,      SS3 MC "I"},
      {Key::KP_Enter,    SS3 MC "M"},
      {Key::KP_Star,     SS3 MC "j"},
      {Key::KP_Plus,     SS3 MC "k"},
      {Key::KP_Comma,    SS3 MC "l"},
      {Key::KP_Minus,    SS3 MC "m"},
      {Key::KP_Delete,   SS3 MC "n"},
      {Key::KP_Slash,    SS3 MC "o"},
      {Key::KP_Insert,   SS3 MC "p"},
      {Key::KP_End,      SS3 MC "q"},
      {Key::KP_Down,     SS3 MC "r"},
      {Key::KP_PageDown, SS3 MC "s"},
      {Key::KP_Left,     SS3 MC "t"},
      {Key::KP_Begin,    SS3 MC "u"},
      {Key::KP_Right,    SS3 MC "v"},
      {Key::KP_Home,     SS3 MC "w"},
      {Key::KP_Up,       SS3 MC "x"},
      {Key::KP_PageUp,   SS3 MC "y"},
      {Key::KP_Equal,    SS3 MC "X"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_VT52_KeypadKeys [] =
   {
      {Key::KP_Space,    ESC "? "},
      {Key::KP_Tab,      ESC "?I"},
      {Key::KP_Enter,    ESC "?M"},
      {Key::KP_Star,     ESC "?j"},
      {Key::KP_Plus,     ESC "?k"},
      {Key::KP_Comma,    ESC "?l"},
      {Key::KP_Minus,    ESC "?m"},
      {Key::KP_Delete,   ESC "?n"},
      {Key::KP_Slash,    ESC "?o"},
      {Key::KP_Insert,   ESC "?p"},
      {Key::KP_End,      ESC "?q"},
      {Key::KP_Down,     ESC "?r"},
      {Key::KP_PageDown, ESC "?s"},
      {Key::KP_Left,     ESC "?t"},
      {Key::KP_Begin,    ESC "?u"},
      {Key::KP_Right,    ESC "?v"},
      {Key::KP_Home,     ESC "?w"},
      {Key::KP_Up,       ESC "?x"},
      {Key::KP_PageUp,   ESC "?y"},
      {Key::KP_Equal,    ESC "?X"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_VT52_FunctionKeys [] =
   {
      {Key::F1,          ESC "P"},      {Key::KP_F1,       ESC "P"},
      {Key::F2,          ESC "Q"},      {Key::KP_F2,       ESC "Q"},
      {Key::F3,          ESC "R"},      {Key::KP_F3,       ESC "R"},
      {Key::F4,          ESC "S"},      {Key::KP_F4,       ESC "S"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Ansi_CursorKeys [] =
   {
      {Key::Up,          CSI "A"},
      {Key::Down,        CSI "B"},
      {Key::Right,       CSI "C"},
      {Key::Left,        CSI "D"},
      {Key::Home,        CSI "H"},
      {Key::End,         CSI "F"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Appl_CursorKeys [] =
   {
      {Key::Up,          SS3 "A"},
      {Key::Down,        SS3 "B"},
      {Key::Right,       SS3 "C"},
      {Key::Left,        SS3 "D"},
      {Key::Home,        SS3 "H"},
      {Key::End,         SS3 "F"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Mod_CursorKeys [] =
   {
      {Key::Up,          CSI "1;" MC "A"},
      {Key::Down,        CSI "1;" MC "B"},
      {Key::Right,       CSI "1;" MC "C"},
      {Key::Left,        CSI "1;" MC "D"},
      {Key::Home,        CSI "1;" MC "H"},
      {Key::End,         CSI "1;" MC "F"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_VT52_CursorKeys [] =
   {
      {Key::Up,          ESC "A"},
      {Key::Down,        ESC "B"},
      {Key::Right,       ESC "C"},
      {Key::Left,        ESC "D"},
      {Key::Home,        ESC "H"},
      {Key::End,         ESC "F"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_ReturnKey_ANL [] =
   {
      {Key::Return,      "\r\n"},
      {Key::KP_Enter,    "\r\n"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_BackspaceKey_BkSp [] =
   {
      {Key::Backspace,   "\b"},
      {Key::NONE,        nullptr},
   };

   const InputSpec is_Alt_BackspaceKey_BkSp [] =
   {
      {Key::Backspace,   ESC "\b"},
      {Key::NONE,        nullptr},
   };

   #undef ESC
   #undef CSI
   #undef SS3

   inline uint8_t
   getModifierCode (VtModifier modifiers)
   {
      switch (modifiers)
      {
      case VtModifier::none:              return 0;
      case VtModifier::shift:             return 2;
      case VtModifier::alt:               return 3;
      case VtModifier::shift_alt:         return 4;
      case VtModifier::control:           return 5;
      case VtModifier::shift_control:     return 6;
      case VtModifier::control_alt:       return 7;
      case VtModifier::shift_control_alt: return 8;
      }
      return 0;
   }


   void
   makePalette256 (Color p[])
   {
      // Standard colors
      opts.getColor ("color0", p[0]);
      opts.getColor ("color1", p[1]);
      opts.getColor ("color2", p[2]);
      opts.getColor ("color3", p[3]);
      opts.getColor ("color4", p[4]);
      opts.getColor ("color5", p[5]);
      opts.getColor ("color6", p[6]);
      opts.getColor ("color7", p[7]);
      opts.getColor ("color8", p[8]);
      opts.getColor ("color9", p[9]);
      opts.getColor ("color10", p[10]);
      opts.getColor ("color11", p[11]);
      opts.getColor ("color12", p[12]);
      opts.getColor ("color13", p[13]);
      opts.getColor ("color14", p[14]);
      opts.getColor ("color15", p[15]);

      // 216 colors
      for (uint8_t r = 0; r < 6; ++r)
         for (uint8_t g = 0; g < 6; ++g)
            for (uint8_t b = 0; b < 6; ++b)
            {
               uint8_t ri = r ? 55 + 40 * r : 0;
               uint8_t gi = g ? 55 + 40 * g : 0;
               uint8_t bi = b ? 55 + 40 * b : 0;
               p[16 + 36 * r + 6 * g + b] = {ri, gi, bi};
            }

      // Grayscale colors
      for (uint8_t s = 0; s < 24; ++s)
      {
         uint8_t i = 8 + 10 * s;
         p[232 + s] = {i, i, i};
      }
   }

   /* These tables perform translation of built-in "hard" character sets
    * to 16-bit Unicode points. All sets are defined as 96 characters, even
    * those originally designated by DEC as 94-character sets.
    *
    * These tables are referenced by Vterm::charCodes (see below).
    */

   // Ref: https://en.wikipedia.org/wiki/DEC_Special_Graphics
   const uint16_t uc_DecSpec [] =
   {
      0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
      0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
      0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
      0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,

      0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
      0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
      0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
      0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,

      0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
      0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
      0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
      0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020,
   };

   // Ref: https://en.wikipedia.org/wiki/Multinational_Character_Set
   const uint16_t uc_DecSuppl [] =
   {
      0x0020, 0x00a1, 0x00a2, 0x00a3, 0x0024, 0x00a5, 0x0026, 0x00a7,
      0x00a4, 0x00a9, 0x00aa, 0x00ab, 0x002c, 0x002d, 0x002e, 0x002f,
      0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0034, 0x00b5, 0x00b6, 0x00b7,
      0x0038, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x003e, 0x00bf,

      0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
      0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
      0x0050, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0152,
      0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0178, 0x005e, 0x00df,

      0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
      0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
      0x0070, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0153,
      0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00ff, 0x007e, 0x007f,
   };

   // Ref: https://en.wikipedia.org/wiki/DEC_Technical_Character_Set
   const uint16_t uc_DecTechn [] =
   {
      0x0020, 0x23b7, 0x250c, 0x2500, 0x2320, 0x2321, 0x2502, 0x23a1,
      0x23a3, 0x23a4, 0x23a6, 0x239b, 0x239d, 0x239e, 0x23a0, 0x23a8,
      0x23ac, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
      0x0020, 0x0020, 0x0020, 0x0020, 0x2264, 0x2260, 0x2265, 0x222b,

      0x2234, 0x221d, 0x221e, 0x00f7, 0x0394, 0x2207, 0x03a6, 0x0393,
      0x223c, 0x2243, 0x0398, 0x00d7, 0x039b, 0x21d4, 0x21d2, 0x2261,
      0x03a0, 0x03a8, 0x0020, 0x03a3, 0x0020, 0x0020, 0x221a, 0x03a9,
      0x039e, 0x03a5, 0x2282, 0x2283, 0x2229, 0x222a, 0x2227, 0x2228,

      0x00ac, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3,
      0x03b7, 0x03b9, 0x03b8, 0x03ba, 0x03bb, 0x0020, 0x03bd, 0x2202,
      0x03c0, 0x03c8, 0x03c1, 0x03c3, 0x03c4, 0x0020, 0x0192, 0x03c9,
      0x03be, 0x03c5, 0x03b6, 0x2190, 0x2191, 0x2192, 0x2193, 0x007f,
   };

   // Ref: https://en.wikipedia.org/wiki/ISO/IEC_8859-1
   const uint16_t uc_IsoLatin1 [] =
   {
      0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
      0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
      0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
      0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,

      0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
      0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
      0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
      0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,

      0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
      0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
      0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
      0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
   };

   // Same as ASCII, but with Pound sign (0x00a3 in place of 0x0023)
   const uint16_t uc_IsoUK [] =
   {
      0x0020, 0x0021, 0x0022, 0x00a3, 0x0024, 0x0025, 0x0026, 0x0027,
      0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
      0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
      0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,

      0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
      0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
      0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
      0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,

      0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
      0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
      0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
      0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
   };

} // namespace

namespace zutty
{
   const uint16_t* Vterm::charCodes [] =
   {
      // Sync this with enumerators of Charset!
      nullptr, // Dummy slot for UTF-8 (handled differently)
      uc_DecSpec,
      uc_DecSuppl,
      uc_DecSuppl, // Slot for 'User-preferred supplemental'
      uc_DecTechn,
      uc_IsoLatin1,
      uc_IsoUK
   };

   Vterm::Vterm (uint16_t glyphPx_, uint16_t glyphPy_,
                 uint16_t winPx_, uint16_t winPy_,
                 int ptyFd_)
      : winPx (winPx_)
      , winPy (winPy_)
      , nCols ((winPx - 2 * opts.border) / glyphPx_)
      , nRows ((winPy - 2 * opts.border) / glyphPy_)
      , glyphPx (glyphPx_)
      , glyphPy (glyphPy_)
      , ptyFd (ptyFd_)
      , onRefresh ([] (const Frame&) {})
      , onOsc ([] (int cmd, const std::string& arg)
               { logU << "OSC: '" << cmd << ";" << arg << "'" << std::endl; })
      , onBell ([] () { logI << "* Bell *" << std::endl; })
      , frame_pri (winPx, winPy, nCols, nRows, marginTop, marginBottom,
                   opts.saveLines)
      , cf (&frame_pri)
      , rgb_fg (opts.fg)
      , rgb_bg (opts.bg)
      , utf8dec ([this] () { placeGraphicChar (); })
      , nColsEff (nCols)
      , hMargin (0)
   {
      makePalette256 (palette256);

      defaultFgPalIx = (opts.fg == palette256 [15]) ? 15 : -1;
      defaultBgPalIx = (opts.bg == palette256 [0]) ? 0 : -1;
      fgPalIx = defaultFgPalIx;
      bgPalIx = defaultBgPalIx;

      resetTerminal ();
   }

   void
   Vterm::setRefreshHandler (const RefreshHandlerFn& onRefresh_)
   {
      onRefresh = onRefresh_;
   }

   void
   Vterm::setOscHandler (const OscHandlerFn& onOsc_)
   {
      haveOscHandler = true;
      onOsc = onOsc_;
   }

   void
   Vterm::setBellHandler (const BellHandlerFn& onBell_)
   {
      onBell = onBell_;
   }

   void
   Vterm::resize (uint16_t winPx_, uint16_t winPy_)
   {
      winPx = winPx_;
      winPy = winPy_;

      uint16_t nCols_ = std::max (1, (winPx - 2 * opts.border) / glyphPx);
      uint16_t nRows_ = std::max (1, (winPy - 2 * opts.border) / glyphPy);

      if (nCols == nCols_ && nRows == nRows_)
      {
         cf->winPx = winPx;
         cf->winPy = winPy;
         return;
      }

      hideCursor ();

      if (altScreenBufferMode)
      {
         frame_alt = Frame (winPx, winPy, nCols_, nRows_,
                            marginTop, marginBottom);
      }
      else
      {
         if (nRows_ < posY + 1)
         {
            int nScroll = nRows - nRows_;
            cf->scrollUp (nScroll);
            posY -= nScroll;
         }

         frame_pri.resize (winPx, winPy, nCols_, nRows_,
                           marginTop, marginBottom);

         if (nRows < nRows_)
         {
            int nScroll = std::min (nRows_ - nRows, (int)cf->getHistoryRows ());
            cf->scrollDown (nScroll);
            posY += nScroll;
         }

         frame_alt.freeCells ();
      }
      nCols = nCols_;
      nRows = nRows_;

      if (horizMarginMode)
      {
         nColsEff = std::min (nColsEff, nCols);
         hMargin = std::max (0, std::min ((int)hMargin, nColsEff - 2));
      }
      else
      {
         nColsEff = nCols;
         hMargin = 0;
      }
      normalizeCursorPos ();
      showCursor ();

      pty_resize (ptyFd, nCols, nRows);
   }

   std::string
   Vterm::getLocalEcho (const unsigned char *const begin,
                        const unsigned char *const end)
   {
      std::ostringstream oss;
      for (const unsigned char* p = begin; p < end; ++p)
      {
         if (*p == '\r' || *p >= ' ')
            oss << *p;
         else
            oss << '^' << (char)(*p + 0x40);
      }
      return oss.str ();
   }

   int
   Vterm::writePty (VtKey key, VtModifier modifiers_, bool userInput)
   {
#ifdef DEBUG
      if (key == VtKey::Print)
      {
         debugKey ();
         return 0;
      }
#endif
      modifiers = modifiers_;
      const auto& spec = getInputSpec (key);
      if (modifiers == VtModifier::none)
      {
         return writePty ((const uint8_t *)spec.input, spec.getLength (),
                          userInput);
      }
      else
      {
         // substitute the MC token with the actual modifier code
         static uint8_t buf [32];
         int k = 0;
         const char* end = spec.input + spec.getLength ();
         for (const char* p = spec.input; p != end; ++p)
            if (*p == *MC)
               buf [k++] = '0' + getModifierCode (modifiers);
            else
               buf [k++] = *p;
         buf [k] = '\0';
         return writePty (buf, k, userInput);
      }
   }

   int
   Vterm::writePty (uint8_t ch, VtModifier modifiers, bool userInput)
   {
      using VM = VtModifier;

      auto uch = (unsigned char*)&ch;
      logT << "pty write (mod=" << (int)modifiers << "): "
           << dumpBuffer (uch, uch + 1);

      const auto& mod2_encode =
         [&] (uint8_t ch)
         {
            const char* exempt = "!#$%&*()-+=?.,:;<>'\"";
            auto x = const_cast <char*> (exempt);

            while (*x)
               if (ch == *x++)
                  return (modifiers & VM::control_alt) != VM::none;

            return modifiers != VM::none;
         };

      if ((modifyOtherKeys == 2 && mod2_encode (ch)) ||
          (modifyOtherKeys == 1 && (modifiers & VM::control) != VM::none &&
           ch > ' '))
      {
         if (ch < ' ' && (modifiers & VM::control) != VM::none)
         {
            const char* ctrlmap = ((modifiers & VM::shift) != VM::none)
               ? "@ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}^/"
               : " abcdefghijklmnopqrstuvwxyz[\\]^/";
            ch = ctrlmap [ch];
         }

         uint8_t wbuf [16] = { '\e', '[', '2', '7', ';', '_', ';' };
         wbuf [5] = '0' + getModifierCode (modifiers);
         uint8_t pos = 7;

         if (ch > 99)
         {
            wbuf [pos] = ch / 100;
            ch -= 100 * wbuf [pos];
            wbuf [pos] += '0';
            ++pos;
         }
         if (pos > 7 || ch > 9)
         {
            wbuf [pos] = ch / 10;
            ch -= 10 * wbuf [pos];
            wbuf [pos] += '0';
            ++pos;
         }
         wbuf [pos++] = '0' + ch;
         wbuf [pos++] = '~';
         wbuf [pos] = '\0';

         return writePty (wbuf, pos, userInput);
      }
      else if ((modifiers & VM::alt) != VM::none)
      {
         if (altSendsEscape)
         {
            static uint8_t wbuf [2] = { '\e', '\0' };
            wbuf [1] = ch;
            return writePty (wbuf, 2, userInput);
         }
         else
         {
            std::vector <char> utf8_out;
            auto sinkFn = [&] (char ch) { utf8_out.push_back (ch); };
            Utf8Encoder::pushUnicode (ch | 0x80, sinkFn);
            return writePty ((const uint8_t *)utf8_out.data (),
                             utf8_out.size (), userInput);
         }
      }
      else
      {
         return writePty (uch, 1, userInput);
      }
   }

   int
   Vterm::writePty (const char* cstr, bool userInput)
   {
      auto ucstr = (unsigned char *)cstr;
      return writePty (ucstr, strlen (cstr), userInput);
   }

   int
   Vterm::writePty (const uint8_t* ucstr, size_t len, bool userInput)
   {
      if (userInput && keyboardLocked)
      {
         logT << "pty write: discarding due to keyboard lock (DECKAM): "
              << dumpBuffer (ucstr, ucstr + len);
         return len;
      }

      logT << "pty write: " << dumpBuffer (ucstr, ucstr + len);
      if (userInput && localEcho)
         processInput (getLocalEcho (ucstr, ucstr + len));
      return write (ptyFd, ucstr, len);
   }

   using Key = VtKey;
   using Mod = VtModifier;

   Vterm::InputSpecTable *
   Vterm::getInputSpecTable ()
   {
      static InputSpecTable ist [] =
      {
         { [this] () { return (autoNewlineMode == true); },
           is_ReturnKey_ANL
         },

         { [this] () { return ((modifiers & Mod::alt) != Mod::none &&
                               bkspSendsDel == false); },
           is_Alt_BackspaceKey_BkSp
         },

         { [this] () { return (modifyOtherKeys == 2 &&
                               modifiers != Mod::none); },
           is_modOtherKeys2
         },

         { [this] () { return (modifyOtherKeys > 0 && modifiers != Mod::none); },
           is_modOtherKeys
         },

         { [this] () { return (modifyOtherKeys > 0 &&
                               (modifiers & Mod::control) != Mod::none); },
           is_Control_modOtherKeys
         },

         { [this] () { return (altSendsEscape &&
                               (modifiers & Mod::control_alt) == Mod::control_alt); },
           is_ControlAlt_altSendsEscape
         },

         { [this] () { return (altSendsEscape &&
                               (modifiers & Mod::alt) != Mod::none); },
           is_Alt_altSendsEscape
         },

         { [this] () { return ((modifiers & Mod::alt) != Mod::none); },
           is_Alt
         },

         { [this] () { return ((modifiers & Mod::control) != Mod::none); },
           is_Control
         },

         { [this] () { return ((modifiers & Mod::shift) != Mod::none); },
           is_Shift
         },

         { [this] () { return (bkspSendsDel == false); },
           is_BackspaceKey_BkSp
         },

         { [this] () { return (compatLevel == CompatibilityLevel::VT52 &&
                               keypadMode == KeypadMode::Application); },
           is_VT52_KeypadKeys
         },
         { [this] () { return (compatLevel == CompatibilityLevel::VT52); },
           is_VT52_CursorKeys
         },
         { [this] () { return (compatLevel == CompatibilityLevel::VT52); },
           is_VT52_FunctionKeys
         },

         { [this] () { return (modifiers != Mod::none &&
                               keypadMode == KeypadMode::Application); },
           is_Mod_Appl_KeypadKeys
         },
         { [this] () { return (keypadMode == KeypadMode::Application); },
           is_Appl_KeypadKeys
         },
         { [this] () { return (modifiers != Mod::none); },
           is_Mod_CursorKeys
         },
         { [this] () { return (cursorKeyMode == CursorKeyMode::Application); },
           is_Appl_CursorKeys
         },

         { [this] () { return (modifiers != Mod::none); },
           is_Mod_Ansi
         },
         { [this] () { return (modifiers != Mod::none); },
           is_Mod_Ansi_FunctionKeys
         },

         // default entries
         { [] () { return true; }, is_Ansi },
         { [] () { return true; }, is_Ansi_CursorKeys },
         { [] () { return true; }, is_Ansi_FunctionKeys },
         { [] () { return true; }, is_Ansi_KeypadKeys },

         // end marker to delimit iteration
         { [] () { return true; }, nullptr }
      };
      return ist;
   }

   void
   Vterm::resetInputSpecTable ()
   {
      for (InputSpecTable* e = getInputSpecTable (); e->specs != nullptr; ++e)
         e->visited = false;
   }

   const Vterm::InputSpec *
   Vterm::selectInputSpecs ()
   {
      InputSpecTable* ist = getInputSpecTable ();
      for (auto e = ist; e->specs != nullptr; ++e)
      {
         if (!e->visited)
         {
            e->visited = true;
            if (e->predicate ())
               return e->specs;
         }
      }
      return nullptr;
   }

   const Vterm::InputSpec &
   Vterm::getInputSpec (Key key)
   {
      static InputSpec nullSpec = {Key::NONE, ""};

      resetInputSpecTable ();
      const InputSpec* specs;
      while ((specs = selectInputSpecs ()) != nullptr)
      {
         for (int k = 0; specs [k].key != Key::NONE; ++k)
            if (specs [k].key == key)
               return specs [k];
      }

      return nullSpec;
   }

#define IGNORE_SEQUENCE_ON_BAD_PARAMS                  \
   case ':': case '<': case '=': case '>': case '?':   \
   setState (InputState::IgnoreSequence);              \
   break

#define COLLECT_NUMERIC_PARAMS                                        \
   case '0': case '1': case '2': case '3': case '4':                  \
   case '5': case '6': case '7': case '8': case '9':                  \
      if (inputOps [nInputOps - 1] < 429496704)                       \
      {                                                               \
         inputOps [nInputOps - 1] *= 10;                              \
         inputOps [nInputOps - 1] += ch - '0';                        \
      }                                                               \
      else                                                            \
      {                                                               \
         logE << "inputOp overflow!" << std::endl;                    \
         setState (InputState::Normal);                               \
      }                                                               \
      break;                                                          \
   case ';':                                                          \
      if (nInputOps < maxEscOps)                                      \
         inputOps [nInputOps ++] = 0;                                 \
      else                                                            \
      {                                                               \
         logE << "inputOps full, increase maxEscOps (currently: "     \
              << maxEscOps << ")!" << std::endl;                      \
         setState (InputState::Normal);                               \
      }                                                               \
      break

   void
   Vterm::processInput (const std::string& str)
   {
      processInput ((unsigned char*)str.c_str (), str.length ());
   }

   void
   Vterm::processInput (const unsigned char *const input, int inputSize)
   {
      lastEscBegin = 0;
      lastNormalBegin = 0;
      lastStopPos = 0;
      hideCursor ();
      cf->pageToBottom ();
      for (readPos = 0; readPos < inputSize; ++readPos)
      {
         const unsigned char& ch = input [readPos];
         switch (inputState)
         {
         case InputState::Normal:
            switch (ch)
            {
            case '\x00': // ignore NUL
               break;
            case '\e':
               setState (compatLevel == CompatibilityLevel::VT52
                         ? InputState::Escape_VT52
                         : InputState::Escape);
               inputOps [0] = 0;
               nInputOps = 1;
               lastEscBegin = readPos;
               break;
            case '\r': traceNormalInput (); inp_CR (); break;
            case '\f': // fall through, treat as LineFeed ('\n')
            case '\v': // fall through, treat as LineFeed ('\n')
            case '\n': traceNormalInput (); esc_IND (); break;
            case '\t': traceNormalInput (); inp_HT (); break;
            case '\b': traceNormalInput (); csi_CUB (); break;
            case '\a': traceNormalInput (); onBell (); break;
            case '\x0e': traceNormalInput (); charsetState.gl = 1; break;
            case '\x0f': traceNormalInput (); charsetState.gl = 0; break;
            case '\x05': // ENQ - Enquiry
               traceNormalInput ();
               break;
            default: inputGraphicChar (ch);
            }
            break;
         case InputState::IgnoreSequence:
            if (ch >= '\x40' && ch <= '\x7e') // DEC-STD-070: final chars
               setState (InputState::Normal);
            break;
         case InputState::Escape_VT52:
            switch (ch)
            {
            case '\x18': case '\x1a': // CAN and SUB interrupts ESC sequence
               setState (InputState::Normal); break;
            case '\e': // ESC restarts ESC sequence
               inputOps [0] = 0;
               nInputOps = 1;
               lastEscBegin = readPos;
               break;
            case '=':
               keypadMode = KeypadMode::Application;
               setState (InputState::Normal);
               break;
            case '>':
               keypadMode = KeypadMode::Normal;
               setState (InputState::Normal);
               break;
            case '<':
               compatLevel = CompatibilityLevel::VT100;
               setState (InputState::Normal);
               break;
            case 'A': csi_CUU (); break;
            case 'B': csi_CUD (); break;
            case 'C': csi_CUF (); break;
            case 'D': csi_CUB (); break;
            case 'F':
               charsetState = CharsetState {};
               charsetState.g [charsetState.gl] = Charset::DecSpec;
               setState (InputState::Normal);
               break;
            case 'G':
               charsetState = CharsetState {};
               setState (InputState::Normal);
               break;
            case 'H': csi_CUP (); break;
            case 'I': esc_RI (); break;
            case 'J': csi_ED (); break;
            case 'K': csi_EL (); break;
            case 'Y': setState (InputState::VT52_CUP_Arg1); break;
            case 'Z': writePty ("\e/Z"); break;
            case 'c': esc_RIS (); break; // allow "reset" command to escape VT52
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::VT52_CUP_Arg1:
            inputOps [0] = input [readPos] - 31;
            setState (InputState::VT52_CUP_Arg2);
            break;
         case InputState::VT52_CUP_Arg2:
            inputOps [1] = input [readPos] - 31;
            nInputOps = 2;
            csi_CUP ();
            break;
         case InputState::Escape:
            switch (ch)
            {
            case '\x18': case '\x1a': // CAN and SUB interrupts ESC sequence
               setState (InputState::Normal);
               break;
            case '\e': // ESC restarts ESC sequence
               inputOps [0] = 0;
               nInputOps = 1;
               lastEscBegin = readPos;
               break;
            case ' ': setState (InputState::Esc_SPC); break;
            case '#': setState (InputState::Esc_Hash); break;
            case '%': setState (InputState::Esc_Pct); break;
            case '[': setState (InputState::CSI); break;
            case ']': argBuf.clear (); setState (InputState::OSC); break;
            case '(': case ')': case '*': case '+':
            case '-': case '.': case '/':
            case ',': case '$': // from ISO/IEC 2022 (absorbed, treat as no-op)
               scsDst = ch;
               scsMod = '\0';
               setState (InputState::SelectCharset);
               break;
            case 'D': esc_IND (); break;
            case 'M': esc_RI (); break;
            case 'E': esc_NEL (); break;
            case 'H': esc_HTS (); break;
            case 'N': charsetState.ss = 2; setState (InputState::Normal); break;
            case 'O': charsetState.ss = 3; setState (InputState::Normal); break;
            case 'P': argBuf.clear (); setState (InputState::DCS); break;
            case 'c': esc_RIS (); break;
            case '6': esc_BI (); break;
            case '7': esc_DECSC (); break;
            case '8': esc_DECRC (); break;
            case '9': esc_FI (); break;
            case '=':
               keypadMode = KeypadMode::Application;
               setState (InputState::Normal);
               break;
            case '>':
               keypadMode = KeypadMode::Normal;
               setState (InputState::Normal);
               break;
            case '<':
               compatLevel = CompatibilityLevel::VT400;
               setState (InputState::Normal);
               break;
            case '~': charsetState.gr = 1; setState (InputState::Normal); break;
            case 'n': charsetState.gl = 2; setState (InputState::Normal); break;
            case '}': charsetState.gr = 2; setState (InputState::Normal); break;
            case 'o': charsetState.gl = 3; setState (InputState::Normal); break;
            case '|': charsetState.gr = 3; setState (InputState::Normal); break;
            case '\\': setState (InputState::Normal); break; // ignore lone ST
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::Esc_SPC:
            switch (ch)
            {
            case 'F':
               logU << "S7C1T: Send 7-bit controls" << std::endl;
               setState (InputState::Normal);
               break;
            case 'G':
               logU << "S8C1T: Send 8-bit controls" << std::endl;
               setState (InputState::Normal);
               break;
            case 'L':
               logU << "Set ANSI conformance level 1" << std::endl;
               setState (InputState::Normal);
               break;
            case 'M':
               logU << "Set ANSI conformance level 2" << std::endl;
               setState (InputState::Normal);
               break;
            case 'N':
               logU << "Set ANSI conformance level 3" << std::endl;
               setState (InputState::Normal);
               break;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::Esc_Hash:
            switch (ch)
            {
            case '3':
               logU << "DECDHL: Double-height, top half" << std::endl;
               setState (InputState::Normal);
               break;
            case '4':
               logU << "DECDHL: Double-height, bottom half" << std::endl;
               setState (InputState::Normal);
               break;
            case '5':
               logU << "DECSWL: Single-width line" << std::endl;
               setState (InputState::Normal);
               break;
            case '6':
               logU << "DECDWL: Double-width line" << std::endl;
               setState (InputState::Normal);
               break;
            case '8': esch_DECALN (); break;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::Esc_Pct:
            switch (ch)
            {
            case '@':
               logT << "Select charset: default (ISO-8859-1)" << std::endl;
               charsetState = CharsetState {};
               charsetState.g [charsetState.gr] = Charset::IsoLatin1;
               setState (InputState::Normal);
               break;
            case 'G':
               logT << "Select charset: UTF-8" << std::endl;
               charsetState = CharsetState {};
               setState (InputState::Normal);
               break;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::SelectCharset:
            if (ch < 0x30) // intermediate
               scsMod = ch;
            else
               esc_DCS (ch);
            break;
         case InputState::CSI:
            switch (ch)
            {
            COLLECT_NUMERIC_PARAMS;
            case '\e': setState (InputState::Normal); break;
            case 'A': csi_CUU (); break;
            case 'B': csi_CUD (); break;
            case 'C': csi_CUF (); break;
            case 'D': csi_CUB (); break;
            case 'E': csi_CNL (); break;
            case 'F': csi_CPL (); break;
            case 'G': csi_CHA (); break;
            case 'H': case 'f': csi_CUP (); break;
            case 'I': csi_CHT (); break;
            case 'J': csi_ED (); break;
            case 'K': csi_EL (); break;
            case 'L': csi_IL (); break;
            case 'M': csi_DL (); break;
            case 'P': csi_DCH (); break;
            case 'S': csi_SU (); break;
            case 'T': csi_SD (); break;
            case 'X': csi_ECH (); break;
            case 'Z': csi_CBT (); break;
            case '@': csi_ICH (); break;
            case '`': csi_HPA (); break;
            case 'a': csi_HPR (); break;
            case 'b': csi_REP (); break;
            case 'c': csi_priDA (); break;
            case 'd': csi_VPA (); break;
            case 'e': csi_VPR (); break;
            case 'g': csi_TBC (); break;
            case 'h': csi_SM (); break;
            case 'l': csi_RM (); break;
            case 'm': csi_SGR (); break;
            case 'n': csi_DSR (); break;
            case 'r': csi_STBM (); break;
            case 's': csi_SCOSC_SLRM (); break;
            case 't': csi_XTWINOPS (); break;
            case 'u': csi_SCORC (); break;
            case '\'': setState (InputState::CSI_Quote); break;
            case '\"': setState (InputState::CSI_DblQuote); break;
            case '!': setState (InputState::CSI_Bang); break;
            case '?': setState (readPos == lastEscBegin + 2
                                ? InputState::CSI_priv
                                : InputState::IgnoreSequence);
               break;
            case ' ': setState (InputState::CSI_SPC); break;
            case '>': setState (readPos == lastEscBegin + 2
                                ? InputState::CSI_GT
                                : InputState::IgnoreSequence);
               break;
            case '\a': break; // ignore
            case '\b': // undo last character in CSI sequence:
               if (readPos && input [readPos - 1] == ';')
                  --nInputOps;
               else
                  inputOps [nInputOps - 1] /= 10;
               break;
            case '\t': inp_HT (); setState (InputState::CSI); break;
            case '\r': inp_CR (); setState (InputState::CSI); break;
            case '\f': // fall through
            case '\v': esc_IND (); setState (InputState::CSI); break;
            // N.B. '>' and '?' above, so no IGNORE_SEQUENCE_ON_BAD_PARAMS:
            case ':': case '<': case '=':
               setState (InputState::IgnoreSequence);
               break;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_Bang:
            switch (ch)
            {
            case 'p': csi_DECSTR (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_Quote:
            switch (ch)
            {
            case '}': csi_DECIC (); break;
            case '~': csi_DECDC (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_DblQuote:
            switch (ch)
            {
            case 'p': csiq_DECSCL (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_SPC:
            switch (ch)
            {
            case '@': csi_ecma48_SL (); break;
            case 'A': csi_ecma48_SR (); break;
            case 'q': csi_DECSCUSR (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_GT:
            switch (ch)
            {
            COLLECT_NUMERIC_PARAMS;
            case 'c': csi_secDA (); break;
            case 'm': csi_XTMODKEYS (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::CSI_priv:
            switch (ch)
            {
            COLLECT_NUMERIC_PARAMS;
            case '\e': setState (InputState::Normal); break;
            case 'h': csi_privSM (); break;
            case 'l': csi_privRM (); break;
            case 'm': csi_XTQMODKEYS (); break;
            IGNORE_SEQUENCE_ON_BAD_PARAMS;
            default: unhandledInput (ch); break;
            }
            break;
         case InputState::DCS:
            switch (ch)
            {
            case '\e': setState (InputState::DCS_Esc); break;
            default:
               if (argBuf.size () < 4095)
                  argBuf.push_back (ch);
               else
               {
                  logE << "DCS argument string overflow" << std::endl;
                  setState (InputState::Normal);
               }
               break;
            }
            break;
         case InputState::DCS_Esc:
            switch (ch)
            {
            case '\\': handle_DCS (); break;
            default:
               argBuf.push_back ('\e');
               argBuf.push_back (ch);
               setState (InputState::DCS);
               break;
            }
            break;
         case InputState::OSC:
            switch (ch)
            {
            case '\a': handle_OSC (); break;
            case '\e': setState (InputState::OSC_Esc); break;
            default:
               if (argBuf.size () < 4095)
                  argBuf.push_back (ch);
               else
               {
                  logE << "OSC argument string overflow" << std::endl;
                  setState (InputState::Normal);
               }
               break;
            }
            break;
         case InputState::OSC_Esc:
            switch (ch)
            {
            case '\\': handle_OSC (); break;
            default:
               argBuf.push_back ('\e');
               argBuf.push_back (ch);
               setState (InputState::OSC);
               break;
            }
            break;
         }
      }
      traceNormalInput ();
      showCursor ();
      redraw ();
   }

   void
   Vterm::selectStart (int pX, int pY, bool cycleSnapTo)
   {
      logT << "selectStart (" << pX << "," << pY
           << "), cycleSnapTo=" << cycleSnapTo << std::endl;

      if (cycleSnapTo)
      {
         selectExtend (pX, pY, true);
         return;
      }

      pX = std::min (std::max (0, pX - opts.border), winPx - 2 * opts.border);
      pY = std::min (std::max (0, pY - opts.border), winPy - 2 * opts.border);
      Point pt (pX / glyphPx, pY / glyphPy);

      Rect& selection = cf->getSelection ();
      cf->setSelectSnapTo (Frame::SelectSnapTo::Char);
      selection.tl = pt;
      selection.br = pt;
      selectUpdatesTop = false;
      selectUpdatesLeft = false;

      hideCursor ();
      redraw ();
   }

   void
   Vterm::selectExtend (int pX, int pY, bool cycleSnapTo)
   {
      logT << "selectExtend (" << pX << "," << pY
           << "), cycleSnapTo=" << cycleSnapTo << std::endl;

      pX = std::min (std::max (0, pX - opts.border), winPx - 2 * opts.border);
      pY = std::min (std::max (0, pY - opts.border), winPy - 2 * opts.border);
      Point pt (pX / glyphPx, pY / glyphPy);

      Rect& selection = cf->getSelection ();
      if (cycleSnapTo)
         cf->cycleSelectSnapTo ();

      if (selection.rectangular)
      {
         selectUpdatesLeft = pt.x < selection.mid ().x;
         selectUpdatesTop = pt.y < selection.mid ().y;
      }
      else
         selectUpdatesLeft = selectUpdatesTop = pt < selection.mid ();

      if (selectUpdatesTop && selectUpdatesLeft)
         selection.tl = pt;
      else if (selectUpdatesTop)
      {
         selection.br.x = pt.x;
         selection.tl.y = pt.y;
      }
      else if (selectUpdatesLeft)
      {
         selection.tl.x = pt.x;
         selection.br.y = pt.y;
      }
      else
         selection.br = pt;

      hideCursor ();
      redraw ();
   }

   void
   Vterm::selectUpdate (int pX, int pY)
   {
      logT << "selectUpdate (" << pX << "," << pY << ")" << std::endl;

      pX = std::min (std::max (0, pX - opts.border), winPx - 2 * opts.border);
      pY = std::min (std::max (0, pY - opts.border), winPy - 2 * opts.border);
      Point pt (pX / glyphPx, pY / glyphPy);

      Rect& selection = cf->getSelection ();

      if (selection.rectangular)
      {
         if (selectUpdatesLeft && pt.x > selection.br.x)
         {
            std::swap (selection.tl.x, selection.br.x);
            selectUpdatesLeft = false;
         }
         else if (!selectUpdatesLeft && pt.x < selection.tl.x)
         {
            std::swap (selection.tl.x, selection.br.x);
            selectUpdatesLeft = true;
         }

         if (selectUpdatesTop && pt.y > selection.br.y)
         {
            std::swap (selection.tl.y, selection.br.y);
            selectUpdatesTop = false;
         }
         else if (!selectUpdatesTop && pt.y < selection.tl.y)
         {
            std::swap (selection.tl.y, selection.br.y);
            selectUpdatesTop = true;
         }

         if (selectUpdatesTop && selectUpdatesLeft)
            selection.tl = pt;
         else if (selectUpdatesTop)
         {
            selection.br.x = pt.x;
            selection.tl.y = pt.y;
         }
         else if (selectUpdatesLeft)
         {
            selection.tl.x = pt.x;
            selection.br.y = pt.y;
         }
         else
            selection.br = pt;
      }
      else if (selectUpdatesTop)
      {
         if (selection.br < pt)
         {
            selection.tl = selection.br;
            selection.br = pt;
            selectUpdatesTop = selectUpdatesLeft = false;
         }
         else
         {
            selection.tl = pt;
         }
      }
      else
      {
         if (pt < selection.tl)
         {
            selection.br = selection.tl;
            selection.tl = pt;
            selectUpdatesTop = selectUpdatesLeft = true;
         }
         else
         {
            selection.br = pt;
         }
      }
      redraw ();
   }

   bool
   Vterm::selectFinish (std::string& utf8_selection)
   {
      logT << "selectFinish ()" << std::endl;

      showCursor ();
      redraw ();

      return  cf->getSelectedUtf8 (utf8_selection);
   }

   void
   Vterm::selectClear ()
   {
      logT << "selectClear ()" << std::endl;
      cf->getSelection ().clear ();
      redraw ();
   }

   void
   Vterm::selectRectangularModeToggle ()
   {
      logT << "selectRectangularModeToggle ()" << std::endl;
      cf->getSelection ().toggleRectangular ();
      redraw ();
   }

   void
   Vterm::pasteSelection (const std::string& utf8_selection)
   {
      std::ostringstream oss;

      if (bracketedPasteMode)
         oss << "\e[200~";

      for (const auto ch: utf8_selection)
         oss << (ch == '\n' ? '\r' : ch);

      if (bracketedPasteMode)
         oss << "\e[201~";

      if (oss.str ().size ())
         writePty (oss.str ().c_str (), true);
   }

} // namespace zutty
