#' Create an attribute for an HDF5 object
#'
#' Creates an attribute, `name`, which is attached to the object specified
#' by the identifier `h5obj`. The attribute name must be unique for the object.
#'
#' @param h5obj An object of class [H5IdComponent-class] representing a H5 object
#' identifier (file, group, or dataset). See [H5Fcreate()], [H5Fopen()],
#' [H5Gcreate()], [H5Gopen()], [H5Dcreate()], or [H5Dopen()] to create an object of this kind.
#' @param name The name of the attribute (character).
#' @param dtype_id A character name of a datatype. See `h5const("H5T")` for
#' possible datatypes. Can also be an integer representing an HDF5 datatype.
#' Only simple datatypes are allowed for attributes.
#' @param h5space An object of class [H5IdComponent-class] representing a H5
#' dataspace. See [H5Dget_space()], [H5Screate_simple()], [H5Screate()] to create an object
#' of this kind.
#'
#' @returns An object of class [H5IdComponent-class] representing a H5 attribute identifier.
#'
#' @export
H5Acreate <- function(h5obj, name, dtype_id, h5space) {
  h5checktype(h5obj, "object")
  if (length(name) != 1 || !is.character(name)) {
    stop("'name' must be a character string of length 1")
  }
  ## dont check if we have an H5T identifier already
  if (!grepl(pattern = "^[[:digit:]]+$", dtype_id)) {
    dtype_id <- h5checkConstants("H5T", dtype_id)
  }
  h5checktype(h5space, "dataspace")
  aid <- .Call(
    "_H5Acreate",
    h5obj@ID,
    name,
    dtype_id,
    h5space@ID,
    PACKAGE = "rhdf5"
  )
  if (aid > 0) {
    h5attribute <- new("H5IdComponent", ID = aid, native = h5obj@native)
  } else {
    message("HDF5: unable to create attribute")
    h5attribute <- FALSE
  }
  invisible(h5attribute)
}

#' Open an attribute for an HDF5 object
#'
#' @param h5obj An object of class [H5IdComponent-class] representing a H5 object
#' identifier (file, group, or dataset). See [H5Fcreate()], [H5Fopen()],
#' [H5Gcreate()], [H5Gopen()], [H5Dcreate()], or [H5Dopen()] to create an object of this kind.
#' @param name The name of the attribute (character).
#' @param objname The name of the object the attribute belongs to.
#' @param n Opens attribute number `n` in the given order and index. Indexing is C-style, base-0,
#' so the first attribute is opened with `n=0`.
#' @param index_type See `h5const("H5_INDEX")` for possible arguments.
#' @param order See `h5const("H5_ITER")` for possible arguments.
#'
#' @returns An object of class [H5IdComponent-class] representing a H5 attribute identifier.
#'
#' @name H5Aopen
NULL

#' @rdname H5Aopen
#' @export
H5Aopen <- function(h5obj, name) {
  h5checktype(h5obj, "object")
  if (length(name) != 1 || !is.character(name)) {
    stop("'name' must be a character string of length 1")
  }
  aid <- .Call("_H5Aopen", h5obj@ID, name, PACKAGE = "rhdf5")
  if (aid > 0) {
    h5attribute <- new("H5IdComponent", ID = aid, native = h5obj@native)
  } else {
    message("HDF5: unable to open attribute")
    h5attribute <- FALSE
  }
  invisible(h5attribute)
}

#' @rdname H5Aopen
#' @export
H5Aopen_by_name <- function(h5obj, objname = ".", name) {
  h5checktype(h5obj, "object")
  if (length(objname) != 1 || !is.character(objname)) {
    stop("'objname' must be a character string of length 1")
  }
  if (length(name) != 1 || !is.character(name)) {
    stop("'name' must be a character string of length 1")
  }
  aid <- .Call("_H5Aopen_by_name", h5obj@ID, objname, name, PACKAGE = "rhdf5")
  if (aid > 0) {
    h5attribute <- new("H5IdComponent", ID = aid, native = h5obj@native)
  } else {
    message("HDF5: unable to open attribute")
    h5attribute <- FALSE
  }
  invisible(h5attribute)
}

#' @rdname H5Aopen
#' @export
H5Aopen_by_idx <- function(
  h5obj,
  n,
  objname = ".",
  index_type = h5default("H5_INDEX"),
  order = h5default("H5_ITER")
) {
  h5checktype(h5obj, "object")
  if (length(objname) != 1 || !is.character(objname)) {
    stop("'objname' must be a character string of length 1")
  }
  index_type <- h5checkConstants("H5_INDEX", index_type)
  order <- h5checkConstants("H5_ITER", order)
  if (length(n) != 1 || !is.numeric(n)) {
    stop("'n' must be an integer of length 1")
  }
  n <- as.integer(n)
  aid <- .Call(
    "_H5Aopen_by_idx",
    h5obj@ID,
    objname,
    index_type,
    order,
    n,
    PACKAGE = "rhdf5"
  )
  if (aid > 0) {
    h5attribute <- new("H5IdComponent", ID = aid, native = h5obj@native)
  } else {
    message("HDF5: unable to open attribute")
    h5attribute <- FALSE
  }
  invisible(h5attribute)
}

#' Check whether an specific attribute exists for an HDF5 object
#'
#' @param h5obj An object of class [H5IdComponent-class] representing a H5 object
#' identifier (file, group, or dataset). See [H5Fcreate()], [H5Fopen()],
#' [H5Gcreate()], [H5Gopen()], [H5Dcreate()], or [H5Dopen()] to create an object of this kind.
#' @param name The name of the attribute (character).
#'
#' @returns A logical value indicating whether an attribute with name `name`
#'   exists for the object specified by `h5obj`.
#'
#' @export
H5Aexists <- function(h5obj, name) {
  h5checktype(h5obj, "object")
  if (length(name) != 1 || !is.character(name)) {
    stop("'name' must be a character string of length 1")
  }
  res <- .Call("_H5Aexists", h5obj@ID, name, PACKAGE = "rhdf5")
  res > 0
}


#' Close an HDF5 attribute
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing a
#' the attribute to be closed.  Normally created by [H5Aopen()] or similar.
#'
#' @seealso [H5Aopen()]
#' @export
H5Aclose <- function(h5attribute) {
  h5checktype(h5attribute, "attribute")
  invisible(.Call("_H5Aclose", h5attribute@ID, PACKAGE = "rhdf5"))
}

#' Delete an specified attribute of an HDF5 object
#'
#' @param h5obj An object of class [H5IdComponent-class] representing a H5 object
#' identifier (file, group, or dataset). See [H5Fcreate()], [H5Fopen()],
#' [H5Gcreate()], [H5Gopen()], [H5Dcreate()], or [H5Dopen()] to create an object of this kind.
#' @param name The name of the attribute (character).
#'
#' @export
H5Adelete <- function(h5obj, name) {
  h5checktype(h5obj, "object")
  if (length(name) != 1 || !is.character(name)) {
    stop("'name' must be a character string of length 1")
  }
  res <- .Call("_H5Adelete", h5obj@ID, name, PACKAGE = "rhdf5")
  res
}

#' Get the name of an HDF5 attribute object
#'
#' Retrieves the name of the attribute specified by an HDF5 attribute object.
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing an
#' attribute.  Normally created by [H5Aopen()] or similar.
#'
#' @returns A character vector of length 1 containing the name of the attribute.
#'
#' @export
H5Aget_name <- function(h5attribute) {
  h5checktype(h5attribute, "attribute")
  name <- .Call("_H5Aget_name", h5attribute@ID, PACKAGE = "rhdf5")
  name
}

#' Get a copy of the attribute dataspace
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing an
#' attribute.  Normally created by [H5Aopen()] or similar.
#'
#' @returns Returns an object of class [H5IdComponent-class] representing a H5
#' dataspace identifier
#'
#' @export
H5Aget_space <- function(h5attribute) {
  h5checktype(h5attribute, "attribute")
  sid <- .Call("_H5Aget_space", h5attribute@ID, PACKAGE = "rhdf5")
  if (sid > 0) {
    h5space <- new("H5IdComponent", ID = sid, native = h5attribute@native)
  } else {
    message("HDF5: unable to create simple data space")
    h5space <- FALSE
  }
  invisible(h5space)
}

#' Get a copy of the attribute datatype
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing an
#' attribute.  Normally created by [H5Aopen()] or similar.
#'
#' @export
H5Aget_type <- function(h5attribute) {
  h5checktype(h5attribute, "attribute")
  tid <- .Call("_H5Aget_type", h5attribute@ID, PACKAGE = "rhdf5")
  invisible(tid)
}

#' Read data from an HDF5 attribute
#'
#' @details Internally, R does not support 64-bit integers. All integers in R are 32-bit
#' integers. By setting bit64conversion='int', a coercing to 32-bit integers is
#' enforced, with the risk of data loss, but with the insurance that numbers
#' are represented as integers. bit64conversion='double' coerces the 64-bit
#' integers to floating point numbers. doubles can represent integers with up
#' to 54-bits, but they are not represented as integer values anymore. For
#' larger numbers there is again a data loss. bit64conversion='bit64' is
#' recommended way of coercing. It represents the 64-bit integers as objects
#' of class 'integer64' as defined in the package 'bit64'. Make sure that you
#' have installed 'bit64'. The datatype 'integer64' is not part of base R, but
#' defined in an external package. This can produce unexpected behaviour when
#' working with the data.
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing an
#' attribute.  Normally created by [H5Aopen()] or similar.
#' @param buf Optional buffer to store retrieved values. The buffer size has to
#' fit the size of the memory space `h5spaceMem`. No extra memory will be
#' allocated for the data. Default is `NULL` which means the function will
#' return the attribute data.
#' @param bit64conversion Defines how 64-bit integers are converted. (See
#' the details section for more information on these options.)
#'
#' @returns If `buf=NULL` returns the contents of the attribute.  Otherwise
#' return 0 if attribute is read successfully.
#'
#' @export
H5Aread <- function(
  h5attribute,
  buf = NULL,
  bit64conversion = c("int", "double", "bit64")
) {
  h5checktype(h5attribute, "attribute")

  bit64conversion <- fix_missing(bit64conversion, "int")
  if (identical(bit64conversion, "default")) {
    bit64conversion <- "int"
    warning(
      "To rely on the `bit64conversion` argument default, ",
      'you should omit it or explicitly set it to its default value (`"int"`).',
      ' Passing `"default"` will be disallowed in the next release cycle.'
    )
  }
  bit64conversion <- match.arg(bit64conversion)
  bit64conv <- switch(
    bit64conversion,
    int = 0L,
    double = 1L,
    bit64 = 2L
  )
  if (bit64conv == 2L && !requireNamespace("bit64", quietly = TRUE)) {
    stop("install package 'bit64' before using bit64conversion='bit64'")
  }

  rval <- .Call("_H5Aread", h5attribute@ID, buf, bit64conv, PACKAGE = "rhdf5")
  if (is(rval, "H5IdComponent")) {
    rval@native <- h5attribute@native
  }

  return(rval)
}

#' Write data to an HDF5 attribute
#'
#' @param h5attribute An object of class [H5IdComponent-class] representing an
#' attribute.  Normally created by [H5Aopen()] or similar.
#' @param buf The data to be written.
#'
#' @export
H5Awrite <- function(h5attribute, buf) {
  h5checktype(h5attribute, "attribute")
  invisible(.Call("_H5Awrite", h5attribute@ID, buf, PACKAGE = "rhdf5"))
}
