/*
    This file is part of Konsole, an X terminal.
    Copyright (C) 1997-1998, 2013 by Lars Doelle <lars.doelle@on-line.de>

    Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008

    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 2 of the License, or
    (at your option) any later version.

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

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/

// Own
#include "unix/Screen.h"

// Standard
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>

// Qt
#include <QtCore/QTextStream>
#include <QtCore/QDate>

// Konsole
#include "unix/konsole_wcwidth.h"
#include "unix/TerminalCharacterDecoder.h"

//FIXME: this is emulation specific. Use false for xterm, true for ANSI.
//FIXME: see if we can get this from terminfo.
#define BS_CLEARS false

//Macro to convert x,y position on screen to position within an image.
//
//Originally the image was stored as one large contiguous block of
//memory, so a position within the image could be represented as an
//offset from the beginning of the block.  For efficiency reasons this
//is no longer the case.
//Many internal parts of this class still use this representation for parameters and so on,
//notably moveImage() and clearImage().
//This macro converts from an X,Y position into an image offset.
#ifndef loc
#define loc(X,Y) ((Y)*columns+(X))
#endif


Character Screen::defaultChar = Character(' ',
					  CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR),
					  CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR),
					  DEFAULT_RENDITION);

//#define REVERSE_WRAPPED_LINES  // for wrapped line debug

Screen::Screen(int l, int c)
  : lines(l),
    columns(c),
    screenLines(new ImageLine[lines+1] ),
    _scrolledLines(0),
    _droppedLines(0),
    hist(new HistoryScrollNone()),
    cuX(0), cuY(0),
    cu_re(0),
    tmargin(0), bmargin(0),
    tabstops(0),
    sel_begin(0), sel_TL(0), sel_BR(0),
    sel_busy(false),
    columnmode(false),
    ef_fg(CharacterColor()), ef_bg(CharacterColor()), ef_re(0),
    sa_cuX(0), sa_cuY(0),
    sa_cu_re(0),
    lastPos(-1)
{
  lineProperties.resize(lines+1);
  for (int i=0;i<lines+1;i++)
          lineProperties[i]=LINE_DEFAULT;

  initTabStops();
  clearSelection();
  reset();
}

/*! Destructor
*/

Screen::~Screen()
{
  delete[] screenLines;
  delete[] tabstops;
  delete hist;
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/* Normalized                    Screen Operations                           */
/*                                                                           */
/* ------------------------------------------------------------------------- */

// Cursor Setting --------------------------------------------------------------

/*! @section Cursor

    The `cursor' is a location within the screen that is implicitely used in
    many operations. The operations within this section allow to manipulate
    the cursor explicitly and to obtain it's value.

    The position of the cursor is guarantied to be between (including) 0 and
    `columns-1' and `lines-1'.
*/

/*!
    Move the cursor up.

    The cursor will not be moved beyond the top margin.
*/

void Screen::cursorUp(int n)
//=CUU
{
  if (n == 0) n = 1; // Default
  int stop = cuY < tmargin ? 0 : tmargin;
  cuX = qMin(columns-1,cuX); // nowrap!
  cuY = qMax(stop,cuY-n);
}

/*!
    Move the cursor down.

    The cursor will not be moved beyond the bottom margin.
*/

void Screen::cursorDown(int n)
//=CUD
{
  if (n == 0) n = 1; // Default
  int stop = cuY > bmargin ? lines-1 : bmargin;
  cuX = qMin(columns-1,cuX); // nowrap!
  cuY = qMin(stop,cuY+n);
}

/*!
    Move the cursor left.

    The cursor will not move beyond the first column.
*/

void Screen::cursorLeft(int n)
//=CUB
{
  if (n == 0) n = 1; // Default
  cuX = qMin(columns-1,cuX); // nowrap!
  cuX = qMax(0,cuX-n);
}

/*!
    Move the cursor left.

    The cursor will not move beyond the rightmost column.
*/

void Screen::cursorRight(int n)
//=CUF
{
  if (n == 0) n = 1; // Default
  cuX = qMin(columns-1,cuX+n);
}

void Screen::setMargins(int top, int bot)
//=STBM
{
  if (top == 0) top = 1;      // Default
  if (bot == 0) bot = lines;  // Default
  top = top - 1;              // Adjust to internal lineno
  bot = bot - 1;              // Adjust to internal lineno
  if ( !( 0 <= top && top < bot && bot < lines ) )
  { qDebug()<<" setRegion("<<top<<","<<bot<<") : bad range.";
    return;                   // Default error action: ignore
  }
  tmargin = top;
  bmargin = bot;
  cuX = 0;
  cuY = getMode(MODE_Origin) ? top : 0;

}

int Screen::topMargin() const
{
    return tmargin;
}
int Screen::bottomMargin() const
{
    return bmargin;
}

void Screen::index()
//=IND
{
  if (cuY == bmargin)
  {
    scrollUp(1);
  }
  else if (cuY < lines-1)
    cuY += 1;
}

void Screen::reverseIndex()
//=RI
{
  if (cuY == tmargin)
     scrollDown(tmargin,1);
  else if (cuY > 0)
    cuY -= 1;
}

/*!
    Move the cursor to the begin of the next line.

    If cursor is on bottom margin, the region between the
    actual top and bottom margin is scrolled up.
*/

void Screen::NextLine()
//=NEL
{
  Return(); index();
}

void Screen::eraseChars(int n)
{
  if (n == 0) n = 1; // Default
  int p = qMax(0,qMin(cuX+n-1,columns-1));
  clearImage(loc(cuX,cuY),loc(p,cuY),' ');
}

void Screen::deleteChars(int n)
{
  Q_ASSERT( n >= 0 );

  // always delete at least one char
  if (n == 0)
      n = 1;

  // if cursor is beyond the end of the line there is nothing to do
  if ( cuX >= screenLines[cuY].count() )
      return;

  if ( cuX+n >= screenLines[cuY].count() )
       n = screenLines[cuY].count() - 1 - cuX;

  Q_ASSERT( n >= 0 );
  Q_ASSERT( cuX+n < screenLines[cuY].count() );

  screenLines[cuY].remove(cuX,n);
}

void Screen::insertChars(int n)
{
  if (n == 0) n = 1; // Default

  if ( screenLines[cuY].size() < cuX )
    screenLines[cuY].resize(cuX);

  screenLines[cuY].insert(cuX,n,' ');

  if ( screenLines[cuY].count() > columns )
      screenLines[cuY].resize(columns);
}

void Screen::deleteLines(int n)
{
  if (n == 0) n = 1; // Default
  scrollUp(cuY,n);
}

/*! insert `n' lines at the cursor position.

    The cursor is not moved by the operation.
*/

void Screen::insertLines(int n)
{
  if (n == 0) n = 1; // Default
  scrollDown(cuY,n);
}

// Mode Operations -----------------------------------------------------------

/*! Set a specific mode. */

void Screen::setMode(int m)
{
  currParm.mode[m] = true;
  switch(m)
  {
    case MODE_Origin : cuX = 0; cuY = tmargin; break; //FIXME: home
  }
}

/*! Reset a specific mode. */

void Screen::resetMode(int m)
{
  currParm.mode[m] = false;
  switch(m)
  {
    case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home
  }
}

/*! Save a specific mode. */

void Screen::saveMode(int m)
{
  saveParm.mode[m] = currParm.mode[m];
}

/*! Restore a specific mode. */

void Screen::restoreMode(int m)
{
  currParm.mode[m] = saveParm.mode[m];
}

bool Screen::getMode(int m) const
{
  return currParm.mode[m];
}

void Screen::saveCursor()
{
  sa_cuX     = cuX;
  sa_cuY     = cuY;
  sa_cu_re   = cu_re;
  sa_cu_fg   = cu_fg;
  sa_cu_bg   = cu_bg;
}

void Screen::restoreCursor()
{
  cuX     = qMin(sa_cuX,columns-1);
  cuY     = qMin(sa_cuY,lines-1);
  cu_re   = sa_cu_re;
  cu_fg   = sa_cu_fg;
  cu_bg   = sa_cu_bg;
  effectiveRendition();
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Screen Operations                             */
/*                                                                           */
/* ------------------------------------------------------------------------- */

/*! Resize the screen image

    The topmost left position is maintained, while lower lines
    or right hand side columns might be removed or filled with
    spaces to fit the new size.

    The region setting is reset to the whole screen and the
    tab positions reinitialized.

    If the new image is narrower than the old image then text on lines
    which extends past the end of the new image is preserved so that it becomes
    visible again if the screen is later resized to make it larger.
*/

void Screen::resizeImage(int new_lines, int new_columns)
{
  if ((new_lines==lines) && (new_columns==columns)) return;

  if (cuY > new_lines-1)
  { // attempt to preserve focus and lines
    bmargin = lines-1; //FIXME: margin lost
    for (int i = 0; i < cuY-(new_lines-1); i++)
    {
      addHistLine(); scrollUp(0,1);
    }
  }

  // create new screen lines and copy from old to new

   ImageLine* newScreenLines = new ImageLine[new_lines+1];
   for (int i=0; i < qMin(lines-1,new_lines+1) ;i++)
           newScreenLines[i]=screenLines[i];
   for (int i=lines;(i > 0) && (i<new_lines+1);i++)
           newScreenLines[i].resize( new_columns );

  lineProperties.resize(new_lines+1);
  for (int i=lines;(i > 0) && (i<new_lines+1);i++)
          lineProperties[i] = LINE_DEFAULT;

  clearSelection();

  delete[] screenLines;
  screenLines = newScreenLines;

  lines = new_lines;
  columns = new_columns;
  cuX = qMin(cuX,columns-1);
  cuY = qMin(cuY,lines-1);

  // FIXME: try to keep values, evtl.
  tmargin=0;
  bmargin=lines-1;
  initTabStops();
  clearSelection();
}

void Screen::setDefaultMargins()
{
	tmargin = 0;
	bmargin = lines-1;
}


/*
   Clarifying rendition here and in the display.

   currently, the display's color table is
     0       1       2 .. 9    10 .. 17
     dft_fg, dft_bg, dim 0..7, intensive 0..7

   cu_fg, cu_bg contain values 0..8;
   - 0    = default color
   - 1..8 = ansi specified color

   re_fg, re_bg contain values 0..17
   due to the TerminalDisplay's color table

   rendition attributes are

      attr           widget screen
      -------------- ------ ------
      RE_UNDERLINE     XX     XX    affects foreground only
      RE_BLINK         XX     XX    affects foreground only
      RE_BOLD          XX     XX    affects foreground only
      RE_REVERSE       --     XX
      RE_TRANSPARENT   XX     --    affects background only
      RE_INTENSIVE     XX     --    affects foreground only

   Note that RE_BOLD is used in both widget
   and screen rendition. Since xterm/vt102
   is to poor to distinguish between bold
   (which is a font attribute) and intensive
   (which is a color attribute), we translate
   this and RE_BOLD in falls eventually appart
   into RE_BOLD and RE_INTENSIVE.
*/

void Screen::reverseRendition(Character& p) const
{
	CharacterColor f = p.foregroundColor;
	CharacterColor b = p.backgroundColor;
  	
	p.foregroundColor = b;
	p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT;
}

void Screen::effectiveRendition()
// calculate rendition
{
  //copy "current rendition" straight into "effective rendition", which is then later copied directly
  //into the image[] array which holds the characters and their appearance properties.
  //- The old version below filtered out all attributes other than underline and blink at this stage,
  //so that they would not be copied into the image[] array and hence would not be visible by TerminalDisplay
  //which actually paints the screen using the information from the image[] array.
  //I don't know why it did this, but I'm fairly sure it was the wrong thing to do.  The net result
  //was that bold text wasn't printed in bold by Konsole.
  ef_re = cu_re;

  //OLD VERSION:
  //ef_re = cu_re & (RE_UNDERLINE | RE_BLINK);

  if (cu_re & RE_REVERSE)
  {
    ef_fg = cu_bg;
    ef_bg = cu_fg;
  }
  else
  {
    ef_fg = cu_fg;
    ef_bg = cu_bg;
  }

  if (cu_re & RE_BOLD)
    ef_fg.toggleIntensive();
}

/*!
    returns the image.

    Get the size of the image by @sa getLines and @sa getColumns.

    NOTE that the image returned by this function must later be
    freed.

*/

void Screen::copyFromHistory(Character* dest, int startLine, int count) const
{
  Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= hist->getLines() );

  for (int line = startLine; line < startLine + count; line++)
  {
    const int length = qMin(columns,hist->getLineLen(line));
    const int destLineOffset  = (line-startLine)*columns;

    hist->getCells(line,0,length,dest + destLineOffset);

    for (int column = length; column < columns; column++)
		dest[destLineOffset+column] = defaultChar;

	// invert selected text
	if (sel_begin !=-1)
	{
    	for (int column = 0; column < columns; column++)
    	{
        	if (isSelected(column,line))
			{
          		reverseRendition(dest[destLineOffset + column]);
    		}
  		}
	}
  }
}

void Screen::copyFromScreen(Character* dest , int startLine , int count) const
{
	Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines );

    for (int line = startLine; line < (startLine+count) ; line++)
    {
       int srcLineStartIndex  = line*columns;
	   int destLineStartIndex = (line-startLine)*columns;

       for (int column = 0; column < columns; column++)
       {
		 int srcIndex = srcLineStartIndex + column;
		 int destIndex = destLineStartIndex + column;

         dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar);

	     // invert selected text
         if (sel_begin != -1 && isSelected(column,line + hist->getLines()))
           reverseRendition(dest[destIndex]);
       }

    }
}

void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const
{
  Q_ASSERT( startLine >= 0 );
  Q_ASSERT( endLine >= startLine && endLine < hist->getLines() + lines );

  const int mergedLines = endLine - startLine + 1;

  Q_ASSERT( size >= mergedLines * columns );
  Q_UNUSED( size );

  const int linesInHistoryBuffer = qBound(0,hist->getLines()-startLine,mergedLines);
  const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer;

  // copy lines from history buffer
  if (linesInHistoryBuffer > 0) {
  	copyFromHistory(dest,startLine,linesInHistoryBuffer);
    }

  // copy lines from screen buffer
  if (linesInScreenBuffer > 0) {
  	copyFromScreen(dest + linesInHistoryBuffer*columns,
				   startLine + linesInHistoryBuffer - hist->getLines(),
				   linesInScreenBuffer);
    }				

  // invert display when in screen mode
  if (getMode(MODE_Screen))
  {
    for (int i = 0; i < mergedLines*columns; i++)
      reverseRendition(dest[i]); // for reverse display
  }

  // mark the character at the current cursor position
  int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer);
  if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines)
    dest[cursorIndex].rendition |= RE_CURSOR;
}

QVector<LineProperty> Screen::getLineProperties( int startLine , int endLine ) const
{
  Q_ASSERT( startLine >= 0 );
  Q_ASSERT( endLine >= startLine && endLine < hist->getLines() + lines );

	const int mergedLines = endLine-startLine+1;
	const int linesInHistory = qBound(0,hist->getLines()-startLine,mergedLines);
  const int linesInScreen = mergedLines - linesInHistory;

  QVector<LineProperty> result(mergedLines);
  int index = 0;

  // copy properties for lines in history
  for (int line = startLine; line < startLine + linesInHistory; line++)
  {
		//TODO Support for line properties other than wrapped lines
	  if (hist->isWrappedLine(line))
	  {
	  	result[index] = (LineProperty)(result[index] | LINE_WRAPPED);
	  }
    index++;
  }

  // copy properties for lines in screen buffer
  const int firstScreenLine = startLine + linesInHistory - hist->getLines();
  for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++)
	{
    result[index]=lineProperties[line];
  	index++;
	}

  return result;
}

/*!
*/

void Screen::reset(bool clearScreen)
{
    setMode(MODE_Wrap  ); saveMode(MODE_Wrap  );  // wrap at end of margin
  resetMode(MODE_Origin); saveMode(MODE_Origin);  // position refere to [1,1]
  resetMode(MODE_Insert); saveMode(MODE_Insert);  // overstroke
    setMode(MODE_Cursor);                         // cursor visible
  resetMode(MODE_Screen);                         // screen not inverse
  resetMode(MODE_NewLine);

  tmargin=0;
  bmargin=lines-1;

  setDefaultRendition();
  saveCursor();

  if ( clearScreen )
    clear();
}

/*! Clear the entire screen and home the cursor.
*/

void Screen::clear()
{
  clearEntireScreen();
  home();
}

void Screen::BackSpace()
{
  cuX = qMin(columns-1,cuX); // nowrap!
  cuX = qMax(0,cuX-1);
 // if (BS_CLEARS) image[loc(cuX,cuY)].character = ' ';

  if (screenLines[cuY].size() < cuX+1)
          screenLines[cuY].resize(cuX+1);

  if (BS_CLEARS) screenLines[cuY][cuX].character = ' ';
}

void Screen::Tabulate(int n)
{
  // note that TAB is a format effector (does not write ' ');
  if (n == 0) n = 1;
  while((n > 0) && (cuX < columns-1))
  {
    cursorRight(1); while((cuX < columns-1) && !tabstops[cuX]) cursorRight(1);
    n--;
  }
}

void Screen::backTabulate(int n)
{
  // note that TAB is a format effector (does not write ' ');
  if (n == 0) n = 1;
  while((n > 0) && (cuX > 0))
  {
     cursorLeft(1); while((cuX > 0) && !tabstops[cuX]) cursorLeft(1);
     n--;
  }
}

void Screen::clearTabStops()
{
  for (int i = 0; i < columns; i++) tabstops[i] = false;
}

void Screen::changeTabStop(bool set)
{
  if (cuX >= columns) return;
  tabstops[cuX] = set;
}

void Screen::initTabStops()
{
  delete[] tabstops;
  tabstops = new bool[columns];

  // Arrg! The 1st tabstop has to be one longer than the other.
  // i.e. the kids start counting from 0 instead of 1.
  // Other programs might behave correctly. Be aware.
  for (int i = 0; i < columns; i++) tabstops[i] = (i%8 == 0 && i != 0);
}

/*!
   This behaves either as IND (Screen::Index) or as NEL (Screen::NextLine)
   depending on the NewLine Mode (LNM). This mode also
   affects the key sequence returned for newline ([CR]LF).
*/

void Screen::NewLine()
{
  if (getMode(MODE_NewLine)) Return();
  index();
}

/*! put `c' literally onto the screen at the current cursor position.

    VT100 uses the convention to produce an automatic newline (am)
    with the *first* character that would fall onto the next line (xenl).
*/

void Screen::checkSelection(int from, int to)
{
  if (sel_begin == -1) return;
  int scr_TL = loc(0, hist->getLines());
  //Clear entire selection if it overlaps region [from, to]
  if ( (sel_BR > (from+scr_TL) )&&(sel_TL < (to+scr_TL)) )
  {
    clearSelection();
  }
}

void Screen::ShowCharacter(unsigned short c)
{
  // Note that VT100 does wrapping BEFORE putting the character.
  // This has impact on the assumption of valid cursor positions.
  // We indicate the fact that a newline has to be triggered by
  // putting the cursor one right to the last column of the screen.

  int w = konsole_wcwidth(c);

  if (w <= 0)
     return;

  if (cuX+w > columns) {
    if (getMode(MODE_Wrap)) {
      lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED);
      NextLine();
    }
    else
      cuX = columns-w;
  }

  // ensure current line vector has enough elements
  int size = screenLines[cuY].size();
  if (size == 0 && cuY > 0)
  {
          screenLines[cuY].resize( qMax(screenLines[cuY-1].size() , cuX+w) );
  }
  else
  {
    if (size < cuX+w)
    {
          screenLines[cuY].resize(cuX+w);
    }
  }

  if (getMode(MODE_Insert)) insertChars(w);

  lastPos = loc(cuX,cuY);

  // clear selection on text input
  clearSelection ();

  Character& currentChar = screenLines[cuY][cuX];

  currentChar.character = c;
  currentChar.foregroundColor = ef_fg;
  currentChar.backgroundColor = ef_bg;
  currentChar.rendition = ef_re;

  int i = 0;
  int newCursorX = cuX + w--;
  while(w)
  {
     i++;

     if ( screenLines[cuY].size() < cuX + i + 1 )
         screenLines[cuY].resize(cuX+i+1);

     Character& ch = screenLines[cuY][cuX + i];
     ch.character = 0;
     ch.foregroundColor = ef_fg;
     ch.backgroundColor = ef_bg;
     ch.rendition = ef_re;

     w--;
  }
  cuX = newCursorX;
}

void Screen::compose(const QString& /*compose*/)
{
   Q_ASSERT( 0 /*Not implemented yet*/ );

/*  if (lastPos == -1)
     return;

  QChar c(image[lastPos].character);
  compose.prepend(c);
  //compose.compose(); ### FIXME!
  image[lastPos].character = compose[0].unicode();*/
}

int Screen::scrolledLines() const
{
        return _scrolledLines;
}
int Screen::droppedLines() const
{
    return _droppedLines;
}
void Screen::resetDroppedLines()
{
    _droppedLines = 0;
}
void Screen::resetScrolledLines()
{
    //kDebug() << "scrolled lines reset";

    _scrolledLines = 0;
}

// Region commands -------------------------------------------------------------

void Screen::scrollUp(int n)
{
   if (n == 0) n = 1; // Default
   if (tmargin == 0) addHistLine(); // hist.history
   scrollUp(tmargin, n);
}

/*! scroll up `n' lines within current region.
    The `n' new lines are cleared.
    @sa setRegion @sa scrollDown
*/

QRect Screen::lastScrolledRegion() const
{
    return _lastScrolledRegion;
}

void Screen::scrollUp(int from, int n)
{
  if (n <= 0 || from + n > bmargin) return;

  _scrolledLines -= n;
  _lastScrolledRegion = QRect(0,tmargin,columns-1,(bmargin-tmargin));

  //FIXME: make sure `tmargin', `bmargin', `from', `n' is in bounds.
  moveImage(loc(0,from),loc(0,from+n),loc(columns-1,bmargin));
  clearImage(loc(0,bmargin-n+1),loc(columns-1,bmargin),' ');
}

void Screen::scrollDown(int n)
{
   if (n == 0) n = 1; // Default
   scrollDown(tmargin, n);
}

/*! scroll down `n' lines within current region.
    The `n' new lines are cleared.
    @sa setRegion @sa scrollUp
*/

void Screen::scrollDown(int from, int n)
{

  //kDebug() << "Screen::scrollDown( from: " << from << " , n: " << n << ")";

  _scrolledLines += n;

//FIXME: make sure `tmargin', `bmargin', `from', `n' is in bounds.
  if (n <= 0) return;
  if (from > bmargin) return;
  if (from + n > bmargin) n = bmargin - from;
  moveImage(loc(0,from+n),loc(0,from),loc(columns-1,bmargin-n));
  clearImage(loc(0,from),loc(columns-1,from+n-1),' ');
}

void Screen::setCursorYX(int y, int x)
{
  setCursorY(y); setCursorX(x);
}

void Screen::setCursorX(int x)
{
  if (x == 0) x = 1; // Default
  x -= 1; // Adjust
  cuX = qMax(0,qMin(columns-1, x));
}

void Screen::setCursorY(int y)
{
  if (y == 0) y = 1; // Default
  y -= 1; // Adjust
  cuY = qMax(0,qMin(lines  -1, y + (getMode(MODE_Origin) ? tmargin : 0) ));
}

void Screen::home()
{
  cuX = 0;
  cuY = 0;
}

void Screen::Return()
{
  cuX = 0;
}

int Screen::getCursorX() const
{
  return cuX;
}

int Screen::getCursorY() const
{
  return cuY;
}

// Erasing ---------------------------------------------------------------------

/*! @section Erasing

    This group of operations erase parts of the screen contents by filling
    it with spaces colored due to the current rendition settings.

    Althought the cursor position is involved in most of these operations,
    it is never modified by them.
*/

/*! fill screen between (including) `loca' (start) and `loce' (end) with spaces.

    This is an internal helper functions. The parameter types are internal
    addresses of within the screen image and make use of the way how the
    screen matrix is mapped to the image vector.
*/

void Screen::clearImage(int loca, int loce, char c)
{
  int scr_TL=loc(0,hist->getLines());
  //FIXME: check positions

  //Clear entire selection if it overlaps region to be moved...
  if ( (sel_BR > (loca+scr_TL) )&&(sel_TL < (loce+scr_TL)) )
  {
    clearSelection();
  }

  int topLine = loca/columns;
  int bottomLine = loce/columns;

  Character clearCh(c,cu_fg,cu_bg,DEFAULT_RENDITION);

  //if the character being used to clear the area is the same as the
  //default character, the affected lines can simply be shrunk.
  bool isDefaultCh = (clearCh == Character());

  for (int y=topLine;y<=bottomLine;y++)
  {
        lineProperties[y] = 0;

        int endCol = ( y == bottomLine) ? loce%columns : columns-1;
        int startCol = ( y == topLine ) ? loca%columns : 0;

        QVector<Character>& line = screenLines[y];

        if ( isDefaultCh && endCol == columns-1 )
        {
            line.resize(startCol);
        }
        else
        {
            if (line.size() < endCol + 1)
                line.resize(endCol+1);

            Character* data = line.data();
            for (int i=startCol;i<=endCol;i++)
                data[i]=clearCh;
        }
  }
}

/*! move image between (including) `sourceBegin' and `sourceEnd' to 'dest'.

    The 'dest', 'sourceBegin' and 'sourceEnd' parameters can be generated using
    the loc(column,line) macro.

NOTE:  moveImage() can only move whole lines.

    This is an internal helper functions. The parameter types are internal
    addresses of within the screen image and make use of the way how the
    screen matrix is mapped to the image vector.
*/

void Screen::moveImage(int dest, int sourceBegin, int sourceEnd)
{
  //kDebug() << "moving image from (" << (sourceBegin/columns)
  //    << "," << (sourceEnd/columns) << ") to " <<
  //    (dest/columns);

  Q_ASSERT( sourceBegin <= sourceEnd );

  int lines=(sourceEnd-sourceBegin)/columns;

  //move screen image and line properties:
  //the source and destination areas of the image may overlap,
  //so it matters that we do the copy in the right order -
  //forwards if dest < sourceBegin or backwards otherwise.
  //(search the web for 'memmove implementation' for details)
  if (dest < sourceBegin)
  {
    for (int i=0;i<=lines;i++)
    {
        screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ];
        lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i];
    }
  }
  else
  {
    for (int i=lines;i>=0;i--)
    {
        screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ];
        lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i];
    }
  }

  if (lastPos != -1)
  {
     int diff = dest - sourceBegin; // Scroll by this amount
     lastPos += diff;
     if ((lastPos < 0) || (lastPos >= (lines*columns)))
        lastPos = -1;
  }

  // Adjust selection to follow scroll.
  if (sel_begin != -1)
  {
     bool beginIsTL = (sel_begin == sel_TL);
     int diff = dest - sourceBegin; // Scroll by this amount
     int scr_TL=loc(0,hist->getLines());
     int srca = sourceBegin+scr_TL; // Translate index from screen to global
     int srce = sourceEnd+scr_TL; // Translate index from screen to global
     int desta = srca+diff;
     int deste = srce+diff;

     if ((sel_TL >= srca) && (sel_TL <= srce))
        sel_TL += diff;
     else if ((sel_TL >= desta) && (sel_TL <= deste))
        sel_BR = -1; // Clear selection (see below)

     if ((sel_BR >= srca) && (sel_BR <= srce))
        sel_BR += diff;
     else if ((sel_BR >= desta) && (sel_BR <= deste))
        sel_BR = -1; // Clear selection (see below)

     if (sel_BR < 0)
     {
        clearSelection();
     }
     else
     {
        if (sel_TL < 0)
           sel_TL = 0;
     }

     if (beginIsTL)
        sel_begin = sel_TL;
     else
        sel_begin = sel_BR;
  }
}

void Screen::clearToEndOfScreen()
{
  clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' ');
}

void Screen::clearToBeginOfScreen()
{
  clearImage(loc(0,0),loc(cuX,cuY),' ');
}

void Screen::clearEntireScreen()
{
  // Add entire screen to history
  for (int i = 0; i < (lines-1); i++)
  {
    addHistLine(); scrollUp(0,1);
  }

  clearImage(loc(0,0),loc(columns-1,lines-1),' ');
}

/*! fill screen with 'E'
    This is to aid screen alignment
*/

void Screen::helpAlign()
{
  clearImage(loc(0,0),loc(columns-1,lines-1),'E');
}

void Screen::clearToEndOfLine()
{
  clearImage(loc(cuX,cuY),loc(columns-1,cuY),' ');
}

void Screen::clearToBeginOfLine()
{
  clearImage(loc(0,cuY),loc(cuX,cuY),' ');
}

void Screen::clearEntireLine()
{
  clearImage(loc(0,cuY),loc(columns-1,cuY),' ');
}

void Screen::setRendition(int re)
{
  cu_re |= re;
  effectiveRendition();
}

void Screen::resetRendition(int re)
{
  cu_re &= ~re;
  effectiveRendition();
}

void Screen::setDefaultRendition()
{
  setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR);
  setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR);
  cu_re   = DEFAULT_RENDITION;
  effectiveRendition();
}

void Screen::setForeColor(int space, int color)
{
  cu_fg = CharacterColor(space, color);

  if ( cu_fg.isValid() )
    effectiveRendition();
  else
    setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR);
}

void Screen::setBackColor(int space, int color)
{
  cu_bg = CharacterColor(space, color);

  if ( cu_bg.isValid() )
    effectiveRendition();
  else
    setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR);
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                            Marking & Selection                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

void Screen::clearSelection()
{
  sel_BR = -1;
  sel_TL = -1;
  sel_begin = -1;
}

void Screen::getSelectionStart(int& column , int& line)
{
    if ( sel_TL != -1 )
    {
        column = sel_TL % columns;
        line = sel_TL / columns;
    }
    else
    {
        column = cuX + getHistLines();
        line = cuY + getHistLines();
    }
}
void Screen::getSelectionEnd(int& column , int& line)
{
    if ( sel_BR != -1 )
    {
        column = sel_BR % columns;
        line = sel_BR / columns;
    }
    else
    {
        column = cuX + getHistLines();
        line = cuY + getHistLines();
    }
}
void Screen::setSelectionStart(/*const ScreenCursor& viewCursor ,*/ const int x, const int y, const bool mode)
{
//  kDebug(1211) << "setSelBeginXY(" << x << "," << y << ")";
  sel_begin = loc(x,y); //+histCursor) ;

  /* FIXME, HACK to correct for x too far to the right... */
  if (x == columns) sel_begin--;

  sel_BR = sel_begin;
  sel_TL = sel_begin;
  columnmode = mode;
}

void Screen::setSelectionEnd( const int x, const int y)
{
//  kDebug(1211) << "setSelExtentXY(" << x << "," << y << ")";
  if (sel_begin == -1) return;
  int l =  loc(x,y); // + histCursor);

  if (l < sel_begin)
  {
    sel_TL = l;
    sel_BR = sel_begin;
  }
  else
  {
    /* FIXME, HACK to correct for x too far to the right... */
    if (x == columns) l--;

    sel_TL = sel_begin;
    sel_BR = l;
  }
}

bool Screen::isSelected( const int x,const int y) const
{
  if (columnmode) {
    int sel_Left,sel_Right;
    if ( sel_TL % columns < sel_BR % columns ) {
      sel_Left = sel_TL; sel_Right = sel_BR;
    } else {
      sel_Left = sel_BR; sel_Right = sel_TL;
    }
    return ( x >= sel_Left % columns ) && ( x <= sel_Right % columns ) &&
           ( y >= sel_TL / columns ) && ( y <= sel_BR / columns );
            //( y+histCursor >= sel_TL / columns ) && ( y+histCursor <= sel_BR / columns );
  }
  else {
  //int pos = loc(x,y+histCursor);
  int pos = loc(x,y);
  return ( pos >= sel_TL && pos <= sel_BR );
  }
}

QString Screen::selectedText(bool preserveLineBreaks)
{
  QString result;
  QTextStream stream(&result, QIODevice::ReadWrite);

  PlainTextDecoder decoder;
  decoder.begin(&stream);
  writeSelectionToStream(&decoder , preserveLineBreaks);
  decoder.end();

  return result;
}

bool Screen::isSelectionValid() const
{
    return ( sel_TL >= 0 && sel_BR >= 0 );
}

void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder ,
                                    bool preserveLineBreaks)
{
    // do nothing if selection is invalid
    if ( !isSelectionValid() )
        return;

	int top = sel_TL / columns;	
	int left = sel_TL % columns;

	int bottom = sel_BR / columns;
	int right = sel_BR % columns;

    Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 );

    //kDebug() << "sel_TL = " << sel_TL;
    //kDebug() << "columns = " << columns;

	for (int y=top;y<=bottom;y++)
	{
			int start = 0;
			if ( y == top || columnmode ) start = left;
		
			int count = -1;
			if ( y == bottom || columnmode ) count = right - start + 1;

            const bool appendNewLine = ( y != bottom );
			copyLineToStream( y,
                              start,
                              count,
                              decoder,
                              appendNewLine,
                              preserveLineBreaks );
	}	
}


void Screen::copyLineToStream(int line ,
                              int start,
                              int count,
                              TerminalCharacterDecoder* decoder,
                              bool appendNewLine,
                              bool preserveLineBreaks)
{
		//buffer to hold characters for decoding
		//the buffer is static to avoid initialising every
        //element on each call to copyLineToStream
		//(which is unnecessary since all elements will be overwritten anyway)
		static const int MAX_CHARS = 1024;
		static Character characterBuffer[MAX_CHARS];
		
		assert( count < MAX_CHARS );

        LineProperty currentLineProperties = 0;

		//determine if the line is in the history buffer or the screen image
		if (line < hist->getLines())
		{
            const int lineLength = hist->getLineLen(line);

            // ensure that start position is before end of line
            start = qMin(start,qMax(0,lineLength-1));

			//retrieve line from history buffer
			if (count == -1)
            {
					count = lineLength-start;
            }
			else
            {
					count = qMin(start+count,lineLength)-start;
            }

            // safety checks
            assert( start >= 0 );
            assert( count >= 0 );
            assert( (start+count) <= hist->getLineLen(line) );

			hist->getCells(line,start,count,characterBuffer);

            if ( hist->isWrappedLine(line) )
                currentLineProperties |= LINE_WRAPPED;
		}
		else
		{
			if ( count == -1 )
					count = columns - start;

            assert( count >= 0 );

            const int screenLine = line-hist->getLines();

            Character* data = screenLines[screenLine].data();
            int length = screenLines[screenLine].count();

			//retrieve line from screen image
			for (int i=start;i < qMin(start+count,length);i++)
			{
			    characterBuffer[i-start] = data[i];
            }

            // count cannot be any greater than length
			count = qBound(0,count,length-start);

            Q_ASSERT( screenLine < lineProperties.count() );
            currentLineProperties |= lineProperties[screenLine];
		}

		//do not decode trailing whitespace characters
		for (int i=count-1 ; i >= 0; i--)
				if (QChar(characterBuffer[i].character).isSpace())
						count--;
				else
						break;

        // add new line character at end
        const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) ||
                                   !preserveLineBreaks;

        if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) )
        {
            characterBuffer[count] = '\n';
            count++;
        }

		//decode line and write to text stream	
		decoder->decodeLine( (Character*) characterBuffer ,
                             count, currentLineProperties );
}

// Method below has been removed because of its reliance on 'histCursor'
// and I want to restrict the methods which have knowledge of the scroll position
// to just those which deal with selection and supplying final screen images.
//
/*void Screen::writeToStream(QTextStream* stream , TerminalCharacterDecoder* decoder) {
  sel_begin = 0;
  sel_BR = sel_begin;
  sel_TL = sel_begin;
  setSelectionEnd(columns-1,lines-1+hist->getLines()-histCursor);

  writeSelectionToStream(stream,decoder);

  clearSelection();
}*/

void Screen::writeToStream(TerminalCharacterDecoder* decoder, int from, int to)
{
	sel_begin = loc(0,from);
	sel_TL = sel_begin;
	sel_BR = loc(columns-1,to);
	writeSelectionToStream(decoder);
	clearSelection();
}

QString Screen::getHistoryLine(int no)
{
  sel_begin = loc(0,no);
  sel_TL = sel_begin;
  sel_BR = loc(columns-1,no);
  return selectedText(false);
}

void Screen::addHistLine()
{
  // add line to history buffer
  // we have to take care about scrolling, too...

  if (hasScroll())
  {
    int oldHistLines = hist->getLines();

    hist->addCellsVector(screenLines[0]);
    hist->addLine( lineProperties[0] & LINE_WRAPPED );

    int newHistLines = hist->getLines();

    bool beginIsTL = (sel_begin == sel_TL);

    // If the history is full, increment the count
    // of dropped lines
    if ( newHistLines == oldHistLines )
        _droppedLines++;

    // Adjust selection for the new point of reference
    if (newHistLines > oldHistLines)
    {
       if (sel_begin != -1)
       {
          sel_TL += columns;
          sel_BR += columns;
       }
    }

    if (sel_begin != -1)
    {
       // Scroll selection in history up
       int top_BR = loc(0, 1+newHistLines);

       if (sel_TL < top_BR)
          sel_TL -= columns;

       if (sel_BR < top_BR)
          sel_BR -= columns;

       if (sel_BR < 0)
       {
          clearSelection();
       }
       else
       {
          if (sel_TL < 0)
             sel_TL = 0;
       }

       if (beginIsTL)
          sel_begin = sel_TL;
       else
          sel_begin = sel_BR;
    }
  }

}

int Screen::getHistLines()
{
  return hist->getLines();
}

void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll)
{
  clearSelection();

  if ( copyPreviousScroll )
    hist = t.scroll(hist);
  else
  {
      HistoryScroll* oldScroll = hist;
      hist = t.scroll(0);
      delete oldScroll;
  }
}

bool Screen::hasScroll()
{
  return hist->hasScroll();
}

const HistoryType& Screen::getScroll()
{
  return hist->getType();
}

void Screen::setLineProperty(LineProperty property , bool enable)
{
	if ( enable )
	{
		lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property);
	}
	else
	{
		lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property);
	}
}
void Screen::fillWithDefaultChar(Character* dest, int count)
{
	for (int i=0;i<count;i++)
		dest[i] = defaultChar;
}
