# Functions for ODE-Solver: Odex

"""Name for Loading odex solver (64bit integers)."""
const DL_ODEX = "odex"

"""Name for Loading odex solver (32bit integers)."""
const DL_ODEX_I32 = "odex_i32"

"""macro for import Odex solver."""
macro import_odex()
    return :(
        using ODEInterface: odex, odex_i32
    )
end

"""macro for import Odex dynamic lib names."""
macro import_DLodex()
    return :(
        using ODEInterface: DL_ODEX, DL_ODEX_I32
    )
end

"""macro for importing Odex help."""
macro import_odex_help()
    return :(
        using ODEInterface: help_odex_compile, help_odex_license
    )
end

"""
Type encapsulating all required data for Odex-Solver-Callbacks.

We have the typical calling stack:

     odex
         call_julia_output_fcn(  ... INIT ... )
             output_fcn ( ... INIT ...)
         ccall( ODEX_ ... )
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_HW1RHSCallback                      │  ⎬ cb. rhs
            │    rhs                                    │  ⎪
            └───────────────────────────────────────────┘  ⎭
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_odexSoloutCallback                  │  ⎪
            │    call_julia_output_fcn( ... STEP ...)   │  ⎪ cb. solout
            │        output_fcn ( ... STEP ...)         │  ⎬ with eval
            │            eval_sol_fcn                   │  ⎪
            │                ccall(CONTEX_ ... )        │  ⎪
            └───────────────────────────────────────────┘  ⎭
         call_julia_output_fcn(  ... DONE ... )
             output_fcn ( ... DONE ...)
"""
mutable struct OdexInternalCallInfos{
        FInt <: FortranInt,
        RHS_F, OUT_F,
    } <: ODEinternalCallInfos
    logio::IO                    # where to log
    loglevel::UInt64                # log level
    # RHS:
    rhs::RHS_F                 # right-hand-side
    rhs_mode::RHS_CALL_MODE         # how to call rhs
    rhs_lprefix::AbstractString        # saved log-prefix for rhs
    # SOLOUT & output function
    output_mode::OUTPUTFCN_MODE        # what mode for output function
    output_fcn::OUT_F                 # the output function to call
    output_data::Dict                  # extra_data for output_fcn
    out_lprefix::AbstractString        # saved log-prefix for solout
    eval_sol_fcn::Function              # eval_sol_fcn
    eval_lprefix::AbstractString        # saved log-prefix for eval_sol
    tOld::Float64               # tOld and
    tNew::Float64               # tNew and
    xNew::Vector{Float64}       # xNew of current solout interval
    cont_i::Vector{FInt}          # argument to contex
    cont_s::Vector{Float64}       # argument to contex
    cont_con::Ptr{Float64}          # saved pointers for contex
    cont_ncon::Ptr{FInt}             # saved pointers for contex
    cont_icomp::Ptr{FInt}             # saved pointers for contex
    cont_nd::Ptr{FInt}             # saved pointers for contex
end

"""
     mutable struct OdexArguments{FInt} <: AbstractArgumentsODESolver{FInt}

Stores Arguments for Odex solver.

FInt is the Integer type used for the fortran compilation.
"""
mutable struct OdexArguments{FInt <: FortranInt} <: AbstractArgumentsODESolver{FInt}
    N::Vector{FInt}      # Dimension
    FCN::Ptr{Cvoid}        # rhs callback
    t::Vector{Float64}   # start time (and current)
    tEnd::Vector{Float64}   # end time
    x::Vector{Float64}   # initial value (and current state)
    H::Vector{Float64}   # initial step size
    RTOL::Vector{Float64}   # relative tolerance
    ATOL::Vector{Float64}   # absolute tolerance
    ITOL::Vector{FInt}      # switch for RTOL, ATOL
    SOLOUT::Ptr{Cvoid}        # solout callback
    IOUT::Vector{FInt}      # switch for SOLOUT
    WORK::Vector{Float64}   # double working array
    LWORK::Vector{FInt}      # length of WORK
    IWORK::Vector{FInt}      # integer working array
    LIWORK::Vector{FInt}      # length of IWORK
    RPAR::Vector{Float64}   # add. double-array
    IPAR::Ref{OdexInternalCallInfos} # misuse IPAR
    IDID::Vector{FInt}      # Status code
    ## Allow uninitialized construction
    function OdexArguments{FInt}(dummy::FInt) where {FInt}
        return new{FInt}()
    end
end

"""
     function unsafe_odexSoloutCallback(
             nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
             x_::Ptr{Float64}, n_::Ptr{FInt}, con_::Ptr{Float64},
             ncon_::Ptr{FInt}, icomp_::Ptr{FInt}, nd_::Ptr{FInt},
             rpar_::Ptr{Float64}, cbi::CI,
             irtrn_::Ptr{FInt}) where {FInt<:FortranInt,
                                       CI<:OdexInternalCallInfos}

This is the solout given as callback to Fortran-odex.

The `unsafe` prefix in the name indicates that no validations are
performed on the `Ptr`-pointers.

This function saves the state information of the solver in
`OdexInternalCallInfos`, where they can be found by
the `eval_sol_fcn`, see `create_odex_eval_sol_fcn_closure`.

Then the user-supplied `output_fcn` is called (which in turn can use
`eval_sol_fcn`, to evaluate the solution at intermediate points).

The return value of the `output_fcn` is propagated to `ODEX_`.

For the typical calling sequence, see `OdexInternalCallInfos`.
"""
function unsafe_odexSoloutCallback(
        nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
        x_::Ptr{Float64}, n_::Ptr{FInt}, con_::Ptr{Float64},
        ncon_::Ptr{FInt}, icomp_::Ptr{FInt}, nd_::Ptr{FInt},
        rpar_::Ptr{Float64}, cbi::CI,
        irtrn_::Ptr{FInt}
    ) where {
        FInt <: FortranInt,
        CI <: OdexInternalCallInfos,
    }

    nr = unsafe_load(nr_); told = unsafe_load(told_); t = unsafe_load(t_)
    n = unsafe_load(n_)
    x = unsafe_wrap(Array, x_, (n,), own = false)
    irtrn = unsafe_wrap(Array, irtrn_, (1,), own = false)

    (lio, l, lprefix) = (cbi.logio, cbi.loglevel, cbi.out_lprefix)
    l_sol = l & LOG_SOLOUT > 0

    l_sol && println(
        lio, lprefix, "called with nr=", nr, " told=", told,
        " t=", t, " x=", x
    )

    cbi.tOld = told; cbi.tNew = t; cbi.xNew = x
    cbi.cont_con = con_; cbi.cont_ncon = ncon_; cbi.cont_icomp = icomp_
    cbi.cont_nd = nd_
    cbi.output_data["nr"] = nr

    ret = call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_STEP, told, t, x,
        cbi.eval_sol_fcn
    )
    if ret == OUTPUTFCN_RET_STOP
        irtrn[1] = -1
    elseif ret == OUTPUTFCN_RET_CONTINUE
        irtrn[1] = 0
    elseif ret == OUTPUTFCN_RET_CONTINUE_XCHANGED
        throw(
            FeatureNotSupported(
                string(
                    "Sorry; odex does not support ",
                    "to change the solution inside the output function."
                )
            )
        )
    else
        throw(InternalErrorODE(string("Unknown ret=", ret, " of output function")))
    end

    return nothing
end

"""
     function unsafe_odexSoloutCallback_c(cbi::CI,
             fint_flag::FInt) where {FInt,CI}
"""
function unsafe_odexSoloutCallback_c(
        cbi::CI,
        fint_flag::FInt
    ) where {FInt, CI}
    return @cfunction(
        unsafe_odexSoloutCallback, Cvoid, (
            Ptr{FInt},
            Ptr{Float64}, Ptr{Float64}, Ptr{Float64},
            Ptr{FInt}, Ptr{Float64}, Ptr{FInt},
            Ptr{FInt}, Ptr{FInt}, Ptr{Float64},
            Ref{CI}, Ptr{FInt},
        )
    )
end

"""
     function create_odex_eval_sol_fcn_closure(cbi::CI, d::FInt,
             method_contex::Ptr{Cvoid}) where {FInt<:FortranInt,
                                              CI<:OdexInternalCallInfos}

generates a eval_sol_fcn for odex.

Why is a closure needed? We need a function `eval_sol_fcn`
that calls `CONTEX_` (with `ccall`).
But `CONTEX_` needs the information for the current state. This
information were saved by `unsafe_odexSoloutCallback` in the
`OdexInternalCallInfos`. `eval_sol_fcn` needs to get this information.
Here comes `create_odex_eval_sol_fcn_closure` into play: this function
takes the call information and generates a `eval_sol_fcn` with this data.

Why doesn't `unsafe_odexSoloutCallback` generate a closure (then
the current state needs not to be saved in `OdexInternalCallInfos`)?
Because then every call to `unsafe_odexSoloutCallback` would have
generated a closure function. That's a lot of overhead: 1 closure function
for every solout call. With the strategy above, we have 1 closure function
per ODE-solver-call, i.e. 1 closure function per ODE.

For the typical calling sequence, see `OdexInternalCallInfos`.
"""
function create_odex_eval_sol_fcn_closure(
        cbi::CI, d::FInt,
        method_contex::Ptr{Cvoid}
    ) where {
        FInt <: FortranInt,
        CI <: OdexInternalCallInfos,
    }

    function eval_sol_fcn_closure(s::Float64)
        (lio, l, lprefix) = (cbi.logio, cbi.loglevel, cbi.eval_lprefix)
        l_eval = l & LOG_EVALSOL > 0

        l_eval && println(lio, lprefix, "called with s=", s)
        cbi.cont_s[1] = s
        result = Vector{Float64}(undef, d)
        if s == cbi.tNew
            result[:] = cbi.xNew
            l_eval && println(lio, lprefix, "not calling contex because s==tNew")
        else
            for k in 1:d
                cbi.cont_i[1] = k
                result[k] = ccall(
                    method_contex, Float64,
                    (
                        Ptr{FInt}, Ptr{Float64}, Ptr{Float64}, Ptr{FInt},
                        Ptr{FInt}, Ptr{FInt},
                    ),
                    cbi.cont_i, cbi.cont_s, cbi.cont_con, cbi.cont_ncon,
                    cbi.cont_icomp, cbi.cont_nd
                )
            end
        end

        l_eval && println(lio, lprefix, "contex returned ", result)
        return result
    end
    return eval_sol_fcn_closure
end

"""
     function odex(rhs, t0::Real, T::Real,
                   x0::Vector, opt::AbstractOptionsODE)
         -> (t,x,retcode,stats)

`retcode` can have the following values:

      1: computation successful
      2: computation. successful, but interrupted by output function
     -1: error

main call for using Fortran-odex solver. In `opt` the following
options are used:

    ╔═════════════════╤══════════════════════════════════════════╤═════════╗
    ║  Option OPT_…   │ Description                              │ Default ║
    ╠═════════════════╪══════════════════════════════════════════╪═════════╣
    ║ RTOL         &  │ relative and absolute error tolerances   │    1e-3 ║
    ║ ATOL            │ both scalars or both vectors with the    │    1e-6 ║
    ║                 │ length of length(x0)                     │         ║
    ║                 │ error(xₖ) ≤ OPT_RTOLₖ⋅|xₖ|+OPT_ATOLₖ     │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OUTPUTFCN       │ output function                          │ nothing ║
    ║                 │ see help_outputfcn                       │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OUTPUTMODE      │ OUTPUTFCN_NEVER:                         │   NEVER ║
    ║                 │   dont't call OPT_OUTPUTFCN              │         ║
    ║                 │ OUTPUTFCN_WODENSE                        │         ║
    ║                 │   call OPT_OUTPUTFCN, but without        │         ║
    ║                 │   possibility for dense output           │         ║
    ║                 │ OUTPUTFCN_DENSE                          │         ║
    ║                 │   call OPT_OUTPUTFCN with support for    │         ║
    ║                 │   dense output                           │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSTEPS        │ maximal number of allowed steps          │   10000 ║
    ║                 │ OPT_MAXSTEPS > 0                         │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ EPS             │ the rounding unit                        │ 2.3e-16 ║
    ║                 │ 1e-35 < OPT_EPS < 1.0                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSS           │ maximal step size                        │  T - t0 ║
    ║                 │ OPT_MAXSS ≠ 0                            │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ INITIALSS       │ initial step size guess                  │    1e-4 ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXEXCOLUMN     │ the maximum number of columns in         │       9 ║
    ║                 │ the extrapolation table                  │         ║
    ║                 │ OPT_MAXEXCOLUMN ≥ 3                      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ STEPSIZESEQUENCE│ switch for the step size sequence        │       4 ║
    ║                 │ 1: 2, 4,  6,  8, 10, 12, 14, 16, …       │ if      ║
    ║                 │ 2: 2, 4,  8, 12, 16, 20, 24, 28, …       │ OUTPUT- ║
    ║                 │ 3: 2, 4,  6,  8, 12, 16, 24, 32, …       │ MODE == ║
    ║                 │ 4: 2, 6, 10, 14, 18, 22, 26, 30, …       │ DENSE;  ║
    ║                 │ 5: 4, 8, 12, 16, 20, 24, 28, 32, …       │ other-  ║
    ║                 │ 1 ≤ OPT_STEPSIZESEQUENCE ≤ 5             │ wise  1 ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSTABCHECKS   │ how many times is the stability check    │       1 ║
    ║                 │ activated at most in one line of the     │         ║
    ║                 │ extrapolation table                      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSTABCHECKLINE│ stability check is only activated in     │       1 ║
    ║                 │ the lines 1 to MAXMAXSTABCHECKLINE of    │         ║
    ║                 │ the extrapolation table                  │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ DENSEOUTPUTWOEE │ boolean flag: suppress error estimator   │   false ║
    ║                 │ in dense output                          │         ║
    ║                 │ true is only possible, if                │         ║
    ║                 │      OUTPUTMODE == DENSE                 │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ INTERPOLDEGREE  │ determines the degree of interpolation   │       4 ║
    ║                 │ formula:                                 │         ║
    ║                 │ μ = 2*κ - INTERPOLDEGREE + 1             │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ SSREDUCTION     │ step size is reduced by factor if the    │     0.5 ║
    ║                 │ stability check is negative              │         ║
    ║                 │ OPT_EPS < OPT_SSREDUCTION < 1            │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ SSSELECTPAR1 &  │ parameters for step size selection       │    0.02 ║
    ║ SSSELECTPAR2    │ the new step size for the k-th diagonal  │    4.00 ║
    ║                 │ entry is chosen subject to               │         ║
    ║                 │ FMIN/SSSELECTPAR2 ≤ hnewₖ/hold ≤ 1/FMIN  │         ║
    ║                 │ with FMIN = SSSELECTPAR1^(1/(2*k-1))     │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ ORDERDECFRAC &  │ parameters for the order selection       │     0.8 ║
    ║ ORDERINCFRAC    │ decrease order if                        │     0.9 ║
    ║                 │         W(k-1) ≤   W(k)*ORDERDECFRAC     │         ║
    ║                 │ increase order if                        │         ║
    ║                 │         W(k)   ≤ W(k-1)*ORDERINCFRAC     │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OPT_RHO      &  │ safety factors for step control algorithm│    0.94 ║
    ║ OPT_RHO2        │ hnew=h*RHO*(RHO2*TOL/ERR)^(1/(k-1) )     │    0.65 ║
    ╚═════════════════╧══════════════════════════════════════════╧═════════╝

"""
function odex(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return odex_impl(rhs, t0, T, x0, opt, OdexArguments{Int64}(Int64(0)))
end

"""
odex with 32bit integers, see odex.
"""
function odex_i32(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )

    return odex_impl(rhs, t0, T, x0, opt, OdexArguments{Int32}(Int32(0)))
end


"""
     function odex_impl(rhs,
             t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
             args::OdexArguments{FInt}) where {FInt<:FortranInt}

implementation of odex for FInt.
"""
function odex_impl(
        rhs,
        t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
        args::OdexArguments{FInt}
    ) where {FInt <: FortranInt}

    (lio, l, l_g, l_solver, lprefix) = solver_start("odex", rhs, t0, T, x0, opt)

    (method_odex, method_contex) = getAllMethodPtrs(
        (FInt == Int64) ? DL_ODEX : DL_ODEX_I32
    )

    (d, nrdense, scalarFlag, rhs_mode, output_mode, output_fcn) =
        solver_extract_commonOpt(t0, T, x0, opt, args)

    args.ITOL = [scalarFlag ? 0 : 1]
    args.IOUT = [
        FInt(
            output_mode == OUTPUTFCN_NEVER ? 0 :
                (output_mode == OUTPUTFCN_DENSE ? 2 : 1)
        ),
    ]

    OPT = nothing; KM = FInt(0)
    try
        OPT = OPT_MAXEXCOLUMN; KM = convert(FInt, getOption(opt, OPT, 9))
        @assert KM ≥ 3
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end

    # WORK memory
    args.LWORK = [d * (KM + 5) + 5 * KM + 20 + (2 * KM * (KM + 2) + 5) * nrdense]
    args.WORK = zeros(Float64, args.LWORK[1])

    # IWORK memory
    args.LIWORK = [2 * KM + 21 + nrdense]
    args.IWORK = zeros(FInt, args.LIWORK[1])

    OPT = nothing
    try
        # fill IWORK
        OPT = OPT_MAXSTEPS; args.IWORK[1] = convert(FInt, getOption(opt, OPT, 10000))
        @assert 0 < args.IWORK[1]
        args.IWORK[2] = KM

        OPT = OPT_STEPSIZESEQUENCE
        args.IWORK[3] = convert(
            FInt, getOption(
                opt, OPT,
                output_mode == OUTPUTFCN_DENSE ? 4 : 1
            )
        )
        @assert 1 ≤ args.IWORK[3] ≤ 5

        OPT = OPT_MAXSTABCHECKS; args.IWORK[4] = convert(FInt, getOption(opt, OPT, 1))

        OPT = OPT_MAXSTABCHECKLINE
        args.IWORK[5] = convert(FInt, getOption(opt, OPT, 1))

        OPT = OPT_DENSEOUTPUTWOEE
        supp_flag = convert(Bool, getOption(opt, OPT, false))
        args.IWORK[6] = FInt(supp_flag ? 1 : 0)
        @assert !supp_flag || (supp_flag && output_mode == OUTPUTFCN_DENSE)

        OPT = OPT_INTERPOLDEGREE
        args.IWORK[7] = convert(FInt, getOption(opt, OPT, 4))
        @assert 1 ≤ args.IWORK[7] ≤ 6

        args.IWORK[8] = nrdense

        # fill WORK
        OPT = OPT_EPS; args.WORK[1] = convert(Float64, getOption(opt, OPT, 2.3e-16))
        @assert 1.0e-35 < args.WORK[1] < 1.0

        OPT = OPT_MAXSS; args.WORK[2] = convert(Float64, getOption(opt, OPT, T - t0))
        @assert 0 ≠ args.WORK[2]

        OPT = OPT_SSREDUCTION; args.WORK[3] = convert(Float64, getOption(opt, OPT, 0.5))
        @assert args.WORK[1] < args.WORK[3] < 1.0

        OPT = OPT_SSSELECTPAR1
        args.WORK[4] = convert(Float64, getOption(opt, OPT, 0.02))
        @assert 0 < args.WORK[4]

        OPT = OPT_SSSELECTPAR2
        args.WORK[5] = convert(Float64, getOption(opt, OPT, 4.0))
        @assert 0 < args.WORK[5]

        OPT = OPT_ORDERDECFRAC
        args.WORK[6] = convert(Float64, getOption(opt, OPT, 0.8))
        @assert 0 < args.WORK[6]

        OPT = OPT_ORDERINCFRAC
        args.WORK[7] = convert(Float64, getOption(opt, OPT, 0.9))
        @assert 0 < args.WORK[7]

        OPT = OPT_RHO2
        args.WORK[8] = convert(Float64, getOption(opt, OPT, 0.65))
        @assert 0 < args.WORK[8]

        OPT = OPT_RHO
        args.WORK[9] = convert(Float64, getOption(opt, OPT, 0.94))
        @assert 0 < args.WORK[9]

        # H
        OPT = OPT_INITIALSS
        args.H = [convert(Float64, getOption(opt, OPT, 1.0e-4))]
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end

    args.RPAR = zeros(Float64, 0)
    args.IDID = zeros(FInt, 1)
    rhs_lprefix = "unsafe_HW1RHSCallback: "
    out_lprefix = "unsafe_odexSoloutCallback: "
    eval_lprefix = "eval_sol_fcn_closure: "

    cbi = OdexInternalCallInfos(
        lio, l, rhs, rhs_mode, rhs_lprefix,
        output_mode, output_fcn,
        Dict(), out_lprefix, eval_sol_fcn_noeval, eval_lprefix,
        NaN, NaN, Vector{Float64}(),
        Vector{FInt}(undef, 1), Vector{Float64}(undef, 1),
        Ptr{Float64}(C_NULL), Ptr{FInt}(C_NULL),
        Ptr{FInt}(C_NULL), Ptr{FInt}(C_NULL)
    )

    if output_mode == OUTPUTFCN_DENSE
        cbi.eval_sol_fcn = create_odex_eval_sol_fcn_closure(cbi, d, method_contex)
    end

    args.FCN = unsafe_HW1RHSCallback_c(cbi, FInt(0))
    args.SOLOUT = output_mode ≠ OUTPUTFCN_NEVER ?
        unsafe_odexSoloutCallback_c(cbi, FInt(0)) :
        @cfunction(dummy_func, Cvoid, ())
    args.IPAR = cbi

    output_mode ≠ OUTPUTFCN_NEVER &&
        call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_INIT,
        args.t[1], args.tEnd[1], args.x, eval_sol_fcn_init
    ) # ignore result

    if l_solver
        println(lio, lprefix, "call Fortran-odex $method_odex with")
        dump(lio, args)
    end

    ccall(
        method_odex, Cvoid,
        (
            Ptr{FInt}, Ptr{Cvoid},                   # N=d, Rightsidefunc
            Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, # t, x, tEnd
            Ptr{Float64},                             # h
            Ptr{Float64}, Ptr{Float64}, Ptr{FInt},    # RTOL, ATOL, ITOL
            Ptr{Cvoid}, Ptr{FInt},                    # Soloutfunc, IOUT
            Ptr{Float64}, Ptr{FInt},                  # WORK, LWORK
            Ptr{FInt}, Ptr{FInt},                     # IWORK, LIWORK
            Ptr{Float64}, Ref{OdexInternalCallInfos}, # RPAR, IPAR,
            Ptr{FInt},                                # IDID
        ),
        args.N, args.FCN,
        args.t, args.x, args.tEnd,
        args.H,
        args.RTOL, args.ATOL, args.ITOL,
        args.SOLOUT, args.IOUT,
        args.WORK, args.LWORK,
        args.IWORK, args.LIWORK,
        args.RPAR, args.IPAR, args.IDID,
    )

    if l_solver
        println(lio, lprefix, "Fortran-odex $method_odex returned")
        dump(lio, args)
    end

    output_mode ≠ OUTPUTFCN_NEVER &&
        call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_DONE,
        args.t[1], args.tEnd[1], args.x, eval_sol_fcn_done
    ) # ignore result

    l_g && println(lio, lprefix, string("done IDID=", args.IDID[1]))
    stats = Dict{AbstractString, Any}(
        "step_predict" => args.H[1],
        "no_rhs_calls" => args.IWORK[17],
        "no_steps" => args.IWORK[18],
        "no_steps_accepted" => args.IWORK[19],
        "no_steps_rejected" => args.IWORK[20]
    )
    return (args.t[1], args.x, args.IDID[1], stats)
end

"""
## Compile ODEX

The julia ODEInterface tries to compile and link the solvers
automatically at the build-time of this module. The following
calls need only be done, if one uses a different compiler and/or if
one wants to change/add some compiler options.

The Fortran source code can be found at:

     http://www.unige.ch/~hairer/software.html

See `help_odex_license` for the license information.

### Using `gfortran` and 64bit integers (Linux and Mac)

Here is an example how to compile ODEX with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o odex.o odex.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o odex.so odex.o
     gfortran -shared -fPIC -o odex.dylib odex.o

### Using `gfortran` and 64bit integers (Windows)

Here is an example how to compile ODEX with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o odex.o odex.f

In order to get create a shared library (from the object file above) use

     gfortran -shared -o odex.dll odex.o

### Using `gfortran` and 32bit integers (Linux and Mac)

Here is an example how to compile ODEX with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c -fPIC
              -fdefault-real-8 -fdefault-double-8
              -o odex_i32.o   odex.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o odex_i32.so odex_i32.o
     gfortran -shared -fPIC -o odex_i32.dylib odex_i32.o

### Using `gfortran` and 32bit integers (Windows)

Here is an example how to compile ODEX with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c
              -fdefault-real-8 -fdefault-double-8
              -o odex_i32.o   odex.f

In order to get create a shared library (from the object file above) use:

     gfortran -shared -o odex_i32.dll odex_i32.o

"""
function help_odex_compile()
    return Docs.doc(help_odex_compile)
end

function help_odex_license()
    return Docs.doc(help_odex_license)
end

@doc(@doc(hw_license), help_odex_license)

# Add information about solver in global solverInfo-array.
push!(
    solverInfo,
    SolverInfo(
        "odex",
        "GBS Extrapolation-Algorithm based on the explicit midpoint rule",
        tuple(
            :OPT_RTOL, :OPT_ATOL,
            :OPT_OUTPUTMODE, :OPT_OUTPUTFCN,
            :OPT_MAXSTEPS, :OPT_EPS, :OPT_MAXS, :OPT_INITIALSS,
            :OPT_MAXEXCOLUMN, :OPT_STEPSIZESEQUENCE, :OPT_MAXSTABCHECKS,
            :OPT_MAXSTABCHECKLINE, :OPT_DENSEOUTPUTWOEE, :OPT_INTERPOLDEGREE,
            :OPT_SSREDUCTION, :OPT_SSSELECTPAR1, :OPT_SSSELECTPAR2,
            :OPT_ORDERDECFRAC, :OPT_ORDERINCFRAC, :OPT_RHO, :OPT_RHO2,
        ),
        tuple(
            SolverVariant(
                "odex_i64",
                "Odex with 64bit integers",
                DL_ODEX,
                tuple("odex", "contex")
            ),
            SolverVariant(
                "odex_i32",
                "Odex with 32bit integers",
                DL_ODEX_I32,
                tuple("odex", "contex")
            ),
        ),
        help_odex_compile,
        help_odex_license,
    )
)


# vim:syn=julia:cc=79:fdm=indent:
