//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Param/Node/INode.h
//! @brief     Defines interface INode.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_PARAM_NODE_INODE_H
#define BORNAGAIN_PARAM_NODE_INODE_H

#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

const double INF = std::numeric_limits<double>::infinity();

//! Metadata of one model parameter.
struct ParaMeta {
    std::string name;
    std::string unit;
};

//! Base class for tree-like structures containing parameterized objects.

class INode {
public:
    INode() = default;
    INode(const std::vector<double>& PValues);

    virtual ~INode() = default;

    //! Returns the class name, to be hard-coded in each leaf class that inherits from INode.
    virtual std::string className() const = 0;

#ifndef SWIG
    //! Returns all children.
    virtual std::vector<const INode*> nodeChildren() const;

    //! Returns all descendants. // TODO sep22: rm if it remains UNUSED
    std::vector<const INode*> nodeOffspring() const;
#endif // SWIG

    //! Returns the parameter definitions, to be hard-coded in each leaf class.
    virtual std::vector<ParaMeta> parDefs() const { return {}; }

    const std::vector<double>& pars() const { return m_P; }

    //! Returns number of actual parameters.
    size_t nPars() const { return m_P.size(); }

    //! Returns value of specified parameter.
    double parVal(size_t i) const { return m_P.at(i); }

    virtual std::string validate() const { return ""; }
    void validateOrThrow() const;

protected:
    std::vector<double> m_P;

    mutable bool m_validated{false};

    static void requestGt0(std::vector<std::string>& errs, const double& val,
                           const std::string& name);
    static void requestGe0(std::vector<std::string>& errs, const double& val,
                           const std::string& name);
    static void requestIn(std::vector<std::string>& errs, const double& val,
                          const std::string& name, double min, double max);

    std::string jointError(const std::vector<std::string> errs) const;
};

//  ************************************************************************************************
//  vector concatenation, for use in nodeChildren functions
//  ************************************************************************************************

#ifndef SWIG

template <typename T>
std::vector<const INode*>& operator<<(std::vector<const INode*>& v_node,
                                      const std::unique_ptr<T>& node)
{
    if (node)
        v_node.push_back(node.get());
    return v_node;
}

template <typename T>
std::vector<const INode*>& operator<<(std::vector<const INode*>&& v_node,
                                      const std::unique_ptr<T>& node)
{
    if (node)
        v_node.push_back(node.get());
    return v_node;
}

inline std::vector<const INode*>& operator<<(std::vector<const INode*>& v_node, const INode* node)
{
    v_node.push_back(node);
    return v_node;
}

inline std::vector<const INode*>& operator<<(std::vector<const INode*>&& v_node, const INode* node)
{
    v_node.push_back(node);
    return v_node;
}

inline std::vector<const INode*>& operator<<(std::vector<const INode*>& v_node,
                                             const std::vector<const INode*>& other)
{
    v_node.insert(v_node.end(), other.begin(), other.end());
    return v_node;
}

inline std::vector<const INode*>& operator<<(std::vector<const INode*>&& v_node,
                                             const std::vector<const INode*>& other)
{
    v_node.insert(v_node.end(), other.begin(), other.end());
    return v_node;
}

#endif // SWIG

#endif // BORNAGAIN_PARAM_NODE_INODE_H
