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

#include "SeerArrayVisualizerWidget.h"
#include "SeerHelpPageDialog.h"
#include "SeerUtl.h"
#include <QtCharts/QChart>
#include <QtCharts/QLineSeries>
#include <QtCharts/QSplineSeries>
#include <QtCharts/QScatterSeries>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QToolTip>
#include <QtGui/QIntValidator>
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3)
#include <QtGui/QGuiApplication>
#include <QtGui/QStyleHints>
#endif
#include <QtPrintSupport/QPrinter>
#include <QtPrintSupport/QPrintDialog>
#include <QtCore/QRegularExpression>
#include <QtCore/QSettings>
#include <QtCore/QDebug>
#include <QtGlobal>

//
// Qt Charts 6.x workaround: scatter marker items can be (re)created without
// inheriting the series' pen/brush, leaving near-invisible ~1px markers.
// Small arrays usually render fine; large ones (e.g. 4000 points) reliably
// degrade. The series-level state is correct in both cases, so the style is
// lost at the marker-item level inside Qt Charts. Re-applying the pen/brush
// after the series is added pushes the style down to the marker items.
//
// Two passes suffice, not four. This runs after addSeries(), so the series
// already carries the chart theme's pen/brush; setPen(NoPen) and setBrush()
// are therefore real value transitions (no built-in Qt theme uses NoPen for
// a scatter pen nor this exact colour for the brush) and are not swallowed
// by the guarded setters. Even if one were a no-op, the other setter's
// update re-pushes both pen and brush to every marker item. Two passes over
// the (up to thousands of) marker items instead of four roughly halves this
// step's cost, with no visible change to the markers.
//
static void redecorateScatterSeries (QXYSeries* series) {

    QScatterSeries* scatter = qobject_cast<QScatterSeries*>(series);

    if (scatter == nullptr) {
        return;
    }

    scatter->setPen(QPen(Qt::NoPen));
    scatter->setBrush(QBrush(QColor(31, 119, 180)));
}

SeerArrayVisualizerWidget::SeerArrayVisualizerWidget (QWidget* parent) : QWidget(parent) {

    // Init variables.
    _aVariableId = Seer::createID(); // Create id's for A queries.
    _aMemoryId   = Seer::createID();
    _aLengthId   = Seer::createID();
    _aOffsetId   = Seer::createID();
    _aStrideId   = Seer::createID();

    _bVariableId = Seer::createID(); // Create id's for B queries.
    _bMemoryId   = Seer::createID();
    _bLengthId   = Seer::createID();
    _bOffsetId   = Seer::createID();
    _bStrideId   = Seer::createID();

    _aSeries     = 0;
    _bSeries     = 0;

    // Set up UI.
    setupUi(this);

    // Setup the widgets
    setWindowTitle("Seer Array Visualizer");
    setAttribute(Qt::WA_DeleteOnClose);

    arrayTableWidget->setAAxis(aAxisComboBox->currentText());
    arrayTableWidget->setBAxis(bAxisComboBox->currentText());

    lineRadioButton->setChecked(true);

    aArrayDisplayFormatComboBox->setCurrentIndex(0);
    bArrayDisplayFormatComboBox->setCurrentIndex(0);

    handleaArrayDisplayFormatComboBox(0);
    handlebArrayDisplayFormatComboBox(0);

    aVariableNameLineEdit->enableReturnPressedOnClear();
    bVariableNameLineEdit->enableReturnPressedOnClear();

    // A single series chart.
    QChart* chart = new QChart;
    chart->legend()->hide();
    chart->createDefaultAxes();
    chart->legend()->setVisible(true);
    chart->legend()->setAlignment(Qt::AlignBottom);

    arrayChartView->setRenderHint(QPainter::Antialiasing);
    arrayChartView->setChart(chart);
    arrayChartView->setFocusPolicy(Qt::StrongFocus);

    // Connect things.
    QObject::connect(aRefreshToolButton,            &QToolButton::clicked,                                     this,            &SeerArrayVisualizerWidget::handleaRefreshButton);
    QObject::connect(bRefreshToolButton,            &QToolButton::clicked,                                     this,            &SeerArrayVisualizerWidget::handlebRefreshButton);
    QObject::connect(helpToolButton,                &QToolButton::clicked,                                     this,            &SeerArrayVisualizerWidget::handleHelpButton);
    QObject::connect(aVariableNameLineEdit,         &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handleaVariableNameLineEdit);
    QObject::connect(aVariableNameLineEdit,         &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handleaVariableNameLineEdit);
    QObject::connect(aArrayLengthLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handleaRefreshButton);
    QObject::connect(aArrayLengthLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handleaElementLengthLineEdit);
    QObject::connect(aArrayOffsetLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handleaRefreshButton);
    QObject::connect(aArrayOffsetLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handleaElementOffsetLineEdit);
    QObject::connect(aArrayStrideLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handleaRefreshButton);
    QObject::connect(aArrayStrideLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handleaElementStrideLineEdit);
    QObject::connect(aArrayDisplayFormatComboBox,   QOverload<int>::of(&QComboBox::currentIndexChanged),       this,            &SeerArrayVisualizerWidget::handleaArrayDisplayFormatComboBox);
    QObject::connect(aAxisComboBox,                 QOverload<int>::of(&QComboBox::currentIndexChanged),       this,            &SeerArrayVisualizerWidget::handleaAxisComboBox);

    QObject::connect(bVariableNameLineEdit,         &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handlebVariableNameLineEdit);
    QObject::connect(bVariableNameLineEdit,         &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handlebVariableNameLineEdit);
    QObject::connect(bArrayLengthLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handlebRefreshButton);
    QObject::connect(bArrayLengthLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handlebElementLengthLineEdit);
    QObject::connect(bArrayOffsetLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handlebRefreshButton);
    QObject::connect(bArrayOffsetLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handlebElementOffsetLineEdit);
    QObject::connect(bArrayStrideLineEdit,          &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handlebRefreshButton);
    QObject::connect(bArrayStrideLineEdit,          &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handlebElementStrideLineEdit);
    QObject::connect(bArrayDisplayFormatComboBox,   QOverload<int>::of(&QComboBox::currentIndexChanged),       this,            &SeerArrayVisualizerWidget::handlebArrayDisplayFormatComboBox);
    QObject::connect(bAxisComboBox,                 QOverload<int>::of(&QComboBox::currentIndexChanged),       this,            &SeerArrayVisualizerWidget::handlebAxisComboBox);

    QObject::connect(arrayTableWidget,              &SeerArrayWidget::dataChanged,                             this,            &SeerArrayVisualizerWidget::handleDataChanged);
    QObject::connect(splitter,                      &QSplitter::splitterMoved,                                 this,            &SeerArrayVisualizerWidget::handleSplitterMoved);
    QObject::connect(titleLineEdit,                 &SeerHistoryLineEdit::returnPressed,                       this,            &SeerArrayVisualizerWidget::handleTitleLineEdit);
    QObject::connect(titleLineEdit,                 &SeerHistoryLineEdit::editingFinished,                     this,            &SeerArrayVisualizerWidget::handleTitleLineEdit);
    QObject::connect(pointsCheckBox,                &QCheckBox::clicked,                                       this,            &SeerArrayVisualizerWidget::handlePointsCheckBox);
    QObject::connect(labelsCheckBox,                &QCheckBox::clicked,                                       this,            &SeerArrayVisualizerWidget::handleLabelsCheckBox);
    QObject::connect(lineTypeButtonGroup,           QOverload<int>::of(&QButtonGroup::idClicked),              this,            &SeerArrayVisualizerWidget::handleLineTypeButtonGroup);
    QObject::connect(printPushButton,               &QPushButton::clicked,                                     arrayChartView,  &QZoomChartView::printView);
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3)
    QObject::connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged,                          this,            &SeerArrayVisualizerWidget::handleThemeChanged);
#endif

    // Colorize icons for theme.
    Seer::colorizeAllIcons(this, Seer::iconColorTheme());
    Seer::colorizeChartViewItem(arrayChartView, Seer::iconColorTheme());

    // Restore window settings.
    readSettings();
}

SeerArrayVisualizerWidget::~SeerArrayVisualizerWidget () {
}

void SeerArrayVisualizerWidget::setAVariableName (const QString& name) {

    setWindowTitle("Seer Array Visualizer - '" + name + "'");

    aVariableNameLineEdit->setText(name);

    if (aVariableNameLineEdit->text() == "") {
        aVariableAddressLineEdit->setText("");
        aArrayLengthLineEdit->setText("");
        aArrayOffsetLineEdit->setText("");
        aArrayStrideLineEdit->setText("");

        arrayTableWidget->setAData("deleted", 0);

        return;
    }

    // Coalesce this variable's setup (address reset + data/offset/stride) into
    // a single table rebuild; the guard flushes it when it leaves scope.
    SeerArrayWidget::BulkUpdate bulk(arrayTableWidget);

    setAVariableAddress("");

    // Clear old contents.
    QByteArray array;
    bool ok;

    arrayTableWidget->setAData(name, new SeerArrayWidget::DataStorageArray(array));

    if (aArrayOffsetLineEdit->text() != "") {
        arrayTableWidget->setAAddressOffset(aArrayOffsetLineEdit->text().toULong(&ok));
        if (ok == false) {
            qWarning() << "Invalid string for address offset." << aArrayOffsetLineEdit->text();
        }
    }else{
        arrayTableWidget->setAAddressOffset(0);
    }

    if (aArrayStrideLineEdit->text() != "") {
        arrayTableWidget->setAAddressStride(aArrayStrideLineEdit->text().toULong(&ok));
        if (ok == false) {
            qWarning() << "Invalid string for address stride." << aArrayStrideLineEdit->text();
        }
    }else{
        arrayTableWidget->setAAddressStride(1);
    }

    // Send signal to get variable address.
    emit evaluateVariableExpression(_aVariableId, aVariableNameLineEdit->text());
}

QString SeerArrayVisualizerWidget::aVariableName () const {
    return aVariableNameLineEdit->text();
}

void SeerArrayVisualizerWidget::setAVariableAddress (const QString& address) {

    unsigned long offset  = 0;
    bool          ok      = false;

    if (address == "") {

        aVariableAddressLineEdit->setText("");
        offset = 0;

    }else{

        // Test for base10
        if (ok == false) {
            offset = address.toULong(&ok, 10);
            if (ok) {
                aVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
            }
        }

        // Test for base16
        if (ok == false) {
            offset = address.toULong(&ok, 16);
            if (ok) {
                aVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
            }
        }

        if (ok == false) {
            aVariableAddressLineEdit->setText("not an address");
            offset = 0;
        }
    }

    arrayTableWidget->setAAddressOffset(0);
}

QString SeerArrayVisualizerWidget::aVariableAddress () const {
    return aVariableAddressLineEdit->text();
}

void SeerArrayVisualizerWidget::setAVariableLength (const QString& length) {
    aArrayLengthLineEdit->setText(length);
}

QString SeerArrayVisualizerWidget::aVariableLength () const {
    return aArrayLengthLineEdit->text();
}

void SeerArrayVisualizerWidget::setAVariableOffset (const QString& offset) {
    aArrayOffsetLineEdit->setText(offset);
}

QString SeerArrayVisualizerWidget::aVariableOffset () const {
    return aArrayOffsetLineEdit->text();
}

void SeerArrayVisualizerWidget::setAVariableStride (const QString& stride) {
    aArrayStrideLineEdit->setText(stride);
}

QString SeerArrayVisualizerWidget::aVariableStride () const {
    return aArrayStrideLineEdit->text();
}



void SeerArrayVisualizerWidget::setBVariableName (const QString& name) {

    setWindowTitle("Seer Array Visualizer - '" + name + "'");

    bVariableNameLineEdit->setText(name);

    if (bVariableNameLineEdit->text() == "") {
        bVariableAddressLineEdit->setText("");
        bArrayLengthLineEdit->setText("");
        bArrayOffsetLineEdit->setText("");
        bArrayStrideLineEdit->setText("");

        arrayTableWidget->setBData("deleted", 0);

        return;
    }

    // Coalesce this variable's setup (address reset + data/offset/stride) into
    // a single table rebuild; the guard flushes it when it leaves scope.
    SeerArrayWidget::BulkUpdate bulk(arrayTableWidget);

    setBVariableAddress("");

    // Clear old contents.
    QByteArray array;
    bool ok;

    arrayTableWidget->setBData(name, new SeerArrayWidget::DataStorageArray(array));

    if (bArrayOffsetLineEdit->text() != "") {
        arrayTableWidget->setBAddressOffset(bArrayOffsetLineEdit->text().toULong(&ok));
        if (ok == false) {
            qWarning() << "Invalid string for address offset." << bArrayOffsetLineEdit->text();
        }
    }else{
        arrayTableWidget->setBAddressOffset(0);
    }

    if (bArrayStrideLineEdit->text() != "") {
        arrayTableWidget->setBAddressStride(bArrayStrideLineEdit->text().toULong(&ok));
        if (ok == false) {
            qWarning() << "Invalid string for address stride." << bArrayStrideLineEdit->text();
        }
    }else{
        arrayTableWidget->setBAddressStride(1);
    }

    // Send signal to get variable address.
    emit evaluateVariableExpression(_bVariableId, bVariableNameLineEdit->text());
}

QString SeerArrayVisualizerWidget::bVariableName () const {
    return bVariableNameLineEdit->text();
}

void SeerArrayVisualizerWidget::setBVariableAddress (const QString& address) {

    unsigned long offset  = 0;
    bool          ok      = false;

    if (address == "") {

        bVariableAddressLineEdit->setText("");
        offset = 0;

    }else{

        // Test for base10
        if (ok == false) {
            offset = address.toULong(&ok, 10);
            if (ok) {
                bVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
            }
        }

        // Test for base16
        if (ok == false) {
            offset = address.toULong(&ok, 16);
            if (ok) {
                bVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
            }
        }

        if (ok == false) {
            bVariableAddressLineEdit->setText("not an address");
            offset = 0;
        }
    }

    arrayTableWidget->setBAddressOffset(0);
}

QString SeerArrayVisualizerWidget::bVariableAddress () const {
    return bVariableAddressLineEdit->text();
}

void SeerArrayVisualizerWidget::setBVariableLength (const QString& length) {
    bArrayLengthLineEdit->setText(length);
}

QString SeerArrayVisualizerWidget::bVariableLength () const {
    return bArrayLengthLineEdit->text();
}

void SeerArrayVisualizerWidget::setBVariableOffset (const QString& offset) {
    bArrayOffsetLineEdit->setText(offset);
}

QString SeerArrayVisualizerWidget::bVariableOffset () const {
    return bArrayOffsetLineEdit->text();
}

void SeerArrayVisualizerWidget::setBVariableStride (const QString& stride) {
    bArrayStrideLineEdit->setText(stride);
}

QString SeerArrayVisualizerWidget::bVariableStride () const {
    return bArrayStrideLineEdit->text();
}

void SeerArrayVisualizerWidget::handleText (const QString& text) {

    //qDebug() << text;

    if (text.contains(QRegularExpression("^([0-9]+)\\^done,value="))) {

        // 11^done,value="1"
        // 11^done,value="0x7fffffffd538"

        QString id_text = text.section('^', 0,0);

        if (id_text.toInt() == _aVariableId) {

            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
            QString address    = "";

            // Look for an address in the value.
            if (address == "") {
                QRegularExpression      re("0[xX][0-9a-fA-F]+");
                QRegularExpressionMatch match = re.match(value_text);

                if (match.hasMatch()) {
                    address = match.captured();
                }
            }

            // Look for a number in the value.
            if (address == "") {
                QRegularExpression      re("[0-9]+");
                QRegularExpressionMatch match = re.match(value_text);

                if (match.hasMatch()) {
                    address = match.captured();
                }
            }

            // Set the variable address.
            setAVariableAddress(address);
        }

        if (id_text.toInt() == _aLengthId) {

            // Set the memory length.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setAVariableLength(value_text);

            handleaRefreshButton();
        }

        if (id_text.toInt() == _aOffsetId) {

            // Set the memory offset.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setAVariableOffset(value_text);

            handleaRefreshButton();
        }

        if (id_text.toInt() == _aStrideId) {

            // Set the memory stride.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setAVariableStride(value_text);

            handleaRefreshButton();
        }

        if (id_text.toInt() == _bVariableId) {

            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
            QString address    = "";

            // Look for an address in the value.
            if (address == "") {
                QRegularExpression      re("0[xX][0-9a-fA-F]+");
                QRegularExpressionMatch match = re.match(value_text);

                if (match.hasMatch()) {
                    address = match.captured();
                }
            }

            // Look for a number in the value.
            if (address == "") {
                QRegularExpression      re("[0-9]+");
                QRegularExpressionMatch match = re.match(value_text);

                if (match.hasMatch()) {
                    address = match.captured();
                }
            }

            // Set the variable address.
            setBVariableAddress(address);
        }

        if (id_text.toInt() == _bLengthId) {

            // Set the memory length.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setBVariableLength(value_text);

            handlebRefreshButton();
        }

        if (id_text.toInt() == _bOffsetId) {

            // Set the memory offset.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setBVariableOffset(value_text);

            handlebRefreshButton();
        }

        if (id_text.toInt() == _bStrideId) {

            // Set the memory stride.
            QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);

            setBVariableStride(value_text);

            handlebRefreshButton();
        }

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^done,memory="))) {

        // 3^done,memory=[{begin="0x0000000000613e70",offset="0x0000000000000000",end="0x0000000000613e71",contents="00"}]
        // 4^done,memory=[{begin="0x0000000000613e70",offset="0x0000000000000000",end="0x0000000000613ed4",contents="000000000000000000000000"}]

        QString id_text = text.section('^', 0,0);

        if (id_text.toInt() == _aMemoryId) {

            QString memory_text = Seer::parseFirst(text, "memory=", '[', ']', false);

            QStringList range_list = Seer::parse(memory_text, "", '{', '}', false);

            // Loop through the memory ranges.
            for ( const auto& range_text : range_list  ) {

                QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false);

                // Convert hex string to byte array.
                QByteArray array;

                for (int i = 0; i<contents_text.size(); i += 2) {
                    QString num = contents_text.mid(i, 2);
                    bool ok = false;
                    array.push_back(num.toInt(&ok, 16));
                    Q_ASSERT(ok);
                }

                // Give the byte array to the hex widget.
                bool ok;
                SeerArrayWidget::BulkUpdate bulk(arrayTableWidget);

                arrayTableWidget->setAData(arrayTableWidget->aLabel(), new SeerArrayWidget::DataStorageArray(array));

                if (aArrayOffsetLineEdit->text() != "") {
                    arrayTableWidget->setAAddressOffset(aArrayOffsetLineEdit->text().toULong(&ok));
                    if (ok == false) {
                        qWarning() << "Invalid string for address offset." << aArrayOffsetLineEdit->text();
                    }
                }else{
                    arrayTableWidget->setAAddressOffset(0);
                }

                if (aArrayStrideLineEdit->text() != "") {
                    arrayTableWidget->setAAddressStride(aArrayStrideLineEdit->text().toULong(&ok));
                    if (ok == false) {
                        qWarning() << "Invalid string for address stride." << aArrayStrideLineEdit->text();
                    }
                }else{
                    arrayTableWidget->setAAddressStride(1);
                }

                break; // Take just the first range for now.
            }
        }

        if (id_text.toInt() == _bMemoryId) {

            QString memory_text = Seer::parseFirst(text, "memory=", '[', ']', false);

            QStringList range_list = Seer::parse(memory_text, "", '{', '}', false);

            // Loop through the memory ranges.
            for ( const auto& range_text : range_list  ) {

                QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false);

                // Convert hex string to byte array.
                QByteArray array;

                for (int i = 0; i<contents_text.size(); i += 2) {
                    QString num = contents_text.mid(i, 2);
                    bool ok = false;
                    array.push_back(num.toInt(&ok, 16));
                    Q_ASSERT(ok);
                }

                // Give the byte array to the hex widget.
                bool ok;
                SeerArrayWidget::BulkUpdate bulk(arrayTableWidget);

                arrayTableWidget->setBData(arrayTableWidget->bLabel(), new SeerArrayWidget::DataStorageArray(array));

                if (bArrayOffsetLineEdit->text() != "") {
                    arrayTableWidget->setBAddressOffset(bArrayOffsetLineEdit->text().toULong(&ok));
                    if (ok == false) {
                        qWarning() << "Invalid string for address offset." << bArrayOffsetLineEdit->text();
                    }
                }else{
                    arrayTableWidget->setBAddressOffset(0);
                }

                if (bArrayStrideLineEdit->text() != "") {
                    arrayTableWidget->setBAddressStride(bArrayStrideLineEdit->text().toULong(&ok));
                    if (ok == false) {
                        qWarning() << "Invalid string for address stride." << bArrayStrideLineEdit->text();
                    }
                }else{
                    arrayTableWidget->setBAddressStride(1);
                }

                break; // Take just the first range for now.
            }
        }

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^error,msg="))) {

        // 12^error,msg="No symbol \"return\" in current context."
        // 13^error,msg="No symbol \"cout\" in current context."
        // 3^error,msg="Unable to read memory."

        QString id_text = text.section('^', 0,0);

        if (id_text.toInt() == _aVariableId) {
            aVariableAddressLineEdit->setText( Seer::filterEscapes(Seer::parseFirst(text, "msg=", '"', '"', false)) );
        }

        if (id_text.toInt() == _aMemoryId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            if (msg_text != "") {
                QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
            }
        }

        if (id_text.toInt() == _aLengthId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            aArrayLengthLineEdit->setText("");
            aArrayLengthLineEdit->setFocus();
        }

        if (id_text.toInt() == _aOffsetId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            aArrayOffsetLineEdit->setText("");
            aArrayOffsetLineEdit->setFocus();
        }

        if (id_text.toInt() == _aStrideId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            aArrayStrideLineEdit->setText("");
            aArrayStrideLineEdit->setFocus();
        }

        if (id_text.toInt() == _bVariableId) {
            bVariableAddressLineEdit->setText( Seer::filterEscapes(Seer::parseFirst(text, "msg=", '"', '"', false)) );
        }

        if (id_text.toInt() == _bMemoryId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            if (msg_text != "") {
                QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
            }
        }

        if (id_text.toInt() == _bLengthId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            bArrayLengthLineEdit->setText("");
            bArrayLengthLineEdit->setFocus();
        }

        if (id_text.toInt() == _bOffsetId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            bArrayOffsetLineEdit->setText("");
            bArrayOffsetLineEdit->setFocus();
        }

        if (id_text.toInt() == _bStrideId) {
            // Display the error message.
            QString msg_text = Seer::parseFirst(text, "msg=", false);

            QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));

            bArrayStrideLineEdit->setText("");
            bArrayStrideLineEdit->setFocus();
        }

    // At a stopping point, refresh.
    }else if (text.startsWith("*stopped,reason=\"")) {

        if (autoRefreshCheckBox->isChecked()) {
            handleaRefreshButton();
            handlebRefreshButton();
        }

    }else{
        // Ignore anything else.
    }
}

void SeerArrayVisualizerWidget::handleaRefreshButton () {

    if (aVariableNameLineEdit->text() == "") {
        return;
    }

    if (aVariableAddressLineEdit->text() == "") {
        return;
    }

    if (aVariableAddressLineEdit->text() == "not an address") {
        return;
    }

    int bytes = aArrayLengthLineEdit->text().toInt() * Seer::typeBytes(aArrayDisplayFormatComboBox->currentText());

    //qDebug() << _aMemoryId << aVariableAddressLineEdit->text() << aArrayLengthLineEdit->text() << aArrayDisplayFormatComboBox->currentText() << bytes;

    emit evaluateMemoryExpression(_aMemoryId, aVariableAddressLineEdit->text(), bytes);
}

void SeerArrayVisualizerWidget::handlebRefreshButton () {

    if (bVariableNameLineEdit->text() == "") {
        return;
    }

    if (bVariableAddressLineEdit->text() == "") {
        return;
    }

    if (bVariableAddressLineEdit->text() == "not an address") {
        return;
    }

    int bytes = bArrayLengthLineEdit->text().toInt() * Seer::typeBytes(bArrayDisplayFormatComboBox->currentText());

    //qDebug() << _bMemoryId << bVariableAddressLineEdit->text() << bArrayLengthLineEdit->text() << bArrayDisplayFormatComboBox->currentText() << bytes;

    emit evaluateMemoryExpression(_bMemoryId, bVariableAddressLineEdit->text(), bytes);
}

void SeerArrayVisualizerWidget::handleHelpButton () {

    SeerHelpPageDialog* help = new SeerHelpPageDialog;
    help->loadFile(":/seer/resources/help/ArrayVisualizer.md");
    help->show();
    help->raise();
}

void SeerArrayVisualizerWidget::handleaVariableNameLineEdit () {

    setAVariableName (aVariableNameLineEdit->text());
}

void SeerArrayVisualizerWidget::handlebVariableNameLineEdit () {

    setBVariableName (bVariableNameLineEdit->text());
}

void SeerArrayVisualizerWidget::handleaElementLengthLineEdit () {

    if (aVariableLength() == "") {
        return;
    }

    emit evaluateVariableExpression(_aLengthId, aVariableLength());
}

void SeerArrayVisualizerWidget::handleaElementOffsetLineEdit () {

    if (aVariableOffset() == "") {
        return;
    }

    emit evaluateVariableExpression(_aOffsetId, aVariableOffset());
}

void SeerArrayVisualizerWidget::handleaElementStrideLineEdit () {

    if (aVariableStride() == "") {
        return;
    }

    emit evaluateVariableExpression(_aStrideId, aVariableStride());
}

void SeerArrayVisualizerWidget::handlebElementLengthLineEdit () {

    if (bVariableLength() == "") {
        return;
    }

    emit evaluateVariableExpression(_bLengthId, bVariableLength());
}

void SeerArrayVisualizerWidget::handlebElementOffsetLineEdit () {

    if (bVariableOffset() == "") {
        return;
    }

    emit evaluateVariableExpression(_bOffsetId, bVariableOffset());
}

void SeerArrayVisualizerWidget::handlebElementStrideLineEdit () {

    if (bVariableStride() == "") {
        return;
    }

    emit evaluateVariableExpression(_bStrideId, bVariableStride());
}

void SeerArrayVisualizerWidget::handleaArrayDisplayFormatComboBox (int index) {

    //qDebug() << index;

    if (index == 0) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::Int16ArrayMode);

    }else if (index == 1) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::Int32ArrayMode);

    }else if (index == 2) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::Int64ArrayMode);

    }else if (index == 3) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt16ArrayMode);

    }else if (index == 4) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt32ArrayMode);

    }else if (index == 5) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt64ArrayMode);

    }else if (index == 6) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::Float32ArrayMode);

    }else if (index == 7) {
        arrayTableWidget->setAArrayMode(SeerArrayWidget::Float64ArrayMode);

    }else{
        // Do nothing.
    }

    handleaRefreshButton();
}

void SeerArrayVisualizerWidget::handlebArrayDisplayFormatComboBox (int index) {

    //qDebug() << index;

    if (index == 0) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::Int16ArrayMode);

    }else if (index == 1) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::Int32ArrayMode);

    }else if (index == 2) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::Int64ArrayMode);

    }else if (index == 3) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt16ArrayMode);

    }else if (index == 4) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt32ArrayMode);

    }else if (index == 5) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt64ArrayMode);

    }else if (index == 6) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::Float32ArrayMode);

    }else if (index == 7) {
        arrayTableWidget->setBArrayMode(SeerArrayWidget::Float64ArrayMode);

    }else{
        // Do nothing.
    }

    handlebRefreshButton();
}

void SeerArrayVisualizerWidget::handleaAxisComboBox (int index) {

    arrayTableWidget->setAAxis(aAxisComboBox->itemText(index));

    handleDataChanged();
}

void SeerArrayVisualizerWidget::handlebAxisComboBox (int index) {

    arrayTableWidget->setBAxis(bAxisComboBox->itemText(index));

    handleDataChanged();
}

void SeerArrayVisualizerWidget::handleDataChanged () {

    // Delete old series.
    if (_aSeries) {
        arrayChartView->chart()->removeSeries(_aSeries);
        arrayChartView->chart()->update();
        delete _aSeries;
        _aSeries = 0;
    }

    if (_bSeries) {
        arrayChartView->chart()->removeSeries(_bSeries);
        arrayChartView->chart()->update();
        delete _bSeries;
        _bSeries = 0;
    }

    // If only the first array is defined, create a series for it.
    // Handle the X and Y axis.
    if (arrayTableWidget->aSize() > 0 && arrayTableWidget->bSize() == 0) {

        createASeries();

    // If only the second array is defined, create a series for it.
    // Handle the X and Y axis.
    }else if (arrayTableWidget->aSize() == 0 && arrayTableWidget->bSize() > 0) {

        createBSeries();

    } else if (arrayTableWidget->aSize() > 0 && arrayTableWidget->bSize() > 0) {

        // If the axis is the same for the two arrays, plot them as
        // two unique series.
        if (arrayTableWidget->aAxis() == arrayTableWidget->bAxis()) {

            createASeries();
            createBSeries();

        // Otherwise, we have an X and Y axis.
        // Plot one series using the two arrays as an X and as a Y.
        }else{

            if (scatterRadioButton->isChecked()) {

                QScatterSeries* scatter = new QScatterSeries;
                scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
                scatter->setMarkerSize(7);
                _aSeries = scatter;

            }else if (lineRadioButton->isChecked()) {

                QLineSeries* line = new QLineSeries;
                _aSeries = line;

            }else if (splineRadioButton->isChecked()) {

                QSplineSeries* line  = new QSplineSeries;
                _aSeries = line;

            }else{
                qWarning() << "Invalid line type.";
                return;
            }

            _aSeries->setPointsVisible(false);
            _aSeries->setPointLabelsVisible(false);
            _aSeries->setPointLabelsClipping(true);

            const QVector<double>& avalues = arrayTableWidget->aArrayValues();
            const QVector<double>& bvalues = arrayTableWidget->bArrayValues();

            if (arrayTableWidget->aAxis() == "X" && arrayTableWidget->bAxis() == "Y") {

                for (int i = 0; i < std::min(avalues.size(),bvalues.size()); ++i) {
                    _aSeries->append(avalues[i], bvalues[i]);
                }

            }else if (arrayTableWidget->aAxis() == "Y" && arrayTableWidget->bAxis() == "X") {

                for (int i = 0; i < std::min(avalues.size(),bvalues.size()); ++i) {
                    _aSeries->append(bvalues[i], avalues[i]);
                }

            }else{
                qWarning() << "Invalid axis type of '" << arrayTableWidget->aAxis() << "'. Not 'X' or 'Y'.";
            }

            _aSeries->setName(aVariableName() + "/" + bVariableName());
            _aSeries->setName(QString("%1:%2:%3 | %4:%5:%6")
                                      .arg(arrayTableWidget->aLabel()).arg(arrayTableWidget->aAddressOffset()).arg(arrayTableWidget->aAddressStride())
                                      .arg(arrayTableWidget->bLabel()).arg(arrayTableWidget->bAddressOffset()).arg(arrayTableWidget->bAddressStride()));
        }
    }

    if (_aSeries) {
        QObject::connect(_aSeries, &QLineSeries::hovered,    this, &SeerArrayVisualizerWidget::handleSeriesHovered);

        arrayChartView->chart()->addSeries(_aSeries);
        arrayChartView->chart()->createDefaultAxes();
        redecorateScatterSeries(_aSeries);
    }

    if (_bSeries) {
        QObject::connect(_bSeries, &QLineSeries::hovered,    this, &SeerArrayVisualizerWidget::handleSeriesHovered);

        arrayChartView->chart()->addSeries(_bSeries);
        arrayChartView->chart()->createDefaultAxes();
        redecorateScatterSeries(_bSeries);
    }

    // Zoom out slightly to allow for text label at edges.
    arrayChartView->chart()->zoomReset();
    arrayChartView->chart()->zoom(.9);
    arrayChartView->chart()->update();

    // Check for points or labels to be shown.
    handlePointsCheckBox();
    handleLabelsCheckBox();
}

void SeerArrayVisualizerWidget::writeSettings() {

    QSettings settings;

    settings.beginGroup("arrayvisualizerwindow"); {
        settings.setValue("size", size());
        settings.setValue("splitter", splitter->saveState());
    } settings.endGroup();
}

void SeerArrayVisualizerWidget::readSettings() {

    QSettings settings;

    settings.beginGroup("arrayvisualizerwindow"); {
        resize(settings.value("size", QSize(800, 400)).toSize());
        splitter->restoreState(settings.value("splitter").toByteArray());
    } settings.endGroup();
}

void SeerArrayVisualizerWidget::resizeEvent (QResizeEvent* event) {

    writeSettings();

    QWidget::resizeEvent(event);
}

void SeerArrayVisualizerWidget::handleSplitterMoved (int pos, int index) {

    Q_UNUSED(pos);
    Q_UNUSED(index);

    writeSettings();
}

void SeerArrayVisualizerWidget::handleSeriesHovered (const QPointF& point, bool state) {

    //qDebug() << "QPointF=" << point << "State=" << state << "MapToPosition=" << arrayChartView->chart()->mapToPosition(point) << "MapFromScene=" << arrayChartView->mapFromScene(arrayChartView->chart()->mapToPosition(point));

    if (state) {
        QToolTip::showText(arrayChartView->mapToGlobal(arrayChartView->mapFromScene(arrayChartView->chart()->mapToPosition(point))), QString("%1 / %2").arg(point.x()).arg(point.y()), this, QRect(), 10000);
    }else{
        QToolTip::hideText();
    }
}

void SeerArrayVisualizerWidget::handleTitleLineEdit () {

    arrayChartView->chart()->setTitle(titleLineEdit->text());

    titleLineEdit->setText("");
}

void SeerArrayVisualizerWidget::handlePointsCheckBox () {

    if (_aSeries) {
        _aSeries->setPointsVisible(pointsCheckBox->isChecked());
        arrayChartView->chart()->update();
    }

    if (_bSeries) {
        _bSeries->setPointsVisible(pointsCheckBox->isChecked());
        arrayChartView->chart()->update();
    }
}

void SeerArrayVisualizerWidget::handleLabelsCheckBox () {

    if (_aSeries) {
        _aSeries->setPointLabelsVisible(labelsCheckBox->isChecked());
        arrayChartView->chart()->update();
    }

    if (_bSeries) {
        _bSeries->setPointLabelsVisible(labelsCheckBox->isChecked());
        arrayChartView->chart()->update();
    }
}

void SeerArrayVisualizerWidget::handleLineTypeButtonGroup () {

    if (scatterRadioButton->isChecked()) {
        pointsCheckBox->setChecked(true);
        pointsCheckBox->setEnabled(false);
    }else{
        pointsCheckBox->setChecked(false);
        pointsCheckBox->setEnabled(true);
    }

    handleDataChanged();
}

void SeerArrayVisualizerWidget::handleThemeChanged () {

    // Colorize icons for theme.
    Seer::colorizeAllIcons(this, Seer::iconColorTheme());

    // And the ChartView.
    Seer::colorizeChartViewItem(arrayChartView, Seer::iconColorTheme());
}

void SeerArrayVisualizerWidget::createASeries() {

    if (scatterRadioButton->isChecked()) {

        QScatterSeries* scatter = new QScatterSeries;
        scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
        scatter->setMarkerSize(7);
        _aSeries = scatter;

    }else if (lineRadioButton->isChecked()) {

        QLineSeries* line = new QLineSeries;
        _aSeries = line;

    }else if (splineRadioButton->isChecked()) {

        QSplineSeries* line  = new QSplineSeries;
        _aSeries = line;

    }else{
        qWarning() << "Invalid line type.";
        return;
    }

    _aSeries->setPointsVisible(false);
    _aSeries->setPointLabelsVisible(false);
    _aSeries->setPointLabelsClipping(true);

    const QVector<double>& values = arrayTableWidget->aArrayValues();

    if (arrayTableWidget->aAxis() == "Y") {

        for (int i = 0; i < values.size(); ++i) {
            _aSeries->append(i, values[i]);
        }

    }else if (arrayTableWidget->aAxis() == "X") {

        for (int i = 0; i < values.size(); ++i) {
            _aSeries->append(values[i], i);
        }

    }else{
        qWarning() << "Invalid axis type of '" << arrayTableWidget->aAxis() << "'. Not 'X' or 'Y'.";
    }

    _aSeries->setName(QString("%1:%2:%3").arg(arrayTableWidget->aLabel()).arg(arrayTableWidget->aAddressOffset()).arg(arrayTableWidget->aAddressStride()));
}

void SeerArrayVisualizerWidget::createBSeries() {

    if (scatterRadioButton->isChecked()) {

        QScatterSeries* scatter = new QScatterSeries;
        scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
        scatter->setMarkerSize(7);
        _bSeries = scatter;

    }else if (lineRadioButton->isChecked()) {

        QLineSeries* line = new QLineSeries;
        _bSeries = line;

    }else if (splineRadioButton->isChecked()) {

        QSplineSeries* line  = new QSplineSeries;
        _bSeries = line;

    }else{
        qWarning() << "Invalid line type.";
        return;
    }

    _bSeries->setPointsVisible(false);
    _bSeries->setPointLabelsVisible(false);
    _bSeries->setPointLabelsClipping(true);

    const QVector<double>& values = arrayTableWidget->bArrayValues();

    if (arrayTableWidget->bAxis() == "Y") {

        for (int i = 0; i < values.size(); ++i) {
            _bSeries->append(i, values[i]);
        }

    }else if (arrayTableWidget->bAxis() == "X") {

        for (int i = 0; i < values.size(); ++i) {
            _bSeries->append(values[i], i);
        }

    }else{
        qWarning() << "Invalid axis type of '" << arrayTableWidget->bAxis() << "'. Not 'X' or 'Y'.";
    }

    _bSeries->setName(QString("%1:%2:%3").arg(arrayTableWidget->bLabel()).arg(arrayTableWidget->bAddressOffset()).arg(arrayTableWidget->bAddressStride()));
}

