/*
 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.
*/
#include "Screen.h"

#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <QDate>
#include <QTextStream>

#include "CharWidth.h"
#include "TerminalCharacterDecoder.h"

// 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), history(new HistoryScrollNone()),
            cuX(0), cuY(0), currentRendition(0), _topMargin(0), _bottomMargin(0),
            selBegin(0), selTopLeft(0), selBottomRight(0), blockSelectionMode(false),
            effectiveForeground(CharacterColor()),
            effectiveBackground(CharacterColor()), effectiveRendition(0),
            lastPos(-1) {
    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) {
    if (n == 0)
        n = 1; // Default
    int stop = cuY < _topMargin ? 0 : _topMargin;
    cuX = qMax(0, qMin(columns - 1, cuX)); // nowrap and ensure non-negative!
    cuY = qMax(stop, qMin(lines - 1, cuY - n));
}

void Screen::cursorDown(int n) {
    if (n == 0)
        n = 1; // Default
    int stop = cuY > _bottomMargin ? lines - 1 : _bottomMargin;
    cuX = qMax(0, qMin(columns - 1, cuX)); // nowrap and ensure non-negative!
    cuY = qMin(stop, qMax(0, cuY + n));
}

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

void Screen::cursorNextLine(int n) {
    if (n == 0) {
        n = 1; // Default
    }
    cuX = 0;
    while (n > 0) {
        if (cuY < lines - 1) {
            cuY += 1;
        }
        n--;
    }
}

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

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

void Screen::setMargins(int top, int bot) {
    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() {
    if (cuY == _bottomMargin)
        scrollUp(1);
    else if (cuY < lines - 1)
        cuY += 1;
}

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

void Screen::nextLine() {
    toStartOfLine();
    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() - 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::repeatChars(int count) {
    if (count == 0) {
        count = 1;
    }
    /**
     * 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 < count; 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] = true;
    switch (m) {
    case MODE_Origin:
        cuX = 0;
        cuY = _topMargin;
        break; // FIXME: home
    }
}

void Screen::resetMode(int m) {
    currentModes[m] = false;
    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]; }

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

    ImageLine *newScreenLines = new ImageLine[new_lines + 1];
    for (int i = 0; i < qMin(lines, 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.
    _topMargin = 0;
    _bottomMargin = lines - 1;
    initTabStops();
    clearSelection();
}

void Screen::setDefaultMargins() {
    _topMargin = 0;
    _bottomMargin = 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

 currentForeground, currentBackground 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 apart
 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::updateEffectiveRendition() {
    effectiveRendition = currentRendition;
    if (currentRendition & RE_REVERSE) {
        effectiveForeground = currentBackground;
        effectiveBackground = currentForeground;
    } else {
        effectiveForeground = currentForeground;
        effectiveBackground = currentBackground;
    }

    if (currentRendition & RE_BOLD)
        effectiveForeground.setIntensive();
}

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] = 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, 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
    }

    // 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 < 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] = (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(bool clearScreen) {
    setMode(MODE_Wrap);
    saveMode(MODE_Wrap); // wrap at end of margin
    resetMode(MODE_Origin);
    saveMode(MODE_Origin); // position refers to [1,1]
    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;

    setDefaultRendition();
    saveCursor();

    if (clearScreen)
        clear();
}

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

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);
#if 0 // TODO: implement when unicode_width is fixed, we need more
    // backspace/cursorMove
    wchar_t c = 0;
    if(cuX <= 0) {
        if(cuY > 0) {
            uint32_t endx = screenLines[cuY-1].size();
            if(endx > 0) {
                    c = screenLines[cuY-1][endx-1].character;
            }
        }
    } else {
        c = screenLines[cuY][cuX-1].character;
    }
    if(c) {
        int ow = CharWidth::unicode_width(c,false);
        int w = CharWidth::unicode_width(c,true);
        if(w == 2 && ow == 1) {
            cuX = qMin(columns-1,cuX); // nowrap!
            cuX = qMax(0,cuX-1);

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

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

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

void Screen::newLine() {
    if (getMode(MODE_NewLine))
        toStartOfLine();
    index();
    // Ensure cursor position is valid after newline
    cuX = qMax(0, qMin(columns - 1, cuX));
    cuY = qMax(0, qMin(lines - 1, cuY));
}

void Screen::checkSelection(int from, int to) {
    if (selBegin == -1)
        return;
    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(wchar_t 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 = CharWidth::unicode_width(c);
    if (w < 0)
        return;

    if (w == 0) {
        if (QChar(c).category() != QChar::Mark_NonSpacing)
            return;
        // Find previous "real character" to try to combine with
        int charToCombineWithX = qMin(cuX, screenLines[cuY].length());
        int charToCombineWithY = cuY;
        bool previousChar = true;
        do {
            if (charToCombineWithX > 0) {
                --charToCombineWithX;
            } else if (charToCombineWithY > 0 && lineProperties.at(charToCombineWithY - 1) & LINE_WRAPPED) {
                // Try previous line
                --charToCombineWithY;
                charToCombineWithX = screenLines[charToCombineWithY].length() - 1;
            } else {
                // Give up
                previousChar = false;
                break;
            }

            // Failsafe
            if (charToCombineWithX < 0) {
                previousChar = false;
                break;
            }
        } while (screenLines[charToCombineWithY][charToCombineWithX] == 0);

        if (!previousChar) {
            w = 2;
            goto notcombine;
        }

        Character& currentChar = screenLines[charToCombineWithY][charToCombineWithX];
        if ((currentChar.rendition & RE_EXTENDED_CHAR) == 0) {
            uint chars[2] = { static_cast<uint>(currentChar.character), static_cast<uint>(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 && extendedCharLength < 8) {
                Q_ASSERT(extendedCharLength > 1);
                Q_ASSERT(extendedCharLength < 65535); // redundant due to above check
                auto chars = std::make_unique<uint[]>(extendedCharLength + 1);
                std::copy_n(oldChars, extendedCharLength, chars.get());
                chars[extendedCharLength] = c;
                currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars.get(), extendedCharLength + 1);
            }
        }
        return;
    }

notcombine:
    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 < 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;

    lastDrawnChar = c;

    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 = effectiveForeground;
        ch.backgroundColor = effectiveBackground;
        ch.rendition = effectiveRendition;

        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() { _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));

    // Ensure bounds are valid
    from = qMax(0, qMin(lines - 1, from));
    int sourceEnd = qMin(lines - 1, _bottomMargin);
    int sourceStart = qMin(sourceEnd, from + n);

    if (sourceStart <= sourceEnd) {
        moveImage(loc(0, from), loc(0, sourceStart), loc(columns - 1, sourceEnd));
    }

    int clearStart = qMax(from, _bottomMargin - n + 1);
    int clearEnd = _bottomMargin;
    if (clearStart <= clearEnd && clearStart >= 0 && clearEnd < lines) {
        clearImage(loc(0, clearStart), loc(columns - 1, clearEnd), ' ');
    }
}

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

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

    // Ensure bounds are valid
    if (n <= 0)
        return;
    if (from > _bottomMargin || from < 0)
        return;
    if (from + n > _bottomMargin)
        n = _bottomMargin - from;

    from = qMax(0, qMin(lines - 1, from));
    int destLine = qMin(lines - 1, from + n);
    int sourceEnd = qMax(0, qMin(lines - 1, _bottomMargin - n));

    if (from <= sourceEnd && destLine < lines) {
        moveImage(loc(0, destLine), loc(0, from),
                            loc(columns - 1, sourceEnd));
    }

    int clearEnd = qMin(lines - 1, qMin(_bottomMargin, from + n - 1));
    if (from <= clearEnd && from >= 0) {
        clearImage(loc(0, from), loc(columns - 1, clearEnd), ' ');
    }
}

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
    // Ensure columns is valid before using it
    if (columns > 0) {
        cuX = qMax(0, qMin(columns - 1, x));
    } else {
        cuX = 0;
    }
}

void Screen::setCursorY(int y) {
    if (y == 0)
        y = 1; // Default
    y -= 1;  // Adjust
    int newY = y + (getMode(MODE_Origin) ? _topMargin : 0);
    // Ensure lines is valid before using it
    if (lines > 0) {
        cuY = qMax(0, qMin(lines - 1, newY));
    } else {
        cuY = 0;
    }
}

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

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

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

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

QString Screen::getScreenText(int row1, int col1, int row2, int col2, int mode) {
    Q_ASSERT(row1 >= 0 && row1 < lines);
    Q_ASSERT(row2 >= 0 && row2 < lines);
    Q_ASSERT(col1 >= 0 && col1 < columns);
    Q_ASSERT(col2 >= 0 && col2 < columns);

    QString text;
    int startLine = qMin(row1, row2);
    int endLine = qMax(row1, row2);
    int startCol = qMin(col1, col2);
    int endCol = qMax(col1, col2);

    if (mode == 1) {
        for (int i = startLine; i <= endLine; i++) {
            if (screenLines->size() <= i)
                break;
            for (int j = startCol; j <= endCol; j++) {
                if (screenLines[i].count() <= j)
                    break;
                wchar_t c = screenLines[i][j].character;
                text += QChar(c);
            }
        }
    } else if (mode == 2) {
        for (int i = startLine; i <= endLine; i++) {
            if (screenLines->size() <= i)
                break;
            int size = 0;
            for (int j = startCol; j <= endCol; j++) {
                if (screenLines[i].count() <= j)
                    break;
                wchar_t c = screenLines[i][j].character;
                text += QChar(c);
                size++;
            }
            if (size != 0) {
                text += '\n';
            }
        }
    }

    return text;
}

void Screen::clearImage(int loca, int loce, char c) {
    // Validate input parameters
    if (columns <= 0 || lines <= 0)
        return;

    // Ensure loca and loce are within valid bounds
    const int imageSize = columns * lines;
    loca = qMax(0, qMin(imageSize - 1, loca));
    loce = qMax(0, qMin(imageSize - 1, loce));

    // Ensure loca <= loce
    if (loca > loce)
        return;

    int scr_TL = loc(0, history->getLines());

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

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

    // Additional bounds check for lines
    topLine = qMax(0, qMin(lines - 1, topLine));
    bottomLine = qMax(0, qMin(lines - 1, bottomLine));

    Character clearCh(c, currentForeground, currentBackground, 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;
        }
    }
}

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

    // Validate parameters
    if (columns <= 0 || lines <= 0)
        return;

    const int imageSize = columns * lines;

    // Clamp all positions to valid ranges
    dest = qMax(0, qMin(imageSize - 1, dest));
    sourceBegin = qMax(0, qMin(imageSize - 1, sourceBegin));
    sourceEnd = qMax(0, qMin(imageSize - 1, sourceEnd));

    // Re-validate after clamping
    if (sourceBegin > sourceEnd)
        return;

    int numLines = (sourceEnd - sourceBegin) / columns;

    // Ensure we don't exceed screen boundaries
    int destLine = dest / columns;
    int sourceBeginLine = sourceBegin / columns;
    int sourceEndLine = sourceEnd / columns;

    if (destLine >= lines || sourceBeginLine >= lines || sourceEndLine >= lines)
        return;

    // Additional check: ensure the move won't go out of bounds
    if (destLine + numLines >= lines)
        numLines = lines - destLine - 1;

    if (numLines < 0)
        return;

    // 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 <= numLines; i++) {
            int destIdx = (dest / columns) + i;
            int srcIdx = (sourceBegin / columns) + i;
            if (destIdx < lines && srcIdx < lines) {
                screenLines[destIdx] = screenLines[srcIdx];
                lineProperties[destIdx] = lineProperties[srcIdx];
            }
        }
    } else {
        for (int i = numLines; i >= 0; i--) {
            int destIdx = (dest / columns) + i;
            int srcIdx = (sourceBegin / columns) + i;
            if (destIdx < lines && srcIdx < lines) {
                screenLines[destIdx] = screenLines[srcIdx];
                lineProperties[destIdx] = lineProperties[srcIdx];
            }
        }
    }

    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 (selBegin != -1) {
        bool beginIsTL = (selBegin == selTopLeft);
        int diff = dest - sourceBegin; // Scroll by this amount
        int scr_TL = loc(0, history->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 ((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() {
    // 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), ' ');

    // Reset cursor position to avoid orphaned cursor positions
    cuX = qMax(0, qMin(columns - 1, cuX));
    cuY = qMax(0, qMin(lines - 1, cuY));
}

/*! 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) {
    currentRendition |= re;
    updateEffectiveRendition();
}

void Screen::resetRendition(int re) {
    currentRendition &= ~re;
    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(space, color);

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

void Screen::setBackColor(int space, int color) {
    currentBackground = CharacterColor(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::isClearSelection() {
    return selBottomRight == -1 && selTopLeft == -1 && 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 mode) {
    selBegin = loc(x, y);
    /* FIXME, HACK to correct for x too far to the right... */
    if (x == columns)
        selBegin--;

    selBottomRight = selBegin;
    selTopLeft = selBegin;
    blockSelectionMode = mode;
}

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) {
        int topRow = selTopLeft / columns;
        int topColumn = selTopLeft % columns;
        int bottomRow = selBottomRight / columns;
        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);
    }

    int pos = loc(x, y);
    return pos >= selTopLeft && pos <= selBottomRight && columnInSelection;
}

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

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

    return result;
}

bool Screen::isSelectionValid() const {
    return selTopLeft >= 0 && selBottomRight >= 0;
}

void Screen::writeSelectionToStream(TerminalCharacterDecoder *decoder,
                                                                        bool preserveLineBreaks) const {
    if (!isSelectionValid())
        return;
    writeToStream(decoder, selTopLeft, selBottomRight, preserveLineBreaks);
}

void Screen::writeToStream(TerminalCharacterDecoder *decoder, int startIndex,
                            int endIndex, bool preserveLineBreaks) const {
    int top = startIndex / columns;
    int left = startIndex % columns;

    int bottom = endIndex / columns;
    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,
                                                                    preserveLineBreaks);

        // 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) {
            Character newLineChar('\n');
            decoder->decodeLine(&newLineChar, 1, 0);
        }
    }
}

int Screen::copyLineToStream(int line, int start, int count,
                            TerminalCharacterDecoder *decoder,
                            bool appendNewLine,
                            bool preserveLineBreaks) const {
    // 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];

    Q_ASSERT(count < MAX_CHARS);

    LineProperty currentLineProperties = 0;

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

        // 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 = columns - start;

        Q_ASSERT(count >= 0);

        const int screenLine = line - history->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 ? length - start : 0);

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

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

    return count;
}

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

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

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

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

        int newHistLines = history->getLines();

        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
            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] = (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;
}
