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

#include <MaterialXCore/Node.h>

#include <MaterialXCore/Document.h>
#include <MaterialXCore/Material.h>

#include <deque>

MATERIALX_NAMESPACE_BEGIN

const string Backdrop::CONTAINS_ATTRIBUTE = "contains";
const string Backdrop::WIDTH_ATTRIBUTE = "width";
const string Backdrop::HEIGHT_ATTRIBUTE = "height";

//
// Node methods
//

void Node::setNameGlobal(const string& name)
{
    vector<PortElementPtr> downStreamPorts = getDownstreamPorts();
    setName(name);
    const string& newName = getName();
    for (PortElementPtr& port : downStreamPorts)
    {
        port->setNodeName(newName);
    }
}

void Node::setConnectedNode(const string& inputName, ConstNodePtr node)
{
    InputPtr input = getInput(inputName);
    if (!input)
    {
        input = addInput(inputName);
    }
    if (node)
    {
        const string& type = node->getType();
        if (type != MULTI_OUTPUT_TYPE_STRING)
        {
            input->setType(type);
        }
    }
    input->setConnectedNode(node);
}

NodePtr Node::getConnectedNode(const string& inputName) const
{
    InputPtr input = getInput(inputName);
    if (!input)
    {
        return NodePtr();
    }
    return input->getConnectedNode();
}

void Node::setConnectedNodeName(const string& inputName, const string& nodeName)
{
    InputPtr input = getInput(inputName);
    if (!input)
    {
        input = addInput(inputName);
    }
    input->setNodeName(nodeName);
}

string Node::getConnectedNodeName(const string& inputName) const
{
    InputPtr input = getInput(inputName);
    if (!input)
    {
        return EMPTY_STRING;
    }
    return input->getNodeName();
}

NodeDefPtr Node::getNodeDef(const string& target, bool allowRoughMatch) const
{
    if (hasNodeDefString())
    {
        return resolveNameReference<NodeDef>(getNodeDefString());
    }
    vector<NodeDefPtr> nodeDefs = getDocument()->getMatchingNodeDefs(getQualifiedName(getCategory()));
    vector<NodeDefPtr> secondary = getDocument()->getMatchingNodeDefs(getCategory());
    vector<NodeDefPtr> roughMatches;
    nodeDefs.insert(nodeDefs.end(), secondary.begin(), secondary.end());
    for (NodeDefPtr nodeDef : nodeDefs)
    {
        if (!targetStringsMatch(nodeDef->getTarget(), target) ||
            !nodeDef->isVersionCompatible(getVersionString()) ||
            nodeDef->getType() != getType())
        {
            continue;
        }
        if (!hasExactInputMatch(nodeDef))
        {
            if (allowRoughMatch)
            {
                roughMatches.push_back(nodeDef);
            }
            continue;
        }
        return nodeDef;
    }
    if (!roughMatches.empty())
    {
        return roughMatches[0];
    }
    return NodeDefPtr();
}

Edge Node::getUpstreamEdge(size_t index) const
{
    if (index < getUpstreamEdgeCount())
    {
        InputPtr input = getInputs()[index];
        ElementPtr upstreamNode = input->getConnectedNode();
        if (upstreamNode)
        {
            return Edge(getSelfNonConst(), input, upstreamNode);
        }
    }

    return getNullEdge();
}

OutputPtr Node::getNodeDefOutput(ElementPtr connectingElement)
{
    string outputName;
    const PortElementPtr port = connectingElement->asA<PortElement>();
    if (port)
    {
        // The connecting element is an input/output port,
        // so get the name of the output connected to this
        // port. If no explicit output is specified this will
        // return an empty string which is handled below.
        outputName = port->getOutputString();

        // Handle case where it's an input to a top level output
        InputPtr connectedInput = connectingElement->asA<Input>();
        OutputPtr output;
        if (connectedInput)
        {
            InputPtr interfaceInput = connectedInput->getInterfaceInput();
            if (interfaceInput)
            {
                output = interfaceInput->getConnectedOutput();
                outputName = interfaceInput->getOutputString();
            }
            else
            {
                output = connectedInput->getConnectedOutput();
            }
        }
        if (output)
        {
            if (connectedInput ||
                output->getParent() == output->getDocument())
            {
                if (!output->getOutputString().empty())
                {
                    outputName = output->getOutputString();
                }
            }
        }
    }
    if (!outputName.empty())
    {
        // Find this output on our nodedef.
        NodeDefPtr nodeDef = getNodeDef();
        if (nodeDef)
        {
            return nodeDef->getActiveOutput(outputName);
        }
    }
    return OutputPtr();
}

vector<PortElementPtr> Node::getDownstreamPorts() const
{
    vector<PortElementPtr> downstreamPorts;
    for (PortElementPtr port : getDocument()->getMatchingPorts(getQualifiedName(getName())))
    {
        if (port->getConnectedNode() == getSelf())
        {
            downstreamPorts.push_back(port);
        }
    }
    std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b)
    {
        return a->getName() > b->getName();
    });
    return downstreamPorts;
}

bool Node::validate(string* message) const
{
    bool res = true;
    validateRequire(!getCategory().empty(), res, message, "Node element is missing a category");
    validateRequire(hasType(), res, message, "Node element is missing a type");

    NodeDefPtr nodeDef = getNodeDef(EMPTY_STRING, true);
    if (nodeDef)
    {
        string matchMessage;
        bool exactMatch = hasExactInputMatch(nodeDef, &matchMessage);
        validateRequire(exactMatch, res, message, "Node interface error: " + matchMessage);

        const vector<OutputPtr>& activeOutputs = nodeDef->getActiveOutputs();
        const size_t numActiveOutputs = activeOutputs.size();
        if (numActiveOutputs > 1)
        {
            validateRequire(getType() == MULTI_OUTPUT_TYPE_STRING, res, message, "Node type is not 'multioutput' for node with multiple outputs");
        }
        else if (numActiveOutputs == 1)
        {
            validateRequire(getType() == activeOutputs[0]->getType(), res, message, "Node type does not match output port type");
        }
    }
    else
    {
        bool categoryDeclared = !getDocument()->getMatchingNodeDefs(getCategory()).empty();
        validateRequire(!categoryDeclared, res, message, "Node interface doesn't support this output type");
    }

    return InterfaceElement::validate(message) && res;
}

//
// GraphElement methods
//

NodePtr GraphElement::addMaterialNode(const string& name, ConstNodePtr shaderNode)
{
    bool isVolumeShader = shaderNode && shaderNode->getType() == VOLUME_SHADER_TYPE_STRING;
    string category = isVolumeShader ? VOLUME_MATERIAL_NODE_STRING : SURFACE_MATERIAL_NODE_STRING;
    NodePtr materialNode = addNode(category, name, MATERIAL_TYPE_STRING);
    if (shaderNode)
    {
        InputPtr input = materialNode->addInput(shaderNode->getType(), shaderNode->getType());
        input->setConnectedNode(shaderNode);
    }
    return materialNode;
}

void GraphElement::flattenSubgraphs(const string& target, NodePredicate filter)
{
    vector<NodePtr> nodeQueue = getNodes();
    while (!nodeQueue.empty())
    {
        // Determine which nodes require processing, and precompute declarations
        // and graph implementations for these nodes.
        using PortElementVec = vector<PortElementPtr>;
        std::vector<NodePtr> processNodeVec;
        std::unordered_map<NodePtr, NodeGraphPtr> graphImplMap;
        std::unordered_map<NodePtr, ConstInterfaceElementPtr> declarationMap;
        std::unordered_map<NodePtr, PortElementVec> downstreamPortMap;
        for (NodePtr node : nodeQueue)
        {
            if (filter && !filter(node))
            {
                continue;
            }

            InterfaceElementPtr implement = node->getImplementation(target);
            if (implement && implement->isA<NodeGraph>())
            {
                processNodeVec.push_back(node);
                graphImplMap[node] = implement->asA<NodeGraph>();
                declarationMap[node] = node->getDeclaration(target);
                downstreamPortMap[node] = node->getDownstreamPorts();
                for (NodePtr sourceSubNode : implement->asA<NodeGraph>()->getNodes())
                {
                    downstreamPortMap[sourceSubNode] = sourceSubNode->getDownstreamPorts();
                }
            }
        }
        nodeQueue.clear();

        // Iterate through nodes with graph implementations.
        for (NodePtr processNode : processNodeVec)
        {
            NodeGraphPtr sourceSubGraph = graphImplMap[processNode];
            std::unordered_map<NodePtr, NodePtr> subNodeMap;

            // Create a new instance of each original subnode.
            for (NodePtr sourceSubNode : sourceSubGraph->getNodes())
            {
                string origName = sourceSubNode->getName();
                string destName = createValidChildName(origName);
                NodePtr destSubNode = addNode(sourceSubNode->getCategory(), destName);

                destSubNode->copyContentFrom(sourceSubNode);
                setChildIndex(destSubNode->getName(), getChildIndex(processNode->getName()));

                // Store the mapping between subgraphs.
                subNodeMap[sourceSubNode] = destSubNode;

                // Add the subnode to the queue, allowing processing of nested subgraphs.
                nodeQueue.push_back(destSubNode);
            }

            // Update properties of generated subnodes.
            for (const auto& subNodePair : subNodeMap)
            {
                NodePtr sourceSubNode = subNodePair.first;
                NodePtr destSubNode = subNodePair.second;

                // Update node connections.
                for (PortElementPtr sourcePort : downstreamPortMap[sourceSubNode])
                {
                    if (sourcePort->isA<Input>())
                    {
                        auto it = subNodeMap.find(sourcePort->getParent()->asA<Node>());
                        if (it != subNodeMap.end())
                        {
                            InputPtr processNodeInput = it->second->getInput(sourcePort->getName());
                            if (processNodeInput)
                            {
                                processNodeInput->setNodeName(destSubNode->getName());
                            }
                        }
                    }
                    else if (sourcePort->isA<Output>())
                    {
                        for (PortElementPtr processNodePort : downstreamPortMap[processNode])
                        {
                            processNodePort->setNodeName(destSubNode->getName());
                        }
                    }
                }

                // Transfer interface properties.
                for (InputPtr destInput : destSubNode->getInputs())
                {
                    if (destInput->hasInterfaceName())
                    {
                        InputPtr sourceInput = processNode->getInput(destInput->getInterfaceName());
                        if (sourceInput)
                        {
                            destInput->copyContentFrom(sourceInput);
                            NodePtr connectedNode = destInput->getConnectedNode();
                            // Update downstream port map with the new instance
                            if (connectedNode && downstreamPortMap.count(connectedNode) > 0)
                            {
                                downstreamPortMap[connectedNode] = connectedNode->getDownstreamPorts();
                            }
                        }
                        else
                        {
                            ConstInterfaceElementPtr declaration = declarationMap[processNode];
                            InputPtr declInput = declaration ? declaration->getActiveInput(destInput->getInterfaceName()) : nullptr;
                            if (declInput)
                            {
                                if (declInput->hasValueString())
                                {
                                    destInput->setValueString(declInput->getValueString());
                                }
                                if (declInput->hasDefaultGeomPropString())
                                {
                                    ConstGeomPropDefPtr geomPropDef = getDocument()->getGeomPropDef(declInput->getDefaultGeomPropString());
                                    if (geomPropDef)
                                    {
                                        destInput->setConnectedNode(addGeomNode(geomPropDef, "geomNode"));
                                    }
                                }
                            }
                            destInput->removeAttribute(ValueElement::INTERFACE_NAME_ATTRIBUTE);
                        }
                    }
                }
            }

            // Update downstream ports with connections to subgraph outputs.
            for (PortElementPtr downstreamPort : downstreamPortMap[processNode])
            {
                if (downstreamPort->hasOutputString())
                {
                    OutputPtr subGraphOutput = sourceSubGraph->getOutput(downstreamPort->getOutputString());
                    if (subGraphOutput)
                    {
                        string destName = subGraphOutput->getNodeName();
                        NodePtr sourceSubNode = sourceSubGraph->getNode(destName);
                        NodePtr destNode = sourceSubNode ? subNodeMap[sourceSubNode] : nullptr;
                        if (destNode)
                        {
                            destName = destNode->getName();
                        }
                        downstreamPort->setNodeName(destName);
                        downstreamPort->setOutputString(EMPTY_STRING);
                    }
                }
            }

            // The processed node has been replaced, so remove it from the graph.
            removeNode(processNode->getName());
        }
    }
}

ElementVec GraphElement::topologicalSort() const
{
    // Calculate a topological order of the children, using Kahn's algorithm
    // to avoid recursion.
    //
    // Running time: O(numNodes + numEdges).

    const ElementVec& children = getChildren();

    // Calculate in-degrees for all children.
    std::unordered_map<ElementPtr, size_t> inDegree(children.size());
    std::deque<ElementPtr> childQueue;
    for (ElementPtr child : children)
    {
        size_t connectionCount = 0;
        for (size_t i = 0; i < child->getUpstreamEdgeCount(); ++i)
        {
            Edge upstreamEdge = child->getUpstreamEdge(i);
            if (upstreamEdge)
            {
                if (upstreamEdge.getUpstreamElement())
                {
                    ElementPtr elem = upstreamEdge.getUpstreamElement()->getParent();
                    if (elem == child->getParent())
                    {
                        connectionCount++;
                    }
                }
            }
        }

        inDegree[child] = connectionCount;

        // Enqueue children with in-degree 0.
        if (connectionCount == 0)
        {
            childQueue.push_back(child);
        }
    }

    ElementVec result;
    while (!childQueue.empty())
    {
        // Pop the queue and add to topological order.
        ElementPtr child = childQueue.front();
        childQueue.pop_front();
        result.push_back(child);

        // Find connected nodes and decrease their in-degree,
        // adding node to the queue if in-degrees becomes 0.
        if (child->isA<Node>())
        {
            for (PortElementPtr port : child->asA<Node>()->getDownstreamPorts())
            {
                const ElementPtr downstreamElem = port->isA<Output>() ? port : port->getParent();
                if (inDegree[downstreamElem] > 1)
                {
                    inDegree[downstreamElem]--;
                }
                else
                {
                    inDegree[downstreamElem] = 0;
                    childQueue.push_back(downstreamElem);
                }
            }
        }
    }

    return result;
}

NodePtr GraphElement::addGeomNode(ConstGeomPropDefPtr geomPropDef, const string& namePrefix)
{
    string geomNodeName = namePrefix + "_" + geomPropDef->getName();
    NodePtr geomNode = getNode(geomNodeName);
    if (!geomNode)
    {
        geomNode = addNode(geomPropDef->getGeomProp(), geomNodeName, geomPropDef->getType());
        if (geomPropDef->hasAttribute(GeomPropDef::SPACE_ATTRIBUTE))
        {
            geomNode->setInputValue(GeomPropDef::SPACE_ATTRIBUTE, geomPropDef->getAttribute(GeomPropDef::SPACE_ATTRIBUTE));
        }
        if (geomPropDef->hasAttribute(GeomPropDef::INDEX_ATTRIBUTE))
        {
            geomNode->setInputValue(GeomPropDef::INDEX_ATTRIBUTE, geomPropDef->getAttribute(GeomPropDef::INDEX_ATTRIBUTE), getTypeString<int>());
        }
    }
    return geomNode;
}

string GraphElement::asStringDot() const
{
    string dot = "digraph {\n";

    // Create a unique name for each child element.
    ElementVec children = topologicalSort();
    StringMap nameMap;
    StringSet nameSet;
    for (ElementPtr elem : children)
    {
        string uniqueName = elem->getCategory();
        while (nameSet.count(uniqueName))
        {
            uniqueName = incrementName(uniqueName);
        }
        nameMap[elem->getName()] = uniqueName;
        nameSet.insert(uniqueName);
    }

    // Write out all nodes.
    for (ElementPtr elem : children)
    {
        NodePtr node = elem->asA<Node>();
        if (node)
        {
            dot += "    \"" + nameMap[node->getName()] + "\" ";
            NodeDefPtr nodeDef = node->getNodeDef();
            const string& nodeGroup = nodeDef ? nodeDef->getNodeGroup() : EMPTY_STRING;
            if (nodeGroup == NodeDef::CONDITIONAL_NODE_GROUP)
            {
                dot += "[shape=diamond];\n";
            }
            else
            {
                dot += "[shape=box];\n";
            }
        }
    }

    // Write out all connections.
    std::set<Edge> processedEdges;
    StringSet processedInterfaces;
    for (OutputPtr output : getOutputs())
    {
        for (Edge edge : output->traverseGraph())
        {
            if (!processedEdges.count(edge))
            {
                ElementPtr upstreamElem = edge.getUpstreamElement();
                ElementPtr downstreamElem = edge.getDownstreamElement();
                ElementPtr connectingElem = edge.getConnectingElement();

                dot += "    \"" + nameMap[upstreamElem->getName()];
                dot += "\" -> \"" + nameMap[downstreamElem->getName()];
                dot += "\" [label=\"";
                dot += connectingElem ? connectingElem->getName() : EMPTY_STRING;
                dot += "\"];\n";

                NodePtr upstreamNode = upstreamElem->asA<Node>();
                if (upstreamNode && !processedInterfaces.count(upstreamNode->getName()))
                {
                    for (InputPtr input : upstreamNode->getInputs())
                    {
                        if (input->hasInterfaceName())
                        {
                            dot += "    \"" + input->getInterfaceName();
                            dot += "\" -> \"" + nameMap[upstreamElem->getName()];
                            dot += "\" [label=\"";
                            dot += input->getName();
                            dot += "\"];\n";
                        }
                    }
                    processedInterfaces.insert(upstreamNode->getName());
                }

                processedEdges.insert(edge);
            }
        }
    }

    dot += "}\n";

    return dot;
}

//
// NodeGraph methods
//

void NodeGraph::setNameGlobal(const string& name)
{
    vector<PortElementPtr> downStreamPorts = getDownstreamPorts();
    setName(name);
    const string& newName = getName();
    for (PortElementPtr& port : downStreamPorts)
    {
        port->setNodeGraphString(newName);
    }
}

vector<OutputPtr> NodeGraph::getMaterialOutputs() const
{
    vector<OutputPtr> materialOutputs;
    for (auto graphOutput : getActiveOutputs())
    {
        if (graphOutput->getType() == MATERIAL_TYPE_STRING)
        {
            NodePtr node = graphOutput->getConnectedNode();
            if (node && node->getType() == MATERIAL_TYPE_STRING)
            {
                materialOutputs.push_back(graphOutput);
            }
        }
    }
    return materialOutputs;
}

void NodeGraph::setNodeDef(ConstNodeDefPtr nodeDef)
{
    if (nodeDef)
    {
        setNodeDefString(nodeDef->getName());
    }
    else
    {
        removeAttribute(NODE_DEF_ATTRIBUTE);
    }
}

InputPtr Node::addInputFromNodeDef(const string& inputName)
{
    InputPtr nodeInput = getInput(inputName);
    if (!nodeInput)
    {
        NodeDefPtr nodeDef = getNodeDef();
        InputPtr nodeDefInput = nodeDef ? nodeDef->getActiveInput(inputName) : nullptr;
        if (nodeDefInput)
        {
            nodeInput = addInput(nodeDefInput->getName(), nodeDefInput->getType());
            if (nodeDefInput->hasValueString())
            {
                nodeInput->setValueString(nodeDefInput->getValueString());
            }
        }
    }
    return nodeInput;
}

void Node::addInputsFromNodeDef()
{
    NodeDefPtr nodeDef = getNodeDef();
    if (nodeDef)
    {
        for (InputPtr nodeDefInput : nodeDef->getActiveInputs())
        {
            const string& inputName = nodeDefInput->getName();
            InputPtr nodeInput = getInput(inputName);
            if (!nodeInput)
            {
                nodeInput = addInput(inputName, nodeDefInput->getType());
                if (nodeDefInput->hasValueString())
                {
                    nodeInput->setValueString(nodeDefInput->getValueString());
                }
            }
        }
    }
}

InputPtr NodeGraph::addInterfaceName(const string& inputPath, const string& interfaceName)
{
    NodeDefPtr nodeDef = getNodeDef();
    InterfaceElementPtr interfaceElement = nodeDef ? nodeDef->asA<InterfaceElement>() : getSelf()->asA<InterfaceElement>();
    if (interfaceElement->getChild(interfaceName))
    {
        throw Exception("Interface: " + interfaceName + " has already been declared on the interface: " + interfaceElement->getNamePath());
    }

    InputPtr interfaceInput;
    ElementPtr elem = getDescendant(inputPath);
    InputPtr input = elem ? elem->asA<Input>() : nullptr;
    if (input && !input->getConnectedNode())
    {
        input->setInterfaceName(interfaceName);
        interfaceInput = interfaceElement->getInput(interfaceName);
        if (!interfaceInput)
        {
            interfaceInput = interfaceElement->addInput(interfaceName, input->getType());
        }
        if (input->hasValue())
        {
            interfaceInput->setValueString(input->getValueString());
            input->removeAttribute(Input::VALUE_ATTRIBUTE);
        }
    }
    return interfaceInput;
}

void NodeGraph::removeInterfaceName(const string& inputPath)
{
    ElementPtr desc = getDescendant(inputPath);
    InputPtr input = desc ? desc->asA<Input>() : nullptr;
    if (input)
    {
        const string& interfaceName = input->getInterfaceName();
        if (!interfaceName.empty())
        {
            NodeDefPtr nodeDef = getNodeDef();
            InterfaceElementPtr interface = nodeDef ? nodeDef->asA<InterfaceElement>() : getSelf()->asA<InterfaceElement>();
            ElementPtr interfacePort = interface->getChild(interfaceName);
            if (interfacePort)
            {
                InputPtr interfaceInput = interfacePort->asA<Input>();
                if (interfaceInput && interfaceInput->hasValue())
                {
                    input->setValueString(interfaceInput->getValueString());
                }
                interface->removeChild(interfaceName);
            }
            input->setInterfaceName(EMPTY_STRING);
        }
    }
}

void NodeGraph::modifyInterfaceName(const string& inputPath, const string& interfaceName)
{
    NodeDefPtr nodeDef = getNodeDef();
    InterfaceElementPtr interfaceElement = nodeDef ? nodeDef->asA<InterfaceElement>() : getSelf()->asA<InterfaceElement>();

    ElementPtr desc = getDescendant(inputPath);
    InputPtr input = desc ? desc->asA<Input>() : nullptr;
    if (input)
    {
        const string& previousName = input->getInterfaceName();
        if (previousName != interfaceName)
        {
            ElementPtr previousChild = interfaceElement->getChild(previousName);
            if (previousChild)
            {
                previousChild->setName(interfaceName);
            }
            input->setInterfaceName(interfaceName);
        }
    }
}

NodeDefPtr NodeGraph::getNodeDef() const
{
    NodeDefPtr nodedef = resolveNameReference<NodeDef>(getNodeDefString());
    // If not directly defined look for an implementation which has a nodedef association
    if (!nodedef)
    {
        for (auto impl : getDocument()->getImplementations())
        {
            if (impl->getNodeGraph() == getQualifiedName(getName()))
            {
                nodedef = impl->getNodeDef();
            }
        }
    }
    return nodedef;
}

InterfaceElementPtr NodeGraph::getImplementation() const
{
    NodeDefPtr nodedef = getNodeDef();
    return nodedef ? nodedef->getImplementation() : InterfaceElementPtr();
}

vector<PortElementPtr> NodeGraph::getDownstreamPorts() const
{
    vector<PortElementPtr> downstreamPorts;
    for (PortElementPtr port : getDocument()->getMatchingPorts(getQualifiedName(getName())))
    {
        ElementPtr node = port->getParent();
        ElementPtr graph = node ? node->getParent() : nullptr;
        if (graph && graph->isA<GraphElement>() && graph == getParent())
        {
            downstreamPorts.push_back(port);
        }
    }
    std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b)
    {
        return a->getName() > b->getName();
    });
    return downstreamPorts;
}

bool NodeGraph::validate(string* message) const
{
    bool res = true;

    validateRequire(!hasVersionString(), res, message, "NodeGraph elements do not support version strings");
    if (hasNodeDefString())
    {
        NodeDefPtr nodeDef = getNodeDef();
        validateRequire(nodeDef != nullptr, res, message, "NodeGraph implementation refers to non-existent NodeDef");
        if (nodeDef)
        {
            vector<OutputPtr> graphOutputs = getOutputs();
            vector<OutputPtr> nodeDefOutputs = nodeDef->getActiveOutputs();
            validateRequire(graphOutputs.size() == nodeDefOutputs.size(), res, message, "NodeGraph implementation has a different number of outputs than its NodeDef");
            if (graphOutputs.size() == 1 && nodeDefOutputs.size() == 1)
            {
                validateRequire(graphOutputs[0]->getType() == nodeDefOutputs[0]->getType(), res, message, "NodeGraph implementation has a different output type than its NodeDef");
            }
        }
    }

    return GraphElement::validate(message) && res;
}

ConstInterfaceElementPtr NodeGraph::getDeclaration(const string&) const
{
    ConstNodeDefPtr nodeDef = getNodeDef();
    if (nodeDef)
    {
        return nodeDef;
    }
    if (!hasNodeDefString())
    {
        return getSelf()->asA<InterfaceElement>();
    }
    return nullptr;
}

//
// Backdrop methods
//

void Backdrop::setContainsElements(const vector<ConstTypedElementPtr>& elems)
{
    if (!elems.empty())
    {
        StringVec stringVec;
        for (ConstTypedElementPtr elem : elems)
        {
            stringVec.push_back(elem->getName());
        }
        setTypedAttribute(CONTAINS_ATTRIBUTE, stringVec);
    }
    else
    {
        removeAttribute(CONTAINS_ATTRIBUTE);
    }
}

vector<TypedElementPtr> Backdrop::getContainsElements() const
{
    vector<TypedElementPtr> vec;
    ConstGraphElementPtr graph = getAncestorOfType<GraphElement>();
    if (graph)
    {
        for (const string& str : getTypedAttribute<StringVec>(CONTAINS_ATTRIBUTE))
        {
            TypedElementPtr elem = graph->getChildOfType<TypedElement>(str);
            if (elem)
            {
                vec.push_back(elem);
            }
        }
    }
    return vec;
}

bool Backdrop::validate(string* message) const
{
    bool res = true;
    if (hasContainsString())
    {
        StringVec stringVec = getTypedAttribute<StringVec>("contains");
        vector<TypedElementPtr> elemVec = getContainsElements();
        validateRequire(stringVec.size() == elemVec.size(), res, message, "Invalid element in contains string");
    }
    return Element::validate(message) && res;
}

MATERIALX_NAMESPACE_END
