/* 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 <QStringList>


/////////////////////// Local includes
#include "Coordinates.hpp"


namespace MsXpS
{

namespace libXpertMass
{

/*!
\class MsXpS::libXpertMass::Coordinates
\inmodule libXpertMass
\ingroup PolChemDefBuildingdBlocks
\inheaderfile Coordinates.hpp

\brief The Coordinates class provides the localization of a sequence region
in a \l Polymer \l Sequence.

The localization of the sequence region is performed by using \e indices
pointing to the location in the Polymer sequence.
*/

/*!
\variable MsXpS::libXpertMass::Coordinates::m_start

\brief The index of the first monomer in the
sequence region described by this Coordinates instance.
*/

/*!
  \variable MsXpS::libXpertMass::Coordinates::m_end

  \brief The index of the last monomer in the
sequence region described by this Coordinates instance.
  */


///////////////////////// Coordinates /////////////////////////
///////////////////////// Coordinates /////////////////////////
///////////////////////// Coordinates /////////////////////////
///////////////////////// Coordinates /////////////////////////
///////////////////////// Coordinates /////////////////////////

/*!
  \brief Constructs a Coordinates object with \a index_start and \a index_end
indices.

 The indices define a region in the \l Sequence. They are sorted so as to
ensure that index_start <= index_end.
  */
Coordinates::Coordinates(int index_start, int index_end)
{
  if(index_start > index_end)
    {
      m_start = index_end;
      m_end   = index_start;
    }
  else
    {
      m_start = index_start;
      m_end   = index_end;
    }
}


/*!
  \brief Constructs a Coordinates object as a copy of \a other.

After assigning \a other to this Coordinates, the indices are sorted so as to
ensure that index_start <= index_end.
  */
Coordinates::Coordinates(const Coordinates &other)
  : m_start(other.m_start), m_end(other.m_end)
{
  int temp;

  if(m_start > m_end)
    {
      temp    = m_end;
      m_end   = m_start;
      m_start = temp;
    }
}

/*!
\brief Destructs this Coordinates instance.
*/
Coordinates::~Coordinates()
{
}

/*!
\brief Set the start index to \a value.

Ensures that m_start is <= m_end.
*/
void
Coordinates::setStart(int value)
{
  if(value > m_end)
    {
      m_start = m_end;
      m_end   = value;
    }
  else
    {
      m_start = value;
    }
}

/*!
\brief Returns the member start index.
*/
int
Coordinates::start() const
{
  return m_start;
}

/*!
\brief Increments by one unit the member end index.
*/
void
Coordinates::incrementEnd()
{
  ++m_end;
}


/*!
\brief Set the end index to \a value.

Ensures that m_start is <= m_end.
*/
void
Coordinates::setEnd(int value)
{
  m_end = value;
}


/*!
\brief Returns the member end index.
*/
int
Coordinates::end() const
{
  return m_end;
}

/*!
\brief Returns the number of monomer codes in the region described by this
Coordinates instance.

  \code
  return (m_end - m_start + 1)
  \endcode
*/
int
Coordinates::length() const
{
  return (m_end - m_start + 1);
}

/*!
\brief Returns a string with the region described by this Coordinates
instance.

The values are the member indices and the format is

\code
[125--259]
\endcode
*/
QString
Coordinates::indicesAsText() const
{
  QString text = QString("[%1--%2]").arg(m_start).arg(m_end);

  return text;
}


/*!
\brief Returns a string with the region described by this Coordinates
instance.

The values are the member indices \e{incremented} by one unit (thus being
\e{positions} and not \e{indices}) and the format is

\code
[126--260]
\endcode
*/
QString
Coordinates::positionsAsText() const
{
  QString text = QString("[%1--%2]").arg(m_start + 1).arg(m_end + 1);

  return text;
}

/*!
\brief Resets the member indices to 0.
*/
void
Coordinates::reset()
{
  m_start = 0;
  m_end   = 0;
}


/*!
\class MsXpS::libXpertMass::CoordinateList
\inmodule libXpertMass
\ingroup PolChemDefBuildingdBlocks

\brief The CoordinateList class provides a list of \l Coordinates
allocated instances.

The Coordinates instances are allocated on the heap and stored as a QList.
*/

/*!
  \variable int MsXpS::libXpertMass::CoordinateList::m_comment

  \brief A comment string.
  */

///////////////////////// CoordinateList /////////////////////////
///////////////////////// CoordinateList /////////////////////////
///////////////////////// CoordinateList /////////////////////////
///////////////////////// CoordinateList /////////////////////////
///////////////////////// CoordinateList /////////////////////////

/*!
\brief Constructs a CoordinateList using \a comment and \a list.

  The \l Coordinates instances in \a list are duplicated and stored in this
CoordinateList.
*/
CoordinateList::CoordinateList(QString comment, QList<Coordinates *> *list)
  : m_comment(comment)
{
  if(list)
    {
      for(int iter = 0; iter < list->size(); ++iter)
        {
          Coordinates *iterCoordinates = list->at(iter);

          Coordinates *coordinates = new Coordinates(*iterCoordinates);

          append(coordinates);
        }
    }
}

/*!
\brief Constructs a CoordinateList as a copy of \a other.

  The copy is a deep copy, with all the \l Coordinates instances in \a other
duplicated and stored in this CoordinateList.
*/
CoordinateList::CoordinateList(const CoordinateList &other)
  : QList(), m_comment(other.m_comment)
{
  for(int iter = 0; iter < other.size(); ++iter)
    {
      Coordinates *iterCoordinates = other.at(iter);

      Coordinates *coordinates = new Coordinates(*iterCoordinates);

      append(coordinates);
    }
}

/*!
\brief Destructs this CoordinateList.

The \l Coordinates instances are freed.
*/
CoordinateList::~CoordinateList()
{
  // The members of the list were allocated with new()...
  qDeleteAll(begin(), end());
  clear();
}

/*!
\brief Assigns to this CoordinateList the members of \a other.

The copy involves duplicationg all the Coordinates objects in \a other and all
other members.

Returns a reference to this CoordinateList instance.
*/
CoordinateList &
CoordinateList::operator=(const CoordinateList &other)
{
  if(&other == this)
    return *this;

  m_comment = other.m_comment;

  empty();

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

      Coordinates *coordinates = new Coordinates(*iterCoordinates);

      append(coordinates);
    }

  return *this;
}

/*!
\brief Adds a copy of \a coordinates to this CoordinateList instance.

\note This CoordinateList's list of Coordinates instances is first emptied,
essentially replacing its contents with a copy of \a coordinates.
*/
void
CoordinateList::setCoordinates(const Coordinates &coordinates)
{
  empty();

  Coordinates *newCoordinates = new Coordinates(coordinates);
  append(newCoordinates);
}


/*!
\brief Allocates a copy of each Coordinates instance in \a list and adds
it to this CoordinateList instance.

\note This CoordinateList's list of Coordinates instances is first emptied,
essentially replacing its contents with a copy of those in \a list.
*/
void
CoordinateList::setCoordinates(const CoordinateList &list)
{
  empty();

  qDebug() << "The list of Coordinates:" << list.size();

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

      qDebug() << "Created new copy of Coordinates:"
               << coordinates->indicesAsText();

      append(coordinates);
    }
}


/*!
\brief Add a copy of \a coordinates this CoordinateList instance.
*/
void
CoordinateList::appendCoordinates(const Coordinates &coordinates)
{
  Coordinates *newCoordinates = new Coordinates(coordinates);

  append(newCoordinates);
}

/*!
\brief Creates the Coordinates instances based on \a coordinates_string and add
them to this CoordinateList's list of Coordinates.

\note This CoordinateList's list of Coordinates instances is first emptied

The format of the \a coordinates_string is

\code
"[228-246]"
\endcode

if there are not multiple regions. If there are multiple regions (for example
when a cross-link exists), the format changes to account for the multiple
regions:

\code
"[228-246][276-282][247-275]"
\endcode

\note It is expected that the values in the coordinates_string are \e positions
strings and not \e indices.

Returns the count of added Coordinates instances or -1 if an error occurred.
*/
int
CoordinateList::setCoordinates(const QString &coordinates_string)
{
  // We get a string in the form [xxx-yyy] (there can be more than
  // one such element, if cross-linked oligomers are calculated.
  // "[228-246][276-282][247-275]". Because that string comes from
  // outside of massXpert, it is expected that it contains positions
  // and not indexes. So we have to decrement the start and end
  // values by one.

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

  // Whatever will happen, we wanto set coordinates, not append,
  // thus first empty().
  empty();

  if(!coordinates_string.contains('[') || !coordinates_string.contains(']') ||
     !coordinates_string.contains('-'))
    return -1;

  QStringList coordinateList =
    coordinates_string.split(']', Qt::SkipEmptyParts);

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

  for(int iter = 0; iter < coordinateList.size(); ++iter)
    {
      QString coordinates = coordinateList.at(iter);

      coordinates = coordinates.remove('[');

      QStringList positionList = coordinates.split('-');

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

      if(positionList.size() != 2)
        {
          // Error.
          // qDebug() << __FILE__<< __LINE__
          //          << "error: return -1";

          return -1;
        }

      // At this point we should have two numeric values in the the
      // positionList.
      bool ok   = false;
      int start = positionList.at(0).toInt(&ok);

      // The index might well be 0, but the, ok should be true.

      if(!--start && !ok)
        return -1;

      ok      = false;
      int end = positionList.at(1).toInt(&ok);

      if(!--end && !ok)
        return -1;

      Coordinates *newCoordinates = 0;

      if(start > end)
        newCoordinates = new Coordinates(end, start);
      else
        newCoordinates = new Coordinates(start, end);

      append(newCoordinates);
    }

  QString text = positionsAsText();

  // qDebug() << __FILE__ << __LINE__
  //          << "positionsAsText: " << text;

  return size();
}

/*!
\brief Sets the comment to \a text.
*/
void
CoordinateList::setComment(QString text)
{
  m_comment = text;
}

/*!
\brief Returns the comment.
*/
QString
CoordinateList::comment() const
{
  return m_comment;
}

/*!
\brief Searches all the Coordinates that have the smallest m_start value.

Searches all the Coordinates instances in this CoordinateList that share the
same Coordinates::m_start value that is actually the smallest such value in the
list. Each found Coordinates instance's index in this CoordinateList is added
to \a index_list.

\note \a index_list is first emptied.

Returns the count of Coordinates instances added to \a index_list.

\sa rightMostCoordinates(), isLeftMostCoordinates(), isRightMostCoordinates
*/
int
CoordinateList::leftMostCoordinates(QList<int> &index_list) const
{
  if(isEmpty())
    return 0;

  while(!index_list.isEmpty())
    index_list.removeFirst();

  int leftMostValue = first()->start();

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

      int start = coordinates->start();

      if(start < leftMostValue)
        leftMostValue = start;
    }

  // At this point we now what's the leftmost index. We can use that
  // index to now search for all the items that are also leftmost.

  for(int iter = 0; iter < size(); ++iter)
    {
      if(at(iter)->start() == leftMostValue)
        index_list.append(iter);
    }

  return index_list.size();
}

/*!
\brief Returns true if \a coordinates is the left-most Coordinates in this
CoordinateList. false otherwise.
*/
bool
CoordinateList::isLeftMostCoordinates(Coordinates *coordinates) const
{
  Q_ASSERT(coordinates);

  // Are the coordinates the leftmost coordinates of *this
  // CoordinateList ?

  int value = coordinates->start();

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

      if(value > coordinates->start())
        return false;
    }

  return true;
}


/*!
\brief Searches all the Coordinates that have the greatest m_end value.

Searches all the Coordinates instances in this CoordinateList that share the
same Coordinates::m_end value that is actually the greatest such value in the
list. Each found Coordinates instance's index in this CoordinateList is added
to \a index_list.

\note \a index_list is first emptied.

Returns the count of Coordinates instances added to \a index_list.

\sa leftMostCoordinates(), isLeftMostCoordinates(), isRightMostCoordinates
*/
int
CoordinateList::rightMostCoordinates(QList<int> &index_list) const
{
  if(isEmpty())
    return 0;

  while(!index_list.isEmpty())
    index_list.removeFirst();

  int rightMostValue = first()->end();

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

      int end = coordinates->end();

      if(end > rightMostValue)
        rightMostValue = end;
    }

  // At this point we now what's the rightmost index. We can use
  // that index to now search for all the items that are also
  // rightmost.

  for(int iter = 0; iter < size(); ++iter)
    {
      if(at(iter)->end() == rightMostValue)
        index_list.append(iter);
    }

  return index_list.size();
}


/*!
\brief Returns true if \a coordinates is the right-most Coordinates in this
CoordinateList. false otherwise.
*/
bool
CoordinateList::isRightMostCoordinates(Coordinates *coordinates) const
{
  Q_ASSERT(coordinates);

  // Are the coordinates the rightmost coordinates of *this
  // CoordinateList ?

  int value = coordinates->end();

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

      if(value < coordinates->end())
        return false;
    }

  return true;
}

/*!
\brief Returns true if \a index is found to be between the start and end
indices of at least one Coordinates instance in this CoordinateList, false
otherwise.
*/
bool
CoordinateList::encompassIndex(int index) const
{
  for(int iter = 0; iter < size(); ++iter)
    {
      Coordinates *coordinates = at(iter);

      if(index >= coordinates->start() && index <= coordinates->end())
        return true;
    }

  return false;
}

/*!
\brief Returns true if at least two Coordinates instances overlap.

Two Coordinates instances overlap if the second's m_start member is less than
the first's m_end and greater than the first's m_start.

\sa encompassIndex()
*/
bool
CoordinateList::overlap() const
{
  // Return true if there are overlapping regions in this
  // coordinate list.

  if(size() <= 1)
    return false;

  for(int iter = 0; iter < size(); ++iter)
    {
      Coordinates coords1 = *at(iter);

      int start1 = coords1.start();
      int end1   = coords1.end();

      for(int jter = 0; jter < size(); ++jter)
        {
          // Do not compare one item to itself.
          if(jter == iter)
            continue;

          Coordinates coords2 = *at(jter);

          int start2 = coords2.start();

          if(start2 <= end1 && start2 >= start1)
            return true;
        }
    }

  return false;
}

/*!
\brief Returns a string documenting the Coordinates in this CoordinateList.

Each Coordinates instance is described like the following with the values being
the indices m_start and m_end:

\code
[156-350]
\endcode

\sa positionsAsText()
*/
QString
CoordinateList::indicesAsText() const
{
  QString text;

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

      text +=
        QString("[%1-%2]").arg(coordinates->start()).arg(coordinates->end());
    }

  return text;
}


/*!
\brief Returns a string documenting the Coordinates in this CoordinateList.

Each Coordinates instance is described like the following with the values being
the indices m_start+1 and m_end+1:

\code
[157-351]
\endcode

\note The values reported are not \e indices, but \e positions.

\sa indicesAsText()
*/
QString
CoordinateList::positionsAsText() const
{
  QString text;

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

      text += QString("[%1-%2]")
                .arg(coordinates->start() + 1)
                .arg(coordinates->end() + 1);
    }

  return text;
}

/*!
\brief Clears this CoordinateList of all its Coordinates.

The Coordinates instances are \c{free}'d.
*/
void
CoordinateList::empty()
{
  qDeleteAll(begin(), end());
  clear();
}

/*!
\brief Outputs a string listing all the Coordinates using qDebug().
*/
void
CoordinateList::debugPutStdErr()
{
  qDebug() << __FILE__ << __LINE__ << "CoordinateList:";

  QString text;

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

      text += coordinates->indicesAsText();
    }

  qDebug() << __FILE__ << __LINE__ << text;
}

} // namespace libXpertMass

} // namespace MsXpS
