/*
 * Copyright (c) 2015, Technische Universität Kaiserslautern
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors:
 *    Janik Schlemminger
 *    Robert Gernhardt
 *    Matthias Jung
 *    Derek Christ
 *    Iron Prando da Silva
 */

#include "tracefiletab.h"
#include "QFileInfo"
#include "businessObjects/commentmodel.h"
#include "businessObjects/configmodels.h"
#include "businessObjects/pythoncaller.h"
#include "businessObjects/traceplotlinemodel.h"
#include "businessObjects/tracetime.h"
#include "businessObjects/dramTimeDependencies/phasedependenciestracker.h"
#include "qmessagebox.h"
#include "queryeditor.h"
#include "qwt_legend.h"
#include "qwt_plot_canvas.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_histogram.h"
#include "qwt_plot_layout.h"
#include "qwt_plot_magnifier.h"
#include "qwt_plot_panner.h"
#include "qwt_scale_draw.h"
#include "qwt_scale_widget.h"
#include "ui_tracefiletab.h"

#include <QCloseEvent>
#include <QDebug>
#include <QFileDialog>
#include <QItemDelegate>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QString>
#include <QTextStream>
#include <QtConcurrent/QtConcurrent>
#include <iostream>

TraceFileTab::TraceFileTab(QWidget *parent, const QString &path)
    : QWidget(parent), ui(new Ui::TraceFileTab), commentModel(new CommentModel(this)),
      navigator(new TraceNavigator(path, commentModel, this)),
      mcConfigModel(new McConfigModel(navigator->TraceFile(), this)),
      memSpecModel(new MemSpecModel(navigator->TraceFile(), this)),
      availableRowsModel(new AvailableTracePlotLineModel(navigator->GeneralTraceInfo(), this)),
      selectedRowsModel(new SelectedTracePlotLineModel(navigator->GeneralTraceInfo(), this)),
      tracePlotLineDataSource(new TracePlotLineDataSource(selectedRowsModel, this)),
      depInfosView(new DependencyInfosModel(navigator->TraceFile(), this)),
      savingChangesToDB(false)
{
    ui->setupUi(this);
    this->path = path;

    std::cout << "Opening new tab for \"" << path.toStdString() << "\"" <<
              std::endl;

    ui->mcConfigView->setModel(mcConfigModel);
    ui->mcConfigView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);

    ui->memSpecView->setModel(memSpecModel);
    ui->memSpecView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

    ui->depInfosView->setModel(depInfosView);
    ui->depInfosView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

    setUpTraceSelector();
    initNavigatorAndItsDependentWidgets(path);
    setUpFileWatcher(path);
    setUpTraceplotScrollbar();
    setUpCommentView();

    setUpPossiblePhases();

    tracefileChanged();
}

TraceFileTab::~TraceFileTab()
{
    delete ui;
}

void TraceFileTab::commitChangesToDB()
{
    savingChangesToDB = true;
    navigator->commitChangesToDB();
}

void TraceFileTab::exportAsVCD()
{
    QString filename = QFileDialog::getSaveFileName(this, "Export to VCD", "",
                                                    "VCD files (*.vcd)");
    auto dumpVcd = [=]() {
        QString dump = PythonCaller::instance().exportAsVcd(path);

        QFile file(filename);
        file.open(QIODevice::WriteOnly | QIODevice::Text);
        QTextStream out(&file);
        out << dump;
        file.close();

        Q_EMIT statusChanged(QString("VCD export finished."));
    };

    if (filename != "") {
        QtConcurrent::run(dumpVcd);
    }
}

void TraceFileTab::setUpTraceSelector()
{
    ui->availableTreeView->setModel(availableRowsModel);
    ui->availableTreeView->setSelectionModel(availableRowsModel->selectionModel());
    ui->availableTreeView->installEventFilter(availableRowsModel);

    ui->selectedTreeView->setModel(selectedRowsModel);
    ui->selectedTreeView->setSelectionModel(selectedRowsModel->selectionModel());
    ui->selectedTreeView->installEventFilter(selectedRowsModel);

    connect(availableRowsModel, &AvailableTracePlotLineModel::returnPressed, selectedRowsModel,
            &SelectedTracePlotLineModel::addIndexesFromAvailableModel);

    connect(ui->availableTreeView, &QAbstractItemView::doubleClicked, availableRowsModel,
            &AvailableTracePlotLineModel::itemsDoubleClicked);
    connect(ui->selectedTreeView, &QAbstractItemView::doubleClicked, selectedRowsModel,
            &SelectedTracePlotLineModel::itemsDoubleClicked);

    connect(selectedRowsModel, &QAbstractItemModel::dataChanged, tracePlotLineDataSource,
            &TracePlotLineDataSource::updateModel);
    connect(selectedRowsModel, &QAbstractItemModel::rowsInserted, tracePlotLineDataSource,
            &TracePlotLineDataSource::updateModel);
    connect(selectedRowsModel, &QAbstractItemModel::rowsRemoved, tracePlotLineDataSource,
            &TracePlotLineDataSource::updateModel);
}

void TraceFileTab::setUpTraceplotScrollbar()
{
    QObject::connect(ui->traceplotScrollbar, SIGNAL(valueChanged(int)),
                     ui->traceplot, SLOT(verticalScrollbarChanged(int)));
}

void TraceFileTab::initNavigatorAndItsDependentWidgets(QString path)
{
    ui->traceplot->init(navigator, ui->traceplotScrollbar, tracePlotLineDataSource, commentModel);

    ui->traceScroller->init(navigator, ui->traceplot, tracePlotLineDataSource);
    connect(this, SIGNAL(colorGroupingChanged(ColorGrouping)),
            ui->traceScroller, SLOT(colorGroupingChanged(ColorGrouping)));

    ui->selectedTransactionTree->init(navigator);
    //ui->debugMessages->init(navigator,ui->traceplot);

    ui->bandwidthPlot->canvas()->installEventFilter(this);
    ui->powerPlot->canvas()->installEventFilter(this);
    ui->bufferPlot->canvas()->installEventFilter(this);
}

void TraceFileTab::setUpFileWatcher(QString path)
{
    fileWatcher = new QFileSystemWatcher(QStringList(path), this);
    QObject::connect(fileWatcher, SIGNAL(fileChanged(QString)), this,
                     SLOT(tracefileChanged()));
}

void TraceFileTab::setUpCommentView()
{
    ui->commentView->setModel(commentModel);
    ui->commentView->setSelectionModel(commentModel->selectionModel());
    ui->commentView->installEventFilter(commentModel);
    ui->commentView->setContextMenuPolicy(Qt::CustomContextMenu);

    QObject::connect(ui->commentView, &QTableView::customContextMenuRequested,
                     commentModel, &CommentModel::openContextMenu);

    QObject::connect(commentModel, &CommentModel::editTriggered, ui->commentView,
                     [=](const QModelIndex &index)
                     {
                         ui->tabWidget->setCurrentWidget(ui->tabComments);
                         ui->commentView->edit(index);
                         ui->commentView->scrollTo(index);
                     });

    QObject::connect(ui->commentView, &QTableView::doubleClicked,
                     commentModel, &CommentModel::rowDoubleClicked);
}

void TraceFileTab::setUpPossiblePhases() {
    const auto possiblePhases = ConfigurationFactory::possiblePhases(navigator->TraceFile());
    for (auto p : possiblePhases) {
        auto item = new QListWidgetItem(p, ui->depTabPossiblePhases);
        item->setFlags(item->flags() | Qt::ItemIsUserCheckable); // set checkable flag
        item->setCheckState(Qt::Unchecked); // AND initialize check state

    }

    ui->calculateDependencies->setEnabled(ConfigurationFactory::deviceSupported(navigator->TraceFile()));
}

void TraceFileTab::tracefileChanged()
{
    if (savingChangesToDB == true) {
        // Database has changed due to user action (e.g., saving comments).
        // No need to disable the "Save changes to DB" menu.
        savingChangesToDB = false;
        Q_EMIT statusChanged(QString("Changes saved "));
    } else {
        // External event changed the database file (e.g., the database file
        // was overwritten when running a new test).
        // The "Save changes to DB" menu must be disabled to avoid saving
        // changes to a corrupted or inconsistent file.
        Q_EMIT statusChanged(QString("At least one database has changed on disk "));
    }
    navigator->refreshData();
}

void TraceFileTab::closeEvent(QCloseEvent *event)
{
    if (navigator->existChangesToCommit())
    {
        QMessageBox saveDialog;
        saveDialog.setWindowTitle(QFileInfo(path).baseName());
        saveDialog.setText("The trace file has been modified.");
        saveDialog.setInformativeText("Do you want to save your changes?<br><b>Unsaved changes will be lost.</b>");
        saveDialog.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
        saveDialog.setDefaultButton(QMessageBox::Save);
        saveDialog.setIcon(QMessageBox::Warning);
        int returnCode = saveDialog.exec();

        switch (returnCode)
        {
        case QMessageBox::Cancel:
            event->ignore();
            break;
        case QMessageBox::Discard:
            event->accept();
            break;
        case QMessageBox::Save:
            commitChangesToDB();
            event->accept();
            break;
        };
    }
    else
        event->accept();
}

traceTime TraceFileTab::getCurrentTraceTime() const
{
    return navigator->CurrentTraceTime();
}

void TraceFileTab::navigateToTime(traceTime time)
{
    navigator->navigateToTime(time);
}

traceTime TraceFileTab::getZoomLevel() const
{
    TracePlot *traceplot = static_cast<TracePlot *>(ui->traceplot);
    return traceplot->ZoomLevel();
}

void TraceFileTab::setZoomLevel(traceTime zoomLevel)
{
    TracePlot *traceplot = static_cast<TracePlot *>(ui->traceplot);
    TraceScroller *tracescroller = static_cast<TraceScroller *>(ui->traceScroller);
    traceplot->setZoomLevel(zoomLevel);
    tracescroller->tracePlotZoomChanged();
}

std::shared_ptr<AbstractTracePlotLineModel::Node> TraceFileTab::saveTraceSelectorState() const
{
    return selectedRowsModel->getClonedRootNode();
}

void TraceFileTab::restoreTraceSelectorState(std::shared_ptr<AbstractTracePlotLineModel::Node> rootNode)
{
    selectedRowsModel->setRootNode(std::move(rootNode));
}

class ItemDelegate: public QItemDelegate
{
public:
    ItemDelegate(QObject* parent = nullptr): QItemDelegate(parent)
    {
    }

    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
    {
        if (index.column() == 1) {
            double progress = index.data().toDouble();
            QStyleOptionProgressBar opt;
            opt.rect = option.rect;
            opt.minimum = 0;
            opt.maximum = 100;
            opt.progress = static_cast<int>(floor(progress));
            opt.text = QString::number(progress, 'f', 2)+" %";
            opt.textVisible = true;
            QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, nullptr);
        } else {
            QItemDelegate::paint(painter, option, index);
        }
    }
};

void TraceFileTab::on_latencyTreeView_doubleClicked(const QModelIndex &index)
{
    // Get onlye the leaf:
    if(index.column() == 0 && index.model()->hasChildren(index) == false) {
        unsigned int id = index.data().toUInt();
        if(id!=0) {
            navigator->selectTransaction(id);
        }
    }
}

bool TraceFileTab::eventFilter(QObject *object, QEvent *event)
{
    if (auto canvas = qobject_cast<QwtPlotCanvas *>(object))
    {
        if (event->type() == QEvent::MouseButtonDblClick)
        {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);

            if (mouseEvent->button() != Qt::LeftButton)
                return false;

            QwtPlot *plot = canvas->plot();

            double realTime = plot->invTransform(QwtPlot::xBottom, mouseEvent->x());

            // Convert from seconds to picoseconds
            traceTime time = realTime * 1000 * 1000 * 1000 * 1000;

            navigator->navigateToTime(time);
            return true;
        }
    }

    return QWidget::eventFilter(object, event);
}

void TraceFileTab::on_calculateDependencies_clicked() {
    std::vector<QString> dependencyFilter;
    for (int row = 0; row < ui->depTabPossiblePhases->count(); row++) {
        auto item = ui->depTabPossiblePhases->item(row);
        if (item->checkState() == Qt::Checked)
            dependencyFilter.push_back(item->text());
    }
    
    savingChangesToDB = true;
    PhaseDependenciesTracker::calculateDependencies(navigator->TraceFile(), dependencyFilter);
    depInfosView = new DependencyInfosModel(navigator->TraceFile(), this);
    ui->depInfosView->setModel(depInfosView);
    ui->depInfosView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

}

void TraceFileTab::on_startLatencyAnalysis_clicked()
{
    // Setup Database:
    QSqlDatabase db = navigator->TraceFile().getDatabase();
    QSqlQuery query(db);

    // Check the count of transactions:
    QString sql = "SELECT COUNT(*) FROM Transactions;";
    query.exec(sql);
    query.next();
    int maxTransactions = query.value(0).toInt();

    // Create Database Setup and Query:
    sql = "SELECT ((p2.PhaseEnd - p1.PhaseBegin)/1000) as latency, t.id "
          "FROM Transactions t, Phases p1, Phases p2 "
          "WHERE t.id = p1.Transact "
          "AND t.id = p2.Transact "
          "AND p1.PhaseName = \"REQ\" "
          "AND p2.PhaseName = \"RESP\" ORDER BY latency;";

    query.exec(sql);

    // Creatoe model and fill it from Database:
    QStandardItemModel* model = new QStandardItemModel();

    int currentLatency = 0;
    QStandardItem* currentLatencyItem = nullptr;
    int counter = 0;
    while (query.next()) {
        if(query.value(0) != currentLatency) {
            currentLatencyItem = new QStandardItem(QString::number(query.value(0).toInt())+" ns");
            currentLatency = query.value(0).toInt();
            QList<QStandardItem *> row;
            row.append(currentLatencyItem);
            row.append(new QStandardItem());
            model->appendRow(row);
        }
        QStandardItem * id = new QStandardItem(query.value(1).toString());
        currentLatencyItem->appendRow(id);
        counter++;

        int percentage = int(ceil((double(counter))/(double(maxTransactions))*100.0));
        ui->latencyAnalysisProgressBar->setValue(percentage);
    }
    QStringList header = {"Latency","Occurences"};
    model->setHorizontalHeaderLabels(header);

    // Generate Histrogram and Tree:
    QwtPlotHistogram *hist = new QwtPlotHistogram;
    QVector<QwtIntervalSample> *intervals = new QVector<QwtIntervalSample>;
    for(int i = 0; i < model->rowCount(); i++) {
        double latency = model->item(i,0)->text().replace(" ns","").toDouble();
        int numberOfChilds = model->item(i)->rowCount();
        double percentage = 100*((double(numberOfChilds))/(double(counter)));
        model->item(i,1)->setText(QString::number(percentage));
        intervals->append(QwtIntervalSample(percentage, latency, latency+1));
    }
    ui->latencyTreeView->setItemDelegate(new ItemDelegate(ui->latencyTreeView));
    ui->latencyTreeView->setModel(model);
    hist->setSamples(*intervals);
    hist->attach(ui->latencyPlot);
    hist->setPen(QPen(QColor(255,0,0,100)));
    hist->setBrush(QBrush(QColor(255,0,0,255)));
    ui->latencyPlot->setAxisTitle(0,"Occurences [%]");
    QwtText axisTitle( "Latency [ns]" );
    axisTitle.setFont( ui->latencyPlot->axisTitle( QwtPlot::xBottom ).font() );
    ui->latencyPlot->setAxisTitle( QwtPlot::xBottom, axisTitle );
    ui->latencyPlot->replot();
}

void TraceFileTab::on_startPowerAnalysis_clicked()
{
    qDebug() << "Power Analysis";
    QSqlDatabase db = navigator->TraceFile().getDatabase();
    QSqlQuery query(db);

    QString sql = "SELECT time, AveragePower FROM Power;";

    query.exec(sql);

    QwtPointSeriesData * data = new QwtPointSeriesData;
    QwtPlotCurve * cur = new QwtPlotCurve("Speed");
    QVector<QPointF>* samples=new QVector<QPointF>;

    while (query.next()) {
        double time = query.value(0).toDouble();
        double power = query.value(1).toDouble();
        samples->push_back(QPointF(time,power));
    }

    //ui->powerPlot->setAxisTitle(QwtPlot::xBottom,"Time");
    //ui->powerPlot->setAxisLabelRotation(QwtPlot::xBottom,-50.0);
    //ui->powerPlot->setAxisLabelAlignment(QwtPlot::xBottom,Qt::AlignLeft|Qt::AlignBottom);
    //ui->powerPlot->setAxisTitle(QwtPlot::yLeft,"Power");

    data->setSamples(*samples);
    cur->setData(data);
    cur->attach(ui->powerPlot);
    cur->setPen(QPen(QColor(255,0,0)));

    QwtPlotMagnifier *mag1 = new QwtPlotMagnifier(ui->powerPlot->canvas());
    mag1->setAxisEnabled(QwtPlot::xBottom, true);
    mag1->setAxisEnabled(QwtPlot::yLeft, false);
    mag1->setWheelFactor(5);
    QwtPlotPanner *pan1 = new QwtPlotPanner(ui->powerPlot->canvas());
    pan1->setAxisEnabled(QwtPlot::xBottom, true);
    pan1->setAxisEnabled(QwtPlot::yLeft, false);

    ui->powerPlot->replot();

    // Bandwidth analysis:
    sql = "SELECT time, AverageBandwidth FROM Bandwidth;";
    query.exec(sql);

    QwtPointSeriesData * data2 = new QwtPointSeriesData;
    QwtPlotCurve * cur2 = new QwtPlotCurve("Speed");
    QVector<QPointF>* samples2=new QVector<QPointF>;

    while (query.next()) {
        double time = query.value(0).toDouble();
        double percentage = query.value(1).toDouble() * 100.0;
        samples2->push_back(QPointF(time, percentage));
    }

    data2->setSamples(*samples2);
    cur2->setData(data2);
    cur2->attach(ui->bandwidthPlot);
    cur2->setPen(QPen(QColor(255,0,0)));

    ui->bandwidthPlot->setAxisTitle(0,"Bandwidth [%]");
    ui->bandwidthPlot->setAxisScale(0,0.0,100.0);
    QwtText axisTitle2( "Time [s]" );
    axisTitle2.setFont( ui->bandwidthPlot->axisTitle( QwtPlot::xBottom ).font() );
    ui->bandwidthPlot->setAxisTitle( QwtPlot::xBottom, axisTitle2 );

    QwtPlotMagnifier *mag2 = new QwtPlotMagnifier(ui->bandwidthPlot->canvas());
    mag2->setAxisEnabled(QwtPlot::xBottom, true);
    mag2->setAxisEnabled(QwtPlot::yLeft, false);
    mag2->setWheelFactor(5);
    QwtPlotPanner *pan2 = new QwtPlotPanner(ui->bandwidthPlot->canvas());
    pan2->setAxisEnabled(QwtPlot::xBottom, true);
    pan2->setAxisEnabled(QwtPlot::yLeft, false);

    ui->bandwidthPlot->replot();

    // Buffer analysis:
    sql = "select max(BufferNumber) from BufferDepth;";
    query.exec(sql);
    query.next();
    unsigned int numberOfBuffers = query.value(0).toUInt();

    sql = "select MaxBufferDepth from GeneralInfo;";
    query.exec(sql);
    query.next();
    unsigned int maxBufferDepth = query.value(0).toUInt();

    sql = "select Time, AverageBufferDepth from BufferDepth where BufferNumber = 0"; // TODO
    query.exec(sql);

    QwtPointSeriesData * data3 = new QwtPointSeriesData;
    QwtPlotCurve * cur3 = new QwtPlotCurve("Speed");
    QVector<QPointF>* samples3 = new QVector<QPointF>;

    while (query.next()) {
        double time = query.value(0).toDouble();
        double queue = query.value(1).toDouble();
        samples3->push_back(QPointF(time, queue));
    }

    data3->setSamples(*samples3);
    cur3->setData(data3);
    cur3->attach(ui->bufferPlot);
    cur3->setPen(QPen(QColor(255,0,0)));

    ui->bufferPlot->setAxisTitle(0,"Buffer Utilization");
    ui->bufferPlot->setAxisScale(0,0.0, maxBufferDepth);
    QwtText axisTitle3( "Time [s]" );
    axisTitle3.setFont( ui->bufferPlot->axisTitle( QwtPlot::xBottom ).font() );
    ui->bufferPlot->setAxisTitle( QwtPlot::xBottom, axisTitle3 );

    QwtPlotMagnifier *mag3 = new QwtPlotMagnifier(ui->bufferPlot->canvas());
    mag3->setAxisEnabled(QwtPlot::xBottom, true);
    mag3->setAxisEnabled(QwtPlot::yLeft, false);
    mag3->setWheelFactor(5);
    QwtPlotPanner *pan3 = new QwtPlotPanner(ui->bufferPlot->canvas());
    pan3->setAxisEnabled(QwtPlot::xBottom, true);
    pan3->setAxisEnabled(QwtPlot::yLeft, false);

    ui->bufferPlot->replot();
}
