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

#include "SeerStackFramesBrowserWidget.h"
#include "SeerUtl.h"
#include <QtWidgets/QTreeWidget>
#include <QtWidgets/QTreeWidgetItemIterator>
#include <QtWidgets/QApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>

SeerStackFramesBrowserWidget::SeerStackFramesBrowserWidget (QWidget* parent) : QWidget(parent) {

    // Construct the UI.
    setupUi(this);

    _previousStackFrameText = "";

    // Setup the widgets
    stackTreeWidget->setMouseTracking(true);
    stackTreeWidget->setSortingEnabled(false);
    stackTreeWidget->resizeColumnToContents(0); // level
    stackTreeWidget->resizeColumnToContents(1); // func
    stackTreeWidget->resizeColumnToContents(2); // file
    stackTreeWidget->resizeColumnToContents(3); // line
    stackTreeWidget->resizeColumnToContents(4); // fullname
    stackTreeWidget->resizeColumnToContents(5); // addr
    stackTreeWidget->resizeColumnToContents(6); // arch

    stackTreeWidget->clear();

    // Connect things.
    QObject::connect(stackTreeWidget, &QTreeWidget::itemClicked,          this,  &SeerStackFramesBrowserWidget::handleItemClicked);
    QObject::connect(stackTreeWidget, &QTreeWidget::itemEntered,          this,  &SeerStackFramesBrowserWidget::handleItemEntered);
}

SeerStackFramesBrowserWidget::~SeerStackFramesBrowserWidget () {
}

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

    // Stackframes is important. Always do it.
    //
    // Don't do any work if the widget is hidden.
    // if (isHidden()) {
    //     return;
    // }

    QApplication::setOverrideCursor(Qt::BusyCursor);

    if (text.startsWith("^done,stack=[") && text.endsWith("]")) {

        // ^done,stack=[
        //     frame={level=\"0\",addr=\"0x0000000000400d72\",func=\"function1\",file=\"function1.cpp\",fullname=\"/home/erniep/Development/Peak/src/Seer/helloworld/function1.cpp\",line=\"7\",arch=\"i386:x86-64\"},
        //     frame={level=\"1\",addr=\"0x0000000000400cc3\",func=\"main\",file=\"helloworld.cpp\",fullname=\"/home/erniep/Development/Peak/src/Seer/helloworld/helloworld.cpp\",line=\"14\",arch=\"i386:x86-64\"}
        // ]

        // If we are simply moving up and down the stack (via frame-select) then the text won't change.
        // If it is different, reconstruct the tree. Select the first frame.
        if (text != _previousStackFrameText) {

            _previousStackFrameText = text;

            stackTreeWidget->clear();

            QString stack_text = Seer::parseFirst(text, "stack=", '[', ']', false);

            if (stack_text != "") {

                // Parse through the frame list and set the current lines that are in the frame list.
                QStringList frame_list = Seer::parse(text, "frame=", '{', '}', false);

                QString firstLiveFrameLevel     = "";
                QString firstLiveFrameFile      = "";
                QString firstLiveFrameFullname  = "";
                QString firstLiveFrameLine      = "";

                QString firstFrameLevel         = "";
                QString firstFrameAddr          = "";

                for ( const auto& frame_text : frame_list  ) {

                    QString level_text    = Seer::parseFirst(frame_text, "level=",    '"', '"', false);
                    QString addr_text     = Seer::parseFirst(frame_text, "addr=",     '"', '"', false);
                    QString func_text     = Seer::parseFirst(frame_text, "func=",     '"', '"', false);
                    QString file_text     = Seer::parseFirst(frame_text, "file=",     '"', '"', false);
                    QString fullname_text = Seer::parseFirst(frame_text, "fullname=", '"', '"', false);
                    QString line_text     = Seer::parseFirst(frame_text, "line=",     '"', '"', false);
                    QString arch_text     = Seer::parseFirst(frame_text, "arch=",     '"', '"', false);

                    // Create the item.
                    QTreeWidgetItem* item = new QTreeWidgetItem;
                    item->setText(0, level_text);
                    item->setText(1, func_text);
                    item->setText(2, QFileInfo(file_text).fileName());
                    item->setText(3, line_text);
                    item->setText(4, fullname_text);
                    item->setText(5, addr_text);
                    item->setText(6, arch_text);

                    // Enable/disable interaction with this row depending if there is a valid file and line number.
                    if (file_text != "" && fullname_text != "" && line_text != "") {

                        item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);

                        // Save the first live frame (that has a valid file and lineno) so we can select it automatically.
                        // This only happens if the stack text is different (a stop point is reached).
                        if (firstLiveFrameLevel == "") {
                            firstLiveFrameLevel    = level_text;
                            firstLiveFrameFile     = QFileInfo(file_text).fileName();
                            firstLiveFrameFullname = fullname_text;
                            firstLiveFrameLine     = line_text;
                        }

                    }else{
                        item->setFlags(Qt::NoItemFlags);
                    }

                    // Get the address of the first entry of the frame.
                    if (addr_text != "") {
                        if (firstFrameLevel == "") {
                            firstFrameLevel = level_text;
                            firstFrameAddr  = addr_text;
                        }
                    }

                    // Add the frame to the tree.
                    stackTreeWidget->addTopLevelItem(item);
                }

                // Automatically bring up the file for the first live frame.
                if (firstLiveFrameLevel != "") {
                    if (firstLiveFrameFile != "" && firstLiveFrameFullname != "") {
                        emit selectedFile(firstLiveFrameFile, firstLiveFrameFullname, firstLiveFrameLine.toInt());
                    }
                }

                // Automatically bring up the assembly for the first frame.
                emit maybeSelectedAddress(firstLiveFrameFile, firstLiveFrameFullname, firstFrameAddr);
            }

            // Select the first frame level.
            // ??? Perhaps show the current frame level somehow.
            stackTreeWidget->clearSelection();

            QList<QTreeWidgetItem*> matches = stackTreeWidget->findItems("0", Qt::MatchExactly, 0);
            if (matches.size() > 0) {
                stackTreeWidget->setCurrentItem(matches.first());
            }
        }

    }else{
        // Ignore others.
    }

    stackTreeWidget->resizeColumnToContents(0);
    stackTreeWidget->resizeColumnToContents(1);
    stackTreeWidget->resizeColumnToContents(2);
    stackTreeWidget->resizeColumnToContents(3);
    stackTreeWidget->resizeColumnToContents(4);
    stackTreeWidget->resizeColumnToContents(5);
    stackTreeWidget->resizeColumnToContents(6);

    QApplication::restoreOverrideCursor();
}

void SeerStackFramesBrowserWidget::handleStoppingPointReached () {

    // Stackframes is important. Always do it.
    //
    // Don't do any work if the widget is hidden.
    // if (isHidden()) {
    //     return;
    // }

    refresh();
}

void SeerStackFramesBrowserWidget::handleSessionTerminated () {

    // Delete previous contents and remembered state.
    stackTreeWidget->clear();

    _previousStackFrameText = "";
}

void SeerStackFramesBrowserWidget::refresh () {

    // Don't do any work if the widget is hidden.
    if (isHidden()) {
        return;
    }

    emit refreshStackFrames();
}

void SeerStackFramesBrowserWidget::handleItemClicked (QTreeWidgetItem* item, int column) {

    Q_UNUSED(column);

    int lineno = item->text(3).toInt();

    // Select frame.
    emit selectedFrame(item->text(0).toInt());

    // Select file if we can.
    if (item->text(2) != "" && item->text(4) != "") {
        emit selectedFile(item->text(2), item->text(4), lineno);
    }

    // Select address if we can.
    if (item->text(5) != "") {
        emit maybeSelectedAddress(item->text(2), item->text(4), item->text(5));
    }
}

void SeerStackFramesBrowserWidget::handleItemEntered (QTreeWidgetItem* item, int column) {

    Q_UNUSED(column);

    item->setToolTip(0, item->text(0) + " : " + item->text(1) + " : " + item->text(2) + " : " + item->text(3));

    for (int i=1; i<stackTreeWidget->columnCount(); i++) { // Copy tooltip to other columns.
        item->setToolTip(i, item->toolTip(0));
    }
}

void SeerStackFramesBrowserWidget::showEvent (QShowEvent* event) {

    QWidget::showEvent(event);

    refresh();
}

