/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.


   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Qt includes
#include <QMessageBox>


/////////////////////// Local includes
#include "application.hpp"
#include "monomerModificationDlg.hpp"


namespace massXpert
{

  MonomerModificationDlg::MonomerModificationDlg(QWidget *parent)
    : QDialog(parent)
  {
    Q_ASSERT(parent);

    m_ui.setupUi(this);

    mp_editorWnd = static_cast<SequenceEditorWnd *>(parent);

    populateAvailableModifList();
    populateMonomerList();
    populateModifAndModifiedMonomerLists();

    updateSelectionData();
    if (m_coordinateList.size() >= 1)
      m_ui.currentSelectionRadioButton->setChecked(true);


    QSettings settings
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("monomer_modification_dlg");

    restoreGeometry(settings.value("geometry").toByteArray());
    m_ui.hSplitter->restoreState(settings.value("hSplitter").toByteArray());
    m_ui.vSplitter->restoreState(settings.value("vSplitter").toByteArray());

    settings.endGroup();

    connect(m_ui.updateCurrentSelectionDataPushButton,
            SIGNAL(clicked()),
            this,
            SLOT(updateSelectionData()));

    connect(m_ui.modifiedMonomerListWidget,
            SIGNAL(itemSelectionChanged()),
            this,
            SLOT(modifiedMonomerListWidgetItemSelectionChanged()));

    connect(m_ui.modifListWidget,
            SIGNAL(itemSelectionChanged()),
            this,
            SLOT(modifListWidgetItemSelectionChanged()));

    m_ui.displayAllModifsCheckBox->setChecked(true);
    connect(m_ui.displayAllModifsCheckBox,
            SIGNAL(stateChanged(int)),
            this,
            SLOT(displayAllModifsChanged(int)));

    connect(m_ui.modifyPushButton,
            SIGNAL(clicked()),
            this,
            SLOT(modify()));

    connect(m_ui.unmodifyPushButton,
            SIGNAL(clicked()),
            this,
            SLOT(unmodify()));

    connect(this,
            SIGNAL(rejected()),
            this,
            SLOT(close()));
  }


  MonomerModificationDlg::~MonomerModificationDlg()
  {
  }


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

    QSettings settings
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("monomer_modification_dlg");

    settings.setValue("geometry", saveGeometry());
    settings.setValue("hSplitter", m_ui.hSplitter->saveState());
    settings.setValue("vSplitter", m_ui.vSplitter->saveState());

    settings.endGroup();
  }



  bool
  MonomerModificationDlg::populateAvailableModifList()
  {
    PolChemDef *polChemDef = mp_editorWnd->polChemDef();
    Q_ASSERT(polChemDef);

    for (int iter = 0; iter < polChemDef->modifList().size(); ++iter)
      {
        Modif *modif = polChemDef->modifList().at(iter);
        Q_ASSERT(modif);

        m_ui.availableModifListWidget->addItem(modif->name());
      }

    return true;
  }


  bool
  MonomerModificationDlg::populateModifAndModifiedMonomerLists()
  {
    // We'll need a pointer to the polymer sequence.
    Polymer *polymer = mp_editorWnd->polymer();

    m_ui.modifiedMonomerListWidget->clear();
    m_ui.modifListWidget->clear();

    // For all the monomers in the polymer sequence, check which
    // modifications they bear. If any, add their name and monomer
    // pointer to the list.

    for (int iter = 0; iter < polymer->size(); ++iter)
      {
        const Monomer *monomer = polymer->at(iter);

        QList<Modif *> *list = monomer->modifList();

        if(!list || list->isEmpty())
          continue;

        // At this point we know the monomer is modified. Let's take the
        // opportunity to add this monomer to the list of modified
        // monomers.

        QString text = QString("%1/%2/%3")
          .arg(monomer->code())
          .arg(iter + 1)
          .arg((quintptr) monomer);

        m_ui.modifiedMonomerListWidget->addItem(text);

        for(int jter = 0; jter < list->size(); ++jter)
          {
            Modif *modif = list->at(jter);

            QString text = QString("%1/%2/%3/%4/%5")
              .arg(modif->name())
              .arg(monomer->code())
              .arg(iter + 1)
              .arg((quintptr) modif)
              .arg((quintptr) monomer);

            m_ui.modifListWidget->addItem(text);
          }
      }

    return true;
  }


  bool
  MonomerModificationDlg::populateModifiedMonomerList()
  {
    // We'll need a pointer to the polymer sequence.
    Polymer *polymer = mp_editorWnd->polymer();

    m_ui.modifiedMonomerListWidget->clear();

    // For all the monomers in the polymer sequence, check if they are
    // modified. If so, add their code position and monomer pointer to
    // the list.

    for (int iter = 0; iter < polymer->size(); ++iter)
      {
        const Monomer *monomer = polymer->at(iter);

        QList<Modif *> *list = monomer->modifList();

        if(!list || list->isEmpty())
          continue;

        QString text = QString("%1/%2/%3")
          .arg(monomer->code())
          .arg(iter + 1)
          .arg((quintptr) monomer);

        m_ui.modifiedMonomerListWidget->addItem(text);
      }

    return true;
  }


  bool
  MonomerModificationDlg::populateModifList(bool all)
  {
    // We'll need a pointer to the polymer sequence.
    Polymer *polymer = mp_editorWnd->polymer();

    // First-off remove all the items.
    m_ui.modifListWidget->clear();

    if (all)
      {
        // For all the monomers in the polymer sequence, check which
        // modifications they bear. If any, add their name and monomer
        // pointer to the list.

        for(int iter = 0; iter < polymer->size(); ++iter)
          {
            const Monomer *monomer = polymer->at(iter);

            QList<Modif *> *modifList = monomer->modifList();

            if (!modifList || modifList->isEmpty())
              continue;

            for (int jter = 0; jter < modifList->size(); ++jter)
              {
                Modif *modif = modifList->at(jter);

                QString text = QString("%1/%2/%3/%4/%5")
                  .arg(modif->name())
                  .arg(monomer->code())
                  .arg(iter + 1)
                  .arg((quintptr) modif)
                  .arg((quintptr) monomer);

                m_ui.modifListWidget->addItem(text);
              }
          }
      }
    else
      {
        // We are interested only in the modifs for the currently
        // selected cross-linked monomers(if any) in the
        // modifiedMonomerListWidget.

        QList<QListWidgetItem *> selectedList =
          m_ui.modifiedMonomerListWidget->selectedItems();

        if(!selectedList.size())
          return true;

        for(int iter = 0; iter < selectedList.size(); ++iter)
          {
            QListWidgetItem *item = selectedList.at(iter);

            // What's the text of the item ?
            QString text = item->text();

            // The string is in the form "code/pos/monomerPtr"

            QStringList stringList = text.split('/',
                                                QString::SkipEmptyParts,
                                                Qt::CaseSensitive );

            // The monomer position is the second string in the list.
            bool ok = false;
            int pos = stringList.at(1).toInt(&ok);

            if (!pos && !ok)
              return false;

            // What if the sequence changed and the monomer is no more
            // in a valid range? We want to avoid a crash. See below for
            // an even better sanity check.
            if (pos - 1 < 0 || pos > polymer->size())
              {
                QMessageBox::warning(this,
                                     tr("massXpert - Modify monomers"),
                                     tr("%1@%2\n"
                                        "The monomer index does not correspond "
                                        "to a valid polymer sequence range.\n"
                                        "Avoid modifying the sequence while "
                                        "working with modifications.")
                                     .arg(__FILE__)
                                     .arg(__LINE__),
                                     QMessageBox::Ok);

                populateModifAndModifiedMonomerLists();

                return false;
              }

            // The monomer(quintptr) pointer is the third string in the list.
            ok = false;
            quintptr pointerCastToInt = stringList.at(2).toInt(&ok);
            Monomer *monomer = reinterpret_cast<Monomer *>(pointerCastToInt);

            // Sanity check, are we dealing with the same monomer now
            // compared to the one of which the item was displayed when
            // the window was opened ?
            if (monomer != polymer->at(pos - 1))
              {
                QMessageBox::warning(this,
                                     tr("massXpert - Modify monomers"),
                                     tr("%1@%2\n"
                                        "The monomer selected does not "
                                        "correspond to a valid sequence "
                                        "monomer.\n"
                                        "Avoid modifying the sequence while "
                                        "working with modifications.")
                                     .arg(__FILE__)
                                     .arg(__LINE__),
                                     QMessageBox::Ok);

                populateModifAndModifiedMonomerLists();

                return false;
              }

            // Iterate in the monomer's modifications and for each one
            // create a new item.

            QList<Modif *> *modifList = monomer->modifList();

            // It is not possible that the monomer has no modif,
            // otherwise how come would it be listed in the list of
            // modified monomers.

            Q_ASSERT(modifList && !modifList->isEmpty());

            for (int jter = 0; jter < modifList->size(); ++jter)
              {
                Modif *modif = modifList->at(jter);

                QString text = QString("%1/%2/%3/%4/%5")
                  .arg(modif->name())
                  .arg(monomer->code())
                  .arg(pos)
                  .arg((quintptr) modif)
                  .arg((quintptr) monomer);

                m_ui.modifListWidget->addItem(text);
              }
          }
        // End of
        // for (int iter = 0; iter < selectedList.size(); ++iter)
      }
    // End of else , that is we only display modifs for selected
    // monomers.

    return true;
  }



  bool
  MonomerModificationDlg::populateMonomerList()
  {
    PolChemDef *polChemDef = mp_editorWnd->polChemDef();
    Q_ASSERT(polChemDef);

    for (int iter = 0; iter < polChemDef->monomerList().size(); ++iter)
      {
        Monomer *monomer = polChemDef->monomerList().at(iter);
        Q_ASSERT(monomer);

        QString text = monomer->code() + '=' + monomer->name();

        m_ui.monomerListWidget->addItem(text);
      }

    return true;
  }


  void
  MonomerModificationDlg::modifiedMonomerListWidgetItemSelectionChanged()
  {
    // When an item is selected in the list of modified monomers, then
    // that means that the user does not want *all* the modifs to be
    // listed.

    m_ui.displayAllModifsCheckBox->setChecked(false);

    // Update the modif list data by listing only the modifs of the
    // currently selected monomer.

    populateModifList(false);
  }


  void
  MonomerModificationDlg::modifListWidgetItemSelectionChanged()
  {
    //   qDebug() << __FILE__ << __LINE__
    // 	    << "MonomerModificationDlg::"
    //     "modifListWidgetItemSelectionChanged()";
  }


  void MonomerModificationDlg::displayAllModifsChanged(int checkState)
  {
    // When checked, we should list all the modifs in the
    // modifListWidget, and not only the modifs for the currently
    // selected modified monomer.

    if (checkState == Qt::Checked)
      populateModifList(true);
    else
      populateModifList(false);
  }


  bool
  MonomerModificationDlg::updateSelectionData()
  {
    // There might be more than one region selections. So get all
    // these coordinates !

    bool res = mp_editorWnd->mpa_editorGraphicsView->
      selectionIndices(&m_coordinateList);

    if (res)
      {
        // If there are more than one region selection or if there is
        // one region selection but spanning more than one monomer,
        // then set the target to be "current selected sequence".

        if(m_coordinateList.size() > 1)
          {
            // Apparently there are multiple regions selected.

            m_ui.currentSelectionLineEdit->
              setText(m_coordinateList.positionsAsText());
          }
        else
          {
            Coordinates *coordinates = m_coordinateList.first();

            if (coordinates->start() == coordinates->end())
              {

                // Construct a string with both the monomer code and
                // its position in the sequence.

                Polymer *polymer = mp_editorWnd->polymer();
                const Monomer *monomer =
                  polymer->monomerList().at(coordinates->start());

                QString text = tr("%1 at pos. %2")
                  .arg(monomer->code())
                  .arg(coordinates->start() + 1);

                m_ui.currentSelectionLineEdit->setText(text);
              }
            else
              m_ui.currentSelectionLineEdit->
                setText(m_coordinateList.positionsAsText());
          }
      }

    // Return if there was a selection(multiple-region or not) or
    // not.
    return res;
  }



  int
  MonomerModificationDlg::prepareIndicesList()
  {
    Polymer *polymer = mp_editorWnd->polymer();

    m_indicesList.clear();

    bool isSelectionPresent = updateSelectionData();

    if (m_ui.currentSelectionRadioButton->isChecked())
      {
        // If there is no selection.
        if(!isSelectionPresent)
          return 0;

        // Now, for each Coordinates in the CoordinateList, append the
        // indices...

        for(int iter = 0; iter < m_coordinateList.size(); ++iter)
          {
            Coordinates *coordinates = m_coordinateList.at(iter);

            for (int iter = coordinates->start();
                 iter < coordinates->end()  + 1; ++iter)
              m_indicesList.append(iter);
          }
      }
    else if (m_ui.sameCodeRadioButton->isChecked())
      {
        // Get the code of the currently selected monomer.
        if(!isSelectionPresent)
          return 0;

        if(m_coordinateList.size() > 1)
          return 0;

        Coordinates *coordinates = m_coordinateList.at(0);

        if(coordinates->start() != coordinates->end())
          return 0;

        QString code =
          polymer->monomerList().at(coordinates->start())->code();

        for(int iter = 0; iter < polymer->monomerList().size(); ++iter)
          {
            const Monomer *monomer = polymer->monomerList().at(iter);
            Q_ASSERT(monomer);

            if (monomer->code() == code)
              m_indicesList.append(iter);
          }
      }
    else if (m_ui.fromListRadioButton->isChecked())
      {
        QList<QListWidgetItem *> selectedList =
          m_ui.monomerListWidget->selectedItems();

        if(!selectedList.size())
          return 0;

        for(int iter = 0; iter < selectedList.size(); ++iter)
          {
            QListWidgetItem *item = selectedList.at(iter);

            QString text = item->text();

            int index = text.indexOf('=');
            Q_ASSERT(index > 0);

            QString code = text.left(index);

            for (int jter = 0 ; jter < polymer->monomerList().size(); ++jter)
              {
                const Monomer *monomer = polymer->monomerList().at(jter);
                Q_ASSERT(monomer);

                if(monomer->code() == code)
                  m_indicesList.append(jter);
              }
          }
      }
    else if (m_ui.allRadioButton->isChecked())
      {
        for(int iter = 0; iter < polymer->monomerList().size(); ++iter)
          m_indicesList.append(iter);
      }
    else
      {
        QMessageBox::warning(this,
                             tr("massXpert - Modify monomers"),
                             tr("No target is selected."),
                             QMessageBox::Ok);;
        return 0;
      }

    //   qDebug() << "Indices:" << m_indicesList.size();

    return m_indicesList.size();
  }


  bool
  MonomerModificationDlg::parseModifDefinition(Modif *modif)
  {
    Q_ASSERT(modif);

    QString text = m_ui.modifNameLineEdit->text();

    modif->setName(text);

    text = m_ui.modifFormulaLineEdit->text();

    modif->setFormula(text);

    // Attention, we have to compute the masses of the modif !

    if (!modif->calculateMasses())
      return false;

    text = m_ui.modifTargetsLineEdit->text();

    modif->setTargets(text);

    if (!modif->validate())
      return false;

    return true;
  }


  void
  MonomerModificationDlg::modify()
  {
    QStringList errorList;

    Polymer *polymer = mp_editorWnd->polymer();

    // There are two ways to perform a modification: either select one
    // modification from the list of available modifications as defined
    // in the polymer chemistry definition, or perform a quick and dirty
    // modification definition in the dialog.

    QString text;

    Modif *modif =  new Modif(polymer->polChemDef(),
                              "NOT_SET",
                              "NOT_SET");

    if (m_ui.defineModifGroupBox->isChecked())
      {
        // The user wants to use a self-defined modification.

        if(!parseModifDefinition(modif))
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Modify monomers"),
                                 tr("The defined modification failed "
                                    "to parse."),
                                 QMessageBox::Ok);
            return;
          }

        // At this point the modification is correct.
      }
    else
      {
        // Get the modification currently selected.
        QList<QListWidgetItem *> selectedList =
          m_ui.availableModifListWidget->selectedItems();

        if(selectedList.size() != 1)
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Modify monomers"),
                                 tr("No modification is selected "
                                    "in the list."),
                                 QMessageBox::Ok);
            return;
          }

        text = selectedList.at(0)->text();
        Q_ASSERT(!text.isEmpty());

        // With the name of the modification get to the modif proper.

        bool res = polymer->polChemDef()->modif(text, modif);
        if(!res)
          qFatal("Fatal error at %s@%d. Program aborted.",
                 __FILE__, __LINE__);

        // At this point the modification is correct. Set a bool to true
        // to know later that the modif was from the polChemDef.
      }

    // Is the modification work with allowed overriding of the target
    // and the max count limitations?
    bool override = m_ui.overrideLimitationsCheckBox->isChecked();

    // Construct a list of all the indices where the modification should
    // apply.
    if (!prepareIndicesList())
      return;

    for (int iter = 0; iter < m_indicesList.size(); ++iter)
      {
        int index = m_indicesList.at(iter);

        const Monomer *monomer = polymer->monomerList().at(index);

        // Append the position and code of the currently iterated
        // monomer. We will remove that string if the result is not
        // bad. If the result below is bad, then the corresponding error
        // will have been appended and we will not remove anything.

        errorList.append(tr("Pos. %1: %2 --------------")
                         .arg(iter + 1)
                         .arg(monomer->code()));

        // Upon modification, the monomer takes ownership of the modif,
        // so we have to allocate one each time using the template
        // prepared above.

        Modif *newModif = new Modif(*modif);

        int ret = const_cast<Monomer *>(monomer)->modify(newModif,
                                                         override,
                                                         errorList);

        // The modification went ok, which means we can remove the last
        // string that we prudentially added in case an error was to be
        // output. If the modification failed(ret == false) we do not
        // perform any modification for the monomer dealt-with.

        if(!ret)
          {
            errorList.append("\n");
          }
        else
          {
            errorList.removeLast();

            mp_editorWnd->setWindowModified(true);

            // We have to make sure that the vignette knows for which
            // chemical entity it is created.

            ret = mp_editorWnd->mpa_editorGraphicsView->
              modifyVignetteAt(index, newModif);

            Q_ASSERT(ret);
          }
      }
    // End of
    // for (int iter = 0; iter < m_indicesList.size(); ++iter)

    m_ui.messagesTextEdit->append(tr("New operation: modify with %1\n")
                                  .arg(modif->name()) + errorList.join(""));

    // At this point we can delete the modif template.
    delete modif;

    populateModifAndModifiedMonomerLists();

    mp_editorWnd->updateWholeSequenceMasses(true);
    mp_editorWnd->updateSelectedSequenceMasses(true);

    //  mp_editorWnd->mpa_editorGraphicsView->updateSequence();
  }


  void
  MonomerModificationDlg::unmodify()
  {
    // We only can unmodify in the following cases:

    // If one or more modifications are selected in the
    // m_ui.modifListWidget(which is a multiple selection list).

    QList<QListWidgetItem *> selectedList =
      m_ui.modifListWidget->selectedItems();

    if (!selectedList.size())
      return;

    // For each item in the selection list, get the item, get to the
    // monomer and perform the unmodification.

    for (int iter = 0; iter < selectedList.size(); ++iter)
      {
        QListWidgetItem *item = selectedList.at(iter);

        // Get the text which is of the form

        // "Phosphorylation/S/12/136958312/136678312",

        // which is the name of the modification, the code of the
        // modified monomer and its position, the pointer to
        // the modification and finally the pointer to
        // the modified monomer".

        QString text = item->text();

        QStringList stringList = text.split('/',
                                            QString::SkipEmptyParts,
                                            Qt::CaseSensitive );

        bool ok = false;

        // The modif(quintptr) pointer is the third string in the list.
        quintptr pointerCastToInt = stringList.at(3).toInt(&ok);
        Modif *modif = reinterpret_cast<Modif *>(pointerCastToInt);

        // The monomer(quintptr) pointer is the fourth string in the
        // list.
        ok = false;
        pointerCastToInt = stringList.at(4).toInt(&ok);
        Monomer *monomer = reinterpret_cast<Monomer *>(pointerCastToInt);

        //Because the sequence might have changed since the moment
        //this window was opened, we should make sure the monomer to
        //unmodify is still there !

        int index = mp_editorWnd->polymer()->monomerIndex(monomer);

        if(index == -1)
          {
            // Hmmm, the monomer is no more in the sequence, the
            // sequence has been modified and the monomer has been
            // erased.

            QMessageBox::warning(this,
                                 tr("massXpert - Modify monomers"),
                                 tr("%1@%2\n"
                                    "The monomer to unmodify does not exist "
                                    "in the sequence anymore.\n"
                                    "Avoid modifying the sequence while "
                                    "working with modifications.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                                 QMessageBox::Ok);

            populateModifAndModifiedMonomerLists();

            return;
          }


        // The monomer index at the time the monomer modification window
        // was opened. Note that the index might change if the user
        // edits the sequence after having opened *this window. Also,
        // note that for the reader to use the value in the list item,
        // it was set as a position, not an index, which means we have
        // to remove one from the value.
        //	ok = false;
        //	int index = stringList.at(2).toInt(&ok) - 1;


        // At this point we know which modification we have to remove
        // and from which monomer.

        int ret = monomer->unmodify(modif);
        if(!ret)
          qFatal("Fatal error at %s@%d. Program aborted.",
                 __FILE__, __LINE__);

        mp_editorWnd->setWindowModified(true);

        // At this point, we have to make sure we remove the
        // modification vignette.

        // Note that because a monomer might be modified more than once,
        // and with the same modification, we ought to remove the proper
        // vignette.

        bool val = mp_editorWnd->mpa_editorGraphicsView->
          unmodifyVignetteAt(index, modif);

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

        //      mp_editorWnd->mpa_editorGraphicsView->updateSequence();
      }

    populateModifAndModifiedMonomerLists();

    mp_editorWnd->updateWholeSequenceMasses(true);
    mp_editorWnd->updateSelectedSequenceMasses(true);
  }

} // namespace massXpert
