/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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
#include <cmath>


/////////////////////// Qt includes
#include <QColor>
#include <QDebug>
#include <QIcon>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/msrun/msrundatasettreenode.h>
#include <pappsomspp/core/msrun/msrundatasettree.h>


/////////////////////// Local includes
#include "MsRunDataSetTableViewModel.hpp"
#include "MsRunDataSetTableViewItem.hpp"

namespace MsXpS
{
namespace MineXpert
{

std::map<int, QString> massSpecDataViewColumnsMap = {

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL), "MS level"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_SPECTRUM_INDEX), "Index"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_RT), "Rt"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_DT), "Dt"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_PRECURSOR_INDEX),
   "Prec. index"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_PRECURSOR_MZ), "Prec. m/z"},

  {static_cast<int>(MassSpecDataViewColumns::COLUMN_PRECURSOR_Z), "Prec. z"}};

MsRunDataSetTableViewModel::MsRunDataSetTableViewModel(
  pappso::MsRunDataSetTreeCstSPtr ms_run_data_set_tree_csp, QObject *parent)
  : QAbstractItemModel(parent), mcsp_msRunDataSetTree(ms_run_data_set_tree_csp)
{
  setupModelData(mcsp_msRunDataSetTree);
}

MsRunDataSetTableViewModel::~MsRunDataSetTableViewModel()
{
  delete mp_rootItem;
}

int
MsRunDataSetTableViewModel::columnCount(const QModelIndex &parent) const
{
  if(parent.isValid())
    return static_cast<MsRunDataSetTableViewItem *>(parent.internalPointer())
      ->columnCount();
  else
    return mp_rootItem->columnCount();
}

QVariant
MsRunDataSetTableViewModel::data(const QModelIndex &index, int role) const
{
  if(!index.isValid())
    return QVariant();

  MsRunDataSetTableViewItem *view_item_p =
    static_cast<MsRunDataSetTableViewItem *>(index.internalPointer());

  if(role == Qt::BackgroundRole)
    {
      QVariant ms_level_variant = view_item_p->data(
        static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL));

      bool ok = false;

      int ms_level_int = ms_level_variant.toInt(&ok);

      if(!ok)
        qFatal() << "Could not convert QVariant to int.";

      if(ms_level_int == 1 && view_item_p->childCount() == 0)
        return QVariant();

      switch(ms_level_int)
        {
          case 1:
            // return QColor(Qt::darkGreen);
            return QColor("#b9d3b6");
          case 2:
            // return QColor(Qt::darkCyan);
            return QColor("#d5bb72");
          case 3:
            // return QColor(Qt::darkCyan);
            return QColor("#fc853b");
          case 4:
            // return QColor(Qt::darkCyan);
            return QColor("#fa5023");
          case 5:
            // return QColor(Qt::darkCyan);
            return QColor("#f252f7");
          case 6:
            // return QColor(Qt::darkCyan);
            return QColor("#893dae");
          default:
            return QVariant();
        }
    }
  else if(role == Qt::ForegroundRole)
    {
      QVariant ms_level_variant = view_item_p->data(
        static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL));

      bool ok = false;

      int ms_level_int = ms_level_variant.toInt(&ok);

      if(!ok)
        qFatal() << "Programming error.";

      if(ms_level_int == 1 && view_item_p->childCount() == 0)
        return QVariant();

      switch(ms_level_int)
        {
          case 1:
            return QColor(Qt::black);
          case 2:
            return QColor(Qt::black);
          default:
            return QVariant();
        }
    }
  else if(role == Qt::DecorationRole)
    {
      // We only decorate with an icon the ms level data.

      int index_column = index.column();

      if(index_column ==
         static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL))
        {
          QVariant ms_level_variant = view_item_p->data(
            static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL));

          bool ok          = false;
          int ms_level_int = ms_level_variant.toInt(&ok);

          if(!ok)
            qFatal() << "Programming error";

          switch(ms_level_int)
            {
              case 1:
                return QIcon(":/images/svg/ms-level-1.svg");
              case 2:
                return QIcon(":/images/svg/ms-level-2.svg");
              case 3:
                return QIcon(":/images/svg/ms-level-3.svg");
              default:
                return QVariant();
            }
        }
      else
        {
          return QVariant();
        }
    }
  else if(role == Qt::DisplayRole) // the string text to be printed in the cell
    {
      // When we are handling the ms level data, we do not return any text

      int index_column = index.column();

      if(index_column ==
         static_cast<int>(MassSpecDataViewColumns::COLUMN_MS_LEVEL))
        {
          return QString("");
        }
      else
        {
          return view_item_p->data(index.column());
        }
    }

  // There are other role_s that we do not handle specifically.

  return QVariant();
}

Qt::ItemFlags
MsRunDataSetTableViewModel::flags(const QModelIndex &index) const
{
  if(!index.isValid())
    return Qt::NoItemFlags;

  return QAbstractItemModel::flags(index);
}

QVariant
MsRunDataSetTableViewModel::headerData(int section,
                                       Qt::Orientation orientation,
                                       int role) const
{
  if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
    return mp_rootItem->data(section);

  return QVariant();
}

QModelIndex
MsRunDataSetTableViewModel::index(int row,
                                  int column,
                                  const QModelIndex &parent) const
{
  if(!hasIndex(row, column, parent))
    return QModelIndex();

  MsRunDataSetTableViewItem *parentItem;

  if(!parent.isValid())
    parentItem = mp_rootItem;
  else
    parentItem =
      static_cast<MsRunDataSetTableViewItem *>(parent.internalPointer());

  MsRunDataSetTableViewItem *childItem = parentItem->child(row);

  if(childItem)
    {
      return createIndex(row, column, childItem);
    }

  return QModelIndex();
}

QModelIndex
MsRunDataSetTableViewModel::parent(const QModelIndex &index) const
{
  if(!index.isValid())
    return QModelIndex();

  MsRunDataSetTableViewItem *childItem =
    static_cast<MsRunDataSetTableViewItem *>(index.internalPointer());
  MsRunDataSetTableViewItem *parentItem = childItem->parentItem();

  if(parentItem == mp_rootItem)
    return QModelIndex();

  return createIndex(parentItem->row(), 0, parentItem);
}

int
MsRunDataSetTableViewModel::rowCount(const QModelIndex &parent) const
{
  MsRunDataSetTableViewItem *parentItem;
  if(parent.column() > 0)
    return 0;

  if(!parent.isValid())
    parentItem = mp_rootItem;
  else
    parentItem =
      static_cast<MsRunDataSetTableViewItem *>(parent.internalPointer());

  return parentItem->childCount();
}

void
MsRunDataSetTableViewModel::setupModelData(
  pappso::MsRunDataSetTreeCstSPtr ms_run_data_set_tree_csp)
{

  // Seed what will become the headers.
  QList<QVariant> column_data;

  for(auto &item : massSpecDataViewColumnsMap)
    {
      if(item.first != static_cast<int>(MassSpecDataViewColumns::COLUMN_LAST))
        column_data << item.second;
    }

  // This is going to be the parent of all the other items.
  mp_rootItem = new MsRunDataSetTableViewItem(column_data);

  // We need to iterate in the MsRunDataSetTree's index/node map and for each
  // map item create a corresponding table view item.

  // See below for the former use of this variable.
  // std::size_t last_precursor_index;

  for(auto &map_item : ms_run_data_set_tree_csp->getIndexNodeMap())
    {
      auto index  = map_item.first;
      auto node_p = map_item.second;

      pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
        node_p->getQualifiedMassSpectrum();

      unsigned int msLevel = qualified_mass_spectrum_csp->getMsLevel();

      // qDebug() << "msLevel:" << msLevel;

      double rt = qualified_mass_spectrum_csp->getRtInMinutes();

      // Handle the mobility data, either drift time or 1/K0 depending
      // on the file type.
      double ion_mobility_value = -1;

      // Get the format of the file, because that will condition the
      // way we provide mobility values.

      pappso::Enums::MsDataFormat file_format =
        qualified_mass_spectrum_csp->getMassSpectrumId()
          .getMsRunIdCstSPtr()
          ->getMsDataFormat();

      if(file_format == pappso::Enums::MsDataFormat::brukerTims)
        {
          QVariant ion_mobility_variant_value =
            qualified_mass_spectrum_csp->getParameterValue(
              pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

          if(ion_mobility_variant_value.isValid())
            {
              bool ok            = false;
              ion_mobility_value = ion_mobility_variant_value.toDouble(&ok);
              if(!ok)
                {
                  qFatal() << "Failed to convert QVariant 1/K0 value to double.";
                }
            }
        }
      else
        {
          ion_mobility_value =
            qualified_mass_spectrum_csp->getDtInMilliSeconds();
        }

      // The precursor data

      std::size_t precursor_index =
        qualified_mass_spectrum_csp->getPrecursorSpectrumIndex();

      // We want to present precursor ion data sorted using the mz values, and
      // we need to maintain coherence for the charge values.
      QString precursor_mz_string =
        qualified_mass_spectrum_csp->getMzSortedPrecursorDataMzValuesAsString(
          ";");

      // qDebug() << "The precursors m/z values are:" << precursor_mz_string;

      QString precursor_z_string =
        qualified_mass_spectrum_csp
          ->getMzSortedPrecursorDataChargeValuesAsString(";");

      // qDebug() << "The precursors z values are:" << precursor_z_string;

      QList<QVariant> column_data;

      column_data << QString::number(msLevel) << QString::number(index)
                  << QString::number(rt, 'f', 8)

                  // Only show the drift time value if experiment is mobility.
                  << (ion_mobility_value == -1
                        ? "-1"
                        : QString::number(ion_mobility_value, 'f', 8));

      // qDebug() << "Recorded ms level:"
      //<< column_data[static_cast<int>(
      // MassSpecDataViewColumns::COLUMN_MS_LEVEL)];

      if(msLevel == 1)
        {

          // This node contains a mass spectrum that was acquired as a full scan
          // mass spectrum, not a fragmentation spectrum.

          // Not applicable to this situation: precursor index, mz and
          // z.
          column_data << QString("") << QString("") << QString("");

          // Store the precursor MS1 index for later sanity checks.
          // last_precursor_index = index;
        }
      else
        {

          // This node contains a fragmentation spectrum. We need to insert the
          // new item with its parent being the precursor ion's item.

          column_data << (int)precursor_index << precursor_mz_string
                      << precursor_z_string;

          // We now need to get the parent item that has its index
          // identical
          // to precursor_index.

          // FIXME for the moment we kill this fatal call because we want to
          // test loading of the Bruker data.

#if 0
          if(precursor_index != last_precursor_index)
            // qFatal() << "Cannot be that precursor_index != last_precursor_index";
            qDebug()
              << "Attention, the MSn mass spectrum has no MS(n-1) "
                 "precursor spectrum. This might be a MSn-only mass data file"
                 "or Bruker data.";
#endif
        }

      // Now that we have finalized the column data, we can append the new child
      // item. Note that we store there the pointer to the mass spectrum for
      // later use for the integrations (see the wnd class).

      MsRunDataSetTableViewItem *view_item_p = new MsRunDataSetTableViewItem(
        column_data, qualified_mass_spectrum_csp, mp_rootItem);


      mp_rootItem->appendChild(view_item_p);
    }
}

} // namespace MineXpert
} // namespace MsXpS
