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

 Copyright 2016 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 <QComboBox>
#include <QCursor>
#include <QDebug>
#include <QFileInfo>
#include <QLabel>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QSplitter>
#include <QStatusBar>
#include <QString>
#include <QStringList>
#include <QTabBar>
#include <QTextEdit>
#include <QToolButton>
#include <QVBoxLayout>

#include <QProgressDialog>

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QGuiApplication>
#endif

#include "BufrExaminer.h"

#ifdef ECCODES_UI
#include "CodesDirHandler.h"
#endif

#include "BufrCompDataWidget.h"
#include "BufrExpandDataModel.h"
#include "BufrFilterEngine.h"
#include "BufrMetaData.h"
#include "BufrKeyWidget.h"
#include "BufrFilterDialog.h"
#include "IconProvider.h"
#include "LocationWidget.h"
#include "LogHandler.h"
#include "MessageLabel.h"
#include "MvEccBufr.h"
#include "MvKeyProfile.h"
#include "MvQArrowSpinWidget.h"
#include "MvQFileDialog.h"
#include "MvQFileInfoLabel.h"
#include "MvQFileList.h"
#include "MvQFileListWidget.h"
#include "MvQPanel.h"
#include "MvQFileUtil.h"
#include "MvQAbout.h"
#include "MvQBufrDataModel.h"
#include "MvQMethods.h"

#include "MvException.h"
#include "MvQKeyManager.h"
#include "MvQKeyProfileModel.h"
#include "MvQKeyProfileTree.h"
#include "MvQLogPanel.h"
#include "MvQMethods.h"
#include "MvQTheme.h"
#include "MvQTreeExpandState.h"
#include "MvQTreeView.h"
#include "MvQTreeViewSearchLine.h"

#include "MvTmpFile.h"
#include "MessageControlPanel.h"
#include "PlainTextWidget.h"
#include "StatusMsgHandler.h"

#define _MV_BUFREXAMINER_DEBUG
#define _MV_BUFREXAMINER_FILTER

//===========================================================
//
// MvQBufrMainPanel
//
//===========================================================

MvQBufrMainPanel::MvQBufrMainPanel(bool /*filtered*/, QWidget* parent) :
    QWidget(parent),
    data_(nullptr),
    inProfileLoad_(false)
{
    Q_ASSERT(parent);

    messagePanel_ = new MvQMessagePanel(this);
    dumpPanel_ = new MvQBufrDumpPanel(this);
    gotoPanel_ = new MessageControlPanel(true, true, this);

    auto* dataLayout = new QVBoxLayout(this);
    dataLayout->setContentsMargins(0, 0, 0, 0);
    dataLayout->setSpacing(0);
    dataLayout->addWidget(gotoPanel_);

    dataSplitter_ = new QSplitter(this);
    dataSplitter_->setOrientation(Qt::Horizontal);
    dataSplitter_->setOpaqueResize(false);
    dataLayout->addWidget(dataSplitter_, 1);

    dataSplitter_->addWidget(messagePanel_);
    dataSplitter_->addWidget(dumpPanel_);

    connect(messagePanel_, SIGNAL(keyProfileChanged()),
            this, SIGNAL(keyProfileChanged()));

    connect(messagePanel_, SIGNAL(messageSelected(int)),
            this, SLOT(messageSelected(int)));

    connect(messagePanel_, SIGNAL(messageNumDetermined()),
            this, SIGNAL(messageNumDetermined()));

    connect(messagePanel_, SIGNAL(keyProfileReady()),
            this, SLOT(keyProfileReady()));

    connect(messagePanel_, SIGNAL(keyProfileLoadFinished()),
            this, SLOT(keyProfileLoadFinished()));

    connect(gotoPanel_, SIGNAL(messageChanged(int)),
            this, SLOT(messageChangedInGoto(int)));

    connect(gotoPanel_, SIGNAL(subsetChanged(int)),
            this, SLOT(subsetChangedInGoto(int)));

    connect(dumpPanel_, SIGNAL(messageDataInvalid(int)),
            messagePanel_, SLOT(slotMessageDataInvalid(int)));

    connect(dumpPanel_, SIGNAL(subsetSelected(int)),
            this, SLOT(subsetChangedInDump(int)));

    connect(dumpPanel_, SIGNAL(messageSelectedInMap(int)),
            this, SLOT(messageSelectedInMap(int)));

    // Extra actions for the control panel

    // Debug
    actionDebug_ = new QAction(this);
    actionDebug_->setObjectName(QString::fromUtf8("actionDebug"));
    actionDebug_->setText(tr("Enable message debug"));
    actionDebug_->setCheckable(true);
    actionDebug_->setChecked(false);
    actionDebug_->setToolTip(tr("Enable message debug"));
    // actionDebug_->setIcon(QPixmap(QString::fromUtf8(":/examiner/debug.svg")));

    connect(actionDebug_, SIGNAL(toggled(bool)),
            dumpPanel_, SLOT(setEnableDebug(bool)));

    gotoPanel_->addOptionsAction(actionDebug_);

    // Navigate uncompressed
    actionNavigateUncompressed_ = new QAction(this);
    actionNavigateUncompressed_->setText("Step through uncompressed subsets");
    actionNavigateUncompressed_->setCheckable(true);
    actionNavigateUncompressed_->setChecked(false);

    connect(actionNavigateUncompressed_, SIGNAL(toggled(bool)),
            this, SLOT(navigateUncompressedChanged(bool)));

    gotoPanel_->addOptionsAction(actionNavigateUncompressed_);
}

void MvQBufrMainPanel::init(BufrMetaData* data)
{
    Q_ASSERT(!data_);
    data_ = data;

    gotoPanel_->resetMessageNum(0, false);
    gotoPanel_->resetSubsetNum(0, false);

    messagePanel_->init(data_);
    dumpPanel_->init(data_);
}

void MvQBufrMainPanel::setFilterInfo(bool filtered, int oriNum, int num)
{
    gotoPanel_->setFilterInfo(filtered, oriNum, num);
}

void MvQBufrMainPanel::clear()
{
    dumpPanel_->clear();
    inProfileLoad_ = true;
    gotoPanel_->resetMessageNum(0, false);
    gotoPanel_->resetSubsetNum(0, false);
    messagePanel_->clearProfile();
    inProfileLoad_ = false;
}

MvKeyProfile* MvQBufrMainPanel::keyProfile() const
{
    return messagePanel_->keyProfile();
}

void MvQBufrMainPanel::loadKeyProfile(MvKeyProfile* prof)
{
    inProfileLoad_ = true;
    messagePanel_->loadKeyProfile(prof);
    inProfileLoad_ = false;
}

void MvQBufrMainPanel::reloadData(bool hasFilterResult)
{
    inProfileLoad_ = true;
    gotoPanel_->resetMessageNum(0, false);
    gotoPanel_->resetSubsetNum(0, false);
    dumpPanel_->setHasFilterResult(hasFilterResult);
    messagePanel_->adjustProfile(true, -1);
    inProfileLoad_ = false;

    if (!keyProfile() || keyProfile()->valueNum() == 0)
        dumpPanel_->loadEmpty();
}

void MvQBufrMainPanel::messageChangedInGoto(int value)
{
    QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
    messagePanel_->selectMessage(value - 1);
    QApplication::restoreOverrideCursor();
}

void MvQBufrMainPanel::subsetChangedInGoto(int value)
{
    // Override cursor
    QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
    dumpPanel_->loadDumpsForSubset(value);
    QApplication::restoreOverrideCursor();
}

// The received subset count starts at 1
void MvQBufrMainPanel::subsetChangedInDump(int currentSubset)
{
    if (currentSubset >= 1) {
        gotoPanel_->setCurrentSubset(currentSubset);
    }
}

void MvQBufrMainPanel::messageSelectedInMap(int value)
{
    QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
    messagePanel_->selectMessage(value - 1);
    QApplication::restoreOverrideCursor();
}

// msgCnt: starts at 0
void MvQBufrMainPanel::messageSelected(int msgCnt)
{
    // Notify the log panel about the selection.
    if (inProfileLoad_ == false)
        emit newMessageSelected(msgCnt);

    // The message counter might not have been initialised. But at this point
    // we must know the correct message num so we can set the counter.
    if (gotoPanel_->messageValue() == 0) {
        gotoPanel_->resetMessageNum(data_->messageNum(), false);
    }

    // Set the message counter
    gotoPanel_->setMessageValue(msgCnt + 1, false);

    MvEccBufrMessage* msg = data_->message(msgCnt);
    if (!msg)
        return;

    Q_ASSERT(msg);

    gotoPanel_->resetSubsetNum(msg->subsetNum(), false);

    QString subsetText;
    if (msg->isCompressed() == false && doNavigateUncompressed() == false) {
        subsetText = tr("subsets: <b>") + QString::number(msg->subsetNum()) + "</b>";
        if (msg->subsetNum() > 1) {
            subsetText += " (uncompressed)";
        }
    }
    else {
        subsetText = tr("subsets: <b>") + QString::number(msg->subsetNum()) + "</b>";
        if (msg->subsetNum() > 1) {
            subsetText += " (";
            subsetText += (msg->isCompressed() ? "compressed" : "uncompressed");
            subsetText += ")";
        }
    }

    gotoPanel_->setSubsetLabel(subsetText);
    if (msg->isCompressed() == false)
        gotoPanel_->showSubsetControl(doNavigateUncompressed());
    else
        gotoPanel_->showSubsetControl(true);

    QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
    dumpPanel_->loadDumps(msgCnt, gotoPanel_->subsetValue());
    QApplication::restoreOverrideCursor();
}

void MvQBufrMainPanel::keyProfileReady()
{
    Q_ASSERT(data_);
    gotoPanel_->setSubsetTotalLabel(data_->totalMessageNum(),
                                    data_->totalSubsetNum(false));
}

void MvQBufrMainPanel::keyProfileLoadFinished()
{
    Q_ASSERT(data_);
    gotoPanel_->setSubsetTotalLabel(data_->totalMessageNum(),
                                    data_->totalSubsetNum(true));
}

bool MvQBufrMainPanel::doNavigateUncompressed() const
{
    return actionNavigateUncompressed_->isChecked();
}

void MvQBufrMainPanel::navigateUncompressedChanged(bool b)
{
    dumpPanel_->setNavigateUncompressed(b);
    int msgCnt = gotoPanel_->messageValue() - 1;
    if (msgCnt >= 0) {
        MvEccBufrMessage* msg = data_->message(msgCnt);
        Q_ASSERT(msg);
        if (msg->isCompressed() == false) {
            messageSelected(msgCnt);
        }
    }
}

void MvQBufrMainPanel::loadFilterResult(MvKeyProfile* prof)
{
    dumpPanel_->loadFilterResult(prof);
}

void MvQBufrMainPanel::selectFilterResultTab()
{
    dumpPanel_->selectFilterResultTab();
}

QString MvQBufrMainPanel::fileName() const
{
    return (data_) ? QString::fromStdString(data_->fileName()) : QString();
}

void MvQBufrMainPanel::messageFilterChanged(const BufrFilterDef& /*def*/)
{
    // gotoPanel_->setFilterText(QString::fromStdString(def.messageFilterLabel()));
}

void MvQBufrMainPanel::writeSettings(QSettings& settings)
{
    settings.beginGroup("mainPanel");
    settings.setValue("dataSplitter", dataSplitter_->saveState());

    settings.setValue("navigateUncompressed", actionNavigateUncompressed_->isChecked());
    settings.setValue("debug", actionDebug_->isChecked());

    dumpPanel_->writeSettings(settings);
    settings.endGroup();
}

void MvQBufrMainPanel::readSettings(QSettings& settings)
{
    settings.beginGroup("mainPanel");
    if (settings.contains("dataSplitter")) {
        dataSplitter_->restoreState(settings.value("dataSplitter").toByteArray());
    }

    if (settings.contains("navigateUncompressed")) {
        bool b = settings.value("navigateUncompressed").toBool();
        if (b != actionNavigateUncompressed_->isChecked()) {
            actionNavigateUncompressed_->setChecked(b);
        }
    }

    if (settings.contains("debug")) {
        bool b = settings.value("debug").toBool();
        if (b != actionDebug_->isChecked()) {
            actionDebug_->setChecked(b);
        }
    }

    dumpPanel_->readSettings(settings);
    settings.endGroup();
}

//===============================================================
//
// BufrJsonDumpWidget
//
//===============================================================

BufrJsonDumpWidget::BufrJsonDumpWidget(MvQBufrDumpPanel* context, QWidget* parent) :
    QWidget(parent),
    context_(context),
    expand_(nullptr)
{
    Q_ASSERT(context_);

    auto* vb = new QVBoxLayout(this);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->setSpacing(0);

    messageLabel_ = new MessageLabel(this);
    messageLabel_->setShowTypeTitle(false);
    vb->addWidget(messageLabel_);
    messageLabel_->hide();

    // temporary code to test the MessageLabel
#if 0
    auto m1 = new MessageLabel(this);
    vb->addWidget(m1);
    m1->showInfo("THIS IS SOME TEXT!!!!");

    m1 = new MessageLabel(this);
    vb->addWidget(m1);
    m1->showWarning("THIS IS SOME TEXT!!!!");

    m1 = new MessageLabel(this);
    vb->addWidget(m1);
    m1->showError("THIS IS SOME TEXT!!!!");

    m1 = new MessageLabel(this);
    vb->addWidget(m1);
    m1->showTip("THIS IS SOME TEXT!!!!");
#endif

    auto* w1 = new QWidget(this);
    //    auto* w1 = new MvQPanel(this);
    auto* dataControlLayout = new QHBoxLayout(w1);
    dataControlLayout->setContentsMargins(0, 0, 0, 0);
    vb->addWidget(w1);

    dataControlLayout->addStretch(1);

    searchTb_ = new QToolButton(this);
    searchTb_->setAutoRaise(true);
    searchTb_->setIcon(QPixmap(":/examiner/panel_search.svg"));
    searchTb_->setToolTip(tr("Search in dump tree"));
    // searchTb_->setProperty("panelStyle", "2");
    searchTb_->setShortcut(QKeySequence(tr("Ctrl+F")));
    dataControlLayout->addWidget(searchTb_);

    keyInfoTb_ = new QToolButton(this);
    keyInfoTb_->setAutoRaise(true);
    keyInfoTb_->setIcon(QPixmap(":/examiner/panel_info.svg"));
    keyInfoTb_->setCheckable(true);
    keyInfoTb_->setToolTip(tr("Show key information sidebar"));
    // keyInfoTb_->setProperty("panelStyle", "2");
    keyInfoTb_->setChecked(true);

    dataControlLayout->addWidget(keyInfoTb_);
    dataControlLayout->addSpacing(4);

    splitter_ = new QSplitter(this);
    vb->addWidget(splitter_, 1);

    tree_ = new MvQTreeView(this, true);
    model_ = new MvQBufrDataModel(this);
    tree_->setModel(model_);
    tree_->setObjectName("dataDumpTree");
    tree_->setAlternatingRowColors(true);
    tree_->setAllColumnsShowFocus(true);
    tree_->setRootIsDecorated(true);
    splitter_->addWidget(tree_);

    tree_->setContextMenuPolicy(Qt::ActionsContextMenu);

    // Add actions to dump tree
    auto* acCopyDumpKey = new QAction(this);
    acCopyDumpKey->setText(tr("&Copy item"));
    acCopyDumpKey->setShortcut(QKeySequence(tr("Ctrl+C")));
    tree_->addAction(acCopyDumpKey);

    connect(acCopyDumpKey, SIGNAL(triggered()),
            this, SLOT(slotCopyDumpKey()));

    keyWidget_ = new BufrKeyWidget(this);
    splitter_->addWidget(keyWidget_);
    splitter_->setCollapsible(1, false);

    // Initial ratio of widths in the splitter
    splitter_->setStretchFactor(0, 6);
    splitter_->setStretchFactor(1, 1);

    // Search widget
    searchLine_ = new TreeViewSearchLine(this);
    searchLine_->setView(tree_);
    searchLine_->setColumns({0});
    vb->addWidget(searchLine_);
    searchLine_->hide();

    connect(tree_, SIGNAL(selectionChanged(const QModelIndex&)),
            this, SLOT(dataDumpSelected(const QModelIndex&)));

    connect(tree_, SIGNAL(doubleClicked(const QModelIndex&)),
            this, SLOT(dataDumpDoubleClicked(const QModelIndex&)));

    connect(searchTb_, SIGNAL(clicked()),
            this, SLOT(slotDataDumpSearch()));

    connect(keyInfoTb_, SIGNAL(toggled(bool)),
            keyWidget_, SLOT(setVisible(bool)));

    connect(keyWidget_, SIGNAL(subsetSelected(int)),
            this, SIGNAL(subsetSelectedInKeyWidget(int)));

    // Adjust ns tree columns
    QFont f;
    QFontMetrics fm(f);
    tree_->setColumnWidth(0, MvQ::textWidth(fm, "Dat  pressureReducedToMeanSealevel "));
    tree_->setColumnWidth(1, MvQ::textWidth(fm, "281.999999999"));
    tree_->setColumnWidth(2, MvQ::textWidth(fm, "CODE TABLE  "));
}

BufrJsonDumpWidget::~BufrJsonDumpWidget()
{
    if (expand_)
        delete expand_;
}

void BufrJsonDumpWidget::slotDataDumpSearch()
{
    searchLine_->setVisible(true);
    searchLine_->setFocus();
    // dataDumpSearch_->selectAll();
}

void BufrJsonDumpWidget::saveExpandState()
{
    if (expand_)
        delete expand_;

    // Save expand state in the tree
    expand_ = new MvQTreeExpandState(tree_);

    // If the dumpData has already been loaded
    if (model_->hasData()) {
        expand_->save();
    }
    else {
        // By default we expand the "Data" toplevel node
        QStringList topLev;
        topLev << "Data";
        expand_->init(topLev);
    }
}

void BufrJsonDumpWidget::restoreExpandState()
{
    if (expand_) {
        expand_->restore();
        delete expand_;
        expand_ = nullptr;
    }
}

void BufrJsonDumpWidget::setSubsetNumber(int n)
{
    model_->setSubsetNumber(n);
}

void BufrJsonDumpWidget::showError(QString errMsg)
{
    messageLabel_->showError(errMsg);
}

void BufrJsonDumpWidget::updateKeyInfo()
{
    QModelIndex idx = tree_->currentIndex();
    updateKeyInfo(idx);
}

void BufrJsonDumpWidget::updateKeyInfo(const QModelIndex& idx)
{
    Q_ASSERT(context_->currentMessage() >= 0);
    Q_ASSERT(context_->currentSubset() >= 1);
    MvEccBufrMessage* msg = context_->data()->message(context_->currentMessage());
    Q_ASSERT(msg);

    keyWidget_->setData(model_->indexToItem(idx), context_->currentSubset() - 1, msg);
}

void BufrJsonDumpWidget::dataDumpSelected(const QModelIndex& idx)
{
    updateKeyInfo(idx);
}

void BufrJsonDumpWidget::dataDumpDoubleClicked(const QModelIndex&)
{
}

void BufrJsonDumpWidget::slotCopyDumpKey()
{
    QModelIndex index = tree_->currentIndex();
    if (!index.isValid())
        return;

    QString name = index.data().toString();
    if (!name.isEmpty()) {
        MvQ::toClipboard(name);
    }
}

void BufrJsonDumpWidget::clearData()
{
    model_->clear();
    messageLabel_->clear();
    messageLabel_->hide();
    keyWidget_->clear();
}

bool BufrJsonDumpWidget::loadData(const QByteArray& json, bool compressed, QString& err)
{
    return model_->loadJson(json, compressed, err);
}

void BufrJsonDumpWidget::writeSettings(QSettings& settings)
{
    settings.beginGroup("json_dump");

    MvQ::saveTreeColumnWidth(settings, "treeColumnWidth", tree_);
    settings.setValue("splitter", splitter_->saveState());
    searchLine_->writeSettings(settings);

    settings.endGroup();
}

void BufrJsonDumpWidget::readSettings(QSettings& settings)
{
    if (settings.childGroups().contains("json_dump")) {
        settings.beginGroup("json_dump");

        MvQ::initTreeColumnWidth(settings, "treeColumnWidth", tree_);
        if (settings.contains("splitter")) {
            splitter_->restoreState(settings.value("splitter").toByteArray());
        }
        searchLine_->readSettings(settings);

        settings.endGroup();
    }
    else {
        // TODO: remove this else branch in versions > 5.7.0
        MvQ::initTreeColumnWidth(settings, "dataTreeColumnWidth", tree_);
        if (settings.contains("dataDumpSplitter")) {
            splitter_->restoreState(settings.value("dataDumpSplitter").toByteArray());
        }
        searchLine_->readSettings(settings);
    }
}

//===============================================================
//
// BufrExpandDumpWidget
//
//===============================================================

BufrExpandDumpWidget::BufrExpandDumpWidget(QWidget* parent) :
    QWidget(parent)
{
    auto* vb = new QVBoxLayout(this);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->setSpacing(0);

    messageLabel_ = new MessageLabel(this);
    messageLabel_->setShowTypeTitle(false);
    vb->addWidget(messageLabel_);
    messageLabel_->hide();

    // auto* w1 = new MvQPanel(this);
    auto* w1 = new QWidget(this);
    auto* dataControlLayout = new QHBoxLayout(w1);
    dataControlLayout->setContentsMargins(0, 0, 0, 0);
    vb->addWidget(w1);

    dataControlLayout->addStretch(1);

    searchTb_ = new QToolButton(this);
    searchTb_->setAutoRaise(true);
    searchTb_->setIcon(QPixmap(":/examiner/panel_search.svg"));
    searchTb_->setToolTip(tr("Search in dump tree"));
    // searchTb_->setProperty("panelStyle", "2");
    searchTb_->setShortcut(QKeySequence(tr("Ctrl+F")));
    dataControlLayout->addWidget(searchTb_);

    dataControlLayout->addSpacing(4);

    tree_ = new MvQTreeView(this);
    model_ = new BufrExpandDataModel(this);
    tree_->setModel(model_);

    tree_->setAllColumnsShowFocus(true);
    tree_->setRootIsDecorated(false);
    tree_->setAlternatingRowColors(true);
    tree_->setUniformRowHeights(true);
    vb->addWidget(tree_);

    tree_->setContextMenuPolicy(Qt::ActionsContextMenu);

    // Add actions to dump tree
    auto* acCopyDumpKey = new QAction(this);
    acCopyDumpKey->setText(tr("&Copy item"));
    acCopyDumpKey->setShortcut(QKeySequence(tr("Ctrl+C")));
    tree_->addAction(acCopyDumpKey);

    connect(acCopyDumpKey, SIGNAL(triggered()),
            this, SLOT(slotCopyKey()));

    // Search widget
    searchLine_ = new TreeViewSearchLine(this);
    searchLine_->setView(tree_);
    searchLine_->setColumns({0, 1});
    vb->addWidget(searchLine_);
    searchLine_->hide();

    connect(searchTb_, SIGNAL(clicked()),
            this, SLOT(slotSearch()));

    // Adjust ns tree columns
    QFont f;
    QFontMetrics fm(f);
    tree_->setColumnWidth(0, MvQ::textWidth(fm, "0000009 "));
    tree_->setColumnWidth(1, MvQ::textWidth(fm, "nonCoordinateGeopotentialHeight  "));
    tree_->setColumnWidth(2, MvQ::textWidth(fm, "TEMPERATURE/DRY-BULB TEMPERATURE  "));
}

BufrExpandDumpWidget::~BufrExpandDumpWidget() = default;

void BufrExpandDumpWidget::slotSearch()
{
    searchLine_->setVisible(true);
    searchLine_->setFocus();
}

void BufrExpandDumpWidget::showError(QString errMsg)
{
    messageLabel_->showError(errMsg);
}

void BufrExpandDumpWidget::slotCopyKey()
{
    QModelIndex index = tree_->currentIndex();
    if (!index.isValid())
        return;

    QString name = index.data().toString();
    if (!name.isEmpty()) {
        MvQ::toClipboard(name);
    }
}

void BufrExpandDumpWidget::clearData()
{
    model_->clear();
    messageLabel_->clear();
    messageLabel_->hide();
}

bool BufrExpandDumpWidget::loadData(const std::string& dump, QString& err)
{
    return model_->loadDumpData(dump, err);
}

void BufrExpandDumpWidget::writeSettings(QSettings& settings)
{
    settings.beginGroup("expanded_dump");

    MvQ::saveTreeColumnWidth(settings, "treeColumnWidth", tree_);
    searchLine_->writeSettings(settings);

    settings.endGroup();
}

void BufrExpandDumpWidget::readSettings(QSettings& settings)
{
    settings.beginGroup("expanded_dump");

    MvQ::initTreeColumnWidth(settings, "treeColumnWidth", tree_);
    searchLine_->readSettings(settings);

    settings.endGroup();
}

//===========================================================
//
// MvQBufrDumpPanel
//
//===========================================================

MvQBufrDumpPanel::MvQBufrDumpPanel(QWidget* parent) :
    QWidget(parent)
{
    QWidget* w = nullptr;
    QVBoxLayout* vb = nullptr;

    auto* dumpLayout = new QVBoxLayout(this);
    dumpLayout->setContentsMargins(0, 0, 0, 0);

    // Tab
    dumpTab_ = new QTabWidget(this);
    dumpLayout->addWidget(dumpTab_);

    //--------------------------------
    // Json dump
    //--------------------------------

    jsonDumpWidget_ = new BufrJsonDumpWidget(this);
    dumpTab_->addTab(jsonDumpWidget_, tr("Data tree"));
    Q_ASSERT(static_cast<int>(JsonTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "data");
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows hierarchical structure of the current message");

    connect(jsonDumpWidget_, SIGNAL(subsetSelectedInKeyWidget(int)),
            this, SIGNAL(subsetSelected(int)));

    //--------------------------------
    // Expanded dump
    //--------------------------------

    expandDumpWidget_ = new BufrExpandDumpWidget(this);
    dumpTab_->addTab(expandDumpWidget_, tr("Descriptors"));
    Q_ASSERT(static_cast<int>(ExpandTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "expanded");
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows expanded descriptors of the current message");

    //--------------------------------
    // Compressed value dump
    //--------------------------------

#if 0
    w=new QWidget;
    vb=new QVBoxLayout;
    vb->setContentsMargins(0,0,0,0);
    vb->setSpacing(0);
    w->setLayout(vb);
#endif

#if 0
    dataDumpMessageLabel_=new MessageLabel(this);
    dataDumpMessageLabel_->setShowTypeTitle(false);
    vb->addWidget(dataDumpMessageLabel_);
    dataDumpMessageLabel_->hide();
#endif


#if 0
    compDumpModel_= new MvQKeyProfileModel(this);
    compDumpSortModel_= new MvQKeyProfileSortFilterModel;
    compDumpSortModel_->setSourceModel(compDumpModel_);
    compDumpSortModel_->setDynamicSortFilter(true);
    compDumpSortModel_->setFilterRole(Qt::UserRole);
    compDumpSortModel_->setFilterFixedString("1");
    compDumpSortModel_->setFilterKeyColumn(0);

    compDumpTree_=new MvQKeyProfileTree(compDumpModel_,compDumpSortModel_);
    compDumpTree_->setUniformRowHeights(true);
#endif

#if 0
    valueDumpTree_->setModel(valueDumpModel_);
    valueDumpTree_->setObjectName("dataDumpTree");
    valueDumpTree_->setProperty("mvStyle",1);
    valueDumpTree_->setAllColumnsShowFocus(true);
    valueDumpTree_->setRootIsDecorated(true);
#endif

#if 0
    vb->addWidget(compDumpTree_,1);
#endif

    compDumpWidget_ = new BufrCompDataWidget(this);
    dumpTab_->addTab(compDumpWidget_, tr("Compressed"));
    Q_ASSERT(static_cast<int>(CompTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "compressed");
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows data of all the compressed subsets in the current message");

#if 0
    connect(compDumpTree_,SIGNAL(doubleClicked(const QModelIndex&)),
            this,SLOT(valueDumpDoubleClicked(const QModelIndex&)));
#endif
    //--------------------------------
    // Table dump
    //--------------------------------

    w = new QWidget;
    vb = new QVBoxLayout(w);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->setSpacing(0);

    tableTe_ = new QTextEdit(this);
    tableTe_->setReadOnly(true);

    vb->addWidget(tableTe_);

    // dataDumpTree_->setDragDropMode(QAbstractItemView::DragOnly);

    dumpTab_->addTab(w, tr("Tables"));
    Q_ASSERT(static_cast<int>(TableTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "table");
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows details of BUFR tables used to decode the current message");

    //--------------------------------
    // Debug dump
    //--------------------------------

    w = new QWidget;
    vb = new QVBoxLayout(w);

    // debugTe_=new QPlainTextEdit(this);
    debugTe_ = new PlainTextWidget(this);
    debugTe_->editor()->setShowLineNumbers(false);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->addWidget(debugTe_);

    dumpTab_->addTab(w, tr("Debug"));
    Q_ASSERT(static_cast<int>(DebugTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "debug");
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows output when data tree dump is generated in debug mode");

    //--------------------------------
    // Locations
    //--------------------------------

    w = new QWidget;
    vb = new QVBoxLayout(w);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->setSpacing(0);

    locationMessageLabel_ = new MessageLabel(this);
    locationMessageLabel_->setShowTypeTitle(false);
    vb->addWidget(locationMessageLabel_);
    locationMessageLabel_->hide();

    locationW_ = new LocationWidget(this);
    vb->addWidget(locationW_);

    // dataDumpTree_->setDragDropMode(QAbstractItemView::DragOnly);

    dumpTab_->addTab(w, tr("Locations"));
    Q_ASSERT(static_cast<int>(LocationTabIndex) == dumpTab_->count() - 1);
    dumpTab_->tabBar()->setTabData(dumpTab_->count() - 1, "location");
    dumpTab_->setTabIcon(LocationTabIndex, QPixmap(":/examiner/globe.svg"));
    dumpTab_->tabBar()->setTabToolTip(dumpTab_->count() - 1, "Shows all the locations in the whole BUFR file");

    connect(locationW_, SIGNAL(messageSelected(int)),
            this, SIGNAL(messageSelectedInMap(int)));

    //----------------------------------------
    // Init
    //----------------------------------------

    connect(dumpTab_, SIGNAL(currentChanged(int)),
            this, SLOT(currentDumpChanged(int)));

    dumpTab_->setCurrentIndex(0);  // Default is data dump

    // The debug tab is disabled by default
    setEnableDebug(false);
}

void MvQBufrDumpPanel::init(BufrMetaData* data)
{
    data_ = data;
    data_->registerObserver(this);
    compDumpWidget_->init(data_);
}

void MvQBufrDumpPanel::loadEmpty()
{
    Q_ASSERT(currentMsg_ == -1);
    Q_ASSERT(currentSubset_ == -1);

    currentMsg_ = -1;
    currentSubset_ = -1;
    messageCanBeDecoded_ = false;
    expandDumpLoaded_ = false;
    debugDumpLoaded_ = false;
    compDumpLoaded_ = false;

    loadJsonDump();
    loadExpandDump();
    loadTableDump();
    loadDebugDump();
    loadCompDump();
    loadLocations();
}

#if 0
void MvQBufrDumpPanel::slotDataDumpSearch()
{
    dataDumpSearch_->setVisible(true);
    dataDumpSearch_->setFocus();
    //dataDumpSearch_->selectAll();
}
#endif

void MvQBufrDumpPanel::dataFileChanged()
{
    // The locations are connected to the whole BUFR file.
    // We only need to clear them if the BUFR file changes.
    clearLocations();
}

void MvQBufrDumpPanel::setEnableDebug(bool b)
{
    if (b != dumpTab_->isTabEnabled(DebugTabIndex)) {
        if (!b) {
            clearDebugDump();
            if (dumpTab_->currentIndex() == DebugTabIndex) {
                dumpTab_->setCurrentIndex(JsonTabIndex);
            }
        }
        dumpTab_->setTabEnabled(DebugTabIndex, b);

        if (b && dumpTab_->currentIndex() == DebugTabIndex) {
            loadDebugDump();
        }
    }
}

void MvQBufrDumpPanel::setNavigateUncompressed(bool b)
{
    navigateUncompressed_ = b;
}

// msgCnt: starts at 0, subsetCnt: starts at 1
void MvQBufrDumpPanel::loadDumps(int msgCnt, int subsetCnt)
{
    Q_ASSERT(data_);

    StatusMsgHandler::instance()->show("Message: " + QString::number(msgCnt + 1), true);

    currentMsg_ = msgCnt;
    currentSubset_ = subsetCnt;

    // Save expand state in the tree
    jsonDumpWidget_->saveExpandState();

    // Clear all the dumps except the locations. Locations are
    // attached to the whole file not to a message.
    clearDumps();

    // Check if we could completely decode the header!!
    if (currentMsg_ >= 0 && data_->totalMessageNum() > 0) {
        MvEccBufrMessage* msg = data_->message(currentMsg_);
        Q_ASSERT(msg);
        // Header decoding is not complete. We display an error message in the Data tab.
        if (!msg->isHeaderValid()) {
            MvQ::showTabLabel(dumpTab_, JsonTabIndex, IconProvider::failedPixmap());
            QString errMsg("Failed to properly decode header! ");
            for (const auto& it : msg->headerErrors()) {
                errMsg += "<br>ERROR - " + QString::fromStdString(it);
            }
            jsonDumpWidget_->showError(errMsg);
            messageCanBeDecoded_ = false;
            StatusMsgHandler::instance()->task("Generating json dump");
            StatusMsgHandler::instance()->failed();

            // We do not continue with all of the dumps!!!
            loadTableDump();
            return;
        }
    }

    // Check for other serious problems
    if (currentMsg_ < 0 || currentSubset_ < 1 || data_->totalMessageNum() == 0) {
        MvQ::showTabLabel(dumpTab_, JsonTabIndex, IconProvider::failedPixmap());
        QString errMsg;
        if (currentSubset_ < 0) {
            errMsg = "Failed to determine number of subsets!";
            jsonDumpWidget_->showError(errMsg);
            StatusMsgHandler::instance()->task("Generating json dump");
            StatusMsgHandler::instance()->failed();
        }
        messageCanBeDecoded_ = false;
        return;
    }

    Q_ASSERT(currentMsg_ >= 0);

    messageCanBeDecoded_ = true;
    expandDumpLoaded_ = false;
    debugDumpLoaded_ = false;
    compDumpLoaded_ = false;

    loadJsonDump();
    loadExpandDump();
    loadTableDump();
    loadDebugDump();
    loadCompDump();
    loadLocations();

    // Restore data tree expand state
    jsonDumpWidget_->restoreExpandState();
}

void MvQBufrDumpPanel::loadDumpsForSubset(int subsetCnt)
{
    // Override cursor
    // QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));

    int msgCnt = currentMsg_;
    Q_ASSERT(currentMsg_ >= 0);
    currentSubset_ = subsetCnt;

    Q_ASSERT(data_);
    MvEccBufrMessage* msg = data_->message(msgCnt);

    if (msg && !msg->isCompressed()) {
        Q_ASSERT(navigateUncompressed_);

        StatusMsgHandler::instance()->show("Message: " + QString::number(msgCnt + 1) +
                                               " subset: " + QString::number(subsetCnt),
                                           true);

        // Save expand state in th tree
        jsonDumpWidget_->saveExpandState();

        loadJsonDump();

        debugDumpLoaded_ = false;
        loadDebugDump();

        // Restore data tree expand state
        jsonDumpWidget_->restoreExpandState();
    }
    else {
        jsonDumpWidget_->setSubsetNumber(subsetCnt - 1);
    }

    // Update the key info widget
    jsonDumpWidget_->updateKeyInfo();

    // QApplication::restoreOverrideCursor();
}

void MvQBufrDumpPanel::clear()
{
    clearDumps();
    clearLocations();
    currentMsg_ = -1,
    currentSubset_ = -1,
    messageCanBeDecoded_ = true;
}

void MvQBufrDumpPanel::clearDumps()
{
    MvQ::hideTabLabel(dumpTab_, JsonTabIndex);
    MvQ::hideTabLabel(dumpTab_, DebugTabIndex);

    jsonDumpWidget_->clearData();
    expandDumpWidget_->clearData();
    tableTe_->clear();
    debugTe_->clear();
    debugDumpLoaded_ = false;
    compDumpWidget_->clearData();
    compDumpLoaded_ = false;
}

void MvQBufrDumpPanel::clearDebugDump()
{
    MvQ::hideTabLabel(dumpTab_, DebugTabIndex);
    debugTe_->clear();
    debugDumpLoaded_ = false;
}

// The locations are connected to the whole BUFR file.
// We only need to clear them if the BUFR file changes.
void MvQBufrDumpPanel::clearLocations()
{
    if (locationLoaded_) {
        locationMessageLabel_->clear();
        locationW_->clear();
        locationLoaded_ = false;
    }
}

void MvQBufrDumpPanel::loadJsonDump()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    Q_ASSERT(currentMsg_ >= 0);
    Q_ASSERT(currentSubset_ >= 1);
    MvEccBufrMessage* msg = data_->message(currentMsg_);
    Q_ASSERT(msg);

    jsonDumpWidget_->clearData();
    if (messageCanBeDecoded_) {
        StatusMsgHandler::instance()->task("Generating json dump");

        auto* dump = new BufrDataDump;

        int subsetNum = msg->subsetNum();
        bool compressed = msg->isCompressed();
        std::string strErr;

        bool readAsCompressed = compressed;
        if (compressed == false && navigateUncompressed_ == false)
            readAsCompressed = true;

        bool ret = dump->read(data_->fileName(), currentMsg_ + 1, currentSubset_, subsetNum, readAsCompressed,
                              msg->offset(), strErr);
        if (ret) {
            QString s = QString::fromStdString(dump->text());
            // qDebug() << s;

            if (s.startsWith(","))
                s.remove(0, 1);

            GuiLog().task() << "Parsing JSON dump for message: " << currentMsg_ + 1;

            QString err;
            if ((ret = jsonDumpWidget_->loadData(s.toUtf8(), compressed, err)) == false) {
                GuiLog().error() << err.toStdString();
            }
        }

        if (!ret) {
            MvQ::showTabLabel(dumpTab_, JsonTabIndex, IconProvider::failedPixmap());
            QString s = QString::fromStdString(strErr).simplified();
            s.replace("ECCODES ERROR", "<br>ECCODES ERROR");
            jsonDumpWidget_->showError(s);
            StatusMsgHandler::instance()->failed();

            msg->setDataValid(false);
            emit messageDataInvalid(currentMsg_);
        }
        else {
            StatusMsgHandler::instance()->done();
        }

        delete dump;
    }

    else {
    }

    // Just for development
    // data_->readMessageData(currentMsg_,currentSubset_);
}

void MvQBufrDumpPanel::loadExpandDump()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    Q_ASSERT(currentMsg_ >= 0);
    MvEccBufrMessage* msg = data_->message(currentMsg_);
    Q_ASSERT(msg);

    // It can be expensve to run so we check if it is really needed
    if (dumpTab_->currentIndex() != ExpandTabIndex || expandDumpLoaded_)
        return;

    expandDumpWidget_->clearData();
    if (messageCanBeDecoded_) {
        StatusMsgHandler::instance()->task("Generating expanded descriptor dump");

        auto* dump = new BufrExpandDataDump;
        std::string strErr;
        bool ret = dump->read(data_->fileName(), currentMsg_ + 1, msg->offset(), strErr);
        if (ret) {
            QString s = QString::fromStdString(dump->text());
            if (s.startsWith(","))
                s.remove(0, 1);

            GuiLog().task() << "Parsing expanded descriptor dump for message: " << currentMsg_ + 1;

            QString err;
            if ((ret = expandDumpWidget_->loadData(dump->text(), err)) == false) {
                GuiLog().error() << err.toStdString();
            }
        }

        if (!ret) {
            MvQ::showTabLabel(dumpTab_, ExpandTabIndex, IconProvider::failedPixmap());
            QString s = QString::fromStdString(strErr).simplified();
            s.replace("ECCODES ERROR", "<br>ECCODES ERROR");
            expandDumpWidget_->showError(s);
            StatusMsgHandler::instance()->failed();
        }
        else {
            StatusMsgHandler::instance()->done();
        }

        delete dump;
    }

    expandDumpLoaded_ = true;

    // Just for development
    // data_->readMessageData(currentMsg_,currentSubset_);
}

// Load data for compressed messages
void MvQBufrDumpPanel::loadCompDump()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    Q_ASSERT(currentMsg_ >= 0);
    Q_ASSERT(currentSubset_ >= 1);
    MvEccBufrMessage* msg = data_->message(currentMsg_);
    Q_ASSERT(msg);

    if (msg->isCompressed() == false) {
        dumpTab_->setTabEnabled(CompTabIndex, false);
        return;
    }
    else {
        dumpTab_->setTabEnabled(CompTabIndex, true);
    }


    // It can be expensve to run so we check if it is really needed
    if (dumpTab_->currentIndex() != CompTabIndex || compDumpLoaded_)
        return;

    compDumpWidget_->clearData();

    if (messageCanBeDecoded_) {
        StatusMsgHandler::instance()->task("Generating compressed value dump");

        // BufrDataDump* dump = new BufrDataDump;

        int subsetNum = msg->subsetNum();
        // bool compressed=msg->isCompressed();
        std::string err;

        // bool readAsCompressed=compressed;
        // if(compressed == false && navigateUncompressed_ == false)
        //     readAsCompressed=true;

        // data_->messageData(msg->prof(),currentMsg_);

        compDumpWidget_->loadData(currentMsg_, subsetNum);

        StatusMsgHandler::instance()->done();
    }

    compDumpLoaded_ = true;
}

void MvQBufrDumpPanel::loadTableDump()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    Q_ASSERT(currentMsg_ >= 0);
    MvEccBufrMessage* msg = data_->message(currentMsg_);
    Q_ASSERT(msg);

    tableTe_->clear();

    auto hCol = MvQTheme::accentText();

    std::vector<std::string> masterDir, localDir;
    msg->tablesDirs(masterDir, localDir);

    QString s;
    s += "ecCodes used the following tables to decode the current message: <br>";
    s += "<h2><font color=\'" + hCol.name() + "\'>WMO tables</font></h2>";
    s += "<br>&nbsp;&nbsp;<b>Paths</b>: <ul>";
    for (const auto& p : masterDir) {
        s += "<li>" + QString::fromStdString(p) + "</li>";
    }
    s += "</ul>";
    s += "<br>&nbsp;&nbsp;<b>Element table</b>: element.table";
    s += "<br>&nbsp;&nbsp;<b>Sequence table</b>: sequence.def";

    if (msg->localTablesVersionNumber() != 0) {
        s += "<br><h2><font color=\'" + hCol.name() + "\'>Local tables</font></h2>";
        s += "<br>&nbsp;&nbsp;<b>Paths:</b> <ul>";
        for (const auto& p : localDir) {
            s += "<li>" + QString::fromStdString(p) + "</li>";
        }
        s += "</ul>";
        s += "<br>&nbsp;&nbsp;<b>Element table</b>: element.table";
        s += "<br>&nbsp;&nbsp;<b>Sequence table</b>: sequence.def";
    }
    tableTe_->setHtml(s);
}

// It can be costly. We delay loading it until it becomes first visible with
// the new contents
void MvQBufrDumpPanel::loadDebugDump()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    if (!dumpTab_->isTabEnabled(DebugTabIndex))
        return;

    Q_ASSERT(currentMsg_ >= 0);
    Q_ASSERT(currentSubset_ >= 1);
    MvEccBufrMessage* msg = data_->message(currentMsg_);
    Q_ASSERT(msg);

    // It can be expensve to run so we check if it is really needed
    if (dumpTab_->currentIndex() != DebugTabIndex || debugDumpLoaded_)
        return;

    debugTe_->editor()->clear();

    auto* dump = new BufrDataDump;
    if (messageCanBeDecoded_) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
        QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif
        StatusMsgHandler::instance()->task("Generating debug dump");

        int subsetNum = msg->subsetNum();
        bool compressed = msg->isCompressed();
        bool readAsCompressed = compressed;
        if (compressed == false && navigateUncompressed_ == false)
            readAsCompressed = true;

        std::string err;
        bool ret = dump->debug(data_->fileName(), currentMsg_ + 1, currentSubset_, subsetNum,
                               readAsCompressed, msg->offset(), err);
        QString s = QString::fromStdString(dump->debug());
        debugTe_->editor()->setPlainText(s);

        if (ret) {
            QString infoTxt =
                "This is the <b>debug</b> information generated while running \
                            <b>bufr_dump</b> (see the log for details)";
            debugTe_->messageLabel()->showInfo(infoTxt);
            StatusMsgHandler::instance()->done();
        }
        else {
            MvQ::showTabLabel(dumpTab_, DebugTabIndex, IconProvider::failedPixmap());
            debugTe_->messageLabel()->showError(QString::fromStdString(err));
            StatusMsgHandler::instance()->failed();
        }

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
        QGuiApplication::restoreOverrideCursor();
#endif
    }

    delete dump;
    debugDumpLoaded_ = true;
}

void MvQBufrDumpPanel::locationScanStepChanged(int n)
{
    Q_EMIT locationScanProgess(n);
}

void MvQBufrDumpPanel::selectFilterResultTab()
{
    if (locationContent_ == FilterResultContent)
        dumpTab_->setCurrentIndex(LocationTabIndex);
}

// Sets what the location tab should contain
void MvQBufrDumpPanel::setHasFilterResult(bool b)
{
    setLocationContent((b) ? FilterResultContent : LocationContent);
}

void MvQBufrDumpPanel::setLocationContent(LocationTabContent locCont)
{
    LocationTabContent oriVal = locationContent_;
    if (locCont != oriVal) {
        locationContent_ = locCont;
        if (locCont == LocationContent) {
            dumpTab_->setTabText(LocationTabIndex, tr("Locations"));
            dumpTab_->setTabIcon(LocationTabIndex, QPixmap(":/examiner/globe.svg"));
            locationW_->setMode(LocationWidget::LocationMode);
        }
        else {
            dumpTab_->setTabText(LocationTabIndex, tr("Extracted values"));
            dumpTab_->setTabIcon(LocationTabIndex, QIcon());
            locationW_->setMode(LocationWidget::DataMode);
        }

        clearLocations();
    }
}

// Load locations for the whole BUFR file!!!
// It can be costly. We delay loading it until it becomes first visible with
// the new contents.
void MvQBufrDumpPanel::loadLocations()
{
    if (currentMsg_ == -1 && currentSubset_ == -1) {
        return;
    }

    // It can be expensve to run so we check if it is really needed
    if (dumpTab_->currentIndex() != LocationTabIndex) {
        return;
    }

    // The current tab is the location tab and it is already loaded:
    // we just adjust the message in the location widget
    else if (locationLoaded_) {
        // currentMsg_ starts at 0
        locationW_->updateMessage(currentMsg_ + 1);
        return;
    }

    // The rest of this functions is dealing with the locations
    if (locationContent_ == FilterResultContent)
        return;

    if (messageCanBeDecoded_) {
        Q_ASSERT(data_);
        std::string err;

        // Start status message
        StatusMsgHandler::instance()->task("Scanning locations");

        // Create progress bar
        int num = data_->totalMessageNum();
        int minDuration = 1000.;
        if (num > 1000) {
            minDuration = 0;
        }

        QProgressDialog progress("Scan locations ...", QString(), 0, data_->totalMessageNum());
        progress.setModal(true);
        progress.setMinimumDuration(minDuration);

        connect(this, SIGNAL(locationScanProgess(int)),
                &progress, SLOT(setValue(int)));

        // Create a collector
        BufrLocationCollector* collector = locationW_->makeCollector();

        // get the locations, will start and update the progress bar
        bool ret = data_->readLocations(collector, err);

        locationW_->update(collector);
        locationW_->updateMessage(currentMsg_);

        if (ret) {
            StatusMsgHandler::instance()->done();
        }
        else {
            MvQ::showTabLabel(dumpTab_, LocationTabIndex, IconProvider::failedPixmap());
            QString s = QString::fromStdString(err).simplified();
            locationMessageLabel_->showError(s);
            StatusMsgHandler::instance()->failed();
        }

        locationLoaded_ = true;
    }
}

void MvQBufrDumpPanel::loadFilterResult(MvKeyProfile* prof)
{
    setHasFilterResult(true);
    locationW_->update(prof);
    locationLoaded_ = true;
}

void MvQBufrDumpPanel::currentDumpChanged(int)
{
    if (data_) {
        if (dumpTab_->currentIndex() == ExpandTabIndex)
            loadExpandDump();
        else if (dumpTab_->currentIndex() == CompTabIndex)
            loadCompDump();
        else if (dumpTab_->currentIndex() == DebugTabIndex)
            loadDebugDump();
        else if (dumpTab_->currentIndex() == LocationTabIndex)
            loadLocations();
    }
}

void MvQBufrDumpPanel::writeSettings(QSettings& settings)
{
    settings.beginGroup("dump");
    settings.setValue("dumpTabIndex", dumpTab_->currentIndex());
    jsonDumpWidget_->writeSettings(settings);
    expandDumpWidget_->writeSettings(settings);
    settings.endGroup();
}

void MvQBufrDumpPanel::readSettings(QSettings& settings)
{
    settings.beginGroup("dump");

    MvQ::initTabId(settings, "dumpTabIndex", dumpTab_);
    jsonDumpWidget_->readSettings(settings);
    expandDumpWidget_->readSettings(settings);
    settings.endGroup();
}

//==========================================================
//
// BufrExaminer
//
//==========================================================

BufrExaminer::BufrExaminer(QWidget* parent) :
#ifdef ECCODES_UI
    MvQAbstractMessageExaminer("codes_ui", MvQAbstractMessageExaminer::BufrType, parent)
#else
    MvQAbstractMessageExaminer("BufrExaminer", MvQAbstractMessageExaminer::BufrType, parent)
#endif
{
    setAttribute(Qt::WA_DeleteOnClose);

#ifdef ECCODES_UI
    winTitleBase_ = "codes_ui";
    settingsName_ = "codesui-bufr";
#else
    winTitleBase_ = "Bufr Examiner (Metview)";
    settingsName_ = "mv-BufrExaminer";
#endif
    setWindowTitle(winTitleBase_);

    // Initial size
    setInitialSize(1100, 800);

    // Init
    messageType_ = "BUFR";

    // The main tab - holding the original and filtered bufr file
    mainTab_ = new QTabWidget(this);
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    mainTab_->setTabBarAutoHide(true);
#endif

    //    mainTab_->setProperty("filter", "1");
    //    mainTab_->tabBar()->setProperty("filter", "1");

    // The main panel - holding the original bufr file
    mainPanel_ = new MvQBufrMainPanel(false, this);

    // The main panel is displayed in the tab. The filtered panel
    // will be created and added to the tab on demand.
    mainTab_->addTab(mainPanel_, tr("Original data"));

    // The main tab is on the right hand side of the central splitter
    centralSplitter_->addWidget(mainTab_);

    connect(mainPanel_, SIGNAL(keyProfileChanged()),
            this, SLOT(slotKeyProfileChanged()));

    connect(mainPanel_, SIGNAL(newMessageSelected(int)),
            this, SLOT(newMessageLoaded(int)));

    connect(mainPanel_, SIGNAL(newMessageSelected(int)),
            logPanel_, SLOT(newMessageLoaded(int)));

    connect(mainPanel_, SIGNAL(messageNumDetermined()),
            this, SLOT(updateFileInfoLabel()));

    connect(mainPanel_, SIGNAL(messageNumDetermined()),
            this, SLOT(initFilterEditorHelpers()));

    //----------------------------
    // Setup menus and toolbars
    //----------------------------

    setupFileMenu();
    setupFilterMenu();
    setupMenus(menuItems_);

    //-------------------------
    // Settings
    //-------------------------

    readSettings();
}

BufrExaminer::~BufrExaminer()
{
    saveKeyProfiles(true);
    writeSettings();
    if (filteredData_)
        delete filteredData_;
}

void BufrExaminer::setupFileMenu()
{
    //#ifdef ECCODES_UI
    actionSave_ = new QAction(this);
    actionSave_->setObjectName(QString::fromUtf8("actionSave"));
    actionSave_->setText(tr("&Save filtered BUFR messages ..."));
    actionSave_->setToolTip(tr("Save filtered BUFR messages ..."));
    actionSave_->setShortcut(tr("Ctrl+S"));
    actionSave_->setEnabled(false);
    QIcon icon;
    icon.addPixmap(QPixmap(QString::fromUtf8(":/examiner/saveas_teal.svg")), QIcon::Normal, QIcon::Off);
    actionSave_->setIcon(icon);

    connect(actionSave_, SIGNAL(triggered(bool)),
            this, SLOT(slotSaveFilteredFile(bool)));

    MvQMainWindow::MenuType menuType = MvQMainWindow::FileMenu;

    menuItems_[menuType].push_back(new MvQMenuItem(actionSave_, MvQMenuItem::MenuTarget));

    //#endif
}

void BufrExaminer::setupFilterMenu()
{
    // Filters
    actionFilterEdit_ = new QAction(this);
    actionFilterEdit_->setText("Edit filter");
    actionFilterEdit_->setIcon(QPixmap(":/examiner/filter_blue.svg"));

    connect(actionFilterEdit_, SIGNAL(triggered()),
            this, SLOT(slotFilterEdit()));

    actionFilterRemove_ = new QAction(this);
    actionFilterRemove_->setText("Remove filtered data");
    actionFilterRemove_->setIcon(QPixmap(":/examiner/filter_blue_clear.svg"));
    actionFilterRemove_->setEnabled(false);  // no filter by default

    connect(actionFilterRemove_, SIGNAL(triggered()),
            this, SLOT(slotFilterRevert()));

    MvQMainWindow::MenuType menuType = MvQMainWindow::FilterMenu;
    menuItems_[menuType].push_back(new MvQMenuItem(actionFilterEdit_));
    menuItems_[menuType].push_back(new MvQMenuItem(actionFilterRemove_, MvQMenuItem::MenuTarget));
}

void BufrExaminer::initMain(MvMessageMetaData* data)
{
    Q_ASSERT(mainPanel_);
    auto* bufr = dynamic_cast<BufrMetaData*>(data);
    mainPanel_->init(bufr);
}

void BufrExaminer::initDumps()
{
    // dataDumpTree_->setFocus(Qt::OtherFocusReason);
}

void BufrExaminer::initAllKeys()
{
    // Get all keys
    auto* prof = new MvKeyProfile("Metview keys");
    prof->addKey(new MvKey("MV_Index", "Message", "Message index"));
    allKeys_ << prof;

    std::vector<MvKeyProfile*> allKeyProf;
    keyManager_->loadAllKeys(allKeyProf);
    for (auto& it : allKeyProf) {
        allKeys_ << it;
    }
#if 0
    sectionDumpModel_->setAllKeys(allKeys_);
#endif
}

// A new bufr file is loaded
void BufrExaminer::slotLoadFile(QString f)
{
    if (data_->fileName() == f.toStdString())
        return;

    sourceFileName_ = f;

    loadFile(f);

    // We need to clean the filter panel
    slotFilterRevert();
}

void BufrExaminer::addCloseButtonFilteredTab()
{
    Q_ASSERT(filteredPanel_);

    if (mainTab_->tabBar()->tabButton(FilteredTabIndex, QTabBar::RightSide) == nullptr) {
        auto* closeTb = new QToolButton(filteredPanel_);
        closeTb->setProperty("filterClose", "1");
        closeTb->setAutoRaise(true);
        QIcon icon;
        icon.addPixmap(QPixmap(":/examiner/close_grey.svg"), QIcon::Normal);
        icon.addPixmap(QPixmap(":/examiner/close_red.svg"), QIcon::Active);
        closeTb->setIcon(icon);
        closeTb->setIconSize(QSize(12, 12));
        closeTb->setToolTip(tr("Remove filter"));
        connect(closeTb, SIGNAL(clicked()), this, SLOT(slotFilterRevert()));
        mainTab_->tabBar()->setTabButton(FilteredTabIndex, QTabBar::RightSide, closeTb);
    }

    if (mainTab_->tabBar()->tabButton(FilteredTabIndex, QTabBar::LeftSide) == nullptr) {
        Q_ASSERT(actionSave_);
        auto* hello = new QToolButton(filteredPanel_);
        hello->setProperty("filterClose", "1");
        hello->setDefaultAction(actionSave_);
        hello->setAutoRaise(true);
        mainTab_->tabBar()->setTabButton(FilteredTabIndex, QTabBar::LeftSide, hello);
    }
}

void BufrExaminer::createFilteredPanel()
{
    if (filteredPanel_) {
        // Add the existing panel to the tab and make it visible
        if (mainTab_->count() - 1 < FilteredTabIndex) {
            mainTab_->addTab(filteredPanel_, tr("Filtered data"));
            addCloseButtonFilteredTab();
            filteredPanel_->setVisible(true);
            actionFilterRemove_->setEnabled(true);
        }
        return;
    }

    // The main panel - holding the original bufr file
    filteredPanel_ = new MvQBufrMainPanel(true, this);

    // The filtered panel is displayed in the tab.
    mainTab_->addTab(filteredPanel_, tr("Filtered data"));

    // Add close button to tab
    addCloseButtonFilteredTab();

    connect(filteredPanel_, SIGNAL(keyProfileChanged()),
            this, SLOT(slotKeyProfileChanged()));

    connect(filteredPanel_, SIGNAL(newMessageSelected(int)),
            this, SLOT(newMessageLoaded(int)));

    connect(filteredPanel_, SIGNAL(newMessageSelected(int)),
            logPanel_, SLOT(newMessageLoaded(int)));

    if (!filteredData_) {
        filteredData_ = new BufrMetaData();
    }
    Q_ASSERT(filteredData_);
    filteredPanel_->init(filteredData_);

    // Set the profile on the panel
    MvKeyProfile* profDef = mainPanel_->keyProfile();
    Q_ASSERT(profDef);
    filteredPanel_->loadKeyProfile(profDef);

    actionFilterRemove_->setEnabled(true);
}

// Load a new bufr file in the main panel
void BufrExaminer::loadFile(QString f)
{
#ifdef ECCODES_UI
    filePanel_->setEnabled(false);
#endif
    clearLog();
    mainPanel_->clear();

    // Set the new file name
    data_->setFileName(f.toStdString());
    currentMessageNo_ = -1;

    // Reload
    mainPanel_->reloadData(false);

    // if(sourceFileName_ == f)
    //     sourceMessageNum_=data_->totalMessageNum();

    updateFileInfoLabel();

    // Notify the the filter dialogue
    if (filterDialog_) {
        filterDialog_->newFileLoaded(f.toStdString());
        // helpers will be initailised later when
        // there is data already scanned available!!!
    }

#ifdef ECCODES_UI
    filePanel_->setEnabled(true);
#endif
}

// Load an empty file in the main panel
void BufrExaminer::loadEmptyFile(QString f)
{
#ifdef ECCODES_UI
    filePanel_->setEnabled(false);
#endif
    clearLog();
    mainPanel_->clear();

    // Set the new file name
    data_->setFileName(f.toStdString());
    currentMessageNo_ = -1;

    // Notify main panel thet a new file will be loaded
    // mainPanel_->dataFileChanged();

    // Reload
    // mainPanel_->reloadData();

    // if(sourceFileName_ == f)
    //     sourceMessageNum_=data_->totalMessageNum();

    updateFileInfoLabel();
#ifdef ECCODES_UI
    filePanel_->setEnabled(true);
#endif
}

// Load a new bufr file in the filtered panel
void BufrExaminer::loadFilteredFile(QString f, MvKeyProfile* extractedProf)
{
#ifdef ECCODES_UI
    filePanel_->setEnabled(false);
#endif
    // clearLog();

    Q_ASSERT(filteredPanel_);
    Q_ASSERT(filteredData_);
    Q_ASSERT(data_);

    filteredPanel_->clear();

    // Set the new file name
    filteredData_->setFileName(f.toStdString());
    // currentMessageNo_=-1;

    // Reload
    filteredPanel_->reloadData(extractedProf != nullptr);

    // if(sourceFileName_ == f)
    //     sourceMessageNum_=data_->totalMessageNum();

    // Add the extracted results to the filtered panel.
    // it will take ownership of it.
    if (extractedProf) {
        filteredPanel_->loadFilterResult(extractedProf);
    }

    // Set save action
    actionSave_->setEnabled(filteredData_->totalMessageNum() > 0);

    // set control panel
    filteredPanel_->setFilterInfo(true, data_->totalMessageNum(), filteredData_->totalMessageNum());

    // updateFileInfoLabel();
#ifdef ECCODES_UI
    filePanel_->setEnabled(true);
#endif
}

// Load an empty file in the main panel
void BufrExaminer::loadFilteredEmptyFile(QString f)
{
#ifdef ECCODES_UI
    filePanel_->setEnabled(false);
#endif
    // clearLog();

    Q_ASSERT(filteredPanel_);
    Q_ASSERT(filteredData_);
    Q_ASSERT(data_);

    filteredPanel_->clear();

    // Set the new file name
    filteredData_->setFileName(f.toStdString());

    // Reload
    filteredPanel_->reloadData(false);

    // Set save action
    actionSave_->setEnabled(false);

    // set control panel
    filteredPanel_->setFilterInfo(true, data_->totalMessageNum(), 0);

#ifdef ECCODES_UI
    filePanel_->setEnabled(true);
#endif
}

void BufrExaminer::loadKeyProfile(MvKeyProfile* prof)
{
    Q_ASSERT(mainPanel_);
    mainPanel_->loadKeyProfile(prof);
}

MvKeyProfile* BufrExaminer::loadedKeyProfile() const
{
    Q_ASSERT(mainPanel_);
    return mainPanel_->keyProfile();
}

void BufrExaminer::slotUncompressedMode(bool)
{
    Q_ASSERT(mainPanel_);
    // mainPanel_->setNavigateUncompressed(actionUncompressedMode_->isChecked());
}

void BufrExaminer::newMessageLoaded(int msg)
{
    if (filterDialog_) {
        auto* panel = dynamic_cast<MvQBufrMainPanel*>(sender());
        if (panel != nullptr) {
            filterDialog_->updateHelpers(panel->data(), msg);
        }
    }
}

void BufrExaminer::slotFilterEdit()
{
    if (!filterDialog_) {
        filterDialog_ = new BufrFilterDialog(this);
        filterDialog_->setModal(false);

        // Init helpers
        filterDialog_->initHelpers(mainPanel_->data(),
                                   mainPanel_->currentMessage());

        if (filteredPanel_) {
            filterDialog_->updateHelpers(filteredPanel_->data(), 0);
            filterDialog_->updateHelpers(filteredPanel_->data(),
                                         filteredPanel_->currentMessage());
        }

        connect(filterDialog_, SIGNAL(edited(const BufrFilterDef&)),
                this, SLOT(slotRunFilter(const BufrFilterDef&)));
    }
    filterDialog_->show();
    filterDialog_->newFileLoaded(data_->fileName());
}

void BufrExaminer::slotFilterRevert()
{
    actionFilterRemove_->setEnabled(false);
    actionSave_->setEnabled(false);

    // these are created on demand so we need to check their existence
    if (filteredPanel_ && filteredData_) {
        Q_ASSERT(filteredPanel_);
        Q_ASSERT(filteredData_);
        filteredPanel_->clear();
        if (mainTab_->count() - 1 >= FilteredTabIndex) {
            // Q_ASSERT(mainTab_->count()-1 >= FilteredTabIndex);
            mainTab_->setTabEnabled(FilteredTabIndex, false);
            mainTab_->removeTab(FilteredTabIndex);
            filteredPanel_->hide();
        }
    }
}

void BufrExaminer::slotRunFilter(const BufrFilterDef& def)
{
    Q_ASSERT(data_);

    // if the message filter is empty we revert to the
    // source file
    std::string err;

    // Start status message
    StatusMsgHandler::instance()->task("Running BUFR filter");

    MvTmpFile resFile(false);
    BufrFilterEngine filter(sourceFileName_.toStdString(), BufrFilterEngine::GuiMode, this);

    // Create progress bar
    int num = data_->totalMessageNum();
    int minDuration = 1000.;
    if (num > 1000) {
        minDuration = 0;
    }

    QProgressDialog progress("Running bufr filter ...", QString(), 0, num);
    progress.setModal(true);
    progress.setMinimumDuration(minDuration);

    connect(this, SIGNAL(filterProgess(int)),
            &progress, SLOT(setValue(int)));

    // The object to store the extracted values
    auto* extractedProf = new MvKeyProfile("result");


    try {
        auto* bmd = dynamic_cast<BufrMetaData*>(data_);
        Q_ASSERT(bmd);
        filter.run(def, resFile.path(), extractedProf, num, bmd->messages());

        StatusMsgHandler::instance()->done();

        createFilteredPanel();
        Q_ASSERT(filteredPanel_);
        Q_ASSERT(mainTab_->count() - 1 >= FilteredTabIndex);
        mainTab_->setTabEnabled(FilteredTabIndex, true);

        // Check if data values were extracted
        if (!filter.isExtractedDefined()) {
            delete extractedProf;
            extractedProf = nullptr;
        }

        // Load the filtered bufr file (in the filter panel)
        QFileInfo f(QString::fromStdString(resFile.path()));
        if (f.exists()) {
            // The extracted results (if exist) will be added to the filtered panel.
            // The panel will take ownership of it.
            loadFilteredFile(QString::fromStdString(resFile.path()), extractedProf);
        }
        else {
            loadFilteredEmptyFile(QString::fromStdString(resFile.path()));
        }

        // Set the main tab index to the filtered panel
        Q_ASSERT(mainTab_->count() - 1 >= FilteredTabIndex);
        mainTab_->setCurrentIndex(FilteredTabIndex);
        filteredPanel_->selectFilterResultTab();
    }
    catch (MvException& e) {
        progress.cancel();

        StatusMsgHandler::instance()->failed();

        QMessageBox::critical(this, "codes_ui",
                              "<b>Filter failed</b><br>Error message: " +
                                  QString::fromStdString(e.what()));

        // We need to delete prof
        delete extractedProf;
    }
}

void BufrExaminer::notifyBufrFilterProgress(int n)
{
    Q_EMIT filterProgess(n);
}

void BufrExaminer::slotSaveFilteredFile(bool)
{
#ifdef ECCODES_UI
    if (filteredPanel_) {
        QString startDir = QString::fromStdString(data_->fileName());
        if (!startDir.isEmpty()) {
            QFileInfo fi(startDir);
            startDir = fi.dir().path();
        }

        MvQFileDialog dialog(startDir, QObject::tr("Save BUFR file as"), this);
        dialog.setAcceptMode(QFileDialog::AcceptSave);
        dialog.setFileMode(QFileDialog::AnyFile);
        dialog.setLabelText(QFileDialog::Accept, "Save");

        if (dialog.exec() == QDialog::Accepted) {
            QStringList lst = dialog.selectedFiles();
            if (lst.count() > 0) {
                QString fTarget = lst[0];
                QString fSrc = filteredPanel_->fileName();

                QString err;
                if (!MvQFileUtil::copy(fSrc, fTarget, err)) {
                    QMessageBox::critical(this, "Failed to save filter result", err);
                }
                else {
                    err.clear();
                    Q_ASSERT(fileList_);
                    Q_ASSERT(filePanel_);

                    if (!fileList_->isPresent(fTarget)) {
                        fileList_->add(fTarget, err);
                    }

                    if (!err.isEmpty()) {
                        QMessageBox::critical(this, "Could not add save file to file list", err);
                    }
                }
            }
        }
    }
#endif
}

void BufrExaminer::slotShowAboutBox()
{
#ifdef ECCODES_UI
    MvQAbout about("codes_ui", "", MvQAbout::GribApiVersion);
#else
    MvQAbout about("BufrExaminer", "", MvQAbout::MetviewVersion);
#endif
    about.exec();
}

void BufrExaminer::slotStatusMessage(QString s)
{
    statusMessageLabel_->setText(s);
}

void BufrExaminer::updateFileInfoLabel()
{
    // file info panel
    fileInfoLabel_->setBufrTextLabel(sourceFileName_, data_->totalMessageNum(), false, data_->totalMessageNum(), false);

    // window title
    // updateWinTitle(sourceFileName_ + ((filtered_)?" [filtered]":""));
    updateWinTitle(sourceFileName_);
}

void BufrExaminer::initFilterEditorHelpers()
{
    if (filterDialog_)
        filterDialog_->initHelpers(mainPanel_->data(), mainPanel_->currentMessage());
}

void BufrExaminer::writeSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, settingsName_);

    settings.clear();

    MvQAbstractMessageExaminer::writeSettings(settings);
    mainPanel_->writeSettings(settings);
}

void BufrExaminer::readSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, settingsName_);

    MvQAbstractMessageExaminer::readSettings(settings);
    mainPanel_->readSettings(settings);
}
