/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QSettings>
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QColor>


/////////////////////// pappsomspp includes


/////////////////////// libXpertMassCore includes


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>


/////////////////////// Local includes
#include "OpenMsRunDataSetsDlg.hpp"
#include "../nongui/MsRunDataSet.hpp"
#include "Application.hpp"
#include "BaseTracePlotCompositeWidget.hpp"
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "DataPlottableNode.hpp"

#include "ui_OpenMsRunDataSetsDlg.h"


namespace MsXpS
{
namespace MineXpert
{


//! Construct an OpenMsRunDataSetsDlg instance.
OpenMsRunDataSetsDlg::OpenMsRunDataSetsDlg(QWidget *parent)
  : QDialog(parent),
    mp_programWindow(static_cast<ProgramWindow *>(parent)),
    m_ui(new ::Ui::OpenMsRunDataSetsDlg)
{
  // if(static_cast<Application *>(qApp)
  //      ->getProgramWindow()
  //      ->mp_openMsRunDataSetsDlg != nullptr)
  //   {
  //     qFatal() << "One instance of OpenMsRunDataSetsDlg exists already.";
  //   }

  if(parent == Q_NULLPTR)
    qFatal() << "Programming error.";

  if(!initialize())
    qFatal() << "Programming error.";
}

//! Destruct \c this OpenMsRunDataSetsDlg instance.
OpenMsRunDataSetsDlg::~OpenMsRunDataSetsDlg()
{
  qDebug() << "In ~OpenMsRunDataSetsDlg().";

  writeSettings();
}

//! Handle the close event.
void
OpenMsRunDataSetsDlg::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  writeSettings();
  hide();
}

//! Write the settings to as to restore the window geometry later.
void
OpenMsRunDataSetsDlg::writeSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  // qDebug() << "Saving window geometry.";

  libXpertMassGui::WindowGeometryManager::save(
    dynamic_cast<const QWidget *>(this), "OpenMsRunDataSetsDlg", settings);

  settings.beginGroup("OpenMsRunDataSetsDlg");

  settings.setValue("visible", isVisible());

  settings.endGroup();
}

//! Read the settings to as to restore the window geometry.
void
OpenMsRunDataSetsDlg::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  // qDebug() << "Restoring window geometry.";

  libXpertMassGui::WindowGeometryManager::restore(this, "OpenMsRunDataSetsDlg", settings);

  settings.beginGroup("OpenMsRunDataSetsDlg");

  setVisible(settings.value("visible").toBool());

  settings.endGroup();
}

//! Initialize the window.
bool
OpenMsRunDataSetsDlg::initialize()
{
  m_ui->setupUi(this);

  setWindowTitle("MineXpert3 - Open MS run data sets");

  createMainMenu();

  readSettings();

  return true;
}

void
OpenMsRunDataSetsDlg::createMainMenu()
{

  if(mp_mainMenu != nullptr)
    delete mp_mainMenu;

  // Create the menu.
  mp_mainMenu = new QMenu(this);

  // Create the menu push button.
  const QIcon main_menu_icon =
    QIcon(":/images/svg/mobile-phone-like-menu-button.svg");
  m_ui->mainMenuPushButton->setIcon(main_menu_icon);

  // Finally set the push button to be the menu...
  m_ui->mainMenuPushButton->setMenu(mp_mainMenu);

  QAction *close_selected_act_p = new QAction(tr("&Close selected..."), this);
  close_selected_act_p->setStatusTip(tr("Close all but selected"));

  connect(close_selected_act_p,
          &QAction::triggered,
          this,
          &OpenMsRunDataSetsDlg::closeSelected);

  mp_mainMenu->addAction(close_selected_act_p);

  QAction *close_all_but_selected_act_p =
    new QAction(tr("&Close all but selected..."), this);
  close_all_but_selected_act_p->setStatusTip(tr("Close all but selected"));

  connect(close_all_but_selected_act_p,
          &QAction::triggered,
          this,
          &OpenMsRunDataSetsDlg::closeAllButSelected);

  mp_mainMenu->addAction(close_all_but_selected_act_p);

  QAction *show_ms_run_data_set_tableview_p =
    new QAction(tr("&Show MS run data set tableview..."), this);
  show_ms_run_data_set_tableview_p->setStatusTip(
    tr("Show MS run data set tableview"));

  connect(show_ms_run_data_set_tableview_p,
          &QAction::triggered,
          this,
          &OpenMsRunDataSetsDlg::showMsRunDataSetTableView);

  mp_mainMenu->addAction(show_ms_run_data_set_tableview_p);

  QAction *change_plotting_color_p =
    new QAction(tr("&Change plotting color..."), this);
  change_plotting_color_p->setStatusTip(tr("Change plotting color"));

  connect(change_plotting_color_p,
          &QAction::triggered,
          this,
          &OpenMsRunDataSetsDlg::changePlottingColor);

  mp_mainMenu->addAction(change_plotting_color_p);
}

void
OpenMsRunDataSetsDlg::show()
{
  readSettings();

  QDialog::show();
}

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

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

void
OpenMsRunDataSetsDlg::changePlottingColor()
{
  // There must be either only one item in the list,selected or not, or multiple
  // items in the list with only one selected.

  MsRunDataSetCstSPtr ms_run_data_set_csp;
  QColor old_color;

  if(!singleSelectedOrUniqueMsRunDataSet(ms_run_data_set_csp, old_color))
    return;

  // We now know which MsRunDataSet is to be handled and what is its current
  // color

  // We need to let the user select a color.

  libXpertMassGui::ColorSelector color_selector;

  QColor new_color = color_selector.chooseColor();

  if(!new_color.isValid())
    return;

  // At this point we know the new color. We need to trace back the various
  // plots (graphs or color maps) that descend from the ms run data set.

  const DataPlottableTree &data_plottable_tree =
    mp_programWindow->getDataPlottableTree();

  std::vector<DataPlottableNode *> data_plottable_nodes =
    // search the nodes recursively : true;
    const_cast<DataPlottableTree &>(data_plottable_tree)
      .findNodes(ms_run_data_set_csp, true);

  // Now, for each node, ask that the new color be used.

  for(auto &&node_p : data_plottable_nodes)
    {
      BasePlotCompositeWidget *composite_widget_p = node_p->getPlotWidget();

      QCPAbstractPlottable *plottable_p = node_p->getPlottable();

      composite_widget_p->setPlottingColor(plottable_p, new_color);
      composite_widget_p->setSampleNameColor(new_color);
    }


  // At this point change the color of the list widget item.

  QListWidgetItem *item_p = listWidgetItemForMsRunDataSet(ms_run_data_set_csp);

  // Release the color that will be made available to other data sets.

  QBrush brush(Qt::SolidPattern);
  brush = item_p->foreground();

  color_selector.releaseColor(old_color);

  // Update the brush to use the new color.
  brush.setColor(new_color);

  // The text of the item
  item_p->setForeground(brush);

  // The icon of the item
  QPixmap rectangle(15, 15);
  rectangle.fill(new_color);
  QIcon icon(rectangle);

  item_p->setIcon(icon);
}

QColor
OpenMsRunDataSetsDlg::colorForMsRunDataSet(
  MsRunDataSetCstSPtr &ms_run_data_set_csp) const
{
  // Get the list widget item out of the map using the widget.

  std::map<MsRunDataSetCstSPtr, QListWidgetItem *>::const_iterator result =
    std::find_if(
      m_msRunDataSetListWidgetItemMap.begin(),
      m_msRunDataSetListWidgetItemMap.end(),
      [ms_run_data_set_csp](
        const std::pair<MsRunDataSetCstSPtr, QListWidgetItem *> &item) {
        return item.first == ms_run_data_set_csp;
      });

  if(result == m_msRunDataSetListWidgetItemMap.end())
    {
      qFatal() << "Cannot reach this point.";
    }

  // Release the color that will be made available to other data sets.

  QBrush brush(Qt::SolidPattern);
  brush = result->second->foreground();

  QColor color;
  color = brush.color();

  return color;
}

//! 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.

*/
void
OpenMsRunDataSetsDlg::newOpenMsRunDataSet(
  MsRunDataSetCstSPtr &ms_run_data_set_csp, QColor color)
{
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Programming error.";

  // qDebug() << "Creating new item in OpenMsRunDataSetsDlg for sample name:"
  //<< ms_run_data_set_csp->getMsRunId()->getSampleName();

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

  // Create an item with the sample name of the ms run data set that was
  // loaded from file. Indeed, a file might contain more than one run. The
  // element that differentiates them is the sample name, not the file name.
  QListWidgetItem *item = new QListWidgetItem(
    icon, ms_run_data_set_csp->getMsRunId()->getSampleName());

  // 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 widget.
  int row = m_ui->openMsRunDataSetsListWidget->count();
  m_ui->openMsRunDataSetsListWidget->insertItem(row, item);

  using Pair        = std::pair<MsRunDataSetCstSPtr, QListWidgetItem *>;
  using Map         = std::map<MsRunDataSetCstSPtr, QListWidgetItem *>;
  using MapIterator = Map::iterator;

  // Remember that when a ms run data set is proactively closed by the
  // user, this step needs to be reversed (-- ref use_count).

  std::pair<MapIterator, bool> res =
    m_msRunDataSetListWidgetItemMap.insert(Pair(ms_run_data_set_csp, item));

  if(!res.second)
    {
      // One ms run data set was already set, this is a serious error.

      qFatal() << "Programming error.";
    }

  // Select the lastly added item.

  m_ui->openMsRunDataSetsListWidget->setCurrentItem(item);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();
}

QListWidgetItem *
OpenMsRunDataSetsDlg::listWidgetItemForMsRunDataSet(
  MsRunDataSetCstSPtr &ms_run_data_set_csp)
{
  // Get the list widget item out of the map using the widget.

  std::map<MsRunDataSetCstSPtr, QListWidgetItem *>::iterator result =
    std::find_if(
      m_msRunDataSetListWidgetItemMap.begin(),
      m_msRunDataSetListWidgetItemMap.end(),
      [ms_run_data_set_csp](
        const std::pair<MsRunDataSetCstSPtr, QListWidgetItem *> &item) {
        return item.first == ms_run_data_set_csp;
      });

  if(result == m_msRunDataSetListWidgetItemMap.end())
    return nullptr;
  else
    return result->second;
}

MsRunDataSetCstSPtr
OpenMsRunDataSetsDlg::msRunDataSetForListWidgetItem(int index)
{
  QListWidgetItem *list_widget_item_p =
    m_ui->openMsRunDataSetsListWidget->item(index);

  if(list_widget_item_p == nullptr)
    return nullptr;

  return msRunDataSetForListWidgetItem(list_widget_item_p);
}

MsRunDataSetCstSPtr
OpenMsRunDataSetsDlg::msRunDataSetForListWidgetItem(
  QListWidgetItem *list_widget_item_p) const
{
  // Get the MsRunDataSetSPtr out of the map using the widget.

  std::map<MsRunDataSetCstSPtr, QListWidgetItem *>::const_iterator result =
    std::find_if(
      m_msRunDataSetListWidgetItemMap.begin(),
      m_msRunDataSetListWidgetItemMap.end(),
      [list_widget_item_p](
        const std::pair<MsRunDataSetCstSPtr, QListWidgetItem *> &item) {
        return item.second == list_widget_item_p;
      });

  if(result == m_msRunDataSetListWidgetItemMap.end())
    return nullptr;

  return result->first;
}

void
OpenMsRunDataSetsDlg::removeMsRunDataSet(
  MsRunDataSetCstSPtr &ms_run_data_set_csp)
{
  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // Get the list widget item out of the map using the widget.

  QListWidgetItem *list_widget_item_p =
    listWidgetItemForMsRunDataSet(ms_run_data_set_csp);

  if(list_widget_item_p == nullptr)
    {
      qFatal() << "Cannot reach this point.";
    }

  // Release the color that will be made available to other data sets.

  QBrush brush(Qt::SolidPattern);
  brush = list_widget_item_p->foreground();

  QColor color;
  color = brush.color();
  libXpertMassGui::ColorSelector::releaseColor(color);

  m_msRunDataSetListWidgetItemMap.erase(ms_run_data_set_csp);

  delete list_widget_item_p;

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();
}

void
OpenMsRunDataSetsDlg::closeItems(QList<QListWidgetItem *> widget_items)
{
  for(int iter = 0; iter < widget_items.size(); ++iter)
    {
      QListWidgetItem *current_widget_item_p = widget_items.at(iter);

      MsRunDataSetCstSPtr ms_run_data_set_csp =
        msRunDataSetForListWidgetItem(current_widget_item_p);

      if(ms_run_data_set_csp == nullptr)
        {
          qFatal() << "Cannot reach this point.";
        }

      // qDebug() << "Going to request the removal of ms run data set:"
      //<< ms_run_data_set_csp.get();

      // At this point, we can start removing this data set from all the
      // places it is still stored in.

      // Now continue with all the remaining tasks.
      mp_programWindow->msRunDataSetRemovalRequested(ms_run_data_set_csp);
    }
}

void
OpenMsRunDataSetsDlg::closeSelected(void)
{
  // qDebug();

  QList<QListWidgetItem *> selected_item_list =
    m_ui->openMsRunDataSetsListWidget->selectedItems();

  closeItems(selected_item_list);
}

void
OpenMsRunDataSetsDlg::closeAllButSelected(void)
{
  QList<QListWidgetItem *> unselected_item_list = unselectedItems();
  closeItems(unselected_item_list);
}

void
OpenMsRunDataSetsDlg::showMsRunDataSetTableView()
{
  // qDebug();

  // We want to show the tree view window for the ms run data set that is
  // selected.

  QList<QListWidgetItem *> selected_item_list =
    m_ui->openMsRunDataSetsListWidget->selectedItems();

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

      QColor color = current_widget_item_p->foreground().color();

      // Get the MsRunDataSetSPtr out of the map using the widget.

      MsRunDataSetCstSPtr ms_run_data_set_csp =
        msRunDataSetForListWidgetItem(current_widget_item_p);

      if(ms_run_data_set_csp == nullptr)
        {
          qFatal() << "Cannot reach this point.";
        }

      std::const_pointer_cast<MsRunDataSet>(ms_run_data_set_csp);

      // qDebug() << "Going to call show ms run data set tree view window.";

      mp_programWindow->showMsRunDataSetTableViewWnd(ms_run_data_set_csp,
                                                     color);
    }
}

QList<QListWidgetItem *>
OpenMsRunDataSetsDlg::unselectedItems()
{
  int item_count = m_ui->openMsRunDataSetsListWidget->count();

  QList<QListWidgetItem *> unselected_items;

  for(int iter = 0; iter < item_count; ++iter)
    {
      QListWidgetItem *current_widget_item =
        m_ui->openMsRunDataSetsListWidget->item(iter);

      if(!current_widget_item->isSelected())
        unselected_items.append(current_widget_item);
    }

  return unselected_items;
}

std::size_t
OpenMsRunDataSetsDlg::msRunDataSetCount() const
{
  return m_ui->openMsRunDataSetsListWidget->count();
}

void
OpenMsRunDataSetsDlg::selectFirstMsRunDataSet()
{

  if(!m_ui->openMsRunDataSetsListWidget->count())
    return;

  m_ui->openMsRunDataSetsListWidget->setCurrentRow(0);
}

bool
OpenMsRunDataSetsDlg::singleSelectedOrUniqueMsRunDataSet(
  MsRunDataSetCstSPtr &ms_run_data_set_csp, QColor &color) const
{
  // We want to return only *one* ms run data set. Either there is only one data
  // set in the list widget, and we return that one, or there are more than one
  // and we return the single one that is selected. Otherwise we rerturn false;

  // First, get the list of selected items, there should be one single
  // item selected or only one item (selected or not).

  QList<QListWidgetItem *> item_list;

  // It can perfectly be that the user wants to use features of the program
  // without loading any mass spectral data file (like working with the IsoSpec
  // framework).

  int list_widget_count = m_ui->openMsRunDataSetsListWidget->count();

  // qDebug() << "Number of list widget items:" << list_widget_count;

  if(!list_widget_count)
    {
      // Let the caller know that we did not modify the arguments passed to this
      // function.

      return false;
    }

  if(list_widget_count == 1)
    item_list.append(m_ui->openMsRunDataSetsListWidget->item(0));
  else
    item_list = m_ui->openMsRunDataSetsListWidget->selectedItems();

  if(item_list.size() != 1)
    {
      return false;
    }

  // At this point we can fulfill the work.

  ms_run_data_set_csp = msRunDataSetForListWidgetItem(item_list.first());
  color               = colorForMsRunDataSet(ms_run_data_set_csp);

  return true;
}

std::vector<MsRunDataSetCstSPtr>
OpenMsRunDataSetsDlg::allMsRunDataSets() const
{
  std::vector<MsRunDataSetCstSPtr> vector_of_ms_run_data_set_csp;

  QList<QListWidgetItem *> item_list =
    m_ui->openMsRunDataSetsListWidget->findItems(".*",
                                                 Qt::MatchRegularExpression);

  for(int iter = 0; iter < item_list.size(); ++iter)
    {
      vector_of_ms_run_data_set_csp.push_back(
        msRunDataSetForListWidgetItem(item_list.at(iter)));
    }

  return vector_of_ms_run_data_set_csp;
}

std::vector<MsRunDataSetCstSPtr>
OpenMsRunDataSetsDlg::allSelectedOrUniqueMsRunDataSets() const
{
  std::vector<MsRunDataSetCstSPtr> vector_of_ms_run_data_set_csp;

  QList<QListWidgetItem *> selected_item_list =
    m_ui->openMsRunDataSetsListWidget->selectedItems();

  for(int iter = 0; iter < selected_item_list.size(); ++iter)
    {
      vector_of_ms_run_data_set_csp.push_back(
        msRunDataSetForListWidgetItem(selected_item_list.at(iter)));
    }

  if(!vector_of_ms_run_data_set_csp.size())
    {
      if(m_ui->openMsRunDataSetsListWidget->count() == 1)
        vector_of_ms_run_data_set_csp.push_back(msRunDataSetForListWidgetItem(
          m_ui->openMsRunDataSetsListWidget->item(0)));
    }

  return vector_of_ms_run_data_set_csp;
}

bool
OpenMsRunDataSetsDlg::msRunDataSetFromSampleName(
  const QString &sample_name,
  MsRunDataSetCstSPtr &ms_run_data_set_csp,
  QColor &color) const
{

  // qDebug() << "The sample name searched for:" << sample_name;

  // qDebug() << "Number of items:" <<
  // m_ui->openMsRunDataSetsListWidget->count();

  QList<QListWidgetItem *> matched_items =
    m_ui->openMsRunDataSetsListWidget->findItems(sample_name, Qt::MatchExactly);

  if(!matched_items.size())
    {
      // qDebug() << "no matched items, returning false.";

      return false;
    }

  ms_run_data_set_csp = msRunDataSetForListWidgetItem(matched_items.first());
  color               = colorForMsRunDataSet(ms_run_data_set_csp);

  return true;
}

bool
OpenMsRunDataSetsDlg::colorFromMsRunDataSet(
  MsRunDataSetCstSPtr ms_run_data_set_csp, QColor &color)
{
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  color = colorForMsRunDataSet(ms_run_data_set_csp);

  return true;
}


} // namespace MineXpert

} // namespace MsXpS
