/***************************** LICENSE START ***********************************

 Copyright 2013 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
#include <QMenu>
#include <QPushButton>
#include <QSplitter>
#include <QSettings>
#include <QString>
#include <QStringList>
#include <QTabWidget>
#include <QTextEdit>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>

#include "ProcMonitor.h"

#include <iostream>

#include "MvQPixmapCache.h"
#include "MvProcNode.h"
#include "MvQProcModel.h"
#include "MvQTreeView.h"
#include "MvQMethods.h"


QSize ProcViewDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QSize size = QStyledItemDelegate::sizeHint(option, index);
    return size + QSize(0, 2);
}


ProcMonitor::ProcMonitor(svc* service, QWidget* parent) :
    MvQMainWindow(parent),
    firstUpdate_(true)
{
    service_ = service;

    setAttribute(Qt::WA_DeleteOnClose);

    setWindowTitle("Process Monitor - Metview");
    QApplication::setWindowIcon(QPixmap(":/debug/monitor.svg"));
    // Initial size
    setInitialSize(460, 400);

    QDir procFs("/proc");
    MvProcNode::initHasProcFs(procFs.exists());

    //---------------------------------------------------
    // The main layout (the upper part of mainSplitter)
    //---------------------------------------------------

    auto* mainLayout = new QVBoxLayout;
    mainLayout->setObjectName(QString::fromUtf8("vboxLayout"));
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);

    auto* w = new QWidget(this);
    w->setLayout(mainLayout);
    setCentralWidget(w);

    mainSplitter_ = new QSplitter(this);
    mainSplitter_->setOpaqueResize(false);
    mainSplitter_->setOrientation(Qt::Vertical);

    mainLayout->addWidget(mainSplitter_);

    //------------------
    // File info label
    //------------------

    // Add to layout
    // mainLayout->addWidget(examinerBase_->fileInfoLabel());

    //---------------------------
    // Process tree to the left
    //---------------------------

    procModel_ = new MvQProcModel(this);
    loadIcons(procModel_->pixmapCache());

    filterModel_ = new MvQProcFilterModel(this);
    filterModel_->setSourceModel(procModel_);
    filterModel_->setDynamicSortFilter(true);

    /*messageSortModel_= new MvQBufrDumpSortFilterModel;
        messageSortModel_->setSourceModel(messageModel_);
    messageSortModel_->setDynamicSortFilter(true);
    messageSortModel_->setFilterRole(Qt::UserRole);
    //messageSortModel_->setFilterRegExp(QRegExp("[1]"));
    messageSortModel_->setFilterFixedString("1");
    messageSortModel_->setFilterKeyColumn(0);*/

    procTree_ = new MvQTreeView(this);
    procTree_->setSortingEnabled(true);
    procTree_->sortByColumn(0, Qt::AscendingOrder);
    procTree_->setAlternatingRowColors(true);
    procTree_->setAllColumnsShowFocus(true);
    procTree_->setModel(filterModel_);
    procTree_->setActvatedByKeyNavigation(true);
    // procTree_->setUniformRowHeights(true);
    procTree_->setItemsExpandable(false);
    procTree_->setRootIsDecorated(false);
    // procTree_->setIconSize(QSize(40,40));

    auto* delegate = new ProcViewDelegate;
    procTree_->setItemDelegate(delegate);

    if (!MvProcNode::hasProcFs()) {
        procTree_->setColumnHidden(1, true);
        procTree_->setColumnHidden(2, true);
        procTree_->setColumnHidden(3, true);
    }


    mainSplitter_->addWidget(procTree_);

    // Add context menu
    procTree_->setContextMenuPolicy(Qt::CustomContextMenu);

    // Context menu
    connect(procTree_, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotContextMenu(const QPoint&)));

    connect(procTree_, SIGNAL(clicked(const QModelIndex&)),
            this, SLOT(slotProcInfo(const QModelIndex&)));

    // Init model
    procModel_->changeData(MvProcNode::root());

    //---------------------------------
    // Process info at the bottom
    //---------------------------------

    // Read css for the text formatting
    QString cssDoc;
    QFile f(":/debug/procInfo.css");
    QTextStream in(&f);
    if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        cssDoc = QString(f.readAll());
    }
    f.close();

    infoTab_ = new QTabWidget(this);
    mainSplitter_->addWidget(infoTab_);

    // Info
    procInfo_ = new QTextEdit(this);
    procInfo_->setReadOnly(true);
    procInfo_->setLineWrapMode(QTextEdit::NoWrap);
    procInfo_->document()->setDefaultStyleSheet(cssDoc);
    infoTab_->addTab(procInfo_, tr(" Information"));

    // Request
    procReq_ = new QTextEdit(this);
    procReq_->setReadOnly(true);
    procReq_->setLineWrapMode(QTextEdit::NoWrap);
    procReq_->document()->setDefaultStyleSheet(cssDoc);
    infoTab_->addTab(procReq_, tr("Request"));

    auto* tbCloseInfo = new QToolButton(this);
    tbCloseInfo->setIcon(QPixmap(":/debug/close_grey.svg"));
    tbCloseInfo->setAutoRaise(true);
    tbCloseInfo->setToolTip(tr("Hide information panel"));

    connect(tbCloseInfo, SIGNAL(clicked()),
            infoTab_, SLOT(hide()));

    infoTab_->setCornerWidget(tbCloseInfo);
    infoTab_->hide();

    // procInfo_=new QTextEdit(this);
    // procInfo_->setReadOnly(true);
    // procInfo_->document()->setDefaultStyleSheet(cssDoc);
    // procInfo_->setLineWrapMode(QTextEdit::NoWrap);

    // procInfo_->setWordWrapMode(QTextOption::WordWrap);


    // mainSplitter_->addWidget(procInfo_);

    //--------------------------------------
    // Buttons
    //--------------------------------------

    buttonBox_ = new QDialogButtonBox(this);

    buttonBox_->addButton(QDialogButtonBox::Close);

    connect(buttonBox_, SIGNAL(clicked(QAbstractButton*)),
            this, SLOT(slotButtonClicked(QAbstractButton*)));

    mainLayout->addWidget(buttonBox_);

    // Starts stats update
    if (MvProcNode::hasProcFs()) {
        startService();
    }

    // Read settings
    readSettings();
}

void ProcMonitor::closeEvent(QCloseEvent* event)
{
    writeSettings();
    close();
    event->accept();
}

void ProcMonitor::slotButtonClicked(QAbstractButton* button)
{
    if (!button)
        return;

    if (buttonBox_->standardButton(button) == QDialogButtonBox::Close) {
        writeSettings();
        close();
        // accept();
    }
}

void ProcMonitor::input(svcid* id, request* r)
{
    // ref is not always present in tQModelIndex index=indexAt(position);	he request, so we get it from the id
    long ref = atol(get_value(id->r, "REF", 0));
    QString name(get_value(id->r, "NAME", 0));
    QString mode(get_value(id->r, "MODE", 0));

    // std::cout << ref << " " << name << " " << mode << std::endl;
#if 0
    std::cout << "id->r ------------------------>" << std::endl;

    print_all_requests(id->r);

    std::cout << "r ----------------------->" << std::endl;

    print_all_requests(r);
    //std::cout << r << " " << id->r << std::endl;
#endif

    if (mode == "REPLY") {
        // long reqid = atol(get_value(r, "REQ_ID", 0));
        // qDebug() << "reply" << name << reqid;
    }
    else if (mode == "WAIT") {
        //        long reqid     = atol(get_value(r, "REQ_ID", 0));
        //        long from      = atol(get_value(r, "SOURCE_REF", 0));
        //        const char* to = get_value(r, "TARGET", 0);
        // qDebug() << "wait" << name << reqid << from << to;
    }
    else if (mode == "REGISTER") {
        // qDebug() << "register" << name << ref;
        procModel_->dataIsAboutToChange();
        MvProcNode::add(ref, r);
        procModel_->updateData();
        procTree_->expandAll();
        adjustView();
    }
    else if (mode == "BUSY") {
        // r          = r->next;
        // slong reqid = atol(get_value(r, "REQ_ID", 0));
        // qDebug() << "busy" << name << reqid;
    }
    else if (mode == "SERVICE") {
        long reqid = atol(get_value(r, "REQ_ID", 0));
        MvProcNode::addProcRequest(reqid, ref, name.toStdString(), r);
        //        long reqId = atol(get_value(r, "REQ_ID", 0));
        //        long from  = atol(get_value(r, "SOURCE_REF", 0));
        //        const char* x = get_value(r, "WAITMODE", 0);
        // int sync      = x ? atoi(x) : 0;
        // qDebug() << "service" << name << ref << reqid << from << sync;

        if (r->next)
            print_all_requests(r->next);
    }
    else if (mode == "EXIT") {
        // qDebug() << "exit" << name << ref;
        procModel_->dataIsAboutToChange();
        MvProcNode::remove(ref, name.toStdString());
        procModel_->updateData();
        procTree_->expandAll();
        adjustView();
    }
    else if (mode == "PROGRESS") {
        // long reqid = atol(get_value(r, "REQ_ID", 0));
        // qDebug() << "progress" << name << reqid;
    }
    else if (mode == "FOLLOWUP") {
        long reqid = atol(get_value(r, "REQ_ID", 0));

        MvProcNode::assignProcRequest(reqid, ref, name.toStdString());

        // qDebug() << "followup" << name << reqid;
    }
#if 0
    std::cout << "<-----------------------" << std::endl;
    std::cout << std::endl;
#endif
}

void ProcMonitor::adjustView()
{
    if (firstUpdate_) {
        MvProcNode::updateInfo();

        // Adjust treeview column size to contents
        for (int i = 0; i < procModel_->columnCount() - 1; i++)
            procTree_->resizeColumnToContents(i);

        procTree_->setCurrentIndex(filterModel_->index(0, 0));

        firstUpdate_ = false;
    }
}

void ProcMonitor::startService()
{
    auto* timer = new QTimer(this);
    timer->setInterval(3000);
    timer->start();

    connect(timer, SIGNAL(timeout()),
            this, SLOT(slotProcessService()));
}

void ProcMonitor::slotProcessService()
{
    MvProcNode::updateInfo();
}

void ProcMonitor::slotContextMenu(const QPoint& position)
{
    MvProcNode* proc = procModel_->itemForIndex(filterModel_->mapToSource(procTree_->indexAt(position)));
    if (proc) {
        QList<QAction*> lst;

        auto* infoAction = new QAction(tr("Information"), this);
        lst << infoAction;

        auto* reqAction = new QAction(tr("Request"), this);
        lst << reqAction;
        MvProcRequest* r = proc->procRequest();
        if (!r) {
            reqAction->setEnabled(false);
        }

        auto* abortAction = new QAction(tr("Abort"), this);
        abortAction->setShortcut(tr("Del"));
        abortAction->setIcon(QPixmap(":/debug/remove.svg"));

        lst << abortAction;

        QAction* ac = QMenu::exec(lst, procTree_->mapToGlobal(position), infoAction, this);
        if (ac == infoAction) {
            infoTab_->show();
            showInfo(proc);
            showRequest(proc);
            infoTab_->setCurrentIndex(0);
        }
        else if (ac == reqAction) {
            infoTab_->show();
            showInfo(proc);
            showRequest(proc);
            infoTab_->setCurrentIndex(1);
        }
        else if (ac == abortAction) {
            proc->killProc();
        }
    }
}

void ProcMonitor::slotProcInfo(const QModelIndex& index)
{
    MvProcNode* proc = procModel_->itemForIndex(filterModel_->mapToSource(index));
    showInfo(proc);
    showRequest(proc);
}

void ProcMonitor::showInfo(MvProcNode* proc)
{
    if (!proc) {
        procInfo_->clear();
        return;
    }

    QString txt;
    txt += "<table>";

    //
    addInfoRow("Name", proc->name(), txt);
    addInfoRow("Full name", proc->niceName(), txt);
    addInfoRow("Host", proc->host(), txt);
    addInfoRow("User", proc->user(), txt);
    addInfoRow("Pid", proc->pidAsString(), txt);

    txt += "</tr></table>";

    procInfo_->setHtml(txt);
}

void ProcMonitor::showRequest(MvProcNode* proc)
{
    if (!proc) {
        procReq_->clear();
        return;
    }

    QString txt;
    txt += "<table>";

    // Request
    MvProcRequest* r = proc->procRequest();
    if (r) {
        // addTitleInfoRow(tr("Request"),txt,2);

        addInfoRow("Class", r->className(), txt);
        addInfoRow("Name", r->name(), txt);
        addInfoRow("Sender", r->source(), txt);
        addInfoRow("Receiver", r->target(), txt);
        addInfoRow("Request", r->reqStr(), txt);
    }

    txt += "</tr></table>";

    procReq_->setHtml(txt);
}


void ProcMonitor::addInfoRow(QString label, const std::string& val, QString& info)
{
    info += "<tr><td class=\"first\">" + label + "</td><td>" + QString::fromStdString(val) + "</td></tr>";
}

void ProcMonitor::addTitleInfoRow(QString label, QString& info, int colSpan)
{
    info += R"(<tr><td class="title" align="center" colspan=")" + QString::number(colSpan + 1) + +"\">" + label + "</td></tr>";
}

void ProcMonitor::loadIcons(MvQPixmapCache* pm)
{
    request* r = mars.setup;

    while (r) {
        if (strcmp(r->name, "object") == 0) {
            const char* p = get_value(r, "class", 0);
            const char* q = get_value(r, "pixmap", 0);
            if (p && q) {
                pm->addMvIconPath(QString(p), QString(q));
            }
        }
        r = r->next;
    }
}


void ProcMonitor::writeSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "MV4-MvMonitor");

    // We have to clear it not to remember all the previous windows
    settings.clear();

    settings.beginGroup("main");
    settings.setValue("geometry", saveGeometry());
    settings.setValue("mainSplitter", mainSplitter_->saveState());
    settings.setValue("infoTab", infoTab_->isVisible());
    settings.endGroup();
}

void ProcMonitor::readSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "MV4-MvMonitor");

    settings.beginGroup("main");
    restoreGeometry(settings.value("geometry").toByteArray());
    mainSplitter_->restoreState(settings.value("mainSplitter").toByteArray());
    if (settings.contains("infoTab"))
        infoTab_->setVisible(settings.value("infoTab").toBool());
    else
        infoTab_->setVisible(false);

    settings.endGroup();
}
