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


/////////////////////// Local includes
#include <massxpert/nongui/CrossLink.hpp>
#include <massxpert/nongui/Polymer.hpp>


namespace msXpSmassXpert
{

CrossLink::CrossLink(const PolChemDef *polChemDef,
                     Polymer *polymer,
                     const QString &name,
                     const QString &formula,
                     const QString &comment)
  : CrossLinker(polChemDef, name, formula),
    mp_polymer(polymer),
    m_comment(comment)
{
}


CrossLink::CrossLink(const CrossLinker &crossLinker,
                     Polymer *polymer,
                     const QString &comment)
  : CrossLinker(crossLinker), mp_polymer(polymer), m_comment(comment)
{
}


CrossLink::CrossLink(const CrossLink &crossLink)
  : CrossLinker(crossLink),
    mp_polymer(crossLink.mp_polymer),
    m_comment(crossLink.m_comment)
{
}


CrossLink::~CrossLink()
{
}


bool
CrossLink::setMonomerAt(Monomer *monomer, int index)
{
  Q_ASSERT(monomer);
  Q_ASSERT(index >= 0 && index < m_monomerList.size());

  m_monomerList.replace(index, monomer);

  return true;
}


bool
CrossLink::appendMonomer(const Monomer *monomer)
{
  Q_ASSERT(monomer);

  m_monomerList.append(monomer);

  return true;
}


const Monomer *
CrossLink::monomerAt(int index)
{
  Q_ASSERT(index >= 0 && index < m_monomerList.size());

  return m_monomerList.at(index);
}


bool
CrossLink::removeMonomerAt(int index)
{
  Q_ASSERT(index < m_monomerList.size());

  m_monomerList.removeAt(index);

  return true;
}


void
CrossLink::setPolymer(Polymer *polymer)
{
  Q_ASSERT(polymer);

  mp_polymer = polymer;
}


Polymer *
CrossLink::polymer()
{
  return mp_polymer;
}


void
CrossLink::setComment(const QString &comment)
{
  m_comment = comment;
}


const QString &
CrossLink::comment() const
{
  return m_comment;
}


bool
CrossLink::operator==(const CrossLink &other) const
{
  int tests = 0;

  tests += CrossLinker::operator==(other);
  tests += mp_polymer.data() == other.mp_polymer.data();

  if(m_monomerList.size() != other.m_monomerList.size())
    return false;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      if(m_monomerList.at(iter) != other.m_monomerList.at(iter))
        return false;
    }

  tests += m_comment == other.m_comment;

  if(tests < 3)
    return false;
  else
    return true;
}


int
CrossLink::populateMonomerList(QString text)
{
  // We do not own the monomers pointed to by the pointers in
  // m_monomerList.

  while(m_monomerList.size())
    m_monomerList.removeFirst();

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

  // There must be at least 2 monomers to make a crossLink !

  if(stringList.size() < 2)
    return -1;

  for(int iter = 0; iter < stringList.size(); ++iter)
    {
      QString value = stringList.at(iter);

      bool ok = false;

      int index = value.toInt(&ok);

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

      Q_ASSERT(index >= 0 && index < mp_polymer->size());

      m_monomerList.append(mp_polymer->at(index));
    }

  return m_monomerList.size();
}


QList<const Monomer *> *
CrossLink::monomerList()
{
  return &m_monomerList;
}


// Returns the number of monomers involved in the cross-link.
int
CrossLink::monomerIndexList(QList<int> *list)
{
  if(!list)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  int count = 0;

  QList<int> localIndexList;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      // qDebug() << __FILE__ << __LINE__
      //          << "Other monomer index:" << index;

      localIndexList.append(index);

      ++count;
    }

  qSort(localIndexList.begin(), localIndexList.end());

  // Now that we have the minIndex and the maxIndex of the region
  // involved by the cross-link, we can fill-in the integer list
  // with all the values contained between these two borders.i

  for(int iter = localIndexList.first(); iter <= localIndexList.last(); ++iter)
    {
      // If we had a cross-link between monomers [4] and [10] of the
      // polymer, then the indices appended to the list would be 4,
      // 5, 6, 7, 8, 9 and 10.
      list->append(iter);
    }

  return count;
}


QString
CrossLink::monomerIndexText()
{
  QString text = ";";

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      text += QString("%1;").arg(index);
    }

  return text;
}


QString
CrossLink::monomerPosText()
{
  QString text = ";";

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      text += QString("%1;").arg(index + 1);
    }

  return text;
}


int
CrossLink::involvesMonomer(const Monomer *monomer) const
{
  Q_ASSERT(monomer);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      if(m_monomerList.at(iter) == monomer)
        {
          return iter;
        }
    }

  return -1;
}


const Monomer *
CrossLink::firstMonomer() const
{
  if(!m_monomerList.size())
    return 0;

  return m_monomerList.first();
}

int
CrossLink::encompassedBy(int start, int end, int *in, int *out)
{
  // Iterate in the list of monomers, and for each monomer check if
  // their index is contained in the stretch.

  int countIn  = 0;
  int countOut = 0;

  int localStart = (start < end ? start : end);
  int localEnd   = (end >= start ? end : start);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      if(index >= localStart && index <= localEnd)
        ++countIn;
      else
        ++countOut;
    }

  Q_ASSERT((countIn + countOut) == m_monomerList.size());

  if(in)
    *in = countIn;
  if(out)
    *out = countOut;

  if(countOut && countIn)
    return CROSS_LINK_ENCOMPASSED_PARTIAL;

  if(countOut)
    return CROSS_LINK_ENCOMPASSED_NO;

  if(countIn)
    return CROSS_LINK_ENCOMPASSED_FULL;

  return CROSS_LINK_ENCOMPASSED_NO;
}


int
CrossLink::encompassedBy(const CoordinateList &coordinateList,
                         int *in,
                         int *out)
{
  // Iterate in the list of monomers involved in *this crossLink,
  // and for each monomer check if their index is contained in any
  // of the Coordinates [start--end] of the coordinateList passed as
  // parameter.

  int countIn  = 0;
  int countOut = 0;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      // 	qDebug() << __FILE__ <<  __LINE__
      // 		  << "           monomer at index:" << index
      // 		  << "tested:";

      // Is the index encompassed by any of the Coordinates of the
      // CoordinateList ?

      bool countedIn = false;

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

          if(index >= coordinates->start() && index <= coordinates->end())
            {
              ++countIn;
              countedIn = true;

              // 		qDebug() << __FILE__ <<  __LINE__
              // 			  << "           encompassedBy YES by coordinates"
              // 			  << "coordinates->start()" << coordinates->start()
              // 			  << "coordinates->end()" << coordinates->end();

              break;
            }
          else
            {
              // 		qDebug() << __FILE__ <<  __LINE__
              // 			  << "           encompassedBy NO by coordinates"
              // 			  << "coordinates->start()" << coordinates->start()
              // 			  << "coordinates->end()" << coordinates->end();
            }
        }

      if(!countedIn)
        ++countOut;
    }

  Q_ASSERT((countIn + countOut) == m_monomerList.size());

  if(in)
    *in = countIn;
  if(out)
    *out = countOut;

  if(countOut && countIn)
    return CROSS_LINK_ENCOMPASSED_PARTIAL;

  if(countOut)
    return CROSS_LINK_ENCOMPASSED_NO;

  if(countIn)
    return CROSS_LINK_ENCOMPASSED_FULL;

  return CROSS_LINK_ENCOMPASSED_NO;
}


bool
CrossLink::validate()
{
  if(m_monomerList.size() <= 1)
    {
      qDebug() << __FILE__ << __LINE__
               << "A crossLink with a single monomer "
                  "cannot be created.";

      return false;
    }

  if(mp_polymer.isNull())
    return false;

  // If the crossLinker has at least one modif in its definition, then
  // we have to make sure that the modification has as one of its
  // targets the corresponding monomer in the list of monomer
  // pointers.

  // Indeed, there is some logic here. Either the m_modifList in the
  // parent class CrossLinker contains no item, in which case
  // everything is fine, or it does contain items. In the latter case,
  // then, the number of items must match the number of monomers being
  // crosslinked, and then we get to know which modif is attributable
  // to which monomer, hence the possibility of a check.

  if(m_modifList.size())
    {
      if(m_modifList.size() != m_monomerList.size())
        {
          qDebug() << __FILE__ << __LINE__
                   << QObject::tr(
                        "The number of modification items "
                        "does not match the number of "
                        "monomers. This is an error.");
          return false;
        }

      // At this point, we can make the check for each modif:

      for(int iter = 0; iter < m_modifList.size(); ++iter)
        {
          Modif *modif           = m_modifList.at(iter);
          const Monomer *monomer = m_monomerList.at(iter);

          if(!monomer->isModifTarget(*modif))
            {
              // The monomer is not target for the modification that is
              // implied by the crossLinking.

              qDebug() << __FILE__ << __LINE__
                       << QObject::tr(
                            "The monomer '%1' is not target of "
                            "crossLinker modif '%2'")
                            .arg(monomer->name())
                            .arg(modif->name());

              return false;
            }
        }
    }

  return true;
}


bool
CrossLink::calculateMasses()
{
  return CrossLinker::calculateMasses();
}


bool
CrossLink::accountMasses(double *mono, double *avg, int times)
{
  return CrossLinker::accountMasses(mono, avg, times);
}


bool
CrossLink::accountMasses(Ponderable *ponderable, int times)
{
  return CrossLinker::accountMasses(ponderable, times);
}


QString *
CrossLink::prepareResultsTxtString()
{
  QString *text = new QString();

  *text += QObject::tr(
             "\nCross-link:\n"
             "===============\n"
             "Cross-linker name: %1\n"
             "Cross-link comment: %2\n")
             .arg(m_name)
             .arg(m_comment);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      *text += QObject::tr("Partner %1: %2 at position: %3\n")
                 .arg(iter + 1)
                 .arg(monomer->code())
                 .arg(index + 1);
    }

  return text;
}

} // namespace msXpSmassXpert
