#' Constants for VSIFile$seek()
#'
#' These are package global constants for convenience in calling
#' `VSIFile$seek()`.
#'
#' @name vsi_constants
#' @export
SEEK_SET <- "SEEK_SET"
#' @name vsi_constants
#' @export
SEEK_CUR <- "SEEK_CUR"
#' @name vsi_constants
#' @export
SEEK_END <- "SEEK_END"

#' @name VSIFile-class
#'
#' @aliases
#' Rcpp_VSIFile Rcpp_VSIFile-class VSIFile
#'
#' @title Class wrapping the GDAL VSIVirtualHandle API for binary file I/O
#' @description
#' `VSIFile` provides bindings to the GDAL VSIVirtualHandle API. Encapsulates a
#' `VSIVirtualHandle`
#' (\url{https://gdal.org/en/stable/api/cpl_cpp.html#_CPPv416VSIVirtualHandle}).
#' This API abstracts binary file I/O across "regular" file systems, URLs,
#' cloud storage services, Zip/GZip/7z/RAR, and in-memory files.
#' It provides analogs of several Standard C file I/O functions, allowing
#' virtualization of disk I/O so that non-file data sources can be made to
#' appear as files.
#'
#' `VSIFile` is a C++ class exposed directly to \R (via `RCPP_EXPOSED_CLASS`).
#' Fields and methods of the class are accessed using the `$` operator.
#'
#' @param filename Character string containing the filename to open. It may be
#' a file in a regular local filesystem, or a filename with a GDAL /vsiPREFIX/
#' (see \url{https://gdal.org/en/stable/user/virtual_file_systems.html}).
#' @param access Character string containing the access requested (i.e., `"r"`,
#' `"r+"`, `"w"`, `"w+`). Defaults to `"r"`. Binary access is always implied
#' and the "b" does not need to be included in `access`.
#' \tabular{lll}{
#'  **Access** \tab **Explanation**             \tab **If file exists**\cr
#'  `"r"`      \tab open file for reading       \tab read from start\cr
#'  `"r+"`     \tab open file for read/write    \tab read from start\cr
#'  `"w"`      \tab create file for writing     \tab destroy contents\cr
#'  `"w+"`     \tab create file for read/write  \tab destroy contents
#' }
#' @param options Optional character vector of `NAME=VALUE` pairs specifying
#' filesystem-dependent options. These are the options provided by
#' `VSIFOpenEx2L()` in the GDAL API (GDAL >= 3.3, see Details).
#' @returns An object of class `VSIFile` which contains a pointer to a
#' `VSIVirtualHandle`. Class methods are described in Details, along with a set
#' of writable fields for per-object settings.
#'
#' @section Usage (see Details):
#' ```
#' ## Constructors
#' vf <- new(VSIFile, filename)
#' # specifying access:
#' vf <- new(VSIFile, filename, access)
#' # specifying access and options (both required):
#' vf <- new(VSIFile, filename, access, options)
#'
#' ## Read/write fields (per-object settings)
#' vf$reportVSIFErrorAsEof
#'
#' ## Methods
#' vf$seek(offset, origin)
#' vf$tell()
#' vf$rewind()
#' vf$read(nbytes)
#' vf$write(object)
#' vf$eof()
#' vf$truncate(new_size)
#' vf$flush()
#' vf$ingest(max_size)
#'
#' vf$close()
#' vf$open()
#' vf$get_filename()
#' vf$get_access()
#' vf$set_access(access)
#' ```
#' @section Details:
#' ## Constructors
#'
#' \code{new(VSIFile, filename)}\cr
#' Returns an object of class `VSIFile`, or an error is raised if a file
#' handle cannot be obtained.
#'
#' \code{new(VSIFile, filename, access)}\cr
#' Alternate constructor for passing `access` as a character string
#' (e.g., `"r"`, `"r+"`, `"w"`, `"w+"`).
#' Returns an object of class `VSIFile` with an open file handle, or an error
#' is raised if a file handle cannot be obtained.
#'
#' \code{new(VSIFile, filename, access, options)}\cr
#' Alternate constructor for passing `access` as a character string, and
#' `options` as a character vector of "NAME=VALUE" pairs (all arguments
#' required, GDAL >= 3.3 required for `options` support).
#'
#' The `options` argument is highly file system dependent. Supported options
#' as of GDAL 3.10 include:
#' * MIME headers such as Content-Type and Content-Encoding are supported for
#' the /vsis3/, /vsigs/, /vsiaz/, /vsiadls/ file systems.
#' * DISABLE_READDIR_ON_OPEN=YES/NO (GDAL >= 3.6) for /vsicurl/ and other
#' network-based file systems. By default, directory file listing is done,
#' unless YES is specified.
#' * WRITE_THROUGH=YES (GDAL >= 3.8) for Windows regular files to set the
#' FILE_FLAG_WRITE_THROUGH flag to the `CreateFile()` function. In that mode,
#' the data are written to the system cache but are flushed to disk without
#' delay.
#'
#' Options specific to /vsis3/, /vsigs/, /vsioss/ and /vsiaz/:
#' * CACHE=YES/NO (GDAL >= 3.10) whether file metadata and content that is read
#' from the network should be kept cached in memory, after file handle closing.
#' Default is YES, unless the filename is set in CPL_VSIL_CURL_NON_CACHED.
#' * CHUNK_SIZE=val in MiB (GDAL >= 3.10) for "w" mode. Size of a block. Default
#' is 50 MiB. For /vsis3/, /vsigz/, /vsioss/, it can be up to 5000 MiB.
#' For /vsiaz/, only taken into account when BLOB_TYPE=BLOCK. It can be up to
#' 4000 MiB.
#'
#' Options specific to /vsiaz/ in "w" mode:
#' * BLOB_TYPE=APPEND/BLOCK (GDAL >= 3.10) type of blob. Defaults to APPEND.
#' Append blocks are limited to 195 GiB (however if the file size is below
#' 4 MiB, a block blob will be created in a single PUT operation).
#'
#' See the GDAL documentation for `VSIFOpenEx2L()` for the most current set of
#' available options (\url{https://gdal.org/en/stable/api/cpl.html}).
#'
#' ## Read/write fields (per-object settings)
#'
#' \code{$reportVSIFErrorAsEof}\cr
#' A logical value, `TRUE` (the default) to report a VSI error condition,
#' as indicated by `VSIFErrorL()` in GDAL >= 3.10, as `TRUE` in a call to the
#' \code{$eof()} method (see below). Since GDAL 3.10, some virtual file systems
#' that used to report errors through `VSIFEofL` now do so through `VSIFErrorL`.
#' Code testing \code{$eof()} to potentially end read loops should also test
#' for an error condition, which this setting enables by default.
#' Ignored if GDAL < 3.10.
#'
#' ## Methods
#'
#' \code{$seek(offset, origin)}\cr
#' Seek to a requested `offset` in the file.
#' `offset` is given as a positive numeric scalar, optionally as
#' `bit64::integer64` type.
#' `origin` is given as a character string, one of `SEEK_SET`, `SEEK_CUR` or
#' `SEEK_END`. Package global constants are defined for convenience, so these
#' can be passed unquoted. Note that `offset` is an unsigned type, so `SEEK_CUR`
#' can only be used for positive seek. If negative seek is needed, use:
#' ```
#' vf$seek(vf$tell() + negative_offset, SEEK_SET)
#' ```
#' Returns `0` on success or `-1` on failure.
#'
#' \code{$tell()}\cr
#' Returns the current file read/write offset in bytes from the beginning of
#' the file. The return value is a numeric scalar carrying the `integer64`
#' class attribute.
#'
#' \code{$rewind()}\cr
#' Rewind the file pointer to the beginning of the file. This is equivalent to
#' `vf$seek(0, SEEK_SET)`. No return value, called for that side effect.
#'
#' \code{$read(nbytes)}\cr
#' Read `nbytes` bytes from the file at the current offset. Returns an \R `raw`
#' vector, or `NULL` if the operation fails.
#'
#' \code{$write(object)}\cr
#' Write bytes to the file at the current offset. `object` is a `raw` vector.
#' Returns the number of bytes successfully written, as numeric scalar
#' carrying the `integer64` class attribute.
#' See also base \R `charToRaw()` / `rawToChar()`, convert to or from raw
#' vectors, and `readBin()` / `writeBin()` which read binary data from or write
#' binary data to a raw vector.
#'
#' \code{$eof()}\cr
#' Test for end of file. Returns `TRUE` if an end-of-file condition occurred
#' during the previous read operation. The end-of-file flag is cleared by a
#' successful call to \code{$seek()}.
#'
#' \code{$truncate(new_size)}\cr
#' Truncate/expand the file to the specified `new_size`, given as a positive
#' numeric scalar, optionally as `bit64::integer64` type.
#' Returns `0` on success.
#'
#' \code{$flush()}\cr
#' Flush pending writes to disk. For files in write or update mode and on
#' file system types where it is applicable, all pending output on the file is
#' flushed to the physical disk.
#' On Windows regular files, this method does nothing, unless the
#' `VSI_FLUSH=YES` configuration option is set (and only when the file has not
#' been opened with the `WRITE_THROUGH` option).
#' Returns `0` on success or `-1` on error.
#'
#' \code{$ingest(max_size)}\cr
#' Ingest a file into memory. Read the whole content of the file into a `raw`
#' vector.
#' `max_size` is the maximum size of file allowed, given as a numeric scalar,
#' optionally as `bit64::integer64` type. If no limit, set to a negative value.
#' Returns a `raw` vector, or `NULL` if the operation fails.
#'
#' \code{$close()}\cr
#' Closes the file. The file should always be closed when I/O has been
#' completed. Returns `0` on success or `-1` on error.
#'
#' \code{$open()}\cr
#' This method can be used to re-open the file after it has been closed, using
#' the same `filename`, and same `options` if any are set. The file will be
#' opened using `access` as currently set. The \code{$set_access()} method can
#' be called to change the requested access while the file is closed.
#' No return value. An error is raised if a file handle cannot be obtained.
#'
#' \code{$get_filename()}\cr
#' Returns a character string containing the `filename` associated with this
#' `VSIFile` object (the `filename` originally used to create the object).
#'
#' \code{$get_access()}\cr
#' Returns a character string containing the `access` as currently set on this
#' `VSIFile` object.
#'
#' \code{$set_access(access)}\cr
#' Sets the requested read/write access on this `VSIFile` object, given as a
#' character string (i.e., `"r"`, `"r+"`, `"w"`, `"w+"`). The access can be
#' changed only while the `VSIFile` object is closed, and will apply when it is
#' re-opened with a call to \code{$open()}.
#' Returns `0` on success or `-1` on error.
#'
#' @note
#' File offsets are given as \R `numeric` (i.e., `double` type), optionally
#' carrying the `bit64::integer64` class attribute. They are returned as
#' `numeric` with the `integer64` class attribute attached. The `integer64`
#' type is signed, so the maximum file offset supported by this interface
#' is `9223372036854775807` (the value of `bit64::lim.integer64()[2]`).
#'
#' Some virtual file systems allow only sequential write, so no seeks or read
#' operations are then allowed (e.g., AWS S3 files with /vsis3/).
#' Starting with GDAL 3.2, a configuration option can be set with:
#' ```
#' set_config_option("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "YES")
#' ````
#' in which case random-write access is possible (involves the creation of a
#' temporary local file, whose location is controlled by the `CPL_TMPDIR`
#' configuration option). In this case, setting `access` to `"w+"` may be
#' needed for writing with seek and read operations (if creating a new file,
#' otherwise, `"r+"` to open an existing file), while `"w"` access would
#' allow sequential write only.
#'
#' @seealso
#' GDAL Virtual File Systems (compressed, network hosted, etc...):\cr
#' /vsimem, /vsizip, /vsitar, /vsicurl, ...\cr
#' \url{https://gdal.org/en/stable/user/virtual_file_systems.html}
#'
#' [vsi_copy_file()], [vsi_read_dir()], [vsi_stat()], [vsi_unlink()]
#'
#' @examples
#' # The examples make use of the FARSITE LCP format specification at:
#' # https://gdal.org/en/stable/drivers/raster/lcp.html
#' # An LCP file is a raw format with a 7,316-byte header. The format
#' # specification gives byte offets and data types for fields in the header.
#'
#' lcp_file <- system.file("extdata/storm_lake.lcp", package="gdalraster")
#'
#' # identify a FARSITE v.4 LCP file
#' # function to check if the first three fields have valid data
#' # input is the first twelve raw bytes in the file
#' is_lcp <- function(bytes) {
#'   values <- readBin(bytes, "integer", n = 3)
#'   if ((values[1] == 20 || values[1] == 21) &&
#'       (values[2] == 20 || values[2] == 21) &&
#'       (values[3] >= -90 && values[3] <= 90)) {
#'
#'     return(TRUE)
#'   } else {
#'     return(FALSE)
#'   }
#' }
#'
#' vf <- new(VSIFile, lcp_file)
#' vf
#'
#' vf$read(12) |> is_lcp()
#'
#' vf$tell()
#'
#' # read the whole file into memory
#' bytes <- vf$ingest(-1)
#' vf$close()
#'
#' # write to a VSI in-memory file
#' mem_file <- "/vsimem/storml_copy.lcp"
#' vf <- new(VSIFile, mem_file, "w+")
#' vf$write(bytes)
#'
#' vf$tell()
#' vf$rewind()
#' vf$tell()
#'
#' vf$seek(0, SEEK_END)
#' (vf$tell() == vsi_stat(lcp_file, "size"))  # TRUE
#'
#' vf$rewind()
#' vf$read(12) |> is_lcp()
#'
#' # read/write an integer field
#' # write invalid data for the Latitude field and then set back
#' # save the original first
#' vf$seek(8, SEEK_SET)
#' lat_orig <- vf$read(4)
#' readBin(lat_orig, "integer")  # 46
#' # latitude -99 out of range
#' vf$seek(8, SEEK_SET)
#' writeBin(-99L, raw()) |> vf$write()
#' vf$rewind()
#' vf$read(12) |> is_lcp()  # FALSE
#' vf$seek(8, SEEK_SET)
#' vf$read(4) |> readBin("integer")  # -99
#' # set back to original
#' vf$seek(8, SEEK_SET)
#' vf$write(lat_orig)
#' vf$rewind()
#' vf$read(12) |> is_lcp()  # TRUE
#'
#' # read a vector of doubles - xmax, xmin, ymax, ymin
#' # 327766.1, 323476.1, 5105082.0, 5101872.0
#' vf$seek(4172, SEEK_SET)
#' vf$read(32) |> readBin("double", n = 4)
#'
#' # read a short int, the canopy cover units
#' vf$seek(4232, SEEK_SET)
#' vf$read(2) |> readBin("integer", size = 2)  # 1 = "percent"
#'
#' # read the Description field
#' vf$seek(6804, SEEK_SET)
#' bytes <- vf$read(512)
#' rawToChar(bytes)
#'
#' # edit the Description
#' desc <- paste(rawToChar(bytes),
#'               "Storm Lake AOI,",
#'               "Beaverhead-Deerlodge National Forest, Montana.")
#'
#' vf$seek(6804, SEEK_SET)
#' charToRaw(desc) |> vf$write()
#' vf$close()
#'
#' # verify the file as a raster dataset
#' ds <- new(GDALRaster, mem_file)
#' ds$info()
#'
#' # retrieve Description from the metadata
#' # band = 0 for dataset-level metadata, domain = "" for default domain
#' ds$getMetadata(band = 0, domain = "")
#' ds$getMetadataItem(band = 0, mdi_name = "DESCRIPTION", domain = "")
#'
#' ds$close()
#' \dontshow{vsi_unlink(mem_file)}
NULL

Rcpp::loadModule("mod_VSIFile", TRUE)
