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


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The ChemicalGroup class provides a model for specifying the
acido-basic behaviour of a chemical group of either a \l Monomer object or of a
\l Modif object.

If the ChemicalGroup does not prove sufficient to characterize precisely the
acido-basic properties of an entity, \l ChemicalGroupRule instances can be
added to that effect.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    <monomer>
      <code>C</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>Lateral SH2</name>
        <pka>8.3</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>never_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    .....
  <modifs>
    <modif>
      <name>Phosphorylation</name>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>1.2</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>6.5</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
    </modif>
  </modifs>
</pkaphpidata>
\endcode

\sa ChemicalGroupRule,
*/

/*!
\enum MsXpS::libXpertMass::ChemicalGroupTrapping

This enum specifies how the chemical group behaves when the chemical entity
that it holds polymerizes into a \l Polymer.

One example will clear things out:

An amino acid has a amino group and a carboxylic acid group. The amino group
gets entrapped in the monomer-to-monomer bond (the peptide bond) if the
polymerization occurs at the left of the monomer. That means that if the
Monomer holding this ChemicalGroup is N-terminal, then the amino group should
be accounted for because it is intact. Conversely, the carboxylic acid group
gets entrapped in the peptide bond if the polymerization occurs at the right of
the monomer. That means that if the Monomer holding this ChemicalGroup is
C-terminal, then the carboxylic acid group should be accounted for because it is
intact.

\value NEVER_TRAPPED
        The chemical group is not lost upon polymerization, it should thus
always be accounted for.

\value LEFT_TRAPPED
        The chemical group gets trapped in the inter-monomer bond if
polymerization occurs at left of the \l Monomer.

\value RIGHT_TRAPPED
        The chemical group gets trapped in the inter-monomer bond if
polymerization occurs at right of the \l Monomer.

\omitvalue NOT_SET.
*/

/*!
\variable MsXpS::libXpertMass::ChemicalGroup::m_name

\brief The name of the ChemicalGroup instance.
*/

/*!
\variable MsXpS::libXpertMass::ChemicalGroup::m_pka

\brief The pKa of the ChemicalGroup instance.
*/

/*!
\variable MsXpS::libXpertMass::ChemicalGroup::m_acidCharged

\brief Tells if the group is charged when in acid conditions (that is, the pH
is less than the pKa).
*/

/*!
\variable MsXpS::libXpertMass::ChemicalGroup::m_polymerizationRule

\brief The way this ChemicalGroup behaves upon polymerization of the chemical
entity into a \l Polymer.
*/

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

\brief The list of \l ChemicalGroupRule instances.
*/

/*!
\brief Constructs a ChemicalGroup instance.

\a name The name of this ChemicalGroup.
\a pka The pKa value of this ChemicalGroup.
\a is_acid_charged Tells if the ChemicalGroup bears a charge when in acidic
conditions.
\a polymerization_rule Specifies the polymerization rule.
*/
ChemicalGroup::ChemicalGroup(QString name,
                             float pka,
                             bool is_acid_charged,
                             ChemicalGroupTrapping polymerization_rule)
  : m_name(name),
    m_pka(pka),
    m_acidCharged(is_acid_charged),
    m_polymerizationRule(polymerization_rule)
{
  Q_ASSERT(m_pka > 0 && m_pka < 14);
}

/*!
\brief Construct a ChemicalGroup instance as a copy of \a other.
*/
ChemicalGroup::ChemicalGroup(const ChemicalGroup &other)
  : m_name(other.m_name),
    m_pka(other.m_pka),
    m_acidCharged(other.m_acidCharged),
    m_polymerizationRule(other.m_polymerizationRule)
{
}

/*!
\brief Destructs this ChemicalGroup instance.
*/
ChemicalGroup::~ChemicalGroup()
{
  while(!m_ruleList.isEmpty())
    delete m_ruleList.takeFirst();
}

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

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

  m_name               = other.m_name;
  m_pka                = other.m_pka;
  m_acidCharged        = other.m_acidCharged;
  m_polymerizationRule = other.m_polymerizationRule;

  qDeleteAll(m_ruleList);

  for(int iter = 0; iter < other.m_ruleList.size(); ++iter)
    m_ruleList.append(new ChemicalGroupRule(*other.m_ruleList.at(iter)));

  return *this;
}

/*!
\brief Sets the \a name.
*/
void
ChemicalGroup::setName(QString name)
{
  m_name = name;
}

/*!
\brief Returns the name.
*/
QString
ChemicalGroup::name() const
{
  return m_name;
}

/*!
\brief Sets the pKa to \a pka.
*/
void
ChemicalGroup::setPka(float pka)
{
  Q_ASSERT(pka > 0 && pka < 14);

  m_pka = pka;
}

/*!
\brief Returns the pKa.
*/
float
ChemicalGroup::pka() const
{
  return m_pka;
}

/*!
\brief Sets the charge condition in acidic conditions to \a acid_charged.

If true, the group bears a charge when the pH is less than the pKa.
*/
void
ChemicalGroup::setAcidCharged(bool acid_charged)
{
  m_acidCharged = acid_charged;
}

/*!
\brief Returns the charge condition in acidic conditions.

If true, the group bears a charge when the pH is less than the pKa.
*/
bool
ChemicalGroup::isAcidCharged() const
{
  return m_acidCharged;
}

/*!
\brief Sets the polymerization rule to \a pol_rule.

The polymerization rule determines if the chemical group is trapped upon
formation of a Monomer-to-Monomer bond.
*/
void
ChemicalGroup::setPolRule(ChemicalGroupTrapping pol_rule)
{
  m_polymerizationRule = pol_rule;
}

/*!
\brief Returns the polymerization rule.
*/
ChemicalGroupTrapping
ChemicalGroup::polRule() const
{
  return m_polymerizationRule;
}

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

/*!
\brief Searches by \a entity for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRule *
ChemicalGroup::findRuleEntity(QString entity, int *index) const
{
  int ruleIndex = 0;

  if(!index)
    ruleIndex = 0;
  else
    {
      if(*index < 0)
        return 0;
      else if(*index > m_ruleList.size())
        return 0;
      ruleIndex = *index;
    }

  if(entity.isEmpty())
    return 0;

  for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter)
    {
      ChemicalGroupRule *rule = m_ruleList.at(iter);

      if(rule->entity() == entity)
        {
          if(index)
            *index = iter;

          return rule;
        }
    }

  return 0;
}

/*!
\brief Searches by \a name for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRule *
ChemicalGroup::findRuleName(QString name, int *index) const
{
  int ruleIndex = 0;

  if(!index)
    ruleIndex = 0;
  else
    {
      if(*index < 0)
        return 0;
      else if(*index > m_ruleList.size())
        return 0;
      ruleIndex = *index;
    }

  if(name.isEmpty())
    return 0;

  for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter)
    {
      ChemicalGroupRule *rule = m_ruleList.at(iter);

      if(rule->name() == name)
        {
          if(index)
            *index = iter;

          return rule;
        }
    }

  return 0;
}


/*!
\brief Searches by \a entity and \a name for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRule *
ChemicalGroup::findRule(QString entity, QString name, int *index) const
{
  int ruleIndex = 0;

  if(!index)
    ruleIndex = 0;
  else
    {
      if(*index < 0)
        return 0;
      else if(*index > m_ruleList.size())
        return 0;
      ruleIndex = *index;
    }

  if(entity.isEmpty() || name.isEmpty())
    return 0;

  for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter)
    {
      ChemicalGroupRule *rule = m_ruleList.at(iter);

      if(rule->entity() == entity && rule->name() == name)
        {
          if(index)
            *index = iter;

          return rule;
        }
    }

  return 0;
}


/*!
\brief Parses the ChemicalGroup XML \a element \e{related to a \l Monomer}.

Upon parsing of the \a element (tag \code{<mnmchemgroup>}), its data
are validated and set to this ChemicalGroup instance, thus essentially
initializing it.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    <monomer>
      <code>C</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>Lateral SH2</name>
        <pka>8.3</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>never_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    \endcode

Upon parsing of the \a element, all the data are validated and set to this
ChemicalGroup instance, thus essentially initializing it. If there are
\l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are
rendered also.

Returns true if parsing and validation were successful, false otherwise.
*/
bool
ChemicalGroup::renderXmlMnmElement(const QDomElement &element)
{
  // The element the parameter points to is:
  //
  //  <mnmchemgroup>
  //
  // Which means that element.tagName() == "mnmchemgroup" and that we'll
  // have to go one step down to the first child of the current node
  // in order to get to the \code<name>\endcode element.


  QDomElement child;

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

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "pka")
    return false;

  bool ok = false;
  m_pka   = child.text().toFloat(&ok);

  if(!m_pka && !ok)
    return false;

  if(m_pka <= 0 || m_pka >= 14)
    return false;

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "acidcharged")
    return false;

  if(child.text() != "FALSE" && child.text() != "TRUE")
    return false;

  m_acidCharged = (child.text() == "FALSE" ? false : true);

  // And now the polrule element. There should be one, here, in fact,
  // because we are dealing with a monomer, and not a modification.

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "polrule")
    return false;

  if(child.text() == "never_trapped")
    m_polymerizationRule = ChemicalGroupTrapping::NEVER_TRAPPED;
  else if(child.text() == "left_trapped")
    m_polymerizationRule = ChemicalGroupTrapping::LEFT_TRAPPED;
  else if(child.text() == "right_trapped")
    m_polymerizationRule = ChemicalGroupTrapping::RIGHT_TRAPPED;
  else
    return false;

  // And finally the chemical group rules... There might be zero, one
  // or more.

  QDomElement childChemGroupRule = child.nextSiblingElement("chemgrouprule");

  while(!childChemGroupRule.isNull())
    {
      ChemicalGroupRule *rule = new ChemicalGroupRule();

      if(!rule->renderXmlElement(childChemGroupRule))
        {
          delete rule;
          return false;
        }

      m_ruleList.append(rule);

      childChemGroupRule = childChemGroupRule.nextSiblingElement();
    }

  return true;
}


/*!
\brief Parses the ChemicalGroup XML \a element \e{related to a \l Modif}.

Upon parsing of the \a element (tag <mnmchemgroup>), its data are validated and
set to this ChemicalGroup instance, thus essentially initializing it.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    .......
  </monomers>
  <modifs>
    <modif>
      <name>Phosphorylation</name>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>1.2</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>6.5</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
    </modif>
  </modifs>
</pkaphpidata>
    \endcode

Upon parsing of the \a element, all the data are validated and set to this
ChemicalGroup instance, thus essentially initializing it. If there are
\l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are
rendered also.

Returns true if parsing and validation were successful, false otherwise.
*/
bool
ChemicalGroup::renderXmlMdfElement(const QDomElement &element)
{
  QDomElement child;

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

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "pka")
    return false;

  bool ok = false;
  m_pka   = child.text().toFloat(&ok);

  if(!m_pka && !ok)
    return false;

  if(m_pka <= 0 || m_pka >= 14)
    return false;

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "acidcharged")
    return false;

  if(child.text() != "FALSE" && child.text() != "TRUE")
    return false;

  m_acidCharged = (child.text() == "FALSE" ? false : true);

  return true;
}


//////////////////////// ChemicalGroupProp ////////////////////////
//////////////////////// ChemicalGroupProp ////////////////////////


/*!
\class MsXpS::libXpertMass::ChemicalGroupProp
\inmodule libXpertMass
\ingroup ThePropSystem

\brief The ChemicalGroupProp class provides a Prop instance of which the member
data points to a dynamically allocated \l ChemicalGroup instance.
*/


/*!
\brief Constructs a ChemicalGroupProp instance using \a data and \a name.

The \a data pointer is set to the \l mpa_data member.
*/
ChemicalGroupProp::ChemicalGroupProp(const QString &name, ChemicalGroup *data)
{
  if(!name.isEmpty())
    m_name = name;
  else
    m_name = QString();

  mpa_data = static_cast<void *>(data);
}


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

The data in \a other are duplicated and set to this ChemicalGroupProp instance.
*/
ChemicalGroupProp::ChemicalGroupProp(const ChemicalGroupProp &other)
  : Prop(other)
{
  if(other.mpa_data != nullptr)
    {
      ChemicalGroup *chemicalGroup =
        static_cast<ChemicalGroup *>(other.mpa_data);

      mpa_data = static_cast<void *>(new ChemicalGroup(*chemicalGroup));
    }
  else
    mpa_data = nullptr;
}

/*!
\brief Destructs this ChemicalGroupProp instance.

The deletion of the data are delegated to \l deleteData().
*/
ChemicalGroupProp::~ChemicalGroupProp()
{
  deleteData();
}

/*!
\brief Deletes the member data.
*/
void
ChemicalGroupProp::deleteData()
{
  if(mpa_data != nullptr)
    {
      delete static_cast<ChemicalGroup *>(mpa_data);
      mpa_data = nullptr;
    }
}

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

The member data are first deleted and then set to a copy of those in \a other.
*/
ChemicalGroupProp &
ChemicalGroupProp::operator=(const ChemicalGroupProp &other)
{
  if(&other == this)
    return *this;

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      ChemicalGroup *chemicalGroup =
        static_cast<ChemicalGroup *>(other.mpa_data);

      mpa_data = static_cast<void *>(new ChemicalGroup(*chemicalGroup));
    }
  else
    mpa_data = nullptr;

  return *this;
}


/*!
\brief Duplicates this ChemicalGroupProp instance and returns its pointer.
*/
ChemicalGroupProp *
ChemicalGroupProp::cloneOut() const
{
  ChemicalGroupProp *new_p = new ChemicalGroupProp(*this);

  return new_p;
}

bool
ChemicalGroupProp::renderXmlElement([[maybe_unused]] const QDomElement &element,
                                    [[maybe_unused]] int version)
{
  return false;
}

QString *
ChemicalGroupProp::formatXmlElement([[maybe_unused]] int offset,
                                    [[maybe_unused]] const QString &indent)
{
  return nullptr;
}

} // namespace libXpertMass

} // namespace MsXpS
