/* 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 <QApplication>
#include <QClipboard>
#include <QSettings>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/Oligomer.hpp>
#include <MsXpS/libXpertMassCore/PolChemDef.hpp>


/////////////////////// Local includes
#include "MzCalculationDlg.hpp"
#include "MzCalculationTreeViewModel.hpp"


namespace MsXpS
{

namespace MassXpert
{


MzCalculationDlg::MzCalculationDlg(
  QWidget *parent,
  const QString &config_settings_file_path,
  libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp,
  const QString &application_name,
  const QString &description,
  const libXpertMassCore::Ionizer &ionizer,
  double mono,
  double avg)
  : QDialog(parent),
    m_configSettingsFilePath(config_settings_file_path),
    mcsp_polChemDef(pol_chem_def_csp),
    m_ionizer(ionizer)
{
  if(parent == nullptr || pol_chem_def_csp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  m_ui.setupUi(this);

  setWindowIcon(qApp->windowIcon());

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

  setupTreeView();

  if(mono != 0)
    m_ui.monoLineEdit->setText(
      QString("%1").arg(mono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  if(avg != 0)
    m_ui.avgLineEdit->setText(
      QString("%1").arg(avg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  if(mono || avg)
    {
      // If we are setting masses directly from here, then that means
      // that the formula line edit must be disabled.
      m_ui.formulaCheckBox->setCheckState(Qt::Unchecked);
      m_ui.startFormulaLineEdit->setDisabled(true);
    }
  else
    {
      // No masses are being preseeded. So let the user the full
      // opportunity ot enter a formula.
      m_ui.formulaCheckBox->setCheckState(Qt::Checked);
      m_ui.startFormulaLineEdit->setDisabled(false);
    }

  m_ui.srcIonizerChargeSpinBox->setRange(1, 1000000000);
  m_ui.srcIonizerLevelSpinBox->setRange(0, 1000000000);

  m_ui.destIonizerChargeSpinBox->setRange(1, 1000000000);
  m_ui.destIonizerStartLevelSpinBox->setRange(0, 1000000000);
  m_ui.destIonizerEndLevelSpinBox->setRange(0, 1000000000);


  m_ui.srcIonizerFormulaLineEdit->setText(
    m_ionizer.getFormulaCstRef().getActionFormula());
  m_ui.srcIonizerChargeSpinBox->setValue(m_ionizer.getNominalCharge());
  m_ui.srcIonizerLevelSpinBox->setValue(m_ionizer.getLevel());

  m_ui.destIonizerFormulaLineEdit->setText(
    m_ionizer.getFormulaCstRef().getActionFormula());
  m_ui.destIonizerChargeSpinBox->setValue(m_ionizer.getNominalCharge());
  m_ui.destIonizerStartLevelSpinBox->setValue(m_ionizer.getLevel());
  m_ui.destIonizerEndLevelSpinBox->setValue(m_ionizer.getLevel());


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

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


  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup("mz_calculation_dlg");

  restoreGeometry(settings.value("geometry").toByteArray());

  m_ui.ponderableSplitter->restoreState(
    settings.value("ponderableSplitter").toByteArray());

  settings.endGroup();

  connect(m_ui.calculatePushButton, SIGNAL(clicked()), this, SLOT(calculate()));

  connect(m_ui.formulaCheckBox,
          SIGNAL(toggled(bool)),
          this,
          SLOT(formulaCheckBoxToggled(bool)));
}


MzCalculationDlg::~MzCalculationDlg()
{
  delete mpa_mzTreeViewModel;
  delete mpa_mzProxyModel;
}


void
MzCalculationDlg::closeEvent(QCloseEvent *event)
{
  if(event)
    printf("%s", "");

  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup("mz_calculation_dlg");

  settings.setValue("geometry", saveGeometry());

  settings.setValue("ponderableSplitter", m_ui.ponderableSplitter->saveState());

  settings.endGroup();
}


void
MzCalculationDlg::setupTreeView()
{
  // Model stuff all thought for sorting.
  mpa_mzTreeViewModel = new MzCalculationTreeViewModel(&m_ions, this);

  mpa_mzProxyModel = new MzCalculationTreeViewSortProxyModel(this);
  mpa_mzProxyModel->setSourceModel(mpa_mzTreeViewModel);

  m_ui.mzTreeView->setModel(mpa_mzProxyModel);
  m_ui.mzTreeView->setParentDlg(this);
  mpa_mzTreeViewModel->setTreeView(m_ui.mzTreeView);
}


void
MzCalculationDlg::formulaCheckBoxToggled(bool checked)
{
  // If checked, then the user is responsible for entering a valid
  // formula: the line edit must *not* be disabled.

  m_ui.startFormulaLineEdit->setDisabled(!checked);
}


void
MzCalculationDlg::calculate()
{
  libXpertMassCore::IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // The calculation can be accomplished in two different manners depending on
  // the Initial status data entered by the user:
  //
  // 1. Starting from a formula. If a formula is provided, initial status
  // ionizer data need to be provided along with it, so that we know if that
  // formula is for a ionized state or not.

  // When the Ionizer data have been set using the data entered for the
  // Ionization rule, the actual masses (actually m/z, if the charge of Ionizer
  // is non-0) for the formula can be computed and displayed in the Initial
  // status mono and avg mass line edit widgets.
  //
  // 2. Starting from mono and avg m/z values immediately.
  // The libXpertMassCore::Formula check box is unchecked in this case. As for
  // point 1. the user has to document the Initial status Ionizer data,
  // otherwise we would not know what the entered masses are for.

  // Once we have sorted point 1 or point 2 above, we have Initial status masses
  // and Initial status Ionizer data. We must use these data to compute
  // NON-ionized Mr masses which we'll later use as a starting point for all the
  // calculations to come.

  // Because this function can be called multiple times from the
  // same dialog window, make sure that the member formula is cleared.
  m_formula.clear();

  libXpertMassCore::Ionizer ionizer(mcsp_polChemDef->getIonizerCstRef());
  libXpertMassCore::ErrorList error_list;
  if(!ionizer.validate(&error_list))
    qFatal()
      << "Programming error. Failed to validate Ionizer from PolChemDef.";

  // Start by configuring ionizer with the current state, using the
  // source ionization status.

  qDebug() << "Going to get the source ionizer data that will become current "
              "status data in the Ionizer.";

  if(!getSrcIonizerData(ionizer))
    return;

  // Now sort point 1 or point 2 above.

  bool start_from_formula = false;

  if(m_ui.formulaCheckBox->checkState() == Qt::Checked)
    {
      // We are asked to start with a Formula. Check its validity and
      // also check for the ionization status corresponding to this formula.

      QString formula_text = m_ui.startFormulaLineEdit->text();

      if(formula_text.isEmpty())
        {
          // Then the masses for the formula must be 0.

          m_ui.monoLineEdit->setText("0.000");
          m_ui.avgLineEdit->setText("0.000");

          // We have nothing to do, just return.

          return;
        }

      m_formula.setActionFormula(formula_text);

      // At this point we know we could actually validate the formula,
      // so do that work with the member formula:

      // We want to validate the formula and in the mean time construct
      // the list of all the libXpertMassCore::Isotope objects(first true), and
      // since the formula is reused we also ensure that that list is reset
      // (second true).

      libXpertMassCore::ErrorList error_list;

      if(!m_formula.validate(isotopic_data_csp, true, true, &error_list))
        {
          m_ui.monoLineEdit->setText("0.000");
          m_ui.avgLineEdit->setText("0.000");

          QMessageBox::warning(this,
                               tr("MassXpert3 - m/z Ratio Calculator"),
                               tr("%1@%2\n"
                                  "Failed to validate the formula.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          // Let the results printing function that we did not
          // use the formula.
          m_formula.clear();

          return;
        }

      // qDebug() << "Formula:" << formula.formula() << "validated
      // successfully." ;

      // Now that we know the formula is valid, we can check the ionization
      // status that it corresponds to. And only then, will we be able to
      // calculate and display the mono and avg masses coressponding to it.

      // Because the formula provided here already accounts for the ionization
      // status described in ionizer (current state data), all we have to do
      // is provide masses on the basis of the current_charge.

      int current_charge = ionizer.currentStateCharge();

      double mono_mass = 0;
      double avg_mass  = 0;

      // It is impossible that we have an error here, since the formula
      // was previously validated.
      bool ok = false;
      m_formula.accountMasses(ok, isotopic_data_csp, mono_mass, avg_mass, 1);

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

      // qDebug() << qSetRealNumberPrecision(6)
      //          << "The masses computed for the formula:" << mono_mass << "-"
      //          << avg_mass;

      if(current_charge)
        {
          mono_mass /= current_charge;
          avg_mass /= current_charge;
        }

      // qDebug() << qSetRealNumberPrecision(6) << "Accounting the
      // current_charge"
      //          << current_charge << "they become:" << mono_mass << "-" <<
      //          avg_mass;

      // Great, now show the data.
      m_ui.monoLineEdit->setText(
        QString::number(mono_mass, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
      m_ui.avgLineEdit->setText(
        QString::number(avg_mass, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

      start_from_formula = true;
    }
  // End of situation where the user starts with a formula
  // as the starting point compound, instead of mono/avg masses.

  // At this point, either the mono and avg masses were computed using the
  // Initial status formula and ionization rule, or these masses were entered
  // directly by the user. Whatever the case, we get them from the line edit
  // widgets and check for them to be bona fide double values.

  bool ok;
  int errors_sum       = 0;
  double src_mono_mass = getSourceMass(libXpertMassCore::Enums::MassType::MONO, ok);
  errors_sum += ok ? 0 : 1;
  double src_avg_mass = getSourceMass(libXpertMassCore::Enums::MassType::AVG, ok);
  errors_sum += ok ? 0 : 1;

  qDebug() << "The user seeded the system with formula or masses:"
           << src_mono_mass << "-" << src_avg_mass;

  if(errors_sum)
    return;

  // Good, we have mono and avg masses, we know what ionization rule these
  // correspond to. We can store this state in an Ion structure.

  Ion src_ion;
  src_ion.mono       = src_mono_mass;
  src_ion.avg        = src_avg_mass;
  src_ion.ionizer_sp = std::make_shared<libXpertMassCore::Ionizer>(ionizer);

  // Now we need to prepare the data for the ionization loop over the
  // start and stop ionization levels. What we want to do is prepare
  // the formula (if that applies) and the masses (if that applies)
  // in an uncharged state so that we use that uncharged state for each
  // ionization level iteration in the loop below without have to deionize
  // each previously calculated ionization level.

  // Start with the formula: set it to non-ionized.
  // ==============================================

  // If the starting material was a formula (and not only mono/avg masses), what
  // we need to do now is compute the formula of non-ionized state, if the
  // initial status ionization rule corresponded to an effectively charged
  // state. When exporting data to clipboard we will want to export the formula
  // corresponding to each target ionization state so the user may craft an
  // isotopic cluster out of it. This means that we need the formula in an
  // uncharged state to ease the iteration in the ionization levels loop below.

  if(start_from_formula)
    {
      qDebug() << "Since the user started with a formula, check if it needs "
                  "deionization with the current ionization state:"
               << ionizer.toString() << "and the formula right now is:"
               << m_formula.getActionFormula();

      bool ok = false;

      m_formula.accountFormula(
        ionizer.getCurrentStateFormulaCstRef().getActionFormula(),
        isotopic_data_csp,
        -1 * ionizer.getCurrentStateLevel(),
        ok);

      if(!ok)
        {
          qWarning()
            << "Failed to neg-account formula:"
            << ionizer.getCurrentStateFormulaCstRef().getActionFormula();

          return;
        }

      // Now, the starting formula has an elemental composition
      // corresponding to a NON-ionized status (that is, a Mr formula).
      // We will use that NON-ionized formula to account all the levels
      // of ionization below.

      qDebug() << "Formula after (potential) deionization:"
               << m_formula.getActionFormula();
    }

  // Next set the masses (provided or calculated from the formula) to
  // non-ionized.
  // ============================================================================

  // We now want to compute *Mr* mono and avg masses so that we can use them
  // in the loop below without having to first deionize at each loop run.

  qDebug() << "Now willing to make non-ionized masses if they corresponded to "
              "ionized masses.";

  // Seed the masses with the initial status masses.
  double initial_status_non_ion_mono = src_ion.mono;
  double initial_status_non_ion_avg  = src_ion.avg;

  qDebug() << "Before deionization of the masses:" << src_ion.mono << "-"
           << src_ion.avg;

  // If ionizer is not charged, fine, otherwise undergo deionization. The
  // masses that are calculated are stored in the params.
  // libXpertMassCore::Enums::IonizationOutcome outcome.

  // Make a copy
  qDebug() << "Make a copy of the ionizer for ease.";
  libXpertMassCore::Ionizer temp_ionizer(ionizer);

  qDebug() << "Getting molecular masses, that is, non-ionized masses.";

  temp_ionizer.molecularMasses(initial_status_non_ion_mono,
                               initial_status_non_ion_avg);

  qDebug() << "Calculated molecular masses:" << initial_status_non_ion_mono
           << "-" << initial_status_non_ion_avg;

  qDebug() << "Because the masses now correspond to non-ionized masses, "
              "force the current ionization state level to 0."
           << "Which now provides the ionizer current state required for the "
              "loop iteration below.";
  // Now, set the current state of ionizer with the proper ionization level,
  // that is, 0.
  ionizer.forceCurrentStateLevel(0);

  // Get the destination start/end ionization levels and make sure
  // they are in an increasing order.
  int ionization_start_level =
    std::min(m_ui.destIonizerStartLevelSpinBox->value(),
             m_ui.destIonizerEndLevelSpinBox->value());
  int ionization_stop_level =
    std::max(m_ui.destIonizerStartLevelSpinBox->value(),
             m_ui.destIonizerEndLevelSpinBox->value());

  qDebug() << "Start and end levels of ionization:" << ionization_start_level
           << "->" << ionization_stop_level;

  // Empty the treeView model, so that we can put fresh data in
  // it. Same for the oligomer list!

  mpa_mzTreeViewModel->removeAll();

  // And now use these masses for the ionization level loop below.
  for(int level = ionization_start_level; level < ionization_stop_level + 1;
      ++level)
    {
      qDebug() << "Iterating in loop for level:" << level;

      // Re-seed the masses with the initial status NON ionized masses.
      double ion_mono_mass = initial_status_non_ion_mono;
      double ion_avg_mass  = initial_status_non_ion_avg;

      // Make a copy of the Ionizer.
      qDebug() << "Make a copy of the reference ionizer to maintain it in "
                  "uncharged state.";
      libXpertMassCore::Ionizer the_ionizer(ionizer);

      // Now handle the destination status of the ionizer.
      // Will not set the ionization level, because we'll set it
      // below.'
      qDebug() << "Now getting the destination ionization status.";

      if(!getDestIonizerData(the_ionizer))
        return;

      qDebug() << "Now the ionizer is:" << the_ionizer.toString();
      qDebug() << "Setting the ionizer level to" << level;
      the_ionizer.setLevel(level);

      qDebug() << "Now ionizing with ionizer:" << the_ionizer.toString();
      if(libXpertMassCore::Enums::IonizationOutcome::FAILED ==
         the_ionizer.ionize(ion_mono_mass, ion_avg_mass))
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - m/z ratio calculator"),
                               tr("%1@%2\n"
                                  "Failed to perform ionization.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          return;
        }

      qDebug() << "After ionization, the ionizer:" << the_ionizer.toString()
               << "and masses:" << ion_mono_mass << "-" << ion_avg_mass;

      // Now use the new masses to create an object that we can feed to the
      // tree view model. This ion will receive a Ionizer that faithfully
      // and completely represents the calculation starting from a non-ionized
      // state.

      Ion ion;
      ion.ionizer_sp = std::make_shared<libXpertMassCore::Ionizer>(the_ionizer);
      ion.mono       = ion_mono_mass;
      ion.avg        = ion_avg_mass;

      // If the calculation started from a formula, compute the new formula
      // that takes into account the ionization and set that to the ion.

      if(start_from_formula)
        {
          libXpertMassCore::Formula new_formula(m_formula);

          bool ok = false;

          new_formula.accountFormula(
            the_ionizer.getCurrentStateFormulaCstRef().getActionFormula(),
            isotopic_data_csp,
            ionizer.currentStateCharge(),
            ok);

          if(!ok)
            {
              QMessageBox::warning(this,
                                   tr("MassXpert3 - m/z ratio calculator"),
                                   tr("%1@%2\n\n"
                                      "Failed to account Ionizer formula.")
                                     .arg(__FILE__)
                                     .arg(__LINE__),
                                   QMessageBox::Ok);

              return;
            }

          ion.formula = new_formula.getActionFormula();
        }

      // Finally we can add the fully qualified ion.
      IonSPtr ion_sp = std::make_shared<Ion>(ion);
      m_ions.push_back(ion_sp);
      mpa_mzTreeViewModel->addIon(ion_sp);
    }
}


bool
MzCalculationDlg::getSrcIonizerData(libXpertMassCore::Ionizer &ionizer)
{
  qDebug() << "Getting ionizer to fill in. As we got it:" << ionizer.toString();

  if(ionizer.getIsotopicDataCstSPtr() == nullptr ||
     ionizer.getIsotopicDataCstSPtr().get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  ionizer.forceCurrentState(
    libXpertMassCore::Formula(m_ui.srcIonizerFormulaLineEdit->text()),
    m_ui.srcIonizerChargeSpinBox->value(),
    m_ui.srcIonizerLevelSpinBox->value());

  if(!ionizer.isCurrentStateValid())
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - m/z ratio calculator"),
        QString("%1@%2\n\n"
                "Failed to validate initial ionization rule.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);

      return false;
    }

  // We do not validate the ionizer in its entirety because
  // the state (not the current state) might be unset for the moment
  // and if m_nominalCharge is 0 that invalidates the Ionizer. What
  // we now need to do is to set the state (not the current state)
  // and this will be done in getDestIonizerData() specifically
  // using this same ionizer. See calculate() for the general picture.

  qDebug() << "With the current ionization state, the ionizer now has become "
              "state is: "
           << ionizer.toString();

  return true;
}


bool
MzCalculationDlg::getDestIonizerData(libXpertMassCore::Ionizer &ionizer)
{
  if(ionizer.getIsotopicDataCstSPtr() == nullptr ||
     ionizer.getIsotopicDataCstSPtr().get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // We only set the destination ionization (not the current state).

  ionizer.setFormula(m_ui.destIonizerFormulaLineEdit->text());
  ionizer.setNominalCharge(m_ui.destIonizerChargeSpinBox->value());

  // The level is dealt with in another function, as it might
  // encompass more than one single value(see calculate()). Note
  // that if level is 0, this does not invalidate the ionizer.

  MsXpS::libXpertMassCore::ErrorList error_list;

  if(!ionizer.validate(&error_list))
    {
      QMessageBox::warning(
        this,
        tr("MassXpert3 - m/z ratio calculator"),
        QString("%1@%2\n"
                "Failed to validate initial ionization rule with errors:\n%3")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(libXpertMassCore::Utils::joinErrorList(error_list)),
        QMessageBox::Ok);

      return false;
    }

  qDebug() << "With the destination ionization state, the ionizer now has "
              "become state is: "
           << ionizer.toString();

  return true;
}

double
MzCalculationDlg::getSourceMass(libXpertMassCore::Enums::MassType mass_type,
                                bool &ok)
{
  if(mass_type == libXpertMassCore::Enums::MassType::BOTH)
    qFatal() << "Programming error. The MassType is incorrect.";

  QString text;

  double mass = 0;

  if(mass_type == libXpertMassCore::Enums::MassType::MONO)
    text = m_ui.monoLineEdit->text();
  else
    text = m_ui.avgLineEdit->text();

  if(text.isEmpty())
    return 0.0;

  mass = text.toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - m/z Ratio Calculator"),
                           tr("%1@%2\n"
                              "Failed to read mass of type %3.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(libXpertMassCore::massTypeMap[mass_type]),
                           QMessageBox::Ok);

      return 0;
    }

  return mass;
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
MzCalculationDlg::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
MzCalculationDlg::prepareResultsTxtString()
{
  m_resultsString.clear();

  m_resultsString += QObject::tr(
    "# \n"
    "# ---------------------------\n"
    "# m/z Calculations: \n"
    "# ---------------------------\n");

  QString value;

  m_resultsString += QObject::tr(
    "\nSource conditions:\n"
    "------------------\n");

  if(m_formula.totalAtoms())
    {
      m_resultsString += QObject::tr("Initial formula: %1\n\n")
                           .arg(m_formula.getActionFormula());
    }

  m_resultsString +=
    QObject::tr("Mono mass: %1").arg(m_ui.monoLineEdit->text());

  m_resultsString +=
    QObject::tr(" - Avg mass: %1\n").arg(m_ui.avgLineEdit->text());

  m_resultsString += QObject::tr("Ionization formula: %1\n")
                       .arg(m_ui.srcIonizerFormulaLineEdit->text());

  m_resultsString += QObject::tr("Ionization charge: %1 - ")
                       .arg(m_ui.srcIonizerChargeSpinBox->value());

  m_resultsString += QObject::tr("Ionization level: %1\n")
                       .arg(m_ui.srcIonizerLevelSpinBox->value());

  m_resultsString += QObject::tr(
    "\nDestination conditions:\n"
    "-----------------------\n");

  m_resultsString += QObject::tr("Ionization formula: %1\n")
                       .arg(m_ui.destIonizerFormulaLineEdit->text());

  m_resultsString += QObject::tr("Ionization charge: %1 - ")
                       .arg(m_ui.destIonizerChargeSpinBox->value());

  m_resultsString += QObject::tr("Start level: %1 - ")
                       .arg(m_ui.destIonizerStartLevelSpinBox->value());

  m_resultsString += QObject::tr("End level: %1\n\n\n")
                       .arg(m_ui.destIonizerEndLevelSpinBox->value());

  MzCalculationTreeViewModel *model =
    static_cast<MzCalculationTreeViewModel *>(m_ui.mzTreeView->model());
  Q_ASSERT(model);

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

  QString mzString;

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

      bool ok = false;

      QString valueString =
        model->data(currentIndex, Qt::DisplayRole).toString();

      double value = valueString.toDouble(&ok);

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

      mzString += QObject::tr("Level: %1 -- ")
                    .arg(value, 0, 'f', libXpertMassCore::PKA_PH_PI_DEC_PLACES);

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

      ok = false;

      valueString = model->data(currentIndex, Qt::DisplayRole).toString();

      value = valueString.toDouble(&ok);

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

      mzString += QObject::tr("Mono: %1 -- ")
                    .arg(value, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

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

      ok = false;

      valueString = model->data(currentIndex, Qt::DisplayRole).toString();

      value = valueString.toDouble(&ok);

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

      mzString += QObject::tr("Avg: %1").arg(
        value, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

      mzString += ("\n");
    }

  m_resultsString += mzString;
}


bool
MzCalculationDlg::exportResultsClipboard()
{
  prepareResultsTxtString();

  QClipboard *clipboard = QApplication::clipboard();

  clipboard->setText(m_resultsString, QClipboard::Clipboard);

  return true;
}


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

  QFile file(m_resultsFilePath);

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

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

  prepareResultsTxtString();

  stream << m_resultsString;

  file.close();

  return true;
}


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