// SPDX-FileCopyrightText: 2021 Ernie Pasveer <epasveer@att.net>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "SeerHexWidget.h"
#include "SeerUtl.h"
#include <QtGui/QPainter>
#include <QtGui/QPaintEvent>
#include <QtGui/QKeyEvent>
#include <QtGui/QClipboard>
#include <QtGui/QTextCursor>
#include <QtGui/QTextBlock>
#include <QtCore/QStringConverter>
#include <QtCore/QSize>
#include <QtCore/QDebug>
#include <stdexcept>

#define byteArrayToType( data, order, precision, type ) \
        QDataStream stream( data ); \
        stream.setByteOrder( order ); \
        stream.setFloatingPointPrecision( precision ); \
        type t; \
        stream >> t; \
        return t;

qint8 toQInt8 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, qint8)
}

quint8 toQUInt8 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, quint8)
}

qint16 toQInt16 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, qint16)
}

quint16 toQUInt16 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, quint16)
}

qint32 toQInt32 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, qint32)
}

quint32 toQUInt32 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, quint32)
}

qint64 toQInt64 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, qint64)
}

quint64 toQUInt64 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, quint64)
}

float toQFloat32 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::SinglePrecision, float)
}

double toQFloat64 (const QByteArray& data, const QDataStream::ByteOrder order=QDataStream::BigEndian) {
    byteArrayToType(data, order, QDataStream::DoublePrecision, double)
}

SeerHexWidget::SeerHexWidget(QWidget* parent) : QWidget(parent), _pdata(nullptr), _pdataPrevious(nullptr) {

    // Construct the UI.
    setupUi(this);

    // Setup the widgets
    QFont font;
    font.setFamily("monospace [Consolas]");
    font.setFixedPitch(true);
    font.setStyleHint(QFont::TypeWriter);

    plainTextEdit->setFont(font);
    plainTextEdit->setFocusPolicy(Qt::StrongFocus);
    plainTextEdit->setTextInteractionFlags(Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse);
    plainTextEdit->setWordWrapMode(QTextOption::NoWrap);

    _memoryMode    = SeerHexWidget::HexMemoryMode;
    _charMode      = SeerHexWidget::AsciiCharMode;
    _addressOffset = 0;
    _charWidth     = plainTextEdit->fontMetrics().horizontalAdvance(QLatin1Char('9'));
    _charHeight    = plainTextEdit->fontMetrics().height();
    _gapAddrHex    = 10; // Gap between address and hex fields.
    _gapHexAscii   = 16; // Gap between hex and ascii fields.
    _highlightByte = -1;

    setBytesPerLine(16);

    // Connect things.
    QObject::connect(plainTextEdit,                   &QPlainTextEdit::cursorPositionChanged,        this,  &SeerHexWidget::handleCursorPositionChanged);
    QObject::connect(showAsLittleEndianCheckBox,      &QCheckBox::clicked,                           this,  &SeerHexWidget::handleCursorPositionChanged);
    QObject::connect(showUnsignedFloatAsHexCheckBox,  &QCheckBox::clicked,                           this,  &SeerHexWidget::handleCursorPositionChanged);
    QObject::connect(this,                            &SeerHexWidget::byteOffsetChanged,             this,  &SeerHexWidget::handleByteOffsetChanged);
}

SeerHexWidget::~SeerHexWidget() {

    if (_pdata) {
        delete _pdata;
        _pdata = nullptr;
    }

    if (_pdataPrevious) {
        delete _pdataPrevious;
        _pdataPrevious = nullptr;
    }
}

void SeerHexWidget::setBytesPerLine (int count) {

    // 3 = 'a0 '
    // 4 = '007 '
    // 9 = '00011010 '
    // 4 = ' 10 '
    if (memoryMode() == SeerHexWidget::HexMemoryMode) {
        _hexCharsPerByte = 3;
    }else if (memoryMode() == SeerHexWidget::OctalMemoryMode) {
        _hexCharsPerByte = 4;
    }else if (memoryMode() == SeerHexWidget::BinaryMemoryMode) {
        _hexCharsPerByte = 9;
    }else if (memoryMode() == SeerHexWidget::DecimalMemoryMode) {
        _hexCharsPerByte = 4;
    }else{
        _hexCharsPerByte = 3;
    }

    _bytesPerLine       = count;
    _hexCharsPerLine    = _bytesPerLine * _hexCharsPerByte - 1;

    int posHex          = SeerHexWidget::HexFieldWidth * _charWidth + gapAddrHex(); // x position of hex field.
    int posAscii        = posHex + hexCharsPerLine() * _charWidth + gapHexAscii(); // x position of ascii field.

    setMinimumWidth(posAscii + (bytesPerLine() * _charWidth)); // x position after the ascii field.

    // Repaint the widget.
    create();
}

int SeerHexWidget::bytesPerLine () const {
    return _bytesPerLine;
}

int SeerHexWidget::hexCharsPerLine () const {
    return _hexCharsPerLine;
}

int SeerHexWidget::hexCharsPerByte () const {
    return _hexCharsPerByte;
}

int SeerHexWidget::nLines () const {
    return plainTextEdit->blockCount();
}

int SeerHexWidget::gapAddrHex () const {
    return _gapAddrHex;
}

int SeerHexWidget::gapHexAscii () const {
    return _gapHexAscii;
}

void SeerHexWidget::setAddressOffset (unsigned long offset) {

    _addressOffset = offset;

    // Repaint the widget.
    create();
}

unsigned long SeerHexWidget::addressOffset () const {
    return _addressOffset;
}

unsigned long SeerHexWidget::size () const {

    if (_pdata) {
        return _pdata->size();
    }

    return 0;
}

void SeerHexWidget::setMemoryMode (SeerHexWidget::MemoryMode memoryMode) {

    _memoryMode = memoryMode;

    // This repaints the widget with the new memory mode
    setBytesPerLine(bytesPerLine());
}

SeerHexWidget::MemoryMode SeerHexWidget::memoryMode () const {
    return _memoryMode;
}

QString SeerHexWidget::memoryModeString () const {

    if (memoryMode() == SeerHexWidget::HexMemoryMode) {
        return "hex";
    }else if (memoryMode() == SeerHexWidget::OctalMemoryMode) {
        return "octal";
    }else if (memoryMode() == SeerHexWidget::BinaryMemoryMode) {
        return "binary";
    }else if (memoryMode() == SeerHexWidget::DecimalMemoryMode) {
        return "decimal";
    }

    return "???";
}

void SeerHexWidget::setCharMode (SeerHexWidget::CharMode charMode) {

    _charMode = charMode;

    // Repaint the widget.
    create();
}

SeerHexWidget::CharMode SeerHexWidget::charMode() const {
    return _charMode;
}

QString SeerHexWidget::charModeString () const {

    if (charMode() == SeerHexWidget::AsciiCharMode) {
        return "ascii";
    }else if (charMode() == SeerHexWidget::Utf8Mode) {
        return "utf8";
    }else if (charMode() == SeerHexWidget::Utf16Mode) {
        return "utf16";
    }else if (charMode() == SeerHexWidget::Utf32Mode) {
        return "utf32";
    }else if (charMode() == SeerHexWidget::EbcdicCharMode) {
        return "ebcdic";
    }

    return "???";
}

QTextDocument* SeerHexWidget::document () {

    return plainTextEdit->document();
}

QString SeerHexWidget::toPlainText () {

    return plainTextEdit->toPlainText();
}

void SeerHexWidget::setData(SeerHexWidget::DataStorage* pData) {

    // Delete previous data, if any.
    if (_pdataPrevious) {
        delete _pdataPrevious;
        _pdataPrevious = nullptr;
    }

    // Save away the current data as the previous so we can use it for comparisons.
    _pdataPrevious = _pdata;
    _pdata = nullptr;

    // Save the current data;
    _pdata = pData;

    // Repaint the widget.
    create();
}

void SeerHexWidget::handleCursorPositionChanged () {

    // Get the current cursor position.
    QTextCursor cursor = plainTextEdit->textCursor();

    // Is it before the hex values? (address region)
    if (cursor.positionInBlock() < SeerHexWidget::HexFieldWidth) {
        emit byteOffsetChanged(-1);
        return;
    }

    // Is is after the hex values? (ascii/utf/ebcdic region)
    if (cursor.positionInBlock() > SeerHexWidget::HexFieldWidth + hexCharsPerLine()) {
        emit byteOffsetChanged(-1);
        return;
    }

    int line = cursor.blockNumber();
    int col  = cursor.positionInBlock() - SeerHexWidget::HexFieldWidth;
    int byte = (col / hexCharsPerByte()) + (line * bytesPerLine());

    _highlightByte = byte;

    emit byteOffsetChanged(byte);
}

void SeerHexWidget::handleByteOffsetChanged (int byte) {

    // Clear all fields.
    lineEdit_1->setText("");
    lineEdit_2->setText("");
    lineEdit_3->setText("");
    lineEdit_4->setText("");
    lineEdit_5->setText("");
    lineEdit_6->setText("");
    lineEdit_7->setText("");
    lineEdit_8->setText("");
    lineEdit_9->setText("");
    lineEdit_10->setText("");
    lineEdit_11->setText("");
    lineEdit_12->setText("");
    lineEdit_13->setText("");
    lineEdit_14->setText("");

    // Remove previous highlight.
    plainTextEdit->setExtraSelections(QList<QTextEdit::ExtraSelection>());

    // Invalid byte number, do nothing.
    if (byte < 0) {
        return;
    }

    // If there's no data, do nothing.
    if (!_pdata) {
        return;
    }

    // Byte past the end, do nothing.
    if ((unsigned int)byte >= size()) {
        return;
    }

    // Calculate line and position in line.
    int line = (byte / bytesPerLine());
    int pos  = byte - (line * bytesPerLine());

    int pos_s = SeerHexWidget::HexFieldWidth + (pos * hexCharsPerByte());
    int pos_e = pos_s + hexCharsPerByte() - 1;

    int pos_a = SeerHexWidget::HexFieldWidth + hexCharsPerLine() + pos + 4; // 4 == ' ' .... ' | '

    // Highlight the text in the hex region and the ascii region.
    QList<QTextEdit::ExtraSelection> extraSelections; {

        // Get a cursor to the current line.
        QTextBlock  block  = plainTextEdit->document()->findBlockByLineNumber(line);
        QTextCursor cursor = QTextCursor(block);

        // Highlight the current hex value.
        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
        cursor.movePosition(QTextCursor::Right,       QTextCursor::MoveAnchor, pos_s);
        cursor.movePosition(QTextCursor::Right,       QTextCursor::KeepAnchor, pos_e - pos_s + 1);

        // Add it to the extra selections.
        QTextEdit::ExtraSelection extra_byte;
        extra_byte.format.setBackground(plainTextEdit->palette().highlight().color());
        extra_byte.cursor = cursor;

        extraSelections.append(extra_byte);

        // Highlight the current ascii value.
        // For Ascii and Ebcdic only. No UTF as it can be variable length encoding.
        if (charMode() == SeerHexWidget::AsciiCharMode || charMode() == SeerHexWidget::EbcdicCharMode) {
            cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
            cursor.movePosition(QTextCursor::Right,       QTextCursor::MoveAnchor, pos_a);
            cursor.movePosition(QTextCursor::Right,       QTextCursor::KeepAnchor, 1);

            // Add it to the extra selections.
            QTextEdit::ExtraSelection extra_ascii;
            extra_ascii.format.setBackground(plainTextEdit->palette().highlight().color());
            extra_ascii.cursor = cursor;

            extraSelections.append(extra_ascii);
        }

    } plainTextEdit->setExtraSelections(extraSelections);

    // Set the endian default.
    QDataStream::ByteOrder byteOrder = QDataStream::BigEndian;

    if (showAsLittleEndianCheckBox->isChecked()) {
        byteOrder = QDataStream::LittleEndian;
    }

    // Set the 'AsHex' default.
    bool unsignedAndFloatAsHex = false;

    if (showUnsignedFloatAsHexCheckBox->isChecked()) {
        unsignedAndFloatAsHex = true;
    }

    // Go through each one and display the value.
    {
        QByteArray arr = _pdata->getData(byte, sizeof(char));                       // Extract a bytearray from the data for the size of the value we are after.
        if (arr.size() == sizeof(char)) {                                           // If not the right size, skip it. Near the end of the data.
            lineEdit_1->setText(QString::number(toQInt8(arr, byteOrder)));          // Fill in the signed value.
            if (unsignedAndFloatAsHex) {                                            // Show unsigned value as hex?
                if (byteOrder == QDataStream::LittleEndian) {                       // Swap bytes to handle endianess.
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_2->setText("0x"+QString(arr.toHex()));                     // Print value as hex.
            }else{
                lineEdit_2->setText(QString::number(toQUInt8(arr, byteOrder)));     // Print value as a value.
            }
        }
    }

    {
        QByteArray arr = _pdata->getData(byte, sizeof(short));
        if (arr.size() == sizeof(short)) {
            lineEdit_3->setText(QString::number(toQInt16(arr, byteOrder)));
            if (unsignedAndFloatAsHex) {
                if (byteOrder == QDataStream::LittleEndian) {
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_4->setText("0x"+QString(arr.toHex()));
            }else{
                lineEdit_4->setText(QString::number(toQUInt16(arr, byteOrder)));
            }
        }
    }

    {
        QByteArray arr = _pdata->getData(byte, sizeof(float));
        if (arr.size() == sizeof(float)) {
            if (unsignedAndFloatAsHex) {
                if (byteOrder == QDataStream::LittleEndian) {
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_5->setText("0x"+QString(arr.toHex()));
            }else{
                lineEdit_5->setText(QString::number(toQFloat32(arr, byteOrder)));
            }
        }
    }

    {
        QByteArray arr = _pdata->getData(byte, sizeof(int));
        if (arr.size() == sizeof(int)) {
            lineEdit_6->setText(QString::number(toQInt32(arr, byteOrder)));
            if (unsignedAndFloatAsHex) {
                if (byteOrder == QDataStream::LittleEndian) {
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_7->setText("0x"+QString(arr.toHex()));
            }else{
                lineEdit_7->setText(QString::number(toQUInt32(arr, byteOrder)));
            }
        }
    }

    {
        QByteArray arr = _pdata->getData(byte, sizeof(long int));
        if (arr.size() == sizeof(long int)) {
            lineEdit_8->setText(QString::number(toQInt64(arr, byteOrder)));
            if (unsignedAndFloatAsHex) {
                if (byteOrder == QDataStream::LittleEndian) {
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_9->setText("0x"+QString(arr.toHex()));
            }else{
                lineEdit_9->setText(QString::number(toQUInt64(arr, byteOrder)));
            }
        }
    }

    {
        QByteArray arr = _pdata->getData(byte, sizeof(double));
        if (arr.size() == sizeof(double)) {
            if (unsignedAndFloatAsHex) {
                if (byteOrder == QDataStream::LittleEndian) {
                    std::reverse(arr.begin(), arr.end());
                }
                lineEdit_10->setText("0x"+QString(arr.toHex()));
            }else{
                lineEdit_10->setText(QString::number(toQFloat64(arr, byteOrder)));
            }
        }
    }

    {
        QString    val;
        QByteArray arr = _pdata->getData(byte, 4);

        for (int i=0; i<arr.size(); i++) {

            unsigned char ch = arr[i];

            if (val != "") {
                val += " ";
            }

            val += QString("%1").arg(ushort(ch), int(2), int(16), QChar('0'));
        }

        lineEdit_11->setText(val);
    }

    {
        QString    val;
        QByteArray arr = _pdata->getData(byte, 4);

        for (int i=0; i<arr.size(); i++) {

            unsigned char ch = arr[i];

            if (val != "") {
                val += " ";
            }

            val += QString("%1").arg(ushort(ch), int(3), int(8), QChar('0'));
        }

        lineEdit_12->setText(val);
    }

    {
        QString    val;
        QByteArray arr = _pdata->getData(byte, 4);

        for (int i=0; i<arr.size(); i++) {

            unsigned char ch = arr[i];

            if (val != "") {
                val += " ";
            }

            val += QString("%1").arg(ushort(ch), int(8), int(2), QChar('0'));
        }

        lineEdit_13->setText(val);
    }

    {
        QString    val;
        QByteArray arr = _pdata->getData(byte, 4);

        // Print N bytes in their char value.
        if (charMode() == SeerHexWidget::AsciiCharMode) {
            for (int i=0; i<arr.size(); i++) {

                unsigned char ch = Seer::ucharToAscii( arr[i] );

                QChar symbol = QChar(ch);
                val += QString(symbol);
            }

        }else if (charMode() == SeerHexWidget::Utf8Mode || charMode() == SeerHexWidget::Utf16Mode || charMode() == SeerHexWidget::Utf32Mode) {

            val = "Can't show for UTF";

        }else if (charMode() == SeerHexWidget::EbcdicCharMode) {
            for (int i=0; i<arr.size(); i++) {

                unsigned char ch = Seer::ebcdicToAscii( arr[i] );

                QChar symbol = QChar(ch);
                val += QString(symbol);
            }

        }else{
            val = "";
        }

        lineEdit_14->setText(val);
    }
}

void SeerHexWidget::create () {

    createDiffMap();

    // Ignore changing of cursor positions.
    QObject::disconnect(plainTextEdit, &QPlainTextEdit::cursorPositionChanged,    this,  &SeerHexWidget::handleCursorPositionChanged);

    // Clear the current document. We're going to recreate it.
    plainTextEdit->clear();

    // Clear the checksum.
    lineEdit_15->setText("");

    // If there's no data, do nothing.
    if (!_pdata) {
        // Re-enable notification of changing of cursor positions.
        QObject::connect(plainTextEdit, &QPlainTextEdit::cursorPositionChanged,   this,  &SeerHexWidget::handleCursorPositionChanged);
        return;
    }

    // Convert the data to a 'text' string.
    QString    textString = "";
    QByteArray data       = _pdata->getData();

    if (charMode() == SeerHexWidget::AsciiCharMode) {

        for (int b=0; b<data.size(); b++) {

            unsigned char ch = Seer::ucharToAscii(data[b]);

            textString.append(QChar(ch));
        }

    }else if (charMode() == SeerHexWidget::Utf8Mode) {

        auto toUtf16 = QStringDecoder(QStringDecoder::Utf8, QStringConverter::Flag::ConvertInvalidToNull);

        textString = toUtf16.decode(data);

        // Filter out ascii control characters.
        for (int b=0; b<textString.length(); b++) {
            if (textString[b] == QChar(127) || textString[b] < QChar(32)) {
                textString[b] = QChar::ReplacementCharacter;
            }
        }

    }else if (charMode() == SeerHexWidget::Utf16Mode) {

        auto toUtf16 = QStringDecoder(QStringDecoder::Utf16, QStringConverter::Flag::ConvertInvalidToNull);

        textString = toUtf16.decode(data);

        // Filter out ascii control characters.
        for (int b=0; b<textString.length(); b++) {
            if (textString[b] == QChar(127) || textString[b] < QChar(32)) {
                textString[b] = QChar::ReplacementCharacter;
            }
        }

    }else if (charMode() == SeerHexWidget::Utf32Mode) {

        auto toUtf16 = QStringDecoder(QStringDecoder::Utf32, QStringConverter::Flag::ConvertInvalidToNull);

        textString = toUtf16.decode(data);

        // Filter out ascii control characters.
        for (int b=0; b<textString.length(); b++) {
            if (textString[b] == QChar(127) || textString[b] < QChar(32)) {
                textString[b] = QChar::ReplacementCharacter;
            }
        }

    }else if (charMode() == SeerHexWidget::EbcdicCharMode) {

        for (int b=0; b<data.size(); b++) {

            unsigned char ch = Seer::ebcdicToAscii(data[b]);

            textString.append(QChar(ch));
        }

    }else{
        // No 'text' string.
    }

    // Set text formats.
    QTextCharFormat defaultFormat      = plainTextEdit->currentCharFormat();
    QTextCharFormat grayFormat         = defaultFormat;
    grayFormat.setBackground(QBrush(Qt::lightGray));
    QTextCharFormat underlineFormat    = defaultFormat;
    underlineFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);

    // Get a cursor
    QTextCursor cursor(plainTextEdit->textCursor());

    cursor.movePosition(QTextCursor::Start);

    // Go through the data, one byte at a time.
    for (int i=0; i<data.size(); i+=bytesPerLine()) {

        // Place a new hex address on the left side.
        if (i % bytesPerLine() == 0) {

            QString address = QString("0x%1").arg(i + addressOffset(), SeerHexWidget::HexFieldWidth-2, 16, QChar('0')); // -2 to allow '0x'.

            // Write adress to document.
            cursor.insertText (address, grayFormat);

            // Write spacer to document.
            cursor.insertText (QString(" "), defaultFormat);
        }

        // Print N bytes in their datatype value.
        int b = 0;

        for (b=0; b<bytesPerLine() && i+b < data.size(); b++) {

            unsigned char ch = data[i+b];

            QString val;

            if (memoryMode() == SeerHexWidget::HexMemoryMode) {
                val = QString("%1").arg(ushort(ch), int(2), int(16), QChar('0'));
            }else if (memoryMode() == SeerHexWidget::OctalMemoryMode) {
                val = QString("%1").arg(ushort(ch), int(3), int(8), QChar('0'));
            }else if (memoryMode() == SeerHexWidget::BinaryMemoryMode) {
                val = QString("%1").arg(ushort(ch), int(8), int(2), QChar('0'));
            }else if (memoryMode() == SeerHexWidget::DecimalMemoryMode) {
                val = QString("%1").arg(ushort(ch), int(3), int(10), QChar(' '));
            }else{
                val = "??";
            }

            if (_diffMap[i+b]) {
                cursor.setCharFormat(underlineFormat);
                cursor.insertText (val);
                cursor.setCharFormat(defaultFormat);
            }else{
                cursor.insertText (val, defaultFormat);
            }

            // Write spacer to document.
            cursor.insertText (QString(" "), defaultFormat);
        }

        // Print N odd remainder bytes.
        for (; b<bytesPerLine(); b++) {

            QString val;

            if (memoryMode() == SeerHexWidget::HexMemoryMode) {
                val = "  ";
            }else if (memoryMode() == SeerHexWidget::OctalMemoryMode) {
                val = "   ";
            }else if (memoryMode() == SeerHexWidget::BinaryMemoryMode) {
                val = "        ";
            }else if (memoryMode() == SeerHexWidget::DecimalMemoryMode) {
                val = "   ";
            }else{
                val = "??";
            }

            // Write memory value to document
            cursor.insertText (val, defaultFormat);

            // Write spacer to document.
            cursor.insertText (QString(" "), defaultFormat);
        }

        // Print vertical line.
        // Write spacer to document.
        cursor.insertText (QString("| "), defaultFormat);

        // Print N bytes of the 'text' string.
        for (int b=0; b<bytesPerLine() && i+b < textString.length(); b++) {

            QString val = textString[i+b];

            // Write display character to document.
            cursor.insertText (val, defaultFormat);
        }

        // Write eol to document.
        QString eol("\n");

        cursor.insertText (eol, defaultFormat);
    }


    // Print checksum.
    {
        quint16 crc16    = qChecksum(data, Qt::ChecksumIso3309);
        QString crc16str = QString::number(crc16);

        lineEdit_15->setText(crc16str);
    }

    handleByteOffsetChanged(_highlightByte);

    // Re-enable notification of changing of cursor positions.
    QObject::connect(plainTextEdit, &QPlainTextEdit::cursorPositionChanged, this,  &SeerHexWidget::handleCursorPositionChanged);
}

void SeerHexWidget::createDiffMap() {

    qsizetype pdata_size         = 0;
    qsizetype pdataPrevious_size = 0;
    qsizetype len                = 0;

    if (_pdata) {
        pdata_size = _pdata->getData().size();
    }

    if (_pdataPrevious) {
        pdataPrevious_size = _pdataPrevious->getData().size();
    }

    // Allocate diff map. Set it to zeros.
    len = qMax(pdata_size, pdataPrevious_size);

    _diffMap.fill(0, len);

    // Create diff map if we can.
    if (_pdata && _pdataPrevious) {
        for (qsizetype i = 0; i < len; ++i) {

            // Positions beyond the shorter array always differ
            bool outOfBounds;
            bool bytesDiffer;

            outOfBounds = (i >= _pdata->getData().size()) || (i >= _pdataPrevious->getData().size());

            if (outOfBounds) {
                bytesDiffer = true;
            }else{
                bytesDiffer = _pdata->getData()[i] != _pdataPrevious->getData()[i];
            }

            _diffMap[i] = bytesDiffer;
        }
    }
}

SeerHexWidget::DataStorageArray::DataStorageArray(const QByteArray& arr) {
    _data = arr;
}

QByteArray SeerHexWidget::DataStorageArray::getData(int position, int length) {
    return _data.mid(position, length);
}

QByteArray SeerHexWidget::DataStorageArray::getData() {
    return _data;
}

int SeerHexWidget::DataStorageArray::size() {
    return _data.size();
}

