# 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.

#' Danger zone: low-level pointer operations
#'
#' The [nanoarrow_schema][as_nanoarrow_schema],
#' [nanoarrow_array][as_nanoarrow_array],
#' and [nanoarrow_array_stream][as_nanoarrow_array_stream] classes are
#' represented in R as external pointers (`EXTPTRSXP`). When these objects
#' go out of scope (i.e., when they are garbage collected or shortly
#' thereafter), the underlying object's `release()` callback is called if
#' the underlying pointer is non-null and if the `release()` callback is
#' non-null.
#'
#' When interacting with other C Data Interface implementations, it is
#' important to keep in mind that the R object wrapping these pointers is
#' always passed by reference (because it is an external pointer) and may
#' be referred to by another R object (e.g., an element in a `list()` or as a
#' variable assigned in a user's environment). When importing a schema,
#' array, or array stream into nanoarrow this is not a problem: the R object
#' takes ownership of the lifecycle and memory is released when the R
#' object is garbage collected. In this case, one can use
#' [nanoarrow_pointer_move()] where `ptr_dst` was created using
#' `nanoarrow_allocate_*()`.
#'
#' The case of exporting is more complicated and as such has a dedicated
#' function, [nanoarrow_pointer_export()], that implements different logic
#' schemas, arrays, and array streams:
#'
#' - Schema objects are (deep) copied such that a fresh copy of the schema
#'   is exported and made the responsibility of some other C data interface
#'   implementation.
#' - Array objects are exported as a shell around the original array that
#'   preserves a reference to the R object. This ensures that the buffers
#'   and children pointed to by the array are not copied and that any references
#'   to the original array are not invalidated.
#' - Array stream objects are moved: the responsibility for the object is
#'   transferred to the other C data interface implementation and any
#'   references to the original R object are invalidated. Because these
#'   objects are mutable, this is typically what you want (i.e., you should
#'   not be pulling arrays from a stream accidentally from two places).
#'
#' If you know the lifecycle of your object (i.e., you created the R object
#' yourself and never passed references to it elsewhere), you can slightly
#' more efficiently call [nanoarrow_pointer_move()] for all three pointer
#' types.
#'
#' @param ptr,ptr_src,ptr_dst An external pointer to a `struct ArrowSchema`,
#'   `struct ArrowArray`, or `struct ArrowArrayStream`.
#' @param protected An object whose scope must outlive that of `ptr`. This is
#'   useful for array streams since at least two specifications involving the
#'   array stream specify that the stream is only valid for the lifecycle of
#'   another object (e.g., an AdbcStatement or OGRDataset).
#' @return
#'   - `nanoarrow_pointer_is_valid()` returns TRUE if the pointer is non-null
#'     and has a non-null release callback.
#'   - `nanoarrow_pointer_addr_dbl()` and `nanoarrow_pointer_addr_chr()` return
#'     pointer representations that may be helpful to facilitate moving or
#'     exporting nanoarrow objects to other libraries.
#'   - `nanoarrow_pointer_addr_pretty()` gives a pointer representation suitable
#'     for printing or error messages.
#'   - `nanoarrow_pointer_release()` returns `ptr`, invisibly.
#'   - `nanoarrow_pointer_move()` and `nanoarrow_pointer_export()` reeturn
#'     `ptr_dst`, invisibly.
#'   - `nanoarrow_allocate_array()`, `nanoarrow_allocate_schema()`, and
#'     `nanoarrow_allocate_array_stream()` return an
#'     [array][as_nanoarrow_array], a [schema][as_nanoarrow_schema], and an
#'     [array stream][as_nanoarrow_array_stream], respectively.
#' @export
#'
nanoarrow_pointer_is_valid <- function(ptr) {
  .Call(nanoarrow_c_pointer_is_valid, ptr)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_addr_dbl <- function(ptr) {
  .Call(nanoarrow_c_pointer_addr_dbl, ptr)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_addr_chr <- function(ptr) {
  .Call(nanoarrow_c_pointer_addr_chr, ptr)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_addr_pretty <- function(ptr) {
  .Call(nanoarrow_c_pointer_addr_pretty, ptr)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_release <- function(ptr) {
  invisible(.Call(nanoarrow_c_pointer_release, ptr))
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_move <- function(ptr_src, ptr_dst) {
  invisible(.Call(nanoarrow_c_pointer_move, ptr_src, ptr_dst))
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_export <- function(ptr_src, ptr_dst) {
  if (inherits(ptr_src, "nanoarrow_schema")) {
    .Call(nanoarrow_c_export_schema, ptr_src, ptr_dst)
  } else if (inherits(ptr_src, "nanoarrow_array")) {
    .Call(nanoarrow_c_export_array, ptr_src, ptr_dst)
  } else if (inherits(ptr_src, "nanoarrow_array_stream")) {
    .Call(nanoarrow_c_export_array_stream, ptr_src, ptr_dst)
  } else {
    stop(
      "`ptr_src` must inherit from 'nanoarrow_schema', 'nanoarrow_array', or 'nanoarrow_array_stream'"
    )
  }

  invisible(ptr_dst)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_allocate_schema <- function() {
  .Call(nanoarrow_c_allocate_schema)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_allocate_array <- function() {
  .Call(nanoarrow_c_allocate_array)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_allocate_array_stream <- function() {
  .Call(nanoarrow_c_allocate_array_stream)
}

#' @rdname nanoarrow_pointer_is_valid
#' @export
nanoarrow_pointer_set_protected <- function(ptr_src, protected) {
  if (!inherits(ptr_src, c("nanoarrow_schema", "nanoarrow_array", "nanoarrow_array_stream"))) {
    stop(
      "`ptr_src` must inherit from 'nanoarrow_schema', 'nanoarrow_array', or 'nanoarrow_array_stream'"
    )
  }

  .Call(nanoarrow_c_pointer_set_protected, ptr_src, protected)
  invisible(ptr_src)
}
