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

#' Create ADBC drivers
#'
#' Creates the R object representation of an ADBC driver, which consists of a
#' name and an initializer function with an optional subclass to control
#' finer-grained behaviour at the R level.
#'
#' @param x,entrypoint An ADBC driver may be defined either as an init function
#'   or as an identifier with an entrypoint name. A driver init func
#'   must be an external pointer to a DL_FUNC with the type
#'   `AdbcDriverInitFunc` specified in the adbc.h header.
#' @param load_flags Integer flags generated by [adbc_load_flags()]
#' @param ... Further key/value parameters to store with the (R-level) driver
#'   object.
#' @param subclass An optional subclass for finer-grained control of
#'   behaviour at the R level.
#'
#' @return An object of class 'adbc_driver'
#' @export
#'
#' @examples
#' adbc_driver_void()
#'
adbc_driver_void <- function() {
  if (is.null(internal_driver_env$void)) {
    internal_driver_env$void <- adbc_driver(
      .Call(RAdbcVoidDriverInitFunc),
      subclass = "adbc_driver_void"
    )
  }

  internal_driver_env$void
}

#' @rdname adbc_driver_void
#' @export
adbc_driver <- function(x, entrypoint = NULL, ...,
                        load_flags = adbc_load_flags(),
                        subclass = character()) {
  error <- adbc_allocate_error()
  driver_alloc <- .Call(RAdbcAllocateDriver)
  driver <- driver_alloc$driver
  version <- driver_alloc$version

  # Attempt to load the driver to ensure it works
  status <- adbc_driver_load(x, entrypoint, version, driver, error, load_flags)
  stop_for_error(status, error)

  # Keep track of the version/mechanism used to successfully load a driver
  driver$version <- version
  driver$load_flags <- load_flags
  if (inherits(x, "adbc_driver_init_func")) {
    driver$driver_init_func <- x
  } else {
    driver$name <- x
    driver$entrypoint <- entrypoint
  }

  # Add extra properties
  args <- list(...)
  for (i in seq_along(args)) {
    driver[[names(args)[i]]] <- args[[i]]
  }

  class(driver) <- c(subclass, class(driver))
  driver
}

#' Low-level driver loader
#'
#' Most users should use [adbc_driver()]; however, this function may be used
#' to allow other libraries (e.g., GDAL) to access the driver loader.
#'
#' @inheritParams adbc_driver_void
#' @param driver An external pointer to an `AdbcDriver`
#' @param version The version number corresponding to the `driver` supplied
#' @param error An external pointer to an `AdbcError` or NULL
#' @param additional_search_path_list A path list of additional locations to search for driver manifests
#'
#' @return An integer ADBC status code
#' @export
#'
adbc_driver_load <- function(x, entrypoint, version, driver, error,
                             load_flags = adbc_load_flags(), additional_search_path_list = NULL) {
  if (inherits(x, "adbc_driver_init_func")) {
    .Call(RAdbcLoadDriverFromInitFunc, x, version, driver, error)
  } else {
    .Call(RAdbcLoadDriver, x, entrypoint, version, load_flags, additional_search_path_list, driver, error)
  }
}

#' Driver search/load options
#'
#' Options that indicate where to look for driver manifests. Manifests
#' (.toml files) can be installed at the system level, the user level,
#' in location(s) specified by the ADBC_DRIVER_PATH environment variable,
#' and/or in a conda environment. See the ADBC documentation for details
#' regarding the locations of the user and system paths on various platforms.
#'
#' @param search_env Search for manifest files in the directories specified in
#'   the ADBC_DRIVER_PATH environment variable and (when installed with conda)
#'   in the conda environment.
#' @param search_user Search for manifest files in the designated directory
#'   for user ADBC driver installs.
#' @param search_system Search for manifest files in the designtaed directory
#'   for system ADBC driver installs.
#' @param allow_relative_paths Allow shared objects to be specified relative to
#'   the current working directory.
#'
#' @return An integer flag value for use in `adbc_driver()`
#'
adbc_load_flags <- function(search_env = TRUE, search_user = TRUE,
                            search_system = TRUE, allow_relative_paths = TRUE) {
  load_flags <- 0L

  # define ADBC_LOAD_FLAG_SEARCH_ENV 1
  # define ADBC_LOAD_FLAG_SEARCH_USER 2
  # define ADBC_LOAD_FLAG_SEARCH_SYSTEM 4
  # define ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS 8
  if (search_env) {
    load_flags <- bitwOr(load_flags, 1L)
  }

  if (search_user) {
    load_flags <- bitwOr(load_flags, 2L)
  }

  if (search_system) {
    load_flags <- bitwOr(load_flags, 4L)
  }

  if (allow_relative_paths) {
    load_flags <- bitwOr(load_flags, 8L)
  }

  load_flags
}

internal_driver_env <- new.env(parent = emptyenv())

#' @export
print.adbc_driver <- function(x, ...) {
  str(x, ...)
}

#' @importFrom utils str
#' @export
str.adbc_driver <- function(object, ...) {
  cat(sprintf("<%s> ", class(object)[1]))

  fields <- names(object)
  object_lst <- Map("[[", list(object), fields)
  names(object_lst) <- fields

  str(object_lst, ...)
  invisible(object)
}
