/*
 * 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 src/ffi/object.cc
 * \brief Registry to record dynamic types
 */
#include <tvm/ffi/any.h>
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/dict.h>
#include <tvm/ffi/container/list.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/enum.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/reflection/accessor.h>
#include <tvm/ffi/reflection/creator.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>

#include <string_view>
#include <utility>
#include <vector>

#include "object_internal.h"

namespace tvm {
namespace ffi {

/*!
 * \brief Global registry that manages
 *
 * \note We do not use mutex to guard updating of TypeTable
 *
 * The assumption is that updating of TypeTable will be done
 * in the main thread during initialization or loading, or
 * explicitly locked from the caller.
 *
 * Then the followup code will leverage the information
 */
class TypeTable {
 public:
  /*! \brief Type information */
  struct Entry : public TypeInfo {
    /*! \brief stored type key */
    String type_key_data;
    /*! \brief acenstor information */
    std::vector<const TVMFFITypeInfo*> type_ancestors_data;
    /*! \brief type fields informaton */
    std::vector<TVMFFIFieldInfo> type_fields_data;
    /*! \brief type methods informaton */
    std::vector<TVMFFIMethodInfo> type_methods_data;
    /*! \brief extra information */
    TVMFFITypeMetadata metadata_data;
    // NOTE: the indices in [index, index + num_reserved_slots) are
    // reserved for the child-class of this type.
    /*! \brief Total number of slots reserved for the type and its children. */
    int32_t num_slots;
    /*! \brief number of allocated child slots. */
    int32_t allocated_slots;
    /*! \brief Whether child can overflow. */
    bool child_slots_can_overflow{true};

    Entry(int32_t type_index, int32_t type_depth, String type_key, int32_t num_slots,
          bool child_slots_can_overflow, const Entry* parent) {
      // setup fields in the class
      this->type_key_data = std::move(type_key);
      this->num_slots = num_slots;
      this->allocated_slots = 1;
      this->child_slots_can_overflow = child_slots_can_overflow;
      // set up type acenstors information
      if (type_depth != 0) {
        TVM_FFI_ICHECK_NOTNULL(parent);
        TVM_FFI_ICHECK_EQ(type_depth, parent->type_depth + 1);
        type_ancestors_data.resize(type_depth);
        // copy over parent's type information
        for (int32_t i = 0; i < parent->type_depth; ++i) {
          type_ancestors_data[i] = parent->type_ancestors[i];
        }
        // set last type information to be parent
        type_ancestors_data[parent->type_depth] = parent;
      }
      // initialize type info: no change to type_key and type_ancestors fields
      // after this line
      this->type_index = type_index;
      this->type_depth = type_depth;
      this->type_key = TVMFFIByteArray{this->type_key_data.data(), this->type_key_data.length()};
      this->type_key_hash = std::hash<String>()(this->type_key_data);
      this->type_ancestors = type_ancestors_data.data();
      // initialize the reflection information
      this->num_fields = 0;
      this->num_methods = 0;
      this->fields = nullptr;
      this->methods = nullptr;
      this->metadata = nullptr;
    }
  };

  struct TypeAttrColumnData : public TVMFFITypeAttrColumn {
    std::vector<Any> data_;
  };

  int32_t GetOrAllocTypeIndex(String type_key, int32_t static_type_index, int32_t type_depth,
                              int32_t num_child_slots, bool child_slots_can_overflow,
                              int32_t parent_type_index) {
    auto it = type_key2index_.find(type_key);
    if (it != type_key2index_.end()) {
      return type_table_[(*it).second]->type_index;
    }

    // get parent's entry
    Entry* parent = [&]() -> Entry* {
      if (parent_type_index < 0) return nullptr;
      // try to allocate from parent's type table.
      TVM_FFI_ICHECK_LT(parent_type_index, type_table_.size())
          << " type_key=" << type_key << ", static_index=" << static_type_index;
      return type_table_[parent_type_index].get();
    }();

    // get allocated index
    int32_t allocated_tindex = [&]() {
      // Step 0: static allocation
      if (static_type_index >= 0) {
        TVM_FFI_ICHECK_LT(static_type_index, type_table_.size());
        TVM_FFI_ICHECK(type_table_[static_type_index] == nullptr)
            << "Conflicting static index " << static_type_index << " between "
            << ToStringView(type_table_[static_type_index]->type_key) << " and " << type_key;
        return static_type_index;
      }
      TVM_FFI_ICHECK_NOTNULL(parent);
      int num_slots = num_child_slots + 1;
      if (parent->allocated_slots + num_slots <= parent->num_slots) {
        // allocate the slot from parent's reserved pool
        int32_t allocated_tindex = parent->type_index + parent->allocated_slots;
        // update parent's state
        parent->allocated_slots += num_slots;
        return allocated_tindex;
      }
      // Step 2: allocate from overflow
      TVM_FFI_ICHECK(parent->child_slots_can_overflow)
          << "Reach maximum number of sub-classes for " << ToStringView(parent->type_key);
      // allocate new entries.
      int32_t allocated_tindex = static_cast<int32_t>(type_counter_);
      type_counter_ += num_slots;
      TVM_FFI_ICHECK_LE(type_table_.size(), type_counter_);
      type_table_.reserve(type_counter_);
      // resize type table
      while (static_cast<int32_t>(type_table_.size()) < type_counter_) {
        type_table_.emplace_back(nullptr);
      }
      return allocated_tindex;
    }();

    // if parent cannot overflow, then this class cannot.
    if (parent != nullptr && !(parent->child_slots_can_overflow)) {
      child_slots_can_overflow = false;
    }
    // total number of slots include the type itself.

    if (parent != nullptr) {
      TVM_FFI_ICHECK_GT(allocated_tindex, parent->type_index);
    }

    type_table_[allocated_tindex] =
        std::make_unique<Entry>(allocated_tindex, type_depth, type_key, num_child_slots + 1,
                                child_slots_can_overflow, parent);
    // update the key2index mapping.
    type_key2index_.Set(type_key, allocated_tindex);
    return allocated_tindex;
  }

  int32_t TypeKeyToIndex(const TVMFFIByteArray* type_key) {
    String type_key_str(type_key->data, type_key->size);
    auto it = type_key2index_.find(type_key_str);
    TVM_FFI_ICHECK(it != type_key2index_.end()) << "Cannot find type `" << type_key_str << "`";
    return static_cast<int32_t>((*it).second);
  }

  Entry* GetTypeEntry(int32_t type_index) {
    Entry* entry = nullptr;
    if (type_index >= 0 && static_cast<size_t>(type_index) < type_table_.size()) {
      entry = type_table_[type_index].get();
    }
    TVM_FFI_ICHECK(entry != nullptr) << "Cannot find type info for type_index=" << type_index;
    return entry;
  }

  Array<String> GetRegisteredTypeKeys() const {
    Array<String> ret;
    for (const auto& entry : type_table_) {
      if (entry) {
        ret.push_back(entry->type_key_data);
      }
    }
    return ret;
  }

  void RegisterTypeField(int32_t type_index, const TVMFFIFieldInfo* info) {
    Entry* entry = GetTypeEntry(type_index);
    std::string_view new_name(info->name.data, info->name.size);
    std::string_view type_key(entry->type_key.data, entry->type_key.size);
    // Check: no duplicate field name within this type's own fields.
    for (const auto& existing : entry->type_fields_data) {
      TVM_FFI_ICHECK(std::string_view(existing.name.data, existing.name.size) != new_name)
          << "Duplicate field name \"" << new_name << "\" in type \"" << type_key << "\"";
    }
    // Warn: field name should not shadow any ancestor field.
    for (int32_t d = 0; d < entry->type_depth; ++d) {
      const TVMFFITypeInfo* ancestor = entry->type_ancestors[d];
      for (int32_t i = 0; i < ancestor->num_fields; ++i) {
        if (std::string_view(ancestor->fields[i].name.data, ancestor->fields[i].name.size) ==
            new_name) {
          std::cerr << "[WARNING] Field \"" << new_name << "\" in type \"" << type_key
                    << "\" duplicates an ancestor field in \""
                    << std::string_view(ancestor->type_key.data, ancestor->type_key.size)
                    << "\". Child types should not re-register inherited fields." << std::endl;
        }
      }
    }
    TVMFFIFieldInfo field_data = *info;
    // Retain FunctionObj setter via any_pool_ so it outlives the Entry.
    if ((field_data.flags & kTVMFFIFieldFlagBitSetterIsFunctionObj) &&
        field_data.setter != nullptr) {
      TVMFFIAny setter_ref;
      setter_ref.type_index = kTVMFFIFunction;
      setter_ref.zero_padding = 0;
      setter_ref.v_obj = static_cast<TVMFFIObject*>(field_data.setter);
      any_pool_.emplace_back(AnyView::CopyFromTVMFFIAny(setter_ref));
    }
    field_data.name = this->CopyString(info->name);
    field_data.doc = this->CopyString(info->doc);
    field_data.metadata = this->CopyString(info->metadata);
    if (info->flags & kTVMFFIFieldFlagBitMaskHasDefault) {
      field_data.default_value_or_factory =
          this->CopyAny(AnyView::CopyFromTVMFFIAny(info->default_value_or_factory))
              .CopyToTVMFFIAny();
    } else {
      field_data.default_value_or_factory = AnyView(nullptr).CopyToTVMFFIAny();
    }
    entry->type_fields_data.push_back(field_data);
    // refresh ptr as the data can change
    entry->fields = entry->type_fields_data.data();
    entry->num_fields = static_cast<int32_t>(entry->type_fields_data.size());
  }

  void RegisterTypeMethod(int32_t type_index, const TVMFFIMethodInfo* info) {
    Entry* entry = GetTypeEntry(type_index);
    TVMFFIMethodInfo method_data = *info;
    method_data.name = this->CopyString(info->name);
    method_data.doc = this->CopyString(info->doc);
    method_data.metadata = this->CopyString(info->metadata);
    method_data.method = this->CopyAny(AnyView::CopyFromTVMFFIAny(info->method)).CopyToTVMFFIAny();
    entry->type_methods_data.push_back(method_data);
    entry->methods = entry->type_methods_data.data();
    entry->num_methods = static_cast<int32_t>(entry->type_methods_data.size());
  }

  void RegisterTypeMetadata(int32_t type_index, const TVMFFITypeMetadata* metadata) {
    Entry* entry = GetTypeEntry(type_index);
    if (entry->metadata != nullptr) {
      TVM_FFI_LOG_AND_THROW(RuntimeError)
          << "Overriding " << ToStringView(entry->type_key) << ", possible causes:\n"
          << "- two ObjectDef<T>() calls for the same T \n"
          << "- when we forget to assign _type_key to ObjectRef<Y> that inherits from T\n"
          << "- another type with the same key is already registered\n"
          << "Cross check the reflection registration.";
    }
    entry->metadata_data = *metadata;
    entry->metadata_data.doc = this->CopyString(metadata->doc);
    entry->metadata = &(entry->metadata_data);
  }

  void RegisterTypeAttr(int32_t type_index, const TVMFFIByteArray* name, const TVMFFIAny* value) {
    AnyView value_view = AnyView::CopyFromTVMFFIAny(*value);
    String name_str(*name);
    size_t column_index = 0;
    auto it = type_attr_name_to_column_index_.find(name_str);
    if (it == type_attr_name_to_column_index_.end()) {
      column_index = type_attr_columns_.size();
      type_attr_columns_.emplace_back(std::make_unique<TypeAttrColumnData>());
      type_attr_name_to_column_index_.Set(name_str, static_cast<int64_t>(column_index));
    } else {
      column_index = (*it).second;
    }
    TypeAttrColumnData* column = type_attr_columns_[column_index].get();
    if (type_index == kTVMFFINone) {
      // Sentinel: just ensure the column exists without registering a value.
      if (column->data_.empty()) {
        column->data = reinterpret_cast<const TVMFFIAny*>(column->data_.data());
        column->size = 0;
        column->begin_index = 0;
      }
      return;
    }
    // TODO(1.0): set begin_index to first registered type_index for sparse column storage
    // For now, begin_index is always 0 (resize from index 0).
    if (static_cast<size_t>(type_index) >= column->data_.size()) {
      // Extend back from index 0.
      column->data_.resize(static_cast<size_t>(type_index) + 1, Any(nullptr));
    }
    column->data = reinterpret_cast<const TVMFFIAny*>(column->data_.data());
    column->size = static_cast<int32_t>(column->data_.size());
    column->begin_index = 0;
    Any& slot = column->data_[type_index - column->begin_index];
    if (slot.type_index() != kTVMFFINone) {
      TVM_FFI_THROW(RuntimeError)
          << "TypeAttr `" << name_str << "` is already registered for type index " << type_index
          << ". To update the stored value, register a mutable container (e.g., Dict/List) "
          << "once and mutate it in place on subsequent calls.";
    }
    slot = value_view;
  }
  const TVMFFITypeAttrColumn* GetTypeAttrColumn(const TVMFFIByteArray* name) {
    String name_str(*name);
    auto it = type_attr_name_to_column_index_.find(name_str);
    if (it == type_attr_name_to_column_index_.end()) return nullptr;
    return type_attr_columns_[(*it).second].get();
  }

  void Dump(int min_children_count) {
    std::vector<int> num_children(type_table_.size(), 0);
    // expected child slots compute the expected slots
    // based on the current child slot setting
    std::vector<int> expected_child_slots(type_table_.size(), 0);
    // reverse accumulation so we can get total counts in a bottom-up manner.
    for (auto it = type_table_.rbegin(); it != type_table_.rend(); ++it) {
      const Entry* ptr = it->get();
      if (ptr != nullptr && ptr->type_depth != 0) {
        int parent_index = ptr->type_ancestors[ptr->type_depth - 1]->type_index;
        num_children[parent_index] += num_children[ptr->type_index] + 1;
        if (expected_child_slots[ptr->type_index] + 1 < ptr->num_slots) {
          expected_child_slots[ptr->type_index] = ptr->num_slots - 1;
        }
        expected_child_slots[parent_index] += expected_child_slots[ptr->type_index] + 1;
      }
    }

    for (const auto& ptr : type_table_) {
      if (ptr != nullptr && num_children[ptr->type_index] >= min_children_count) {
        std::cerr << '[' << ptr->type_index << "]\t" << ToStringView(ptr->type_key);
        if (ptr->type_depth != 0) {
          int32_t parent_index = ptr->type_ancestors[ptr->type_depth - 1]->type_index;
          std::cerr << "\tparent=" << ToStringView(type_table_[parent_index]->type_key);
        } else {
          std::cerr << "\tparent=root";
        }
        std::cerr << "\tnum_child_slots=" << ptr->num_slots - 1
                  << "\tnum_children=" << num_children[ptr->type_index]
                  << "\texpected_child_slots=" << expected_child_slots[ptr->type_index]
                  << std::endl;
      }
    }
  }

  static TypeTable* Global() {
    // deliberately create a new instance via raw new
    // to ensure table lives longer in case unloading
    // still need the table info
    // memory will be recycled by the OS at program exit
    static TypeTable* inst = new TypeTable();
    return inst;
  }

 private:
  TypeTable() : type_attr_columns_{}, type_attr_name_to_column_index_{} {
    type_table_.reserve(TypeIndex::kTVMFFIDynObjectBegin);
    for (int32_t i = 0; i < TypeIndex::kTVMFFIDynObjectBegin; ++i) {
      type_table_.emplace_back(nullptr);
    }
    // initialize the entry for object
    this->GetOrAllocTypeIndex(String(Object::_type_key), Object::_type_index, Object::_type_depth,
                              Object::_type_child_slots, Object::_type_child_slots_can_overflow,
                              -1);
    TVMFFITypeMetadata info;
    info.total_size = sizeof(Object);
    info.structural_eq_hash_kind = kTVMFFISEqHashKindUnsupported;
    info.creator = nullptr;
    info.doc = TVMFFIByteArray{nullptr, 0};
    RegisterTypeMetadata(Object::_type_index, &info);
    // reserve the static types
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFINone, TypeIndex::kTVMFFINone);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIInt, TypeIndex::kTVMFFIInt);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIBool, TypeIndex::kTVMFFIBool);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIFloat, TypeIndex::kTVMFFIFloat);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIOpaquePtr, TypeIndex::kTVMFFIOpaquePtr);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIDataType, TypeIndex::kTVMFFIDataType);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIDevice, TypeIndex::kTVMFFIDevice);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIDLTensorPtr, TypeIndex::kTVMFFIDLTensorPtr);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIRawStr, TypeIndex::kTVMFFIRawStr);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIByteArrayPtr, TypeIndex::kTVMFFIByteArrayPtr);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIObjectRValueRef,
                            TypeIndex::kTVMFFIObjectRValueRef);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFISmallStr, TypeIndex::kTVMFFISmallStr);
    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFISmallBytes, TypeIndex::kTVMFFISmallBytes);
    // reserved static type indices for depth 1 object types
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIStr, TypeIndex::kTVMFFIStr);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIBytes, TypeIndex::kTVMFFIBytes);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIError, TypeIndex::kTVMFFIError);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIFunction, TypeIndex::kTVMFFIFunction);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIShape, TypeIndex::kTVMFFIShape);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFITensor, TypeIndex::kTVMFFITensor);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIArray, TypeIndex::kTVMFFIArray);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIList, TypeIndex::kTVMFFIList);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIMap, TypeIndex::kTVMFFIMap);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIDict, TypeIndex::kTVMFFIDict);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIModule, TypeIndex::kTVMFFIModule);
    ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIOpaquePyObject,
                                   TypeIndex::kTVMFFIOpaquePyObject);
  }

  void ReserveBuiltinTypeIndex(const char* type_key, int32_t static_type_index) {
    this->GetOrAllocTypeIndex(String(type_key), static_type_index, 0, 0, false, -1);
  }

  void ReserveDepthOneObjectTypeIndex(const char* type_key, int32_t static_type_index) {
    this->GetOrAllocTypeIndex(String(type_key), static_type_index, 1, 0, false,
                              TypeIndex::kTVMFFIObject);
  }

  static ObjectPtr<details::StringObj> MakeInplaceString(const char* data, size_t length) {
    ObjectPtr<details::StringObj> p =
        make_inplace_array_object<details::StringObj, char>(length + 1);
    static_assert(alignof(details::StringObj) % alignof(char) == 0);
    static_assert(sizeof(details::StringObj) % alignof(char) == 0);
    char* dest_data = reinterpret_cast<char*>(p.get()) + sizeof(details::StringObj);
    p->data = dest_data;
    p->size = length;
    std::memcpy(dest_data, data, length);
    dest_data[length] = '\0';
    return p;
  }

  TVMFFIByteArray CopyString(TVMFFIByteArray str) {
    if (str.size == 0) {
      return TVMFFIByteArray{nullptr, 0};
    }
    // use explicit object creation to ensure the space pointer to not move
    auto str_obj = MakeInplaceString(str.data, str.size);
    TVMFFIByteArray c_val{str_obj->data, str_obj->size};
    any_pool_.emplace_back(ObjectRef(std::move(str_obj)));
    return c_val;
  }

  AnyView CopyAny(Any val) {
    AnyView view = AnyView(val);
    any_pool_.emplace_back(std::move(val));
    return view;
  }

  int64_t type_counter_{TypeIndex::kTVMFFIDynObjectBegin};
  std::vector<std::unique_ptr<Entry>> type_table_;
  Map<String, int64_t> type_key2index_;
  std::vector<Any> any_pool_;
  // type attribute columns
  std::vector<std::unique_ptr<TypeAttrColumnData>> type_attr_columns_;
  Map<String, int64_t> type_attr_name_to_column_index_;
};

/**
 * \brief Opaque implementation
 */
class OpaqueObjectImpl : public Object, public TVMFFIOpaqueObjectCell {
 public:
  OpaqueObjectImpl(void* handle, void (*deleter)(void* handle)) : deleter_(deleter) {
    this->handle = handle;
  }

  void SetTypeIndex(int32_t type_index) {
    details::ObjectUnsafe::GetHeader(this)->type_index = type_index;
  }

  ~OpaqueObjectImpl() {
    if (deleter_ != nullptr) {
      deleter_(handle);
    }
  }

 private:
  void (*deleter_)(void* handle);
};

ObjectRef GetMissingObject() {
  static ObjectRef missing_obj(make_object<Object>());
  return missing_obj;
}

ObjectRef GetKwargsObject() {
  static ObjectRef kwargs_obj(make_object<Object>());
  return kwargs_obj;
}

}  // namespace ffi
}  // namespace tvm

void TVMFFIGetVersion(TVMFFIVersion* out_version) {
  out_version->major = TVM_FFI_VERSION_MAJOR;
  out_version->minor = TVM_FFI_VERSION_MINOR;
  out_version->patch = TVM_FFI_VERSION_PATCH;
}

int TVMFFIObjectDecRef(TVMFFIObjectHandle handle) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::details::ObjectUnsafe::DecRefObjectHandle(handle);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFIObjectIncRef(TVMFFIObjectHandle handle) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::details::ObjectUnsafe::IncRefObjectHandle(handle);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFIObjectCreateOpaque(void* handle, int32_t type_index, void (*deleter)(void* handle),
                             TVMFFIObjectHandle* out) {
  TVM_FFI_SAFE_CALL_BEGIN();
  if (type_index != kTVMFFIOpaquePyObject) {
    TVM_FFI_THROW(RuntimeError) << "Only kTVMFFIOpaquePyObject is supported for now";
  }
  // create initial opaque object
  tvm::ffi::ObjectPtr<tvm::ffi::OpaqueObjectImpl> p =
      tvm::ffi::make_object<tvm::ffi::OpaqueObjectImpl>(handle, deleter);
  // need to set the type index after creation, because the set to RuntimeTypeIndex()
  // happens after the constructor is called
  p->SetTypeIndex(type_index);
  *out = tvm::ffi::details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(p));
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFITypeKeyToIndex(const TVMFFIByteArray* type_key, int32_t* out_tindex) {
  TVM_FFI_SAFE_CALL_BEGIN();
  out_tindex[0] = tvm::ffi::TypeTable::Global()->TypeKeyToIndex(type_key);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFITypeRegisterField(int32_t type_index, const TVMFFIFieldInfo* info) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::TypeTable::Global()->RegisterTypeField(type_index, info);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFITypeRegisterMethod(int32_t type_index, const TVMFFIMethodInfo* info) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::TypeTable::Global()->RegisterTypeMethod(type_index, info);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFITypeRegisterMetadata(int32_t type_index, const TVMFFITypeMetadata* metadata) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::TypeTable::Global()->RegisterTypeMetadata(type_index, metadata);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFITypeRegisterAttr(int32_t type_index, const TVMFFIByteArray* name,
                           const TVMFFIAny* value) {
  TVM_FFI_SAFE_CALL_BEGIN();
  tvm::ffi::TypeTable::Global()->RegisterTypeAttr(type_index, name, value);
  TVM_FFI_SAFE_CALL_END();
}

const TVMFFITypeAttrColumn* TVMFFIGetTypeAttrColumn(const TVMFFIByteArray* name) {
  TVM_FFI_LOG_EXCEPTION_CALL_BEGIN();
  return tvm::ffi::TypeTable::Global()->GetTypeAttrColumn(name);
  TVM_FFI_LOG_EXCEPTION_CALL_END(TVMFFIGetTypeAttrColumn);
}

int32_t TVMFFITypeGetOrAllocIndex(const TVMFFIByteArray* type_key, int32_t static_type_index,
                                  int32_t type_depth, int32_t num_child_slots,
                                  int32_t child_slots_can_overflow, int32_t parent_type_index) {
  TVM_FFI_LOG_EXCEPTION_CALL_BEGIN();
  tvm::ffi::String s_type_key(type_key->data, type_key->size);
  return tvm::ffi::TypeTable::Global()->GetOrAllocTypeIndex(
      s_type_key, static_type_index, type_depth, num_child_slots, child_slots_can_overflow,
      parent_type_index);
  TVM_FFI_LOG_EXCEPTION_CALL_END(TVMFFITypeGetOrAllocIndex);
}

const TVMFFITypeInfo* TVMFFIGetTypeInfo(int32_t type_index) {
  TVM_FFI_LOG_EXCEPTION_CALL_BEGIN();
  return tvm::ffi::TypeTable::Global()->GetTypeEntry(type_index);
  TVM_FFI_LOG_EXCEPTION_CALL_END(TVMFFIGetTypeInfo);
}

// string APIs, we blend into object.cc to keep things simple
int TVMFFIStringFromByteArray(const TVMFFIByteArray* input, TVMFFIAny* out) {
  TVM_FFI_SAFE_CALL_BEGIN();
  // must set to none first
  out->type_index = kTVMFFINone;
  tvm::ffi::TypeTraits<tvm::ffi::String>::MoveToAny(tvm::ffi::String(input->data, input->size),
                                                    out);
  TVM_FFI_SAFE_CALL_END();
}

int TVMFFIBytesFromByteArray(const TVMFFIByteArray* input, TVMFFIAny* out) {
  TVM_FFI_SAFE_CALL_BEGIN();
  // must set to none first
  out->type_index = kTVMFFINone;
  tvm::ffi::TypeTraits<tvm::ffi::Bytes>::MoveToAny(tvm::ffi::Bytes(input->data, input->size), out);
  TVM_FFI_SAFE_CALL_END();
}

namespace {
TVM_FFI_STATIC_INIT_BLOCK() {
  namespace ffi = ::tvm::ffi;
  namespace refl = ::tvm::ffi::reflection;
  refl::TypeAttrDef<ffi::Object>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::ObjectRef>);
  refl::TypeAttrDef<ffi::details::StringObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::String>);
  refl::TypeAttrDef<ffi::details::BytesObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Bytes>);
  refl::TypeAttrDef<ffi::ErrorObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Error>);
  refl::TypeAttrDef<ffi::FunctionObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Function>);
  refl::TypeAttrDef<ffi::ShapeObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Shape>);
  refl::TypeAttrDef<ffi::TensorObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Tensor>);
  refl::TypeAttrDef<ffi::ArrayObj>().def(
      refl::type_attr::kConvert,
      &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Array<ffi::Any>>);
  refl::TypeAttrDef<ffi::MapObj>().def(
      refl::type_attr::kConvert,
      &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Map<ffi::Any, ffi::Any>>);
  // Skipped: TypeIndex::kTVMFFIModule
  // Skipped: TypeIndex::kTVMFFIOpaquePyObject
  refl::TypeAttrDef<ffi::ListObj>().def(
      refl::type_attr::kConvert,
      &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::List<ffi::Any>>);
  refl::TypeAttrDef<ffi::DictObj>().def(
      refl::type_attr::kConvert,
      &refl::details::FFIConvertFromAnyViewToObjectRef<ffi::Dict<ffi::Any, ffi::Any>>);
  refl::ObjectDef<ffi::EnumObj>(refl::init(false))
      .def_ro("_value", &ffi::EnumObj::_value, "Ordinal assigned at registration.",
              refl::AttachFieldFlag::SEqHashIgnore())
      .def_ro("_name", &ffi::EnumObj::_name, "Instance name.");
  refl::EnsureTypeAttrColumn(refl::type_attr::kEnumEntries);
  refl::EnsureTypeAttrColumn(refl::type_attr::kEnumAttrs);
  refl::EnsureTypeAttrColumn(refl::type_attr::kEnumValueEntries);
  refl::GlobalDef()
      .def_method("ffi.GetRegisteredTypeKeys",
                  []() -> ffi::Array<ffi::String> {
                    return ffi::TypeTable::Global()->GetRegisteredTypeKeys();
                  })
      .def("ffi.GetInvalidObject", ffi::GetMissingObject)
      .def("ffi.GetKwargsObject", ffi::GetKwargsObject);
}
}  // namespace
