/* 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
#include <QObject>
#include <QDebug>
#include <QDialog>
#include <QWidget>
#include <QColorDialog>
#include <QPen>


/////////////////////// Local includes
#include <minexpert/gui/OpenSpectraDlg.hpp>
#include <minexpert/nongui/MassSpecDataSet.hpp>
#include <minexpert/gui/AbstractPlotWidget.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/gui/DataPlotWidgetRelation.hpp>


namespace msXpSmineXpert
{


//! Construct an OpenSpectraDlg instance.
OpenSpectraDlg::OpenSpectraDlg(QWidget *parent) : QDialog{parent}
{
  if(parent == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_ui.setupUi(this);

  if(!initialize())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
}


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


//! Handle the close event.
void
OpenSpectraDlg::closeEvent(QCloseEvent *event)
{
  writeSettings();
  hide();
}


//! Write the settings to as to restore the window geometry later.
void
OpenSpectraDlg::writeSettings()
{
  QSettings settings;
  settings.setValue("OpenSpectraDlg_geometry", saveGeometry());
  settings.setValue("OpenSpectraDlg_visible", isVisible());
}


//! Read the settings to as to restore the window geometry.
void
OpenSpectraDlg::readSettings()
{
  QSettings settings;
  restoreGeometry(settings.value("OpenSpectraDlg_geometry").toByteArray());

  bool wasVisible = settings.value("OpenSpectraDlg_visible").toBool();
  setVisible(wasVisible);
}


//! Initialize the window.
bool
OpenSpectraDlg::initialize()
{
  setWindowTitle("mineXpert - Loaded mass spectrum files");

  // Populate the action combo box.

  m_ui.actionComboBox->addItems(m_actionList);

  readSettings();

  // Make the connections

  connect(
    m_ui.actionComboBox,
    static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
    this,
    &OpenSpectraDlg::actionComboBoxActivated);

  return true;
}


//! Add a new list widget item corresponding to a new TIC chromatogram spectrum.
/*!

  The color with which the list widget item text is printed is \p color and
  the text itself is the name of the file from which the data were loaded in
  the first place, that is stored in the \p dataSet MassSpecDataSet instance.

*/
QListWidgetItem *
OpenSpectraDlg::addOpenSpectrum(const MassSpecDataSet *dataSet, QColor color)
{
  if(dataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Create an icon that is simply a rectangle filled with color.
  QPixmap rectangle(15, 15);
  rectangle.fill(color);
  QIcon icon(rectangle);

  QListWidgetItem *item = new QListWidgetItem(icon, dataSet->fileName());

  // The font color must match the color that was used to display the
  // corresponding graph.

  QBrush brush(Qt::SolidPattern);
  brush.setColor(color);

  item->setForeground(brush);

  // Append the new items to the current list.
  int row = m_ui.openSpectraListWidget->count();
  m_ui.openSpectraListWidget->insertItem(row, item);

  // Get the TIC chrom wnd's plot widget pointer so that we can store it in
  // the map along with the pointer to the list widget item. This mapping
  // will allow to remove the list widget item when the plot widget is
  // destroyed.
  AbstractPlotWidget *widget =
    dynamic_cast<AbstractPlotWidget *>(dataSet->parent());
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_listItemTicPlotMap.insert(item, widget);

  return item;
}


//! Remove a list widget item corresponding to the \p TicChromPlotWidget.
void
OpenSpectraDlg::removeListWidgetItem(AbstractPlotWidget *widget)
{
  // Get the widget item corresponding to the plot widget:
  QListWidgetItem *item = m_listItemTicPlotMap.key(widget);
  m_listItemTicPlotMap.remove(item);

  // Deleting the list widget item will remove it automatically from the
  // widget list.
  delete item;
}


//! Perform the action corresponding to the drop down menu list widget item.
/*!

  The actions are encoded for each drop down menu list item with a textual
  string, like "Hide selected" or "Show all", for example.

*/
void
OpenSpectraDlg::actionComboBoxActivated(const QString &text)
{
  MainWindow *mainWindow = static_cast<MainWindow *>(parent());

  if(text == "Hide" || text == "Show" || text == "Toggle visibility")
    {
      // When the user asks for plot widgets to be hidden, what she asks for
      // is that the matching multigraph graph be hidden. But we never hide
      // the graph in the singlegraph plot widgets of the various windows
      // (mass, drift or tic window).
      QList<QListWidgetItem *> selectedItemList =
        m_ui.openSpectraListWidget->selectedItems();

      if(selectedItemList.size() == 0)
        return;

      // qDebug() << __FILE__ << __LINE__ << "selected item count"
      //<< selectedItemList.size();

      for(int iter = 0; iter < selectedItemList.size(); ++iter)
        {
          QListWidgetItem *curItem = selectedItemList.at(iter);

          // This is the corresponding TIC chrom window's plot widget. This
          // is the root widget because that is the widget that is created
          // first right after having loaded data from the file. That is, all
          // the other widgets are targets of this origin widget (for a
          // single file, of course, more than one file might have been
          // opened, in which case we have more than on TicChromPlotWidget
          // listed in this dialog list widget and we are in the loop
          // iterating in these sequentially).

          AbstractPlotWidget *widget = m_listItemTicPlotMap.value(curItem);

          QList<AbstractPlotWidget *> targetPlotWidgetList;
          mainWindow->m_dataPlotWidgetRelationer.targetsRecursive(
            widget, &targetPlotWidgetList);

          for(int jter = 0; jter < targetPlotWidgetList.size(); ++jter)
            {
              AbstractPlotWidget *widget = targetPlotWidgetList.at(jter);

              qDebug() << __FILE__ << __LINE__ << "here";

              if(text == "Hide")
                widget->hideMultiGraph();
              else if(text == "Show")
                widget->showMultiGraph();
              else if(text == "Toggle visibility")
                widget->toggleMultiGraph();
            }
        }
    }
  else if(text == "Hide all" || text == "Show all")
    {
      // All the items must be hidden or shown.
      QList<AbstractPlotWidget *> allPlotWidgetList;
      int itemCount = m_ui.openSpectraListWidget->count();

      for(int iter = 0; iter < itemCount; ++iter)
        {
          QListWidgetItem *curItem = m_ui.openSpectraListWidget->item(iter);

          // This is the corresponding TIC chrom window's plot widget.
          AbstractPlotWidget *widget = m_listItemTicPlotMap.value(curItem);

          QList<AbstractPlotWidget *> targetPlotWidgetList;
          mainWindow->m_dataPlotWidgetRelationer.targetsRecursive(
            widget, &targetPlotWidgetList);

          for(int jter = 0; jter < targetPlotWidgetList.size(); ++jter)
            {
              AbstractPlotWidget *widget = targetPlotWidgetList.at(jter);

              if(text == "Hide all")
                widget->hideMultiGraph();
              else
                widget->showMultiGraph();
            }
        }
    }
  else if(text == "Hide all but selected" || text == "Show all but selected")
    {
      int itemCount = m_ui.openSpectraListWidget->count();

      for(int iter = 0; iter < itemCount; ++iter)
        {
          QListWidgetItem *curItem = m_ui.openSpectraListWidget->item(iter);

          // This is the corresponding TIC chrom window's plot widget.
          AbstractPlotWidget *widget = m_listItemTicPlotMap.value(curItem);

          QList<AbstractPlotWidget *> targetPlotWidgetList;
          mainWindow->m_dataPlotWidgetRelationer.targetsRecursive(
            widget, &targetPlotWidgetList);

          for(int jter = 0; jter < targetPlotWidgetList.size(); ++jter)
            {
              AbstractPlotWidget *widget = targetPlotWidgetList.at(jter);

              if(text == "Hide all but selected")
                {
                  if(curItem->isSelected())
                    widget->showMultiGraph();
                  else
                    widget->hideMultiGraph();
                }
              else
                {
                  if(curItem->isSelected())
                    widget->hideMultiGraph();
                  else
                    widget->showMultiGraph();
                }
            }
        }
    }
  else if(text == "Change color")
    {

      // This option can only be carried over if there is a single item
      // selected.

      QList<QListWidgetItem *> selectedItemList =
        m_ui.openSpectraListWidget->selectedItems();

      if(selectedItemList.size() == 0 || selectedItemList.size() > 1)
        return;

      // This is the TIC chrom plot widget.

      AbstractPlotWidget *plotWidget = firstSelectedPlotWidget();

      // Store the current color for later recycling.
      QColor currentColor = plotWidget->graph()->pen().color();

      // Let the user select the color.

      QColor newColor = QColorDialog::getColor(
        Qt::white,
        Q_NULLPTR,
        QString("Please select a new color for the plot"));

      if(!newColor.isValid())
        return;

      // We need the main window to get access to the color handling features.

      MainWindow *mainWindow = static_cast<MainWindow *>(parent());

      QColor *useColor = Q_NULLPTR;

      // Let's see if the color is already used.

      if(mainWindow->isPlottingColorUsed(newColor))
        {
          QMessageBox::StandardButton ret = QMessageBox::warning(
            this,
            "mineXpert - Open spectra",
            "This color is already used. Go on ?",
            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);

          if(ret == QMessageBox::Cancel)
            return;
          else if(ret == QMessageBox::No)
            return;
        }
      else
        {
          useColor = new QColor(newColor);
          mainWindow->m_usedColorList.append(useColor);
        }

      const DataPlotWidgetRelationer &relationer =
        mainWindow->m_dataPlotWidgetRelationer;
      QList<DataPlotWidgetRelation *> relationList;
      AbstractPlotWidget *recoloringPlotWidget = Q_NULLPTR;

      relationer.getRelationsByOriginRecursive(plotWidget, &relationList);

      qDebug() << __FILE__ << __LINE__
               << "The number of relations is:" << relationList.size();

      if(!relationList.size())
        {
          // We are handling a specific case: the TIC chromatogram plot widget
          // that is created automatically upon loading a mass sectrometry
          // file.

          plotWidget->changePlottingColor(newColor);
        }
      else
        {
          // The plot widget is not a TIC chromatogram plot widget

          for(int iter = 0; iter < relationList.size(); ++iter)
            {
              DataPlotWidgetRelation *relation = relationList.at(iter);

              // A DataPlotWidgetRelation relates plot widgets together.

              recoloringPlotWidget =
                const_cast<AbstractPlotWidget *>(relation->originPlotWidget());
              if(recoloringPlotWidget != Q_NULLPTR)
                recoloringPlotWidget->changePlottingColor(newColor);

              recoloringPlotWidget =
                const_cast<AbstractPlotWidget *>(relation->targetPlotWidget());
              if(recoloringPlotWidget != Q_NULLPTR)
                recoloringPlotWidget->changePlottingColor(newColor);
            }
        }

      // And now finish by recoloring the color vignette of the corresponding
      // item in the QListWidget and the matching file name text.
      QListWidgetItem *item = selectedItemList.first();

      // Create an icon that is simply a rectangle filled with color.
      QPixmap rectangle(15, 15);
      rectangle.fill(newColor);
      QIcon icon(rectangle);

      item->setIcon(icon);

      // The font color must match the color that was used to display the
      // corresponding graph.

      QBrush brush(Qt::SolidPattern);
      brush.setColor(newColor);
      item->setForeground(brush);

      // Finally, recycle the previous color
      mainWindow->recyclePlottingColor(currentColor);
    }
  else if(text == "Close")
    {
      // The currently selected items must be closed.

      QList<QListWidgetItem *> selectedItemList =
        m_ui.openSpectraListWidget->selectedItems();

      if(selectedItemList.size() == 0)
        return;

      for(int iter = 0; iter < selectedItemList.size(); ++iter)
        {
          QListWidgetItem *curItem = selectedItemList.at(iter);

          // This is the corresponding TIC chrom window's plot widget.
          AbstractPlotWidget *widget = m_listItemTicPlotMap.value(curItem);

          // The call below will initiate a cascade of events, first removing
          // the matching multigraph graph, then triggering the removal of
          // this plot widget. This removal will first trigger the removal of
          // any target plot widget related to this origin plot widget (the
          // origin plot widget is
          // the plot widget in which the user started an integration
          // operation that then produces a new plot widget with the results
          // of the integration, like DT->MS, for example. The MS plot widget
          // is a target plot widget and the DT plot widget is an origin plot
          // widget. Each time a root plot widget is removed, automatically
          // the matching widget list
          // item is deleted, so we do not need to handle anything here, else
          // than asking the widget to initiate that cascade of events.

          widget->shouldDestroyPlotWidget();
        }
    }
  else if(text == "Close all but selected")
    {

      // We cannot remove the widget inside the for loop below because when we
      // destroy a widget, the QListWidgetItem is destroyed.
      QList<AbstractPlotWidget *> plotWidgetList = unselectedPlotWidgets();

      // Now that we have a list of plot widgets to remove we can iterate in
      // the list and destroy the widgets sequentially.
      for(int jter = 0; jter < plotWidgetList.size(); ++jter)
        plotWidgetList.at(jter)->shouldDestroyPlotWidget();
    }
  else if(text == "Close all")
    {
      while(m_ui.openSpectraListWidget->count())
        {
          QListWidgetItem *curItem = m_ui.openSpectraListWidget->item(0);

          // This is the corresponding TIC chrom window's plot widget.
          AbstractPlotWidget *widget = m_listItemTicPlotMap.value(curItem);

          widget->shouldDestroyPlotWidget();
        }
    }
}


//! Tell if the the list widget item corresponding to \p widget is selected.
bool
OpenSpectraDlg::isPlotWidgetSelected(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  QListWidgetItem *item = m_listItemTicPlotMap.key(widget);

  if(item == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that obtained pointer is nullptr. Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  return item->isSelected();
}


//! Return the number of items in the list widget.
int
OpenSpectraDlg::itemCount()
{
  return m_ui.openSpectraListWidget->count();
}


//! Return the number of items currently selected in the list widget.
int
OpenSpectraDlg::selectedItemCount()
{
  return m_ui.openSpectraListWidget->selectedItems().size();
}


//! Return the index of the first selected list widget item.
/*!

  If multiple items are selected return the least index and set the
  corresponding TicChromPlotWidget pointer to \p widget if that pointer is
  non-nullptr.

*/
int
OpenSpectraDlg::firstSelectedPlotWidgetIndex(AbstractPlotWidget *widget)
{
  // If there are multiple selected items, return the one with the least
  // index and set widget to the corresponding first selected item.

  QList<QListWidgetItem *> itemList =
    m_ui.openSpectraListWidget->selectedItems();
  QListWidgetItem *item = Q_NULLPTR;

  if(itemList.size() == 0)
    {
      widget = Q_NULLPTR;
      return -1;
    }

  item = itemList.at(0);

  if(widget != Q_NULLPTR)
    widget = m_listItemTicPlotMap.value(item);

  return m_ui.openSpectraListWidget->row(item);
}


//! Return the plot widget of the first selected list widget item.
/*!

  If multiple items are selected return the plot widget corresponding to the
  least index and set the
  index value to \p index if that pointer is non-nullptr.

*/
AbstractPlotWidget *
OpenSpectraDlg::firstSelectedPlotWidget(int *index)
{
  // If there are multiple selected items, return the one with the least
  // index.

  QList<QListWidgetItem *> itemList =
    m_ui.openSpectraListWidget->selectedItems();

  AbstractPlotWidget *widget = Q_NULLPTR;
  QListWidgetItem *item      = Q_NULLPTR;

  if(itemList.size() == 0)
    {
      if(index != Q_NULLPTR)
        *index = -1;

      return widget;
    }

  item = itemList.at(0);

  if(index != Q_NULLPTR)
    *index = m_ui.openSpectraListWidget->row(item);

  return m_listItemTicPlotMap.value(item);
}


//! Return the plot widget corresponding to the list item at \p index.
AbstractPlotWidget *
OpenSpectraDlg::plotWidgetAt(int index)
{
  if(index < 0)
    return Q_NULLPTR;

  if(index >= m_ui.openSpectraListWidget->count())
    return Q_NULLPTR;

  // Get the plot widget pointer on the basis of the list widet item at
  // index.
  QListWidgetItem *item = m_ui.openSpectraListWidget->item(index);

  AbstractPlotWidget *widget = m_listItemTicPlotMap.value(item);

  return widget;
}


//! Select the list widget item at \p index.
/*!

  The plot widget corresponding to \p index is returned.

*/
AbstractPlotWidget *
OpenSpectraDlg::selectPlotWidget(int index)
{
  if(index < 0)
    return Q_NULLPTR;

  if(index >= m_ui.openSpectraListWidget->count())
    return Q_NULLPTR;

  // Get the plot widget pointer on the basis of the list widet item at
  // index.
  QListWidgetItem *item = m_ui.openSpectraListWidget->item(index);

  item->setSelected(true);

  AbstractPlotWidget *widget = m_listItemTicPlotMap.value(item);

  return widget;
}


//! Select the list widget item corresponding to TicChromPlotWidget \p widget.
/*!

  The index of the list widget item is returned.

*/
int
OpenSpectraDlg::selectPlotWidget(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    return -1;

  QListWidgetItem *item = m_listItemTicPlotMap.key(widget);

  item->setSelected(true);

  return m_ui.openSpectraListWidget->row(item);
}


//! Toggle selection of the list widget item at \p index
/*!

  The plot widget corresponding to index is returned.

*/
AbstractPlotWidget *
OpenSpectraDlg::togglePlotWidget(int index)
{
  if(index < 0)
    return Q_NULLPTR;

  if(index >= m_ui.openSpectraListWidget->count())
    return Q_NULLPTR;

  // Get the plot widget pointer on the basis of the list widet item at
  // index.
  QListWidgetItem *item = m_ui.openSpectraListWidget->item(index);

  item->setSelected(!item->isSelected());

  AbstractPlotWidget *widget = m_listItemTicPlotMap.value(item);

  return widget;
}


//! Toggle selection of the list widget item corresponding to \p widget.
/*!

  The index of the list widget item corresponding to \p widget is returned.

*/
int
OpenSpectraDlg::togglePlotWidget(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    return -1;

  QListWidgetItem *item = m_listItemTicPlotMap.key(widget);
  item->setSelected(!item->isSelected());

  return m_ui.openSpectraListWidget->row(item);
}


//! Select all list widget items.
int
OpenSpectraDlg::selectAllPlotWidgets()
{
  int iter      = 0;
  int itemCount = m_ui.openSpectraListWidget->count();
  for(; iter < itemCount; ++iter)
    m_ui.openSpectraListWidget->item(iter)->setSelected(true);

  return itemCount;
}


//! Deselect all list widget items.
int
OpenSpectraDlg::deselectAllPlotWidgets()
{
  int iter      = 0;
  int itemCount = m_ui.openSpectraListWidget->count();

  for(; iter < itemCount; ++iter)
    m_ui.openSpectraListWidget->item(iter)->setSelected(false);

  return itemCount;
}


//! Deselect all list widget items but the one at \p index.
/*!

  The plot widget corresponding to item at \p index is returned.

*/
AbstractPlotWidget *
OpenSpectraDlg::deselectAllPlotWidgetsButOne(int index)
{
  AbstractPlotWidget *widget = Q_NULLPTR;
  QListWidgetItem *item      = Q_NULLPTR;

  for(int iter = 0; iter < m_ui.openSpectraListWidget->count(); ++iter)
    {
      item = m_ui.openSpectraListWidget->item(iter);

      if(iter == index)
        {
          widget = m_listItemTicPlotMap.value(item);

          item->setSelected(true);
        }
      else
        item->setSelected(false);
    }

  return widget;
}


//! Deselect all list widget items but the one corresponding to \p widget.
/*!

  The index of the list widget item corresponding to \p widget is returned.

*/
int
OpenSpectraDlg::deselectAllPlotWidgetsButOne(AbstractPlotWidget *widget)
{
  if(widget == Q_NULLPTR)
    return -1;

  AbstractPlotWidget *curWidget = Q_NULLPTR;
  int itemIndex                 = -1;

  for(int iter = 0; iter < m_ui.openSpectraListWidget->count(); ++iter)
    {
      QListWidgetItem *item = m_ui.openSpectraListWidget->item(iter);
      curWidget             = m_listItemTicPlotMap.value(item);

      if(curWidget == widget)
        {
          item->setSelected(true);
          itemIndex = iter;
        }
      else
        item->setSelected(false);
    }

  return itemIndex;
}


QList<AbstractPlotWidget *>
OpenSpectraDlg::selectedPlotWidgets() const
{
  int itemCount = m_ui.openSpectraListWidget->count();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "itemCount =" << itemCount;

  QList<AbstractPlotWidget *> plotWidgetList;

  for(int iter = 0; iter < itemCount; ++iter)
    {
      QListWidgetItem *curItem = m_ui.openSpectraListWidget->item(iter);

      if(curItem->isSelected())
        plotWidgetList.append(m_listItemTicPlotMap.value(curItem));
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Plot widget list size:" << plotWidgetList.size();

  return plotWidgetList;
}


QList<AbstractPlotWidget *>
OpenSpectraDlg::unselectedPlotWidgets() const
{
  int itemCount = m_ui.openSpectraListWidget->count();
  QList<AbstractPlotWidget *> plotWidgetList;

  for(int iter = 0; iter < itemCount; ++iter)
    {
      QListWidgetItem *curItem = m_ui.openSpectraListWidget->item(iter);

      if(!curItem->isSelected())
        plotWidgetList.append(m_listItemTicPlotMap.value(curItem));
    }

  return plotWidgetList;
}


//! Handle keyboard key release events.
/*!

  - T toggle visibility of the multigraph plot graph corresponding to selected
  list widget items.

  - H hide the multigraph plot graph corresponding to selected list widget
  items.

  - S show the multigraph plot graph corresponding to selected list widget
  items.

*/
void
OpenSpectraDlg::keyReleaseEvent(QKeyEvent *event)
{
  // qDebug() << __FILE__ << __LINE__ << "OpenSpectraDlg::keyReleaseEvent"
  //<< "event key:" << event->key();

  if(event->key() == Qt::Key_T)
    actionComboBoxActivated("Toggle visibility");
  else if(event->key() == Qt::Key_H)
    actionComboBoxActivated("Hide");
  else if(event->key() == Qt::Key_S)
    actionComboBoxActivated("Show");
}


} // namespace msXpSmineXpert
