/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#ifndef PROPERTIES_H
#define PROPERTIES_H

#include "PhysicalModelIO.h"
#include <string>
#include <sstream>
#include <map>
#include <cstdlib>

// XSD include
#include <xsd/cxx/tree/containers-wildcard.hxx>

class PhysicalModel;
/**
  * @ingroup group_cepmodeling_libraries_pml
  *
  * @brief
  * Describes the properties common to all structures and components.
  *
  * Properties uses a generic mechanisms to store any kind of properties,
  * henceforth called "fields".
  * A field is a (name:string, value:string) pair.
  *
  * Fields are read directly from XML, and then can be changed
  * through the modificators methods.
  *
  * The field accessors allows you to get the value in the form
  * of different basic types (string, double, bool, int).
  *
  * Method \a numberOfFields():usigned and \a getField(unsigned):string
  * help you to get information about available fields.
  *
 **/
class Properties {

public:
    /** A nice simple constructor, with a given name */
    Properties(const std::string n = "");

    /** Another nice constructor, with the PM and a name */
    Properties(PhysicalModel*, const std::string n = "");

    /** The default destructor */
    virtual ~Properties() = default;

    /// get the name (be careful, this method DOES NOT return a copy, so you got the direct ptr to the name!!!)
    std::string getName() const;

    /// set the name (use the string = operator)
    void setName(std::string);

    /// set the physical model
    void setPhysicalModel(PhysicalModel*);

    /// get the physical model
    PhysicalModel* getPhysicalModel() const;

    /**@name Field methods (manages any custom fields) */
    //@{
    /// convert the xml node parameters to data fields
    void xmlToFields(xsd::cxx::tree::attribute_set<char> attrs);

    /// get the number of extra fields found in the PML
    unsigned int numberOfFields() const;

    /// check if the field exist in the XML document, return false if it does not
    bool isAField(std::string attName) const;

    /** get the name of field of given index
      * @return the field name if i is valid, otherwise return blank string ""
      */
    std::string getField(unsigned int) const;

    /// field accessor: get the field attName as a double value, if field does not exist, 0.0 is return
    double getDouble(std::string attName);

    /// field accessor: get the field attName as an int value, if field does not exist, 0 is return
    int getInt(std::string attName) const;

    /// field accessor: get the field attName as a bool value, if field does not exist, false is return
    bool getBool(std::string attName) const;

    /// field accessor: get the field attName as a string value, if field does not exist, empty string is return
    std::string getString(std::string attName) const;

    /// field accessor: get the field attName as a string value in attVal, if field does not exist, empty string is return
    void get(std::string attName, std::string& attVal) const;

    /// field modificator: set field attName using a double value
    void set(std::string attName, double val);

    /// field modificator: set field attName using an int value
    void set(std::string attName, int val);

    /// field modificator: set field attName using a bool value
    void set(std::string attName, bool val);

    /// field modificator: set field attName using a string value
    void set(std::string attName, std::string val);
    //@}

protected :
    /// map containing all the different fields (name, value stored as string )
    std::map<std::string, std::string> fields;

private:
    /// name of the physical model object
    std::string name;

    /// pointer to the physical model the object is in
    PhysicalModel* myPM;

};

inline bool Properties::isAField(std::string attName) const {
    auto it = fields.find(attName);
    return (it != fields.end());
}

inline double Properties::getDouble(std::string attName) {
    auto it = fields.find(attName);

    if (it != fields.end()) {
        return atof(it->second.c_str());
    }
    else {
        return 0.0;
    }
}

inline int Properties::getInt(std::string attName) const {
    auto it = fields.find(attName);

    if (it != fields.end()) {
        return std::atoi(it->second.c_str());
    }
    else {
        return 0;
    }
}

inline bool Properties::getBool(std::string attName) const {
    auto it = fields.find(attName);

    if (it == fields.end() || it->second == "false" || it->second == "0") {
        return false;
    }
    else {
        return true;
    }
}

inline std::string Properties::getString(std::string attName) const {
    auto it = fields.find(attName);

    if (it != fields.end()) {
        return it->second;
    }
    else {
        return "";
    }
}

inline void Properties::get(std::string attName, std::string& attVal) const {
    auto it = fields.find(attName);

    if (it != fields.end()) {
        attVal = it->second;
    }
    else {
        attVal = "";
    }
}

inline void Properties::set(std::string attName, double val) {
    std::ostringstream oss;
    oss << val;
    auto it = fields.find(attName);

    if (it != fields.end()) {
        it->second = oss.str();
    }
    else {
        fields.insert(std::pair<std::string, std::string>(attName, oss.str()));
    }
}

inline void Properties::set(std::string attName, int val) {
    std::ostringstream oss;
    oss << val;
    auto it = fields.find(attName);

    if (it != fields.end()) {
        it->second = oss.str() ;
    }
    else {
        fields.insert(std::pair<std::string, std::string>(attName, oss.str()));
    }
}

inline void Properties::set(std::string attName, bool val) {
    std::ostringstream oss;
    oss << val;
    auto it = fields.find(attName);

    if (it != fields.end()) {
        it->second = oss.str() ;
    }
    else {
        fields.insert(std::pair<std::string, std::string>(attName, oss.str()));
    }
}

inline void Properties::set(std::string attName, std::string val) {
    auto it = fields.find(attName);

    if (it != fields.end()) {
        it->second = val ;
    }
    else {
        fields.insert(std::pair<std::string, std::string>(attName, val));
    }
}

inline std::string Properties::getName() const {
    return name;
}

inline void Properties::setName(std::string n) {
    name = std::string(n);
}

inline void Properties::setPhysicalModel(PhysicalModel* pm) {
    myPM = pm;
}

inline PhysicalModel* Properties::getPhysicalModel() const {
    return myPM;
}

#endif //PROPERTIES_H
