# Functions for ODE-Solver: Seulex

"""Name for Loading seulex solver (64bit integers)."""
const DL_SEULEX = "seulex"

"""Name for Loading seulex solver (32bit integers)."""
const DL_SEULEX_I32 = "seulex_i32"

"""macro for import seulex solver."""
macro import_seulex()
    return :(
        using ODEInterface: seulex, seulex_i32
    )
end

"""macro for import seulex dynamic lib names."""
macro import_DLseulex()
    return :(
        using ODEInterface: DL_SEULEX, DL_SEULEX_I32
    )
end

"""macro for importing Seulex help."""
macro import_seulex_help()
    return :(
        using ODEInterface: help_seulex_compile, help_seulex_license
    )
end

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

We have the typical calling stack:

     seulex
         call_julia_output_fcn(  ... INIT ... )
             output_fcn ( ... INIT ...)
         ccall( SEULEX_  ... )
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_HW2RHSCallback                      │  ⎬ cb. rhs
            │    rhs                                    │  ⎪
            └───────────────────────────────────────────┘  ⎭
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_seulexSoloutCallback                │  ⎪
            │    call_julia_output_fcn( ... STEP ...)   │  ⎪ cb. solout
            │        output_fcn ( ... STEP ...)         │  ⎬ with eval
            │            eval_sol_fcn                   │  ⎪
            │                ccall(CONTEX_ ... )        │  ⎪
            └───────────────────────────────────────────┘  ⎭
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_HW1JacCallback:                     │  ⎬ cb. jacobian
            │    call_julia_jac_fcn(             )      │  ⎪
            └───────────────────────────────────────────┘  ⎭
         call_julia_output_fcn(  ... DONE ... )
             output_fcn ( ... DONE ...)
"""
mutable struct SeulexInternalCallInfos{
        FInt <: FortranInt, RHS_F,
        OUT_F, JAC_F,
    } <: ODEinternalCallInfos
    logio::IO                    # where to log
    loglevel::UInt64                # log level
    # special structure
    M1::FInt
    M2::FInt
    # 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_rc::Ptr{Float64}          # saved pointers for contex
    cont_lrc::Ptr{FInt}             # saved pointers for contex
    cont_ic::Ptr{FInt}             # saved pointers for contex
    cont_lic::Ptr{FInt}             # saved pointers for contex
    # Massmatrix
    massmatrix::AbstractArray{Float64} # saved mass matrix
    # Jacobimatrix
    jacobimatrix::JAC_F                 # function for jacobi matrix
    jacobibandstruct                      # (l,u) bandstructure or nothing
    jac_lprefix::AbstractString        # saved log-prefix for jac
end

"""
     mutable struct SeulexArguments{FInt<:FortranInt} <:
              AbstractArgumentsODESolver{FInt}

Stores Arguments for Seulex solver.

FInt is the Integer type used for the fortran compilation.
"""
mutable struct SeulexArguments{FInt <: FortranInt} <: AbstractArgumentsODESolver{FInt}
    N::Vector{FInt}      # Dimension
    FCN::Ptr{Cvoid}        # rhs callback
    IFCN::Vector{FInt}      # autonomous (0) or not (1)
    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
    JAC::Ptr{Cvoid}        # jacobian callback
    IJAC::Vector{FInt}      # jacobian given as callback?
    MLJAC::Vector{FInt}      # lower bandwidth of jacobian
    MUJAC::Vector{FInt}      # upper bandwidth of jacobian
    MAS::Ptr{Cvoid}
    IMAS::Vector{FInt}      # mass matrix given as callback?
    MLMAS::Vector{FInt}      # lower bandwidth of mass matrix
    MUMAS::Vector{FInt}      # upper bandwidth of mass matrix
    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{SeulexInternalCallInfos} # misuse IPAR
    IDID::Vector{FInt}      # Status code
    ## Allow uninitialized construction
    function SeulexArguments{FInt}(dummy::FInt) where {FInt}
        return new{FInt}()
    end
end

"""
     function unsafe_seulexSoloutCallback(
             nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
             x_::Ptr{Float64}, rc_::Ptr{Float64}, lrc_::Ptr{FInt},
             ic_::Ptr{FInt}, lic_::Ptr{FInt}, n_::Ptr{FInt},
             rpar_::Ptr{Float64}, cbi::CI,
             irtrn_::Ptr{FInt}) where {FInt<:FortranInt,
                                       CI<:SeulexInternalCallInfos}

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

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
`SeulexInternalCallInfos`, where they can be found by
the `eval_sol_fcn`, see `create_seulex_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 `SEULEX_`.

For the typical calling sequence, see `SeulexInternalCallInfos`.
"""
function unsafe_seulexSoloutCallback(
        nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
        x_::Ptr{Float64}, rc_::Ptr{Float64}, lrc_::Ptr{FInt},
        ic_::Ptr{FInt}, lic_::Ptr{FInt}, n_::Ptr{FInt},
        rpar_::Ptr{Float64}, cbi::CI,
        irtrn_::Ptr{FInt}
    ) where {
        FInt <: FortranInt,
        CI <: SeulexInternalCallInfos,
    }

    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_rc = rc_; cbi.cont_lrc = lrc_
    cbi.cont_ic = ic_; cbi.cont_lic = lic_
    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; seulex 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_seulexSoloutCallback_c(cbi::CI,
             fint_flag::FInt) where {FInt,CI}
"""
function unsafe_seulexSoloutCallback_c(
        cbi::CI,
        fint_flag::FInt
    ) where {FInt, CI}
    return @cfunction(
        unsafe_seulexSoloutCallback, Cvoid, (
            Ptr{FInt},
            Ptr{Float64}, Ptr{Float64}, Ptr{Float64},
            Ptr{Float64}, Ptr{FInt}, Ptr{FInt}, Ptr{FInt},
            Ptr{FInt}, Ptr{Float64}, Ref{CI}, Ptr{FInt},
        )
    )
end

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

generates a eval_sol_fcn for seulex.

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_seulexSoloutCallback` in the
`SeulexInternalCallInfos`. `eval_sol_fcn` needs to get this information.
Here comes `create_seulex_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_seulexSoloutCallback` generate a closure (then
the current state needs not to be saved in `SeulexInternalCallInfos`)?
Because then every call to `unsafe_seulexSoloutCallback` 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 `SeulexInternalCallInfos`.
"""
function create_seulex_eval_sol_fcn_closure(
        cbi::CI, d::FInt,
        method_contex::Ptr{Cvoid}
    ) where {
        FInt <: FortranInt,
        CI <: SeulexInternalCallInfos,
    }

    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_rc, cbi.cont_lrc,
                    cbi.cont_ic, cbi.cont_lic
                )
            end
        end

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

"""
     function seulex(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: computation unsuccessful


main call for using Fortran seulex solver.

This solver support problems with special structure, see
`help_specialstructure`.

In `opt` the following options are used:

    ╔═════════════════╤══════════════════════════════════════════╤═════════╗
    ║  Option OPT_…   │ Description                              │ Default ║
    ╠═════════════════╪══════════════════════════════════════════╪═════════╣
    ║ RHSAUTONOMOUS   │ Flag, if right-hand side is autonomous.  │   false ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ M1 & M2         │ parameter for special structure, see     │       0 ║
    ║                 │ above                                    │      M1 ║
    ║                 │ M1, M2 ≥ 0                               │         ║
    ║                 │ M1 +M2 ≤ length(x0)                      │         ║
    ║                 │ (M1==M2==0) || (M1≠0≠M2)                 │         ║
    ║                 │ M1 % M2 == 0 or M1==0                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ 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                           │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ LAMBDADENSE     │ parameter λ of dense output              │       0 ║
    ║                 │ OPT_LAMBDADENSE ∈ {0,1}                  │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ EPS             │ the rounding unit                        │   1e-16 ║
    ║                 │ 0 < OPT_EPS < 1.0                        │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ TRANSJTOH       │ The solver transforms the jacobian       │   false ║
    ║                 │ matrix to Hessenberg form.               │         ║
    ║                 │ This option is not supported if the      │         ║
    ║                 │ system is "implicit" (i.e. a mass matrix │         ║
    ║                 │ is given) or if jacobian is banded.      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSTEPS        │ maximal number of allowed steps          │  100000 ║
    ║                 │ OPT_MAXSTEPS > 0                         │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSS           │ maximal step size                        │  T - t0 ║
    ║                 │ OPT_MAXSS ≠ 0                            │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ INITIALSS       │ initial step size guess                  │    1e-6 ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXEXCOLUMN     │ the maximum number of columns in         │      12 ║
    ║                 │ the extrapolation table                  │         ║
    ║                 │ OPT_MAXEXCOLUMN ≥ 3                      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ STEPSIZESEQUENCE│ switch for the step size sequence        │       2 ║
    ║                 │ 1: 1, 2, 3, 6, 8, 12, 16, 24, 32, 48, …  │         ║
    ║                 │ 2: 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, …  │         ║
    ║                 │ 3: 1, 2, 3, 4, 5,  6,  7,  8,  9, 10, …  │         ║
    ║                 │ 4: 2, 3, 4, 5, 6,  7,  8,  9, 10, 11, …  │         ║
    ║                 │ 1 ≤ OPT_STEPSIZESEQUENCE ≤ 4             │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ SSSELECTPAR1 &  │ parameters for step size selection       │     0.1 ║
    ║ SSSELECTPAR2    │ the new step size for the k-th diagonal  │     4.0 ║
    ║                 │ entry is chosen subject to               │         ║
    ║                 │ FMIN/SSSELECTPAR2 ≤ hnewₖ/hold ≤ 1/FMIN  │         ║
    ║                 │ with FMIN = SSSELECTPAR1^(1/(k-1))       │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ ORDERDECFRAC &  │ parameters for the order selection       │     0.7 ║
    ║ ORDERINCFRAC    │ decrease order if                        │     0.9 ║
    ║                 │         W(k-1) ≤   W(k)*ORDERDECFRAC     │         ║
    ║                 │ increase order if                        │         ║
    ║                 │         W(k)   ≤ W(k-1)*ORDERINCFRAC     │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACRECOMPFACTOR │ decides whether the jacobian should be   │ min(    ║
    ║                 │ recomputed.                              │   1e-4, ║
    ║                 │ small (≈ 0.001): recompute often         │ RTOL[1])║
    ║                 │ large (≈ 0.1): recompute rarely          │         ║
    ║                 │ i.e. this number represents how costly   │         ║
    ║                 │ Jacobia evaluations are.                 │         ║
    ║                 │ OPT_JACRECOMPFACTOR ≠ 0                  │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OPT_RHO      &  │ safety factors for step control algorithm│    0.93 ║
    ║ OPT_RHO2        │ hnew=h*RHO*(RHO2*TOL/ERR)^(1/(k-1) )     │    0.80 ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MASSMATRIX      │ the mass matrix of the problem. If not   │ nothing ║
    ║                 │ given (nothing) then the identity matrix  │         ║
    ║                 │ is used.                                 │         ║
    ║                 │ The size has to be (d-M1)×(d-M1).        │         ║
    ║                 │ It can be an full matrix or a banded     │         ║
    ║                 │ matrix (BandedMatrix).                   │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACOBIMATRIX    │ A function providing the Jacobian for    │ nothing ║
    ║                 │ ∂f/∂x or nothing. For nothing the solver │         ║
    ║                 │ uses finite differences to calculate the │         ║
    ║                 │ Jacobian.                                │         ║
    ║                 │ The function has to be of the form:      │         ║
    ║                 │   function (t,x,J) -> nothing       (A)  │         ║
    ║                 │ or for M1>0 & JACOBIBANDSTRUCT ≠ nothing │         ║
    ║                 │   function (t,x,J1,…,JK) -> nothing (B)  │         ║
    ║                 │ with K = 1+M1/M2 and (M1+M2==d)          │         ║
    ║                 │ see help_specialstructure                │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACOBIBANDSTRUCT│ A tuple (l,u) describing the banded      │ nothing ║
    ║                 │ structure of the Jacobian or nothing if  │         ║
    ║                 │ the Jacobian is full.                    │         ║
    ║                 │ see help_specialstructure                │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ WORKFORRHS      │ estimated works (complexity) for a call  │     1.0 ║
    ║ WORKFORJAC      │ to                                       │     5.0 ║
    ║ WORKFORDEC      │ WORKFORRHS: right-hand side f            │     1.0 ║
    ║ WORKFORSOL      │ WORKFORJAC: JACOBIMATRIX                 │     1.0 ║
    ║                 │ WORKFORDEC: LU-decomposition             │         ║
    ║                 │ WORKFORSOL: Forward- and Backward subst. │         ║
    ╚═════════════════╧══════════════════════════════════════════╧═════════╝
"""
function seulex(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return seulex_impl(rhs, t0, T, x0, opt, SeulexArguments{Int64}(Int64(0)))
end

"""
seulex with 32bit integers, see seulex.
"""
function seulex_i32(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return seulex_impl(rhs, t0, T, x0, opt, SeulexArguments{Int32}(Int32(0)))
end

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

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

    (method_seulex, method_contex) = getAllMethodPtrs(
        (FInt == Int64) ? DL_SEULEX : DL_SEULEX_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)
        ),
    ]

    (M1, M2, NM1) = extractSpecialStructureOpt(d, opt)
    massmatrix = extractMassMatrix(M1, M2, NM1, args, opt)

    (jacobimatrix, jacobibandstruct, jac_lprefix) =
        extractJacobiOpt(d, M1, M2, NM1, args, opt)

    flag_implct = args.IMAS[1] ≠ 0
    flag_jband = args.MLJAC[1] < NM1

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

    # WORK memory
    ljac = flag_jband ? 1 + args.MLJAC[1] + args.MUJAC[1] : NM1
    lmas = (!flag_implct) ? 0 :
        (args.MLMAS[1] == NM1) ? NM1 : 1 + args.MLMAS[1] + args.MUMAS[1]
    le = flag_jband ? 1 + 2 * args.MLJAC[1] + args.MUJAC[1] : NM1
    KM2 = 2 + KM * ceil(FInt, (KM + 3) / 2)

    args.LWORK = [
        (M1 == 0) ? d * (ljac + lmas + le + KM + 8) + 4 * KM + 20 + KM2 * nrdense :
            d * (ljac + KM + 8) + NM1 * (lmas + le) + 4 * KM + 20 + KM2 * nrdense,
    ]
    args.WORK = zeros(Float64, args.LWORK[1])

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

    try
        OPT = OPT_RHSAUTONOMOUS
        rhsautonomous = convert(Bool, getOption(opt, OPT, false))
        args.IFCN = [rhsautonomous ? 0 : 1]

        OPT = OPT_TRANSJTOH
        transjtoh = convert(Bool, getOption(opt, OPT, false))
        @assert (!transjtoh) ||
            ((!flag_jband) && (!flag_implct))  string(
            "Does not work, if the jacobian is banded or if the system ",
            " has a mass matrix (≠Id)."
        )
        args.IWORK[1] = transjtoh ? 1 : 0

        OPT = OPT_MAXSTEPS; args.IWORK[2] = convert(FInt, getOption(opt, OPT, 100000))
        @assert 0 < args.IWORK[2]

        args.IWORK[3] = KM

        OPT = OPT_STEPSIZESEQUENCE
        args.IWORK[4] = convert(FInt, getOption(opt, OPT, 2))
        @assert 1 ≤ args.IWORK[4] ≤ 4

        OPT = OPT_LAMBDADENSE
        args.IWORK[5] = convert(FInt, getOption(opt, OPT, 0))
        @assert args.IWORK[5] ∈ (0, 1)

        args.IWORK[6] = nrdense

        for k in 1:nrdense
            args.IWORK[20 + k] = k
        end

        args.IWORK[9] = M1
        args.IWORK[10] = M2

        OPT = OPT_EPS; args.WORK[1] = convert(Float64, getOption(opt, OPT, 1.0e-16))
        @assert 0 < 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_JACRECOMPFACTOR
        args.WORK[3] = convert(Float64, getOption(opt, OPT, min(1.0e-4, args.RTOL[1])))
        @assert !(args.WORK[3] == 0)

        OPT = OPT_SSSELECTPAR1
        args.WORK[4] = convert(Float64, getOption(opt, OPT, 0.1))
        @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.7))
        @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.8))
        @assert 0 < args.WORK[8]

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

        OPT = OPT_WORKFORRHS
        args.WORK[10] = convert(Float64, getOption(opt, OPT, 1.0))
        @assert 0 < args.WORK[10]

        OPT = OPT_WORKFORJAC
        args.WORK[11] = convert(Float64, getOption(opt, OPT, 5.0))
        @assert 0 < args.WORK[11]

        OPT = OPT_WORKFORDEC
        args.WORK[12] = convert(Float64, getOption(opt, OPT, 1.0))
        @assert 0 < args.WORK[12]

        OPT = OPT_WORKFORSOL
        args.WORK[13] = convert(Float64, getOption(opt, OPT, 1.0))
        @assert 0 < args.WORK[13]

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

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

    cbi = SeulexInternalCallInfos(
        lio, l, M1, M2, 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),
        massmatrix === nothing ? zeros(0, 0) : massmatrix,
        jacobimatrix === nothing ? dummy_func : jacobimatrix,
        jacobibandstruct, jac_lprefix
    )

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

    args.FCN = unsafe_HW2RHSCallback_c(cbi, FInt(0))
    args.SOLOUT = output_mode ≠ OUTPUTFCN_NEVER ?
        unsafe_seulexSoloutCallback_c(cbi, FInt(0)) :
        @cfunction(dummy_func, Cvoid, ())
    args.IPAR = cbi
    args.MAS = unsafe_HW1MassCallback_c(cbi, FInt(0))
    args.JAC = unsafe_HW1JacCallback_c(cbi, FInt(0))

    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-seulex $method_seulex with")
        dump(lio, args)
    end

    ccall(
        method_seulex, Cvoid,
        (
            Ptr{FInt}, Ptr{Cvoid}, Ptr{FInt},          # N=d, Rightsidefunc, IFCN
            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}, Ptr{FInt}, Ptr{FInt}, # JAC, IJAC, MLJAC, MUJAC
            Ptr{Cvoid}, Ptr{FInt}, Ptr{FInt}, Ptr{FInt}, # MAS, IMAS, MLMAS, MUMAS
            Ptr{Cvoid}, Ptr{FInt},                      # Soloutfunc, IOUT
            Ptr{Float64}, Ptr{FInt},                    # WORK, LWORK
            Ptr{FInt}, Ptr{FInt},                       # IWORK, LIWORK
            Ptr{Float64}, Ref{SeulexInternalCallInfos}, # RPAR, IPAR
            Ptr{FInt},                                  # IDID
        ),
        args.N, args.FCN, args.IFCN,
        args.t, args.x, args.tEnd,
        args.H,
        args.RTOL, args.ATOL, args.ITOL,
        args.JAC, args.IJAC, args.MLJAC, args.MUJAC,
        args.MAS, args.IMAS, args.MLMAS, args.MUMAS,
        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-seulex $method_seulex 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[14],
        "no_jac_calls" => args.IWORK[15],
        "no_steps" => args.IWORK[16],
        "no_steps_accepted" => args.IWORK[17],
        "no_steps_rejected" => args.IWORK[18],
        "no_lu_decomp" => args.IWORK[19],
        "no_fw_bw_subst" => args.IWORK[20],
    )
    return (args.t[1], args.x, args.IDID[1], stats)
end

"""
## Compile SEULEX

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_seulex_license` for the license information.

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

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

     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o seulex.o seulex.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 seulex.so
              seulex.o dc_lapack.o lapack.o lapackc.o
     gfortran -shared -fPIC -o seulex.dylib
              seulex.o dc_lapack.o lapack.o lapackc.o

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

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

     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o seulex.o seulex.f

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

     gfortran -shared -o seulex.so
              seulex.o dc_lapack.o lapack.o lapackc.o

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

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

     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o seulex_i32.o seulex.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 seulex_i32.so
               seulex_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o
     gfortran -shared -fPIC -o seulex_i32.dylib
               seulex_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

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

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

     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o seulex_i32.o seulex.f

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

     gfortran -shared -o seulex_i32.dll
               seulex_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

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

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

@doc(@doc(hw_license), help_seulex_license)

# Add information about solvers in global solverInfo-array.
push!(
    solverInfo,
    SolverInfo(
        "seulex",
        "Extrapolation method based on the linearly implicit Eueler method",
        tuple(
            :OPT_RTOL, :OPT_ATOL,
            :OPT_OUTPUTMODE, :OPT_OUTPUTFCN,
            :OPT_M1, :OPT_M2,
            :OPT_RHSAUTONOMOUS,
            :OPT_MASSMATRIX,
            :OPT_JACOBIMATRIX, :OPT_JACOBIBANDSTRUCT,
            :OPT_MAXEXCOLUMN,
            :OPT_TRANSJTOH, :OPT_MAXSTEPS, :OPT_STEPSIZESEQUENCE,
            :OPT_LAMBDADENSE, :OPT_EPS, :OPT_MAXSS, :OPT_JACRECOMPFACTOR,
            :OPT_SSSELECTPAR1, :OPT_SSSELECTPAR2,
            :OPT_ORDERDECFRAC, :OPT_ORDERINCFRAC,
            :OPT_RHO2, :OPT_RHO,
            :OPT_INITIALSS,
            :OPT_WORKFORRHS, :OPT_WORKFORJAC, :OPT_WORKFORDEC,
            :OPT_WORKFORSOL,
        ),
        tuple(
            SolverVariant(
                "seulex_i64",
                "Seulex with 64bit integers",
                DL_SEULEX,
                tuple("seulex", "contex")
            ),
            SolverVariant(
                "seulex_i32",
                "Seulex with 32bit integers",
                DL_SEULEX_I32,
                tuple("seulex", "contex")
            ),
        ),
        help_seulex_compile,
        help_seulex_license,
    )
)

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