/*
 * 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.
 */
/*
 * Example code for TVM-FFI ABI overview.
 *
 * Compilation command:
 *
 * ```bash
 * gcc $(tvm-ffi-config --cflags)             \
 *     $(tvm-ffi-config --ldflags)            \
 *     $(tvm-ffi-config --libs)               \
 *     -Wl,-rpath,$(tvm-ffi-config --libdir)  \
 *     -o ./example_code
 * ```
 */
// NOLINTBEGIN(modernize-deprecated-headers,modernize-use-nullptr)
#include <assert.h>
#include <dlpack/dlpack.h>
#include <stdio.h>
#include <string.h>
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/extra/c_env_api.h>

int IS_OWNING_ANY = 1;

// [Any_AnyView.FromInt_Float.begin]
TVMFFIAny Any_AnyView_FromInt(int64_t value) {
  TVMFFIAny any;
  any.type_index = kTVMFFIInt;
  any.zero_padding = 0;
  any.v_int64 = value;
  return any;
}

TVMFFIAny Any_AnyView_FromFloat(double value) {
  TVMFFIAny any;
  any.type_index = kTVMFFIFloat;
  any.zero_padding = 0;
  any.v_float64 = value;
  return any;
}
// [Any_AnyView.FromInt_Float.end]

// [Any_AnyView.FromObjectPtr.begin]
TVMFFIAny Any_AnyView_FromObjectPtr(TVMFFIObject* obj) {
  TVMFFIAny any;
  assert(obj != NULL);
  any.type_index = kTVMFFIObject;
  any.zero_padding = 0;
  any.v_obj = obj;
  // Increment refcount if it's Any (owning) instead of AnyView (borrowing)
  if (IS_OWNING_ANY) {
    TVMFFIObjectIncRef(obj);
  }
  return any;
}
// [Any_AnyView.FromObjectPtr.end]

// [Any_AnyView.Destroy.begin]
void Any_AnyView_Destroy(TVMFFIAny* any) {
  if (IS_OWNING_ANY) {
    // Checks if `any` holds a heap-allocated object,
    // and if so, decrements the reference count
    if (any->type_index >= kTVMFFIStaticObjectBegin) {
      TVMFFIObjectDecRef(any->v_obj);
    }
  }
  *any = (TVMFFIAny){0};  // Clears the `any` struct
}
// [Any_AnyView.Destroy.end]

// [Any_AnyView.GetInt_Float.begin]
int64_t Any_AnyView_GetInt(const TVMFFIAny* any) {
  if (any->type_index == kTVMFFIInt || any->type_index == kTVMFFIBool) {
    return any->v_int64;
  } else if (any->type_index == kTVMFFIFloat) {
    return (int64_t)(any->v_float64);
  }
  assert(0);  // FAILED to read int
  return 0;
}

double Any_AnyView_GetFloat(const TVMFFIAny* any) {
  if (any->type_index == kTVMFFIInt || any->type_index == kTVMFFIBool) {
    return (double)(any->v_int64);
  } else if (any->type_index == kTVMFFIFloat) {
    return any->v_float64;
  }
  assert(0);  // FAILED to read float
  return 0.0;
}
// [Any_AnyView.GetInt_Float.end]

// [Any_AnyView.GetDLTensor.begin]
DLTensor* Any_AnyView_GetDLTensor(const TVMFFIAny* value) {
  if (value->type_index == kTVMFFIDLTensorPtr) {
    return (DLTensor*)(value->v_ptr);
  } else if (value->type_index == kTVMFFITensor) {
    return (DLTensor*)((char*)(value->v_obj) + sizeof(TVMFFIObject));
  }
  assert(0);  // FAILED to read DLTensor
  return NULL;
}
// [Any_AnyView.GetDLTensor.end]

// [Any_AnyView.GetObject.begin]
TVMFFIObject* Any_AnyView_GetObject(const TVMFFIAny* value) {
  if (value->type_index == kTVMFFINone) {
    return NULL;  // Handling nullptr if needed
  } else if (value->type_index >= kTVMFFIStaticObjectBegin) {
    return value->v_obj;
  }
  assert(0);  // FAILED: not a TVM-FFI object
  return NULL;
}
// [Any_AnyView.GetObject.end]

// [Object.IsInstance.begin]
int Object_IsInstance(int32_t sub_type_index, int32_t super_type_index, int32_t super_type_depth) {
  const TVMFFITypeInfo* sub_type_info = NULL;
  // Everything is a subclass of object.
  if (sub_type_index == super_type_index) {
    return 1;
  }
  // Invariance: parent index is always smaller than the child.
  if (sub_type_index < super_type_index) {
    return 0;
  }
  sub_type_info = TVMFFIGetTypeInfo(sub_type_index);
  return sub_type_info->type_depth > super_type_depth &&
         sub_type_info->type_ancestors[super_type_depth]->type_index == super_type_index;
}
// [Object.IsInstance.end]

// [Object.MoveFromAny.begin]
void Object_MoveFromAny(TVMFFIAny* any, TVMFFIObject** obj) {
  assert(any->type_index >= kTVMFFIStaticObjectBegin);
  *obj = any->v_obj;
  (*any) = (TVMFFIAny){0};
  if (!IS_OWNING_ANY) {
    TVMFFIObjectIncRef(*obj);
  }
}
// [Object.MoveFromAny.end]

// [Object.Destroy.begin]
void Object_Destroy(TVMFFIObject* obj) {
  assert(obj != NULL);
  TVMFFIObjectDecRef(obj);
}
// [Object.Destroy.end]

// [Tensor.AccessDLTensor.begin]
DLTensor* Tensor_AccessDLTensor(TVMFFIObject* tensor) {
  assert(tensor != NULL);
  return (DLTensor*)((char*)tensor + sizeof(TVMFFIObject));
}
// [Tensor.AccessDLTensor.end]

// [Tensor_FromDLPack.begin]
TVMFFIObject* Tensor_FromDLPack(DLManagedTensorVersioned* from) {
  int err_code = 0;
  TVMFFIObject* out = NULL;
  err_code = TVMFFITensorFromDLPackVersioned(  //
      from,                                    // input DLPack tensor
      /*require_alignment=*/0,                 // no alignment requirement
      /*require_contiguous=*/1,                // require contiguous tensor
      (void**)(&out));
  assert(err_code == 0);
  return out;
}
// [Tensor_FromDLPack.end]

// [Tensor_Alloc.begin]
TVMFFIObject* Tensor_Alloc(DLTensor* prototype) {
  int err_code = 0;
  TVMFFIObject* out = NULL;
  assert(prototype->data == NULL);
  err_code = TVMFFIEnvTensorAlloc(prototype, (void**)(&out));
  assert(err_code == 0);
  return out;
}
// [Tensor_Alloc.end]

// [Tensor_ToDLPackVersioned.begin]
DLManagedTensorVersioned* Tensor_ToDLPackVersioned(TVMFFIObject* tensor) {
  int err_code = 0;
  DLManagedTensorVersioned* out = NULL;
  err_code = TVMFFITensorToDLPackVersioned(tensor, &out);
  assert(err_code == 0);
  return out;
}
// [Tensor_ToDLPackVersioned.end]

// [Function.Construct.begin]
TVMFFIObject* Function_Construct(void* self, TVMFFISafeCallType safe_call,
                                 void (*deleter)(void* self)) {
  int err_code;
  TVMFFIObject* out = NULL;
  err_code = TVMFFIFunctionCreate(self, safe_call, deleter, (void**)(&out));
  assert(err_code == 0);
  return out;
}
// [Function.Construct.end]

// [Function.Call.begin]
int64_t CallFunction(TVMFFIObject* func, int64_t x, int64_t y) {
  int err_code;
  TVMFFIAny args[2];
  TVMFFIAny result = (TVMFFIAny){0};
  args[0] = Any_AnyView_FromInt(x);
  args[1] = Any_AnyView_FromInt(y);
  err_code = TVMFFIFunctionCall(func, args, 2, &result);
  assert(err_code == 0);
  return Any_AnyView_GetInt(&result);
}
// [Function.Call.end]

// [Function.GetGlobal.begin]
TVMFFIObject* Function_RetrieveGlobal(const char* name) {
  TVMFFIObject* out = NULL;
  TVMFFIByteArray name_byte_array = {name, strlen(name)};
  int err_code = TVMFFIFunctionGetGlobal(&name_byte_array, (void**)(&out));
  assert(err_code == 0);
  return out;
}
// [Function.GetGlobal.end]

// [Function.SetGlobal.begin]
void Function_SetGlobal(const char* name, TVMFFIObject* func) {
  TVMFFIByteArray name_byte_array = {name, strlen(name)};
  int err_code = TVMFFIFunctionSetGlobal(&name_byte_array, func, 0);
  assert(err_code == 0);
}
// [Function.SetGlobal.end]

// [Error.Print.begin]
void PrintError(TVMFFIObject* err) {
  TVMFFIErrorCell* cell = (TVMFFIErrorCell*)((char*)err + sizeof(TVMFFIObject));
  fprintf(stderr, "%.*s: %.*s\n",                        //
          (int)cell->kind.size, cell->kind.data,         // e.g. "ValueError"
          (int)cell->message.size, cell->message.data);  // e.g. "Expected at least 2 arguments"
  if (cell->backtrace.size) {
    fprintf(stderr, "Backtrace:\n%.*s\n", (int)cell->backtrace.size, cell->backtrace.data);
  }
}
// [Error.Print.end]

// [Error.HandleReturnCode.begin]
void Error_HandleReturnCode(int rc) {
  TVMFFIObject* err = NULL;
  if (rc == -1) {
    // Move the raised error from TLS (clears TLS slot)
    TVMFFIErrorMoveFromRaised((void**)(&err));  // now `err` owns the error object
    if (err != NULL) {
      PrintError(err);  // print the error
      // IMPORTANT: Release the error object, or gets memory leaks
      TVMFFIObjectDecRef(err);
    }
  }
}
// [Error.HandleReturnCode.end]

// [Error.RaiseException.begin]
int Error_RaiseException(void* handle, const TVMFFIAny* args, int32_t num_args, TVMFFIAny* result) {
  TVMFFIErrorSetRaisedFromCStr("ValueError", "Expected at least 2 arguments");
  return -1;
}
// [Error.RaiseException.end]

// NOLINTEND(modernize-deprecated-headers,modernize-use-nullptr)
int main() { return 0; }
