module Drivers

export POSIX, ROS3

import ..API
import ..HDF5: HDF5, Properties, h5doc

using Libdl: dlopen, dlsym
if !isdefined(Base, :get_extension)
    using Requires: @require
end

function get_driver(p::Properties)
    driver_id = API.h5p_get_driver(p)
    D = get(DRIVERS, driver_id) do
        error("Unknown driver type")
    end
    get_driver(p, D)
end

abstract type Driver end

const DRIVERS = Dict{API.hid_t,Type{<:Driver}}()

"""
    Core([increment::Csize_t, backing_store::Cuint, [write_tracking::Cuint, page_size::Csize_t]])
    Core(; increment::Csize_t = 8192, backing_store::Cuint = true, write_tracking::Cuint = false, page_size::Csize_t = 524288)

# Arguments

* `increment`: specifies the increment by which allocated memory is to be increased each time more memory is required. (default: 8192)
* `backing_store`: Boolean flag indicating whether to write the file contents to disk when the file is closed. (default: true)
* `write_tracking`: Boolean flag indicating whether write tracking is enabled. (default: false)
* `page_size`: Size, in bytes, of write aggregation pages. (default: 524288)
"""
struct Core <: Driver
    increment::Csize_t
    backing_store::Cuint #Bool
    write_tracking::Cuint #Bool
    page_size::Csize_t
end
Core(increment, backing_store) = Core(increment, backing_store, false, 524288)
Core(; increment=8192, backing_store=true, write_tracking=false, page_size=524288) =
    Core(increment, backing_store, write_tracking, page_size)

function get_driver(p::Properties, ::Type{Core})
    r_increment = Ref{Csize_t}(0)
    r_backing_store = Ref{Bool}(0)
    r_write_tracking = Ref{Bool}(0)
    r_page_size = Ref{Csize_t}(0)
    API.h5p_get_fapl_core(p, r_increment, r_backing_store)
    API.h5p_get_core_write_tracking(p, r_write_tracking, r_page_size)
    return Core(r_increment[], r_backing_store[], r_write_tracking[], r_page_size[])
end

function set_driver!(fapl::Properties, driver::Core)
    HDF5.init!(fapl)
    API.h5p_set_fapl_core(fapl, driver.increment, driver.backing_store)
    API.h5p_set_core_write_tracking(fapl, driver.write_tracking, driver.page_size)
    DRIVERS[API.h5p_get_driver(fapl)] = Core
    return nothing
end

"""
    POSIX()

Also referred to as SEC2, this driver uses POSIX file-system functions like read and
write to perform I/O to a single, permanent file on local disk with no system
buffering. This driver is POSIX-compliant and is the default file driver for all systems.
"""
struct POSIX <: Driver end

function get_driver(fapl::Properties, ::Type{POSIX})
    POSIX()
end

function set_driver!(fapl::Properties, ::POSIX)
    HDF5.init!(fapl)
    API.h5p_set_fapl_sec2(fapl)
    DRIVERS[API.h5p_get_driver(fapl)] = POSIX
    return nothing
end

function __init__()
    # Initialize POSIX key in DRIVERS
    HDF5.FileAccessProperties() do fapl
        set_driver!(fapl, POSIX())
    end

    # Check whether the libhdf5 was compiled with parallel support.
    HDF5.HAS_PARALLEL[] = API._has_symbol(:H5Pset_fapl_mpio)
    HDF5.HAS_ROS3[] = API._has_symbol(:H5Pset_fapl_ros3)

    @static if !isdefined(Base, :get_extension)
        @require MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" include("../../ext/MPIExt.jl")
    end
end

# The docstring for `MPIO` basically belongs to the struct `MPIO` in
# ext/MPIExt/MPIExt.jl. However, we need to document it here to make it
# work smoothly both for Julia without package extensions (up to v1.8) and
# with package extensions (v1.9 and newer).
@doc """
    MPIO(comm::MPI.Comm, info::MPI.Info)
    MPIO(comm::MPI.Comm; kwargs....)

The parallel MPI file driver. This requires the use of
[MPI.jl](https://github.com/JuliaParallel/MPI.jl), and a custom HDF5 binary that has been
built with MPI support.

- `comm` is the communicator over which the file will be opened.
- `info`/`kwargs` are MPI-IO options, and are passed to `MPI_FILE_OPEN`.

# See also

- [`HDF5.has_parallel`](@ref)
- [Parallel HDF5](@ref)

# External links

- $(h5doc("H5P_SET_FAPL_MPIO"))
- [Parallel HDF5](https://portal.hdfgroup.org/display/HDF5/Parallel+HDF5)
"""
function MPIO end

include("ros3.jl")
end # module
