/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright 2009--2026 by 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 "MsXpS/libXpertMassCore/Prop.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{

/*!
\class MsXpS::libXpertMassCore::Prop
\inmodule libXpertMassCore
\ingroup ThePropSystem
\inheaderfile Prop.hpp

\brief The Prop class is the abstract base class for a number of specialized
properties.

Properties are libXpertMassCore' way of extending the capabilities of
objects. A property is merely an encapsulation of:

\list
\li a name(a QString);

\li a pointer to data specifically allocated for this property to
become the owner of these data (the member data pointer is a void *);
\endlist

In order to perform tasks in the derived classes using dynamic
binding, virtual functions are available to derived classes to
perform:

\list
\li XML string formatting (formatXmlElement());

\li XML element rendering (renderXmlElement());

\li data destruction (deleteData()).
\endlist

Derived classes should be named according the following scheme:
XxxYyyZzzzProp, like StringProp or MonomerProp.

The classes that benefit from this Property-based extension mechanism all
derive from \l PropListHolder.

\sa StringProp, PropListHolder
*/

/*!
\variable int MsXpS::libXpertMassCore::Prop::m_name

\brief The name of the property. Initialized to "NOT_SET".
*/

/*!
\variable int MsXpS::libXpertMassCore::Prop::mpa_data

\brief The allocated data belonging to this Prop instance. Initialized to
nullptr.
*/


/*!
\brief Constructs a Prop instance.
*/
Prop::Prop()
{
}

/*!
\brief Constructs a Prop instance with \a name.
*/
Prop::Prop(const QString &name): m_name(name)
{
}

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

\note The data are not duplicated.
*/
Prop::Prop(const Prop &other): m_name(other.m_name)
{
  // We cannot duplicate the data because we do not know their type.
}

/*!
\brief Destructs this Prop instance.

\note No action is taken here.
*/
Prop::~Prop()
{
  // Do nothing here.
}

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

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

/*!
\brief Sets the \a data.

If mpa_data is non-nullptr,  deleteData() is called before assigning \a data to
mpa_data.

Ownership passes to this Prop instance.
*/
void
Prop::setData(void *data)
{
  if(mpa_data != 0)
    deleteData();

  mpa_data = data;
}

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

/*!
\brief Deletes the data.

Nothing is done in this implementation.
*/
void
Prop::deleteData()
{
  // Do nothing here.
}

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

The data are not duplicated because their type is not known.
*/
Prop &
Prop::operator=(const Prop &other)
{
  if(&other == this)
    return *this;

  m_name = other.m_name;

  // We cannot duplicate the data because we do not know their type.

  return *this;
}

/*!
\brief Renders the \a element, with the specified \a version.

Nothing is done in this implementation and this function returns false.
*/
bool
Prop::renderXmlElement([[maybe_unused]] const QDomElement &element,
                       [[maybe_unused]] int version)
{
  // Do nothing here.
  return false;
}

/*!
\brief Formats and returns a string representing this Prop instance.

The formatting of the XML element is performed using \a offset and \a indent.

Nothing is done in this implementation. Returns an empty string.
*/
QString
Prop::formatXmlElement([[maybe_unused]] int offset,
                       [[maybe_unused]] const QString &indent)

{
  // Do nothing here.
  return QString();
}

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

/*!
\class MsXpS::libXpertMassCore::StringProp
\inmodule libXpertMassCore
\ingroup ThePropSystem

\brief The StringProp class is the specialized class for properties
that hold data in the form of string objects.

A StringProp property is a simple property in which the data is a pointer to an
allocated QString.
*/

/*!
\brief Constructs a StringProp instance.

\list
\li \a name: The name of the property.
\li \a data: The data of the property.
\endlist

The string passed as \a data is used to dynamically allocate a new string with
the same contents and then is set to the member mpa_data pointer.
*/
StringProp::StringProp(const QString &name, const QString &data)
{
  m_name   = name;
  mpa_data = static_cast<void *>(new QString(data));
}

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

The member data, if non-, are first deleted.
*/
StringProp::StringProp(const StringProp &other): Prop(other)
{
  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      QString *text = static_cast<QString *>(other.mpa_data);
      mpa_data      = static_cast<void *>(new QString(*text));
    }
  else
    mpa_data = nullptr;
}

/*!
\brief Destructs this StringProp instance.

Deletion of the data is delegated to \l deleteData().
*/
StringProp::~StringProp()
{
  deleteData();
}

/*!
\brief Deletes the member data.
*/
void
StringProp::deleteData()
{
  if(mpa_data != nullptr && !static_cast<QString *>(mpa_data)->isNull())
    {
      delete static_cast<QString *>(mpa_data);
      mpa_data = nullptr;
    }
}

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

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

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      QString *text = static_cast<QString *>(other.mpa_data);
      mpa_data      = static_cast<void *>(new QString(*text));
    }
  else
    mpa_data = nullptr;

  return *this;
}

/*!
\brief Duplicates this StringProp instance and returns a pointer to it.
*/
StringProp *
StringProp::cloneOut() const
{
  StringProp *new_p = new StringProp(*this);

  return new_p;
}

/*!
\brief Parses the string-only property XML \a element using a \a{version}ed
function.

Parses the string-only property XML element passed as argument and for each
encountered data(name and data) will set the data to this string-only
property (this is called XML rendering).

The XML element looks like this:

\code
<prop>
  <name>MODIF</name>
  <data>acetylation</data>
</prop>
\endcode

Returns true if parsing was successful, false otherwise.
*/
bool
StringProp::renderXmlElement(const QDomElement &element,
                             [[maybe_unused]] 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;
}

/*!
\brief 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:

  \code
  <prop>
  <name>MODIF</name>
  <data>Phosphorylation</data>
  </prop>
  <prop>
  <name>COMMENT</name>
  <data>Phosphorylation is only partial</data>
  </prop>
  \endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.
*/
QString
StringProp::formatXmlElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString text;

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

  text += QString("%1<prop>\n").arg(lead);

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

  // Continue with indented elements.

  text += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  text += 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;
    }

  text += QString("%1</prop>\n").arg(lead);

  return text;
}

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


/*!
\class MsXpS::libXpertMassCore::IntProp
\inmodule libXpertMassCore
\ingroup ThePropSystem

\brief The IntProp class is the specialized class for properties
that hold data in the form of integer values.

A IntProp property is a simple property in which the data is a pointer to an
allocated integer.
*/


/*!
\brief Constructs a IntProp instance.

\list
\li \a name: The name of the property.
\li \a data: The data of the property.
\endlist

The integer passed as \a data is used to dynamically allocate a new integer with
the same contents and then is set to the member mpa_data pointer.
*/
IntProp::IntProp(const QString &name, int data): Prop(name)
{
  mpa_data = static_cast<void *>(new int(data));
}

/*!
\brief Constructs a IntProp instance as a copy of \a other.
*/
IntProp::IntProp(const IntProp &other): Prop(other)
{
  if(other.mpa_data != nullptr)
    {
      int *value = static_cast<int *>(other.mpa_data);
      mpa_data   = static_cast<void *>(new int(*value));
    }
}

/*!
\brief Destructs this IntProp instance.

Deletion of the data is delegated to \l deleteData().
*/
IntProp::~IntProp()
{
  deleteData();
}

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

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

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

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      int *value = static_cast<int *>(other.mpa_data);
      mpa_data   = static_cast<void *>(new int(*value));
    }
  else
    mpa_data = nullptr;

  return *this;
}

/*!
\brief Duplicates this IntProp instance and returns a pointer to it.
*/
IntProp *
IntProp::cloneOut() const
{
  IntProp *new_p = new IntProp(*this);

  return new_p;
}

/*!
\brief Parses a integer property XML \a element using a \a{version}ed
function.

Parses the integer property XML element passed as argument and for each
encountered data (name and data) will set the data to this IntProp
instance (this is called XML rendering).

Returns true if parsing was successful, false otherwise.)
*/
bool
IntProp::renderXmlElement(const QDomElement &element,
                          [[maybe_unused]] 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;
}

/*!
\brief Formats a string suitable to use as an XML element.

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

  \code
  <prop>
  <name>IONIZATION_LEVEL</name>
  <data>5</data>
  </prop>
  \endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString
IntProp::formatXmlElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString text;

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

  text += QString("%1<prop>\n").arg(lead);

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

  // Continue with indented elements.

  text += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  QString value;
  value = QString::number(*static_cast<int *>(mpa_data), 'g', 10);

  text += 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;
    }

  text += QString("%1</prop>\n").arg(lead);

  return text;
}

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


/*!
\class MsXpS::libXpertMassCore::DoubleProp
\inmodule libXpertMassCore
\ingroup ThePropSystem

\brief The DoubleProp class is the specialized class for properties
that hold data in the form of double values.

A DoubleProp property is a simple property in which the data is a pointer to an
allocated double.
*/


/*!
\brief Constructs a DoubleProp instance.

\list
\li \a name: The name of the property.
\li \a data: The data of the property.
\endlist

The integer passed as \a data is used to dynamically allocate a new double with
the same contents and then is set to the member mpa_data pointer.
*/
DoubleProp::DoubleProp(const QString &name, double data): Prop(name)
{
  mpa_data = static_cast<void *>(new double(data));
}

/*!
\brief Constructs a DoubleProp instance as a copy of \a other.
*/
DoubleProp::DoubleProp(const DoubleProp &other): Prop(other)
{
  if(other.mpa_data != nullptr)
    {
      double *value = static_cast<double *>(other.mpa_data);
      mpa_data      = static_cast<void *>(new double(*value));
    }
}

/*!
\brief Destructs this DoubleProp instance.

Deletion of the data is delegated to \l deleteData().
*/
DoubleProp::~DoubleProp()
{
  deleteData();
}

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

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

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

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      double *value = static_cast<double *>(other.mpa_data);
      mpa_data      = static_cast<void *>(new double(*value));
    }
  else
    mpa_data = nullptr;

  return *this;
}

/*!
\brief Duplicates this DoubleProp instance and returns a pointer to it.
*/
DoubleProp *
DoubleProp::cloneOut() const
{
  DoubleProp *new_p = new DoubleProp(*this);

  return new_p;
}

/*!
\brief Parses a double property XML \a element using a \a{version}ed
function.

Parses the double property XML element passed as argument and for each
encountered data (name and data) will set the data to this DoubleProp
instance (this is called XML rendering).

Returns true if parsing was successful, false otherwise.)
*/
bool
DoubleProp::renderXmlElement(const QDomElement &element,
                             [[maybe_unused]] 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;
}

/*!
\brief 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:

  \code
  <prop>
  <name>SEARCHED_MASS</name>
  <data>1000.234</data>
  </prop>
  \endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString
DoubleProp::formatXmlElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString text;

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

  text += QString("%1<prop>\n").arg(lead);

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

  // Continue with indented elements.

  text += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  QString value;
  value = QString::number(*static_cast<double *>(mpa_data), 'g', 10);

  text += 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;
    }

  text += QString("%1</prop>\n").arg(lead);

  return text;
}

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


/*!
\class MsXpS::libXpertMassCore::NoDeletePointerProp
\inmodule libXpertMassCore
\ingroup ThePropSystem

\brief The NoDeletePointerProp class provides a pointer property.

A NoDeletePointerProp property is a simple property in which the data is a
pointer to an allocated instance, but which may never be destroyed by the
property itself. This property is regarded as a simple "message-containing
property". The message is nothing but the name of the property.
*/


/*!
\brief Constructs a NoDeletePointerProp instance.

\list
\li \a name: The name of the property.
\li \a no_delete_data_p: The data of the property.
\endlist

The member data pointer (\l mpa_data) is assigned \a no_delete_data_p and is
not going to be deleted upon destruction of this NoDeletePointerProp instance.
*/
NoDeletePointerProp::NoDeletePointerProp(const QString &name,
                                         void *no_delete_data_p)
  : Prop(name)
{
  mpa_data = no_delete_data_p;
}

/*!
\brief Constructs a NoDeletePointerProp instance as a copy of \a other.
*/
NoDeletePointerProp::NoDeletePointerProp(const NoDeletePointerProp &other)
  : Prop(other)
{
  mpa_data = static_cast<void *>(other.mpa_data);
}

/*!
\brief Destructs this NoDeletePointerProp instance.

Deletion of the data is delegated to \l deleteData(), that won't delete the
data.
*/
NoDeletePointerProp::~NoDeletePointerProp()
{
  deleteData();
}

/*!
\brief Does not delete the member data.
*/
void
NoDeletePointerProp::deleteData()
{
  // We do not do anything here.
}

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

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

  Prop::operator=(other);

  mpa_data = static_cast<void *>(other.mpa_data);

  return *this;
}

/*!
\brief Duplicates this NoDeletePointerProp instance and returns a pointer to it.
*/
NoDeletePointerProp *
NoDeletePointerProp::cloneOut() const
{
  NoDeletePointerProp *new_p = new NoDeletePointerProp(*this);

  return new_p;
}

/*!
\brief This function is a no-op.

The \a element and the \a version parameters are not used. The
NoDeletePointerProp class is not used to store or read data to or from files.

Returns false if the \a element tag name is not "prop", true otherwise.
*/
bool
NoDeletePointerProp::renderXmlElement(const QDomElement &element,
                                      [[maybe_unused]] int version)
{
  if(element.tagName() != "prop")
    return false;

  return true;
}

/*!
\brief This function is a no-op.

The \a offset and the \a indent parameters are not used. The
NoDeletePointerProp class is not used to store or read data to or from files.
*/
QString
NoDeletePointerProp::formatXmlElement(int offset, const QString &indent)
{
  QString text =
    QString(QObject::tr("%1-This function does not return anything "
                        "interesting-%2")
              .arg(offset)
              .arg(indent));

  return text;
}


} // namespace libXpertMassCore

} // namespace MsXpS
