/* 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
 */


/////////////////////// Qt includes
#include <QMessageBox>
#include <QFileDialog>
#include <QSettings>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/CrossLink.hpp>
#include <MsXpS/libXpertMassCore/Monomer.hpp>
#include <MsXpS/libXpertMassCore/Ionizer.hpp>


/////////////////////// Local includes
#include "CompositionsDlg.hpp"

namespace MsXpS
{

namespace MassXpert
{


CompositionsDlg::CompositionsDlg(SequenceEditorWnd *editor_wnd_p,
                                 libXpertMassCore::PolymerQSPtr polymer_sp,
                                 /* no libXpertMassCore::PolChemDef **/
                                 const QString &config_settings_file_path,
                                 const QString &application_name,
                                 const QString &description,
                                 const libXpertMassCore::CalcOptions &calc_options,
                                 const libXpertMassCore::Ionizer &ionizer)
  : AbstractSeqEdWndDependentDlg(editor_wnd_p,
                                 polymer_sp,
                                 nullptr /*polChemDef*/,
                                 config_settings_file_path,
                                 "CompositionsDlg",
                                 application_name,
                                 description),
    mref_calcOptions(calc_options),
    m_ionizer(ionizer)
{
  if(polymer_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(!initialize())
    qFatal()
      << "Programming error. Failed to initialize the dialog window"
      << m_wndTypeName.toLatin1().data();

  setWindowTitle(QString("%1 - %2").arg(application_name).arg(description));
}


CompositionsDlg::~CompositionsDlg()
{
  writeSettings();

  delete mpa_compositionTreeViewModel;
  delete mpa_compositionProxyModel;
  delete mpa_resultsString;

  freeMonomerList();
}


void
CompositionsDlg::readSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
  settings.endGroup();
}


void
CompositionsDlg::writeSettings()
{

  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.setValue("splitter", m_ui.splitter->saveState());
  settings.endGroup();
}

bool
CompositionsDlg::initialize()
{
  m_ui.setupUi(this);

  setWindowIcon(qApp->windowIcon());

  // Update the window title because the window title element in m_ui might be
  // either erroneous or empty.
  setWindowTitle(
    QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription));

  updateSelectionData();

  setupTreeView();

  // The results-exporting menus. ////////////////////////////////

  QStringList comboBoxItemList;

  comboBoxItemList << tr("To Clipboard") << tr("To File") << tr("Select File");

  m_ui.exportResultsComboBox->addItems(comboBoxItemList);

  connect(m_ui.exportResultsComboBox,
          SIGNAL(activated(int)),
          this,
          SLOT(exportResults(int)));

  mpa_resultsString = new QString();

  readSettings();

  connect(m_ui.updateSelectionDataPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(updateSelectionData()));

  connect(m_ui.monomericPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(monomericComposition()));

  connect(m_ui.elementalPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(elementalComposition()));

  return true;
}


void
CompositionsDlg::updateSelectionData()
{
  // The selection might exist as a list of region selections.

  if(mp_editorWnd->mpa_editorGraphicsView->selectionIndices(
       m_indexRangeCollection))
    {
      m_ui.selectionCoordinatesLineEdit->setText(
        m_indexRangeCollection.positionsAsText());

      m_ui.selectedSequenceRadioButton->setChecked(true);
    }
  else
    {
      m_ui.selectionCoordinatesLineEdit->setText("");

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

  m_ui.monomericPushButton->setFocus();
}


bool
CompositionsDlg::fetchValidateInputData()
{
  qsizetype polymer_size = (qsizetype)msp_polymer->size();

  if(!m_ui.selectedSequenceRadioButton->isChecked())
    {
      m_indexRangeCollection.setIndexRange(
        libXpertMassCore::IndexRange(0, polymer_size - 1));
    }

  // Make sure the sequence still has a number of residues
  // compatible with the current m_indexRangeCollection. this because the
  // sequence might be edited between opening this dialog window and
  // actually using it.

  foreach(const libXpertMassCore::IndexRange *item,
          m_indexRangeCollection.getRangesCstRef())
    {
      if(item->m_start >= polymer_size || item->m_stop >= polymer_size)
        {
          QMessageBox::warning(0,
                               tr("MassXpert3 - Compositions"),
                               tr("Sequence selection data are no more valid.\n"
                                  "Please update these data."),
                               QMessageBox::Ok);
          return false;
        }
    }

  // Update the ionization status directly from the sequence editor window,
  // which, in fact, will ask the Polymer its ionizer.

  libXpertMassCore::Ionizer ionizer = msp_polymer->getIonizerCstRef();

  // We also want to know if the cross-links should be taken into account.

  if(static_cast<bool>(mref_calcOptions.getMonomerEntities() &
                       libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER))
    {
      // If the whole sequence is dealt with, then by definition all
      // the cross-links are encompassed by the sequence.

      if(!m_ui.selectedSequenceRadioButton->isChecked())
        {
          m_ui.incompleteCrossLinkWarningLabel->setText(
            tr("Incomplete cross-links: %1").arg(0));
        }
      else
        {

          int crossLinkPartial = 0;
          std::size_t in_count;
          std::size_t out_count;

          for(const libXpertMassCore::CrossLinkSPtr &cross_link_sp :
              msp_polymer->getCrossLinksCstRef())
            {
              libXpertMassCore::Enums::CrossLinkEncompassed ret =
                cross_link_sp->isEncompassedByIndexRangeCollection(
                  m_indexRangeCollection, in_count, out_count);

              if(ret == libXpertMassCore::Enums::CrossLinkEncompassed::FULLY)
                {
                  // 		qDebug() << __FILE__ << __LINE__
                  // 			  << "CrossLink at iter:" << iter
                  // 			  << "is fully encompassed";
                }
              else if(ret ==
                      libXpertMassCore::Enums::CrossLinkEncompassed::PARTIALLY)
                {
                  // 		qDebug() << __FILE__ << __LINE__
                  // 			  << "CrossLink at iter:" << iter
                  // 			  << "is partially encompassed";

                  ++crossLinkPartial;
                }
              else
                {
                  // 		qDebug() << __FILE__ << __LINE__
                  // 			  << "CrossLink at iter:" << iter
                  // 			  << "is not encompassed at all";
                }
            }

          m_ui.incompleteCrossLinkWarningLabel->setText(
            tr("Incomplete cross-links: %1").arg(crossLinkPartial));
        }
    }
  else
    {
      m_ui.incompleteCrossLinkWarningLabel->setText(
        tr("Not accounting for cross-links"));
    }

  return true;
}


void
CompositionsDlg::monomericComposition()
{
  if(!fetchValidateInputData())
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - Compositions"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }

  libXpertMassCore::Monomer *fakeMonomer = 0;

  // Empty the treeview. This will also remove all the monomer items
  // in the m_monomerList.
  mpa_compositionTreeViewModel->removeAll();

  setCursor(Qt::WaitCursor);

  // Iterate in the sequence and count all the occurrences of the
  // different monomer that comprise the sequence.

  if(mpa_compositionTreeViewModel)
    {
      delete mpa_compositionTreeViewModel;
      mpa_compositionTreeViewModel = 0;
    }

  if(mpa_compositionProxyModel)
    {
      delete mpa_compositionProxyModel;
      mpa_compositionProxyModel = 0;
    }

  qDebug() << "Determination of the monomeric composition for range:"
           << m_indexRangeCollection.indicesAsText();


  foreach(const libXpertMassCore::IndexRange *item,
          m_indexRangeCollection.getRangesCstRef())
    {
      for(qsizetype iter = item->m_start; iter < item->m_stop + 1; ++iter)
        {
          bool processed = false;

          const libXpertMassCore::MonomerCstSPtr monomer_csp =
            msp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
          Q_ASSERT(monomer_csp);

          // Check if monomer by same name is not already in our list of
          // fake monomers.

          for(int kter = 0; kter < m_monomerList.size(); ++kter)
            {
              fakeMonomer = m_monomerList.at(kter);
              Q_ASSERT(fakeMonomer);

              if(monomer_csp->getName() == fakeMonomer->getName())
                {
                  libXpertMassCore::Prop *prop = fakeMonomer->prop("MONOMER_COUNT");

                  // The fake monomer MUST have a count prop !
                  Q_ASSERT(prop);

                  int *count = new int(*static_cast<const int *>(prop->data()));

                  ++(*count);
                  prop->setData(count);

                  if(monomer_csp->isModified())
                    {
                      // The monomer in the sequence is modified. Let the
                      // fake monomer know it.
                      libXpertMassCore::Prop *prop =
                        fakeMonomer->prop("MODIF_COUNT");

                      // The fake monomer might not have a modif prop.
                      if(prop)
                        {
                          int *count =
                            new int(*static_cast<const int *>(prop->data()));
                          ++(*count);
                          prop->setData(count);
                        }
                      else
                        {
                          libXpertMassCore::IntProp *prop =
                            new libXpertMassCore::IntProp("MODIF_COUNT", 1);
                          fakeMonomer->appendProp(prop);
                        }
                    }

                  processed = true;

                  break;
                }
            }
          // End of
          // for (int kter = 0; kter < m_monomerList.size(); ++kter)

          // Did we find a fake monomer, thus processing the currently
          // iterated sequence monomer or not ? If not we still have to do
          // all the work.

          if(!processed)
            {
              fakeMonomer = new libXpertMassCore::Monomer(*monomer_csp);

              libXpertMassCore::IntProp *prop =
                new libXpertMassCore::IntProp("MONOMER_COUNT", 1);
              fakeMonomer->appendProp(prop);

              if(monomer_csp->isModified())
                {
                  // The monomer in the sequence is modified. Let the
                  // fake monomer know it.

                  libXpertMassCore::IntProp *prop =
                    new libXpertMassCore::IntProp("MODIF_COUNT", 1);
                  fakeMonomer->appendProp(prop);
                }

              m_monomerList.append(fakeMonomer);
            }
        }
      // End of
      // for (int iter = startIndex ; iter < endIndex + 1; ++iter)

      setupTreeView();

      prepareResultsTxtString(CompositionsTarget::MONOMERIC);

      setCursor(Qt::ArrowCursor);
    }
}


void
CompositionsDlg::elementalComposition()
{
  // For each monomer in the sequence, get the formula and account it.

  if(!fetchValidateInputData())
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - Compositions"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }
  // We let the polymer use its own Ionizer below by not passing
  // that argument.
  QString composition = msp_polymer->elementalComposition(
    m_indexRangeCollection, mref_calcOptions /*, polymer's ionizer*/);
  m_ui.elementalCompositionLineEdit->setText(composition);

  prepareResultsTxtString(CompositionsTarget::ELEMENTAL);

  setCursor(Qt::ArrowCursor);
}


void
CompositionsDlg::setupTreeView()
{
  // Model stuff all thought for sorting.
  mpa_compositionTreeViewModel =
    new CompositionTreeViewModel(&m_monomerList, this);

  mpa_compositionProxyModel = new CompositionTreeViewSortProxyModel(this);
  mpa_compositionProxyModel->setSourceModel(mpa_compositionTreeViewModel);

  m_ui.compositionTreeView->setModel(mpa_compositionProxyModel);
  m_ui.compositionTreeView->setParentDlg(this);
  mpa_compositionTreeViewModel->setTreeView(m_ui.compositionTreeView);
}


void
CompositionsDlg::freeMonomerList()
{
  while(!m_monomerList.isEmpty())
    delete m_monomerList.takeFirst();
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
CompositionsDlg::exportResults(int index)
{
  // Remember that we had set up the combobox with the following strings:
  // << tr("To &Clipboard")
  // << tr("To &File")
  // << tr("&Select File");

  if(index == 0)
    {
      exportResultsClipboard();
    }
  else if(index == 1)
    {
      exportResultsFile();
    }
  else if(index == 2)
    {
      selectResultsFile();
    }
  else
    Q_ASSERT(0);
}


void
CompositionsDlg::prepareResultsTxtString(CompositionsTarget target)
{
  mpa_resultsString->clear();

  *mpa_resultsString += QObject::tr(
    "# \n"
    "# -------------\n"
    "# Compositions: \n"
    "# -------------\n");

  if(target == CompositionsTarget::ELEMENTAL)
    {
      *mpa_resultsString += QObject::tr("\nIonization rule:\n");
      *mpa_resultsString +=
        QObject::tr("Formula: %1 - ")
          .arg(m_ionizer.getFormulaRef().getActionFormula());
      *mpa_resultsString +=
        QObject::tr("Charge: %1 - ").arg(m_ionizer.getNominalCharge());
      *mpa_resultsString +=
        QObject::tr("Level: %1\n").arg(m_ionizer.getLevel());
      *mpa_resultsString += QObject::tr("\nCalculation options:\n");

      bool with_modif_entities =
        (static_cast<int>(mref_calcOptions.getMonomerEntities()) &
         static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::MODIF));

      // We want a delimited sequence with indication of the
      // different sequence regions for which the composition was
      // determined, thus the true below.

      QString sequence = msp_polymer->getSequenceCstRef().getSequence(
        m_indexRangeCollection, with_modif_entities, true);

      *mpa_resultsString += sequence;

      if(with_modif_entities)
        *mpa_resultsString += QObject::tr("Account monomer modifs: yes\n");
      else
        *mpa_resultsString += QObject::tr("Account monomer modifs: no\n");

      // Left end and right end modifs
      with_modif_entities =
        (static_cast<int>(mref_calcOptions.getPolymerEntities()) &
           static_cast<int>(
             libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF) ||
         static_cast<int>(mref_calcOptions.getPolymerEntities()) &
           static_cast<int>(
             libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF));

      if(!with_modif_entities)
        {
          *mpa_resultsString += QObject::tr("Account ends' modifs: no\n");
        }
      else
        {
          *mpa_resultsString += QObject::tr("Account ends' modifs: yes - ");

          // Left end modif
          with_modif_entities =
            (static_cast<int>(mref_calcOptions.getPolymerEntities()) &
             static_cast<int>(
               libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF));
          if(with_modif_entities)
            {
              *mpa_resultsString +=
                QObject::tr("Left end modif: %1 - ")
                  .arg(msp_polymer->getLeftEndModifCstRef().getName());
            }

          // Right end modif
          with_modif_entities =
            (static_cast<int>(mref_calcOptions.getPolymerEntities()) &
             static_cast<int>(
               libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF));
          if(with_modif_entities)
            {
              *mpa_resultsString +=
                QObject::tr("Right end modif: %1")
                  .arg(msp_polymer->getLeftEndModifCstRef().getName());
            }
        }

      *mpa_resultsString += QObject::tr("\n\nElemental composition: %1")
                              .arg(m_ui.elementalCompositionLineEdit->text());
    }
  else if(target == CompositionsTarget::MONOMERIC)
    {
      CompositionTreeViewModel *model = static_cast<CompositionTreeViewModel *>(
        m_ui.compositionTreeView->model());
      Q_ASSERT(model);

      int rowCount = model->rowCount();
      //   qDebug() << __FILE__ << __LINE__ << "rowCount" << rowCount;
      if(!rowCount)
        return;

      QString composString;

      for(int iter = 0; iter < rowCount; ++iter)
        {
          QModelIndex currentIndex =
            model->index(iter, COMPOSITION_NAME_COLUMN, QModelIndex());
          Q_ASSERT(currentIndex.isValid());

          composString += QObject::tr("%1 - ").arg(
            model->data(currentIndex, Qt::DisplayRole).toString());


          currentIndex =
            model->index(iter, COMPOSITION_CODE_COLUMN, QModelIndex());
          Q_ASSERT(currentIndex.isValid());

          composString += QObject::tr("%1 - ").arg(
            model->data(currentIndex, Qt::DisplayRole).toString());


          currentIndex =
            model->index(iter, COMPOSITION_MODIF_COLUMN, QModelIndex());
          Q_ASSERT(currentIndex.isValid());

          composString +=
            QObject::tr("Modified ?: %1 - ")
              .arg(model->data(currentIndex, Qt::DisplayRole).toString());


          currentIndex =
            model->index(iter, COMPOSITION_COUNT_COLUMN, QModelIndex());
          Q_ASSERT(currentIndex.isValid());

          composString +=
            QObject::tr("Count: %1.\n")
              .arg(model->data(currentIndex, Qt::DisplayRole).toString());
        }
      *mpa_resultsString += composString;
    }
  else
    Q_ASSERT(0);
}


bool
CompositionsDlg::exportResultsClipboard()
{
  QClipboard *clipboard = QApplication::clipboard();

  clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);

  return true;
}


bool
CompositionsDlg::exportResultsFile()
{
  if(m_resultsFilePath.isEmpty())
    {
      if(!selectResultsFile())
        return false;
    }

  QFile file(m_resultsFilePath);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      QMessageBox::information(0,
                               tr("MassXpert3 - Export Data"),
                               tr("Failed to open file in append mode."),
                               QMessageBox::Ok);
      return false;
    }

  QTextStream stream(&file);
  stream.setEncoding(QStringConverter::Utf8);

  stream << *mpa_resultsString;

  file.close();

  return true;
}


bool
CompositionsDlg::selectResultsFile()
{
  m_resultsFilePath =
    QFileDialog::getSaveFileName(this,
                                 tr("Select file to export data to"),
                                 QDir::homePath(),
                                 tr("Data files(*.dat *.DAT)"));

  if(m_resultsFilePath.isEmpty())
    return false;

  return true;
}
//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.

} // namespace MassXpert

} // namespace MsXpS
