/*
 * terminalemulation.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */


#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stropts.h>

#include <vdr/config.h>
#include <vdr/remote.h>

#include "terminalemulation.h"
#include "i18n.h"




cConsTerminalEmulation::cConsTerminalEmulation() {

  _w = _h = 0;
  _curX = _curY = 0;

  _modeCurVisible = true;  // TODO load default values
  _modeOrigin = false;
  _modeWrapAround = true;
  _modeInsert = false;
  _modeNewLine = false;

  _curPosSaved = NULL;

  _scrollRegionTop = 0;
  //@@TODO
  _scrollRegionBottom = 23;// MUST BE (_h - 1) !!!

  _state = eCtsNormal;

  _changed = false;
  _bell = false;

  _wantRefreshEvent = false;

  _escapeParams = NULL;
  _escapeParamsCount = 0;

  // initialize all possibilities
  for (int i = 0; i < CONSOLE_MAXROWS; ++i) {
    _canvas[i] = 0;
  }

  // initialize tabulators
  int tab = 0;
  for (int i = 0; i < CONSOLE_MAXCOLS; ++i) {
    _tabs[i] = tab += 8;
  }

  // No idea if this is the right size but it is
  // a good value to start.
  // The real size will be setted from the OSD menu.
  //@@TODO setSize(Setup.OSDwidth, Setup.OSDheight - 2);
  setSize(60, 24);

  SelectCharSet(0, 'B');
}



cConsTerminalEmulation::~cConsTerminalEmulation() {

  // No thread should use this object if it will be destroyed.
  cMutexLock l(&_mutex);

  // releasing all allocated buffers
  for (int i = 0; i < _h; ++i) {
    free(_canvas[i]);
  }

  while (_curPosSaved) {
    sConsScreenCursorPos* prev = _curPosSaved->prev;
    delete _curPosSaved;
    _curPosSaved = prev;
  }
}



void cConsTerminalEmulation::SelectCharSet(int g, char set) {

//  fprintf(stderr, "New CharSet selected: %i, %c\n", g, set);

  // First reset all chars
  for (int i = 0; i <= 255; ++i)
    _charSet[i] = i;

  if (set == 'A' || set == 'B') {

    // Mapping the pseudo graphics to our special values in the lower 32 chars.
    _charSet[ 179 ] = 0x19; // vertical line
    _charSet[ 180 ] = 0x16; // right tee
    _charSet[ 181 ] = 0x16; // right tee
    _charSet[ 182 ] = 0x16; // right tee
    _charSet[ 183 ] = 0xC; // upper_right_corner
    _charSet[ 184 ] = 0xC; // upper_right_corner
    _charSet[ 185 ] = 0x16; // right tee
    _charSet[ 186 ] = 0x19; // vertical line
    _charSet[ 187 ] = 0xC; // upper_right_corner
    _charSet[ 188 ] = 0xB; // lower_right_corner
    _charSet[ 189 ] = 0xB; // lower_right_corner
    _charSet[ 190 ] = 0xB; // lower_right_corner
    _charSet[ 191 ] = 0xC; // upper_right_corner
    _charSet[ 192 ] = 0xE; // lower_left_corner
    _charSet[ 193 ] = 0x17; // bottom tee
    _charSet[ 194 ] = 0x18; // top tee
    _charSet[ 195 ] = 0x15; // left tee
    _charSet[ 196 ] = 0x12; // horizontal line / scan line 7
    _charSet[ 197 ] = 0xF; // cross
    _charSet[ 198 ] = 0x15; // left tee
    _charSet[ 199 ] = 0x15; // left tee
    _charSet[ 200 ] = 0xE; // lower_left_corner
    _charSet[ 201 ] = 0xD; // upper_left_corner
    _charSet[ 202 ] = 0x17; // bottom tee
    _charSet[ 203 ] = 0x18; // top tee
    _charSet[ 204 ] = 0x15; // left tee
    _charSet[ 205 ] = 0x12; // horizontal line / scan line 7
    _charSet[ 206 ] = 0xF; // cross
    _charSet[ 207 ] = 0x17; // bottom tee
    _charSet[ 208 ] = 0x17; // bottom tee
    _charSet[ 209 ] = 0x18; // top tee
    _charSet[ 210 ] = 0x18; // top tee
    _charSet[ 211 ] = 0xE; // lower_left_corner
    _charSet[ 212 ] = 0xE; // lower_left_corner
    _charSet[ 213 ] = 0xD; // upper_left_corner
    _charSet[ 214 ] = 0xD; // upper_left_corner
    _charSet[ 215 ] = 0xF; // cross
    _charSet[ 216 ] = 0xF; // cross
    _charSet[ 217 ] = 0xB; // lower_right_corner
    _charSet[ 218 ] = 0xD; // upper_left_corner
    // 219..240 missing :-(
    _charSet[ 241 ] =  32; // plus minus
    _charSet[ 242 ] = 0x1B; // greater than or equal
    _charSet[ 243 ] = 0x1A; // less than or equal
    // 244..255 missing :-(

  } else if (g == 1 && set == '0') {
//  } else if (set == '0') {

    // Map the 7-bit special chars to the 8-bit ASCII chars
/*
    _charSet[  96 ] =  32;
    _charSet[  97 ] = 177;
    _charSet[  98 ] =  32;
    _charSet[  99 ] =  32;
    _charSet[ 100 ] =  32;
    _charSet[ 101 ] =  32;
    _charSet[ 102 ] = 248;
    _charSet[ 103 ] = 241;
    _charSet[ 104 ] =  32;
    _charSet[ 105 ] =  32;
    _charSet[ 106 ] = 217;
    _charSet[ 107 ] = 191;
    _charSet[ 108 ] = 218;
    _charSet[ 109 ] = 192;
    _charSet[ 110 ] = 197;
    _charSet[ 111 ] = 196;
    _charSet[ 112 ] = 196;
    _charSet[ 113 ] = 196;
    _charSet[ 114 ] = 196;
    _charSet[ 115 ] = 196;
    _charSet[ 116 ] = 195;
    _charSet[ 117 ] = 180;
    _charSet[ 118 ] = 193;
    _charSet[ 119 ] = 194;
    _charSet[ 120 ] = 179;
    _charSet[ 121 ] = 243;
    _charSet[ 122 ] = 242;
    _charSet[ 123 ] = 227;
    _charSet[ 124 ] =  32;
    _charSet[ 125 ] = 156;
    _charSet[ 126 ] = 249;
*/
// New: Mapping to the lower 32 chars
    _charSet[  96 ] = 0x1; // diamond
    _charSet[  97 ] =  32; // 50% grid
    _charSet[  98 ] =  32;
    _charSet[  99 ] =  32;
    _charSet[ 100 ] =  32;
    _charSet[ 101 ] =  32;
    _charSet[ 102 ] =  32; // degree
    _charSet[ 103 ] =  32; // plus minus
    _charSet[ 104 ] =  32;
    _charSet[ 105 ] =  32;
    _charSet[ 106 ] = 0xB; // lower_right_corner
    _charSet[ 107 ] = 0xC; // upper_right_corner
    _charSet[ 108 ] = 0xD; // upper_left_corner
    _charSet[ 109 ] = 0xE; // lower_left_corner
    _charSet[ 110 ] = 0xF; // cross
    _charSet[ 111 ] = 0x10; // scan line 1
    _charSet[ 112 ] = 0x11; // scan line 3
    _charSet[ 113 ] = 0x12; // horizontal line / scan line 5
    _charSet[ 114 ] = 0x13; // scan line 7 / scan line 9
    _charSet[ 115 ] = 0x14; // scan line 9 / horizontal line
    _charSet[ 116 ] = 0x15; // left tee
    _charSet[ 117 ] = 0x16; // right tee
    _charSet[ 118 ] = 0x17; // bottom tee
    _charSet[ 119 ] = 0x18; // top tee
    _charSet[ 120 ] = 0x19; // vertical line
    _charSet[ 121 ] = 0x1A; // less than or equal
    _charSet[ 122 ] = 0x1B; // greater than or equal
    _charSet[ 123 ] =  32; // pi
    _charSet[ 124 ] =  32; // not equal
    _charSet[ 125 ] =  32; // british pound
    _charSet[ 126 ] =  32; // dot

  }
}



void cConsTerminalEmulation::Changed() {

  // Must not be locked here, becaus it can
  // only be called from a method that is
  // already locked.
  if (! _changed && _wantRefreshEvent) {
    _changed = true;
    cRemote::Put((eKeys)kRefresh);
  }
  _changed = true;
}



void cConsTerminalEmulation::Refreshed() {
  _changed = false;
}



void cConsTerminalEmulation::BellSeen() {
  _bell = false;
}



bool cConsTerminalEmulation::setSize(int w, int h) {

  cMutexLock l(&_mutex);

  // if nothing is to do then exit
  if (w == _w && h == _h)
    return false;

// fprintf(stderr, "Area resized: old=%i, %i; new=%i, %i\n", _w, _h, w, h);

  // if the width is the same as before when we don't need to
  // realloc rows that exist already and should exist afterwards. 
  int RowsNoChangeNeeded = min(h, _h);

  if (w != _w) {

    for (int row = 0; row < RowsNoChangeNeeded; ++row) {

      sConsCanvasChar* &screenRow = _canvas[row];
      screenRow = (sConsCanvasChar*) realloc((void*)screenRow, w * sizeof(sConsCanvasChar));

      // initialize new cols if any
      for(int col = _w; col < w; ++col) {
        screenRow[col] = _defaultChar;
      }
    }
  }

  // allocating new rows if any
  for (int row = RowsNoChangeNeeded; row < h; ++row) {
    
    sConsCanvasChar* &screenRow = _canvas[row];
    screenRow = (sConsCanvasChar*) realloc((void*)screenRow, w * sizeof(sConsCanvasChar));
    
    // initialize new cols
    for (int col = 0; col < w; ++col) {
      screenRow[col] = _defaultChar;
    }
  }


  // releasing old rows if any
  for (int row = h; row < _h; ++row) {
    sConsCanvasChar* &screenRow = _canvas[row];
    free(screenRow);
    screenRow = 0;
  }

  // change scroll region
  if (_scrollRegionTop == 0 && _scrollRegionBottom == _h - 1)
    _scrollRegionBottom = h - 1;

  // save new size
  _w = w; _h = h;

  Changed();

  return true;
}



// Clears the hole screen
void cConsTerminalEmulation::Clear(int fromX, int fromY, int toX, int toY) {

  // check the range
  if (fromX < 0)
    fromX = 0;
  else if (fromX >= _w)
    fromX = _w - 1;
  
  if (fromY < 0)
    fromY = 0;
  else if (fromY >= _h)
    fromY = _h - 1;

  if (toX < 0 || toX >= _w)
    toX = _w - 1;

  if (toY < 0 || toY >= _h)
    toY = _h - 1;


  for (int row = fromY; row <= toY; ++row) {

    sConsCanvasChar* screenRow = _canvas[row];

    for (int col = fromX; col <= toX; ++col) {
      screenRow[col] = _defaultChar;
    }
  }

  Changed();
}



void cConsTerminalEmulation::ScrollUp(int count, int FromLine) {

  // scroll only if we are in the scrolling range
  if (_curY >= _scrollRegionTop && _curY <= _scrollRegionBottom) {

    for (int i = 0; i < count; ++i) {

      free(_canvas[FromLine]);

      for (int row = FromLine; row <= _scrollRegionBottom - 1; ++row) {
        _canvas[row] = _canvas[row + 1];
      }

      sConsCanvasChar* &screenRow = _canvas[_scrollRegionBottom];
      screenRow = (sConsCanvasChar*) malloc(_w * sizeof(sConsCanvasChar));

      for (int col = 0; col < _w; ++col) {
        screenRow[col] = _defaultChar;
      }
    }

    Changed();
  }
}



void cConsTerminalEmulation::ScrollDown(int count, int FromLine) {

  // scroll only if we are in the scrolling range
  if (_curY >= _scrollRegionTop && _curY <= _scrollRegionBottom) {

    for (int i = 0; i < count; ++i) {

      free(_canvas[_scrollRegionBottom]);

      for (int row = _scrollRegionBottom - 1; row >= FromLine; --row) {
        _canvas[row + 1] = _canvas[row];
      }

      sConsCanvasChar* &screenRow = _canvas[FromLine];
      screenRow = (sConsCanvasChar*) malloc(_w * sizeof(sConsCanvasChar));
  
      for (int col = 0; col < _w; ++col) {
        screenRow[col] = _defaultChar;
      }
    }

    Changed();
  }
}



void cConsTerminalEmulation::Write(const unsigned char* stream) {

  cMutexLock l(&_mutex);

  if (*stream) {

    while (*stream) {
      Write(*stream);
      ++stream;
    }

  } else {
    // Signal, that the client has terminated
    if (_wantRefreshEvent)
      cRemote::Put((eKeys)kRefresh);
  }
}



void cConsTerminalEmulation::Write(unsigned char ch) {

#if 0

  if (ch == '\n')
    fprintf(stderr,"{\\n}");
  else if (ch == '\r')
    fprintf(stderr,"{\\r}");
  else if (ch == '\b')
    fprintf(stderr,"{\\b}");
  else if (ch == '\t')
    fprintf(stderr,"{\\t}");
  else if (ch == 27)
    fprintf(stderr,"{ESC}");
  else if (ch < 32)
    fprintf(stderr,"{\\%i}", ch);
  else if (ch >= 32)
    fprintf(stderr,"%c", ch);

#endif

  if (ch == SI)
    SelectCharSet(0, '0');
  if (ch == SO)
    SelectCharSet(1, '0');


  // search for escape sequences
  switch (_state) {

  case eCtsNormal:

    switch (ch) {

    // interpret control chars
    case ESC:   _state = eCtsEscape;  break; // begin of escape sequence

    case CR:    keyCarriageReturn();  break;
    case LF:
    case FF:
    case VT:    keyLineFeed(true);     break;

    case HT:    keyTab();             break;
    case BS:
    case 127:   keyBackspace();       break;
    case '\a':  keyBell();            break; // 7

    // display all other chars
    default:    keyInsert(_charSet[ch]);
    }
    break;

  case eCtsEscape:

    if (ch == '[') {

      // ok, the escape sequence begins here
      _state = eCtsEscapeParameter;

    } else if (ch == '#') {
      _state = eCtsEscapeSingleCode;

    } else if (ch == '(') {
      _state = eCtsSelectCharSetG0;


    } else if (ch == ')') {
      _state = eCtsSelectCharSetG1;
    
    } else {

      decodeEscapeCode(ch);

      // and back to normal mode
      _state = eCtsNormal;
    }
    break;

  case eCtsEscapeParameter:

    switch (ch) {
      case '0' ... '9':

        if (_escapeParams == 0) {

          // for the first parameter we must allocate memory
        
          _escapeParams = (int*) malloc(sizeof(int));
          *_escapeParams = ch - '0';
          _escapeParamsCount = 1;
      
        } else {

          // assemble figures to a number
          int& Param = _escapeParams[_escapeParamsCount - 1];
          Param = Param * 10 + (ch - '0');
        }
        break;

      case ';':

        // here a new parameter follows
        _escapeParams = (int*) realloc((void*) _escapeParams, ++_escapeParamsCount * sizeof(int));
        _escapeParams[_escapeParamsCount - 1] = 0;
        break;

      case '?':
        break; // ignore the questionmark

      case CAN:
      case SUB:

        // cancel the sequence
        free(_escapeParams);
        _escapeParams = NULL;
        _escapeParamsCount = 0;

        _state = eCtsNormal;
        break;

      default:

        // end of sequence reached
        decodeEscapeSequence(ch);

        free(_escapeParams);
        _escapeParams = NULL;
        _escapeParamsCount = 0;

        _state = eCtsNormal;
        break;
    }

    break;

  case eCtsEscapeSingleCode:

    decodeEscapeSingleCode(ch);
    _state = eCtsNormal;
    break;

  case eCtsSelectCharSetG0:
  case eCtsSelectCharSetG1:

    SelectCharSet(_state - eCtsSelectCharSetG0, ch);
    _state = eCtsNormal;
    break;

  }
}




#define CONSOLE_PARAM(_index, _default) ((_index < _escapeParamsCount) ? _escapeParams[_index] : _default)

void cConsTerminalEmulation::decodeEscapeSequence(char code) {

  switch (code) {

  case 'G':   MoveTo(CONSOLE_PARAM(0, 1) - 1, _curY);                            break; // move to column
  case 'd':   MoveTo(_curX, CONSOLE_PARAM(0, 1) - 1);                            break; // move to line
  case 'f':
  case 'H':   MoveTo(CONSOLE_PARAM(1, 1) - 1, CONSOLE_PARAM(0, 1) - 1);	         break; // move to position

  case 'e':
  case 'A':   MoveRelative(0, - CONSOLE_PARAM(0, 1));                            break; // n rows up
  case 'B':   MoveRelative(0,   CONSOLE_PARAM(0, 1));                            break; // n rows down
  case 'a':
  case 'C':   MoveRelative(CONSOLE_PARAM(0, 1), 0);                              break; // n cols right
  case 'D':   MoveRelative(- CONSOLE_PARAM(0, 1), 0);                            break; // n cols left
  case 'E':   MoveRelative(-_curX,   CONSOLE_PARAM(0, 1));                       break; // n rows down, first col
  case 'F':   MoveRelative(-_curX, - CONSOLE_PARAM(0, 1));                       break; // n rows up, first col


  case 'K':   if (_escapeParamsCount == 0 || _escapeParams[0] == 0) {            // Clear to end of line
                ClearToEnd();

              } else if (_escapeParams[0] == 1) {                                // Clear from begin of line
                ClearFromBegin();

              } else if (_escapeParams[0] == 2) {                                // Clear the hole line
                ClearLine();
              }                                                                  break;


  case 'J':   if (_escapeParamsCount == 0 || _escapeParams[0] == 0) {            // Clear to end of screen
                ClearToEnd();
                if (_modeOrigin)
                  Clear(0, _curY + 1, _w - 1, _scrollRegionBottom);
                else
                  Clear(0, _curY + 1, _w - 1, _h - 1);

              } else if (_escapeParams[0] == 1) {                                // Clear from begin of screen
                if (_modeOrigin)
                  Clear(0, _scrollRegionTop, _w - 1, _curY - 1);
                else
                  Clear(0, 0, _w - 1, _curY - 1);
                ClearFromBegin();

              } else if (_escapeParams[0] == 2) {                                // Clear the hole screen
                if (_modeOrigin)
                  Clear(0, _scrollRegionTop, _w - 1, _scrollRegionBottom);
                else
                  Clear();
              }
              MoveTo(0, 0);                                                      break;

  case 'n':   reportDeviceStatus(CONSOLE_PARAM(0, 0));                           break;
  case 'c':   reportDeviceAttributes(CONSOLE_PARAM(0, 0));                       break;

  case 'm':   setAttributes(_escapeParams, _escapeParamsCount);                  break; // set display attributes

  case 'h':   setModes(_escapeParams, _escapeParamsCount);                       break; // put in screen mode
  case 'l':   resetModes(_escapeParams, _escapeParamsCount);                     break; // resets Mode

  case 'r':   setScrollRegion(CONSOLE_PARAM(0, 1) - 1, CONSOLE_PARAM(1, CONSOLE_MAXROWS) - 1);    break;
  case 's':   CursorPosSave();                                                   break; // saves cursor position
  case 'u':   CursorPosRestore();                                                break; // return to saved cursor position

  case 'g':   if (_escapeParamsCount == 0 || _escapeParams[0] == 0) {            // Clear tab at position
                tabStopRemove(_curY);

              } else if (_escapeParams[0] == 3) {                                // Clear all tabs
                tabStopClear();
              }                                                                  break;

  case 'W':   if (_escapeParamsCount == 0 || _escapeParams[0] == 0) {            // Add tab at position
                tabStopAdd(_curY);

              } else if (_escapeParams[0] == 2) {                                // Clear tab at position
                tabStopRemove(_curY );

              } else if (_escapeParams[0] == 5) {                                // Clear all tabs
                tabStopClear();
              }                                                                  break;

  case '@':   InsertChar(CONSOLE_PARAM(0, 1));                                   break; // insert n spaces
  case 'X':
  case 'P':   DeleteChar(CONSOLE_PARAM(0, 1));                                   break; // Delete n chars
  case 'L':   ScrollDown(CONSOLE_PARAM(0, 1), _curY);                            break; // Insert n lines
  case 'M':   ScrollUp(CONSOLE_PARAM(0, 1), _curY);                              break; // Delete n lines


  // ignore these
  case 'y':   break; // self test
  case 'q':   break; // show lamp on keyboard (1..4, 0 = off)

  default:    CONSOLE_DEBUG( 
                fprintf(stderr,"unknown escape sequence %c ", code);
                for (int i = 0; i < _escapeParamsCount; ++i) {
                  fprintf(stderr, "%i;", _escapeParams[i]);
                }
                fprintf(stderr,"\n");
              );
              break;
  }
}



void cConsTerminalEmulation::decodeEscapeCode(char code) {

  switch (code) {

  case 'D':   keyLineFeed(false);                                                break;
  case 'E':   keyLineFeed(false); MoveTo(0, _curY);                              break;

  case 'M':   if (_curY == _scrollRegionTop)
                ScrollDown(1);
              else if (_curY > 0) {
                --_curY; Changed();
              }                                                                  break;

  case '@':   if (_curX < _w) { ++_curX; DeleteChar(1); --_curX; }               break; // discard subsequent char

  case 'H':   tabStopAdd(_curX);                                                 break; // set column tab

  case 'Z':   reportDeviceAttributes(0);                                         break;

  case '7':   CursorPosSave();                                                   break;
  case '8':   CursorPosRestore();                                                break;

  // ignore these
  case '=':   break; // Application Keypad
  case '>':   break; // Numeric Keypad
  case 'c':   break; // reset terminal

  default:  CONSOLE_DEBUG( printf("unknown escape code %c\n", code));
  }
}



void cConsTerminalEmulation::decodeEscapeSingleCode(char code) {

  switch (code) {

  // not supported
  case '3':          // double height/width
  case '4':
  case '5':          // single width line
  case '6': break;

  case '8': break;   // show test pattern

  default:  CONSOLE_DEBUG( printf("unknown escape single code %c\n", code));
  }
}



void cConsTerminalEmulation::tabStopClear() {

  for (int i = 0; i < CONSOLE_MAXCOLS; ++i) {
    _tabs[i] = 0;
  }
}



void cConsTerminalEmulation::tabStopAdd(int tabstop) {

  // search for right position in tab stop array
  for (int i = 0; i < CONSOLE_MAXCOLS; ++i) {

    if (_tabs[i] == tabstop) {
      // the tab stop is already here
      break;

    } else if (_tabs[i] == 0) {
      // append on the end
      _tabs[i] = tabstop;
      break;

    } else if (tabstop < _tabs[i]) {

      //found the right place to insert
      for (int j = CONSOLE_MAXROWS - 2; j >= i; --j) {
        _tabs[j + 1] = _tabs[j];
      }
      _tabs[i] = tabstop;
      break;
    }
  }
}



void cConsTerminalEmulation::tabStopRemove(int tabstop) {

  // search the position of the tab stop
  for (int i = 0; i < CONSOLE_MAXCOLS; ++i) {

    if (_tabs[i] == tabstop) {

      int j = i;
      for (; j < CONSOLE_MAXCOLS - 1; ++j) {
        _tabs[j] = _tabs[j + 1];
      }
      if (j == CONSOLE_MAXCOLS - 1) {
        _tabs[j] = 0;
      }
      break;
    }
  }
}



void cConsTerminalEmulation::MoveTo(int col, int row) {

  int x = min(max(col, 0), _w - 1);
  int y;
  if (_modeOrigin)
    y = min(max(row, _scrollRegionTop), _scrollRegionBottom);
  else
    y = min(max(row, 0), _h - 1);

  if (x != _curX || y != _curY) {
    _curX = x; _curY = y;

    if (_modeCurVisible)
      Changed();
  }
}



void cConsTerminalEmulation::MoveRelative(int d_col, int d_row) {

  MoveTo(_curX + d_col, _curY + d_row);
}



void cConsTerminalEmulation::ClearFromBegin() {

  sConsCanvasChar* text = _canvas[_curY];

  for (int col = 0; col <= _curX; ++col) {
    text[col] = _defaultChar;
  } 

  Changed();
}



void cConsTerminalEmulation::ClearToEnd() {

  sConsCanvasChar* text = _canvas[_curY];

  for (int col = _curX; col < _w; ++col) {
    text[col] = _defaultChar;
  } 

  Changed();
}



void cConsTerminalEmulation::ClearLine() {

  sConsCanvasChar* text = _canvas[_curY];

  for (int col = 0; col < _w; ++col) {
    text[col] = _defaultChar;
  } 

  Changed();
}



void cConsTerminalEmulation::InsertChar(int count) {

  if (count > 0) {

    if (count > _w - _curX)
      count = _w - _curX;

    sConsCanvasChar* text = _canvas[_curY];

    for (int i = _w - count - 1; i >= _curX; --i) {
      text[i + count] = text[i];
    }

    // clear the moved cols and keep the attributes from the first col
    for (int i = _curX; i < _curX + count; ++i) {
      text[i] = _defaultChar; //sConsCanvasChar(_defaultChar.ch, text[_curX]);
    }

    Changed();
  }
}



void cConsTerminalEmulation::DeleteChar(int count) {

  if (count > 0) {

    if (count > _w - _curX)
      count = _w - _curX;

    sConsCanvasChar* text = _canvas[_curY];

    for (int i = _curX; i < _w - count; ++i) {
      text[i] = text[i + count];
    }

    // clear the erased cols and keep the attributes from the last col
    for (int i = _w - count; i < _w; ++i) {
      text[i] = sConsCanvasChar(_defaultChar.ch, text[_w - 1]);
    }

    Changed();
  }
}



void cConsTerminalEmulation::reportDeviceStatus(int request) {
/*
  if (_fd >= 0) {

    char buf[10];

    switch (request) {

      case 5:   write(_fd, buf, sprintf(buf, "%c[0n", ESC));                     break;	// -> no malfunctions
      case 15:  write(_fd, buf, sprintf(buf, "%c[?13n", ESC));                   break;	// -> no printer connected

      case 6:   write(_fd, buf, sprintf(buf, "%c[%i;%iR", ESC, getCursorY() + 1, getCursorX() + 1)); break; // -> report curor position

      default:  CONSOLE_DEBUG(fprintf(stderr, "CONSOLE: Unknown device status request: %i\n", request));
    }
  } else
    dsyslog("console: file descriptor for output not set!");
*/
}



void cConsTerminalEmulation::reportDeviceAttributes(int request) {
/*
  if (_fd >= 0) {

    char buf[10];

    switch (request) {

      case 0:   write(_fd, buf, sprintf(buf, "%c[?1;2c", ESC));                  break;	// -> i am a VT102

      default:  CONSOLE_DEBUG(fprintf(stderr, "CONSOLE: Unknown device attributes request: %i\n", request));
    }
  } else
    dsyslog("console: file descriptor for output not set!");
*/
}











void cConsTerminalEmulation::keyCarriageReturn() {

  MoveTo(0, _curY);
}



void cConsTerminalEmulation::keyLineFeed(bool NewLine) {

  // are we in the scrolling area?
  if (_curY == _scrollRegionBottom) {
      ScrollUp(1);

  } else {

    // no, don't scroll
    if (_curY < _h - 1)
      ++_curY;
      if (_modeCurVisible)
        Changed();
  }

  if (NewLine && _modeNewLine) {
    keyCarriageReturn();
  }
}



void cConsTerminalEmulation::keyBackspace() {

  if (_curX > 0) {

    // in insert mode, first move the other chars leftwards
    if (_modeInsert) {

      sConsCanvasChar* text = _canvas[_curY];

      for (int i = _curX - 1; i < _w - 1; ++i) {
        text[i] = text[i + 1];
      }
    }

    --_curX;
    Changed();
  }
}



void cConsTerminalEmulation::keyDelete() {

  sConsCanvasChar* text = _canvas[_curY];

  for (int col = _curX; col < _w - 1; ++col) {
    text[col] = text[col + 1];
  }
  text[_w - 1] = _defaultChar;

  Changed();
}



void cConsTerminalEmulation::keyTab() {

  for (int i = 0; i < CONSOLE_MAXCOLS; ++i) {

    if (_tabs[i] > _curX) {

      for (int j = _tabs[i] - _curX; j > 0; --j)
        keyInsert(' ');

      return;
    }
    if (_tabs[i] == 0)
      break;
  }

  // no tab found -> do nothing
}



void cConsTerminalEmulation::keyInsert(unsigned char ch) {

  // ignore all not printable chars
//  if (ch < ' ')
//    return;

  int x = _curX, y = _curY;

  // This is to prevent a line feed if the cursor is on
  // the last col. In this case the cursor stayes out of
  // the screen until it will be placed elsewhere or
  // another char will put in. Then a line feed and if
  // necessary also a scroll up will accour.

  if (_modeWrapAround) {

    if (++_curX > _w) {
      keyCarriageReturn(); keyLineFeed(false);
      return;
    }
  } else {

    if (++_curX >= _w)
      _curX = x = _w - 1;
  }

  sConsCanvasChar* text = _canvas[y];

  // in insert mode, first move the other chars rightwards
  if (_modeInsert) {

    for (int i = _w - 1; i > x; --i) {
      text[i] = text[i - 1];
    }
  }

  text[x] = sConsCanvasChar(ch, _defaultChar);

  Changed();
}



void cConsTerminalEmulation::keyBell() {

  // misuse the incoming console (tty) to produce the bell for us
  printf("\a"); fflush(STDIN_FILENO);

  if (! _bell && _wantRefreshEvent) {
    _bell = true;
    cRemote::Put((eKeys)kRefresh);
  }
  _bell = true;
}



void cConsTerminalEmulation::setScrollRegion(int top, int bottom) {

  if (top < 0)
    top = 0;
  else if (top >= _h - 1)
    top = _h - 2;

  // range minimum 2 rows!
  if (bottom < top + 1)
    bottom = top + 1;
  else if (bottom >= _h)
    bottom = _h - 1;

  _scrollRegionTop = top;
  _scrollRegionBottom = bottom;
}



void cConsTerminalEmulation::CursorPosSave() {

  _curPosSaved = new sConsScreenCursorPos(_curX, _curY, _defaultChar, _modeOrigin, _curPosSaved);
}



void cConsTerminalEmulation::CursorPosRestore() {

  sConsScreenCursorPos* pOld = _curPosSaved;
  if (pOld) {

    _modeOrigin  = pOld->modeOrigin;

    if (_curX != pOld->x || _curY != pOld->y) {
      _curX = pOld->x; _curY = pOld->y;
      Changed();
    }

    _defaultChar = pOld->attributes;

    _curPosSaved = pOld->prev;
    delete pOld;

  } else {
    // No cursor position stored, so set the cursor to home.
    MoveTo(0, 0);
  }
}



void cConsTerminalEmulation::setAttributes(int* attributes, int count) {

  if (count > 0) {

    for (int i = 0; i < count; ++i) {

      switch (attributes[i]) {

      case 0:  _defaultChar = sConsCanvasChar();        break;  // reset all attributes

      case  1: _defaultChar.Bold        = true;         break;  // set style
      case 22: _defaultChar.Bold        = false;        break;  // set style
      case  4: _defaultChar.Underscore  = true;         break;
      case 24: _defaultChar.Underscore  = false;        break;
      case  5: _defaultChar.Blink       = true;         break;
      case 25: _defaultChar.Blink       = false;        break;
      case  7: _defaultChar.Inverted    = true;         break;
      case 27: _defaultChar.Inverted    = false;        break;
      case  8: _defaultChar.Concealed   = true;         break;
      case 28: _defaultChar.Concealed   = false;        break;

      case 30 ... 37:                                           // set foreground color
               _defaultChar.foreColor = attributes[i] - 30;
               _defaultChar.useFore = true;             break;
      case 39: _defaultChar.useFore = false;            break;

      case 40 ... 47:                                           // set background color
               _defaultChar.backColor = attributes[i] - 40;
               _defaultChar.useBack = true;             break;
      case 49: _defaultChar.useBack = false;            break;

      default: return;                                  break;
      }

    } // end for

  } else {

    // no params -> reset attributes
    _defaultChar = sConsCanvasChar();
  }
}



void cConsTerminalEmulation::setModes(int* attributes, int count) {

  for (int i = 0; i < count; ++i) {

//CONSOLE_DEBUG(fprintf(stderr, "mode set %i\n", attributes[i]));

    switch (attributes[i]) {
    case 4:    _modeInsert = true;                      break;
    case 6:    setModeOrigin(true);                     break;
    case 7:    _modeWrapAround = true; MoveTo(0, 0);    break;
    case 20:   _modeNewLine = true;                     break;
    case 25:   setModeCurVisible(true);                 break;

    default: CONSOLE_DEBUG(fprintf(stderr, "unknown mode set %i\n", attributes[i]));
    }
  }
}



void cConsTerminalEmulation::resetModes(int* attributes, int count) {

  for (int i = 0; i < count; ++i) {

//CONSOLE_DEBUG(fprintf(stderr, "mode reset %i\n", attributes[i]));

    switch (attributes[i]) {
    case 4:    _modeInsert = false;                     break;
    case 6:    setModeOrigin(false);                    break;
    case 7:    _modeWrapAround = false; MoveTo(0, 0);   break;
    case 20:   _modeNewLine = false;                    break;
    case 25:   setModeCurVisible(false);                break;

    default: CONSOLE_DEBUG(fprintf(stderr, "unknown mode reset %i\n", attributes[i]));
    }
  }
}



void cConsTerminalEmulation::setModeCurVisible(bool visible) {

  if (_modeCurVisible != visible) {
    _modeCurVisible = visible;
    Changed();
  }
}



void cConsTerminalEmulation::setModeOrigin(bool origin) {

  if (_modeOrigin != origin) {
    _modeOrigin = origin;
    MoveTo(0, 0);
  }
}


