/*
 * 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/target/tag.h
 * \brief Target tag registry
 */
#ifndef TVM_TARGET_TAG_H_
#define TVM_TARGET_TAG_H_

#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/attr_registry_map.h>
#include <tvm/target/target.h>

#include <utility>

namespace tvm {

/*! \brief A target tag */
class TargetTagNode : public ffi::Object {
 public:
  /*! \brief Name of the target */
  ffi::String name;
  /*! \brief Config map to generate the target */
  ffi::Map<ffi::String, Any> config;

  static void RegisterReflection() {
    namespace refl = tvm::ffi::reflection;
    refl::ObjectDef<TargetTagNode>()
        .def_ro("name", &TargetTagNode::name)
        .def_ro("config", &TargetTagNode::config);
  }
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("target.TargetTag", TargetTagNode, ffi::Object);

 private:
  /*! \brief Return the index stored in attr registry */
  uint32_t AttrRegistryIndex() const { return index_; }
  /*! \brief Return the name stored in attr registry */
  ffi::String AttrRegistryName() const { return name; }
  /*! \brief Index used for internal lookup of attribute registry */
  uint32_t index_;

  template <typename, typename>
  friend class AttrRegistry;
  template <typename>
  friend class AttrRegistryMapContainerMap;
  friend class TargetTagRegEntry;
};

/*!
 * \brief Managed reference class to TargetTagNode
 * \sa TargetTagNode
 */
class TargetTag : public ffi::ObjectRef {
 public:
  /*!
   * \brief Retrieve the Target given it the name of target tag
   * \param target_tag_name Name of the target tag
   * \return The Target requested
   */
  TVM_DLL static ffi::Optional<Target> Get(const ffi::String& target_tag_name);
  /*!
   * \brief List all names of the existing target tags
   * \return A dictionary that maps tag name to the concrete target it corresponds to
   */
  TVM_DLL static ffi::Map<ffi::String, Target> ListTags();
  /*!
   * \brief Retrieve the raw config dict for a target tag
   * \param target_tag_name Name of the target tag
   * \return The config dict if the tag exists, nullopt otherwise
   */
  TVM_DLL static ffi::Optional<ffi::Map<ffi::String, Any>> GetConfig(
      const ffi::String& target_tag_name);
  /*!
   * \brief Add a tag into the registry
   * \param name Name of the tag
   * \param config The target config corresponding to the tag
   * \param override Allow overriding existing tags
   * \return Target created with the tag
   */
  TVM_DLL static Target AddTag(ffi::String name, ffi::Map<ffi::String, Any> config, bool override);

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TargetTag, ffi::ObjectRef, TargetTagNode);

 private:
  /*! \brief Mutable access to the container class  */
  TargetTagNode* operator->() { return static_cast<TargetTagNode*>(data_.get()); }
  friend class TargetTagRegEntry;
};

class TargetTagRegEntry {
 public:
  /*!
   * \brief Set the config dict corresponding to the target tag
   * \param config The config dict for target creation
   */
  inline TargetTagRegEntry& set_config(ffi::Map<ffi::String, Any> config);
  /*!
   * \brief Add a key-value pair to the config dict
   * \param key The attribute name
   * \param value The attribute value
   */
  inline TargetTagRegEntry& with_config(ffi::String key, Any value);
  /*! \brief Set name of the TargetTag to be the same as registry if it is empty */
  inline TargetTagRegEntry& set_name();
  /*!
   * \brief Register or get a new entry.
   * \param target_tag_name The name of the TargetTag.
   * \return the corresponding entry.
   */
  TVM_DLL static TargetTagRegEntry& RegisterOrGet(const ffi::String& target_tag_name);

 private:
  TargetTag tag_;
  ffi::String name;

  /*! \brief private constructor */
  explicit TargetTagRegEntry(uint32_t reg_index) : tag_(ffi::make_object<TargetTagNode>()) {
    tag_->index_ = reg_index;
  }
  template <typename, typename>
  friend class AttrRegistry;
  friend class TargetTag;
};

inline TargetTagRegEntry& TargetTagRegEntry::set_config(ffi::Map<ffi::String, Any> config) {
  tag_->config = std::move(config);
  return *this;
}

inline TargetTagRegEntry& TargetTagRegEntry::with_config(ffi::String key, ffi::Any value) {
  tag_->config.Set(key, value);
  return *this;
}

inline TargetTagRegEntry& TargetTagRegEntry::set_name() {
  if (tag_->name.empty()) {
    tag_->name = name;
  }
  return *this;
}

#define TVM_TARGET_TAG_REGISTER_VAR_DEF \
  [[maybe_unused]] static ::tvm::TargetTagRegEntry& __make_##TargetTag

/*!
 * \def TVM_REGISTER_TARGET_TAG
 * \brief Register a new target tag, or set attribute of the corresponding target tag.
 * \param TargetTagName The name of target tag
 */
#define TVM_REGISTER_TARGET_TAG(TargetTagName)                       \
  TVM_FFI_STR_CONCAT(TVM_TARGET_TAG_REGISTER_VAR_DEF, __COUNTER__) = \
      ::tvm::TargetTagRegEntry::RegisterOrGet(TargetTagName).set_name()

}  // namespace tvm

#endif  // TVM_TARGET_TAG_H_
