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


#pragma once

/////////////////////// stdlib includes
#include <memory>
#include <unordered_map>


/////////////////////// Qt includes
#include <QUuid>
#include <QString>
#include <QObject>
#include <QDateTime>
#include <QCryptographicHash>


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "MsXpS/export-import-config.h"

#include "MsXpS/libXpertMassCore/jsclassregistrar.h"
#include "MsXpS/libXpertMassCore/CrossLink.hpp"
#include "MsXpS/libXpertMassCore/Sequence.hpp"
#include "MsXpS/libXpertMassCore/Ionizer.hpp"
#include "MsXpS/libXpertMassCore/Modif.hpp"
#include "MsXpS/libXpertMassCore/CalcOptions.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{

const int POL_SEQ_FILE_FORMAT_VERSION = 1;

class PolChemDef;
typedef std::shared_ptr<const PolChemDef> PolChemDefCstSPtr;
typedef QSharedPointer<PolChemDef> PolChemDefQSPtr;
typedef QSharedPointer<const PolChemDef> PolChemDefCstQSPtr;

class Polymer;
typedef std::shared_ptr<Polymer> PolymerSPtr;
typedef std::shared_ptr<const Polymer> PolymerCstSPtr;
typedef QSharedPointer<Polymer> PolymerQSPtr;
typedef QSharedPointer<const Polymer> PolymerCstQSPtr;

class Ionizer;
class CrossLink;
class CrossLinkList;

/*  BEGIN CLASS JS REFERENCE
 *  namespace: MsXpS::libXpertMassCore
 *  class name: Polymer
 */
class DECLSPEC Polymer: public QObject, public QEnableSharedFromThis<Polymer>
{
  friend class Ionizer;

  Q_OBJECT

  Q_PROPERTY(QString name WRITE setName READ getName)
  Q_PROPERTY(QString code WRITE setCode READ getCode)
  Q_PROPERTY(QString author WRITE setAuthor READ getAuthor)
  Q_PROPERTY(QString filePath WRITE setFilePath READ getFilePath)
  Q_PROPERTY(QString sequence WRITE setSequence READ getSequenceText)
  Q_PROPERTY(std::size_t size READ size)

  public:
  static PolymerQSPtr createSPtr(PolChemDefCstSPtr pol_chem_def_csp = nullptr,
                                 const QString &name                = QString(),
                                 const QString &code                = QString(),
                                 const QString &author              = QString(),
                                 const QString &sequence_string = QString());

  Polymer(const Polymer &other)            = delete;
  Polymer &operator=(const Polymer &other) = delete;

  virtual ~Polymer();

  //////////////// THE SHARED POINTER /////////////////////
  PolymerCstQSPtr getCstSharedPtr();
  PolymerQSPtr getSharedPtr();


  //////////////// THE POLCHEMDEF /////////////////////
  void setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp);
  const PolChemDefCstSPtr &getPolChemDefCstSPtr() const;

  //////////////// THE NAME /////////////////////
  Q_INVOKABLE void setName(const QString &name);
  Q_INVOKABLE QString getName() const;

  //////////////// THE CODE /////////////////////
  Q_INVOKABLE void setCode(const QString &code);
  Q_INVOKABLE QString getCode() const;

  //////////////// THE AUTHOR /////////////////////
  Q_INVOKABLE void setAuthor(const QString &author);
  Q_INVOKABLE QString getAuthor() const;

  //////////////// THE FILE PATH /////////////////////
  Q_INVOKABLE void setFilePath(const QString &file_path);
  Q_INVOKABLE QString getFilePath() const;

  Q_INVOKABLE void setDateTime();
  Q_INVOKABLE void setDateTime(const QString &date_time);
  Q_INVOKABLE QString getDateTime() const;

  //////////////// THE SEQUENCE /////////////////////
  Q_INVOKABLE void setSequence(const QString &sequence_string);
  void setSequence(const Sequence &sequence);
  const Sequence &getSequenceCstRef() const;
  Sequence &getSequenceRef();
  Q_INVOKABLE QString getSequenceText() const;

  Q_INVOKABLE std::size_t size() const;

  // MONOMER MODIFICATIONS
  /////////////////////////////
  std::size_t
  modifiedMonomerCount(const IndexRangeCollection &index_ranges) const;
  Q_INVOKABLE bool
  modifyMonomer(std::size_t index, const QString modif_name, bool override);
  Q_INVOKABLE bool hasModifiedMonomer(std::size_t left_index,
                                      std::size_t right_index) const;

  // POLYMER MODIFICATIONS
  /////////////////////////////
  Q_INVOKABLE void unmodify(Enums::PolymerEnd polymer_end);
  // LEFT END
  Q_INVOKABLE bool setLeftEndModifByName(const QString &name = QString());
  bool setLeftEndModif(const Modif &modif);
  const Modif &getLeftEndModifCstRef() const;
  Modif &getLeftEndModifRef();
  bool isLeftEndModified() const;
  // RIGHT END
  Q_INVOKABLE bool setRightEndModifByName(const QString &name = QString());
  bool setRightEndModif(const Modif &);
  const Modif &getRightEndModifCstRef() const;
  Modif &getRightEndModifRef();
  bool isRightEndModified() const;

  //////////////// THE CALCULATION OPTIONS /////////////////////
  Q_INVOKABLE void setCalcOptions(const CalcOptions &calc_options);
  Q_INVOKABLE void setCalcOptions(const CalcOptions *calc_options);
  const CalcOptions &getCalcOptionsCstRef() const;
  const CalcOptions &getCalcOptionsRef();
  Q_INVOKABLE CalcOptions *getCalcOptions();

  // IONIZATION
  /////////////////////////////
  Q_INVOKABLE void setIonizer(const Ionizer *ionizer_p);
  void setIonizer(const Ionizer &ionizer);
  const Ionizer &getIonizerCstRef() const;
  Ionizer &getIonizerRef();
  Q_INVOKABLE Ionizer *getIonizer();
  Q_INVOKABLE Enums::IonizationOutcome ionize();
  Q_INVOKABLE Enums::IonizationOutcome ionize(const Ionizer &ionizer);
  Q_INVOKABLE Enums::IonizationOutcome deionize();
  Q_INVOKABLE Enums::IonizationOutcome deionize(const Ionizer &ionizer);
  Q_INVOKABLE Enums::IonizationOutcome molecularMasses(double &mono,
                                                       double &avg) const;

  // CROSS-LINKS
  /////////////////////////////
  Q_INVOKABLE std::size_t crossLinkCount() const;
  const std::vector<CrossLinkSPtr> &getCrossLinksCstRef() const;
  std::vector<CrossLinkSPtr> &getCrossLinksRef();

  bool crossLinkedMonomersIndicesInRange(std::size_t start_index,
                                         std::size_t stop_index,
                                         std::vector<std::size_t> &indices,
                                         std::size_t &partials) const;
  bool crossLinksInRange(
    std::size_t start_index,
    std::size_t stop_index,
    std::vector<MsXpS::libXpertMassCore::CrossLinkSPtr> &cross_links,
    std::size_t &partials) const;

  std::vector<std::size_t>
  crossLinkIndicesInvolvingMonomer(MonomerSPtr monomer_sp) const;
  std::vector<std::size_t>
  crossLinkIndicesInvolvingMonomer(MonomerCstRPtr monomer_crp) const;

  QString crossLink(CrossLinkSPtr cross_link_sp);
  bool uncrossLink(CrossLinkSPtr cross_link_sp);

  // MONOMER REMOVAL
  /////////////////////////////
  std::size_t prepareMonomerRemoval(MonomerSPtr monomer_csp);
  bool removeMonomerAt(std::size_t index);

  // MASS CALCULATION FUNCTIONS
  /////////////////////////////
  Q_INVOKABLE static bool calculateMasses(const Polymer *polymer_p,
                                          const CalcOptions &calc_options,
                                          double &mono,
                                          double &avg,
                                          bool reset);

  Q_INVOKABLE static bool calculateMasses(const Polymer *polymer_p,
                                          const CalcOptions &calc_options,
                                          const Ionizer &ionizer,
                                          double &mono,
                                          double &avg);

  Q_INVOKABLE bool calculateMasses(const CalcOptions &calc_options, bool reset);

  Q_INVOKABLE bool calculateMasses(const CalcOptions &calc_options,
                                   const Ionizer &ionizer);

  Q_INVOKABLE bool calculateMasses(const CalcOptions *calc_options_p,
                                   const Ionizer *ionizer_p);


  Q_INVOKABLE bool calculateMasses();

  bool accountMasses(const CalcOptions &calc_options);
  static bool accountMasses(const Polymer *polymer_p,
                            const CalcOptions &calc_options,
                            double &mono,
                            double &avg);

  bool accountCappingMasses(Enums::CapType cap_type, int times = 1);
  static bool accountCappingMasses(const Polymer *polymer_p,
                                   Enums::CapType cap_type,
                                   double &mono,
                                   double &avg,
                                   int times = 1);

  void accountEndModifMasses(Enums::ChemicalEntity polymer_chem_entities);
  static void accountEndModifMasses(const Polymer *polymer_p,
                                    Enums::ChemicalEntity polymer_chem_entities,
                                    double &mono,
                                    double &avg);

  Q_INVOKABLE double getMass(Enums::MassType mass_type) const;
  Q_INVOKABLE double &getMassRef(Enums::MassType mass_type);

  // ELEMENTAL COMPOSITION
  ////////////////////////////
  Q_INVOKABLE QString
  elementalComposition(const IndexRangeCollection &index_range_collection,
                       const CalcOptions &calc_options) const;
  Q_INVOKABLE QString
  elementalComposition(const IndexRangeCollection &index_range_collection,
                       const CalcOptions &calc_options,
                       const Ionizer &ionizer) const;

  Q_INVOKABLE QString
  elementalComposition(const CalcOptions &calc_options) const;

  Q_INVOKABLE QString elementalComposition(const CalcOptions &calc_options,
                                           const Ionizer &ionizer) const;


  // XML DATA RENDERING AND WRITING
  ///////////////////////////////////

  bool renderXmlCodesElement(const QDomElement &element);

  static QString xmlPolymerFileGetPolChemDefName(const QString &file_path);

  bool renderXmlPolymerFile(const QString &file_path = QString(""));
  bool renderXmlPolymerModifElement(const QDomElement &, int);
  bool renderXmlCrossLinksElement(const QDomElement &, int);

  QString formatXmlDtd();
  QString formatXmlPolSeqElement(int, const QString & = QString("  "));
  QString formatXmlCrossLinksElement(int, const QString & = QString("  "));

  bool writeXmlFile();

  // VALIDATIONS
  ////////////////////////////
  bool validate(ErrorList *error_list_p) const;
  Q_INVOKABLE bool isValid() const;

  // UTILITIES
  ////////////////////////////
  QByteArray md5Sum(int hash_data_specifs) const;

  QString storeCrossLink(CrossLinkSPtr cross_link_sp);
  bool hasCrossLink(const CrossLinkSPtr &cross_links_sp) const;
  bool hasUuid(const CrossLinkSPtr &cross_links_sp) const;

  CrossLinkSPtr getCrossLinkFromUuid(const QString &uuid) const;
  QString getUuidForCrossLink(const CrossLinkSPtr &cross_link_sp) const;
  MonomerCstSPtr
  getCrossLinkedMonomerCstSPtrFromUuid(const QString &uuid) const;

  std::vector<QString> getAllCrossLinkUuids() const;

  Q_INVOKABLE QString toString() const;
  Q_INVOKABLE void clear();

  static void registerJsConstructor(QJSEngine *engine);

  signals:
  void polymerDestroyedSignal(Polymer *polymer_p);
  void crossLinkChangedSignal(Polymer *polymer_p);
  void
  crossLinksPartiallyEncompassedSignal(int partial_cross_links_count) const;

  protected:
  PolChemDefCstSPtr mcsp_polChemDef = nullptr;

  QString m_name;
  QString m_code;
  QString m_author;
  QString m_filePath;
  QDateTime m_dateTime;
  Sequence m_sequence;
  Modif m_leftEndModif;
  Modif m_rightEndModif;
  // Always heap-allocated during construction
  CalcOptions *mp_calcOptions = nullptr;
  // Always heap-allocated during construction
  Ionizer *mp_ionizer         = nullptr;

  std::vector<CrossLinkSPtr> m_crossLinks;

  //  We need in code that uses this class,  to constantly keep at hand
  //  the CrossLink instances. For example,
  //  we need to store the CrossLink pointers as strings in QListWidget items
  //  (Qt:UserRole).
  //  We thus make use of the QUuid class to craft a Uuid string that
  //  we associate to the CrossLink weak pointer that in turn relates
  //  to the CrossLink shared pointer.
  //  Note that we create CrossLink instances as SPtr (non-const) but we
  //  provide functions to access them as const.
  //  When a CrossLink is created, it SPtr is referenced along a newly-created
  //  QUuid string and store here along with a WeakPtr of the SPtr.
  std::vector<UuidCrossLinkWPtrPair> m_uuidCrossLinkPairs;

  double m_mono;
  double m_avg;

  mutable bool m_isValid = false;

  private:
  Polymer(PolChemDefCstSPtr pol_chem_def_csp = nullptr,
          const QString &name                = QString(),
          const QString &code                = QString(),
          const QString &author              = QString(),
          const QString &sequence_string     = QString());

  bool removeCrossLink(CrossLinkSPtr crossLink_sp);
  void removeCrossLink(const QString &uuid);
  void cleanupCrossLinks();
};

/*  END CLASS JS REFERENCE
    namespace: MsXpS::libXpertMassCore
    class name: Polymer
    */

} // namespace libXpertMassCore
MSXPS_REGISTER_JS_CLASS(MsXpS::libXpertMassCore, Polymer)

} // namespace MsXpS

Q_DECLARE_METATYPE(MsXpS::libXpertMassCore::Polymer);
extern int polymerMetaTypeId;

Q_DECLARE_METATYPE(MsXpS::libXpertMassCore::PolymerSPtr);
extern int polymerSPtrMetaTypeId;

Q_DECLARE_METATYPE(MsXpS::libXpertMassCore::PolymerCstQSPtr);
extern int PolymerCstQSPtrMetaTypeId;
