/* 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 <QDebug>
#include <QMouseEvent>
#include <QHeaderView>
#include <QMessageBox>

/////////////////////// Local includes
#include "mzLabInputOligomerTableView.hpp"
#include "mzLabInputOligomerTableViewModel.hpp"

#include "oligomer.hpp"

#include "mzLabInputOligomerTableViewDlg.hpp"

#include "cleaveOligomerTableViewMimeData.hpp"
#include "fragmentOligomerTableViewMimeData.hpp"
#include "massSearchOligomerTableViewMimeData.hpp"

#include "application.hpp"

#include "coordinates.hpp"

namespace massXpert
{

  MzLabInputOligomerTableView::MzLabInputOligomerTableView(QWidget *parent)
    : QTableView(parent)
  {

    setAlternatingRowColors(true);

    setSortingEnabled(true);

    setAcceptDrops(true);
    setDropIndicatorShown(true);

    QHeaderView *headerView = horizontalHeader();
    headerView->setSectionsClickable(true);
    headerView->setSectionsMovable(true);

    connect(this,
            SIGNAL(activated(const QModelIndex &)),
            this,
            SLOT(itemActivated(const QModelIndex &)));
  }


  MzLabInputOligomerTableView::~MzLabInputOligomerTableView()
  {

  }

  void
  MzLabInputOligomerTableView::setOligomerList(OligomerList *oligomerList)
  {
    mp_oligomerList = oligomerList;
  }


  const OligomerList *
  MzLabInputOligomerTableView::oligomerList()
  {
    return mp_oligomerList;
  }


  void
  MzLabInputOligomerTableView::setParentDlg(MzLabInputOligomerTableViewDlg *dlg)
  {
    Q_ASSERT(dlg);

    mp_parentDlg = dlg;
  }


  MzLabInputOligomerTableViewDlg *
  MzLabInputOligomerTableView::parentDlg()
  {
    return mp_parentDlg;
  }

  void
  MzLabInputOligomerTableView::setMzLabWnd(MzLabWnd *wnd)
  {
    Q_ASSERT(wnd);

    mp_mzLabWnd = wnd;
  }


  MzLabWnd *
  MzLabInputOligomerTableView::mzLabWnd()
  {
    return mp_mzLabWnd;
  }


  void
  MzLabInputOligomerTableView::setSourceModel
  (MzLabInputOligomerTableViewModel *model)
  {
    mp_sourceModel = model;
  }


  MzLabInputOligomerTableViewModel *
  MzLabInputOligomerTableView::sourceModel()
  {
    return mp_sourceModel;
  }


  void
  MzLabInputOligomerTableView::mousePressEvent(QMouseEvent *mouseEvent)
  {
    if (mouseEvent->buttons() & Qt::RightButton)
      {
        //contextMenu->popup(mouseEvent->globalPos());
        return;
      }

    QTableView::mousePressEvent(mouseEvent);
  }


  MassType
  MzLabInputOligomerTableView::pasteOrDropMassTypeCheck()
  {
    // Each time a new drop/paste happens, we have to perform some
    // checks.

    // It is compulsory that the user has selected either mono or avg
    // as the mass type for the parent dialog window. Ask what's being
    // currently checked (mono and avg are two checkboxes).

    MassType previousMassType = mp_parentDlg->previousMassType();
    MassType massType = mp_parentDlg->massType();

    if(massType == MXT_MASS_NONE)
      {
        // Ask the user to clearly indicate what is the mass type
        // required).

        massType = mp_parentDlg->massTypeQuery();
      }

    // By now we have a current massType.

    // Check if one drop already occurred in the past and if it was
    // with the same mass type as now. Otherwise alert the user.

    if (previousMassType != MXT_MASS_NONE && previousMassType != massType)
      {
        int ret =
          QMessageBox::question(this, tr("massXpert: mzLab"),
                                tr("You are switching mass types."
                                   "Ok to continue ?"),
                                QMessageBox::Yes | QMessageBox::No,
                                QMessageBox::No);

        if(ret == QMessageBox::No)
          {
            return MXT_MASS_NONE;
          }
      }

    return massType;
  }
  
    
  void
  MzLabInputOligomerTableView::dragEnterEvent(QDragEnterEvent *event)
  {
    const CleaveOligomerTableViewMimeData *tableViewData =
      qobject_cast<const CleaveOligomerTableViewMimeData *>(event->mimeData());

    if (tableViewData)
      {
        event->acceptProposedAction();
        event->accept();
        return ;
      }
    else if (event->mimeData()->hasFormat("text/plain"))
      {
        event->acceptProposedAction();
        event->accept();
        return ;
      }

    event->ignore();

    return;
  }


  void
  MzLabInputOligomerTableView::dragMoveEvent(QDragMoveEvent *event)
  {
    event->setDropAction(Qt::CopyAction);
    event->accept();
  }


  void
  MzLabInputOligomerTableView::pasteEvent()
  {
    MassType massType = pasteOrDropMassTypeCheck();
    
    if (massType == MXT_MASS_NONE)
      {
        // The user did not want to answer positively to our
        // question. Conservatory behaviour.

        // qDebug() << __FILE__ << __LINE__
        //          << "massType:" << massType ;
        
        return;
      }

    OligomerList *oligomerList = new OligomerList("NOT_SET");
    
    QClipboard *clipboard = QApplication::clipboard();

    QString text = clipboard->text(QClipboard::Clipboard);

    // qDebug() << __FILE__ << __LINE__
    //          << "text:" << text;
    
    // Now that we have the text we can parse it. Hand over that
    // parsing to the function that handles simple text drop/paste.

    testSimpleTextDataDroppedOrPasted(text, oligomerList);

    // At this point we should have an oligomerList with oligomers in
    // it.

    if (!oligomerList->size())
      {
        delete oligomerList;

        return;
      }

    // We use a sort proxy model, but when adding oligomers to the
    // model we *have to* use the source model, that is not the proxy
    // model. Thus we have written a function in *this* TableView that
    // returns the source model.

    MzLabInputOligomerTableViewModel *tableViewModel = sourceModel();

    while(oligomerList->size())
      {
        // At this point we want to add the oligomers to the model
        // data. we simultaneously remove the oligomer from the
        // list as the ownership passes to the model.
        tableViewModel->addOligomer(oligomerList->takeFirst());
      }

    delete oligomerList;

    // Let the dialog know that we have set masses of type
    // massType. This way, we'll be able to check that later drops
    // are for the same mass type.
    mp_parentDlg->setPreviousMassType(massType);

    return;
  }
  

  void
  MzLabInputOligomerTableView::dropEvent(QDropEvent *event)
  {
    MassType massType = pasteOrDropMassTypeCheck();
    
    if (massType == MXT_MASS_NONE)
      {
        // The user did not want to answer positively to our
        // question. Conservatory behaviour.
        event->ignore();
        return;
      }
    
    OligomerList *oligomerList = new OligomerList("NOT_SET");

    // We may have data from different locations. We test them in
    // sequence. In the called functions, we get the data from the
    // dropped material into the temporary oligomerList allocated
    // right above.

    // A cleavage (CleaveOligomerTableViewMimeData).
    if(testCleaveOligomerDataDropped(event, oligomerList) == -1)
      {
        // A fragmentation (FragmentOligomerTableViewMimeData).
        if(testFragmentOligomerDataDropped(event, oligomerList) == -1)
          {
            // A mass search (MassSearchOligomerTableViewMimeData).
            if(testMassSearchOligomerDataDropped(event, oligomerList) == -1)
              {
                // An arbitrary text.
                testSimpleTextDataDropped(event, oligomerList);
              }

          }
      }

    // At this point we should have an oligomerList with oligomers in
    // it.

    if (!oligomerList->size())
      {
        event->ignore();

        delete oligomerList;

        return;
      }


    // We use a sort proxy model, but when adding oligomers to the
    // model we *have to* use the source model, that is not the proxy
    // model. Thus we have written a function in *this* TableView that
    // returns the source model.

    MzLabInputOligomerTableViewModel *tableViewModel = sourceModel();

    while(oligomerList->size())
      {
        // At this point we want to add the oligomers to the model
        // data. we simultaneously remove the oligomer from the
        // list as the ownership passes to the model.
        tableViewModel->addOligomer(oligomerList->takeFirst());
      }

    // We want the action to be a copy action and not a move
    // action because the data come from "static" data residing in
    // the tableView of the cleavage dialog window.
    event->setDropAction(Qt::CopyAction);
    event->accept();

    delete oligomerList;

    // Let the dialog know that we have set masses of type
    // massType. This way, we'll be able to check that later drops
    // are for the same mass type.
    mp_parentDlg->setPreviousMassType(massType);

    return;
  }


  int
  MzLabInputOligomerTableView::testCleaveOligomerDataDropped
  (QDropEvent *event, OligomerList *oligomerList)
  {
    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();

    const CleaveOligomerTableViewMimeData *tableViewMimeData =
      qobject_cast<const CleaveOligomerTableViewMimeData *>
      (event->mimeData());

    if(!tableViewMimeData)
      return -1;

    const CleaveOligomerTableView *tableView =
      tableViewMimeData->tableView();

    // Perform the copying of the data... The list below will
    // receive a copy of each oligomer in the drag start tableView.

    int count = tableView->selectedOligomers(oligomerList,
                                             oligomerList->size());

    if(!count)
      {
        return 0;
      }

    // We absolutely have to change the mp_polChemDef of the oligomer,
    // as it points to the polymer chemistry definition tied to the
    // sequence editor window's polymer, but we cannot make sure that
    // polymer chemistry definition will remain there. Thus we change
    // that polymer chemistry definition pointer to point to ours.

    for (int iter = 0; iter < oligomerList->size(); ++iter)
      {
        Oligomer *oligomer = oligomerList->at(iter);

        oligomer->setPolChemDef(polChemDef);
      }

    return count;
  }


  int
  MzLabInputOligomerTableView::testFragmentOligomerDataDropped
  (QDropEvent *event, OligomerList *oligomerList)
  {
    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();

    const FragmentOligomerTableViewMimeData *tableViewMimeData =
      qobject_cast<const FragmentOligomerTableViewMimeData *>
      (event->mimeData());

    if(!tableViewMimeData)
      return -1;

    // The data are from a fragmentation simulated in
    // massXpert, thus we can set the data format to fragment:
    mp_parentDlg->setFragment(true);

    const FragmentOligomerTableView *tableView  =
      tableViewMimeData->tableView();

    // Perform the copying of the data... The list below will
    // receive a copy of each oligomer in the drag start tableView.

    int count = tableView->selectedOligomers(oligomerList,
                                             oligomerList->size());

    if (!count)
      {
        return 0;
      }

    // We absolutely have to change the mp_polChemDef of the oligomer,
    // as it points to the polymer chemistry definition tied to the
    // sequence editor window's polymer, but we cannot make sure that
    // polymer chemistry definition will remain there. Thus we change
    // that polymer chemistry definition pointer to point to ours.

    for(int iter = 0; iter < oligomerList->size(); ++iter)
      {
        FragmentOligomer *oligomer =
          static_cast<FragmentOligomer *>(oligomerList->at(iter));

        oligomer->setPolChemDef(polChemDef);

        // qDebug() << __FILE__ << __LINE__
        //          << "charge:" << oligomer->charge();
        // oligomer->ionizeRule().debugPutStdErr();
      }

    return count;
  }


  int
  MzLabInputOligomerTableView::testMassSearchOligomerDataDropped
  (QDropEvent *event, OligomerList *oligomerList)
  {
    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();

    const MassSearchOligomerTableViewMimeData *tableViewMimeData =
      qobject_cast<const MassSearchOligomerTableViewMimeData *>
      (event->mimeData());

    if (!tableViewMimeData)
      return -1;

    const MassSearchOligomerTableView *tableView  =
      tableViewMimeData->tableView();

    // Perform the copying of the data... The list below will
    // receive a copy of each oligomer in the drag start tableView.

    int count = tableView->selectedOligomers(oligomerList,
                                             oligomerList->size());

    if(!count)
      {
        return 0;
      }

    // We absolutely have to change the mp_polChemDef of the oligomer,
    // as it points to the polymer chemistry definition tied to the
    // sequence editor window's polymer, but we cannot make sure that
    // polymer chemistry definition will remain there. Thus we change
    // that polymer chemistry definition pointer to point to ours.

    for (int iter = 0; iter < oligomerList->size(); ++iter)
      {
        Oligomer *oligomer = oligomerList->at(iter);

        oligomer->setPolChemDef(polChemDef);
      }

    return count;
  }


  int
  MzLabInputOligomerTableView::
  testSimpleTextDataDroppedOrPasted(QString &text,
                                    OligomerList *oligomerList)
  {
    int count = 0;

    // Note that if the fragments checkbox is checked, then oligomers
    // that are created here on the basis of the data should be
    // FragmentOligomer instances specially, for their ionization
    // rule, to take into account the fact that a FragmentOligomer
    // instance of charge one has actually no ionizeRule formula, as
    // it was created ionized from the beginning.

    // The text that we get might have a single item per line, in
    // which case this is the mass 'm', or two items per line
    // separated with a blank, then these are the mass 'm' and the
    // charge 'z'. Thre might be other data, like the name of the
    // oligomer and the coordinates. Especially if we have the text
    // that comes indirectly from massXpert.

    // First, split the text file into lines.
    QStringList stringList = text.split("\n",
                                        QString::SkipEmptyParts,
                                        Qt::CaseSensitive);
    // qDebug() << __FILE__ << __LINE__
    //          << "stringList:" << stringList;

    // At this point, each line of the file is in a single string of
    // the list . We have to parse each line to extract, either only
    // the mass, or the mass and the charge or the mass, the charge
    // and the specifications of the oligomer (coordinates and name).

    // Remember that the data should be read using the
    // locale of the system.

    QString delimiter = mp_parentDlg->textFieldDelimiter();

    // qDebug() << __FILE__ << __LINE__
    //          << "delimiter:" << delimiter;
    
    for (int iter = 0; iter < stringList.size(); ++iter)
      {
        QString text = stringList.at(iter);

        // qDebug() << __FILE__ << __LINE__
        //          << "text:" << text;
        
        QStringList stringList = text.split(delimiter,
                                            QString::SkipEmptyParts);

        // qDebug() << __FILE__ << __LINE__
        //          << "stringList:" << stringList;
        

        int stringListSize = stringList.size();

        Oligomer *oligomer = 0;
        
        if (stringListSize == 0)
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Drag and drop"),
                                 tr("%1@%2\n"
                                    "Failed to parse line.")
                                 .arg(__FILE__)
                                 .arg(__LINE__)
                                 .arg(stringList.first()));
            return -1;
          }
        else if (stringListSize == 1)
          {
            oligomer = parseOnlyMassLine(stringList);
          }
        else if (stringListSize == 2)
          {
            oligomer = parseMassAndChargeLine(stringList);
          }
        else // (stringListSize > 2)
          {
            oligomer = parseFullLine(stringList);
          }

        // At this point we should have one oligomer back from the
        // called function.
        if (!oligomer)
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Drag and drop"),
                                 tr("%1@%2\n"
                                    "Failed to parse line:\n"
                                    "%3")
                                 .arg(__FILE__)
                                 .arg(__LINE__)
                                 .arg(stringList.first()));
            return -1;
          }
        else
          {
            oligomerList->append(oligomer);
            count++;
          }
      }
    // End of 
    // for (int iter = 0; iter < stringList.size(); ++iter)

    return count;
  }
  

  int
  MzLabInputOligomerTableView::testSimpleTextDataDropped
  (QDropEvent *event, OligomerList *oligomerList)
  {
    if(!event->mimeData()->hasText())
      {
        return -1;
      }

    QString text = event->mimeData()->text();

    int count = testSimpleTextDataDroppedOrPasted(text, oligomerList);
    if (count == -1)
      {
        event->ignore();
        return -1;
      }
    
    return count;
  }
      
    
  Oligomer *
  MzLabInputOligomerTableView::parseOnlyMassLine(QStringList &stringList)
  {
    // There is only the mass in the line.

    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();
    const QList<Atom*> &atomList = polChemDef->atomList();

    bool isFragment = mp_parentDlg->isFragment();
    IonizeRule ionizeRule = mp_mzLabWnd->ionizeRule();
    int totalCharge = ionizeRule.charge() * ionizeRule.level();

    bool ok = false;
    double mass = locale.toDouble(stringList.first(), &ok);

    if (!mass && !ok)
      {
        QMessageBox::warning(this,
                             tr("massXpert - Drag and drop"),
                             tr("%1@%2\n"
                                "Failed to convert %3 to double.\n"
                                "Make sure the proper delimiter is used.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(stringList.first()));
        return 0;
      }

    Oligomer *oligomer = 0;

    if(!isFragment)
      {
        // Note that if the ionizeRule has a total charge
        //(charge() * level()), then we have to set this
        // oligomer to charged state(totalCharge ? true : false).
        oligomer =
          new Oligomer(polChemDef, "NOT_SET",
                       Ponderable(mass, mass),
                       ionizeRule,
                       CalcOptions(),
                       (totalCharge ? true : false),
                       -1, -1);

        // qDebug() << __FILE__ << __LINE__
        //          << "ionizerule:" << ionizeRule.charge()
        //          << ionizeRule.level();
      }
    else
      {
        // We are dealing with an oligomer
        // obtained via fragmentation, its
        // treatement is thus somewhat more
        // subtle.

        if(totalCharge == 1)
          {
            // The fragment oligomer is
            // charged only once, which means
            // we are creating an oligomer
            // with no ionization rule, as, by
            // defintion a fragmentation
            // oligomer is created charged one
            // with no proactive ionization.

            IonizeRule tempIonizeRule;

            oligomer =
              new Oligomer(polChemDef, "NOT_SET",
                           Ponderable(mass, mass),
                           tempIonizeRule,
                           CalcOptions(),
                           false,
                           -1, -1);
          }
        else
          {
            // The oligomer is ionized more
            // than once. The ionization rule
            // that we'll use is ionizeRule.

            IonizeRule tempIonizeRule;

            tempIonizeRule.setFormula(ionizeRule.formula());

            int incrementCharge = totalCharge - 1;
            int requiredLevel = incrementCharge / ionizeRule.charge();

            tempIonizeRule.setCharge(ionizeRule.charge());
            tempIonizeRule.setLevel(requiredLevel);

            if (!tempIonizeRule.validate(atomList))
              {
                QMessageBox::warning(this,
                                     tr("massXpert - Drag and drop"),
                                     tr("%1@%2\n"
                                        "Failed to validate the "
                                        "ionization rule")
                                     .arg(__FILE__)
                                     .arg(__LINE__));
                return 0;
              }

            // Create an oligomer that IS
            // ionized.
            oligomer =
              new Oligomer(polChemDef, "NOT_SET",
                           Ponderable(mass, mass),
                           tempIonizeRule,
                           CalcOptions(),
                           true,
                           -1, -1);
          }
      }


    // qDebug() << __FILE__ << __LINE__
    //          << "charge:" << oligomer->charge();
    // oligomer->ionizeRule().debugPutStdErr();

    return oligomer;
  }



  Oligomer *
  MzLabInputOligomerTableView::parseMassAndChargeLine(QStringList &stringList)
  {
    // One mass, one charge.

    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();
    const QList<Atom*> &atomList = polChemDef->atomList();

    bool isFragment = mp_parentDlg->isFragment();
    IonizeRule ionizeRule = mp_mzLabWnd->ionizeRule();
    int totalCharge = ionizeRule.charge() * ionizeRule.level();

    bool ok = false;
    double mass = locale.toDouble(stringList.first(), &ok);

    if (!mass && !ok)
      {
        QMessageBox::warning(this,
                             tr("massXpert - Drag and drop"),
                             tr("%1@%2\n"
                                "Failed to convert %3 to double")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(stringList.first()));
        return 0;
      }

    // Work with the charge
    int charge = locale.toInt(stringList.at(1), &ok);

    if (!charge && !ok)
      {
        QMessageBox::warning(this,
                             tr("massXpert - Drag and drop"),
                             tr("%1@%2\n"
                                "Failed to convert %3 to int")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(stringList.at(1)));
        return 0;
      }

    if(!isFragment)
      {
        ionizeRule.setLevel(charge);

        // Note that if the ionizeRule has a total charge
        //(charge() * level()), then we have to set this
        // oligomer to charged state(totalCharge ? true : false).
        Oligomer *oligomer =
          new Oligomer(polChemDef, "NOT_SET",
                       Ponderable(mass, mass),
                       ionizeRule,
                       CalcOptions(),
                       (totalCharge ? true : false),
                       -1, -1);

        // If there are coordinates, try to set them in the oliogmer.
        
        

        // qDebug() << __FILE__ << __LINE__
        //          << "charge:" << oligomer->charge();
        // oligomer->ionizeRule().debugPutStdErr();

        return oligomer;
      }
    else // if (isFragment)
      {
        // We are dealing with an oligomer
        // obtained via fragmentation, its
        // treatement is thus somewhat more
        // subtle.

        if(charge == 1)
          {
            // The fragment oligomer is
            // charged only once, which means
            // we are creating an oligomer
            // with no ionization rule, as, by
            // defintion a fragmentation
            // oligomer is created charged one
            // with no proactive ionization.

            IonizeRule tempIonizeRule;

            FragmentOligomer *oligomer =
              new FragmentOligomer(polChemDef, "NOT_SET",
                                   Ponderable(mass, mass),
                                   tempIonizeRule,
                                   false, -1, -1);

            // qDebug() << __FILE__ << __LINE__
            //          << "charge:"
            //          << static_cast<FragmentOligomer *>(oligomer)->
            //   charge();
            // oligomer->ionizeRule().debugPutStdErr();

            return oligomer;
          }
        else if (charge > 1)
          {
            // The oligomer is ionized more
            // than once. The ionization rule
            // that we'll use is ionizeRule.

            IonizeRule tempIonizeRule;

            tempIonizeRule.setFormula(ionizeRule.formula());

            int incrementCharge = charge - 1;
            int requiredLevel = incrementCharge / ionizeRule.charge();

            tempIonizeRule.setCharge(ionizeRule.charge());
            tempIonizeRule.setLevel(requiredLevel);

            if (!tempIonizeRule.validate(atomList))
              {
                QMessageBox::warning(this,
                                     tr("massXpert - Drag and drop"),
                                     tr("%1@%2\n"
                                        "Failed to validate the "
                                        "ionization rule")
                                     .arg(__FILE__)
                                     .arg(__LINE__));
                return 0;
              }

            // Create an oligomer that IS
            // ionized.
            FragmentOligomer *oligomer =

              new FragmentOligomer(polChemDef, "NOT_SET",
                                   Ponderable(mass, mass),
                                   tempIonizeRule,
                                   true, -1, -1, CalcOptions());

            // qDebug() << __FILE__ << __LINE__
            //          << "charge:"
            //          << static_cast<FragmentOligomer *>(oligomer)->
            //   charge();
            // oligomer->ionizeRule().debugPutStdErr();

            return oligomer;
          }
        else if (!charge)
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Drag and drop"),
                                 tr("%1@%2\n"
                                    "Charge of a fragmentation oligomer "
                                    "cannot be 0")
                                 .arg(__FILE__)
                                 .arg(__LINE__));
            return 0;
          }
      }

    // If we are here, then that is a programming error.
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
  
    return 0;
  }
  
  
  Oligomer *
  MzLabInputOligomerTableView::parseFullLine(QStringList &stringList)
  {
    // One mass, one charge and even the name of the oligomer and of the
    // coordinates of that oligomer.
    
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    PolChemDef *polChemDef = mp_mzLabWnd->polChemDef();
    const QList<Atom*> &atomList = polChemDef->atomList();

    bool isFragment = mp_parentDlg->isFragment();
    IonizeRule ionizeRule = mp_mzLabWnd->ionizeRule();
    int totalCharge = ionizeRule.charge() * ionizeRule.level();

    bool ok = false;
    double mass = locale.toDouble(stringList.first(), &ok);

    if (!mass && !ok)
      {
        QMessageBox::warning(this,
                             tr("massXpert - Drag and drop"),
                             tr("%1@%2\n"
                                "Failed to convert %3 to double")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(stringList.first()));
        return 0;
      }

    // Work with the charge
    int charge = locale.toInt(stringList.at(1), &ok);

    if (!charge && !ok)
      {
        QMessageBox::warning(this,
                             tr("massXpert - Drag and drop"),
                             tr("%1@%2\n"
                                "Failed to convert %3 to int")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(stringList.at(1)));

        return 0;
      }

    // Now get all we can get out of the remaining items. We know that
    // the oligomer name must have a '#' in it, and that the
    // coordinates must have [ and ] in them.

    QString oligoName = "NOT_SET";
    QString coordinates;

    int listSize = stringList.size();
    
    for (int iter = 2; iter < listSize; ++iter)
      {
        QString stringItem = stringList.at(iter);
        
        if (stringItem.contains("#"))
          {
            // That must be the name of the oligomer,
            oligoName = stringItem;
          }
        else if (stringItem.contains("["))
          {
            // That must be the name of the oligomer, Since we know
            // the coordinates, set these into the oligomer.

            // qDebug() << __FILE__ << __LINE__
            //          << "stringItem:" << stringItem;
            
            coordinates = stringItem;

            // qDebug() << __FILE__<< __LINE__
            //          << "coordinates:" << coordinates;
          }
      }

    if(!isFragment)
      {
        ionizeRule.setLevel(charge);
        
        // Note that if the ionizeRule has a total charge
        //(charge() * level()), then we have to set this
        // oligomer to charged state(totalCharge ? true : false).
        Oligomer *oligomer =
          new Oligomer(polChemDef, oligoName,
                       Ponderable(mass, mass),
                       ionizeRule,
                       CalcOptions(),
                       (totalCharge ? true : false),
                       -1, -1);

        // Set coordinates as previously characterized.
        oligomer->setCoordinates(coordinates);
        
        return oligomer;
      }
    else // if (isFragment)
      {
        // We are dealing with an oligomer
        // obtained via fragmentation, its
        // treatement is thus somewhat more
        // subtle.

        if(charge == 1)
          {
            // The fragment oligomer is
            // charged only once, which means
            // we are creating an oligomer
            // with no ionization rule, as, by
            // defintion a fragmentation
            // oligomer is created charged one
            // with no proactive ionization.

            IonizeRule tempIonizeRule;

            FragmentOligomer *oligomer =
              new FragmentOligomer(polChemDef, oligoName,
                                   Ponderable(mass, mass),
                                   tempIonizeRule,
                                   false, -1, -1);

            // qDebug() << __FILE__ << __LINE__
            //          << "charge:"
            //          << static_cast<FragmentOligomer *>(oligomer)->
            //   charge();
            // oligomer->ionizeRule().debugPutStdErr();

            // Set coordinates as previously characterized.
            oligomer->setCoordinates(coordinates);
        
            return oligomer;
          }
        else if (charge > 1)
          {
            // The oligomer is ionized more
            // than once. The ionization rule
            // that we'll use is ionizeRule.

            IonizeRule tempIonizeRule;

            tempIonizeRule.setFormula(ionizeRule.formula());

            int incrementCharge = charge - 1;
            int requiredLevel = incrementCharge / ionizeRule.charge();

            tempIonizeRule.setCharge(ionizeRule.charge());
            tempIonizeRule.setLevel(requiredLevel);

            if (!tempIonizeRule.validate(atomList))
              {
                QMessageBox::warning(this,
                                     tr("massXpert - Drag and drop"),
                                     tr("%1@%2\n"
                                        "Failed to validate the "
                                        "ionization rule")
                                     .arg(__FILE__)
                                     .arg(__LINE__));
                return 0;
              }

            // Create an oligomer that IS
            // ionized.
            FragmentOligomer *oligomer =

              new FragmentOligomer(polChemDef, oligoName,
                                   Ponderable(mass, mass),
                                   tempIonizeRule,
                                   true, -1, -1, CalcOptions());

            // qDebug() << __FILE__ << __LINE__
            //          << "charge:"
            //          << static_cast<FragmentOligomer *>(oligomer)->
            //   charge();
            // oligomer->ionizeRule().debugPutStdErr();
            
            // Set coordinates as previously characterized.
            oligomer->setCoordinates(coordinates);
            
            return oligomer;
          }
        else if (!charge)
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Drag and drop"),
                                 tr("%1@%2\n"
                                    "Charge of a fragmentation oligomer "
                                    "cannot be 0")
                                 .arg(__FILE__)
                                 .arg(__LINE__));
            return 0;
          }
      }

    // If we are here, then that is a programming error.
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
  
    return 0;
  }



  QString *
  MzLabInputOligomerTableView::selectedDataAsPlainText()
  {
    // Provide a most simple list of (m/z,z) pairs like 1021.21 2 for
    // all the selected items in the table view.

    QString *resultString = new QString();

    // Get the delimiter to separate the text fields.
    QString delimiter = mp_parentDlg->textFieldDelimiter();
    
    // We first have to get the selection model for the proxy model.

    QItemSelectionModel *selModel = selectionModel();

    MzLabInputOligomerTableViewModel* modelSource = sourceModel();

    // Now get the selection ranges.

    QItemSelection proxyItemSelection = selModel->selection();

    QSortFilterProxyModel *sortModel =
      static_cast<QSortFilterProxyModel *>(model());

    QItemSelection sourceItemSelection =
      sortModel->mapSelectionToSource(proxyItemSelection);

    QModelIndexList modelIndexList = sourceItemSelection.indexes();

    int modelIndexListSize = modelIndexList.size();

    // Attention, if we select one single row, our modelIndexList will
    // be of size 2, because in one single row there are two cells:
    // each cell for each column, and there are 2 columns. Thus, when
    // we iterate in the modelIndexList, we'll have to take care of
    // this and make sure we are not putting each selected row's data
    // two times. For this, we make sure we are not handling the same
    // row twice or more, by storing the processed rows in a list of
    // integers and by checking for existence of that row each time a
    // new index is processed.

    QList<int> processedRowList;

    for (int iter = 0; iter < modelIndexListSize; ++iter)
      {
        QModelIndex index = modelIndexList.at(iter);

        Q_ASSERT(index.isValid());

        // Get to know what's the row of the index.

        int row = index.row();

        if(processedRowList.contains(row))
          continue;
        else
          processedRowList.append(row);

        // Get the m/z and the z values through the use of the model's
        // data function, which expects an index, which we create
        // using the model's index() function below.

        // First the m/z.
        QModelIndex itemModelIndex =
          modelSource->index(row, MZ_LAB_INPUT_OLIGO_MASS_COLUMN);

        Q_ASSERT(itemModelIndex.isValid());

        QString massString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();

        // Second the charge.
        itemModelIndex =
          modelSource->index(row, MZ_LAB_INPUT_OLIGO_CHARGE_COLUMN);

        Q_ASSERT(itemModelIndex.isValid());

        QString chargeString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();

        // Third the name.
        itemModelIndex =
          modelSource->index(row, MZ_LAB_INPUT_OLIGO_NAME_COLUMN);

        Q_ASSERT(itemModelIndex.isValid());

        QString nameString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();

        // Fourth the coords.
        itemModelIndex =
          modelSource->index(row, MZ_LAB_INPUT_OLIGO_COORDS_COLUMN);

        Q_ASSERT(itemModelIndex.isValid());

        QString coordsString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();

        // Bug: if the delimiter is '%', then there is a problem.
        // resultString->append(QString("%1%2%3%4%5%6%7\n")
        //                      .arg(massString)
        //                      .arg(delimiter)
        //                      .arg(chargeString)
        //                      .arg(delimiter)
        //                      .arg(nameString)
        //                      .arg(delimiter)
        //                      .arg(coordsString));
        
        resultString->append(massString);
        resultString->append(delimiter);

        resultString->append(chargeString);
        resultString->append(delimiter);

        resultString->append(nameString);
        resultString->append(delimiter);

        resultString->append(coordsString);

        // Finally go to a new line.
        resultString->append("\n");
      }

    return resultString;
  }


  void
  MzLabInputOligomerTableView::itemActivated(const QModelIndex &index)
  {
    // We want to allow the user to highlight the sequence
    // corresponding to the activated item in the sequence
    // editor. This will work in two situations:

    // 1. If the item has been created upon dropping an item from a
    // massXpert clevage/fragmentation/massSearch dialog window.

    // 2. If the item originated as a text drag-and-drop or clipboard
    // import for which the text had a coordinates string. In this
    // case, the user must have connected the mzLab dialog window with
    // the sequence editor. If that connection went well, the
    // mp_parentDlg->mp_seqEditorWnd (in the parent dialog window) is
    // non-null.

    // As a general rule, we first try to get the sequence editor
    // window pointer through the oligomer. If that fails, then try
    // the mp_parentDlg->mp_seqEditorWnd.

    if (!index.isValid())
      return;

    MzLabInputOligomerTableViewSortProxyModel *sortModel =
      static_cast<MzLabInputOligomerTableViewSortProxyModel *>(model());

    QModelIndex sourceIndex = sortModel->mapToSource(index);

    int row = sourceIndex.row();

    Oligomer *oligomer = mp_oligomerList->at(row);

    // We may have two situations here with the data in the table view
    //  coming 
    // 
    // 1. from a drag and drop of items directly from massXpert (tree
    // views of cleavage, fragmentation or mass search). In this case,
    // the oligomer will have a polymer pointer and a property
    // providing the pointer to the sequence editor window.
    // 
    // 2. from a drag and drop or a copy paste from the clipboard of
    // textual data. In this case, the oligomer that were created
    // cannot hold a polymer pointer, neither a property that provides
    // the pointer to the sequence editor window.

    SequenceEditorWnd *editorWnd = 0;

    const Polymer *polymer = oligomer->polymer();

    if (polymer)
      {
        // There must be a pointer to the sequence editor window.
        
        Prop *prop = const_cast<Oligomer *>(oligomer)->
          prop("SEQUENCE_EDITOR_WND");

        if (prop)
          {
            editorWnd = static_cast<SequenceEditorWnd *>(prop->data());
          }
        else
          {
            editorWnd = mp_parentDlg->sequenceEditorWnd();
            
          }
      }
    else
      {
        // We do not have access to the polymer. However, if we can
        // get the pointer to the sequence editor window, we can get
        // the pointer to the polymer.
        editorWnd = mp_parentDlg->sequenceEditorWnd();
        if (editorWnd)
          {
            polymer = editorWnd->polymer();
          }
      }
    
    if(!editorWnd || !polymer)
      {
        return;
      }
        
    CoordinateList *coordinateList =
      static_cast<CoordinateList *>(const_cast<Oligomer *>(oligomer));

    // Remove the previous selection, so that we can start fresh.
    editorWnd->mpa_editorGraphicsView->resetSelection();

    for (int iter = 0; iter < coordinateList->size(); ++iter)
      {
        Coordinates *coordinates = coordinateList->at(iter);

        int start = coordinates->start();
        int end = coordinates->end();

        // qDebug() << __FILE__ << __LINE__
        //          << "oligomer:" << oligomer
        //          << "polymer:" << polymer
        //          << "start:" << start << "end:" << end;

        if(start >= polymer->size() ||
           end >= polymer->size())
          {
            QMessageBox::warning(this,
                                 tr("massXpert - Cleavage"),
                                 tr("%1@%2\n"
                                    "The monomer indices do not correspond "
                                    "to a valid polymer sequence range.\n"
                                    "Avoid modifying the sequence while "
                                    "working with oligomers.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                                 QMessageBox::Ok);

            return;
          }

        editorWnd->mpa_editorGraphicsView->setSelection(*coordinates,
                                                        true, false);
      }

    editorWnd->updateSelectedSequenceMasses();
  }

} // namespace massXpert
