/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


///////////////////////////// Qt includes

///////////////////////////// Local includes
#include <minexpert/gui/AbstractMultiPlotWnd.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/nongui/globals.hpp>


namespace msXpSmineXpert
{

//! Construct a AbstractMultiPlotWnd instance.
/*!

  The new instance is initialized using the parameters.

  \param parent pointer to the parent widget.

  \param name name of \c this AbstractMultiPlotWnd instance.

  \param desc description of \c this AbstractMultiPlotWnd instance.

  \sa setupWindow().

*/
AbstractMultiPlotWnd::AbstractMultiPlotWnd(QWidget *parent,
                                           const QString &name,
                                           const QString &desc)
  : QMainWindow{parent}, m_name{name}, m_desc{desc}
{
  if(!setupWindow())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
}

//! Destruct \c this AbstractMultiPlotWnd instance.
AbstractMultiPlotWnd::~AbstractMultiPlotWnd()
{
}


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


//! Write the settings of this window to keep record over sessions.
void
AbstractMultiPlotWnd::writeSettings()
{
  QSettings settings;

  settings.setValue(QString("%1_geometry").arg(m_desc), saveGeometry());
  settings.setValue(QString("%1_windowState").arg(m_desc), saveState());
  settings.setValue(QString("%1_splitterState").arg(m_desc),
                    mp_splitter->saveState());
  settings.setValue(QString("%1_visible").arg(m_desc), isVisible());
}


//! Read the settings of this window.
void
AbstractMultiPlotWnd::readSettings()
{
  QSettings settings;

  restoreGeometry(
    settings.value(QString("%1_geometry").arg(m_desc)).toByteArray());
  restoreState(
    settings.value(QString("%1_windowState").arg(m_desc)).toByteArray());
  mp_splitter->restoreState(
    settings.value(QString("%1_splitterState").arg(m_desc)).toByteArray());

  bool wasVisible = settings.value(QString("%1_visible").arg(m_desc)).toBool();
  setVisible(wasVisible);
}


//! Set the name of this window.
void
AbstractMultiPlotWnd::setName(QString name)
{
  m_name = name;
}


//! Get the name of this window.
QString
AbstractMultiPlotWnd::name()
{
  return m_name;
}


//! Set the description of this window.
void
AbstractMultiPlotWnd::setDesc(QString desc)
{
  m_desc = desc;
}


//! Get the description of this window.
QString
AbstractMultiPlotWnd::desc()
{
  return m_desc;
}


//! Setup the window widgetry.
bool
AbstractMultiPlotWnd::setupWindow()
{
  this->resize(800, 600);

  mp_centralWidget = new QWidget(this);
  mp_centralWidget->setObjectName(QStringLiteral("mp_centralWidget"));

  this->setCentralWidget(mp_centralWidget);

  // We want a QSplitter to divide vertically the window in two parts, the
  // upper part being the plotwidget with all the traces in the same widget
  // (multi-graph widget). The lower part is the scrollarea into which each
  // plot is stacked (multi-plot).

  mp_splitter = new QSplitter(Qt::Vertical, this);

  // Passing the widget as parent to the layout constructor is the same as
  // calling widget->setLayout(layout).
  mp_centralWidgetGridLayout = new QGridLayout(mp_centralWidget);
  mp_centralWidgetGridLayout->setObjectName(
    QStringLiteral("mp_centralWidgetGridLayout"));

  // Now that the main layout is set to the central widget, let's pack the
  // scroll area in it. Upon adding the area to the layout, that area is
  // reparented to the layout that is responsible for its destruction.
  mp_scrollArea = new QScrollArea(mp_centralWidget);
  mp_scrollArea->setObjectName(QStringLiteral("mp_scrollArea"));
  mp_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  mp_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  // We accept that the plots are resized, but we set a minimum size when we
  // create them.
  mp_scrollArea->setWidgetResizable(true);
  mp_scrollArea->setAlignment(Qt::AlignCenter);

  mp_scrollAreaWidgetContents = new QWidget();
  mp_scrollAreaWidgetContents->setObjectName(
    QStringLiteral("mp_scrollAreaWidgetContents"));
  mp_scrollAreaWidgetContents->setGeometry(QRect(0, 0, 766, 525));

  mp_scrollAreaWidgetContentsGridLayout =
    new QGridLayout(mp_scrollAreaWidgetContents);
  mp_scrollAreaWidgetContentsGridLayout->setObjectName(
    QStringLiteral("mp_scrollAreaWidgetContentsGridLayout"));

  mp_scrollAreaVBoxLayout = new QVBoxLayout();
  mp_scrollAreaVBoxLayout->setObjectName(
    QStringLiteral("mp_scrollAreaVBoxLayout"));

  mp_scrollAreaWidgetContentsGridLayout->addLayout(
    mp_scrollAreaVBoxLayout, 0, 0, 1, 1);

  mp_scrollArea->setWidget(mp_scrollAreaWidgetContents);

  mp_centralWidgetGridLayout->addWidget(mp_splitter, 0, 0, 1, 1);

  // At the moment we only add the scroll area where all the plot widgets
  // will be stacked as new plots are required. We'll later "prepend" the
  // multi-graph widget that we will need to display all the traces in a
  // single plot widget.
  mp_splitter->addWidget(mp_scrollArea);

  // Menu bar
  mp_menubar = new QMenuBar(this);
  mp_menubar->setObjectName(QStringLiteral("mp_menubar"));
  mp_menubar->setGeometry(QRect(0, 0, 800, 19));
  this->setMenuBar(mp_menubar);

  // Status bar
  mp_statusbar = new QStatusBar(this);
  mp_statusbar->setObjectName(QStringLiteral("mp_statusbar"));
  this->setStatusBar(mp_statusbar);

  // Make sure we allocate the QLabel and QProgressBar that should be
  // available in the status bar.
  mp_statusBarLabel       = new QLabel(this);
  mp_statusBarProgressBar = new QProgressBar(this);

  // We want to have a cancel push button available.
  const QIcon cancelIcon       = QIcon(":/images/red-cross-cancel.png");
  mp_statusBarCancelPushButton = new QPushButton(cancelIcon, "", this);
  connect(mp_statusBarCancelPushButton,
          &QPushButton::clicked,
          this,
          &AbstractMultiPlotWnd::cancelPushButtonClicked);

  // Append these widgets as permanent widgets.
  mp_statusbar->addPermanentWidget(mp_statusBarLabel);
  mp_statusbar->addPermanentWidget(mp_statusBarProgressBar);
  mp_statusbar->addPermanentWidget(mp_statusBarCancelPushButton);

  // We want them invisible to start with.
  mp_statusBarLabel->setVisible(false);
  mp_statusBarProgressBar->setVisible(false);
  mp_statusBarCancelPushButton->setVisible(false);

  // Tool bar
  mp_toolbar = addToolBar("Settings");
  mp_toolbar->setObjectName(QStringLiteral("mp_toolbar"));

  // Make sure a right mouse button click does not elicit the context menu that
  // when clicked made the toolbar disappear totally.
  mp_toolbar->setContextMenuPolicy(Qt::PreventContextMenu);

  const QIcon xRangeIcon = QIcon(":/images/lock-x-range.png");
  mp_lockXRangeAct       = new QAction(xRangeIcon, tr("Lock X range"), this);
  mp_lockXRangeAct->setCheckable(true);
  mp_lockXRangeAct->setShortcuts(QList<QKeySequence>{
    QKeySequence(Qt::CTRL + Qt::Key_L, Qt::CTRL + Qt::Key_X)});
  mp_lockXRangeAct->setStatusTip(tr("Lock X range"));

  connect(mp_lockXRangeAct,
          &QAction::toggled,
          this,
          &AbstractMultiPlotWnd::lockXRangeToggle);

  mp_toolbar->addAction(mp_lockXRangeAct);

  const QIcon yRangeIcon = QIcon(":/images/lock-y-range.png");
  mp_lockYRangeAct       = new QAction(yRangeIcon, tr("Lock Y range"), this);
  mp_lockYRangeAct->setCheckable(true);
  mp_lockYRangeAct->setShortcuts(QList<QKeySequence>{
    QKeySequence(Qt::CTRL + Qt::Key_L, Qt::CTRL + Qt::Key_Y)});
  mp_lockYRangeAct->setStatusTip(tr("Lock Y range"));

  connect(mp_lockYRangeAct,
          &QAction::toggled,
          this,
          &AbstractMultiPlotWnd::lockYRangeToggle);

  mp_toolbar->addAction(mp_lockYRangeAct);

  const QIcon plotWidgetHelpIcon = QIcon(":/images/plot-widget-help.png");
  QAction *mp_plotHelperAct =
    new QAction(plotWidgetHelpIcon, tr("Show a help summary"), this);
  mp_plotHelperAct->setCheckable(false);
  mp_plotHelperAct->setShortcuts(QList<QKeySequence>{
    QKeySequence(Qt::CTRL + Qt::Key_S, Qt::CTRL + Qt::Key_H)});
  mp_plotHelperAct->setStatusTip(tr("Show a help summary"));

  connect(mp_plotHelperAct, &QAction::triggered, [&]() {
    emit showHelpSummarySignal();
  });

  mp_toolbar->addAction(mp_plotHelperAct);

  // Finally, make sure that the window has a meaningful title:
  setWindowTitle(QString("%1 - %2").arg(m_name).arg(m_desc));

  // Finally set the window to the proper geometry/status, this must occur
  // at the end, because some pointers must be pointing to properly
  // allocated object instances.
  readSettings();

  return true;
}


//! Get the graph in the multigraph plot widget.
/*!

  The graph in the multigraph plot widget that is searched for is the graph
  that replicates the single one located in the \p plotWidget.

  The graph is searched for in \c m_plotWidgetGraphMap.

  \return a pointer to the found graph.

*/
QCPGraph *
AbstractMultiPlotWnd::multiGraph(AbstractPlotWidget *plotWidget)
{
  if(plotWidget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  return m_plotWidgetGraphMap.value(plotWidget);
}


//! Get the plot widget matching the \p graph.
/*!

  The \p graph passed as parameter is one of the graph plotted in the
  multigraph plot widget. We are seeking the pointer to the plot widget of
  which \p graph is a replicate.

  The plot widget is searched for in \c m_plotWidgetGraphMap.

  \return a pointer to the found plot widget.

*/
AbstractPlotWidget *
AbstractMultiPlotWnd::monoGraphPlot(QCPGraph *graph)
{
  if(graph == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);


  return m_plotWidgetGraphMap.key(graph);
}


//! Update the progress feedback using the \p msg string and the \p currentValue
//! value.
void
AbstractMultiPlotWnd::updateFeedback(QString msg,
                                     int currentValue,
                                     bool setVisible,
                                     int logType,
                                     int startValue,
                                     int endValue)
{
  // qDebug() << __FILE__ << __LINE__
  //<< "Entering" << __FUNCTION__
  //<< "with message: " << msg
  //<< "currentValue:" << currentValue
  //<< "set visible:" << setVisible
  //<< "log type:" << logType
  //<< "startValue:" << startValue
  //<< "endValue:" << endValue;

  if(logType & LogType::LOG_TO_CONSOLE)
    {
      MainWindow *mainWindow = static_cast<MainWindow *>(parent());
      mainWindow->logConsoleMessage(msg);
    }

  if(logType & LogType::LOG_TO_STATUS_BAR)
    {
      mp_statusBarLabel->setVisible(setVisible);
      mp_statusBarCancelPushButton->setVisible(setVisible);
      mp_statusBarProgressBar->setVisible(setVisible);

      if(startValue != -1)
        mp_statusBarProgressBar->setMinimum(startValue);
      if(endValue != -1)
        mp_statusBarProgressBar->setMaximum(endValue);

      mp_statusBarProgressBar->setValue(currentValue);

      mp_statusBarLabel->setText(msg);
    }
}


//! Update the status bar message with the \p msg string.
void
AbstractMultiPlotWnd::updateStatusBar(QString msg)
{
  statusBar()->showMessage(msg, 5000);
}


//! Get the plot widget at \p index in \c m_plotWidgetList.
/*!

  Note that index cannot be out of bounds.

*/
AbstractPlotWidget *
AbstractMultiPlotWnd::plotWidgetAt(int index)
{
  if(index >= m_plotWidgetList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  return m_plotWidgetList.at(index);
}


//! Get the index of \p plotWidget in \c m_plotWidgetList.
int
AbstractMultiPlotWnd::plotWidgetIndex(AbstractPlotWidget *plotWidget)
{
  if(plotWidget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  for(int iter = 0; iter < m_plotWidgetList.size(); ++iter)
    {
      if(m_plotWidgetList.at(iter) == plotWidget)
        return iter;
    }

  return -1;
}


//! Clear all the plots, effectively destroying them along with the mass data.
void
AbstractMultiPlotWnd::clearPlots()
{
  // Get a list of children by name plotWidget:

  QList<QWidget *> childrenList = QObject::findChildren<QWidget *>(
    QString("monoGraphPlot"), Qt::FindChildrenRecursively);

  qDebug() << __FILE__ << __LINE__ << "Should be destroying "
           << childrenList.size() << "plot widgets.";

  for(int iter = 0; iter < childrenList.size(); ++iter)
    {
      QWidget *widget = childrenList.at(iter);

      qDebug() << __FILE__ << __LINE__
               << "Asking for destruction of plot widget:" << widget;

      static_cast<AbstractPlotWidget *>(widget)->shouldDestroyPlotWidget();
    }
}

//! Remove from \c m_plotWidgetList the \p plotWidget.
bool
AbstractMultiPlotWnd::delistPlotWidget(AbstractPlotWidget *plotWidget)
{
  for(int iter = 0; iter < m_plotWidgetList.size(); ++iter)
    {
      if(m_plotWidgetList.at(iter) == plotWidget)
        m_plotWidgetList.removeAt(iter);

      return true;
    }

  return false;
}


void
AbstractMultiPlotWnd::keyReleaseEvent(QKeyEvent *event)
{
  Q_UNUSED(event);
}


//! Set the x-axis range lock between all the plot widgets.
void
AbstractMultiPlotWnd::lockXRangeToggle(bool checked)
{
  m_lockXRange = checked;
}


//! Set the y-axis range lock between all the plot widgets.
void
AbstractMultiPlotWnd::lockYRangeToggle(bool checked)
{
  m_lockYRange = checked;
}


//! Toggle visibility of the multigraph plot widget graph
/*!

  The multigraph plot widget graph matching the \p widget is searched for and
  its visibility is toggled.

*/
void
AbstractMultiPlotWnd::toggleMultiGraph(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QCPGraph *graph = m_plotWidgetGraphMap.value(widget);

  if(graph != Q_NULLPTR)
    {
      graph->setVisible(!graph->visible());
      graph->parentPlot()->replot();
    }
  else
    {
      qDebug() << __FILE__ << __LINE__ << "graph is Q_NULLPTR";
    }
}


//! Show the multigraph plot widget graph
/*!

  The multigraph plot widget graph matching the \p widget is searched for and
  made visibile.

*/
void
AbstractMultiPlotWnd::showMultiGraph(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QCPGraph *graph = m_plotWidgetGraphMap.value(widget);

  if(graph != Q_NULLPTR)
    {

      graph->setVisible(true);

      graph->parentPlot()->replot();
    }
  else
    {
      qDebug() << __FILE__ << __LINE__ << "graph is Q_NULLPTR";
    }
}


//! Hide the multigraph plot widget graph
/*!

  The multigraph plot widget graph matching the \p widget is searched for and
  made invisibile.

*/
void
AbstractMultiPlotWnd::hideMultiGraph(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QCPGraph *graph = m_plotWidgetGraphMap.value(widget);

  if(graph != Q_NULLPTR)
    {
      graph->setVisible(false);
      graph->parentPlot()->replot();
    }
  else
    {
      qDebug() << __FILE__ << __LINE__ << "graph is Q_NULLPTR";
    }
}


//! Remove the multigraph plot widget graph matching the \p widget.
void
AbstractMultiPlotWnd::removeMultiGraph(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QCPGraph *graph = m_plotWidgetGraphMap.value(widget);
  if(graph != Q_NULLPTR)
    {
      if(graph->parentPlot()->hasPlottable(graph))
        {
          graph->parentPlot()->removeGraph(graph);
          graph->parentPlot()->replot();
        }
    }
}


//! Emit the signal to replot.
void
AbstractMultiPlotWnd::emitReplotSignal(QCPRange xAxisRange, QCPRange yAxisRange)
{
  int whichAxis = 0;

  if(mp_lockXRangeAct->isChecked())
    {
      whichAxis |= X_AXIS;
    }

  if(mp_lockYRangeAct->isChecked())
    {
      whichAxis |= Y_AXIS;
    }

  emit replotWithAxisRange(xAxisRange, yAxisRange, whichAxis);
}


//! Emit the signal to redraw the \p focusedPlotWidget background.
void
AbstractMultiPlotWnd::emitRedrawPlotBackground(QWidget *focusedPlotWidget)
{
  emit redrawPlotBackground(focusedPlotWidget);
}


//! Get a pointer to the AnalysisPreferences.
const AnalysisPreferences *
AbstractMultiPlotWnd::analysisPreferences() const
{
  return mp_analysisPreferences;
}


//! Set the analysis preferences.
void
AbstractMultiPlotWnd::setAnalysisPreferences(AnalysisPreferences *preferences)
{
  if(preferences == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  mp_analysisPreferences = preferences;
}


//! Set the file in which to store the analysis steps.
void
AbstractMultiPlotWnd::setAnalysisFile(QFile *analysisFile)
{
  if(analysisFile == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d. The file pointer cannot be 0. Program aborted.",
      __FILE__,
      __LINE__);

  mp_analysisFile = analysisFile;
}


//! Tell that the user wants to cancel the operation.
void
AbstractMultiPlotWnd::cancelPushButtonClicked()
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  m_isOperationCancelled = true;
  emit cancelOperationSignal();
}


void
AbstractMultiPlotWnd::setMzIntegrationParamsWnd(MzIntegrationParamsWnd *wnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "with params:" << wnd->paramsAsText();

  mp_mzIntegrationParamsWnd = wnd;
}

void
AbstractMultiPlotWnd::updateMzIntegrationParams(MzIntegrationParams params,
                                                const QColor &color)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< QString::asprintf("\twith params:\n%s\n",
  //params.asText().toLatin1().data())
  //<< "and color:" << color;

  mp_mzIntegrationParamsWnd->updateMzIntegrationParams(params, color);
}


void
AbstractMultiPlotWnd::mzIntegrationParamsChanged(MzIntegrationParams params)
{
  // This function answers to the corresponding signal emitted from the
  // MzIntegrationParamsWnd.

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "with params:" << params.asText();

  // The last focused plot widget needs to "read" the new params and set
  // them to its history.

  if(mp_lastFocusedPlotWidget != Q_NULLPTR)
    {
      dynamic_cast<TicChromPlotWidget *>(mp_lastFocusedPlotWidget)
        ->setMzIntegrationParams(params);
    }
}


} // namespace msXpSmineXpert
