/* 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 <QApplication>
#include <QMessageBox>
#include <QFileDialog>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/PkaPhPi.hpp>

/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "AbstractSeqEdWndDependentDlg.hpp"
#include "PkaPhPiDlg.hpp"


namespace MsXpS
{

namespace MassXpert
{


PkaPhPiDlg::PkaPhPiDlg(SequenceEditorWnd *editor_wnd_p,
                       libXpertMassCore::PolymerQSPtr polymer_sp,
                       /* no libXpertMassCore::PolChemDef **/
                       const QString &settings_file_path,
                       const QString &application_name,
                       const QString &description,
                       const libXpertMassCore::PkaPhPi &pka_ph_pi,
                       const libXpertMassCore::CalcOptions &calcOptions)
  : AbstractSeqEdWndDependentDlg(editor_wnd_p,
                                 polymer_sp,
                                 0, /*polChemDef*/
                                 settings_file_path,
                                 "PkaPhPiDlg",
                                 application_name,
                                 description),
    m_pkaPhPi(std::move(pka_ph_pi)),
    m_calcOptions(calcOptions)
{
  if(polymer_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

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


PkaPhPiDlg::~PkaPhPiDlg()
{
  delete mpa_resultsString;
}


bool
PkaPhPiDlg::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));

  readSettings();

  // The selection might exist as a list of region selections.

  libXpertMassCore::IndexRangeCollection index_range_collection;

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

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

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

  // 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();

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


  connect(m_ui.isoelectricPointPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(isoelectricPoint()));

  connect(m_ui.netChargePushButton, SIGNAL(clicked()), this, SLOT(netCharge()));

  return true;
}


bool
PkaPhPiDlg::fetchValidateInputData()
{
  libXpertMassCore::IndexRangeCollection index_range_collection;

  // If the selected sequence should be dealt-with, then get its
  // borders.
  if(m_ui.selectedSequenceRadioButton->isChecked())
    {
      bool res = mp_editorWnd->mpa_editorGraphicsView->selectionIndices(
        index_range_collection);

      if(!res)
        {
          // There is no selection, set the "selection" to be the
          // whole sequence.

          libXpertMassCore::IndexRange index_range(0, msp_polymer->size() - 1);
          m_calcOptions.setIndexRange(index_range);

          m_ui.wholeSequenceRadioButton->setChecked(true);
        }
      else
        {
          m_calcOptions.setIndexRanges(index_range_collection);
        }
    }
  else
    {
      libXpertMassCore::IndexRange index_range(0, msp_polymer->size() - 1);
      m_calcOptions.setIndexRange(index_range);

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

  m_ui.selectionCoordinatesLineEdit->setText(
    m_calcOptions.getIndexRangeCollectionCstRef().positionsAsText());

  // Duplicate the current calcOptions from the sequence editor and
  // change the start/end indices within it. Then copy these new
  // calcOptions into the m_pkaPhPi.

  m_pkaPhPi.setCalcOptions(m_calcOptions);

  double ph = m_ui.phDoubleSpinBox->value();
  Q_ASSERT(ph > 0 && ph < 14);

  m_pkaPhPi.setPh(ph);

  return true;
}


void
PkaPhPiDlg::netCharge()
{
  if(!fetchValidateInputData())
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - pKa pH pI"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }

  setCursor(Qt::WaitCursor);

  m_chemGroupsTested = -1;

  m_chemGroupsTested = m_pkaPhPi.calculateCharges();

  setCursor(Qt::ArrowCursor);


  if(m_chemGroupsTested == -1)
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - pKa pH pI"),
                           tr("Failed computing charges."),
                           QMessageBox::Ok);
      return;
    }


  // And now display the results.

  QString value;

  value.setNum(m_chemGroupsTested);
  m_ui.testedChemGroupsLabel->setText(value);

  value.setNum(
    m_pkaPhPi.positiveCharges(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);
  m_ui.positiveChargesLabel->setText(value);

  value.setNum(
    m_pkaPhPi.negativeCharges(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);
  m_ui.negativeChargesLabel->setText(value);

  value.setNum(m_pkaPhPi.positiveCharges() + m_pkaPhPi.negativeCharges(),
               'f',
               libXpertMassCore::PKA_PH_PI_DEC_PLACES);
  m_ui.netChargeLabel->setText(value);

  m_ui.isoelectricPointLabel->setText("");

  prepareResultsTxtString(TARGET_NET_CHARGE);
}


void
PkaPhPiDlg::isoelectricPoint()
{
  if(!fetchValidateInputData())
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - pKa pH pI"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }

  setCursor(Qt::WaitCursor);

  m_chemGroupsTested = -1;

  m_chemGroupsTested = m_pkaPhPi.calculatePi();

  setCursor(Qt::ArrowCursor);

  if(m_chemGroupsTested == -1)
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - pKa pH pI"),
                           tr("Failed computing charges."),
                           QMessageBox::Ok);
      return;
    }

  // And now display the results.

  QString value;

  value.setNum(m_chemGroupsTested);
  m_ui.testedChemGroupsLabel->setText(value);

  m_ui.positiveChargesLabel->setText("");

  m_ui.negativeChargesLabel->setText("");

  m_ui.netChargeLabel->setText("");

  value.setNum(m_pkaPhPi.pi(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);
  m_ui.isoelectricPointLabel->setText(value);

  prepareResultsTxtString(TARGET_PI);
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
PkaPhPiDlg::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
PkaPhPiDlg::prepareResultsTxtString(int target)
{
  mpa_resultsString->clear();

  *mpa_resultsString += QObject::tr(
    "# \n"
    "# ----------------------------\n"
    "# pKa - pH - pI Calculations: \n"
    "# ----------------------------\n");

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

  QString sequence = msp_polymer->getSequenceCstRef().getSequence(
    m_startIndex, m_endIndex, entities);

  *mpa_resultsString +=
    QObject::tr("Start position: %1 - End position: %2 - Sequence: %3\n\n")
      .arg(m_startIndex + 1)
      .arg(m_endIndex + 1)
      .arg(sequence);

  if(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
  entities =
    (static_cast<int>(m_calcOptions.getPolymerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::LEFT_END_MODIF)) ||
    (static_cast<int>(m_calcOptions.getPolymerEntities()) &
     static_cast<int>(libXpertMassCore::Enums::ChemicalEntity::RIGHT_END_MODIF));

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

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

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


  if(target == TARGET_PI)
    {
      QString value;

      value.setNum(m_pkaPhPi.pi(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);

      *mpa_resultsString += QObject::tr(
                              "\npI Calculation:\n"
                              "---------------\n"
                              "pI value: %1 - Chemical groups "
                              "tested: %2\n\n")
                              .arg(value)
                              .arg(m_chemGroupsTested);
    }
  else if(target == TARGET_NET_CHARGE)
    {
      QString value;

      value.setNum(m_pkaPhPi.ph(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);

      *mpa_resultsString += QObject::tr(
                              "\nNet Charge Calculation:\n"
                              "-----------------------\n"
                              "At pH value: %1\n"
                              "Chemical groups tested: %2\n")
                              .arg(value)
                              .arg(m_chemGroupsTested);


      value.setNum(
        m_pkaPhPi.positiveCharges(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);
      *mpa_resultsString += QObject::tr("Positive: %1 - ").arg(value);

      value.setNum(
        m_pkaPhPi.negativeCharges(), 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);
      *mpa_resultsString += QObject::tr("Negative:  %1 - ").arg(value);

      value.setNum(m_pkaPhPi.positiveCharges() + m_pkaPhPi.negativeCharges(),
                   'f',
                   libXpertMassCore::PKA_PH_PI_DEC_PLACES);
      *mpa_resultsString += QObject::tr("Net:  %1\n\n").arg(value);
    }
  else
    Q_ASSERT(0);
}


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

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

  return true;
}


bool
PkaPhPiDlg::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
PkaPhPiDlg::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
