/* 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 "CleaveSpec.hpp"


namespace MsXpS
{

namespace libXpertMass
{



/*!
\class MsXpS::libXpertMass::CleaveSpec
\inmodule libXpertMass
\ingroup PolChemDefAqueousChemicalReactions
\inheaderfile CleaveSpec.hpp

\brief The CleaveSpec class provides a model for specifying aqueous cleavage
specifications (patterns) of \l{Polymer} \l{Sequence}s.

Cleavage specifications determine the specificity of cleavage in a
polymer sequence using a simple syntax. For example, Trypsin is able
to cleave after lysyl and arginyl residues. Its cleavage pattern is
thus "Lys/;Arg/". However, it is known that Trypsin fails to cleave
after Lys if that monomer is followed by a Prolyl residue, thus the
complete cleavage specification for Trypsin comprises three cleavage
motifs: "Lys/;Arg/;-Lys/Pro".

A cleavage specification might not be enough information to
determine the manner in which a polymer is cleaved. Cleavage rules
might be required to refine the specification.  A cleavage
specification might hold as many cleavage rules as required.

\sa CleaveMotif, CleaveRule
*/



/*!
\variable MsXpS::libXpertMass::CleaveSpec::m_pattern

\brief The cleavage pattern, that might comprise more than one \l CleaveMotif.

\sa CleaveMotif, CleaveRule
*/

/*!
\variable MsXpS::libXpertMass::CleaveSpec::m_motifList

\brief The list of \l{CleaveMotif}s that together make the CleaveSpec.

\sa CleaveMotif
*/

/*!
\variable MsXpS::libXpertMass::CleaveSpec::m_ruleList

\brief The list of \l{CleaveRule}s that might be requied to refine the
CleaveSpec.

\sa CleaveRule
*/

/*!
\brief Constructs a CleaveSpec instance.

\list
\li \a pol_chem_def_csp: The polymer chemistry definition.
\li \a name: The name of the CleaveSpec.
\li \a pattern: The pattern of the CleaveSpec, like ("Lys/;Arg/;-Lys/Pro").
\endlist
*/
CleaveSpec::CleaveSpec(PolChemDefCstSPtr pol_chem_def_csp,
                       QString name,
                       QString pattern)
  : PolChemDefEntity(pol_chem_def_csp, name), m_pattern(pattern)
{
}

/*!
\brief Constructs a CleaveSpec instance as a copy of \a other.
*/
CleaveSpec::CleaveSpec(const CleaveSpec &other)
  : PolChemDefEntity(other), m_pattern(other.m_pattern)
{
  for(int iter = 0; iter < other.m_motifList.size(); ++iter)
    {
      CleaveMotif *cleaveMotif = new CleaveMotif(*other.m_motifList.at(iter));

      m_motifList.append(cleaveMotif);
    }

  for(int iter = 0; iter < other.m_ruleList.size(); ++iter)
    {
      CleaveRule *cleaveRule = new CleaveRule(*other.m_ruleList.at(iter));

      m_ruleList.append(cleaveRule);
    }
}

/*!
\brief Destructs this CleaveSpec instance.
*/
CleaveSpec::~CleaveSpec()
{
  while(!m_motifList.isEmpty())
    delete m_motifList.takeFirst();

  while(!m_ruleList.isEmpty())
    delete m_ruleList.takeFirst();
}

/*!
\brief Assigns \a other to this CleaveSpec instance.

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

  PolChemDefEntity::operator=(other);

  m_pattern = other.m_pattern;

  while(!m_ruleList.isEmpty())
    delete m_ruleList.takeFirst();

  for(int iter = 0; iter < other.m_ruleList.size(); ++iter)
    {
      CleaveRule *cleaveRule = new CleaveRule(*other.m_ruleList.at(iter));
      m_ruleList.append(cleaveRule);
    }

  while(!m_motifList.isEmpty())
    delete m_motifList.takeFirst();

  for(int iter = 0; iter < other.m_motifList.size(); ++iter)
    {
      CleaveMotif *cleaveMotif = new CleaveMotif(*other.m_motifList.at(iter));
      m_motifList.append(cleaveMotif);
    }

  return *this;
}


/*!
\brief Sets the \a pattern.
*/
void
CleaveSpec::setPattern(const QString &pattern)
{
  m_pattern = pattern;
}

/*!
\brief Returns the pattern.
*/
const QString &
CleaveSpec::pattern()
{
  return m_pattern;
}

/*!
\brief Returns the list of CleaveMotif instances.
 */
QList<CleaveMotif *> *
CleaveSpec::motifList()
{
  return &m_motifList;
}

/*!
\brief Returns the list of CleaveRule instances.
 */
QList<CleaveRule *> *
CleaveSpec::ruleList()
{
  return &m_ruleList;
}

/*!
\brief Searches for a CleaveSpec instance by \a name in \a cleave_spec_list.

If the CleaveSpec instance is found, it is copied into \a other.

Returns the index of the found cleavage specification or -1  if none is found
or if \a other is empty.
*/
int
CleaveSpec::isNameInList(const QString &name,
                         const QList<CleaveSpec *> &cleave_spec_list,
                         CleaveSpec *other)
{
  CleaveSpec *cleaveSpec = 0;

  if(name.isEmpty())
    return -1;

  for(int iter = 0; iter < cleave_spec_list.size(); ++iter)
    {
      cleaveSpec = cleave_spec_list.at(iter);
      Q_ASSERT(cleaveSpec);

      if(cleaveSpec->m_name == name)
        {
          if(other)
            other = new CleaveSpec(*cleaveSpec);

          return iter;
        }
    }

  return -1;
}

/*!
\brief Parses this CleaveSpec instance.

The parsing involves separating the components found in the m_pattern string
and making CleaveMotif instances out of them.

Starting from a pattern "Lys/;Arg/;-Lys/Pro", the parsing would
first split it into three site strings:

\list
\li "Lys/";
\li "Arg/";
\li "-Lys/Pro"
\endlist

Each of these site strings will be deconstructed into motifs, stored
in CleaveMotif objects:

\list
\li First motif has a code list "[0] Lys", an offset of 1 and is for
cleavage;
\li Second motif has a code list "[0] Arg", an offset of 1 and is for
cleavage;
\li Third motif has a code list "[0] Lys, [1] Pro", an offset of 1
and is not for cleavage (see the '-')
\endlist

Returns true if parsing is successful, false otherwise.
*/
bool
CleaveSpec::parse()
{
  if(m_pattern.isEmpty())
    return false;

  // The 'm_pattern' is a ';'-delimited string, in which each
  // sub-string is a 'site'. Each site is in turn constituted by a
  // motif and a '/' that indicates where the motif is actually
  // cleaved.
  //
  // For example the "-Lys/Pro" site is actually a motif of sequence
  // "LysPro" and the site holds two more informations with respect to
  // the mere motif: it says that the motif should not be cleaved
  //('-') and that if the '-' were not there, the cleavage would
  // occur between the Lys and the Pro('/' symbolizes the cleavage).

  // For example, if the cleavespec had a "Lys/;Arg/;-Lys/Pro" string,
  // it would be split into 3 strings: "Lys/" and "Arg/" and
  // "-Lys/Pro"(these are 'site' strings). These three site string
  // would further be deconstructed into motif string(removal of '-'
  // and '/' characters). Where would these 3 motif strings be stored?
  // They would be set into one cleavemotif instance for each
  // motif. Thus, for example, "-Lys/Pro" would yield a cleavemotif of
  // 'motif' LysPro, with a FALSE cleave member and a 1 offset member.

  // Will return the number of cleavemotif instances that were created.
  // Upon error -1 is returned.


  // "Lys/;Arg/;-Lys/Pro" --> [0] Lys/, [1] Arg/, [2] -Lys/Pro
  QStringList sites = m_pattern.split(";", Qt::SkipEmptyParts);

  //   for (int iter = 0; iter < sites.size() ; ++iter)
  //     qDebug() << __FILE__ << __LINE__
  // 	      << sites.at(iter);

  bool isForCleave = true;

  for(int iter = 0; iter < sites.size(); ++iter)
    {
      isForCleave = true;

      QString currSite = sites.at(iter);

      if(currSite.length() < 2)
        {
          qDebug() << currSite << "is an invalid cleavage site";
          return false;
        }

      int count = currSite.count(QChar('/'));
      if(count != 1)
        {
          qDebug() << "The must be one and only one '/' "
                      "in the cleavage site";
          return false;
        }

      // Remove spaces.
      currSite.remove(QRegularExpression("\\s+"));

      // Is there a '-' in the site string indicating that this site
      // is NOT for cleavage? If so, there should be only one such
      // sign and in position 0.

      count = currSite.count(QChar('-'));
      if(count > 1)
        return false;
      else if(count == 1)
        {
          if(currSite.indexOf(QChar('-'), 0, Qt::CaseInsensitive) != 0)
            return false;

          isForCleave = false;

          // We can remove the '-' character, as we now know
          // that the site is NOT for cleavage.
          currSite.remove(0, 1);
        }
      // else: site is for cleavage: no '-' found.


      // We can create a new cleavage motif.
      CleaveMotif *motif = new CleaveMotif(mcsp_polChemDef, "NOT_SET");

      motif->setForCleave(isForCleave);

      if(motif->parse(currSite) == -1)
        {
          qDebug() << "Failed to parse site" << currSite;

          delete motif;

          return false;
        }

      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Appending motif" << motif->motif();

      // Append the newly created motif to the list.
      m_motifList.append(motif);
    }

  return true;
}

/*!
\brief Validates this CleaveSpec instance.

The validation involves checking that:

\list
\li The name is not empty.
\li The pattern is nt empty.
\li The parsing of the pattern is successful.
\li Each CleaveRule instance (if any) validates successfully.
\endlist

Returns true if the validation is successful, false otherwise.
*/
bool
CleaveSpec::validate()
{
  if(m_name.isEmpty())
    return false;

  if(m_pattern.isEmpty())
    return false;


  if(!parse())
    return false;

  // If there are rules, we have to check them all.

  for(int iter = 0; iter < m_ruleList.size(); ++iter)
    {
      if(!m_ruleList.at(iter)->validate())
        return false;
    }

  return true;
}

/*!
\brief Parses a cleavage specification XML \a element using a \a{version}ed
function.

The data in the element are validated and if successful they are set to this
instance, thus initializing it.

Returns true upon success, false otherwise.
*/
bool
CleaveSpec::renderXmlClsElement(const QDomElement &element, int version)
{
  // For the time being the version is not necessary here. As of
  // version up to 2, the current function works ok.

  if(version == 1)
    {
      // NoOp
      version = 1;
    }

  QDomElement child;
  QDomElement childRule;

  CleaveRule *cleaveRule = 0;

  /* The xml node we are in is structured this way:
   *
   * <cls>
   *   <name>CyanogenBromide</name>
   *   <pattern>M/</pattern>
   *   <clr>
   *     <le-mnm-code>M</le-mnm-code>
   *     <le-formula>-C1H2S1+O1</le-formula>
   *     <re-mnm-code>M</re-mnm-code>
   *     <re-formula>-C1H2S1+O1</re-formula>
   *   </clr>
   * </cls>
   *
   * And the element parameter points to the
   *
   * <cls> element tag:
   *  ^
   *  |
   *  +----- here we are right now.
   *
   * Which means that element.tagName() == "cls" and that
   * we'll have to go one step down to the first child of the
   * current node in order to get to the <name> element.
   */

  if(element.tagName() != "cls")
    return false;

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();
  //qDebug() << "CleaveSpec name is" << m_name;

  child = child.nextSiblingElement("pattern");

  if(child.isNull())
    return false;

  m_pattern = child.text();

  // At this point there might be 0, 1 or more cleavage rules.
  child = child.nextSiblingElement("clr");

  while(!child.isNull())
    {
      cleaveRule = new CleaveRule(mcsp_polChemDef, "NOT_SET");

      if(!cleaveRule->renderXmlClrElement(child, version))
        {
          delete cleaveRule;
          return false;
        }

      if(!cleaveRule->validate())
        {
          delete cleaveRule;
          return false;
        }

      m_ruleList.append(cleaveRule);

      child = child.nextSiblingElement("clr");
    }

  if(!validate())
    return false;

  return true;
}

/*!
\brief Formats a string describing this CleaveSpec instance suitable to be used
as an XML element.

The XML element is typically used in a polymer chemistry defintion and looks
like this:

\code
<cls>
<name>CyanogenBromide</name>
<pattern>M/</pattern>
<clr>
<re-mnm-code>M</re-mnm-code>
<re-formula>-CH2S+O</re-formula>
</clr>
</cls>
\endcode


The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

\a indent defaults to two spaces.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString *
CleaveSpec::formatXmlClsElement(int offset, const QString &indent)
{

  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();


  // Prepare the lead.
  newOffset = offset;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }


  *string += QString("%1<cls>\n").arg(lead);

  // Prepare the lead.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // Continue with indented elements.

  *string += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  *string += QString("%1<pattern>%2</pattern>\n").arg(lead).arg(m_pattern);

  for(int iter = 0; iter < m_ruleList.size(); ++iter)
    {
      QString *ruleString = m_ruleList.at(iter)->formatXmlClrElement(newOffset);

      *string += *ruleString;

      delete ruleString;
    }

  // Prepare the lead for the closing element.
  --newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1</cls>\n").arg(lead);

  return string;
}

} // namespace libXpertMass

} // namespace MsXpS
