/*
   This file is part of Konsole, an X terminal.

   Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com>
   Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>

   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 "Screen.h"

// Qt
#include <QTextStream>

// Konsole
#include "TerminalCharacterDecoder.h"
#include "History.h"
#include "ExtendedCharTable.h"

using namespace Konsole;

//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

const Character Screen::DefaultChar = Character(' ',
                                      CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR),
                                      CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR),
                                      DEFAULT_RENDITION,
                                      false);

Screen::Screen(int lines, int columns):
    _currentTerminalDisplay(nullptr),
    _lines(lines),
    _columns(columns),
    _screenLines(new ImageLine[_lines + 1]),
    _screenLinesSize(_lines),
    _scrolledLines(0),
    _lastScrolledRegion(QRect()),
    _droppedLines(0),
    _lineProperties(QVarLengthArray<LineProperty, 64>()),
    _history(new HistoryScrollNone()),
    _cuX(0),
    _cuY(0),
    _currentForeground(CharacterColor()),
    _currentBackground(CharacterColor()),
    _currentRendition(DEFAULT_RENDITION),
    _topMargin(0),
    _bottomMargin(0),
    _tabStops(QBitArray()),
    _selBegin(0),
    _selTopLeft(0),
    _selBottomRight(0),
    _blockSelectionMode(false),
    _effectiveForeground(CharacterColor()),
    _effectiveBackground(CharacterColor()),
    _effectiveRendition(DEFAULT_RENDITION),
    _lastPos(-1),
    _lastDrawnChar(0)
{
    _lineProperties.resize(_lines + 1);
    for (int i = 0; i < _lines + 1; i++) {
        _lineProperties[i] = LINE_DEFAULT;
    }

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

Screen::~Screen()
{
    delete[] _screenLines;
    delete _history;
}

void Screen::cursorUp(int n)
//=CUU
{
    if (n == 0) {
        n = 1; // Default
    }
    const int stop = _cuY < _topMargin ? 0 : _topMargin;
    _cuX = qMin(_columns - 1, _cuX); // nowrap!
    _cuY = qMax(stop, _cuY - n);
}

void Screen::cursorDown(int n)
//=CUD
{
    if (n == 0) {
       n = 1; // Default
    }
    const int stop = _cuY > _bottomMargin ? _lines - 1 : _bottomMargin;
    _cuX = qMin(_columns - 1, _cuX); // nowrap!
    _cuY = qMin(stop, _cuY + n);
}

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

void Screen::cursorNextLine(int n)
//=CNL
{
    if (n == 0) {
        n = 1; // Default
    }
    _cuX = 0;
    while (n > 0) {
        if (_cuY < _lines - 1) {
            _cuY += 1;
        }
        n--;
    }

}

void Screen::cursorPreviousLine(int n)
//=CPL
{
    if (n == 0) {
        n = 1; // Default
    }
    _cuX = 0;
    while (n > 0) {
        if (_cuY  > 0) {
            _cuY -= 1;
        }
        n--;
    }
}

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)) {
        //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range.";
        return;                   // Default error action: ignore
    }
    _topMargin = top;
    _bottomMargin = bot;
    _cuX = 0;
    _cuY = getMode(MODE_Origin) ? top : 0;
}

int Screen::topMargin() const
{
    return _topMargin;
}
int Screen::bottomMargin() const
{
    return _bottomMargin;
}

void Screen::index()
//=IND
{
    if (_cuY == _bottomMargin) {
        scrollUp(1);
    } else if (_cuY < _lines - 1) {
        _cuY += 1;
    }
}

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

void Screen::nextLine()
//=NEL
{
    toStartOfLine();
    index();
}

void Screen::eraseChars(int n)
{
    if (n == 0) {
        n = 1; // Default
    }
    const 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() - _cuX;
    }

    Q_ASSERT(n >= 0);
    Q_ASSERT(_cuX + n <= _screenLines[_cuY].count());

    _screenLines[_cuY].remove(_cuX, n);

    // Append space(s) with current attributes
    Character spaceWithCurrentAttrs(' ', _effectiveForeground,
                                    _effectiveBackground,
                                    _effectiveRendition, false);

    for (int i = 0; i < n; i++) {
        _screenLines[_cuY].append(spaceWithCurrentAttrs);
    }
}

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, Character(' '));

    if (_screenLines[_cuY].count() > _columns) {
        _screenLines[_cuY].resize(_columns);
    }
}

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

    // From ECMA-48 version 5, section 8.3.103:
    // "If the character preceding REP is a control function or part of a
    // control function, the effect of REP is not defined by this Standard."
    //
    // So, a "normal" program should always use REP immediately after a visible
    // character (those other than escape sequences). So, _lastDrawnChar can be
    // safely used.
    for (int i = 0; i < n; i++) {
        displayCharacter(_lastDrawnChar);
    }
}

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

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

void Screen::setMode(int m)
{
    _currentModes[m] = 1;
    switch (m) {
    case MODE_Origin :
        _cuX = 0;
        _cuY = _topMargin;
        break; //FIXME: home
    }
}

void Screen::resetMode(int m)
{
    _currentModes[m] = 0;
    switch (m) {
    case MODE_Origin :
        _cuX = 0;
        _cuY = 0;
        break; //FIXME: home
    }
}

void Screen::saveMode(int m)
{
    _savedModes[m] = _currentModes[m];
}

void Screen::restoreMode(int m)
{
    _currentModes[m] = _savedModes[m];
}

bool Screen::getMode(int m) const
{
    return _currentModes[m] != 0;
}

void Screen::saveCursor()
{
    _savedState.cursorColumn = _cuX;
    _savedState.cursorLine  = _cuY;
    _savedState.rendition = _currentRendition;
    _savedState.foreground = _currentForeground;
    _savedState.background = _currentBackground;
}

void Screen::restoreCursor()
{
    _cuX     = qMin(_savedState.cursorColumn, _columns - 1);
    _cuY     = qMin(_savedState.cursorLine, _lines - 1);
    _currentRendition   = _savedState.rendition;
    _currentForeground   = _savedState.foreground;
    _currentBackground   = _savedState.background;
    updateEffectiveRendition();
}

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
        _bottomMargin = _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

    auto newScreenLines = new ImageLine[new_lines + 1];
    for (int i = 0; i < qMin(_lines, new_lines + 1) ; i++) {
        newScreenLines[i] = _screenLines[i];
    }

    _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;
    _screenLinesSize = new_lines;

    _lines = new_lines;
    _columns = new_columns;
    _cuX = qMin(_cuX, _columns - 1);
    _cuY = qMin(_cuY, _lines - 1);

    // FIXME: try to keep values, evtl.
    _topMargin = 0;
    _bottomMargin = _lines - 1;
    initTabStops();
    clearSelection();
}

void Screen::setDefaultMargins()
{
    _topMargin = 0;
    _bottomMargin = _lines - 1;
}

/*
   Clarifying rendition here and in the display.

   The rendition attributes are

   attribute
   --------------
   RE_UNDERLINE
   RE_BLINK
   RE_BOLD
   RE_REVERSE
   RE_TRANSPARENT
   RE_FAINT
   RE_STRIKEOUT
   RE_CONCEAL
   RE_OVERLINE

   Depending on settings, bold may be rendered as a heavier font
   in addition to a different color.
   */

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::updateEffectiveRendition()
{
    _effectiveRendition = _currentRendition;
    if ((_currentRendition & RE_REVERSE) != 0) {
        _effectiveForeground = _currentBackground;
        _effectiveBackground = _currentForeground;
    } else {
        _effectiveForeground = _currentForeground;
        _effectiveBackground = _currentBackground;
    }

    if ((_currentRendition & RE_BOLD) != 0) {
        if ((_currentRendition & RE_FAINT) == 0) {
            _effectiveForeground.setIntensive();
        }
    } else {
        if ((_currentRendition & RE_FAINT) != 0) {
            _effectiveForeground.setFaint();
        }
    }
}

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

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

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

        for (int column = length; column < _columns; column++) {
            dest[destLineOffset + column] = Screen::DefaultChar;
        }

        // invert selected text
        if (_selBegin != -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, Screen::DefaultChar);

            // invert selected text
            if (_selBegin != -1 && isSelected(column, line + _history->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 < _history->getLines() + _lines);

    const int mergedLines = endLine - startLine + 1;

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

    const int linesInHistoryBuffer = qBound(0, _history->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 - _history->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
        }
    }

    int visX = qMin(_cuX, _columns - 1);
    // mark the character at the current cursor position
    int cursorIndex = loc(visX, _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 < _history->getLines() + _lines);

    const int mergedLines = endLine - startLine + 1;
    const int linesInHistory = qBound(0, _history->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 (_history->isWrappedLine(line)) {
            result[index] = static_cast<LineProperty>(result[index] | LINE_WRAPPED);
        }
        index++;
    }

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

    return result;
}

void Screen::reset()
{
    // Clear screen, but preserve the current line
    scrollUp(0, _cuY);
    _cuY = 0;

    _currentModes[MODE_Origin] = 0;
    _savedModes[MODE_Origin] = 0;

    setMode(MODE_Wrap);
    saveMode(MODE_Wrap);      // wrap at end of margin

    resetMode(MODE_Insert);
    saveMode(MODE_Insert);  // overstroke

    setMode(MODE_Cursor);                         // cursor visible
    resetMode(MODE_Screen);                         // screen not inverse
    resetMode(MODE_NewLine);

    _topMargin = 0;
    _bottomMargin = _lines - 1;

    // Other terminal emulators reset the entire scroll history during a reset
//    setScroll(getScroll(), false);

    setDefaultRendition();
    saveCursor();
}

void Screen::backspace()
{
    _cuX = qMin(_columns - 1, _cuX); // nowrap!
    _cuX = qMax(0, _cuX - 1);

    if (_screenLines[_cuY].size() < _cuX + 1) {
        _screenLines[_cuY].resize(_cuX + 1);
    }
}

void Screen::tab(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::backtab(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()
{
    _tabStops.resize(_columns);

    // 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);
    }
}

void Screen::newLine()
{
    if (getMode(MODE_NewLine)) {
        toStartOfLine();
    }

    index();
}

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

void Screen::displayCharacter(uint 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 = Character::width(c);

    if (w < 0) {
        // Non-printable character
        return;
    } else if (w == 0) {
        const QChar::Category category = QChar::category(c);
        if (category != QChar::Mark_NonSpacing && category != QChar::Letter_Other) {
            return;
        }
        // Find previous "real character" to try to combine with
        int charToCombineWithX = qMin(_cuX, _screenLines[_cuY].length());
        int charToCombineWithY = _cuY;
        do {
            if (charToCombineWithX > 0) {
                charToCombineWithX--;
            } else if (charToCombineWithY > 0) { // Try previous line
                charToCombineWithY--;
                charToCombineWithX = _screenLines[charToCombineWithY].length() - 1;
            } else {
                // Give up
                return;
            }

            // Failsafe
            if (charToCombineWithX < 0) {
                return;
            }
        } while(!_screenLines[charToCombineWithY][charToCombineWithX].isRealCharacter);

        Character& currentChar = _screenLines[charToCombineWithY][charToCombineWithX];
        if ((currentChar.rendition & RE_EXTENDED_CHAR) == 0) {
            const uint chars[2] = { currentChar.character, c };
            currentChar.rendition |= RE_EXTENDED_CHAR;
            currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, 2);
        } else {
            ushort extendedCharLength;
            const uint* oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
            Q_ASSERT(oldChars);
            if (((oldChars) != nullptr) && extendedCharLength < 3) {
                Q_ASSERT(extendedCharLength > 1);
                Q_ASSERT(extendedCharLength < 65535); // redundant due to above check
                auto chars = new uint[extendedCharLength + 1];
                memcpy(chars, oldChars, sizeof(uint) * extendedCharLength);
                chars[extendedCharLength] = c;
                currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, extendedCharLength + 1);
                delete[] chars;
            }
        }
        return;
    }

    if (_cuX + w > _columns) {
        if (getMode(MODE_Wrap)) {
            _lineProperties[_cuY] = static_cast<LineProperty>(_lineProperties[_cuY] | LINE_WRAPPED);
            nextLine();
        } else {
            _cuX = qMax(_columns - w, 0);
        }
    }

    // ensure current line vector has enough elements
    if (_screenLines[_cuY].size() < _cuX + w) {
        _screenLines[_cuY].resize(_cuX + w);
    }

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

    _lastPos = loc(_cuX, _cuY);

    // check if selection is still valid.
    checkSelection(_lastPos, _lastPos);

    Character& currentChar = _screenLines[_cuY][_cuX];

    currentChar.character = c;
    currentChar.foregroundColor = _effectiveForeground;
    currentChar.backgroundColor = _effectiveBackground;
    currentChar.rendition = _effectiveRendition;
    currentChar.isRealCharacter = true;

    _lastDrawnChar = c;

    int i = 0;
    const int newCursorX = _cuX + w--;
    while (w != 0) {
        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 = _effectiveForeground;
        ch.backgroundColor = _effectiveBackground;
        ch.rendition = _effectiveRendition;
        ch.isRealCharacter = false;

        w--;
    }
    _cuX = newCursorX;
}

int Screen::scrolledLines() const
{
    return _scrolledLines;
}
int Screen::droppedLines() const
{
    return _droppedLines;
}
void Screen::resetDroppedLines()
{
    _droppedLines = 0;
}
void Screen::resetScrolledLines()
{
    _scrolledLines = 0;
}

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

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

void Screen::scrollUp(int from, int n)
{
    if (n <= 0) {
        return;
    }
    if (from > _bottomMargin) {
        return;
    }
    if (from + n > _bottomMargin) {
        n = _bottomMargin + 1 - from;
    }

    _scrolledLines -= n;
    _lastScrolledRegion = QRect(0, _topMargin, _columns - 1, (_bottomMargin - _topMargin));

    //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
    moveImage(loc(0, from), loc(0, from + n), loc(_columns, _bottomMargin));
    clearImage(loc(0, _bottomMargin - n + 1), loc(_columns - 1, _bottomMargin), ' ');
}

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

void Screen::scrollDown(int from, int n)
{
    _scrolledLines += n;

    //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
    if (n <= 0) {
        return;
    }
    if (from > _bottomMargin) {
        return;
    }
    if (from + n > _bottomMargin) {
        n = _bottomMargin - from;
    }
    moveImage(loc(0, from + n), loc(0, from), loc(_columns - 1, _bottomMargin - 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) ? _topMargin : 0)));
}

void Screen::toStartOfLine()
{
    _cuX = 0;
}

int Screen::getCursorX() const
{
    return qMin(_cuX, _columns - 1);
}

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

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

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

    const int topLine = loca / _columns;
    const int bottomLine = loce / _columns;

    Character clearCh(uint(c), _currentForeground, _currentBackground, DEFAULT_RENDITION, false);

    //if the character being used to clear the area is the same as the
    //default character, the affected _lines can simply be shrunk.
    const bool isDefaultCh = (clearCh == Screen::DefaultChar);

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

        const int endCol = (y == bottomLine) ? loce % _columns : _columns - 1;
        const 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;
            }
        }
    }
}

void Screen::moveImage(int dest, int sourceBegin, int sourceEnd)
{
    Q_ASSERT(sourceBegin <= sourceEnd);

    const 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) {
        const int diff = dest - sourceBegin; // Scroll by this amount
        _lastPos += diff;
        if ((_lastPos < 0) || (_lastPos >= (lines * _columns))) {
            _lastPos = -1;
        }
    }

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

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

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

        if (_selBottomRight < 0) {
            clearSelection();
        } else {
            if (_selTopLeft < 0) {
                _selTopLeft = 0;
            }
        }

        if (beginIsTL) {
            _selBegin = _selTopLeft;
        } else {
            _selBegin = _selBottomRight;
        }
    }
}

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()
{
    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(RenditionFlags rendition)
{
    _currentRendition |= rendition;
    updateEffectiveRendition();
}

void Screen::resetRendition(RenditionFlags rendition)
{
    _currentRendition &= ~rendition;
    updateEffectiveRendition();
}

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

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

    if (_currentForeground.isValid()) {
        updateEffectiveRendition();
    } else {
        setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
    }
}

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

    if (_currentBackground.isValid()) {
        updateEffectiveRendition();
    } else {
        setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
    }
}

void Screen::clearSelection()
{
    _selBottomRight = -1;
    _selTopLeft = -1;
    _selBegin = -1;
}

bool Screen::hasSelection() const
{
    return _selBegin != -1;
}

void Screen::getSelectionStart(int& column , int& line) const
{
    if (_selTopLeft != -1) {
        column = _selTopLeft % _columns;
        line = _selTopLeft / _columns;
    } else {
        column = _cuX + getHistLines();
        line = _cuY + getHistLines();
    }
}
void Screen::getSelectionEnd(int& column , int& line) const
{
    if (_selBottomRight != -1) {
        column = _selBottomRight % _columns;
        line = _selBottomRight / _columns;
    } else {
        column = _cuX + getHistLines();
        line = _cuY + getHistLines();
    }
}
void Screen::setSelectionStart(const int x, const int y, const bool blockSelectionMode)
{
    _selBegin = loc(x, y);
    /* FIXME, HACK to correct for x too far to the right... */
    if (x == _columns) {
        _selBegin--;
    }

    _selBottomRight = _selBegin;
    _selTopLeft = _selBegin;
    _blockSelectionMode = blockSelectionMode;
}

void Screen::setSelectionEnd(const int x, const int y)
{
    if (_selBegin == -1) {
        return;
    }

    int endPos =  loc(x, y);

    if (endPos < _selBegin) {
        _selTopLeft = endPos;
        _selBottomRight = _selBegin;
    } else {
        /* FIXME, HACK to correct for x too far to the right... */
        if (x == _columns) {
            endPos--;
        }

        _selTopLeft = _selBegin;
        _selBottomRight = endPos;
    }

    // Normalize the selection in column mode
    if (_blockSelectionMode) {
        const int topRow = _selTopLeft / _columns;
        const int topColumn = _selTopLeft % _columns;
        const int bottomRow = _selBottomRight / _columns;
        const int bottomColumn = _selBottomRight % _columns;

        _selTopLeft = loc(qMin(topColumn, bottomColumn), topRow);
        _selBottomRight = loc(qMax(topColumn, bottomColumn), bottomRow);
    }
}

bool Screen::isSelected(const int x, const int y) const
{
    bool columnInSelection = true;
    if (_blockSelectionMode) {
        columnInSelection = x >= (_selTopLeft % _columns) &&
                            x <= (_selBottomRight % _columns);
    }

    const int pos = loc(x, y);
    return pos >= _selTopLeft && pos <= _selBottomRight && columnInSelection;
}

QString Screen::selectedText(const DecodingOptions options) const
{
    if (!isSelectionValid()) {
        return QString();
    }

    return text(_selTopLeft, _selBottomRight, options);
}

QString Screen::text(int startIndex, int endIndex, const DecodingOptions options) const
{
    QString result;
    QTextStream stream(&result, QIODevice::ReadWrite);

    HTMLDecoder htmlDecoder;
    PlainTextDecoder plainTextDecoder;

    TerminalCharacterDecoder *decoder;
    if((options & ConvertToHtml) != 0u) {
        decoder = &htmlDecoder;
    } else {
        decoder = &plainTextDecoder;
    }

    decoder->begin(&stream);
    writeToStream(decoder, startIndex, endIndex, options);
    decoder->end();

    return result;
}

bool Screen::isSelectionValid() const
{
    return _selTopLeft >= 0 && _selBottomRight >= 0;
}

void Screen::writeToStream(TerminalCharacterDecoder* decoder,
                           int startIndex, int endIndex,
                           const DecodingOptions options) const
{
    const int top = startIndex / _columns;
    const int left = startIndex % _columns;

    const int bottom = endIndex / _columns;
    const int right = endIndex % _columns;

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

    for (int y = top; y <= bottom; y++) {
        int start = 0;
        if (y == top || _blockSelectionMode) {
            start = left;
        }

        int count = -1;
        if (y == bottom || _blockSelectionMode) {
            count = right - start + 1;
        }

        const bool appendNewLine = (y != bottom);
        int copied = copyLineToStream(y,
                                      start,
                                      count,
                                      decoder,
                                      appendNewLine,
                                      options);

        // if the selection goes beyond the end of the last line then
        // append a new line character.
        //
        // this makes it possible to 'select' a trailing new line character after
        // the text on a line.
        if (y == bottom &&
                copied < count &&
                !options.testFlag(TrimTrailingWhitespace)) {
            Character newLineChar('\n');
            decoder->decodeLine(&newLineChar, 1, 0);
        }
    }
}

int Screen::getLineLength(const int line) const
{
  // determine if the line is in the history buffer or the screen image
  const bool isInHistoryBuffer = line < _history->getLines();

  if (isInHistoryBuffer) {
    return _history->getLineLen(line);
  }

  return _columns;
}

Character *Screen::getCharacterBuffer(const int size)
{
  // buffer to hold characters for decoding
  // the buffer is static to avoid initializing every
  // element on each call to copyLineToStream
  // (which is unnecessary since all elements will be overwritten anyway)
  static const int MAX_CHARS = 1024;
  static QVector<Character> characterBuffer(MAX_CHARS);

  if (characterBuffer.count() < size) {
    characterBuffer.resize(size);
  }

  return characterBuffer.data();
}

int Screen::copyLineToStream(int line ,
                             int start,
                             int count,
                             TerminalCharacterDecoder* decoder,
                             bool appendNewLine,
                             const DecodingOptions options) const
{
    const int lineLength = getLineLength(line);
    // ensure that this method, can append space or 'eol' character to
    // the selection
    Character *characterBuffer = getCharacterBuffer((count > -1 ? count : lineLength - start) + 1);
    LineProperty currentLineProperties = 0;

    // determine if the line is in the history buffer or the screen image
    if (line < _history->getLines()) {
        // ensure that start position is before end of line
        start = qMin(start, qMax(0, lineLength - 1));

        // retrieve line from history buffer.  It is assumed
        // that the history buffer does not store trailing white space
        // at the end of the line, so it does not need to be trimmed here
        if (count == -1) {
            count = lineLength - start;
        } else {
            count = qMin(start + count, lineLength) - start;
        }

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

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

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

        Q_ASSERT(count >= 0);

        int screenLine = line - _history->getLines();

        Q_ASSERT(screenLine <= _screenLinesSize);

        screenLine = qMin(screenLine, _screenLinesSize);

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

        // Don't remove end spaces in lines that wrap
        if (options.testFlag(TrimTrailingWhitespace) && ((_lineProperties[screenLine] & LINE_WRAPPED) == 0))
        {
            // ignore trailing white space at the end of the line
            for (int i = length-1; i >= 0; i--)
            {
                if (QChar(data[i].character).isSpace()) {
                    length--;
                } else {
                    break;
                }
            }
        }

        //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];
    }

    if (appendNewLine) {
        if ((currentLineProperties & LINE_WRAPPED) != 0) {
            // do nothing extra when this line is wrapped.
        } else {
            // When users ask not to preserve the linebreaks, they usually mean:
            // `treat LINEBREAK as SPACE, thus joining multiple _lines into
            // single line in the same way as 'J' does in VIM.`
            characterBuffer[count] = options.testFlag(PreserveLineBreaks) ? Character('\n') : Character(' ');
            count++;
        }
    }

    if ((options & TrimLeadingWhitespace) != 0u) {
        int spacesCount = 0;
        for (spacesCount = 0; spacesCount < count; spacesCount++) {
            if (QChar::category(characterBuffer[spacesCount].character) != QChar::Category::Separator_Space) {
                break;
            }
        }

        if (spacesCount >= count) {
            return 0;
        }

        for (int i=0; i < count - spacesCount; i++) {
            characterBuffer[i] = characterBuffer[i + spacesCount];
        }

        count -= spacesCount;
    }

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

    return count;
}

void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const
{
    writeToStream(decoder, loc(0, fromLine), loc(_columns - 1, toLine), PreserveLineBreaks);
}

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

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

        _history->addCellsVector(_screenLines[0]);
        _history->addLine((_lineProperties[0] & LINE_WRAPPED) != 0);

        const int newHistLines = _history->getLines();

        const bool beginIsTL = (_selBegin == _selTopLeft);

        // 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 (_selBegin != -1) {
                _selTopLeft += _columns;
                _selBottomRight += _columns;
            }
        }

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

            if (_selTopLeft < top_BR) {
                _selTopLeft -= _columns;
            }

            if (_selBottomRight < top_BR) {
                _selBottomRight -= _columns;
            }

            if (_selBottomRight < 0) {
                clearSelection();
            } else {
                if (_selTopLeft < 0) {
                    _selTopLeft = 0;
                }
            }

            if (beginIsTL) {
                _selBegin = _selTopLeft;
            } else {
                _selBegin = _selBottomRight;
            }
        }
    }
}

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

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

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

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

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

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