//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//

#ifndef MATERIALX_SHADERNODE_H
#define MATERIALX_SHADERNODE_H

/// @file
/// Classes for nodes created during shader generation

#include <MaterialXGenShader/Export.h>

#include <MaterialXGenShader/ShaderNodeImpl.h>
#include <MaterialXGenShader/TypeDesc.h>
#include <MaterialXGenShader/GenUserData.h>

#include <MaterialXCore/Node.h>

MATERIALX_NAMESPACE_BEGIN

class ShaderNode;
class ShaderPort;
class ShaderInput;
class ShaderOutput;
class ShaderGraph;

/// Shared pointer to a ShaderPort
using ShaderPortPtr = shared_ptr<class ShaderPort>;
/// Shared pointer to a ShaderInput
using ShaderInputPtr = shared_ptr<class ShaderInput>;
/// Shared pointer to a ShaderOutput
using ShaderOutputPtr = shared_ptr<class ShaderOutput>;
/// Shared pointer to a ShaderNode
using ShaderNodePtr = shared_ptr<class ShaderNode>;
/// A vector of ShaderInput pointers
using ShaderInputVec = vector<ShaderInput*>;

/// Metadata to be exported to generated shader.
struct MX_GENSHADER_API ShaderMetadata
{
    string name;
    TypeDesc type;
    ValuePtr value;
    ShaderMetadata(const string& n, TypeDesc t, ValuePtr v = nullptr) :
        name(n),
        type(t),
        value(v)
    {
    }
};
using ShaderMetadataVec = vector<ShaderMetadata>;
using ShaderMetadataVecPtr = shared_ptr<ShaderMetadataVec>;

/// @class ShaderMetadataRegistry
/// A registry for metadata that will be exported to the generated shader
/// if found on nodes and inputs during shader generation.
class MX_GENSHADER_API ShaderMetadataRegistry : public GenUserData
{
  public:
    static const string USER_DATA_NAME;

    /// Add a new metadata entry to the registry.
    /// The entry contains the name and data type
    /// for the metadata.
    void addMetadata(const string& name, TypeDesc type, ValuePtr value = nullptr)
    {
        if (_entryIndex.count(name) == 0)
        {
            _entryIndex[name] = _entries.size();
            _entries.emplace_back(name, type, value);
        }
    }

    /// Return the metadata registered for the given name, or nullptr
    /// if no such entry is found.
    const ShaderMetadata* findMetadata(const string& name) const
    {
        auto it = _entryIndex.find(name);
        return it != _entryIndex.end() ? &_entries[it->second] : nullptr;
    }

    /// Return the metadata registered for the given name, or nullptr
    /// if no such entry is found.
    ShaderMetadata* findMetadata(const string& name)
    {
        auto it = _entryIndex.find(name);
        return it != _entryIndex.end() ? &_entries[it->second] : nullptr;
    }

    /// Return all entries in the registry.
    const ShaderMetadataVec& getAllMetadata() const
    {
        return _entries;
    }

    /// Clear all entries in the registry.
    void clear()
    {
        _entryIndex.clear();
        _entries.clear();
    }

  protected:
    vector<ShaderMetadata> _entries;
    std::unordered_map<string, size_t> _entryIndex;
};

using ShaderMetadataRegistryPtr = shared_ptr<ShaderMetadataRegistry>;

/// Flags set on shader ports.
class MX_GENSHADER_API ShaderPortFlag
{
  public:
    static const uint32_t UNIFORM        = 1u << 0;
    static const uint32_t EMITTED        = 1u << 1;
    static const uint32_t BIND_INPUT     = 1u << 2;
    static const uint32_t AUTHORED_VALUE = 1u << 3;
};

/// @class ShaderPort
/// An input or output port on a ShaderNode
class MX_GENSHADER_API ShaderPort : public std::enable_shared_from_this<ShaderPort>
{
  public:
    /// Constructor.
    ShaderPort(ShaderNode* node, TypeDesc type, const string& name, ValuePtr value = nullptr);

    /// Return a shared pointer instance of this object.
    ShaderPortPtr getSelf()
    {
        return shared_from_this();
    }

    /// Return the node this port belongs to.
    ShaderNode* getNode() { return _node; }

    /// Return the node this port belongs to.
    const ShaderNode* getNode() const { return _node; }

    /// Set the data type for this port.
    void setType(TypeDesc type) { _type = type; }

    /// Return the data type for this port.
    TypeDesc getType() const { return _type; }

    /// Set the name of this port.
    void setName(const string& name) { _name = name; }

    /// Return the name of this port.
    const string& getName() const { return _name; }

    /// Return the name of this port.
    string getFullName() const;

    /// Set the variable name of this port.
    void setVariable(const string& name) { _variable = name; }

    /// Return the variable name of this port.
    const string& getVariable() const { return _variable; }

    /// Set the variable semantic of this port.
    void setSemantic(const string& semantic) { _semantic = semantic; }

    /// Return the variable semantic of this port.
    const string& getSemantic() const { return _semantic; }

    /// Set a value on this port.
    void setValue(ValuePtr value, bool authoredValue = true)
    {
        _value = value;
        setFlag(ShaderPortFlag::AUTHORED_VALUE, authoredValue);
    }

    /// Return the value set on this port.
    ValuePtr getValue() const { return _value; }

    /// Return the value set on this port as a string, or an empty string if there is no value.
    string getValueString() const;

    /// Set a source color space for the value on this port.
    void setColorSpace(const string& colorspace) { _colorspace = colorspace; }

    /// Return the source color space for the value on this port.
    const string& getColorSpace() const { return _colorspace; }

    /// Set a unit type for the value on this port.
    void setUnit(const string& unit) { _unit = unit; }

    /// Return the unit type for the value on this port.
    const string& getUnit() const { return _unit; }

    /// Set geomprop name if the input has a default
    /// geomprop to be assigned when it is unconnected.
    void setGeomProp(const string& geomprop) { _geomprop = geomprop; }

    /// Get geomprop name.
    const string& getGeomProp() const { return _geomprop; }

    /// Set the path to this port.
    void setPath(const string& path) { _path = path; }

    /// Return the path to this port.
    const string& getPath() const { return _path; }

    /// Set flags on this port.
    void setFlags(uint32_t flags) { _flags = flags; }

    /// Return flags set on this port.
    uint32_t getFlags() const { return _flags; }

    /// Set the on|off state of a given flag.
    void setFlag(uint32_t flag, bool value)
    {
        _flags = value ? (_flags | flag) : (_flags & ~flag);
    }

    /// Return the on|off state of a given flag.
    bool getFlag(uint32_t flag) const
    {
        return ((_flags & flag) != 0);
    }

    /// Set the uniform flag this port to true.
    void setUniform() { _flags |= ShaderPortFlag::UNIFORM; }

    /// Return the uniform flag on this port.
    bool isUniform() const { return (_flags & ShaderPortFlag::UNIFORM) != 0; }

    /// Set the emitted state on this port to true.
    void setEmitted() { _flags |= ShaderPortFlag::EMITTED; }

    /// Return the emitted state of this port.
    bool isEmitted() const { return (_flags & ShaderPortFlag::EMITTED) != 0; }

    /// Set the bind input state on this port to true.
    void setBindInput() { _flags |= ShaderPortFlag::BIND_INPUT; }

    /// Return the emitted state of this port.
    bool isBindInput() const { return (_flags & ShaderPortFlag::BIND_INPUT) != 0; }

    // Has the value been overridden.
    bool hasAuthoredValue() const { return (_flags & ShaderPortFlag::AUTHORED_VALUE) != 0; }

    /// Set the metadata vector.
    void setMetadata(ShaderMetadataVecPtr metadata) { _metadata = metadata; }

    /// Get the metadata vector.
    ShaderMetadataVecPtr getMetadata() { return _metadata; }

    /// Get the metadata vector.
    const ShaderMetadataVecPtr& getMetadata() const { return _metadata; }

  protected:
    ShaderNode* _node;
    TypeDesc _type;
    string _name;
    string _path;
    string _semantic;
    string _variable;
    ValuePtr _value;
    string _unit;
    string _colorspace;
    string _geomprop;
    ShaderMetadataVecPtr _metadata;
    uint32_t _flags;
};

/// @class ShaderInput
/// An input on a ShaderNode
class MX_GENSHADER_API ShaderInput : public ShaderPort
{
  public:
    ShaderInput(ShaderNode* node, TypeDesc type, const string& name);

    /// Return a connection to an upstream node output,
    /// or nullptr if not connected.
    ShaderOutput* getConnection() { return _connection; }

    /// Return a connection to an upstream node output,
    /// or nullptr if not connected.
    const ShaderOutput* getConnection() const { return _connection; }

    /// Make a connection from the given source output to this input.
    void makeConnection(ShaderOutput* src);

    /// Break the connection to this input.
    void breakConnection();

    /// Return the sibling node connected upstream,
    /// or nullptr if there is no sibling upstream.
    ShaderNode* getConnectedSibling() const;

  protected:
    ShaderOutput* _connection;
    string _channels;
    friend class ShaderOutput;
};

/// @class ShaderOutput
/// An output on a ShaderNode
class MX_GENSHADER_API ShaderOutput : public ShaderPort
{
  public:
    ShaderOutput(ShaderNode* node, TypeDesc type, const string& name);

    /// Return a set of connections to downstream node inputs,
    /// empty if not connected.
    const ShaderInputVec& getConnections() const { return _connections; }

    /// Make a connection from this output to the given input
    void makeConnection(ShaderInput* dst);

    /// Break a connection from this output to the given input
    void breakConnection(ShaderInput* dst);

    /// Break all connections from this output
    void breakConnections();

  protected:
    ShaderInputVec _connections;
    friend class ShaderInput;
};

/// @class ShaderNode
/// Class representing a node in the shader generation DAG
class MX_GENSHADER_API ShaderNode
{
  public:
    virtual ~ShaderNode() { }

    /// Flags for classifying nodes into different categories.
    class Classification
    {
      public:
        // Node classes
        static const uint32_t TEXTURE       = 1 << 0;  /// Any node that outputs floats, colors, vectors, etc.
        static const uint32_t CLOSURE       = 1 << 1;  /// Any node that represents light integration
        static const uint32_t SHADER        = 1 << 2;  /// Any node that outputs a shader
        static const uint32_t MATERIAL      = 1 << 3;  /// Any node that outputs a material
        // Specific texture node types
        static const uint32_t FILETEXTURE   = 1 << 4;  /// A file texture node
        static const uint32_t CONDITIONAL   = 1 << 5;  /// A conditional node
        static const uint32_t CONSTANT      = 1 << 6;  /// A constant node
        // Specific closure types
        static const uint32_t BSDF          = 1 << 7;  /// A BSDF node
        static const uint32_t BSDF_R        = 1 << 8;  /// A reflection BSDF node
        static const uint32_t BSDF_T        = 1 << 9;  /// A transmission BSDF node
        static const uint32_t EDF           = 1 << 10; /// A EDF node
        static const uint32_t VDF           = 1 << 11; /// A VDF node
        static const uint32_t LAYER         = 1 << 12; /// A node for vertical layering of other closure nodes
        static const uint32_t MIX           = 1 << 13; /// A node for mixing of other closure nodes
        // Specific shader types
        static const uint32_t SURFACE       = 1 << 14; /// A surface shader node
        static const uint32_t VOLUME        = 1 << 15; /// A volume shader node
        static const uint32_t LIGHT         = 1 << 16; /// A light shader node
        static const uint32_t UNLIT         = 1 << 17; /// An unlit surface shader node
        // Types based on nodegroup
        static const uint32_t SAMPLE2D      = 1 << 18; /// Can be sampled in 2D (uv space)
        static const uint32_t SAMPLE3D      = 1 << 19; /// Can be sampled in 3D (position)
        static const uint32_t GEOMETRIC     = 1 << 20; /// Geometric input
        static const uint32_t DOT           = 1 << 21; /// A dot node
    };

    static const ShaderNodePtr NONE;

    static const string CONSTANT;
    static const string DOT;
    static const string IMAGE;
    static const string SURFACESHADER;
    static const string BACKSURFACESHADER;
    static const string BSDF_R;
    static const string BSDF_T;
    static const string TEXTURE2D_GROUPNAME;
    static const string TEXTURE3D_GROUPNAME;
    static const string PROCEDURAL2D_GROUPNAME;
    static const string PROCEDURAL3D_GROUPNAME;
    static const string GEOMETRIC_GROUPNAME;

  public:
    /// Constructor.
    ShaderNode(const ShaderGraph* parent, const string& name);

    /// Create a new node from a nodedef.
    static ShaderNodePtr create(const ShaderGraph* parent, const string& name, const NodeDef& nodeDef,
                                GenContext& context);

    /// Create a new node from a node implementation.
    static ShaderNodePtr create(const ShaderGraph* parent, const string& name, ShaderNodeImplPtr impl,
                                unsigned int classification = Classification::TEXTURE);

    /// Return true if this node is a graph.
    virtual bool isAGraph() const { return false; }

    /// Return the parent graph that owns this node.
    /// If this node is a root graph it has no parent
    /// and nullptr will be returned.
    const ShaderGraph* getParent() const
    {
        return _parent;
    }

    /// Set classification bits for this node,
    /// replacing any previous set bits.
    void setClassification(uint32_t c)
    {
        _classification = c;
    }

    /// Get classification bits set for this node.
    uint32_t getClassification() const
    {
        return _classification;
    }

    /// Add classification bits to this node.
    void addClassification(uint32_t c)
    {
        _classification |= c;
    }

    /// Return true if this node matches the given classification.
    bool hasClassification(uint32_t c) const
    {
        return (_classification & c) == c;
    }

    /// Return the name of this node.
    const string& getName() const
    {
        return _name;
    }

    /// Return the unique identifier for this node, used as its key
    /// in the parent graph's node map.
    const string& getUniqueId() const
    {
        return _uniqueId;
    }

    /// Return the implementation used for this node.
    const ShaderNodeImpl& getImplementation() const
    {
        return *_impl;
    }

    /// Initialize this shader node with all required data
    /// from the given node and nodedef.
    void initialize(const Node& node, const NodeDef& nodeDef, GenContext& context);

    /// Add inputs/outputs
    ShaderInput* addInput(const string& name, TypeDesc type);
    ShaderOutput* addOutput(const string& name, TypeDesc type);

    /// Get number of inputs/outputs
    size_t numInputs() const { return _inputOrder.size(); }
    size_t numOutputs() const { return _outputOrder.size(); }

    /// Get inputs/outputs by index
    ShaderInput* getInput(size_t index) { return _inputOrder[index]; }
    ShaderOutput* getOutput(size_t index = 0) { return _outputOrder[index]; }
    const ShaderInput* getInput(size_t index) const { return _inputOrder[index]; }
    const ShaderOutput* getOutput(size_t index = 0) const { return _outputOrder[index]; }

    /// Get inputs/outputs by name
    ShaderInput* getInput(const string& name);
    ShaderOutput* getOutput(const string& name);
    const ShaderInput* getInput(const string& name) const;
    const ShaderOutput* getOutput(const string& name) const;

    /// Get vector of inputs/outputs
    const vector<ShaderInput*>& getInputs() const { return _inputOrder; }
    const vector<ShaderOutput*>& getOutputs() const { return _outputOrder; }

    /// Set the metadata vector.
    void setMetadata(ShaderMetadataVecPtr metadata) { _metadata = metadata; }

    /// Get the metadata vector.
    ShaderMetadataVecPtr getMetadata() { return _metadata; }

    /// Get the metadata vector.
    const ShaderMetadataVecPtr& getMetadata() const { return _metadata; }

    /// Returns true if an input is editable by users.
    /// Editable inputs are allowed to be published as shader uniforms
    /// and hence must be presentable in a user interface.
    bool isEditable(const ShaderInput& input) const
    {
        return (!_impl || _impl->isEditable(input));
    }

    /// Returns true if a graph input is accessible by users.
    /// Editable inputs are allowed to be published as shader uniforms
    /// and hence must be presentable in a user interface.
    bool isEditable(const ShaderGraphInputSocket& input) const
    {
        return (!_impl || _impl->isEditable(input));
    }

  protected:
    /// Create metadata from the nodedef according to registered metadata.
    void createMetadata(const NodeDef& nodeDef, GenContext& context);

    const ShaderGraph* _parent;
    string _name;
    string _uniqueId;
    uint32_t _classification;

    std::unordered_map<string, ShaderInputPtr> _inputMap;
    vector<ShaderInput*> _inputOrder;

    std::unordered_map<string, ShaderOutputPtr> _outputMap;
    vector<ShaderOutput*> _outputOrder;

    ShaderNodeImplPtr _impl;
    ShaderMetadataVecPtr _metadata;

    friend class ShaderGraph;
};

MATERIALX_NAMESPACE_END

#endif
