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

/////////////////////// Local includes
#include <massxpert/gui/CompositionsDlg.hpp>
#include <massxpert/nongui/Coordinates.hpp>

namespace msXpSmassXpert
{

CompositionsDlg::CompositionsDlg(SequenceEditorWnd *editorWnd,
                                 Polymer *polymer,
                                 /* no PolChemDef **/
                                 const QString &configSettingsFilePath,
                                 CalcOptions *calcOptions,
                                 msXpSlibmass::IonizeRule *ionizeRule)
  : AbstractSeqEdWndDependentDlg(editorWnd,
                                 polymer,
                                 0 /*polChemDef*/,
                                 configSettingsFilePath,
                                 "CompositionsDlg",
                                 "Composition calculations"),
    mp_calcOptions{calcOptions},
    mp_ionizeRule{ionizeRule}
{
  if(!mp_polymer)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(!mp_calcOptions || !mp_ionizeRule)
    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());
}


CompositionsDlg::~CompositionsDlg()
{
  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);

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

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

  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_coordinateList))
    {
      m_ui.selectionCoordinatesLineEdit->setText(
        m_coordinateList.positionsAsText());

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

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

  m_ui.monomericPushButton->setFocus();
}


bool
CompositionsDlg::fetchValidateInputData()
{
  if(!m_ui.selectedSequenceRadioButton->isChecked())
    {
      m_coordinateList.setCoordinates(Coordinates(0, mp_polymer->size() - 1));
    }

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

  int polymerSize = mp_polymer->size();

  for(int iter = 0; iter < m_coordinateList.size(); ++iter)
    {
      // New coordinates instance we are iterating into.
      Coordinates *coordinates = m_coordinateList.at(iter);

      if(coordinates->start() >= polymerSize ||
         coordinates->end() >= polymerSize)
        {
          QMessageBox::warning(0,
                               tr("massXpert - Compositions"),
                               tr("Selection data are no more valid.\n"
                                  "Please update these data."),
                               QMessageBox::Ok);
          return false;
        }
    }

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

  if(mp_calcOptions->monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
    {
      // 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
        {
          // We have to count the incomplete cross-links.

          const CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

          int crossLinkPartial = 0;

          for(int iter = 0; iter < crossLinkList.size(); ++iter)
            {
              CrossLink *crossLink = crossLinkList.at(iter);

              int ret = crossLink->encompassedBy(m_coordinateList);

              if(ret == CROSS_LINK_ENCOMPASSED_FULL)
                {
                  // 		qDebug() << __FILE__ << __LINE__
                  // 			  << "CrossLink at iter:" << iter
                  // 			  << "is fully encompassed";
                }
              else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
                {
                  // 		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("massXpert - Compositions"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }

  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;
    }

  for(int iter = 0; iter < m_coordinateList.size(); ++iter)
    {
      // New coordinates instance we are iterating into.
      Coordinates *coordinates = m_coordinateList.at(iter);

      for(int jter = coordinates->start(); jter < coordinates->end() + 1;
          ++jter)
        {
          bool processed = false;

          const Monomer *iterMonomer = mp_polymer->at(jter);
          Q_ASSERT(iterMonomer);

          // 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(iterMonomer->name() == fakeMonomer->name())
                {
                  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(iterMonomer->isModified())
                    {
                      // The monomer in the sequence is modified. Let the
                      // fake monomer know it.
                      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
                        {
                          IntProp *prop = new 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 = iterMonomer->clone();

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

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

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

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

      setupTreeView();

      prepareResultsTxtString(TARGET_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("massXpert - Compositions"),
                           tr("Failed validating input data."),
                           QMessageBox::Ok);
      return;
    }

  QString composition = mp_polymer->elementalComposition(
    *mp_ionizeRule, m_coordinateList, *mp_calcOptions);
  m_ui.elementalCompositionLineEdit->setText(composition);

  prepareResultsTxtString(TARGET_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(int target)
{
  mpa_resultsString->clear();

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

  if(target == TARGET_ELEMENTAL)
    {
      *mpa_resultsString += QObject::tr("\nIonization rule:\n");

      *mpa_resultsString +=
        QObject::tr("Formula: %1 - ").arg(mp_ionizeRule->formula());

      *mpa_resultsString +=
        QObject::tr("Charge: %1 - ").arg(mp_ionizeRule->charge());

      *mpa_resultsString +=
        QObject::tr("Level: %1\n").arg(mp_ionizeRule->level());


      *mpa_resultsString += QObject::tr("\nCalculation options:\n");


      bool withEntities =
        (mp_calcOptions->monomerEntities() & MONOMER_CHEMENT_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 =
        mp_polymer->monomerText(m_coordinateList, withEntities, true);

      *mpa_resultsString += *sequence;

      delete sequence;

      if(withEntities)
        *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
      withEntities =
        (mp_calcOptions->polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF ||
         mp_calcOptions->polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF);

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

          // Left end modif
          withEntities = (mp_calcOptions->polymerEntities() &
                          POLYMER_CHEMENT_LEFT_END_MODIF);
          if(withEntities)
            {
              *mpa_resultsString += QObject::tr("Left end modif: %1 - ")
                                      .arg(mp_polymer->leftEndModif().name());
            }

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

      *mpa_resultsString += QObject::tr("\n\nElemental composition: %1")
                              .arg(m_ui.elementalCompositionLineEdit->text());
    }
  else if(target == TARGET_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("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
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 msXpSmassXpert
