/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file tvm/ir/op.h
 * \brief Primitive operators(builtin intrinsics)
 *        and registry for them.
 */
#ifndef TVM_IR_OP_H_
#define TVM_IR_OP_H_

#include <tvm/ffi/error.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/attr_registry_map.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/env_func.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/type.h>

#include <string>
#include <utility>
#include <vector>

namespace tvm {

// forward declare name.
template <typename>
class OpAttrMap;

/*!
 * \brief Information about an input field of an Op (name, type, description).
 *
 *  Populated via OpRegEntry::add_argument and consumed both by
 *  internal sanity checks / error messages and by external tooling
 *  that wants to introspect an Op's argument schema.
 */
class ArgumentInfoNode : public ffi::Object {
 public:
  /*! \brief name of the field */
  ffi::String name;
  /*! \brief type docstring information in str. */
  ffi::String type_info;
  /*! \brief detailed description of the type */
  ffi::String description;

  static void RegisterReflection() {
    namespace rfl = ffi::reflection;
    rfl::ObjectDef<ArgumentInfoNode>()
        .def_ro("name", &ArgumentInfoNode::name)
        .def_ro("type_info", &ArgumentInfoNode::type_info)
        .def_ro("description", &ArgumentInfoNode::description);
  }

  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.ArgumentInfo", ArgumentInfoNode, ffi::Object);
};

/*! \brief Managed reference to ArgumentInfoNode. */
class ArgumentInfo : public ffi::ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ArgumentInfo, ffi::ObjectRef, ArgumentInfoNode);
};

// TODO(tvm-team): migrate low-level intrinsics to use Op
/*!
 * \brief Primitive Op(builtin intrinsics)
 *
 * This data structure stores the meta-data
 * about primitive operators that can be invoked via Call.
 *
 * Low-level IR intrinsics(such as libc.expf) are also
 * implemented via Op.
 *
 * \sa Op
 */
class OpNode : public ExprNode {
 public:
  /*! \brief name of the operator */
  ffi::String name;
  /*!
   * \brief detailed description of the operator
   *  This can be used to generate docstring automatically for the operator.
   */
  ffi::String description;
  /* \brief Information of input arguments to the operator */
  ffi::Array<ArgumentInfo> arguments;
  /*!
   * \brief The type key of the attribute field
   *  This can be empty, in which case it defaults to anything.
   */
  ffi::String attrs_type_key;
  /*!
   * \brief attribute type index,
   * this field varies in each run and is not exposed to frontend.
   */
  uint32_t attrs_type_index{0};
  /*!
   * \brief number of input arguments to the operator,
   * -1 means it is variable length
   */
  int32_t num_inputs = -1;
  /*!
   * \brief support level of the operator,
   *  The lower the more priority it contains.
   *  This is in analogies to BLAS levels.
   */
  int32_t support_level = 10;

  static void RegisterReflection() {
    namespace refl = tvm::ffi::reflection;
    refl::ObjectDef<OpNode>()
        .def_ro("name", &OpNode::name)
        .def_ro("description", &OpNode::description, refl::AttachFieldFlag::SEqHashIgnore())
        .def_ro("arguments", &OpNode::arguments, refl::AttachFieldFlag::SEqHashIgnore())
        .def_ro("attrs_type_key", &OpNode::attrs_type_key, refl::AttachFieldFlag::SEqHashIgnore())
        .def_ro("num_inputs", &OpNode::num_inputs, refl::AttachFieldFlag::SEqHashIgnore())
        .def_ro("support_level", &OpNode::support_level, refl::AttachFieldFlag::SEqHashIgnore());
  }

  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindUniqueInstance;
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Op", OpNode, ExprNode);

 private:
  /*! \return the internal attr registry index. */
  uint32_t AttrRegistryIndex() const { return index_; }
  /*! \brief repr to be printed in registry*/
  std::string AttrRegistryName() const { return name; }

  // friend class
  template <typename>
  friend class AttrRegistryMapContainerMap;
  template <typename, typename>
  friend class AttrRegistry;
  friend class OpRegEntry;

  // Program internal unique index of operator.
  // Used to help index the program.
  uint32_t index_{0};
};

/*!
 * \brief Managed reference class to OpNode.
 * \sa OpNode
 */
class Op : public Expr {
 public:
  explicit Op(ffi::ObjectPtr<OpNode> n) : Expr(std::move(n)) {
    TVM_FFI_CHECK(defined(), ValueError) << "Op expects a defined OpNode";
  }

  /*!
   * \brief Get additional registered attribute about operators.
   *  If nothing has been registered, an empty OpAttrMap will be returned.
   * \param attr_name The name of the attribute.
   * \return An OpAttrMap of specified attr_name.
   * \tparam ValueType The type of the attribute.
   */
  template <typename ValueType>
  inline static OpAttrMap<ValueType> GetAttrMap(const ffi::String& attr_name);
  /*!
   * \brief Checks if an attr map is present in the registry.
   * \param attr_name The name of the attribute.
   * \return bool True if the attr is present.
   */
  TVM_DLL static bool HasAttrMap(const ffi::String& attr_name);
  /*!
   * \brief Get an Op for a given operator name.
   *  Will raise an error if the op has not been registered.
   * \param op_name Name of the operator.
   * \return Pointer to a Op, valid throughout program lifetime.
   */
  TVM_DLL static const Op& Get(const ffi::String& op_name);

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Op, Expr, OpNode);

 private:
  /*!
   * \brief Get generic attrmap given attr name
   * \param key The attribute key
   * \return The attr map.
   */
  TVM_DLL static const AttrRegistryMapContainerMap<Op>& GetAttrMapContainer(const ffi::String& key);
};

/*!
 * \brief Helper structure to register operators
 * \sa TVM_REGISTER_OP
 */
class OpRegEntry {
 public:
  /*! \return the operator */
  const Op& op() const { return op_; }
  /*!
   * \brief setter function during registration
   *  Set the description of operator
   * \param descr the description string.
   * \return reference to self.
   */
  inline OpRegEntry& describe(const std::string& descr);  // NOLINT(*)
  /*!
   * \brief Add argument information to the function.
   * \param name Name of the argument.
   * \param type Type of the argument.
   * \param description Description of the argument.
   * \return reference to self.
   */
  inline OpRegEntry& add_argument(const std::string& name, const std::string& type,
                                  const std::string& description);
  /*!
   * \brief Set the attrs type key and index to be AttrsType.
   * \tparam AttrsType the attribute type to b set.
   * \return reference to self.
   */
  template <typename AttrsType>
  inline OpRegEntry& set_attrs_type();
  /*!
   * \brief Set the attrs type key and index to be AttrsType.
   * \param key The attribute type key to be set.
   * \return reference to self.
   */
  inline OpRegEntry& set_attrs_type_key(const ffi::String& key);
  /*!
   * \brief Set the num_inputs
   * \param n The number of inputs to be set.
   * \return reference to self.
   */
  inline OpRegEntry& set_num_inputs(int32_t n);  // NOLINT(*)
  /*!
   * \brief Set the support level of op.
   * \param level The support level.
   * \return reference to self.
   */
  inline OpRegEntry& set_support_level(int32_t level);  // NOLINT(*)
  /*!
   * \brief Register additional attributes to operator.
   * \param attr_name The name of the attribute.
   * \param value The value to be set.
   * \param plevel The priority level of this set,
   *  an higher priority level attribute
   *  will replace lower priority level attribute.
   *  Must be bigger than 0.
   *
   *  Cannot set with same plevel twice in the code.
   *
   * \tparam ValueType The type of the value to be set.
   */
  template <typename ValueType>
  inline OpRegEntry& set_attr(const std::string& attr_name,  // NOLINT(*)
                              const ValueType& value, int plevel = 10);

  /*!
   * \brief Resets an attr of the registry.
   * \param attr_name The name of the attribute.
   */
  inline void reset_attr(const std::string& attr_name);

  // set the name of the op to be the same as registry
  inline OpRegEntry& set_name() {  // NOLINT(*)
    if (get()->name.length() == 0) {
      get()->name = name;
    }
    return *this;
  }
  /*!
   * \brief Register or get a new entry.
   * \param name The name of the operator.
   * \return the corresponding entry.
   */
  TVM_DLL static OpRegEntry& RegisterOrGet(const ffi::String& name);

 private:
  template <typename, typename>
  friend class AttrRegistry;
  // the name
  std::string name;
  /*! \brief The operator */
  Op op_;
  /*! \brief Construct the non-null Op for this registry entry. */
  static Op MakeOp(uint32_t reg_index);
  // private constructor
  TVM_DLL OpRegEntry(uint32_t reg_index);
  // return internal pointer to op.
  inline OpNode* get();
  // update the attribute OpAttrMap
  TVM_DLL void UpdateAttr(const ffi::String& key, ffi::Any value, int plevel);
};

/*!
 * \brief ffi::Map<Op,ValueType> used to store meta-information about Op.
 * \tparam ValueType The type of the value stored in map.
 */
template <typename ValueType>
class OpAttrMap : public AttrRegistryMap<Op, ValueType> {
 public:
  /*!
   * \brief get the corresponding value element at op with default value.
   * \param expr The key to the map
   * \param def_value The default value when the key does not exist
   *         or if expr is not an Op.
   * \return the const reference to the content value.
   */
  inline ValueType get(const Expr& expr, ValueType def_value) const;

  using TParent = AttrRegistryMap<Op, ValueType>;
  using TParent::count;
  using TParent::get;
  using TParent::operator[];

 private:
  friend class Op;
  // constructor
  explicit OpAttrMap(const AttrRegistryMapContainerMap<Op>& map) : TParent(map) {}
};

// internal macros to make
#define TVM_OP_REGISTER_VAR_DEF [[maybe_unused]] static ::tvm::OpRegEntry& __make_##Op

/*!
 * \def TVM_REGISTER_OP
 * \brief Register a new operator, or set attribute of the corresponding op.
 *
 * \param OpName The name of registry
 *
 * \code
 *
 *  TVM_REGISTER_OP("add")
 *  .describe("add two inputs together")
 *  .set_num_inputs(2)
 *  .set_attr<OpKernel>("gpu_kernel", AddKernel);
 *
 * \endcode
 */
#define TVM_REGISTER_OP(OpName)                              \
  TVM_FFI_STR_CONCAT(TVM_OP_REGISTER_VAR_DEF, __COUNTER__) = \
      ::tvm::OpRegEntry::RegisterOrGet(OpName).set_name()

// implementations

template <typename ValueType>
inline OpAttrMap<ValueType> Op::GetAttrMap(const ffi::String& key) {
  return OpAttrMap<ValueType>(Op::GetAttrMapContainer(key));
}

inline OpNode* OpRegEntry::get() { return const_cast<OpNode*>(op_.operator->()); }

inline OpRegEntry& OpRegEntry::describe(const std::string& descr) {  // NOLINT(*)
  get()->description = descr;
  return *this;
}

inline OpRegEntry& OpRegEntry::add_argument(const std::string& name, const std::string& type,
                                            const std::string& description) {
  auto n = ffi::make_object<ArgumentInfoNode>();
  n->name = name;
  n->type_info = type;
  n->description = description;
  get()->arguments.push_back(ArgumentInfo(n));
  return *this;
}

inline OpRegEntry& OpRegEntry::set_num_inputs(int32_t n) {  // NOLINT(*)
  get()->num_inputs = n;
  return *this;
}

template <typename AttrsType>
inline OpRegEntry& OpRegEntry::set_attrs_type() {  // NOLINT(*)
  get()->attrs_type_key = AttrsType::_type_key;
  get()->attrs_type_index = AttrsType::RuntimeTypeIndex();
  return *this;
}

inline OpRegEntry& OpRegEntry::set_attrs_type_key(const ffi::String& key) {  // NOLINT(*)
  get()->attrs_type_key = key;
  get()->attrs_type_index = tvm::ffi::TypeKeyToIndex(key.c_str());
  return *this;
}

inline OpRegEntry& OpRegEntry::set_support_level(int32_t n) {  // NOLINT(*)
  get()->support_level = n;
  return *this;
}

template <typename ValueType>
inline OpRegEntry& OpRegEntry::set_attr(  // NOLINT(*)
    const std::string& attr_name, const ValueType& value, int plevel) {
  TVM_FFI_ICHECK_GT(plevel, 0) << "plevel in set_attr must be greater than 0";
  UpdateAttr(attr_name, Any(value), plevel);
  return *this;
}

// member functions of OpAttrMap

template <typename ValueType>
inline ValueType OpAttrMap<ValueType>::get(const Expr& expr, ValueType def_value) const {
  TVM_FFI_ICHECK(expr.defined());
  if (const OpNode* op = expr.as<OpNode>()) {
    return this->map_.get(ffi::GetRef<Op>(op), def_value);
  } else {
    return def_value;
  }
}

}  // namespace tvm
#endif  // TVM_IR_OP_H_
