// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "ngraph/node.hpp"

#include <memory>
#include <ngraph/validation_util.hpp>
#include <sstream>
#include <typeindex>
#include <typeinfo>

#include "atomic_guard.hpp"
#include "itt.hpp"
#include "ngraph/graph_util.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/parameter.hpp"
#include "ngraph/op/result.hpp"
#include "ngraph/pattern/matcher.hpp"
#include "openvino/core/descriptor/input.hpp"

using namespace std;

atomic<size_t> ov::Node::m_next_instance_id(0);

ov::Node::Node(const Node& node)
    : m_control_dependents(node.m_control_dependents),
      m_control_dependencies(node.m_control_dependencies)
      // skip m_node_type -- will be generated automatically
      ,
      m_instance_id(m_next_instance_id.fetch_add(1)),
      m_friendly_name(node.m_friendly_name)
      // skip m_unique_name -- will be generated automatically
      ,
      m_inputs(node.m_inputs)  // will be modified in the body
      // skip m_outputs -- should be initialized outside
      ,
      m_op_annotations(node.m_op_annotations),
      m_rt_info(node.m_rt_info) {
    // cannot do it without copying node.m_inputs first due to too limiting const qualifiers
    for (auto& input : m_inputs) {
        input = descriptor::Input(this, input.get_index(), input.get_output());
        input.get_output().add_input(&input);
    }
}

ov::Node& ov::Node::operator=(const Node& node) {
    this->m_control_dependents = node.m_control_dependents;
    this->m_control_dependencies = node.m_control_dependencies;
    this->m_instance_id = m_next_instance_id.fetch_add(1);
    this->m_friendly_name = node.m_friendly_name;
    this->m_inputs = node.m_inputs;
    this->m_op_annotations = node.m_op_annotations;
    this->m_rt_info = node.m_rt_info;
    // cannot do it without copying node.m_inputs first due to too limiting const qualifiers
    for (auto& input : m_inputs) {
        input = descriptor::Input(this, input.get_index(), input.get_output());
        input.get_output().add_input(&input);
    }
    return *this;
}

ov::Node::Node(size_t output_size) : Node() {
    set_output_size(output_size);
}

ov::Node::Node(const OutputVector& arguments, size_t output_size) : Node() {
    set_arguments(arguments);
    set_output_size(output_size);
}

ov::Node::~Node() {
    for (descriptor::Input& input : m_inputs) {
        if (input.has_output()) {
            // This test adds 1 to the actual count, so a count of 2 means this input is the only
            // reference to the node.
            if (input.get_output().get_node().use_count() == 2) {
                // Don't want to trigger a deep recursive delete
                NodeVector nodes{input.get_output().get_node()};
                input.remove_output();
                safe_delete(nodes, true);
                return;
            }
            input.remove_output();
        }
    }
}

std::shared_ptr<ov::Node> ov::Node::copy_with_new_inputs(const OutputVector& inputs) const {
    return copy_with_new_inputs(inputs, get_control_dependencies());
}

ov::Output<const ov::Node> ov::Node::get_default_output() const {
    return output(get_default_output_index());
}

ov::Output<ov::Node> ov::Node::get_default_output() {
    return output(get_default_output_index());
}

size_t ov::Node::get_default_output_index() const {
    return 0;
}

size_t ov::Node::no_default_index() const {
    NODE_VALIDATION_CHECK(this, false, "Default output not supported");
}

std::shared_ptr<ov::Node> ov::Node::copy_with_new_inputs(
    const OutputVector& inputs,
    const std::vector<std::shared_ptr<Node>>& control_dependencies) const {
    shared_ptr<Node> clone = clone_with_new_inputs(inputs);
    for (auto& cdep : control_dependencies) {
        clone->add_control_dependency(cdep);
    }
    for (size_t i = 0; i < get_output_size(); i++) {
        clone->get_output_tensor(i).set_names(get_output_tensor(i).get_names());
    }
    return clone;
}

void ov::Node::safe_delete(NodeVector& nodes, bool recurse) {
    for (auto& input : m_inputs) {
        if (input.has_output()) {
            // This test adds 1 to the actual count, so a count of 2 means this input is the only
            // reference to the node.
            auto node = input.get_output().get_node();
            if (node.use_count() == 2) {
                // Move the node from the input to nodes so we don't trigger a deep recursive delete
                nodes.push_back(node);
            }
            input.remove_output();
        }
    }
    if (recurse) {
        while (nodes.size() > 0) {
            auto node = nodes.back();
            nodes.pop_back();
            node->safe_delete(nodes, false);
        }
    }
}

void ov::Node::set_arguments(const NodeVector& arguments) {
    OutputVector outputs;
    for (const auto& arg : arguments) {
        for (auto& output : arg->outputs()) {
            outputs.push_back(output);
        }
    }
    set_arguments(outputs);
}

void ov::Node::set_arguments(const OutputVector& arguments) {
    // Remove existing inputs of this node
    m_inputs.clear();

    // Add this node as a user of each argument.
    size_t i = 0;
    for (auto& output : arguments) {
        auto output_node = output.get_node();
        auto& output_descriptor = output_node->m_outputs.at(output.get_index());
        m_inputs.emplace_back(this, i++, output_descriptor);
    }
}

ov::descriptor::Input& ov::Node::get_input_descriptor(size_t position) {
    while (m_inputs.size() <= position) {
        m_inputs.emplace_back(this, m_inputs.size());
    }
    return m_inputs.at(position);
}

ov::descriptor::Output& ov::Node::get_output_descriptor(size_t position) {
    while (m_outputs.size() <= position) {
        size_t i = m_outputs.size();
        auto tensor_descriptor = make_shared<descriptor::Tensor>(element::dynamic, PartialShape::dynamic(), this, i);
        m_outputs.emplace_back(this, i, tensor_descriptor);
    }
    return m_outputs[position];
}

void ov::Node::set_argument(size_t position, const Output<Node>& argument) {
    auto output_node = argument.get_node();
    auto& output_descriptor = output_node->get_output_descriptor(argument.get_index());
    get_input_descriptor(position).replace_output(output_descriptor);
}

void ov::Node::constructor_validate_and_infer_types() {
    validate_and_infer_types();
}

void ov::Node::set_output_size(size_t n) {
    NGRAPH_CHECK(n >= m_outputs.size(), "shrinking ", m_outputs.size(), " to ", n);
    for (size_t i = m_outputs.size(); i < n; ++i) {
        // create the descriptors
        get_output_descriptor(i);
    }
}

void ov::Node::invalidate_values() {
    for (const auto& output : outputs())
        output.get_tensor().invalidate_values();
}

void ov::Node::validate_and_infer_types() {}

void ov::Node::set_input_is_relevant_to_shape(size_t i, bool relevant) {
    NGRAPH_CHECK(i < m_inputs.size(),
                 "index '",
                 i,
                 "' out of range in set_input_is_relevant_to_shape(size_t index, bool relevant)");
    m_inputs[i].m_is_relevant_to_shape = relevant;
}

void ov::Node::set_input_is_relevant_to_value(size_t i, bool relevant) {
    NGRAPH_CHECK(i < m_inputs.size(),
                 "index '",
                 i,
                 "' out of range in set_input_is_relevant_to_value(size_t index, bool relevant)");
    m_inputs[i].m_is_relevant_to_value = relevant;
}

void ov::Node::set_output_type(size_t i, const element::Type& element_type, const PartialShape& pshape) {
    get_output_descriptor(i).get_tensor_ptr()->set_tensor_type(element_type, pshape);
}

std::string ov::Node::description() const {
    return get_type_name();
}

const std::string& ov::Node::get_friendly_name() const {
    if (m_friendly_name.empty()) {
        return get_name();
    }
    return m_friendly_name;
}

const std::string& ov::Node::get_name() const {
    AtomicGuard lock(m_name_changing);
    if (m_unique_name.empty())
        m_unique_name = description() + "_" + to_string(m_instance_id);
    return m_unique_name;
}

void ov::Node::set_friendly_name(const string& name) {
    m_friendly_name = name;
}

ov::Node* ov::Node::get_input_node_ptr(size_t index) const {
    NGRAPH_CHECK(index < m_inputs.size(), "index '", index, "' out of range in get_argument(size_t index)");
    return m_inputs[index].get_output().get_node().get();
}

std::shared_ptr<ov::Node> ov::Node::get_input_node_shared_ptr(size_t index) const {
    NGRAPH_CHECK(index < m_inputs.size(), "index '", index, "' out of range in get_argument(size_t index)");
    return m_inputs[index].get_output().get_node();
}

ov::Output<ov::Node> ov::Node::get_input_source_output(size_t i) const {
    return input(i).get_source_output();
}

const std::vector<std::shared_ptr<ov::Node>>& ov::Node::get_control_dependencies() const {
    return m_control_dependencies;
}

const std::vector<ov::Node*>& ov::Node::get_control_dependents() const {
    return m_control_dependents;
}

void ov::Node::add_control_dependency(std::shared_ptr<Node> node) {
    if (find(m_control_dependencies.begin(), m_control_dependencies.end(), node) == m_control_dependencies.end()) {
        m_control_dependencies.push_back(node);
        if (find(node->m_control_dependents.begin(), node->m_control_dependents.end(), this) ==
            node->m_control_dependents.end()) {
            node->m_control_dependents.push_back(this);
        }
    }
}

void ov::Node::add_node_control_dependencies(std::shared_ptr<Node> source_node) {
    for (auto& node : source_node->get_control_dependencies()) {
        add_control_dependency(node);
    }
}

void ov::Node::add_node_control_dependents(std::shared_ptr<Node> source_node) {
    for (Node* node : source_node->get_control_dependents()) {
        node->add_control_dependency(shared_from_this());
    }
}

void ov::Node::transfer_control_dependents(std::shared_ptr<Node> replacement) {
    replacement->add_node_control_dependents(shared_from_this());
    clear_control_dependents();
}

void ov::Node::remove_control_dependency(std::shared_ptr<Node> node) {
    {
        auto it = find(m_control_dependencies.begin(), m_control_dependencies.end(), node);
        if (it != m_control_dependencies.end()) {
            m_control_dependencies.erase(it);
        }
    }
    {
        auto it = find(node->m_control_dependents.begin(), node->m_control_dependents.end(), this);
        if (it != node->m_control_dependents.end()) {
            node->m_control_dependents.erase(it);
        }
    }
}

void ov::Node::clear_control_dependencies() {
    for (auto& node : m_control_dependencies) {
        auto it = find(node->m_control_dependents.begin(), node->m_control_dependents.end(), this);
        if (it != node->m_control_dependents.end()) {
            node->m_control_dependents.erase(it);
        }
    }
    m_control_dependencies.clear();
}

void ov::Node::clear_control_dependents() {
    while (!m_control_dependents.empty()) {
        (*m_control_dependents.begin())->remove_control_dependency(shared_from_this());
    }
}

const ngraph::op::AutoBroadcastSpec& ov::Node::get_autob() const {
    static ngraph::op::AutoBroadcastSpec s_spec;
    return s_spec;
}

namespace ov {
ostream& operator<<(ostream& out, const Node& node) {
    return node.write_description(out, 1);
}
ostream& operator<<(ostream& out, const Node* node) {
    return node->write_description(out, 1);
}
}  // namespace ov

std::ostream& ov::Node::write_description(std::ostream& out, uint32_t depth) const {
    if (depth == 0) {
        out << get_friendly_name();
    } else {
        out << "v" << get_type_info().version << "::" << get_type_info().name << " " << get_friendly_name() << " (";
        string sep = "";
        for (const auto& arg : input_values()) {
            out << sep << arg;
            sep = ", ";
        }
        out << ") -> (";
        sep = "";
        for (size_t i = 0; i < get_output_size(); i++) {
            out << sep << get_output_element_type(i) << get_output_partial_shape(i);
            sep = ", ";
        }
        out << ")";
    }
    return out;
}

size_t ov::Node::get_output_size() const {
    return m_outputs.size();
}

const ov::element::Type& ov::Node::get_output_element_type(size_t i) const {
    NGRAPH_CHECK(i < m_outputs.size(), "index '", i, "' out of range in get_output_element_type(size_t i)");
    return m_outputs[i].get_element_type();
}

const ov::element::Type& ov::Node::get_element_type() const {
    if (get_output_size() != 1) {
        throw ngraph::ngraph_error("get_element_type() must be called on a node with exactly one output.");
    }
    return get_output_element_type(0);
}

const ov::Shape& ov::Node::get_output_shape(size_t i) const {
    NGRAPH_CHECK(i < m_outputs.size(), "index '", i, "' out of range in get_output_shape(size_t i)");
    return m_outputs[i].get_shape();
}

const ov::PartialShape& ov::Node::get_output_partial_shape(size_t i) const {
    NGRAPH_CHECK(i < m_outputs.size(), "index '", i, "' out of range in get_output_partial_shape(size_t i)");
    return m_outputs[i].get_partial_shape();
}

const ov::Shape& ov::Node::get_shape() const {
    NODE_VALIDATION_CHECK(this, get_output_size() == 1, "get_shape() must be called on a node with exactly one output");
    return get_output_shape(0);
}

std::set<ov::Input<ov::Node>> ov::Node::get_output_target_inputs(size_t i) const {
    std::set<Input<Node>> result;

    for (auto& input : m_outputs.at(i).get_inputs()) {
        result.emplace(input->get_raw_pointer_node(), input->get_index());
    }

    return result;
}

ov::descriptor::Tensor& ov::Node::get_output_tensor(size_t i) const {
    NGRAPH_CHECK(i < m_outputs.size(), "index '", i, "' out of range in get_output_tensor(size_t i)");
    return m_outputs[i].get_tensor();
}

ov::descriptor::Tensor& ov::Node::get_input_tensor(size_t i) const {
    NGRAPH_CHECK(i < m_inputs.size(), "index '", i, "' out of range in get_input_tensor(size_t i)");
    descriptor::Input input = m_inputs[i];
    return input.get_tensor();
}

size_t ov::Node::get_input_size() const {
    return m_inputs.size();
}

const ov::element::Type& ov::Node::get_input_element_type(size_t i) const {
    NGRAPH_CHECK(i < m_inputs.size(), "index '", i, "' out of range in get_input_element_type(size_t i)");
    return m_inputs[i].get_element_type();
}

const ov::Shape& ov::Node::get_input_shape(size_t i) const {
    NGRAPH_CHECK(i < m_inputs.size(), "index '", i, "' out of range in get_input_shape(size_t i)");
    return m_inputs[i].get_shape();
}

const ov::PartialShape& ov::Node::get_input_partial_shape(size_t i) const {
    NGRAPH_CHECK(i < m_inputs.size(), "index '", i, "' out of range in get_input_partial_shape(size_t i)");
    return m_inputs[i].get_partial_shape();
}

NGRAPH_SUPPRESS_DEPRECATED_START
const string& ov::Node::get_input_tensor_name(size_t i) const {
    NGRAPH_CHECK(i < m_inputs.size(), "index '", i, "' out of range in get_input_tensor_name(size_t i)");
    return m_inputs[i].get_tensor().get_name();
}

const string& ov::Node::get_output_tensor_name(size_t i) const {
    NGRAPH_CHECK(i < m_outputs.size(), "index '", i, "' out of range in get_output_tensor_name(size_t i)");
    return m_outputs[i].get_tensor().get_name();
}
NGRAPH_SUPPRESS_DEPRECATED_END

bool ov::Node::has_same_type(std::shared_ptr<const Node> node) const {
    if (get_output_size() != node->get_output_size()) {
        return false;
    }
    for (size_t i = 0; i < get_output_size(); ++i) {
        if (get_output_element_type(i) != node->get_output_element_type(i) ||
            get_output_shape(i) != node->get_output_shape(i)) {
            return false;
        }
    }
    return true;
}

ov::NodeVector ov::Node::get_users(bool check_is_used) const {
    NodeVector result;
    for (const auto& output : outputs()) {
        for (auto input : output.get_target_inputs()) {
            Node* input_node = input.get_node();
            if (!check_is_used || ngraph::is_used(input_node)) {
                result.push_back(input_node->shared_from_this());
            }
        }
    }
    return result;
}

std::string ov::node_validation_failure_loc_string(const Node* node) {
    std::stringstream ss;
    ss << "While validating node '" << *node << "' with friendly_name '" << node->get_friendly_name() << '\'';
    return ss.str();
}

const std::shared_ptr<ov::Node>& ngraph::check_single_output_arg(const std::shared_ptr<Node>& node, size_t i) {
    NGRAPH_CHECK(node->get_output_size() == 1, "Argument ", i, node, " must produce exactly one value.");
    return node;
}

const ov::NodeVector& ngraph::check_single_output_args(const NodeVector& args) {
    for (size_t i = 0; i < args.size(); ++i) {
        ngraph::check_single_output_arg(args.at(i), i);
    }
    return args;
}

bool ov::Node::match_value(ngraph::pattern::Matcher* matcher,
                           const Output<Node>& pattern_value,
                           const Output<Node>& graph_value) {
    if (pattern_value.get_index() != graph_value.get_index() ||
        (matcher->is_strict_mode() &&
         (!pattern_value.get_element_type().compatible(graph_value.get_element_type()) ||
          !pattern_value.get_partial_shape().compatible(graph_value.get_partial_shape())))) {
        return false;
    }
    return match_node(matcher, graph_value);
}

bool ov::Node::match_node(ngraph::pattern::Matcher* matcher, const Output<Node>& graph_value) {
    matcher->add_node(graph_value);
    // Check if a type of a given node, which produces graph_value, matches the type of `this` node
    // or `this` node type is an ancestor of that node type. It is not the exact matching, types of
    // the nodes
    // may not match, but they are connected by the inheritance relation.
    // Not exact matching allows using base classes in the patterns and successfully matching such
    // patterns
    // with sub-graph of descent nodes types.
    if (graph_value.get_node_shared_ptr()->get_type_info().is_castable(get_type_info()) &&
        matcher->match_arguments(this, graph_value.get_node_shared_ptr())) {
        auto& pattern_map = matcher->get_pattern_value_map();
        pattern_map[shared_from_this()] = graph_value;
        return true;
    }
    return false;
}

// default implementation for the node to check if it contains partial shape
// we will override this method, for the Op's which depends on additional shape
// attribute to determine if node contains partial shape or not
bool ov::Node::is_dynamic() const {
    for (size_t i = 0; i < get_input_size(); i++) {
        if (get_input_partial_shape(i).is_dynamic()) {
            return true;
        }
    }
    return false;
}

ov::Input<ov::Node> ov::Node::input(size_t input_index) {
    if (input_index >= m_inputs.size()) {
        throw out_of_range("node input index is out of range");
    }

    return {this, input_index};
}

ov::Output<ov::Node> ov::Node::input_value(size_t input_index) const {
    return input(input_index).get_source_output();
}

ov::Input<const ov::Node> ov::Node::input(size_t input_index) const {
    if (input_index >= m_inputs.size()) {
        throw out_of_range("node input index is out of range");
    }

    return {this, input_index};
}

ov::Output<ov::Node> ov::Node::output(size_t output_index) {
    // All nodes will have at least 1 output
    if (output_index > 0 && output_index >= m_outputs.size()) {
        throw out_of_range("node output index is out of range");
    }

    return Output<Node>(this, output_index);
}

ov::Output<const ov::Node> ov::Node::output(size_t output_index) const {
    // All nodes will have at least 1 output
    if (output_index > 0 && output_index >= m_outputs.size()) {
        throw out_of_range("node output index is out of range");
    }

    return Output<const Node>(this, output_index);
}

vector<ov::Input<ov::Node>> ov::Node::inputs() {
    vector<Input<Node>> result;

    for (size_t i = 0; i < get_input_size(); i++) {
        result.emplace_back(this, i);
    }

    return result;
}

vector<ov::Output<ov::Node>> ov::Node::input_values() const {
    vector<Output<Node>> result;

    for (size_t i = 0; i < get_input_size(); i++) {
        result.emplace_back(input(i).get_source_output());
    }

    return result;
}

vector<ov::Input<const ov::Node>> ov::Node::inputs() const {
    vector<Input<const Node>> result;

    for (size_t i = 0; i < get_input_size(); i++) {
        result.emplace_back(this, i);
    }

    return result;
}

vector<ov::Output<ov::Node>> ov::Node::outputs() {
    vector<Output<Node>> result;

    for (size_t i = 0; i < get_output_size(); i++) {
        result.emplace_back(shared_from_this(), i);
    }

    return result;
}

vector<ov::Output<const ov::Node>> ov::Node::outputs() const {
    vector<Output<const Node>> result;

    for (size_t i = 0; i < get_output_size(); i++) {
        result.emplace_back(shared_from_this(), i);
    }

    return result;
}

bool ov::Node::has_evaluate() const {
    return false;
}

OPENVINO_SUPPRESS_DEPRECATED_START
bool ov::Node::evaluate(const HostTensorVector& output_values, const HostTensorVector& input_values) const {
    return false;
}

bool ov::Node::evaluate(const HostTensorVector& output_values,
                        const HostTensorVector& input_values,
                        const EvaluationContext& evaluationContext) const {
    return evaluate(output_values, input_values);
}
OPENVINO_SUPPRESS_DEPRECATED_END

namespace {

class DynamicTensor : public ngraph::runtime::HostTensor {
private:
    ov::runtime::Tensor tensor;

public:
    DynamicTensor(const ov::element::Type& type) : ngraph::runtime::HostTensor(type, ov::PartialShape::dynamic()) {}

    ov::runtime::Tensor get_tensor() {
        return tensor;
    }

protected:
    void allocate_buffer() override {
        OPENVINO_ASSERT(get_partial_shape().is_static(),
                        "Attempt to allocate buffer for tensor with partial shape: ",
                        get_partial_shape());
        OPENVINO_ASSERT(get_element_type().is_static(),
                        "Attempt to allocate buffer for tensor with dynamic type: ",
                        get_element_type());
        m_buffer_size = m_descriptor->size();
        tensor = ov::runtime::Tensor(get_element_type(), get_partial_shape().get_shape());
        m_memory_pointer = tensor.data();
        m_aligned_buffer_pool = m_memory_pointer;
    }
};

inline ngraph::HostTensorVector create_tmp_tensors(const ov::runtime::TensorVector& tensors) {
    ngraph::HostTensorVector result;
    result.reserve(tensors.size());
    for (const auto& tensor : tensors) {
        if (!tensor || tensor.get_shape() == ov::Shape{0}) {
            auto el_type = ov::element::dynamic;
            if (tensor)
                el_type = tensor.get_element_type();
            // Create dynamic tensor
            result.emplace_back(std::make_shared<DynamicTensor>(el_type));
        } else {
            result.emplace_back(std::make_shared<ngraph::runtime::HostTensor>(tensor.get_element_type(),
                                                                              tensor.get_shape(),
                                                                              tensor.data()));
        }
    }
    return std::move(result);
}

inline void update_output_tensors(ov::runtime::TensorVector& output_values, const ngraph::HostTensorVector& outputs) {
    OPENVINO_ASSERT(output_values.size() == outputs.size());
    for (size_t i = 0; i < outputs.size(); i++) {
        if (auto dyn_output = std::dynamic_pointer_cast<DynamicTensor>(outputs[i])) {
            output_values[i] = dyn_output->get_tensor();
        }
    }
}
}  // namespace

bool ov::Node::evaluate(ov::runtime::TensorVector& output_values, const ov::runtime::TensorVector& input_values) const {
    HostTensorVector output = create_tmp_tensors(output_values);
    HostTensorVector input = create_tmp_tensors(input_values);
    OPENVINO_SUPPRESS_DEPRECATED_START
    bool sts = evaluate(output, input);
    OPENVINO_SUPPRESS_DEPRECATED_END
    update_output_tensors(output_values, output);
    return sts;
}

bool ov::Node::evaluate(ov::runtime::TensorVector& output_values,
                        const ov::runtime::TensorVector& input_values,
                        const ov::EvaluationContext& evaluationContext) const {
    HostTensorVector output = create_tmp_tensors(output_values);
    HostTensorVector input = create_tmp_tensors(input_values);
    OPENVINO_SUPPRESS_DEPRECATED_START
    bool sts = evaluate(output, input, evaluationContext);
    OPENVINO_SUPPRESS_DEPRECATED_END
    update_output_tensors(output_values, output);
    return sts;
}

bool ov::Node::evaluate_lower(ov::runtime::TensorVector& output_values) const {
    HostTensorVector output = create_tmp_tensors(output_values);
    OPENVINO_SUPPRESS_DEPRECATED_START
    bool sts = evaluate_lower(output);
    OPENVINO_SUPPRESS_DEPRECATED_END
    update_output_tensors(output_values, output);
    return sts;
}

bool ov::Node::evaluate_upper(ov::runtime::TensorVector& output_values) const {
    HostTensorVector output = create_tmp_tensors(output_values);
    OPENVINO_SUPPRESS_DEPRECATED_START
    bool sts = evaluate_upper(output);
    OPENVINO_SUPPRESS_DEPRECATED_END
    update_output_tensors(output_values, output);
    return sts;
}

OPENVINO_SUPPRESS_DEPRECATED_START

bool ov::Node::evaluate_lower(const HostTensorVector& output_values) const {
    const auto& inputs = input_values();
    bool dyn_inputs = std::any_of(inputs.begin(), inputs.end(), [](const Output<Node>& output) {
        return !output.get_tensor().has_and_set_bound();
    });
    if (dyn_inputs)
        return false;
    return ngraph::default_lower_bound_evaluator(this, output_values);
}

bool ov::Node::evaluate_upper(const HostTensorVector& output_values) const {
    const auto& inputs = input_values();
    bool dyn_inputs = std::any_of(inputs.begin(), inputs.end(), [](const Output<Node>& output) {
        return !output.get_tensor().has_and_set_bound();
    });
    if (dyn_inputs)
        return false;
    return ngraph::default_upper_bound_evaluator(this, output_values);
}

OPENVINO_SUPPRESS_DEPRECATED_END

bool ov::Node::constant_fold(OutputVector& output_values, const OutputVector& input_values) {
    OV_ITT_SCOPED_TASK(ov::itt::domains::nGraph, "Node::constant_fold");

    if (m_rt_info.count("disabled_constant_folding_0")) {
        return false;
    }

    // If all the inputs are constants, try to evaluate the outputs
    bool all_constants = std::all_of(input_values.begin(), input_values.end(), [](const Output<Node>& input) {
        return ov::as_type_ptr<ngraph::op::v0::Constant>(input.get_node_shared_ptr());
    });
    if (!all_constants)
        return false;

    HostTensorVector input_tensors;
    for (const auto& input : input_values) {
        auto host_tensor = make_shared<ngraph::runtime::HostTensor>(
            ov::as_type_ptr<ngraph::op::v0::Constant>(input.get_node_shared_ptr()));
        input_tensors.push_back(host_tensor);
    }
    HostTensorVector output_tensors;
    OutputVector output_constants;
    for (const auto& output : outputs()) {
        auto tensor = make_shared<HostTensor>(output.get_element_type(), output.get_partial_shape());
        output_tensors.push_back(tensor);
    }
    OPENVINO_SUPPRESS_DEPRECATED_START
    if (evaluate(output_tensors, input_tensors)) {
        for (size_t i = 0; i < output_tensors.size(); ++i) {
            output_values[i] = make_shared<ngraph::op::Constant>(output_tensors[i]);
        }
        return true;
    }
    OPENVINO_SUPPRESS_DEPRECATED_END
    return false;
}

namespace ov {
BWDCMP_RTTI_DEFINITION(AttributeAdapter<shared_ptr<Node>>);

AttributeAdapter<std::shared_ptr<Node>>::AttributeAdapter(std::shared_ptr<Node>& value) : m_ref(value) {}

bool AttributeAdapter<std::shared_ptr<Node>>::visit_attributes(AttributeVisitor& visitor) {
    auto original_id = visitor.get_registered_node_id(m_ref);
    auto id = original_id;
    visitor.on_attribute("ID", id);
    if (id != original_id) {
        m_ref = visitor.get_registered_node(id);
    }
    return true;
}

BWDCMP_RTTI_DEFINITION(AttributeAdapter<NodeVector>);

AttributeAdapter<NodeVector>::AttributeAdapter(NodeVector& ref) : m_ref(ref) {}

bool AttributeAdapter<NodeVector>::visit_attributes(AttributeVisitor& visitor) {
    size_t size = m_ref.size();
    visitor.on_attribute("size", size);
    if (size != m_ref.size()) {
        m_ref.resize(size);
    }
    ostringstream index;
    for (size_t i = 0; i < size; i++) {
        index.str("");
        index << i;
        string id;
        if (m_ref[i]) {
            id = visitor.get_registered_node_id(m_ref[i]);
        }
        visitor.on_attribute(index.str(), id);
        if (!m_ref[i]) {
            m_ref[i] = visitor.get_registered_node(id);
        }
    }
    return true;
}
}  // namespace ov
