/* 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/Prop.hpp>
#include <massxpert/nongui/ChemicalGroup.hpp>
#include <massxpert/nongui/CrossLink.hpp>


namespace msXpSmassXpert
{

//! 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 msXpSmassXpert
