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


/////////////////////// Local includes
#include "prop.hpp"
#include "chemicalGroup.hpp"
#include "crossLink.hpp"


namespace massXpert
{

  //! Construct a property.
  /*! The pointer to the data is set to 0.
   */
  Prop::Prop()
  {
    mpa_data = 0;

    return;
  }


  //! Destroys the property.
  Prop::~Prop()
  {
  }
  

  //! Sets the name.
  /*!  \param name New name.
   */
  void 
  Prop::setName(QString &name)
  {
    m_name = name;
  }


  //! Returns the name.
  /*!  \return The name.
   */
  const QString &
  Prop::name()
  {
    return m_name;
  }


  //! Sets the data.
  /*! If data is already associated to this property, it first gets
    deleted.

    \param data new pointer to data.

    \sa deleteData().
  */
  void 
  Prop::setData(void *data)
  {
    if (mpa_data != 0)
      deleteData();
  
    mpa_data = data;
  }


  //! Returns the data.
  /*!  \return the pointer to data.
   */
  void *
  Prop::data()
  {
    return mpa_data;
  }




  //////////////////////// StringProp ////////////////////////


  //! Constructs a string-only property.
  /*! 
  
    \param name Name. Defaults to the null string, in which case no
    initialization occurs.

    \param data String that gets copied into an allocated
    QString. Defaults to the null string in which case no initialization
    occurs.
  */
  StringProp::StringProp(const QString &name, const QString &data)
  {
    if (!name.isEmpty())
      m_name = name;
    else
      m_name = QString();
  
    if (!data.isEmpty())
      mpa_data = static_cast<void *>(new QString(data));
    else
      mpa_data = 0;
  }


  //! Constructs a string-only property.
  /*! 
  
    \param name Name. Defaults to the null string, in which case no
    initialization occurs.

    \param data Pointer to an allocated string that gets set to the
    data member. Note that the string becomes \em owned by this
    property as a new copy is allocated locally, which will destroy it
    upon its own destruction. Defaults to 0.
  */
  StringProp::StringProp(const QString &name, QString *data)
  {
    if (!name.isEmpty())
      m_name = name;
    else
      m_name = QString();

    if (data)
      mpa_data = new QString(*data);
    else
      mpa_data = 0;
  }


  //! Destroy the string-only property.
  /*! Calls deleteData().
    \sa deleteData().
  */
  StringProp::~StringProp()
  {
    deleteData();
  }


  //! Deletes the data in this string-only property.
  /*! The data is destroyed as one QString is destroyed.
   */
  void
  StringProp::deleteData()
  {
    if (mpa_data && !static_cast<QString *>(mpa_data)->isNull())
      {
        delete static_cast<QString *>(mpa_data);
	mpa_data = 0;
      }
  }


  void *
  StringProp::clone() const
  {
    StringProp *prop = 0;
    
    if (mpa_data)
      prop = new StringProp(m_name, static_cast<QString *>(mpa_data));
    
    return static_cast<void *>(prop);
  }
  
  
  void 
  StringProp::cloneOut(void *other) const
  {
    Q_ASSERT(other);
    
    StringProp *otherProp = static_cast<StringProp *>(other);
    
    if (otherProp->mpa_data)
      otherProp->deleteData();
    
    otherProp->m_name = m_name;
    
    QString *text = 0;
    
    if (mpa_data)
      {
	text = static_cast<QString *>(mpa_data);
	
	otherProp->mpa_data = static_cast<void *>(new QString(*text));
      }
    else
      otherProp->mpa_data = 0;
  }
  
  
  void 
  StringProp::cloneIn(const void *other)
  {
    Q_ASSERT(other);
    
    const StringProp *otherProp = static_cast<const StringProp *>(other);

    if (mpa_data)
      deleteData();
    
    m_name = otherProp->m_name;
    
    QString *text = 0;
    
    if (otherProp->mpa_data)
      {
	text = static_cast<QString *>(otherProp->mpa_data);
	
	mpa_data = static_cast<void *>(new QString(*text));
      }
    else
      mpa_data = 0;    
  }


  //! Parses a string-only property XML element.
  /*! Parses the string-only property XML element passed as argument and
    for each encountered data(name and data) will set the data to \p
    this string-only property(this is called XML rendering).
  
    \param element XML element to be parsed and rendered.
  
    \return true if parsing was successful, false otherwise.

    \sa formatXmlElement(int offset, const QString &indent)
  */
  bool
  StringProp::renderXmlElement(const QDomElement &element, int version)
  {
    QDomElement child;

    /* This is what we expect.
     *   <prop>
     *     <name>MODIF</name>
     *     <data>acetylation</data>
     *   </prop>
     */

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

    child = element.firstChildElement("name");
  
    if (child.isNull())
      return false;
  
    m_name = child.text();
  
    // And now we have to manage the prop objects.
    child = child.nextSiblingElement();

    if (child.isNull())
      return false;

    mpa_data = static_cast<void *>(new QString(child.text()));
  
    return true;
  }

  

  //! Formats a string suitable to use as an XML element.
  /*! Formats a string suitable to be used as an XML element in a
    polymer sequence file. Typical string-only property elements that
    might be generated in this function look like this:

    \verbatim 
    <prop>
    <name>MODIF</name>
    <data>Phosphorylation</data>
    </prop>
    <prop>
    <name>COMMENT</name>
    <data>Phosphorylation is only partial</data>
    </prop>
    \endverbatim  
  
    \param offset times the \p indent string must be used as a lead in the
    formatting of elements.

    \param indent string used to create the leading space that is placed
    at the beginning of indented XML elements inside the XML
    element. Defaults to two spaces(QString(" ")).

    \return a dynamically allocated string that needs to be freed after
    use.

    \sa renderXmlElement(const QDomElement &element)
  */
  QString *
  StringProp::formatXmlElement(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 <prop> node that should look like this:
     *    <prop>
     *      <name>MODIF</name>
     *      <data>Phosphorylation</data>
     *    </prop>
     *    <prop>
     *      <name>COMMENT</name>
     *      <data>Phosphorylation is only partial</data>
     *    </prop>
     *
     * As shown, all the member data of the prop object are simple
     * strings. The name string is never dynamically allocated, while
     * the data string is always dynamically allocated.
     */

    *string += QString("%1<prop>\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<data>%2</data>\n")
      .arg(lead)
      .arg(*static_cast<QString *>(mpa_data));
  
    // Prepare the lead for the closing element.
    --newOffset;
    lead.clear();
    iter = 0;
    while(iter < newOffset)
      {
	lead += indent;
	++iter;
      }

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

  



  //////////////////////// IntProp ////////////////////////


  //! Constructs a int property.
  /*! 
  
    \param name Name. Defaults to the null string, in which case no
    initialization occurs.

    \param data int that gets copied into an allocated int. Defaults
    to 0.
  */
  IntProp::IntProp(const QString &name, int data)
  {
    if (!name.isEmpty())
      m_name = name;
    else
      m_name = QString();
  
    mpa_data = static_cast<void *>(new int(data));
  }


  //! Destroy the int property.
  /*! Calls deleteData().
    \sa deleteData().
  */
  IntProp::~IntProp()
  {
    deleteData();
  }


  //! Deletes the data in this int property.
  /*! The data is destroyed as a int is destroyed.
   */
  void
  IntProp::deleteData()
  {
    if (mpa_data)
      {
	delete static_cast<int *>(mpa_data);
	mpa_data = 0;
      }
  }


  void *
  IntProp::clone() const
  {
    IntProp *prop = 0;
    
    if (mpa_data)
      prop = new IntProp(m_name, *(static_cast<int *>(mpa_data)));

    return static_cast<void *>(prop);
  }
  
  
  void 
  IntProp::cloneOut(void *other) const
  {
    Q_ASSERT(other);
    
    IntProp *otherProp = static_cast<IntProp *>(other);
    
    if (otherProp->mpa_data)
      otherProp->deleteData();
    
    otherProp->m_name = m_name;
    
    int *value = 0;
    
    if (mpa_data)
      {
	value = static_cast<int *>(mpa_data);
	
	otherProp->mpa_data = static_cast<void *>(new int(*value));
      }
    else
      otherProp->mpa_data = 0;  
  }
  
  
  void 
  IntProp::cloneIn(const void *other) 
  {
    Q_ASSERT(other);
    
    const IntProp *otherProp = static_cast<const IntProp *>(other);

    if (mpa_data)
      deleteData();
    
    m_name = otherProp->m_name;
    
    int *value = 0;
    
    if (otherProp->mpa_data)
      {
	value = static_cast<int *>(otherProp->mpa_data);
	
	mpa_data = static_cast<void *>(new int(*value));
      }
    else
      mpa_data = 0;    
  }
  


  //! Parses a int property XML element.
  /*! Parses the int property XML element passed as argument and
    for each encountered data(name and data) will set the data to \p
    this int property(this is called XML rendering).
  
    \param element XML element to be parsed and rendered.
  
    \return true if parsing was successful, false otherwise.

    \sa formatXmlElement(int offset, const QString &indent)
  */
  bool
  IntProp::renderXmlElement(const QDomElement &element, int version)
  {
    QDomElement child;

    /* This is what we expect.
     *  <prop>
     <name>IONIZATION_LEVEL</name>
     <data>5</data>
     *  </prop>
     */

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

    child = element.firstChildElement("name");
  
    if (child.isNull())
      return false;
  
    m_name = child.text();
  
    // And now we have to manage the prop objects.
    child = child.nextSiblingElement();

    if (child.isNull())
      return false;

    mpa_data = static_cast<void*>(new int(child.text().toInt()));
  
    return true;
  }

  

  //! Formats a string suitable to use as an XML element.
  /*! Formats a string suitable to be used as an XML element. Typical
    int property elements that might be generated in this function
    look like this:

    \verbatim 
    <prop>
    <name>IONIZATION_LEVEL</name>
    <data>5</data>
    </prop>
    \endverbatim  
  
    \param offset times the \p indent string must be used as a lead in the
    formatting of elements.

    \param indent string used to create the leading space that is placed
    at the beginning of indented XML elements inside the XML
    element. Defaults to two spaces(QString(" ")).

    \return a dynamically allocated string that needs to be freed after
    use.

    \sa renderXmlElement(const QDomElement &element)
  */
  QString *
  IntProp::formatXmlElement(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 <prop> node that should look like this:
     *
     *  <prop>
     *    <name>SEARCHED_MASS</name>
     *    <data>1000.234</data>
     *  </prop>
     *
     */

    *string += QString("%1<prop>\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);
    
    QString value;
    value = QString::number(*static_cast<int *>(mpa_data), 'g', 10);
  
    *string += QString("%1<data>%2</data>\n")
      .arg(lead)
      .arg(value);
  
    // Prepare the lead for the closing element.
    --newOffset;
    lead.clear();
    iter = 0;
    while(iter < newOffset)
      {
	lead += indent;
	++iter;
      }

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

  

  //////////////////////// DoubleProp ////////////////////////


  //! Constructs a double property.
  /*! 
  
    \param name Name. Defaults to the null string, in which case no
    initialization occurs.

    \param data double that gets copied into an allocated double. Defaults
    to 0.
  */
  DoubleProp::DoubleProp(const QString &name, double data)
  {
    if (!name.isEmpty())
      m_name = name;
    else
      m_name = QString();
  
    mpa_data = static_cast<void *>(new double(data));
  }


  //! Destroy the double property.
  /*! Calls deleteData().
    \sa deleteData().
  */
  DoubleProp::~DoubleProp()
  {
    deleteData();
  }


  //! Deletes the data in this double property.
  /*! The data is destroyed as a double is destroyed.
   */
  void
  DoubleProp::deleteData()
  {
    if (mpa_data)
      {
	delete static_cast<double *>(mpa_data);
	mpa_data = 0;
      }
  }


  void *
  DoubleProp::clone() const
  {
    DoubleProp *prop = 0;
    
    if (mpa_data)
      prop = new DoubleProp(m_name, *(static_cast<double *>(mpa_data)));

    return static_cast<void *>(prop);
  }
  
  
  void 
  DoubleProp::cloneOut(void *other) const
  {
    Q_ASSERT(other);
    
    DoubleProp *otherProp = static_cast<DoubleProp *>(other);
    
    if (otherProp->mpa_data)
      otherProp->deleteData();
    
    otherProp->m_name = m_name;
    
    double *value = 0;
    
    if (mpa_data)
      {
	value = static_cast<double *>(mpa_data);
	
	otherProp->mpa_data = static_cast<void *>(new double(*value));
      }
    else
      otherProp->mpa_data = 0;  
  }
  
  
  void 
  DoubleProp::cloneIn(const void *other) 
  {
    Q_ASSERT(other);
    
    const DoubleProp *otherProp = static_cast<const DoubleProp *>(other);

    if (mpa_data)
      deleteData();
    
    m_name = otherProp->m_name;
    
    double *value = 0;
    
    if (otherProp->mpa_data)
      {
	value = static_cast<double *>(otherProp->mpa_data);
	
	mpa_data = static_cast<void *>(new double(*value));
      }
    else
      mpa_data = 0;    
  }
  

  //! Parses a double property XML element.
  /*! Parses the double property XML element passed as argument and
    for each encountered data(name and data) will set the data to \p
    this double property(this is called XML rendering).
  
    \param element XML element to be parsed and rendered.
  
    \return true if parsing was successful, false otherwise.

    \sa formatXmlElement(int offset, const QString &indent)
  */
  bool
  DoubleProp::renderXmlElement(const QDomElement &element, int version)
  {
    QDomElement child;

    /* This is what we expect.
     *  <prop>
     *    <name>SEARCHED_MASS</name>
     *    <data>1000.234</data>
     *  </prop>
     */

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

    child = element.firstChildElement("name");
  
    if (child.isNull())
      return false;
  
    m_name = child.text();
  
    // And now we have to manage the prop objects.
    child = child.nextSiblingElement();

    if (child.isNull())
      return false;

    mpa_data = static_cast<void*>(new double(child.text().toDouble()));
  
    return true;
  }

  

  //! Formats a string suitable to use as an XML element.
  /*! Formats a string suitable to be used as an XML element. Typical
    double property elements that might be generated in this function
    look like this:

    \verbatim 
    <prop>
    <name>SEARCHED_MASS</name>
    <data>1000.234</data>
    </prop>
    \endverbatim  
  
    \param offset times the \p indent string must be used as a lead in the
    formatting of elements.

    \param indent string used to create the leading space that is placed
    at the beginning of indented XML elements inside the XML
    element. Defaults to two spaces(QString(" ")).

    \return a dynamically allocated string that needs to be freed after
    use.

    \sa renderXmlElement(const QDomElement &element)
  */
  QString *
  DoubleProp::formatXmlElement(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 <prop> node that should look like this:
     *
     *  <prop>
     *    <name>SEARCHED_MASS</name>
     *    <data>1000.234</data>
     *  </prop>
     *
     */

    *string += QString("%1<prop>\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);
    
    QString value;
    value = QString::number(*static_cast<double *>(mpa_data), 'g', 10);
  
    *string += QString("%1<data>%2</data>\n")
      .arg(lead)
      .arg(value);
  
    // Prepare the lead for the closing element.
    --newOffset;
    lead.clear();
    iter = 0;
    while(iter < newOffset)
      {
	lead += indent;
	++iter;
      }

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

  

  /////////////////// NoDeletePointerProp ///////////////////

 

  NoDeletePointerProp::NoDeletePointerProp(const QString &name, 
					    void *noDeletePointer)
  {
    m_name = name;
    mpa_data = noDeletePointer;
  }

  
  NoDeletePointerProp::~NoDeletePointerProp()
  {
    deleteData();
  }

  
  void 
  NoDeletePointerProp::deleteData()
  {
    // We do not do anything here.
  }

  
  void *
  NoDeletePointerProp::clone() const
  {
    NoDeletePointerProp *prop = 0;
    
    if (mpa_data)
      prop = 
        new NoDeletePointerProp(m_name, 
                                static_cast<void *>(mpa_data));

    return static_cast<void *>(prop);
  }
  
  
  void 
  NoDeletePointerProp::cloneOut(void *other) const
  {
    Q_ASSERT(other);
    
    NoDeletePointerProp *otherProp = static_cast<NoDeletePointerProp *>(other);
    
    if (otherProp->mpa_data)
      otherProp->deleteData();
    
    otherProp->m_name = m_name;
    
    if (mpa_data)
      {
	otherProp->mpa_data = static_cast<void *>(mpa_data);
      }
    else
      otherProp->mpa_data = 0;  
  }
  
  
  void 
  NoDeletePointerProp::cloneIn(const void *other) 
  {
    qDebug() << __FILE__ << __LINE__ << "Does nothing";
    Q_ASSERT(0);
  }
  


  bool 
  NoDeletePointerProp::renderXmlElement(const QDomElement &element, 
					 int version)
  {
    if (element.tagName() != "prop")
      return false;
  
    return true;
  }


  QString *
  NoDeletePointerProp::formatXmlElement(int offset, const QString &indent)
  {
    QString *string = 
      new QString(QObject::tr("%1-This function does not return anything "
				"interesting-%2")
		   .arg(offset)
		   .arg(indent));
  
    return string;
  }





  //! Allocates the right property according to its name.
  /*! This function analyzes the contents of the string in \p name and
    calls the proper constructor.

    \attention No arguments are available to pass to the constructor, so
    it should exists either with no arguments or with default arguments.

    For example, if \p name is "MODIF", the constructor that gets called
    is StringProp() with no arguments.  If the \p name paramerter is
    either a null string or an empty string, the function returns 0.
  
    \param name Name of the property for which a new property instance
    is required.
  
    \return A pointer to the newly allocated property or 0 if \p name is
    either null, empty or not registered.
  */
  Prop *
  propAllocator(const QString &name, const PolChemDef *polChemDef)
  {
    if (name.isEmpty())
      return 0;
    else if (name == "MODIF")
      {
	Q_ASSERT(polChemDef);
      
	// Allocate a modif object.

	Modif *modif = new Modif(polChemDef, "NOT_SET");
      
	return new ModifProp(modif);
      }
    else if (name == "CHEMICAL_GROUP")
      return new ChemicalGroupProp();
    else
      return 0;
  }

} // namespace massXpert
