text:
# imports
using FMI
using FMIFlux
using FMIZoo
using Flux
using DifferentialEquations: Tsit5
import Plots

# set seed
import Random
Random.seed!(1234);

tStart = 0.0
tStep = 0.01
tStop = 4.0
tSave = collect(tStart:tStep:tStop)

realFMU = fmiLoad("SpringFrictionPendulum1D", "Dymola", "2022x")
fmiInfo(realFMU)

initStates = ["s0", "v0"]
x₀ = [0.5, 0.0]
params = Dict(zip(initStates, x₀))
vrs = ["mass.s", "mass.v", "mass.a", "mass.f"]

realSimData = fmiSimulate(realFMU, tStart, tStop; parameters=params, recordValues=vrs, saveat=tSave)
posReal = fmi2GetSolutionValue(realSimData, "mass.s")
velReal = fmi2GetSolutionValue(realSimData, "mass.v")
fmiPlot(realSimData)

function simulate(FMU, initStates, x₀, variables, tStart, tStop, tSave)
    params = Dict(zip(initStates, x₀))
    return fmiSimulate(FMU, tStart, tStop; parameters=params, recordValues=variables, saveat=tSave)
end

function extractPosVel(simData)
    if simData.states === nothing
        posData = fmi2GetSolutionValue(simData, "mass.s")
        velData = fmi2GetSolutionValue(simData, "mass.v")
    else
        posData = fmi2GetSolutionState(simData, 1; isIndex=true)
        velData = fmi2GetSolutionState(simData, 2; isIndex=true)
    end

    return posData, velData
end

xMod₀ = [1.0, -1.5]
realSimDataMod = simulate(realFMU, initStates, xMod₀, vrs, tStart, tStop, tSave)
fmiPlot(realSimDataMod)

fmiUnload(realFMU)

simpleFMU = fmiLoad("SpringPendulum1D", "Dymola", "2022x")
fmiInfo(simpleFMU)

initStates = ["mass_s0", "mass_v0", "fixed.s0"]
displacement = 0.1
xSimple₀ = vcat(x₀, displacement)
vrs = vrs[1:end-1]

simpleSimData = simulate(simpleFMU, initStates, xSimple₀, vrs, tStart, tStop, tSave)
fmiPlot(simpleSimData)

xSimpleMod₀ = vcat(xMod₀, displacement)

simpleSimDataMod = simulate(simpleFMU, initStates, xSimpleMod₀, vrs, tStart, tStop, tSave)
fmiPlot(simpleSimDataMod)

# loss function for training
function lossSum()
    global x₀
    solution = neuralFMU(x₀)

    posNet, velNet = extractPosVel(solution)

    (Flux.Losses.mse(posReal, posNet) + Flux.Losses.mse(velReal, velNet)) / 2.0
end

# callback function for training
global counter = 0
function callb()
    global counter, paramsNet
    counter += 1

    # freeze first layer parameters (2,4,6) for velocity -> (static) direct feed trough for velocity
    # parameters for position (1,3,5) are learned
    paramsNet[1][2] = 0.0
    paramsNet[1][4] = 1.0
    paramsNet[1][6] = 0.0

    if counter % 50 == 1
        avgLoss = lossSum()
        @info "  Loss [$counter]: $(round(avgLoss, digits=5))
        Avg displacement in data: $(round(sqrt(avgLoss), digits=5))
        Weight/Scale: $(paramsNet[1][1])   Bias/Offset: $(paramsNet[1][5])"
    end
end

function generate_figure(title, xLabel, yLabel, xlim="auto")
    Plots.plot(
        title=title, xlabel=xLabel, ylabel=yLabel, linewidth=2,
        xtickfontsize=12, ytickfontsize=12, xguidefontsize=12, yguidefontsize=12,
        legendfontsize=12, legend=:topright, xlim=xlim)
end

function plot_results(title, xLabel, yLabel, interval, realData, simpleData, neuralData)
    linestyles = [:dot, :solid]

    fig = generate_figure(title, xLabel, yLabel)
    Plots.plot!(fig, interval, simpleData, label="SimpleFMU", linewidth=2)
    Plots.plot!(fig, interval, realData, label="Reference", linewidth=2)
    for i in 1:length(neuralData)
        Plots.plot!(fig, neuralData[i][1], neuralData[i][2], label="NeuralFMU ($(i*2500))",
                    linewidth=2, linestyle=linestyles[i], linecolor=:green)
    end
    Plots.display(fig)
end

function plot_all_results(realSimData, realSimDataMod, simpleSimData,
        simpleSimDataMod, solutionAfter, solutionAfterMod)
    # collect all data
    posReal, velReal = extractPosVel(realSimData)
    posRealMod, velRealMod = extractPosVel(realSimDataMod)
    posSimple, velSimple = extractPosVel(simpleSimData)
    posSimpleMod, velSimpleMod = extractPosVel(simpleSimDataMod)

    run = length(solutionAfter)

    posNeural, velNeural = [], []
    posNeuralMod, velNeuralMod = [], []
    for i in 1:run
        dataNeural = extractPosVel(solutionAfter[i])
        time = fmi2GetSolutionTime(solutionAfter[i])

        push!(posNeural, (time, dataNeural[1]))
        push!(velNeural, (time, dataNeural[2]))

        dataNeuralMod = extractPosVel(solutionAfterMod[i])
        time = fmi2GetSolutionTime(solutionAfterMod[i])
        push!(posNeuralMod, (time, dataNeuralMod[1]))
        push!(velNeuralMod, (time, dataNeuralMod[2]))
    end

    # plot results s (default initial states)
    xLabel="t [s]"
    yLabel="mass position [m]"
    title = "Default: Mass position after Run: $(run)"
    plot_results(title, xLabel, yLabel, tSave, posReal, posSimple, posNeural)

    # plot results s (modified initial states)
    title = "Modified: Mass position after Run: $(run)"
    plot_results(title, xLabel, yLabel, tSave, posRealMod, posSimpleMod, posNeuralMod)

    # plot results v (default initial states)
    yLabel="mass velocity [m/s]"
    title = "Default: Mass velocity after Run: $(run)"
    plot_results(title, xLabel, yLabel, tSave, velReal, velSimple, velNeural)

    # plot results v (modified initial states)
    title = "Modified: Mass velocity after Run: $(run)"
    plot_results(title, xLabel, yLabel, tSave, velRealMod, velSimpleMod, velNeuralMod)
end

function plot_friction_model(realSimData, netBottom, forces)
    linestyles = [:dot, :solid]

    velReal = fmi2GetSolutionValue(realSimData, "mass.v")
    forceReal = fmi2GetSolutionValue(realSimData, "mass.f")

    push!(forces, zeros(length(velReal)))
    for i in 1:length(velReal)
        forces[end][i] = -netBottom([velReal[i], 0.0])[2]
    end

    run = length(forces)

    fig = generate_figure("Friction model $(run)", "v [m/s]", "friction force [N]", (-1.25, 1.25))

    fricSimple = hcat(velReal, zeros(length(velReal)))
    fricSimple[sortperm(fricSimple[:, 1]), :]
    Plots.plot!(fig, fricSimple[:,1], fricSimple[:,2], label="SimpleFMU", linewidth=2)

    fricReal = hcat(velReal, forceReal)
    fricReal[sortperm(fricReal[:, 1]), :]
    Plots.plot!(fig, fricReal[:,1], fricReal[:,2], label="reference", linewidth=2)

    for i in 1:run
        fricNeural = hcat(velReal, forces[i])
        fricNeural[sortperm(fricNeural[:, 1]), :]
        Plots.plot!(fig, fricNeural[:,1], fricNeural[:,2], label="NeuralFMU ($(i*2500))",
                    linewidth=2, linestyle=linestyles[i], linecolor=:green)
        @info "Friction model $i mse: $(Flux.Losses.mse(fricNeural[:,2], fricReal[:,2]))"
    end
    flush(stderr)

    Plots.display(fig)

    return forces
end

function plot_displacement_model(realSimData, netTop, displacements, tSave, displacement)
    linestyles = [:dot, :solid]

    posReal = fmi2GetSolutionValue(realSimData, "mass.s")

    push!(displacements, zeros(length(posReal)))
    for i in 1:length(posReal)
        displacements[end][i] = netTop([posReal[i], 0.0])[1] - posReal[i] - displacement
    end

    run = length(displacements)
    fig = generate_figure("Displacement model $(run)", "t [s]", "displacement [m]")
    Plots.plot!(fig, [tSave[1], tSave[end]], [displacement, displacement], label="simpleFMU", linewidth=2)
    Plots.plot!(fig, [tSave[1], tSave[end]], [0.0, 0.0], label="reference", linewidth=2)
    for i in 1:run
        Plots.plot!(fig, tSave, displacements[i], label="NeuralFMU ($(i*2500))",
                    linewidth=2, linestyle=linestyles[i], linecolor=:green)
    end

    Plots.display(fig)

    return displacements
end

# NeuralFMU setup
numStates = fmiGetNumberOfStates(simpleFMU)

# diagonal matrix
initW = zeros(numStates, numStates)
for i in 1:numStates
    initW[i,i] = 1
end

net = Chain(Dense(initW, zeros(numStates),  identity),
            inputs -> fmiEvaluateME(simpleFMU, inputs),
            Dense(numStates, 8, identity),
            Dense(8, 8, tanh),
            Dense(8, numStates))

neuralFMU = ME_NeuralFMU(simpleFMU, net, (tStart, tStop), Tsit5(); saveat=tSave);

solutionBefore = neuralFMU(x₀)
fmiPlot(solutionBefore)

# train
paramsNet = Flux.params(neuralFMU)

for i in 1:length(paramsNet[1])
    if paramsNet[1][i] < 0.0
        paramsNet[1][i] = -paramsNet[1][i]
    end
end

optim = ADAM()
Flux.train!(lossSum, paramsNet, Iterators.repeated((), 1), optim; cb=callb)

solutionAfter = []
solutionAfterMod = []
forces = []
displacements = []

numRuns = 2
numEpochs= 5
numIterations = 500;

for run in 1:numRuns
    @time for epoch in 1:numEpochs
        @info "Run: $(run)/$(numRuns)  Epoch: $(epoch)/$(numEpochs)"
        Flux.train!(lossSum, paramsNet, Iterators.repeated((), numIterations), optim; cb=callb)
    end
    flush(stderr)
    flush(stdout)

    push!(solutionAfter, neuralFMU(x₀))
    push!(solutionAfterMod, neuralFMU(xMod₀))

    # generate all plots for the position and velocity
    plot_all_results(realSimData, realSimDataMod, simpleSimData, simpleSimDataMod, solutionAfter, solutionAfterMod)

    # friction model extraction
    layersBottom = neuralFMU.neuralODE.model.layers[3:5]
    netBottom = Chain(layersBottom...)
    transferParams!(netBottom, paramsNet, 7)

    forces = plot_friction_model(realSimData, netBottom, forces)

    # displacement model extraction
    layersTop = neuralFMU.neuralODE.model.layers[1:1]
    netTop = Chain(layersTop...)
    transferParams!(netTop, paramsNet, 1)

    displacements = plot_displacement_model(realSimData, netTop, displacements, tSave, displacement)
end

fmiUnload(simpleFMU)


text:
# Map a rule over the grids it reads from and updating the grids it writes to.
# This is broken into a setup method and an application method
# to introduce a function barrier, for type stability.
maprule!(data::SimData, rule) = maprule!(data, Val{ruletype(rule)}(), rule)
function maprule!(data::SimData, ruletype::Val{T}, rule) where T
    #= keys and grids are separated instead of in a NamedTuple as `rgrids` or `wgrids`
    may be a single grid, not a Tuple. But we still need to know what its key is.
    The structure of rgrids and wgrids determines the values that are sent to the rule
    are in a NamedTuple or single value, and wether a tuple of single return value
    is expected. There may be a cleaner way of doing this. =#
    rkeys, rgrids = _getreadgrids(rule, data)
    wkeys, wgrids = _getwritegrids(rule, data)
    # Copy the source to dest for grids we are writing to, if needed
    _maybeupdatedest!(wgrids, ruletype)
    # Copy or zero out boundary where needed
    _updateboundary!(rgrids)
    # Combine read and write grids to a temporary simdata object.
    # This means that grids not specified to write to are read-only.
    allkeys = map(Val, keys(data))
    allgrids = values(data)
    tempdata = _combinegrids(data, allkeys, allgrids, wkeys, wgrids)
    # Run the rule loop
    maprule!(wgrids, tempdata, proc(data), opt(data), ruletype, rule, rkeys, rgrids, wkeys)
    # Mask writes to dest if a mask is provided, except for
    # CellRule which doesn't move values into masked areas
    T <: CellRule || _maybemask!(wgrids)
    # Copy the dest status to source status if it is in use
    _maybecopystatus!(wgrids)
    # Swap the dest/source of grids that were written to
    readonly_wgrids = _swapsource(wgrids) |> _to_readonly
    # Combine the written grids with the original simdata
    _replacegrids(data, wkeys, readonly_wgrids)
end
function maprule!(simdata::SimData, ruletype::Val{<:SetGridRule}, rule)
    rkeys, rgrids = _getreadgrids(rule, simdata)
    wkeys, wgrids = _getwritegrids(rule, simdata)
    tempsimdata = _combinegrids(simdata, rkeys, rgrids, wkeys, wgrids)
    # Run the rule loop
    applyrule!(tempsimdata, rule)
    # Combine the written grids with the original simdata
    _replacegrids(simdata, wkeys, _to_readonly(wgrids))
end

_maybeupdatedest!(ds::Tuple, ruletype) = map(d -> _maybeupdatedest!(d, ruletype), ds)
_maybeupdatedest!(d::WritableGridData, ::Val{<:Rule}) = nothing
function _maybeupdatedest!(d::WritableGridData, ::Val{<:SetRule})
    copyto!(parent(dest(d)), parent(source(d)))
end

_maybemask!(wgrids::Tuple) = map(_maybemask!, wgrids)
_maybemask!(wgrid::WritableGridData) = _maybemask!(wgrid, proc(wgrid), mask(wgrid))
_maybemask!(wgrid::WritableGridData, proc, mask::Nothing) = nothing
function _maybemask!(wgrid::WritableGridData{Y,X}, proc::CPU, mask::AbstractArray) where {Y,X}
    procmap(proc, 1:X) do j
        for i in 1:Y
            dest(wgrid)[i, j] *= mask[i, j]
        end
    end
end
function _maybemask!(wgrid::WritableGridData{Y,X}, proc, mask::AbstractArray) where {Y,X}
    destview(wgrid) .*= mask
end

_maybecopystatus!(grids::Tuple{Vararg{<:GridData}}) = map(_maybecopystatus!, grids)
_maybecopystatus!(grid::GridData) = _maybecopystatus!(sourcestatus(grid), deststatus(grid))
_maybecopystatus!(srcstatus, deststatus) = nothing
function _maybecopystatus!(srcstatus::AbstractArray, deststatus::AbstractArray)
    copyto!(srcstatus, deststatus)
end

_to_readonly(data::Tuple) = map(ReadableGridData, data)
_to_readonly(data::WritableGridData) = ReadableGridData(data)

function maprule!(
    wgrids::Union{<:GridData{Y,X,R},Tuple{<:GridData{Y,X,R},Vararg}},
    simdata, proc::CPU, opt, ruletype::Val, rule, rkeys, rgrids, wkeys
) where {Y,X,R}
    let simdata=simdata, proc=proc, opt=opt, rule=rule,
        rkeys=rkeys, rgrids=rgrids, wkeys=wkeys, wgrids=wgrids, ruletype=ruletype
        optmap(proc, opt, rgrids, Tuple{Y,X,R}) do i, j
            cell_kernel!(wgrids, simdata, ruletype, rule, rkeys, rgrids, wkeys, i, j)
        end
    end
end
function maprule!(
    wgrids::Union{<:GridData{Y,X,R},Tuple{<:GridData{Y,X,R},Vararg}},
    simdata, proc::CPU, opt, ruletype::Val{<:NeighborhoodRule}, rule, args...
) where {Y,X,R}
    grid = simdata[neighborhoodkey(rule)]
    mapneighborhoodrule!(wgrids, simdata, grid, proc, opt, ruletype, rule, args...)
    return nothing
end

### Rules that don't need a neighborhood buffer ####################

# Run kernels with SparseOpt
function optmap(f, proc, ::SparseOpt, rgrids, ::Type{Tuple{Y,X,R}}) where {Y,X,R}
    # Only use SparseOpt for single-grid rules with grid radii > 0
    if R == 0
        optmap(f, proc, NoOpt(), rgrids, Tuple{Y,X,R})
        return nothing
    end
    B = 2R
    grid = rgrids isa Tuple ? first(rgrids) : rgrids
    status = sourcestatus(grid)
    let f=f, proc=proc, grid=grid
        procmap(proc, 1:_indtoblock(X, B)) do bj
            for  bi in 1:_indtoblock(Y, B)
                status[bi, bj] || continue
                # Convert from padded block to init dimensions
                istart, jstart = _blocktoind(bi, B) - R, _blocktoind(bj, B) - R
                # Stop at the init row/column size, not the padding or block multiple
                istop, jstop = min(istart + B - 1, Y), min(jstart + B - 1, X)
                # Skip the padding
                istart, jstart  = max(istart, 1), max(jstart, 1)
                for j in jstart:jstop, i in istart:istop
                    f(i, j)
                end
            end
        end
    end
    return nothing
end
# Run kernel over the whole grid, cell by cell
optmap(f, proc, ::NoOpt, g, ::Type{Tuple{Y,X,R}}) where {Y,X,R} =
    procmap(proc, 1:X) do j
        for i in 1:Y
            f(i, j) # Run rule for each row in column j
        end
    end

# Looping over cells or blocks on CPU
procmap(f, proc::SingleCPU, range) =
    for n in range
        f(n) # Run rule over each column
    end
procmap(f, proc::ThreadedCPU, range) =
    Threads.@threads for n in range
        f(n) # Run rule over each column, threaded
    end

@inline function cell_kernel!(wgrids, simdata, ::Val{<:Rule}, rule, rkeys, rgrids, wkeys, i, j)
    readval = _readgrids(rkeys, rgrids, i, j)
    writeval = applyrule(simdata, rule, readval, (i, j))
    _writegrids!(wgrids, writeval, i, j)
    writeval
end
@inline function cell_kernel!(wgrids, simdata, ::Val{<:SetRule}, rule, rkeys, rgrids, wkeys, i, j)
    readval = _readgrids(rkeys, rgrids, i, j)
    applyrule!(simdata, rule, readval, (i, j))
    nothing
end

## Rules that need a Neighorhood buffer #############################################

function mapneighborhoodrule!(
    wgrids, simdata, grid::GridData{Y,X,R}, proc::CPU, opt, ruletype::Val, args...
) where {Y,X,R}
    let wgrids=wgrids, simdata=simdata, grid=grid, proc=proc, opt=opt, ruletype=ruletype, args=args
        B = 2R
        # Split the grid in 2 interleaved sets of rows, so that we never run adjacent
        # rows simultaneously - it could cause race conditions when setting status.
        procmap(proc, 1:2:_indtoblock(Y, B)) do bi
            row_kernel!(wgrids, simdata, grid, proc, opt, ruletype, args..., bi)
        end
        procmap(proc, 2:2:_indtoblock(Y, B)) do bi
            if bi <=_indtoblock(Y, B)
                row_kernel!(wgrids, simdata, grid, proc, opt, ruletype, args..., bi)
            end
        end
    end
    return nothing
end

function _maybecopystatus!(grid::GridData, opt::SparseOpt)
    # Copy src to dst - we don't run every block so this is necessary
    deststatus(grid) .= sourcestatus(grid)
    return nothing
end
_maybecopystatus!(grid, opt::NoOpt) = nothing

#= Run the rule row by row. When we move along a row by one cell, we access only
a single new column of data with the height of 4R, and move the existing
data in the neighborhood buffers array across by one column. This saves on reads
from the main array. =#
function row_kernel!(
    wgrids, simdata::SimData, grid::GridData{Y,X,R}, proc, opt::NoOpt,
    ruletype::Val, rule::Rule, rkeys, rgrids, wkeys, bi
) where {Y,X,R}
    B = 2R
    i = _blocktoind(bi, B)
    # Loop along the block ROW.
    src = parent(source(grid))
    buffers = _initialise_buffers(src, Val{R}(), i, 1)
    blocklen = min(Y, i + B - 1) - i + 1
    for j = 1:X
        buffers = _update_buffers(buffers, src, Val{R}(), i, j)
        # Loop over the COLUMN of buffers covering the block
        for b in 1:blocklen
            @inbounds bufrule = _setbuffer(rule, buffers[b])
            cell_kernel!(wgrids, simdata, ruletype, bufrule, rkeys, rgrids, wkeys, i + b - 1, j)
        end
    end
end
function row_kernel!(
    wgrids, simdata::SimData, grid::GridData{Y,X,R}, proc, opt::SparseOpt,
    ruletype::Val, rule::Rule, rkeys, rgrids, wkeys, bi
) where {Y,X,R}
    B = 2R
    S = 2R + 1
    nblockcols = _indtoblock(X, B)
    src = parent(source(grid))
    srcstatus, dststatus = sourcestatus(grid), deststatus(grid)

    # Blocks ignore padding! the first block contains padding.
    i = _blocktoind(bi, B)
    # Get current bloc
    skippedlastblock = true

    # Initialise block status for the start of the row
    # The first column always runs, it's buggy otherwise.
    @inbounds bs11, bs12 = true, true
    @inbounds bs21, bs22 = true, true
    # New block status
    newbs12 = false
    newbs22 = false
    buffers = _initialise_buffers(src, Val{R}(), i, 1)
    for bj = 1:nblockcols
        # Shuffle current buffer status
        bs11, bs21 = bs12, bs22
        @inbounds bs12, bs22 = srcstatus[bi, bj + 1], srcstatus[bi + 1, bj + 1]
        # Skip this block if it and the neighboring blocks are inactive
        if !(bs11 | bs12 | bs21 | bs22)
            skippedlastblock = true
            # Run the rest of the chain if it exists and more than 1 grid is used
            if rule isa Chain && length(rule) > 1 && length(rkeys) > 1
                # Loop over the grid COLUMNS inside the block
                jstart = _blocktoind(bj, B)
                jstop = min(jstart + B - 1, X)
                for j in jstart:jstop
                    # Loop over the grid ROWS inside the block
                    blocklen = min(Y, i + B - 1) - i + 1
                    for b in 1:blocklen
                        cell_kernel!(wgrids, simdata, ruletype, rule, rkeys, rgrids, wkeys, i + b - 1, j)
                    end
                end
            end
            continue
        end
        # Define area to loop over with the block.
        # It's variable because the last block may be partial
        jstart = _blocktoind(bj, B)
        jstop = min(jstart + B - 1, X)

        # Reinitialise neighborhood buffers if we have skipped a section of the array
        if skippedlastblock
            buffers = _initialise_buffers(src, Val{R}(), i, jstart)
            skippedlastblock = false
        end
        # Shuffle new buffer status
        newbs11 = newbs12
        newbs21 = newbs22
        newbs12 = newbs22 = false

        # Loop over the grid COLUMNS inside the block
        for j in jstart:jstop
            # Update buffers unless feshly populated
            buffers = _update_buffers(buffers, src, Val{R}(), i, j)
            # Which block column are we in, 1 or 2
            curblockj = (j - jstart) ÷ R + 1
            # Loop over the COLUMN of buffers covering the block
            blocklen = min(Y, i + B - 1) - i + 1
            for b in 1:blocklen
                # Set rule buffer
                bufrule = _setbuffer(rule, buffers[b])
                # Run the rule kernel for the cell
                writeval = cell_kernel!(wgrids, simdata, ruletype, bufrule, rkeys, rgrids, wkeys, i + b - 1, j)
                # Update the status for the current block
                cs = _cellstatus(opt, wgrids, writeval)
                curblocki = R == 1 ? b : (b - 1) ÷ R + 1
                if curblocki == 1
                    curblockj == 1 ? (newbs11 |= cs) : (newbs12 |= cs)
                else
                    curblockj == 1 ? (newbs21 |= cs) : (newbs22 |= cs)
                end
            end

            # Combine new block status with deststatus array
            @inbounds dststatus[bi, bj] |= newbs11
            @inbounds dststatus[bi+1, bj] |= newbs21
            @inbounds dststatus[bi, bj+1] |= newbs12
            @inbounds dststatus[bi+1, bj+1] |= newbs22
        end
    end
    return nothing
end

@inline _cellstatus(opt::SparseOpt, wgrids::Tuple, writeval) = _cellstatus(opt, writeval[1], writeval)
@inline _cellstatus(opt::SparseOpt, wgrids, writeval) = !can_skip(opt, writeval)

## Low-level generated functiond for working with grids ######################

# Reduces array reads for single grids, when we can just use
# the center of the neighborhood buffer as the cell state

@inline _keys2vals(keys::Tuple) = map(Val, keys)
@inline _keys2vals(key::Symbol) = Val(key)

# Generate an SArray from the main array and the last SArray
@generated function _update_buffers(
    buffers::Tuple, src::AbstractArray{T}, ::Val{R}, i, j
) where {T,R}
    B = 2R; S = 2R + 1; L = S^2
    newvals = Expr[]
    for n in 0:2B-1
        push!(newvals, :(@inbounds src[i + $n, j + 2R]))
    end
    newbuffers = Expr(:tuple)
    for b in 1:B
        bufvals = Expr(:tuple)
        for n in S+1:L
            push!(bufvals.args, :(@inbounds buffers[$b][$n]))
        end
        for n in b:b+B
            push!(bufvals.args, newvals[n])
        end
        push!(newbuffers.args, :(SArray{Tuple{$S,$S},$T,2,$L}($bufvals)))
    end
    return quote
        return $newbuffers
    end
end

# Generate an SArray from the main array
@generated function _initialise_buffers(src::AbstractArray{T}, ::Val{R}, i, j) where {T,R}
    B = 2R; S = 2R + 1; L = S^2
    columns = []
    zerocol = Expr[]
    for r in 1:2B
        push!(zerocol, :(zero(T)))
    end
    push!(columns, zerocol)
    for c in 0:S-2
        newcol = Expr[]
        for r in 0:2B-1
            push!(newcol, :(@inbounds src[i + $r, j + $c]))
        end
        push!(columns, newcol)
    end
    newbuffers = Expr(:tuple)
    for b in 1:B
        bufvals = Expr(:tuple)
        for c in 1:S, r in b:b+B
            exp = columns[c][r]
            push!(bufvals.args, exp)
        end
        push!(newbuffers.args, :(SArray{Tuple{$S,$S},$T,2,$L}($bufvals)))
    end
    return quote
        return $newbuffers
    end
end

# Read values from grid/s at index `I`. This occurs for every cell for every rule,
# so has to be very fast.

# Returns a single value or NamedTuple of values.
function _readgrids end
@generated function _readgrids(rkeys::Tuple, rgrids::Tuple, I...)
    expr = Expr(:tuple)
    for i in 1:length(rgrids.parameters)
        push!(expr.args, :(@inbounds rgrids[$i][I...]))
    end
    return quote
        keys = map(_unwrap, rkeys)
        vals = $expr
        NamedTuple{keys,typeof(vals)}(vals)
    end
end
@inline function _readgrids(rkeys::Val, rgrids::GridData, I...)
    @inbounds rgrids[I...]
end

# Write values to grid/s at index `I`. This occurs for every cell for every rule,
# so has to be very fast.
# Returns a single value or NamedTuple of values.
function _writegrids end
@generated function _writegrids!(wdata::Tuple, vals::Union{Tuple,NamedTuple}, I...)
    expr = Expr(:block)
    for i in 1:length(wdata.parameters)
        push!(expr.args, :(@inbounds dest(wdata[$i])[I...] = vals[$i]))
    end
    push!(expr.args, :(nothing))
    return expr
end
@inline function _writegrids!(wdata::GridData, val, I...)
    @inbounds dest(wdata)[I...] = val
    return nothing
end

# Retrieves `GridData` from a `SimData` object to match the requirements of a `Rule`.
# Returns a `Tuple` holding the key or `Tuple` of keys, and grid or `Tuple` of grids.
@generated function _getreadgrids(::Rule{R,W}, simdata::AbstractSimData) where {R<:Tuple,W}
    Expr(:tuple,
        Expr(:tuple, (:(Val{$(QuoteNode(key))}()) for key in R.parameters)...),
        Expr(:tuple, (:(simdata[$(QuoteNode(key))]) for key in R.parameters)...),
    )
end
@generated function _getreadgrids(::Rule{R,W}, simdata::AbstractSimData) where {R,W}
    :((Val{$(QuoteNode(R))}(), simdata[$(QuoteNode(R))]))
end
@generated function _getwritegrids(::Rule{R,W}, simdata::AbstractSimData) where {R,W<:Tuple}
    Expr(:tuple,
        Expr(:tuple, (:(Val{$(QuoteNode(key))}()) for key in W.parameters)...),
        Expr(:tuple, (:(WritableGridData(simdata[$(QuoteNode(key))])) for key in W.parameters)...),
    )
end
@generated function _getwritegrids(::Rule{R,W}, simdata::AbstractSimData) where {R,W}
    :((Val{$(QuoteNode(W))}(), WritableGridData(simdata[$(QuoteNode(W))])))
end

@inline _vals2syms(x::Type{<:Tuple}) = map(v -> _vals2syms(v), x.parameters)
@inline _vals2syms(::Type{<:Val{X}}) where X = X

# Combine grids into a new NamedTuple of grids depending
# on the read and write keys required by a rule.
@inline function _combinegrids(simdata::SimData, rkeys, rgrids, wkeys, wgrids)
    @set simdata.grids = _combinegrids(rkeys, rgrids, wkeys, wgrids)
end
@inline function _combinegrids(rkey, rgrids, wkey, wgrids)
    _combinegrids((rkey,), (rgrids,), (wkey,), (wgrids,))
end
@inline function _combinegrids(rkey, rgrids, wkeys::Tuple, wgrids::Tuple)
    _combinegrids((rkey,), (rgrids,), wkeys, wgrids)
end
@inline function _combinegrids(rkeys::Tuple, rgrids::Tuple, wkey, wgrids)
    _combinegrids(rkeys, rgrids, (wkey,), (wgrids,))
end
@generated function _combinegrids(rkeys::Tuple, rgrids::Tuple, wkeys::Tuple, wgrids::Tuple)
    rkeys = _vals2syms(rkeys)
    wkeys = _vals2syms(wkeys)
    keysexp = Expr(:tuple, QuoteNode.(wkeys)...)
    dataexp = Expr(:tuple, :(wgrids...))

    for (i, key) in enumerate(rkeys)
        if !(key in wkeys)
            push!(dataexp.args, :(rgrids[$i]))
            push!(keysexp.args, QuoteNode(key))
        end
    end

    return quote
        keys = $keysexp
        vals = $dataexp
        NamedTuple{keys,typeof(vals)}(vals)
    end
end

# Replace grids in a NamedTuple with new grids where required.
function _replacegrids(simdata::AbstractSimData, newkeys, newgrids)
    @set simdata.grids = _replacegrids(grids(simdata), newkeys, newgrids)
end
@generated function _replacegrids(allgrids::NamedTuple, newkeys::Tuple, newgrids::Tuple)
    newkeys = map(_unwrap, newkeys.parameters)
    allkeys = allgrids.parameters[1]
    expr = Expr(:tuple)
    for key in allkeys
        if key in newkeys
            i = findfirst(k -> k == key, newkeys)
            push!(expr.args, :(newgrids[$i]))
        else
            push!(expr.args, :(allgrids.$key))
        end
    end

    return quote
        vals = $expr
        NamedTuple{$allkeys,typeof(vals)}(vals)
    end
end
@generated function _replacegrids(allgrids::NamedTuple, newkey::Val, newgrid::GridData)
    newkey = _unwrap(newkey)
    allkeys = allgrids.parameters[1]
    expr = Expr(:tuple)
    for key in allkeys
        if key == newkey
            push!(expr.args, :(newgrid))
        else
            push!(expr.args, :(allgrids.$key))
        end
    end

    return quote
        vals = $expr
        NamedTuple{$allkeys,typeof(vals)}(vals)
    end
end

ext: jl


text:
# one dimensional Legendre polynomials

@inline function getLegendrePn(x, n::Int64)
    #= Compute Pn evaluated at x ∈ [-1,1] =#
    if n>1
        resm = ones(Float64, length(x))
        res  = copy(x)
        resp = zeros(Float64, length(x))
        for i=2:n
            resp .= ((2*i - 1) * x .* res - (i-1)*resm) / i
            resm .= res; res .= resp
        end
        return res
    elseif n==0
        return ones(Float64, length(x))
    elseif n==1
        return x
    end
end

@inline function getAllLegendrePn(x, n::Int64)
    #= Compute all Pn for all x ∈ [-1, 1] =#
    nx = length(x)
    if n > 1
        res = zeros(Float64, nx, n+1)
        res[:, 2] .= x
        for i=2:n
            res[:, i+1] = ( (2*i -1) * x .* res[:, i] - (i-1)*res[:, i-1]) / i
        end
        return res
    elseif n == 0
        return zeros(Float64, nx, 1)
    elseif n == 1
        res = zeros(Float64, nx, 2)
        res[:, 2] .= x
        return res
    end
end

# interior monomials
function getMonomialsP1(xE::Array{Float64, 1}, hE::Float64)
    #=
    Return an array of Λ-functions enabling the computation of the
    P1 monomials
    =#
    return [x -> 1., x -> (x[1] - xE[1])/hE, x -> (x[2] - xE[2])/hE]
end

function getMonomialsP1(xE::Array{Float64, 1}, hE::Float64, Cs::Array{Float64,1})
    #= Compute single P1 monomials with 0 average =#
    return [x -> 1., x -> (x[1] - xE[1])/hE + Cs[2], x -> (x[2] - xE[2])/hE + Cs[3]]
end

function getGradientMonomialsP1(xE::Array{Float64, 1}, hE::Float64)
    #= Compute the gradient of the single P1 monomials =#
    return [x -> [0.,0.], x -> [1/hE, 0.], x -> [0., 1/hE]]
end

function getMonomialsP2(xE::Array{Float64, 1}, hE::Float64)
    #= Compute single P2 monomials =#
    hE2 = hE^2
    return  [
        x -> 1,
        x -> (x[1] - xE[1])/hE,
        x -> (x[2] - xE[2])/hE,
        x -> (x[1] - xE[1])^2 / hE2,
        x -> (x[1] - xE[1])*(x[2] - xE[2])/hE2,
        x -> (x[2] - xE[2])^2 / hE2]
end

function getMonomialsP2(xE::Array{Float64, 1}, hE::Float64, Cs::Array{Float64,1})
    #= Compute single P2 monomials with 0 average =#
    hE2 = hE^2
    return [
        x -> 1,
        x -> (x[1] - xE[1])/hE + Cs[2],
        x -> (x[2] - xE[2])/hE + Cs[3],
        x -> (x[1] - xE[1])^2 / hE2 + Cs[4],
        x -> (x[1] - xE[1])*(x[2] - xE[2])/hE2 + Cs[5],
        x -> (x[2] - xE[2])^2 / hE2 + Cs[6]
    ]
end

function getGradientMonomialsP2(xE::Array{Float64, 1}, hE::Float64)
    #= Compute gradient of the single P2 monomials =#
    hE2 = hE^2
    return [
        x -> [0.,0.],
        x -> [1/hE, 0.],
        x -> [0., 1/hE],
        x -> [2*(x[1] - xE[1])/hE2, 0.],
        x -> [(x[2] - xE[2])/hE2, (x[1] - xE[1])/hE2],
        x -> [0., 2*(x[2] - xE[2])/hE2]
    ]
end


text:
"""
    to_openfermion(atom::Atom)

Convert `atom` to OpenFermion format.

# Examples
```jldoctest
julia> to_openfermion(Atom(:Li, (0.0, 0.0, 1.4)))
(:Li, (0.0, 0.0, 1.4))
```
"""
function to_openfermion(atom::Atom)
    return (atom.species, atom.coords)
end

"""
    to_openfermion(geom::Geometry)

Convert `geom` to OpenFermion format.

# Examples
```jldoctest
julia> to_openfermion(Geometry(Atom(:H, (0., 0., 0.)), Atom(:H, (0., 0., 0.7414))))
2-element Vector{Any}:
 (:H, (0.0, 0.0, 0.0))
 (:H, (0.0, 0.0, 0.7414))
```
"""
function to_openfermion(geom::Geometry)
    return Any[to_openfermion(atom) for atom in geom]  # This need not be `Any`, but we do this for clarity
end


text:
"""
Writes a graph `g` to a file `f` in the [Pajek
.net](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format.
Returns 1 (number of graphs written).
"""
function writepajek(f::IO, g::AGraphOrDiGraph)
    println(f, "*Vertices $(nv(g))")
    # write edges
    if is_directed(g)
        println(f, "*Arcs")
    else
        println(f, "*Edges")
    end
    for e in edges(g)
        println(f, "$(src(e)) $(dst(e))")
    end
    return 1
end

"""
    readpajek{G}(f::IO, ::Type{G})

Reads a graph from file `f` in the [Pajek .net](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format.
Returns 1 (number of graphs written).
"""
function readpajek(f::IO, ::Type{G}) where G
    line =readline(f)
    # skip comments
    while startswith(line, "%")
        line =readline(f)
    end
    n = parse(Int, match(r"\d+",line).match)
    for fline in eachline(f)
        line = fline
        (occursin(r"^\*Arcs",line) || occursin(r"^\*Edges",line)) && break
    end
    dir = occursin(r"^\*Arcs",line)
    g = G(n)
    g = dir ? digraph(g) : graph(g)
    readpajek_edges!(g, f, line)
    return g
end

function readpajek_edges!(g::G, f::IO, line) where G<:AGraph
    while occursin(r"^\*Edges",line) && !eof(f)
        for fline in eachline(f)
            line = fline
            m = collect(eachmatch(r"\d+",line))
            length(m) < 2 && break
            i1, i2 = parse(Int, m[1].match), parse(Int, m[2].match)
            unsafe_add_edge!(g, i1, i2)
        end
    end
    rebuild!(g)
end

function readpajek_edges!(g::G, f::IO, line) where G<:ADiGraph
    while occursin(r"^\*Arcs",line) # add edges in both directions
        for fline in eachline(f)
            line = fline
            m = collect(eachmatch(r"\d+",line))
            length(m) < 2 && break
            i1, i2 = parse(Int, m[1].match), parse(Int, m[2].match)
            unsafe_add_edge!(g, i1, i2)
        end
    end
    while occursin(r"^\*Edges",line) # add edges in both directions
        for fline in eachline(f)
            line = fline
            m = collect(eachmatch(r"\d+",line))
            length(m) < 2 && break
            i1, i2 = parse(Int, m[1].match), parse(Int, m[2].match)
            unsafe_add_edge!(g, i1, i2)
            unsafe_add_edge!(g, i2, i1)
        end
    end
    rebuild!(g)
end

filemap[:net] = (readpajek, writepajek, NI, NI)


text:
mutable struct SugarSeeker <: AbstractAgent
    id::Int
    pos::Dims{2}
    vision::Int
    metabolic_rate::Int
    age::Int
    max_age::Int
    wealth::Int
end

function distances(pos, sugar_peaks, max_sugar)
    all_dists = Array{Int,1}(undef, length(sugar_peaks))
    for (ind, peak) in enumerate(sugar_peaks)
        d = round(Int, sqrt(sum((pos .- peak) .^ 2)))
        all_dists[ind] = d
    end
    return minimum(all_dists)
end

function sugar_caps(dims, sugar_peaks, max_sugar, dia = 4)
    sugar_capacities = zeros(Int, dims)
    for i in 1:dims[1], j in 1:dims[2]
        sugar_capacities[i, j] = distances((i, j), sugar_peaks, max_sugar)
    end
    for i in 1:dims[1]
        for j in 1:dims[2]
            sugar_capacities[i, j] = max(0, max_sugar - (sugar_capacities[i, j] ÷ dia))
        end
    end
    return sugar_capacities
end

"""
``` julia
sugarscape(;
    dims = (50, 50),
    sugar_peaks = ((10, 40), (40, 10)),
    growth_rate = 1,
    N = 250,
    w0_dist = (5, 25),
    metabolic_rate_dist = (1, 4),
    vision_dist = (1, 6),
    max_age_dist = (60, 100),
    max_sugar = 4,
)
```
Same as in [Sugarscape](@ref).
"""
function sugarscape(;
    dims = (50, 50),
    sugar_peaks = ((10, 40), (40, 10)),
    growth_rate = 1,
    N = 250,
    w0_dist = (5, 25),
    metabolic_rate_dist = (1, 4),
    vision_dist = (1, 6),
    max_age_dist = (60, 100),
    max_sugar = 4,
)
    sugar_capacities = sugar_caps(dims, sugar_peaks, max_sugar, 6)
    sugar_values = deepcopy(sugar_capacities)
    space = GridSpace(dims)
    properties = Dict(
        :growth_rate => growth_rate,
        :N => N,
        :w0_dist => w0_dist,
        :metabolic_rate_dist => metabolic_rate_dist,
        :vision_dist => vision_dist,
        :max_age_dist => max_age_dist,
        :sugar_values => sugar_values,
        :sugar_capacities => sugar_capacities,
    )
    model = AgentBasedModel(
        SugarSeeker,
        space,
        scheduler = schedule_randomly,
        properties = properties,
    )
    for _ in 1:N
        add_agent_single!(
            model,
            rand(model.rng, vision_dist[1]:vision_dist[2]),
            rand(model.rng, metabolic_rate_dist[1]:metabolic_rate_dist[2]),
            0,
            rand(model.rng, max_age_dist[1]:max_age_dist[2]),
            rand(model.rng, w0_dist[1]:w0_dist[2]),
        )
    end
    return model, sugarscape_agent_step!, sugarscape_env!
end

function sugarscape_env!(model)
    # At each position, sugar grows back at a rate of $\alpha$ units per time-step up to the cell's capacity c.
    togrow = findall(
        x -> model.sugar_values[x] < model.sugar_capacities[x],
        1:length(positions(model)),
    )
    model.sugar_values[togrow] .+= model.growth_rate
end

function movement!(agent, model)
    newsite = agent.pos
    # find all unoccupied position within vision
    neighbors = nearby_positions(agent.pos, model, agent.vision)
    empty = collect(empty_positions(model))
    if length(empty) > 0
        # identify the one(s) with greatest amount of sugar
        available_sugar = (model.sugar_values[x,y] for (x, y) in empty)
        maxsugar = maximum(available_sugar)
        if maxsugar > 0
            sugary_sites_inds = findall(x -> x == maxsugar, collect(available_sugar))
            sugary_sites = empty[sugary_sites_inds]
            # select the nearest one (randomly if more than one)
            for dia in 1:(agent.vision)
                np = nearby_positions(agent.pos, model, dia)
                suitable = intersect(np, sugary_sites)
                if length(suitable) > 0
                    newsite = rand(model.rng, suitable)
                    break
                end
            end
            # move there and collect all the sugar in it
            newsite != agent.pos && move_agent!(agent, newsite, model)
        end
    end
    # update wealth (collected - consumed)
    agent.wealth += (model.sugar_values[newsite...] - agent.metabolic_rate)
    model.sugar_values[newsite...] = 0
    agent.age += 1
end

function replacement!(agent, model)
    # If the agent's sugar wealth become zero or less, it dies
    if agent.wealth <= 0 || agent.age >= agent.max_age
        kill_agent!(agent, model)
        # Whenever an agent dies, a young one is added to a random pos.
        # New agent has random attributes
        add_agent_single!(
            model,
            rand(model.rng, model.vision_dist[1]:model.vision_dist[2]),
            rand(model.rng, model.metabolic_rate_dist[1]:model.metabolic_rate_dist[2]),
            0,
            rand(model.rng, model.max_age_dist[1]:model.max_age_dist[2]),
            rand(model.rng, model.w0_dist[1]:model.w0_dist[2]),
        )
    end
end

function sugarscape_agent_step!(agent, model)
    movement!(agent, model)
    replacement!(agent, model)
end


text: encounter(a, b) = "$(name(a)) meets $(name(b)) and $(meets(a, b))."


text:
# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control

"""
    module ModiaMath.DAE

Interface between the [`ModiaMath.SimulationEngine`](@ref) and the index 1 DAE model.

The following functions can be called in the DAE model to inquire
**information about the simulation state**:

- [`ModiaMath.getTime`](@ref)
- [`ModiaMath.getStartTime`](@ref)
- [`ModiaMath.getStopTime`](@ref)
- [`ModiaMath.getTolerance`](@ref)
- [`ModiaMath.isInitial`](@ref)
- [`ModiaMath.isTerminal`](@ref)
- [`ModiaMath.isEvent`](@ref)
- [`ModiaMath.isZeroCrossing`](@ref)
- [`ModiaMath.isAfterSimulationStart`](@ref)
- [`ModiaMath.isStoreResult`](@ref)
- [`ModiaMath.isLogInfos`](@ref)
- [`ModiaMath.isLogWarnings`](@ref)
- [`ModiaMath.isLogEvents`](@ref)

The following functions can be called in the DAE model to set
**properties in the simulation engine**:

- [`ModiaMath.setNominal!`](@ref)
- [`ModiaMath.setNextEvent!`](@ref)
- [`ModiaMath.positive!`](@ref)
- [`ModiaMath.negative!`](@ref)
- [`ModiaMath.change!`](@ref)
- [`ModiaMath.edge!`](@ref)

The following functions can be either called in the DAE model
or they can be called on a simulation model
(before or after `ModiaMath.simulate!(simulationModel, ...)` is called).

- [`ModiaMath.logOn!`](@ref)
- [`ModiaMath.logOff!`](@ref)
- [`ModiaMath.setLogCategories!`](@ref)

# Main developer

[Martin Otter](https://rmc.dlr.de/sr/de/staff/martin.otter/),
[DLR - Institute of System Dynamics and Control](https://www.dlr.de/sr/en)
"""
module DAE

export EventRestart, NoRestart, Restart, FullRestart, Terminate
export getVariableName, getResultNames

export getTime, getStartTime, getStopTime, getTolerance
export isInitial, isTerminal, isEvent, isZeroCrossing, isAfterSimulationStart, isStoreResult, setNominal!
export setNextEvent!, positive!, negative!, change!, edge!
export getSimulationResult

export SimulationState

# export InitInfo, EventInfo, reset!
# export EventHandler
# export getEventResidues!
# export reinitialize!, eventIteration!, initialize!, getResidues!
# export computeAndStoreResult!, terminate!, processEvent!, getEventIndicators

import ModiaMath
@static if VERSION >= v"0.7.0-DEV.2005"
    using LinearAlgebra
end

"""
    @enum EventRestart NoRestart Restart FullRestart Terminate

Define how to continue or restart integration after an event. Usually, `Restart`
should be used. Only in special cases, the other flags are useful.

- `NoRestart`, continue integration without restarting the integrator
- `Restart`, restart integrator
- `FullRestart`, restart integrator and optionally exchange simulationState (so dimensions may change)
- `Terminate`, terminate integration
"""
@enum EventRestart NoRestart Restart FullRestart Terminate

@enum VariableCategory Category_X Category_DERX Category_W

# include code
include("events.jl")
include("returnStructs.jl")
include("simulationState.jl")
include("functionsForUserModels.jl")

end


text:
module HDF5Arrays
include("class.jl")
include("dense.jl")
include("sparse.jl")
include("example.jl")
end # module


text:
#nlsolve functionality
"""
    function nlsolve(f!,x0,method=TrustRegion(Newton(), NWI()),chunk = ForwardDiff.Chunk{2}() options=NEqOptions())

Given a function `f!(result,x)` that returns a system of equations,
`nlsolve(f!,x0)` returns a `NLSolvers.ConvergenceInfo` struct that contains the results of the non-linear solving procedure.

Uses `NLSolvers.jl` as backend, the jacobian is calculated with `ForwardDiff.jl`, with the specified `chunk` size

To obtain the underlying solution vector, use [`x_sol`](@ref)

To see available solvers and options, check `NLSolvers.jl`
"""
function nlsolve(f!,x0,method=TrustRegion(CholeskyNewton(), NWI()),chunk = ForwardDiff.Chunk{2}(),options=NEqOptions();)
    #f! = (F,x) -> Obj_Sat(model, F, T, exp10(x[1]), exp10(x[2]))
    len = length(x0)
    #xcache = zeros(eltype(x0),len)
    Fcache = zeros(eltype(x0),len)
    #JCache = zeros(eltype(x0),len,len)
    jconfig = ForwardDiff.JacobianConfig(f!,x0,x0,chunk)
    function j!(J,x)
        #@show J
        ForwardDiff.jacobian!(J,f!,Fcache,x,jconfig)
    end
    function fj!(F,J,x)
        #@show J,F
        ForwardDiff.jacobian!(J,f!,F,x,jconfig)
        F,J
    end

    function jv!(x)
        function JacV(dy,v)
            return jacvec!(dy,f!,x,v)
        end
        return LinearMap(JacV,length(x))
    end

    vectorobj = NLSolvers.VectorObjective(f!,j!,fj!,jv!)
    vectorprob = NEqProblem(vectorobj)
    return NLSolvers.solve(vectorprob, x0,method , options)
    #@show res
end

#from SparseDiffTools.jl, but it happens to work on dense vectors as well

struct DeivVecTag end

function jacvec!(dy, f, x, v,
                      cache1 = ForwardDiff.Dual{DeivVecTag}.(x, v),
                      cache2 = ForwardDiff.Dual{DeivVecTag}.(x, v))
    cache1 .= Dual{DeivVecTag}.(x, v)
    f(cache2,cache1)
    dy .= partials.(cache2, 1)
end

function jacvec(f, x, v)
    partials.(f(Dual{DeivVecTag}.(x, v)), 1)
end


text:
using Documenter
using RecordedArrays

makedocs(;
    sitename="RecordedArrays.jl",
    pages=["index.md", "manual.md", "example.md", "references.md"],
)

deploydocs(; repo="github.com/wangl-cc/RecordedArrays.jl.git", push_preview=true)


text:
#==============================================================================#
# IoTAnalytics.jl
#
# This file is generated from:
# https://github.com/aws/aws-sdk-js/blob/master/apis/iotanalytics-2017-11-27.normal.json
#==============================================================================#

__precompile__()

module IoTAnalytics

using AWSCore

"""
    using AWSSDK.IoTAnalytics.batch_put_message
    batch_put_message([::AWSConfig], arguments::Dict)
    batch_put_message([::AWSConfig]; channelName=, messages=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/messages/batch", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/messages/batch", channelName=, messages=)

# BatchPutMessage Operation

Sends messages to a channel.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel where the messages are sent.

## `messages = [[ ... ], ...]` -- *Required*
The list of messages to be sent. Each message has format: '{ "messageId": "string", "payload": "string"}'.
```
 messages = [[
        "messageId" => <required> ::String,
        "payload" => <required> blob
    ], ...]
```

# Returns

`BatchPutMessageResponse`

# Exceptions

`ResourceNotFoundException`, `InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/BatchPutMessage)
"""
@inline batch_put_message(aws::AWSConfig=default_aws_config(); args...) = batch_put_message(aws, args)

@inline batch_put_message(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/messages/batch", args)

@inline batch_put_message(args) = batch_put_message(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.cancel_pipeline_reprocessing
    cancel_pipeline_reprocessing([::AWSConfig], arguments::Dict)
    cancel_pipeline_reprocessing([::AWSConfig]; pipelineName=, reprocessingId=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/pipelines/{pipelineName}/reprocessing/{reprocessingId}", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/pipelines/{pipelineName}/reprocessing/{reprocessingId}", pipelineName=, reprocessingId=)

# CancelPipelineReprocessing Operation

Cancels the reprocessing of data through the pipeline.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of pipeline for which data reprocessing is canceled.

## `reprocessingId = ::String` -- *Required*
The ID of the reprocessing task (returned by "StartPipelineReprocessing").

# Returns

`CancelPipelineReprocessingResponse`

# Exceptions

`ResourceNotFoundException`, `InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CancelPipelineReprocessing)
"""
@inline cancel_pipeline_reprocessing(aws::AWSConfig=default_aws_config(); args...) = cancel_pipeline_reprocessing(aws, args)

@inline cancel_pipeline_reprocessing(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/pipelines/{pipelineName}/reprocessing/{reprocessingId}", args)

@inline cancel_pipeline_reprocessing(args) = cancel_pipeline_reprocessing(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.create_channel
    create_channel([::AWSConfig], arguments::Dict)
    create_channel([::AWSConfig]; channelName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/channels", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/channels", channelName=, <keyword arguments>)

# CreateChannel Operation

Creates a channel. A channel collects data from an MQTT topic and archives the raw, unprocessed messages before publishing the data to a pipeline.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel.

## `retentionPeriod = [ ... ]`
How long, in days, message data is kept for the channel.
```
 retentionPeriod = [
        "unlimited" =>  ::Bool,
        "numberOfDays" =>  ::Int
    ]
```

## `tags = [[ ... ], ...]`
Metadata which can be used to manage the channel.
```
 tags = [[
        "key" => <required> ::String,
        "value" => <required> ::String
    ], ...]
```

# Returns

`CreateChannelResponse`

# Exceptions

`InvalidRequestException`, `ResourceAlreadyExistsException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException` or `LimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CreateChannel)
"""
@inline create_channel(aws::AWSConfig=default_aws_config(); args...) = create_channel(aws, args)

@inline create_channel(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/channels", args)

@inline create_channel(args) = create_channel(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.create_dataset
    create_dataset([::AWSConfig], arguments::Dict)
    create_dataset([::AWSConfig]; datasetName=, actions=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/datasets", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/datasets", datasetName=, actions=, <keyword arguments>)

# CreateDataset Operation

Creates a data set. A data set stores data retrieved from a data store by applying an SQL action.

**Note**
> This operation creates the skeleton of a data set. To populate the data set, call "CreateDatasetContent".

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set.

## `actions = [[ ... ], ...]` -- *Required*
A list of actions that create the data set. Only one action is supported at this time.
```
 actions = [[
        "actionName" =>  ::String,
        "queryAction" =>  ["sqlQuery" => <required> ::String]
    ], ...]
```

## `triggers = [["schedule" =>  ["expression" =>  ::String]], ...]`
A list of triggers. A trigger causes data set content to be populated at a specified time or time interval. The list of triggers can be empty or contain up to five **DataSetTrigger** objects.

## `tags = [[ ... ], ...]`
Metadata which can be used to manage the data set.
```
 tags = [[
        "key" => <required> ::String,
        "value" => <required> ::String
    ], ...]
```

# Returns

`CreateDatasetResponse`

# Exceptions

`InvalidRequestException`, `ResourceAlreadyExistsException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException` or `LimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CreateDataset)
"""
@inline create_dataset(aws::AWSConfig=default_aws_config(); args...) = create_dataset(aws, args)

@inline create_dataset(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/datasets", args)

@inline create_dataset(args) = create_dataset(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.create_dataset_content
    create_dataset_content([::AWSConfig], arguments::Dict)
    create_dataset_content([::AWSConfig]; datasetName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/datasets/{datasetName}/content", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/datasets/{datasetName}/content", datasetName=)

# CreateDatasetContent Operation

Creates the content of a data set by applying an SQL action.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CreateDatasetContent)
"""
@inline create_dataset_content(aws::AWSConfig=default_aws_config(); args...) = create_dataset_content(aws, args)

@inline create_dataset_content(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/datasets/{datasetName}/content", args)

@inline create_dataset_content(args) = create_dataset_content(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.create_datastore
    create_datastore([::AWSConfig], arguments::Dict)
    create_datastore([::AWSConfig]; datastoreName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/datastores", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/datastores", datastoreName=, <keyword arguments>)

# CreateDatastore Operation

Creates a data store, which is a repository for messages.

# Arguments

## `datastoreName = ::String` -- *Required*
The name of the data store.

## `retentionPeriod = [ ... ]`
How long, in days, message data is kept for the data store.
```
 retentionPeriod = [
        "unlimited" =>  ::Bool,
        "numberOfDays" =>  ::Int
    ]
```

## `tags = [[ ... ], ...]`
Metadata which can be used to manage the data store.
```
 tags = [[
        "key" => <required> ::String,
        "value" => <required> ::String
    ], ...]
```

# Returns

`CreateDatastoreResponse`

# Exceptions

`InvalidRequestException`, `ResourceAlreadyExistsException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException` or `LimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CreateDatastore)
"""
@inline create_datastore(aws::AWSConfig=default_aws_config(); args...) = create_datastore(aws, args)

@inline create_datastore(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/datastores", args)

@inline create_datastore(args) = create_datastore(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.create_pipeline
    create_pipeline([::AWSConfig], arguments::Dict)
    create_pipeline([::AWSConfig]; pipelineName=, pipelineActivities=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/pipelines", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/pipelines", pipelineName=, pipelineActivities=, <keyword arguments>)

# CreatePipeline Operation

Creates a pipeline. A pipeline consumes messages from one or more channels and allows you to process the messages before storing them in a data store.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of the pipeline.

## `pipelineActivities = [[ ... ], ...]` -- *Required*
A list of pipeline activities.

The list can be 1-25 **PipelineActivity** objects. Activities perform transformations on your messages, such as removing, renaming, or adding message attributes; filtering messages based on attribute values; invoking your Lambda functions on messages for advanced processing; or performing mathematical transformations to normalize device data.
```
 pipelineActivities = [[
        "channel" =>  [
            "name" => <required> ::String,
            "channelName" => <required> ::String,
            "next" =>  ::String
        ],
        "lambda" =>  [
            "name" => <required> ::String,
            "lambdaName" => <required> ::String,
            "batchSize" => <required> ::Int,
            "next" =>  ::String
        ],
        "datastore" =>  [
            "name" => <required> ::String,
            "datastoreName" => <required> ::String
        ],
        "addAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> ::Dict{String,String},
            "next" =>  ::String
        ],
        "removeAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "selectAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "filter" =>  [
            "name" => <required> ::String,
            "filter" => <required> ::String,
            "next" =>  ::String
        ],
        "math" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "math" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceRegistryEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceShadowEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ]
    ], ...]
```

## `tags = [[ ... ], ...]`
Metadata which can be used to manage the pipeline.
```
 tags = [[
        "key" => <required> ::String,
        "value" => <required> ::String
    ], ...]
```

# Returns

`CreatePipelineResponse`

# Exceptions

`InvalidRequestException`, `ResourceAlreadyExistsException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException` or `LimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/CreatePipeline)
"""
@inline create_pipeline(aws::AWSConfig=default_aws_config(); args...) = create_pipeline(aws, args)

@inline create_pipeline(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/pipelines", args)

@inline create_pipeline(args) = create_pipeline(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.delete_channel
    delete_channel([::AWSConfig], arguments::Dict)
    delete_channel([::AWSConfig]; channelName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/channels/{channelName}", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/channels/{channelName}", channelName=)

# DeleteChannel Operation

Deletes the specified channel.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel to delete.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DeleteChannel)
"""
@inline delete_channel(aws::AWSConfig=default_aws_config(); args...) = delete_channel(aws, args)

@inline delete_channel(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/channels/{channelName}", args)

@inline delete_channel(args) = delete_channel(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.delete_dataset
    delete_dataset([::AWSConfig], arguments::Dict)
    delete_dataset([::AWSConfig]; datasetName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/datasets/{datasetName}", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/datasets/{datasetName}", datasetName=)

# DeleteDataset Operation

Deletes the specified data set.

You do not have to delete the content of the data set before you perform this operation.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set to delete.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DeleteDataset)
"""
@inline delete_dataset(aws::AWSConfig=default_aws_config(); args...) = delete_dataset(aws, args)

@inline delete_dataset(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/datasets/{datasetName}", args)

@inline delete_dataset(args) = delete_dataset(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.delete_dataset_content
    delete_dataset_content([::AWSConfig], arguments::Dict)
    delete_dataset_content([::AWSConfig]; datasetName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/datasets/{datasetName}/content", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/datasets/{datasetName}/content", datasetName=, <keyword arguments>)

# DeleteDatasetContent Operation

Deletes the content of the specified data set.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set whose content is deleted.

## `versionId = ::String`
The version of the data set whose content is deleted. You can also use the strings "\$LATEST" or "\$LATEST_SUCCEEDED" to delete the latest or latest successfully completed data set. If not specified, "\$LATEST_SUCCEEDED" is the default.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DeleteDatasetContent)
"""
@inline delete_dataset_content(aws::AWSConfig=default_aws_config(); args...) = delete_dataset_content(aws, args)

@inline delete_dataset_content(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/datasets/{datasetName}/content", args)

@inline delete_dataset_content(args) = delete_dataset_content(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.delete_datastore
    delete_datastore([::AWSConfig], arguments::Dict)
    delete_datastore([::AWSConfig]; datastoreName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/datastores/{datastoreName}", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/datastores/{datastoreName}", datastoreName=)

# DeleteDatastore Operation

Deletes the specified data store.

# Arguments

## `datastoreName = ::String` -- *Required*
The name of the data store to delete.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DeleteDatastore)
"""
@inline delete_datastore(aws::AWSConfig=default_aws_config(); args...) = delete_datastore(aws, args)

@inline delete_datastore(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/datastores/{datastoreName}", args)

@inline delete_datastore(args) = delete_datastore(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.delete_pipeline
    delete_pipeline([::AWSConfig], arguments::Dict)
    delete_pipeline([::AWSConfig]; pipelineName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/pipelines/{pipelineName}", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/pipelines/{pipelineName}", pipelineName=)

# DeletePipeline Operation

Deletes the specified pipeline.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of the pipeline to delete.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DeletePipeline)
"""
@inline delete_pipeline(aws::AWSConfig=default_aws_config(); args...) = delete_pipeline(aws, args)

@inline delete_pipeline(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/pipelines/{pipelineName}", args)

@inline delete_pipeline(args) = delete_pipeline(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.describe_channel
    describe_channel([::AWSConfig], arguments::Dict)
    describe_channel([::AWSConfig]; channelName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/channels/{channelName}", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/channels/{channelName}", channelName=, <keyword arguments>)

# DescribeChannel Operation

Retrieves information about a channel.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel whose information is retrieved.

## `includeStatistics = ::Bool`
If true, include statistics about the channel in the response.

# Returns

`DescribeChannelResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DescribeChannel)
"""
@inline describe_channel(aws::AWSConfig=default_aws_config(); args...) = describe_channel(aws, args)

@inline describe_channel(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/channels/{channelName}", args)

@inline describe_channel(args) = describe_channel(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.describe_dataset
    describe_dataset([::AWSConfig], arguments::Dict)
    describe_dataset([::AWSConfig]; datasetName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/datasets/{datasetName}", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/datasets/{datasetName}", datasetName=)

# DescribeDataset Operation

Retrieves information about a data set.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set whose information is retrieved.

# Returns

`DescribeDatasetResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DescribeDataset)
"""
@inline describe_dataset(aws::AWSConfig=default_aws_config(); args...) = describe_dataset(aws, args)

@inline describe_dataset(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/datasets/{datasetName}", args)

@inline describe_dataset(args) = describe_dataset(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.describe_datastore
    describe_datastore([::AWSConfig], arguments::Dict)
    describe_datastore([::AWSConfig]; datastoreName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/datastores/{datastoreName}", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/datastores/{datastoreName}", datastoreName=, <keyword arguments>)

# DescribeDatastore Operation

Retrieves information about a data store.

# Arguments

## `datastoreName = ::String` -- *Required*
The name of the data store

## `includeStatistics = ::Bool`
If true, include statistics about the data store in the response.

# Returns

`DescribeDatastoreResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DescribeDatastore)
"""
@inline describe_datastore(aws::AWSConfig=default_aws_config(); args...) = describe_datastore(aws, args)

@inline describe_datastore(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/datastores/{datastoreName}", args)

@inline describe_datastore(args) = describe_datastore(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.describe_logging_options
    describe_logging_options([::AWSConfig], arguments::Dict)
    describe_logging_options([::AWSConfig]; )

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/logging", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/logging", )

# DescribeLoggingOptions Operation

Retrieves the current settings of the AWS IoT Analytics logging options.

# Arguments

# Returns

`DescribeLoggingOptionsResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DescribeLoggingOptions)
"""
@inline describe_logging_options(aws::AWSConfig=default_aws_config(); args...) = describe_logging_options(aws, args)

@inline describe_logging_options(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/logging", args)

@inline describe_logging_options(args) = describe_logging_options(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.describe_pipeline
    describe_pipeline([::AWSConfig], arguments::Dict)
    describe_pipeline([::AWSConfig]; pipelineName=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/pipelines/{pipelineName}", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/pipelines/{pipelineName}", pipelineName=)

# DescribePipeline Operation

Retrieves information about a pipeline.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of the pipeline whose information is retrieved.

# Returns

`DescribePipelineResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/DescribePipeline)
"""
@inline describe_pipeline(aws::AWSConfig=default_aws_config(); args...) = describe_pipeline(aws, args)

@inline describe_pipeline(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/pipelines/{pipelineName}", args)

@inline describe_pipeline(args) = describe_pipeline(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.get_dataset_content
    get_dataset_content([::AWSConfig], arguments::Dict)
    get_dataset_content([::AWSConfig]; datasetName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/datasets/{datasetName}/content", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/datasets/{datasetName}/content", datasetName=, <keyword arguments>)

# GetDatasetContent Operation

Retrieves the contents of a data set as pre-signed URIs.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set whose contents are retrieved.

## `versionId = ::String`
The version of the data set whose contents are retrieved. You can also use the strings "\$LATEST" or "\$LATEST_SUCCEEDED" to retrieve the contents of the latest or latest successfully completed data set. If not specified, "\$LATEST_SUCCEEDED" is the default.

# Returns

`GetDatasetContentResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/GetDatasetContent)
"""
@inline get_dataset_content(aws::AWSConfig=default_aws_config(); args...) = get_dataset_content(aws, args)

@inline get_dataset_content(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/datasets/{datasetName}/content", args)

@inline get_dataset_content(args) = get_dataset_content(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.list_channels
    list_channels([::AWSConfig], arguments::Dict)
    list_channels([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/channels", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/channels", <keyword arguments>)

# ListChannels Operation

Retrieves a list of channels.

# Arguments

## `nextToken = ::String`
The token for the next set of results.

## `maxResults = ::Int`
The maximum number of results to return in this request.

The default value is 100.

# Returns

`ListChannelsResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/ListChannels)
"""
@inline list_channels(aws::AWSConfig=default_aws_config(); args...) = list_channels(aws, args)

@inline list_channels(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/channels", args)

@inline list_channels(args) = list_channels(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.list_datasets
    list_datasets([::AWSConfig], arguments::Dict)
    list_datasets([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/datasets", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/datasets", <keyword arguments>)

# ListDatasets Operation

Retrieves information about data sets.

# Arguments

## `nextToken = ::String`
The token for the next set of results.

## `maxResults = ::Int`
The maximum number of results to return in this request.

The default value is 100.

# Returns

`ListDatasetsResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/ListDatasets)
"""
@inline list_datasets(aws::AWSConfig=default_aws_config(); args...) = list_datasets(aws, args)

@inline list_datasets(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/datasets", args)

@inline list_datasets(args) = list_datasets(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.list_datastores
    list_datastores([::AWSConfig], arguments::Dict)
    list_datastores([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/datastores", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/datastores", <keyword arguments>)

# ListDatastores Operation

Retrieves a list of data stores.

# Arguments

## `nextToken = ::String`
The token for the next set of results.

## `maxResults = ::Int`
The maximum number of results to return in this request.

The default value is 100.

# Returns

`ListDatastoresResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/ListDatastores)
"""
@inline list_datastores(aws::AWSConfig=default_aws_config(); args...) = list_datastores(aws, args)

@inline list_datastores(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/datastores", args)

@inline list_datastores(args) = list_datastores(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.list_pipelines
    list_pipelines([::AWSConfig], arguments::Dict)
    list_pipelines([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/pipelines", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/pipelines", <keyword arguments>)

# ListPipelines Operation

Retrieves a list of pipelines.

# Arguments

## `nextToken = ::String`
The token for the next set of results.

## `maxResults = ::Int`
The maximum number of results to return in this request.

The default value is 100.

# Returns

`ListPipelinesResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/ListPipelines)
"""
@inline list_pipelines(aws::AWSConfig=default_aws_config(); args...) = list_pipelines(aws, args)

@inline list_pipelines(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/pipelines", args)

@inline list_pipelines(args) = list_pipelines(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.list_tags_for_resource
    list_tags_for_resource([::AWSConfig], arguments::Dict)
    list_tags_for_resource([::AWSConfig]; resourceArn=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/tags", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/tags", resourceArn=)

# ListTagsForResource Operation

Lists the tags (metadata) which you have assigned to the resource.

# Arguments

## `resourceArn = ::String` -- *Required*
The ARN of the resource whose tags you want to list.

# Returns

`ListTagsForResourceResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException`, `LimitExceededException` or `ResourceNotFoundException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/ListTagsForResource)
"""
@inline list_tags_for_resource(aws::AWSConfig=default_aws_config(); args...) = list_tags_for_resource(aws, args)

@inline list_tags_for_resource(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/tags", args)

@inline list_tags_for_resource(args) = list_tags_for_resource(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.put_logging_options
    put_logging_options([::AWSConfig], arguments::Dict)
    put_logging_options([::AWSConfig]; loggingOptions=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "PUT", "/logging", arguments::Dict)
    iotanalytics([::AWSConfig], "PUT", "/logging", loggingOptions=)

# PutLoggingOptions Operation

Sets or updates the AWS IoT Analytics logging options.

Note that if you update the value of any `loggingOptions` field, it takes up to one minute for the change to take effect. Also, if you change the policy attached to the role you specified in the roleArn field (for example, to correct an invalid policy) it takes up to 5 minutes for that change to take effect.

# Arguments

## `loggingOptions = [ ... ]` -- *Required*
The new values of the AWS IoT Analytics logging options.
```
 loggingOptions = [
        "roleArn" => <required> ::String,
        "level" => <required> "ERROR",
        "enabled" => <required> ::Bool
    ]
```

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/PutLoggingOptions)
"""
@inline put_logging_options(aws::AWSConfig=default_aws_config(); args...) = put_logging_options(aws, args)

@inline put_logging_options(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "PUT", "/logging", args)

@inline put_logging_options(args) = put_logging_options(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.run_pipeline_activity
    run_pipeline_activity([::AWSConfig], arguments::Dict)
    run_pipeline_activity([::AWSConfig]; pipelineActivity=, payloads=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/pipelineactivities/run", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/pipelineactivities/run", pipelineActivity=, payloads=)

# RunPipelineActivity Operation

Simulates the results of running a pipeline activity on a message payload.

# Arguments

## `pipelineActivity = [ ... ]` -- *Required*
The pipeline activity that is run. This must not be a 'channel' activity or a 'datastore' activity because these activities are used in a pipeline only to load the original message and to store the (possibly) transformed message. If a 'lambda' activity is specified, only short-running Lambda functions (those with a timeout of less than 30 seconds or less) can be used.
```
 pipelineActivity = [
        "channel" =>  [
            "name" => <required> ::String,
            "channelName" => <required> ::String,
            "next" =>  ::String
        ],
        "lambda" =>  [
            "name" => <required> ::String,
            "lambdaName" => <required> ::String,
            "batchSize" => <required> ::Int,
            "next" =>  ::String
        ],
        "datastore" =>  [
            "name" => <required> ::String,
            "datastoreName" => <required> ::String
        ],
        "addAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> ::Dict{String,String},
            "next" =>  ::String
        ],
        "removeAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "selectAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "filter" =>  [
            "name" => <required> ::String,
            "filter" => <required> ::String,
            "next" =>  ::String
        ],
        "math" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "math" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceRegistryEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceShadowEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ]
    ]
```

## `payloads = [blob, ...]` -- *Required*
The sample message payloads on which the pipeline activity is run.

# Returns

`RunPipelineActivityResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/RunPipelineActivity)
"""
@inline run_pipeline_activity(aws::AWSConfig=default_aws_config(); args...) = run_pipeline_activity(aws, args)

@inline run_pipeline_activity(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/pipelineactivities/run", args)

@inline run_pipeline_activity(args) = run_pipeline_activity(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.sample_channel_data
    sample_channel_data([::AWSConfig], arguments::Dict)
    sample_channel_data([::AWSConfig]; channelName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "GET", "/channels/{channelName}/sample", arguments::Dict)
    iotanalytics([::AWSConfig], "GET", "/channels/{channelName}/sample", channelName=, <keyword arguments>)

# SampleChannelData Operation

Retrieves a sample of messages from the specified channel ingested during the specified timeframe. Up to 10 messages can be retrieved.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel whose message samples are retrieved.

## `maxMessages = ::Int`
The number of sample messages to be retrieved. The limit is 10, the default is also 10.

## `startTime = timestamp`
The start of the time window from which sample messages are retrieved.

## `endTime = timestamp`
The end of the time window from which sample messages are retrieved.

# Returns

`SampleChannelDataResponse`

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/SampleChannelData)
"""
@inline sample_channel_data(aws::AWSConfig=default_aws_config(); args...) = sample_channel_data(aws, args)

@inline sample_channel_data(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "GET", "/channels/{channelName}/sample", args)

@inline sample_channel_data(args) = sample_channel_data(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.start_pipeline_reprocessing
    start_pipeline_reprocessing([::AWSConfig], arguments::Dict)
    start_pipeline_reprocessing([::AWSConfig]; pipelineName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/pipelines/{pipelineName}/reprocessing", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/pipelines/{pipelineName}/reprocessing", pipelineName=, <keyword arguments>)

# StartPipelineReprocessing Operation

Starts the reprocessing of raw message data through the pipeline.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of the pipeline on which to start reprocessing.

## `startTime = timestamp`
The start time (inclusive) of raw message data that is reprocessed.

## `endTime = timestamp`
The end time (exclusive) of raw message data that is reprocessed.

# Returns

`StartPipelineReprocessingResponse`

# Exceptions

`ResourceNotFoundException`, `ResourceAlreadyExistsException`, `InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/StartPipelineReprocessing)
"""
@inline start_pipeline_reprocessing(aws::AWSConfig=default_aws_config(); args...) = start_pipeline_reprocessing(aws, args)

@inline start_pipeline_reprocessing(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/pipelines/{pipelineName}/reprocessing", args)

@inline start_pipeline_reprocessing(args) = start_pipeline_reprocessing(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.tag_resource
    tag_resource([::AWSConfig], arguments::Dict)
    tag_resource([::AWSConfig]; resourceArn=, tags=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "POST", "/tags", arguments::Dict)
    iotanalytics([::AWSConfig], "POST", "/tags", resourceArn=, tags=)

# TagResource Operation

Adds to or modifies the tags of the given resource. Tags are metadata which can be used to manage a resource.

# Arguments

## `resourceArn = ::String` -- *Required*
The ARN of the resource whose tags will be modified.

## `tags = [[ ... ], ...]` -- *Required*
The new or modified tags for the resource.
```
 tags = [[
        "key" => <required> ::String,
        "value" => <required> ::String
    ], ...]
```

# Returns

`TagResourceResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException`, `LimitExceededException` or `ResourceNotFoundException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/TagResource)
"""
@inline tag_resource(aws::AWSConfig=default_aws_config(); args...) = tag_resource(aws, args)

@inline tag_resource(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "POST", "/tags", args)

@inline tag_resource(args) = tag_resource(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.untag_resource
    untag_resource([::AWSConfig], arguments::Dict)
    untag_resource([::AWSConfig]; resourceArn=, tagKeys=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "DELETE", "/tags", arguments::Dict)
    iotanalytics([::AWSConfig], "DELETE", "/tags", resourceArn=, tagKeys=)

# UntagResource Operation

Removes the given tags (metadata) from the resource.

# Arguments

## `resourceArn = ::String` -- *Required*
The ARN of the resource whose tags will be removed.

## `tagKeys = [::String, ...]` -- *Required*
The keys of those tags which will be removed.

# Returns

`UntagResourceResponse`

# Exceptions

`InvalidRequestException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException`, `LimitExceededException` or `ResourceNotFoundException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/UntagResource)
"""
@inline untag_resource(aws::AWSConfig=default_aws_config(); args...) = untag_resource(aws, args)

@inline untag_resource(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "DELETE", "/tags", args)

@inline untag_resource(args) = untag_resource(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.update_channel
    update_channel([::AWSConfig], arguments::Dict)
    update_channel([::AWSConfig]; channelName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "PUT", "/channels/{channelName}", arguments::Dict)
    iotanalytics([::AWSConfig], "PUT", "/channels/{channelName}", channelName=, <keyword arguments>)

# UpdateChannel Operation

Updates the settings of a channel.

# Arguments

## `channelName = ::String` -- *Required*
The name of the channel to be updated.

## `retentionPeriod = [ ... ]`
How long, in days, message data is kept for the channel.
```
 retentionPeriod = [
        "unlimited" =>  ::Bool,
        "numberOfDays" =>  ::Int
    ]
```

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/UpdateChannel)
"""
@inline update_channel(aws::AWSConfig=default_aws_config(); args...) = update_channel(aws, args)

@inline update_channel(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "PUT", "/channels/{channelName}", args)

@inline update_channel(args) = update_channel(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.update_dataset
    update_dataset([::AWSConfig], arguments::Dict)
    update_dataset([::AWSConfig]; datasetName=, actions=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "PUT", "/datasets/{datasetName}", arguments::Dict)
    iotanalytics([::AWSConfig], "PUT", "/datasets/{datasetName}", datasetName=, actions=, <keyword arguments>)

# UpdateDataset Operation

Updates the settings of a data set.

# Arguments

## `datasetName = ::String` -- *Required*
The name of the data set to update.

## `actions = [[ ... ], ...]` -- *Required*
A list of "DatasetAction" objects. Only one action is supported at this time.
```
 actions = [[
        "actionName" =>  ::String,
        "queryAction" =>  ["sqlQuery" => <required> ::String]
    ], ...]
```

## `triggers = [["schedule" =>  ["expression" =>  ::String]], ...]`
A list of "DatasetTrigger" objects. The list can be empty or can contain up to five **DataSetTrigger** objects.

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/UpdateDataset)
"""
@inline update_dataset(aws::AWSConfig=default_aws_config(); args...) = update_dataset(aws, args)

@inline update_dataset(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "PUT", "/datasets/{datasetName}", args)

@inline update_dataset(args) = update_dataset(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.update_datastore
    update_datastore([::AWSConfig], arguments::Dict)
    update_datastore([::AWSConfig]; datastoreName=, <keyword arguments>)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "PUT", "/datastores/{datastoreName}", arguments::Dict)
    iotanalytics([::AWSConfig], "PUT", "/datastores/{datastoreName}", datastoreName=, <keyword arguments>)

# UpdateDatastore Operation

Updates the settings of a data store.

# Arguments

## `datastoreName = ::String` -- *Required*
The name of the data store to be updated.

## `retentionPeriod = [ ... ]`
How long, in days, message data is kept for the data store.
```
 retentionPeriod = [
        "unlimited" =>  ::Bool,
        "numberOfDays" =>  ::Int
    ]
```

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException` or `ThrottlingException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/UpdateDatastore)
"""
@inline update_datastore(aws::AWSConfig=default_aws_config(); args...) = update_datastore(aws, args)

@inline update_datastore(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "PUT", "/datastores/{datastoreName}", args)

@inline update_datastore(args) = update_datastore(default_aws_config(), args)

"""
    using AWSSDK.IoTAnalytics.update_pipeline
    update_pipeline([::AWSConfig], arguments::Dict)
    update_pipeline([::AWSConfig]; pipelineName=, pipelineActivities=)

    using AWSCore.Services.iotanalytics
    iotanalytics([::AWSConfig], "PUT", "/pipelines/{pipelineName}", arguments::Dict)
    iotanalytics([::AWSConfig], "PUT", "/pipelines/{pipelineName}", pipelineName=, pipelineActivities=)

# UpdatePipeline Operation

Updates the settings of a pipeline.

# Arguments

## `pipelineName = ::String` -- *Required*
The name of the pipeline to update.

## `pipelineActivities = [[ ... ], ...]` -- *Required*
A list of "PipelineActivity" objects.

The list can be 1-25 **PipelineActivity** objects. Activities perform transformations on your messages, such as removing, renaming or adding message attributes; filtering messages based on attribute values; invoking your Lambda functions on messages for advanced processing; or performing mathematical transformations to normalize device data.
```
 pipelineActivities = [[
        "channel" =>  [
            "name" => <required> ::String,
            "channelName" => <required> ::String,
            "next" =>  ::String
        ],
        "lambda" =>  [
            "name" => <required> ::String,
            "lambdaName" => <required> ::String,
            "batchSize" => <required> ::Int,
            "next" =>  ::String
        ],
        "datastore" =>  [
            "name" => <required> ::String,
            "datastoreName" => <required> ::String
        ],
        "addAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> ::Dict{String,String},
            "next" =>  ::String
        ],
        "removeAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "selectAttributes" =>  [
            "name" => <required> ::String,
            "attributes" => <required> [::String, ...],
            "next" =>  ::String
        ],
        "filter" =>  [
            "name" => <required> ::String,
            "filter" => <required> ::String,
            "next" =>  ::String
        ],
        "math" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "math" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceRegistryEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ],
        "deviceShadowEnrich" =>  [
            "name" => <required> ::String,
            "attribute" => <required> ::String,
            "thingName" => <required> ::String,
            "roleArn" => <required> ::String,
            "next" =>  ::String
        ]
    ], ...]
```

# Exceptions

`InvalidRequestException`, `ResourceNotFoundException`, `InternalFailureException`, `ServiceUnavailableException`, `ThrottlingException` or `LimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/iotanalytics-2017-11-27/UpdatePipeline)
"""
@inline update_pipeline(aws::AWSConfig=default_aws_config(); args...) = update_pipeline(aws, args)

@inline update_pipeline(aws::AWSConfig, args) = AWSCore.Services.iotanalytics(aws, "PUT", "/pipelines/{pipelineName}", args)

@inline update_pipeline(args) = update_pipeline(default_aws_config(), args)

end # module IoTAnalytics

#==============================================================================#
# End of file
#==============================================================================#


text:
# Everything is experimental! If you want to use it, you need to import it yourself.
# If you find problems, please open an issue.

using Optim
using Turing
using StatsBase
using LinearAlgebra

#      Run like
# using Turing
# @model height(heights) = begin
#     μ ~ Normal(178, 20)
#     σ ~ Uniform(0, 50)
#     heights .~ Normal(μ, σ)
# end
# m = height(d2.height)
# res = quap(m)

# Find the MAP via optimization and get the information matrix at that point.
# Look if your solution converged. Sometimes even solutions that didn't converge
# might be pretty good, on the other hand, just because the solver converged that
# doesn't mean you got the point you are looking for; this isn't even global
# optimization. In any case, trying other optimization methods might help.
function quap(model::Turing.Model, args...; kwargs...)
    opt = optimize(model, MAP(), args...; kwargs...)

    coef = opt.values.array
    var_cov_matrix = informationmatrix(opt)
    sym_var_cov_matrix = Symmetric(var_cov_matrix)  # lest MvNormal complains, loudly
    converged = Optim.converged(opt.optim_result)

    distr = if length(coef) == 1
        Normal(coef[1], √sym_var_cov_matrix[1])  # Normal expects stddev
    else
        MvNormal(coef, sym_var_cov_matrix)       # MvNormal expects variance matrix
    end

    (coef = coef, vcov = sym_var_cov_matrix, converged = converged, distr = distr)
end