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


/////////////////////// Local includes
#include <massxpert/gui/MzLabOutputOligomerTableView.hpp>
#include <massxpert/gui/MzLabOutputOligomerTableViewModel.hpp>
#include <massxpert/gui/MzLabOutputOligomerTableViewSortProxyModel.hpp>
#include <massxpert/gui/SequenceEditorWnd.hpp>


namespace msXpSmassXpert
{

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

  setAlternatingRowColors(true);

  setSortingEnabled(true);

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

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


MzLabOutputOligomerTableView::~MzLabOutputOligomerTableView()
{
}


void
MzLabOutputOligomerTableView::setOligomerPairList(
  QList<OligomerPair *> *oligomerPairList)
{
  mp_oligomerPairList = oligomerPairList;
}


const QList<OligomerPair *> *
MzLabOutputOligomerTableView::oligomerPairList()
{
  return mp_oligomerPairList;
}


void
MzLabOutputOligomerTableView::setParentDlg(MzLabOutputOligomerTableViewDlg *dlg)
{
  Q_ASSERT(dlg);
  mp_parentDlg = dlg;
}


MzLabOutputOligomerTableViewDlg *
MzLabOutputOligomerTableView::parentDlg()
{
  return mp_parentDlg;
}


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

  mp_mzLabWnd = wnd;
}


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


void
MzLabOutputOligomerTableView::setSourceModel(
  MzLabOutputOligomerTableViewModel *model)
{
  mp_sourceModel = model;
}


MzLabOutputOligomerTableViewModel *
MzLabOutputOligomerTableView::sourceModel()
{
  return mp_sourceModel;
}


QString *
MzLabOutputOligomerTableView::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();

  // We first have to get the selection model for the proxy model.

  QItemSelectionModel *selModel = selectionModel();

  MzLabOutputOligomerTableViewModel *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 5, because in one single row there are five cells:
  // each cell for each column, and there are 5 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
  // more than one time. 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;

  resultString->append("m/z 1 | z 1 | m/z 2 | z 2 | Error | Match\n\n");

  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.

      // The m/z 1.
      QModelIndex itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_1_MASS_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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

      // The z 1.
      itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_1_CHARGE_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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


      // The m/z 2.
      itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_2_MASS_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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

      // The z 2.
      itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_2_CHARGE_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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

      // The error.
      itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_ERROR_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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

      // The match.
      itemModelIndex =
        modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_MATCH_COLUMN);

      Q_ASSERT(itemModelIndex.isValid());

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


      resultString->append(QString("%1 %2 %3 %4 %5 %6\n")
                             .arg(massString1)
                             .arg(chargeString1)
                             .arg(massString2)
                             .arg(chargeString2)
                             .arg(errorString)
                             .arg(matchString));
    }

  return resultString;
}

void
MzLabOutputOligomerTableView::itemActivated(const QModelIndex &index)
{
  // It might be possible that both the oligomer1 and oligomer2 come
  // from data computed theoretically starting from a polymer
  // sequence opened in an editor window. We thus ask each of these
  // two oligomers for existence of a "SEQUENCE_EDITOR_WND" Prop
  // instance.

  if(!index.isValid())
    return;

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

  QModelIndex sourceIndex = sortModel->mapToSource(index);

  int row = sourceIndex.row();

  OligomerPair *oligomerPair = mp_oligomerPairList->at(row);

  // Which are the oligomers of the pair?

  const Oligomer *oligomer1 = oligomerPair->oligomer1();
  //     qDebug() << __FILE__ << __LINE__
  // 	     << oligomer1->name();

  const Oligomer *oligomer2 = oligomerPair->oligomer2();

  //     qDebug() << __FILE__ << __LINE__
  // 	     << oligomer2->name();

  // Get the Polymer instance pointers for both oligomers.

  const Polymer *polymer1 = oligomer1->polymer();
  const Polymer *polymer2 = oligomer2->polymer();

  // If none of the oligomers have a pointer to a polymer, that
  // might mean that the data have been generated using a drag and
  // drop from textual data.

  if(!polymer1 && !polymer2)
    {
      return;
    }

  // Now, try in turn for oligomer1 and oligomer2 to check if we
  // still have a connection with their respective sequence editor
  // window. If so, then select the corresponding sequence in it.

  // oligomer1
  // =========

  if(polymer1)
    provideEditorWindowFeedback(oligomer1);

  // oligomer2
  // =========

  // We only handle oligomer2 if the polymer from which it comes is
  // different from oligomer1.

  if(polymer2 && (polymer1 != polymer2))
    provideEditorWindowFeedback(oligomer2);
}

void
MzLabOutputOligomerTableView::provideEditorWindowFeedback(
  const Oligomer *oligomer)
{
  if(!oligomer)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

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

  // Get the sequence editor window pointer out of the oligomer,
  // if it has one in the form of a NoDeletePointerProp.

  Prop *prop = const_cast<Oligomer *>(oligomer)->prop("SEQUENCE_EDITOR_WND");

  if(!prop)
    {
      // 	qDebug() << __FILE__ << __LINE__;

      return;
    }

  SequenceEditorWnd *editorWnd = static_cast<SequenceEditorWnd *>(prop->data());

  if(!editorWnd)
    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 - mz Lab"),
                               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();
}

int
MzLabOutputOligomerTableView::duplicateSelectedOligomers(
  OligomerList *oligomerList)
{
  if(!oligomerList)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  int count = 0;

  // Determine what's the selected material, that is, we want to
  // know which column is selected.

  // For each selected oligomer, duplicate it and append to the list
  // passed as argument.

  // We first have to get the selection model for the proxy model.

  QItemSelectionModel *selModel = selectionModel();

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

  // If nothing is selected, then return 0.
  if(!modelIndexListSize)
    return count;

  // Attention, if we select one single row, our modelIndexList will
  // be of size 6, because in one single row there are six cells:
  // each cell for each column. 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 oligomer that many
  // 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;


  // In *this* dialog, there are two sets of columns : m/z 1, z 1,
  // m/z 2, z 2. Now, we have to know what's the column currently
  // selected so that we know what oligomers we want to deal with
  // from the oligomerPair set : oligomer1 (m/z 1, z 1) or oligomer2
  // (m/z 2, z 2).

  // Get the first index, just to know which is the column.

  QModelIndex oligomerIndex = modelIndexList.at(0);
  Q_ASSERT(oligomerIndex.isValid());

  int column      = oligomerIndex.column();
  int oligomerSet = -1;

  if(column == MZ_LAB_OUTPUT_OLIGO_1_MASS_COLUMN ||
     column == MZ_LAB_OUTPUT_OLIGO_1_CHARGE_COLUMN)
    oligomerSet = 1;
  else if(column == MZ_LAB_OUTPUT_OLIGO_2_MASS_COLUMN ||
          column == MZ_LAB_OUTPUT_OLIGO_2_CHARGE_COLUMN)
    oligomerSet = 2;
  else
    return count;

  // We now know if we have to deal with oligomers 1 or oligomers 2
  // in the OligomerPair list.

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

      Q_ASSERT(oligomerIndex.isValid());

      // Get to know what's the row of the index, so that we can get
      // to the oligomer.

      int row = oligomerIndex.row();

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

      const Oligomer *iterOligomer;

      if(oligomerSet == 1)
        iterOligomer = mp_oligomerPairList->at(iter)->oligomer1();
      else
        iterOligomer = mp_oligomerPairList->at(iter)->oligomer2();

      // Now perform the duplication.
      Oligomer *newOligomer = new Oligomer(*iterOligomer);

      oligomerList->append(newOligomer);

      ++count;
    }

  return count;
}


} // namespace msXpSmassXpert
