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


/////////////////////// Local includes
#include <massxpert/nongui/globals.hpp>
#include <massxpert/gui/AbstractSeqEdWndDependentDlg.hpp>
#include <massxpert/gui/PkaPhPiDlg.hpp>
#include <massxpert/nongui/PkaPhPi.hpp>


namespace msXpSmassXpert
{


PkaPhPiDlg::PkaPhPiDlg(SequenceEditorWnd *editorWnd,
                       Polymer *polymer,
                       /* no PolChemDef **/
                       const QString &settingsFilePath,
                       PkaPhPi *pkaPhPi,
                       CalcOptions &calcOptions)
  : AbstractSeqEdWndDependentDlg(editorWnd,
                                 polymer,
                                 0, /*polChemDef*/
                                 settingsFilePath,
                                 "PkaPhPiDlg",
                                 "Acido-basic calculations"),
    mpa_pkaPhPi{pkaPhPi},
    m_calcOptions{calcOptions}
{
  if(!mp_polymer)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

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

  if(!initialize())
    qFatal(
      "Fatal error at %s@%d. Failed to initialize the %s window. Program "
      "aborted.",
      __FILE__,
      __LINE__,
      m_wndTypeName.toLatin1().data());
}


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


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

  readSettings();

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

  CoordinateList coordList;

  if(mp_editorWnd->mpa_editorGraphicsView->selectionIndices(&coordList))
    {
      m_ui.selectionCoordinatesLineEdit->setText(coordList.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()
{
  CoordinateList coordList;

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

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

          Coordinates coordinates(0, mp_polymer->size() - 1);
          m_calcOptions.setCoordinateList(coordinates);

          m_ui.wholeSequenceRadioButton->setChecked(true);
        }
      else
        {
          m_calcOptions.setCoordinateList(coordList);
        }
    }
  else
    {
      Coordinates coordinates(0, mp_polymer->size() - 1);
      m_calcOptions.setCoordinateList(coordinates);

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

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

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

  mpa_pkaPhPi->setCalcOptions(m_calcOptions);

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

  mpa_pkaPhPi->setPh(ph);

  return true;
}


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

  setCursor(Qt::WaitCursor);

  m_chemGroupsTested = -1;

  m_chemGroupsTested = mpa_pkaPhPi->calculateCharges();

  setCursor(Qt::ArrowCursor);


  if(m_chemGroupsTested == -1)
    {
      QMessageBox::warning(0,
                           tr("massXpert - 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(mpa_pkaPhPi->positiveCharges(), 'f', msXpS::PH_PKA_DEC_PLACES);
  m_ui.positiveChargesLabel->setText(value);

  value.setNum(mpa_pkaPhPi->negativeCharges(), 'f', msXpS::PH_PKA_DEC_PLACES);
  m_ui.negativeChargesLabel->setText(value);

  value.setNum(mpa_pkaPhPi->positiveCharges() + mpa_pkaPhPi->negativeCharges(),
               'f',
               msXpS::PH_PKA_DEC_PLACES);
  m_ui.netChargeLabel->setText(value);

  m_ui.isoelectricPointLabel->setText("");

  prepareResultsTxtString(TARGET_NET_CHARGE);
}


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

  setCursor(Qt::WaitCursor);

  m_chemGroupsTested = -1;

  m_chemGroupsTested = mpa_pkaPhPi->calculatePi();

  setCursor(Qt::ArrowCursor);

  if(m_chemGroupsTested == -1)
    {
      QMessageBox::warning(0,
                           tr("massXpert - 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(mpa_pkaPhPi->pi(), 'f', msXpS::PH_PKA_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 = (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_MODIF);

  QString *sequence =
    mp_polymer->monomerText(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);

  delete 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 =
    (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF ||
     m_calcOptions.polymerEntities() & POLYMER_CHEMENT_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 =
        (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString += QObject::tr("Left end modif: %1 - ")
                                  .arg(mp_polymer->leftEndModif().name());
        }

      // Right end modif
      entities =
        (m_calcOptions.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF);
      if(entities)
        {
          *mpa_resultsString += QObject::tr("Right end modif: %1")
                                  .arg(mp_polymer->leftEndModif().name());
        }
    }


  if(target == TARGET_PI)
    {
      QString value;

      value.setNum(mpa_pkaPhPi->pi(), 'f', msXpS::PH_PKA_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(mpa_pkaPhPi->ph(), 'f', msXpS::PH_PKA_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(
        mpa_pkaPhPi->positiveCharges(), 'f', msXpS::PH_PKA_DEC_PLACES);
      *mpa_resultsString += QObject::tr("Positive: %1 - ").arg(value);

      value.setNum(
        mpa_pkaPhPi->negativeCharges(), 'f', msXpS::PH_PKA_DEC_PLACES);
      *mpa_resultsString += QObject::tr("Negative:  %1 - ").arg(value);

      value.setNum(mpa_pkaPhPi->positiveCharges() +
                     mpa_pkaPhPi->negativeCharges(),
                   'f',
                   msXpS::PH_PKA_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("massXpert - Export Data"),
                               tr("Failed to open file in append mode."),
                               QMessageBox::Ok);
      return false;
    }

  QTextStream stream(&file);
  stream.setCodec("UTF-8");

  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 msXpSmassXpert
