/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


#include "fragmentOligomer.hpp"
#include "polChemDef.hpp"


namespace massXpert
{

  FragmentOligomer::FragmentOligomer(Polymer *polymer, 
                                     const QString &name, 
                                     const QString &fragmentationPattern,
                                     const Ponderable &ponderable,
                                     int startIndex, int endIndex,
                                     const CalcOptions &calcOptions)
    : Oligomer(polymer, name, ponderable, 
               startIndex, endIndex, calcOptions),
      m_fragmentationPattern(fragmentationPattern)
  {
  }
  
  FragmentOligomer::FragmentOligomer(Polymer*polymer, 
                                     const QString &name, 
                                     const QString &fragmentationPattern,
                                     const Ponderable &ponderable,
                                     const IonizeRule &ionizeRule,
                                     bool isIonized,
                                     int startIndex, int endIndex,
                                     const CalcOptions &calcOptions)
    : Oligomer(polymer, name, ponderable, ionizeRule, calcOptions,
               isIonized, startIndex, endIndex),
      m_fragmentationPattern(fragmentationPattern)
  {
  }

  FragmentOligomer::FragmentOligomer(PolChemDef *polChemDef, 
                                     const QString &name, 
                                     const Ponderable &ponderable,
                                     const IonizeRule &ionizeRule,
                                     bool isIonized,
                                     int startIndex, int endIndex,
                                     const CalcOptions &calcOptions)
    : Oligomer(polChemDef, name, ponderable, ionizeRule, calcOptions, 
               isIonized, startIndex, endIndex)
  {
  }

  FragmentOligomer::FragmentOligomer(Polymer *polymer, 
                                     const QString &name, 
                                     const QString &fragmentationPattern,
                                     double mono, double avg,
                                     int startIndex, int endIndex,
                                     const CalcOptions &calcOptions)
    : Oligomer(polymer, name, mono, avg, startIndex, endIndex, calcOptions),
      m_fragmentationPattern(fragmentationPattern)
  {
  }
  

  FragmentOligomer::FragmentOligomer(const FragmentOligomer &oligomer)
    : Oligomer(oligomer), 
      m_fragmentationPattern(oligomer.m_fragmentationPattern)
  {
  }
  

  int 
  FragmentOligomer::setCharge(int charge)
  {
    if (!m_ionizeRule.isValid())
      return -1;

    // If *this fragmentation oligomer is ionized, first deionize it
    // with its own m_ionizeRule, so that we get back to the single
    // native charge that was set to it by the fragmentation formula.
    
    if (m_isIonized)
      {
	if(!deionize())
	  return -1;
	else
	  // Make clear that at this point we are not ionized.
	  m_isIonized = false;
      }

    // At this point, if charge is 0, then we are done, and we can
    // return a success.
    if (!charge)
      return 1;
    
    // At this point we can compute what IonizeRule's level should be
    //(taking into account its charge, that is its unitary charge) so
    // that the total charge is identical to the parameter.
    
    // Note that is not incrementCharge = charge - 1 because we are
    // next going to call ionize(), and during that call the -1 will
    // be applied.

    int incrementCharge = charge - 1;

    int requiredLevel = incrementCharge / m_ionizeRule.charge();
  
    if (!requiredLevel)
      {
	// It is not necessary to perform anything as the required
	// ionization level is 0.

	return 0;
      }

    Formula formula(m_ionizeRule.formula());

    const QList<Atom *> &refList = mp_polChemDef->atomList();
 
    double localMono = 0;
    double localAvg = 0;
 
    // Note the times param to call below.
    if (!formula.accountMasses(refList, &localMono, &localAvg,
                               requiredLevel))
      return -1;
  
    // OK, the accounting of the masses worked ok. We have to copy local
    // to m_ionizeRule, so that the oligomer knows how the masses have
    // been computed. And then we can update the values in the mono and
    // avg params.

    m_ionizeRule.setLevel(requiredLevel);

    m_mono += localMono;
    m_avg += localAvg;
    
    // In the two calls below, note the '+ 1' that accounts for the
    // normal mono-charged state of the fragment as produced by the
    // fragmentation specification formula.
    
    m_mono = m_mono /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    m_avg = m_avg /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    
    m_isIonized = true;
    
    return 1;
  }


  int 
  FragmentOligomer::charge() const
  {
    // Do not forget that one charge was automatically generated by the
    // fragmention specification formula.

    if(!m_isIonized)
      return 1;
    
    //  Thus we add that charge(see the '+ 1') to the
    // ionizeRule-based charge.

    return m_ionizeRule.charge() * m_ionizeRule.level() + 1;
  }


  void 
  FragmentOligomer::setFragmentationPattern(const QString &pattern)
  {
    m_fragmentationPattern = pattern;
  }
  
  
  QString 
  FragmentOligomer::fragmentationPattern()
  {
    return m_fragmentationPattern;
  }
    

  // return -1 for error, 0 for performed nothing but no error and 1 for
  // performed something without error.
  int
  FragmentOligomer::ionize()
  {
    if (m_isIonized)
      return 0;
    
    if (!m_ionizeRule.isValid())
      return -1;
    
    // We will not be able to ionize the oligomer if the mp_polChemDef
    // member datum is 0.
  
    if (!mp_polChemDef)
      return -1;
    
    // The ionization of a fragmentation oligomer is something special
    // as the fragmentation specification gives a formula to be
    // applied to the fragment so that it is ionized by simple
    // application of the formula(like the immonium ions are
    // generated in protein chemistry using a formula that
    // "automatically" generates a singly-charged species starting
    // from the monomer). Thus we have to proceed in a very specific
    // manner here. A fragment oligomer is considered ionized if and
    // only if its charge is greater than 1, that is it has a charge
    // greater than the unitary charge it was created with in the
    // first place.
    
    // At this point we can ionize the oligomer using the member
    // IonizeRule object. Note that if the ionization goes well,
    // m_isIonized will be set to true.
  
    int requiredCharge = m_ionizeRule.charge() * m_ionizeRule.level() + 1;

    if (!requiredCharge)
      {
	// That means that m_ionizeRule.level() is 0, because
	// m_ionizeRule.charge() cannot be 0, otherwise m_ionizeRule
	// would not have validated above.

	// Asking that the oligomer has no charge equiparates to
	// asking it to be deionized. But it is already not ionized
	// because we tested that above, so we return 0(nothing
	// performed, no error).
	return 0;
      }
    
    // Compute the charge by which we have to increment the charge,
    // that is we simply remove 1, which represents the default charge
    // that was given to the oligomer upon its creation.
    int incrementCharge = requiredCharge - 1;
  
    int requiredLevel = incrementCharge / m_ionizeRule.charge();
  
    if (!requiredLevel)
      {
	// It is not necessary to perform anything as the required
	// ionization level is 0.

	return 0;
      }
  
    Formula formula(m_ionizeRule.formula());

    const QList<Atom *> &refList = mp_polChemDef->atomList();
 
    double localMono = 0;
    double localAvg = 0;
 
    // Note the times param to call below.
    if (!formula.accountMasses(refList, &localMono, &localAvg,
                               requiredLevel))
      return -1;
  
    // OK, the accounting of the masses worked ok. We have to copy local
    // to m_ionizeRule, so that the oligomer knows how the masses have
    // been computed. And then we can update the values in the mono and
    // avg params.

    m_ionizeRule.setLevel(requiredLevel);

    //     qDebug() << __FILE__ << __LINE__
    // 	      << "This oligomer's charge:" << this->charge();
    
    m_mono += localMono;
    m_avg += localAvg;
    
    // In the two calls below, note the '+ 1' that accounts for the
    // normal mono-charged state of the fragment as produced by the
    // fragmentation specification formula.
    
    m_mono = m_mono /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    m_avg = m_avg /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    
    m_isIonized = true;
    
    return 1;
  }


  // return -1 for error, 0 for performed nothing but no error and 1 for
  // performed something without error.
  int
  FragmentOligomer::ionize(const IonizeRule &ionizeRule)
  {
    if (!ionizeRule.isValid())
      return -1;
    
    if (m_isIonized)
      {
	if(!deionize())
	  return -1;
	else
	  // Make clear that at this point we are not ionized.
	  m_isIonized = false;
      }
    
    // We will not be able to ionize the oligomer if the mp_polChemDef
    // member datum is 0.
  
    if (!mp_polChemDef)
      return -1;
    
    // The ionization of a fragmentation oligomer is something special
    // as the fragmentation specification gives a formula to be
    // applied to the fragment so that it is ionized by simple
    // application of the formula(like the immonium ions are
    // generated in protein chemistry using a formula that
    // "automatically" generates a singly-charged species starting
    // from the monomer). Thus we have to proceed in a very specific
    // manner here. A fragment oligomer is considered ionized if and
    // only if its charge is greater than 1, that is it has a charge
    // greater than the unitary charge it was created with in the
    // first place.
    
    // At this point we can ionize the oligomer using the IonizeRule
    // object passed as param. Note that if the ionization goes well,
    // m_isIonized will be set to true.
  
    int requiredCharge = ionizeRule.charge() * ionizeRule.level() + 1;

    if (!requiredCharge)
      {
	// That means that ionizeRule.level() is 0, because
	// ionizeRule.charge() cannot be 0, otherwise ionizeRule
	// would not have validated above.

	// Asking that the oligomer has no charge equiparates to
	// asking it to be deionized. But it is already not ionized
	// because we tested that above, so we return 0(nothing
	// performed, no error).
	return 0;
      }
    
    // Compute the charge by which we have to increment the charge,
    // that is we simply remove 1, which represents the default charge
    // that was given to the oligomer upon its creation.
    int incrementCharge = requiredCharge - 1;
  
    int requiredLevel = incrementCharge / ionizeRule.charge();
  
    if (!requiredLevel)
      {
	// It is not necessary to perform anything as the required
	// ionization level is 0.

	return 0;
      }
  
    Formula formula(ionizeRule.formula());

    const QList<Atom *> &refList = mp_polChemDef->atomList();
 
    double localMono = 0;
    double localAvg = 0;
 
    // Note the times param to call below.
    if (!formula.accountMasses(refList, &localMono, &localAvg,
                               requiredLevel))
      return -1;
  
    // OK, the accounting of the masses worked ok. We have to copy local
    // to ionizeRule, so that the oligomer knows how the masses have
    // been computed. And then we can update the values in the mono and
    // avg params.

    m_ionizeRule = ionizeRule;
    
    m_ionizeRule.setLevel(requiredLevel);

    //     qDebug() << __FILE__ << __LINE__
    // 	      << "This oligomer's charge:" << this->charge();
    
    m_mono += localMono;
    m_avg += localAvg;
    
    // In the two calls below, note the '+ 1' that accounts for the
    // normal mono-charged state of the fragment as produced by the
    // fragmentation specification formula.
    
    m_mono = m_mono /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    m_avg = m_avg /(m_ionizeRule.charge() * m_ionizeRule.level() + 1);
    
    m_isIonized = true;
    
    return 1;
  }



  // return -1 for error, 0 for performed nothing but no error and 1 for
  // performed something without error.
  int
  FragmentOligomer::deionize()
  {
    if (!m_isIonized)
      {
	// The Ionizable is not ionized, nothing to do, return true.
	return 0;
      }
   
    Ponderable temp(*this);

    // At this point we know the Ionizable is ionized, thus it is an
    // error that m_ionizeRule is not valid.

    if (!m_ionizeRule.isValid())
      return -1;
    
    int totalCharge = m_ionizeRule.charge() * m_ionizeRule.level() + 1;

    // Because we know that the oligomer is ionized(see first line in
    // function) it is not possible that totalCharge be less or equal
    // to 1.

    // Indeed, because:

    // 1) m_ionizeRule.charge() cannot be 0(otherwise the ionizeRule
    // would not have validate above).

    // 2) if totalCharge is less or equal to 1, then that would mean
    // that m_ionizeRule.level() is 0. But then, how can the oligomer
    // pretend that it was ionized in the first place ?
    
    Q_ASSERT(totalCharge > 1);
    
    // Compute the number of charges that we have to decrement.
    int decrementCharge = totalCharge - 1;

    // Compute the equivalent in levels of decrementCharge, by taking
    // into account the charge of the m_ionizeRule.
    int decrementLevel = decrementCharge / m_ionizeRule.charge();

    // Now prepare the stage for the actual decrement of mass due to
    // deionization.
    Formula formula(m_ionizeRule.formula());

    const QList<Atom *> &refList = mp_polChemDef->atomList();
 
    // In the two calls below, note the '+ 1' that accounts for the
    // normal mono-charged state of the fragment as produced by the
    // fragmentation specification formula.
    
    double localMono = 
      m_mono * abs(m_ionizeRule.charge() * m_ionizeRule.level() + 1);

    double localAvg = 
      m_avg * abs(m_ionizeRule.charge() * m_ionizeRule.level() + 1);

    // Note the times param to call below.
    if (!formula.accountMasses(refList, &localMono, &localAvg,
                               -decrementLevel))
      return -1;
    
    // At this point we have actually deionized the oligomer.

    m_mono = localMono;
    m_avg = localAvg;
    
    m_isIonized = false;
    
    return 1;
  }
  

  double
  FragmentOligomer::molecularMass(MassType massType)
  {
    FragmentOligomer temp(*this);
    
    if (temp.deionize() == -1)
      return -1;
    
    return temp.mass(massType);
  }
  
} // namespace massXpert
