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


/////////////////////// libmass includes
#include <libXpertMass/PkaPhPi.hpp>

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


namespace MsXpS
{

namespace massxpert
{


  PkaPhPiDlg::PkaPhPiDlg(SequenceEditorWnd *editorWnd,
                         libXpertMass::Polymer *polymer,
                         /* no libXpertMass::PolChemDef **/
                         const QString &settingsFilePath,
                         const QString &applicationName,
                         const QString &description,
                         libXpertMass::PkaPhPi *pkaPhPi,
                         libXpertMass::CalcOptions &calcOptions)
    : AbstractSeqEdWndDependentDlg(editorWnd,
                                   polymer,
                                   0, /*polChemDef*/
                                   settingsFilePath,
                                   "PkaPhPiDlg",
                                   applicationName,
                                   description),
      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);

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

    libXpertMass::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()
  {
    libXpertMass::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.

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

            m_ui.wholeSequenceRadioButton->setChecked(true);
          }
        else
          {
            m_calcOptions.setCoordinateList(coordList);
          }
      }
    else
      {
        libXpertMass::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', libXpertMass::PH_PKA_DEC_PLACES);
    m_ui.positiveChargesLabel->setText(value);

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

    value.setNum(mpa_pkaPhPi->positiveCharges() +
                   mpa_pkaPhPi->negativeCharges(),
                 'f',
                 libXpertMass::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', libXpertMass::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() & libXpertMass::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() &
                  libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF ||
                m_calcOptions.polymerEntities() &
                  libXpertMass::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() &
                    libXpertMass::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() &
                    libXpertMass::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', libXpertMass::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', libXpertMass::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', libXpertMass::PH_PKA_DEC_PLACES);
        *mpa_resultsString += QObject::tr("Positive: %1 - ").arg(value);

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

        value.setNum(mpa_pkaPhPi->positiveCharges() +
                       mpa_pkaPhPi->negativeCharges(),
                     'f',
                     libXpertMass::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.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
