/* 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/CrossLinker.hpp>
#include <massxpert/nongui/PolChemDef.hpp>


namespace msXpSmassXpert
{

CrossLinker::CrossLinker(const PolChemDef *polChemDef,
                         const QString &name,
                         const QString &formula)
  : PolChemDefEntity(polChemDef, name), Formula(formula)
{
}


CrossLinker::CrossLinker(const CrossLinker &other)
  : PolChemDefEntity(other), Formula(other), Ponderable(other)
{
  for(int iter = 0; iter < other.m_modifList.size(); ++iter)
    m_modifList.append(other.m_modifList.at(iter));
}


CrossLinker::~CrossLinker()
{
  // We do not own the modifications in m_modifList!
}


CrossLinker *
CrossLinker::clone() const
{
  CrossLinker *other = new CrossLinker(*this);

  return other;
}


void
CrossLinker::clone(CrossLinker *other) const
{
  Q_ASSERT(other);

  PolChemDefEntity::clone(other);
  Formula::clone(other);
  Ponderable::clone(other);

  // Empty other's list of modifs. Do not delete them, we do not own
  // them.
  while(!other->m_modifList.isEmpty())
    other->m_modifList.removeFirst();

  for(int iter = 0; iter < m_modifList.size(); ++iter)
    other->m_modifList.append(m_modifList.at(iter));
}


bool
CrossLinker::setModifAt(Modif *modif, int index)
{
  Q_ASSERT(modif);
  Q_ASSERT(index >= 0 && index < m_modifList.size());

  m_modifList.replace(index, modif);

  return true;
}


bool
CrossLinker::appendModif(Modif *modif)
{
  Q_ASSERT(modif);
  m_modifList.append(modif);

  return true;
}


const Modif *
CrossLinker::modifAt(int index) const
{
  Q_ASSERT(index >= 0 && index < m_modifList.size());

  return m_modifList.at(index);
}


bool
CrossLinker::removeModifAt(int index)
{
  Q_ASSERT(index < m_modifList.size());

  m_modifList.removeAt(index);

  return true;
}


QString
CrossLinker::formula() const
{
  return Formula::text();
}


QList<Modif *> &
CrossLinker::modifList()
{
  return m_modifList;
}


int
CrossLinker::hasModif(const QString &modifName)
{
  // Iterate in the list of modifications, and check if one of these
  // has the name passed as argument. If so return the index of the
  // found item in the list, otherwise return -1.

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

      if(modif->name() == modifName)
        return iter;
    }

  return -1;
}


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

  tests += PolChemDefEntity::operator==(other);
  tests += Formula::operator==(other);
  tests += Ponderable::operator==(other);

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

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

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


int
CrossLinker::isNameKnown()
{
  const QList<CrossLinker *> &refList = mp_polChemDef->crossLinkerList();

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

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      if(refList.at(iter)->m_name == m_name)
        return iter;
    }

  return -1;
}


int
CrossLinker::isNameInList(const QString &name,
                          const QList<CrossLinker *> &refList,
                          CrossLinker *other)
{
  CrossLinker *crossLinker = 0;

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

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      crossLinker = refList.at(iter);
      Q_ASSERT(crossLinker);

      if(crossLinker->m_name == name)
        {
          if(other)
            crossLinker->clone(other);

          return iter;
        }
    }

  return -1;
}


bool
CrossLinker::validate()
{
  if(!mp_polChemDef)
    return false;

  if(m_name.isEmpty())
    return false;

  Formula formula(m_formula);
  const QList<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

  if(!formula.validate(refList))
    return false;

  // This is mainly a sanity check, as the pointers to Modif in the
  // list all point to modification objects in the polymer chemistry
  // definition, which have been validated already...

  // The validation actually is simple, it might be that there are NO
  // modifs, or if this is not the case there must be at least
  // 2. Indeed, either none of the monomers in the crosslink get
  // modified, or each one has to be(otherwise we cannot know which
  // modif goes to which monomer).

  int size = m_modifList.size();

  if(size > 0 && size < 2)
    return false;

  for(int iter = 0; iter < size; ++iter)
    {
      // Make sure the modification is known to the polymer chemistry
      // definition. This check is not performed by the modif's
      // validation function.

      if(!mp_polChemDef->modif(m_modifList.at(iter)->name()))
        return false;

      if(!m_modifList.at(iter)->validate())
        return false;
    }

  return true;
}


bool
CrossLinker::calculateMasses()
{
  const QList<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

  m_mono = 0;
  m_avg  = 0;

  // Account the masses of the formula parent class.
  if(!Formula::accountMasses(refList, &m_mono, &m_avg))
    return false;

  // Now, for each modif in the crossLinker, have to account their
  // mass.

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

      if(!modif->accountMasses(&m_mono, &m_avg))
        return false;
    }

  return true;
}


bool
CrossLinker::accountMasses(double *mono, double *avg, int times)
{
  if(mono)
    *mono += m_mono * times;

  if(avg)
    *avg += m_avg * times;

  return true;
}


bool
CrossLinker::accountMasses(Ponderable *ponderable, int times)
{
  Q_ASSERT(ponderable);

  ponderable->rmono() += m_mono * times;
  ponderable->ravg() += m_avg * times;

  return true;
}


bool
CrossLinker::renderXmlClkElement(const QDomElement &element, int version)
{
  QDomElement child;

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

  if(version <= 3)
    return renderXmlClkElement(element);
  else if(version > 3)
    return renderXmlClkElementV3(element);

  return false;
}


bool
CrossLinker::renderXmlClkElement(const QDomElement &element)
{
  QDomElement child;

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

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();
  if(m_name.isEmpty())
    return false;

  child = child.nextSiblingElement("formula");

  if(child.isNull())
    return false;

  if(!Formula::renderXmlFormulaElement(child))
    return false;

  const QList<Modif *> &refList = mp_polChemDef->modifList();

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

  while(!child.isNull())
    {
      int index = Modif::isNameInList(child.text(), refList);

      if(index == -1)
        {
          qDebug() << __FILE__ << __LINE__
                   << "Failed to parse one modification of the crosslink:"
                   << m_name;

          return false;
        }

      m_modifList.append(mp_polChemDef->modifList().at(index));

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

  if(!calculateMasses())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to calculate masses for crossLinker" << m_name;

      return false;
    }

  if(!validate())
    return false;

  return true;
}


bool
CrossLinker::renderXmlClkElementV3(const QDomElement &element)
{
  // For the moment we do not use this version of the function.
  //  qDebug() << __FILE__ << __LINE__ << "Using version 3 function";
  return renderXmlClkElement(element);
}


QString *
CrossLinker::formatXmlClkElement(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;
    }

  /* We are willing to create an <modif> node that should look like this:
   *
   <clk>
   <name>Phosphorylation</name>
   <formula>-H+H2PO3</formula>
   <modifname>Phosphorylation</modifname>
   <modifname>Acetylation</modifname>
   </clk>
   *
   */

  *string += QString("%1<clk>\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<formula>%2</formula>\n").arg(lead).arg(m_formula);

  for(int iter = 0; iter < m_modifList.size(); ++iter)
    {
      *string += QString("%1<modifname>%2</modifname>\n")
                   .arg(lead)
                   .arg(m_modifList.at(iter)->name());
    }

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

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

  return string;
}

} // namespace msXpSmassXpert
