############################################################################################
struct SMC2Constructor{W<:AbstractConstructor,J<:AbstractConstructor} <:AbstractConstructor
    "Constructor for (online) state propagation over time."
    propagation::W
    "Constructor for jittering step."
    jitter::J
    function SMC2Constructor(propagation::W, jitter::J) where {W<:AbstractConstructor,J<:AbstractConstructor}
        return new{W,J}(propagation, jitter)
    end
end
function get_sym(constructor::SMC2Constructor)
#    return get_sym(constructor.jitter)
    sym1 = get_sym(constructor.jitter)
    sym2 = get_sym(constructor.propagation)
    #Return unique symbols as tuple
    sym = unique((sym1..., sym2...))
    return Tuple(sym)
end

function SMC2(
    propagation::J, jitter::W; kwargs...
) where {J<:AbstractConstructor, W<:AbstractConstructor}
    return SMC(SMC2Constructor(propagation, jitter); kwargs...)
end

############################################################################################
"""
$(TYPEDEF)

SMC2 Kernel, consisting of a kernel for the particle trajectory and kernel for all model parameter.

# Fields
$(TYPEDFIELDS)
"""
struct SMC2Kernel{P<:ParticleFilter,M<:PMCMC} <: BaytesCore.AbstractAlgorithm
    "Kernel for (online) state propagation over time."
    pf::P   #θ dimension
    "Kernel for jittering step."
    pmcmc::M   #particle dimension
    function SMC2Kernel(pf::P, pmcmc::M) where {P<:ParticleFilter,M<:PMCMC}
        @argcheck isa(pf.tune.referencing, Marginal) "Cannot use Conditional Filter in θ dimension, use Marginal referencing instead"
        return new{P,M}(pf, pmcmc)
    end
end

function result!(kernel::SMC2Kernel, result::L) where {L<:ℓObjectiveResult}
    result!(kernel.pmcmc, result)
    return nothing
end

function get_result(kernel::SMC2Kernel)
    return get_result(kernel.pmcmc)
end
function predict(_rng::Random.AbstractRNG, kernel::SMC2Kernel, objective::Objective)
    return predict(_rng, kernel.pf, objective)
end

function infer(
    _rng::Random.AbstractRNG, kernel::SMC2Kernel, model::ModelWrapper, data::D
) where {D}
    return BaytesCore.infer(_rng, kernel.pf, model, data)
end
function infer(
    _rng::Random.AbstractRNG,
    diagnostics::Type{AbstractDiagnostics},
    kernel::SMC2Kernel,
    model::ModelWrapper,
    data::D,
) where {D}
    return BaytesCore.infer(_rng, diagnostics, kernel.pmcmc, model, data)
end

############################################################################################

function jitterkernel(particles::SMCParticles{<:SMC2Kernel}, iter::Int64)
    return particles.kernel[iter].pmcmc
end

function SMCweight(_rng::Random.AbstractRNG, objective::Objective, algorithm::SMC2Kernel, cumweightsₜ₋₁)
    #!NOTE: Use cumulative weight from propagated pf, taking into account new data
    cumweightsₜ = objective.temperature * algorithm.pf.particles.ℓobjective.cumulative #get_ℓweight(algorithm.pf)
    #!NOTE: cumweightsₜ₋₁ already has correct temperature at t-1
    return cumweightsₜ, cumweightsₜ - cumweightsₜ₋₁
end
function SMCreweight(_rng::Random.AbstractRNG, objective::Objective, algorithm::SMC2Kernel, cumweightsₜ₋₁)
    #!NOTE: Run Particle Filter wrt to jittered model parameter, and compute new weight
    propose!(
        _rng,
        algorithm.pf,
        objective.model,
        objective.data,
        objective.temperature,
        BaytesCore.UpdateTrue()
    )
    cumweightsₜ = objective.temperature * algorithm.pf.particles.ℓobjective.cumulative #get_ℓweight(algorithm.pf)
    #!NOTE: cumweightsₜ₋₁ already has correct temperature at t-1
    return cumweightsₜ, cumweightsₜ - cumweightsₜ₋₁
end

############################################################################################
"""
$(SIGNATURES)
Initialize SMC² sampler.

# Examples
```julia
```

"""
function SMCParticles(
    _rng::Random.AbstractRNG,
    kernel::SMC2Constructor,
    objective::Objective,  #Initial Model with prior (Parameter values are overwritten by prior samples)
    info::BaytesCore.SampleDefault,
    tune::SMCTune
)
    @unpack propagation, jitter = kernel
    ## Initialize individual PF and PMCMC algorithm
    @unpack model, data, temperature = objective
    @unpack Ntuning = tune
    Nchains = tune.chains.Nchains
    modelᵗᵉᵐᵖ = deepcopy(model)
    tagged = Tagged(model, propagation.sym)
    ModelWrappers.fill!(
        modelᵗᵉᵐᵖ,
        tagged,
        BaytesCore.to_NamedTuple(
            keys(tagged.parameter),
            getfield(modelᵗᵉᵐᵖ.val, keys(tagged.parameter)[1])[1:maximum(size(data))],
        ),
    )
    ## Initialize individual Models ~ Assign copies for  Model and Algorithms ~ necessary in initiation so no pointer error
    modelᵛ = [deepcopy(modelᵗᵉᵐᵖ) for _ in Base.OneTo(Nchains)]
    algorithmᵛ = [
        SMC2Kernel(
            propagation(_rng, modelᵛ[iter], data, temperature, info),
            jitter(_rng, modelᵛ[iter], data, temperature, info),
        ) for iter in Base.OneTo(Nchains)
    ]
    ## Assign weights
    weights = BaytesCore.ParameterWeights(Nchains)
    ## Assign buffer for inplace resampling
    buffer = SMCBuffer(
        _rng, algorithmᵛ[1].pmcmc, modelᵗᵉᵐᵖ, data, Nchains
    )
    ## Loop through all models
    #Polyester.@batch per=thread minbatch=tune.batchsize for iter in eachindex(algorithmᵛ)
    Base.Threads.@threads for iter in eachindex(algorithmᵛ)
        ## Tune PMCMC algorithm
        propose!(_rng, algorithmᵛ[iter].pmcmc, modelᵛ[iter], data, temperature, BaytesCore.UpdateTrue())
        for _ in 2:Ntuning
            propose!(_rng, algorithmᵛ[iter].pmcmc, modelᵛ[iter], data, temperature, tune.capture)
        end
        ## Assign a Particle Filter for tuned theta
        propose!(_rng, algorithmᵛ[iter].pf, modelᵛ[iter], data, temperature, BaytesCore.UpdateTrue())
        ## Calculate initial weights
        _objective = Objective(modelᵛ[iter], objective.data, tune.tagged, objective.temperature)
        buffer.cumweights[iter], weights.buffer[iter] = SMCweight(
            _rng,
            _objective,
            algorithmᵛ[iter],
            _objective(_objective.model.val)
        )
    end
    ## Normalize weights
    weights(weights.buffer)
    ## Return container
    return SMCParticles(modelᵛ, algorithmᵛ, weights, buffer)
end

############################################################################################
"""
$(SIGNATURES)
Propagate data forward over time.

# Examples
```julia
```

"""
function propagate!(_rng::Random.AbstractRNG, particles::SMCParticles{<:SMC2Kernel}, tune::SMCTune, data::D, temperature::F) where {D, F<:AbstractFloat}
    #!NOTE: Here, smc.buffer.jitterdiagnostics will not be updated, and for SMCDiagnostics, last available jitterdiagnostics are provided.
    ## Propagate series forward with recent particle
    #Polyester.@batch per=thread minbatch=tune.batchsize for iter in eachindex(particles.model)
    Base.Threads.@threads for iter in eachindex(particles.model)
        #_, diagnostics = propagate!(
        propagate!(
            _rng,
            particles.kernel[iter].pf,
            particles.model[iter],
            data,
            temperature
        )
        #particles.buffer.predictions[iter] = diagnostics.base.prediction
    end
    return nothing
end

############################################################################################
#export
export
    SMC2,
    SMC2Constructor,
    SMC2Kernel,
    SMCweight,
    resample!,
    propagate!,
    weight!


# starting to add exports here

global WORKERPOOL = WorkerPool()

"""
    $SIGNATURES

For use with `multiproc`, nominal use is a worker pool of all processes available above and including 2..., but will return single process [1;] if only the first processes is available.
"""
function setWorkerPool!(pool::Vector{Int}=1 < nprocs() ? setdiff(procs(), [1;]) : [1;])
  global WORKERPOOL
  WORKERPOOL = WorkerPool(pool)
end

function getWorkerPool()
  global WORKERPOOL
  return WORKERPOOL
end

## =============================================================================================
# Iterate over variables in clique
## =============================================================================================

"""
    $SIGNATURES

Dev Notes
- part of refactoring fmcmc.
- function seems excessive
"""
function compileFMCMessages(fgl::AbstractDFG,
                            lbls::Vector{Symbol},
                            solveKey::Symbol,
                            logger=ConsoleLogger())
  #
  d = Dict{Symbol,TreeBelief}()
  for vsym in lbls
    vari = DFG.getVariable(fgl,vsym)
    d[vsym] = TreeBelief(vari, solveKey)
  end
  return d
end

function doFMCIteration(fgl::AbstractDFG,
                        vsym::Symbol,
                        solveKey::Symbol,
                        cliq::TreeClique,
                        fmsgs,
                        N::Int,
                        dbg::Bool,
                        needFreshMeasurements::Bool=true,
                        logger=ConsoleLogger()  )
  #

  vert = DFG.getVariable(fgl, vsym)
  if !getSolverData(vert, solveKey).ismargin
    # potprod = nothing
    densPts, ipc = predictbelief(fgl, vsym, :, needFreshMeasurements=needFreshMeasurements, N=N, dbg=dbg, logger=logger)

    if 0 < length(densPts)
      # TODO --  can we remove this duplicate getVert?
      # updvert = DFG.getVariable(fgl, vsym)
      setValKDE!(vert, densPts, true, ipc)
      # TODO perhaps more debugging inside `predictbelief`?
    end
  end
  nothing
end

"""
    $(SIGNATURES)

Iterate successive approximations of clique marginal beliefs by means
of the stipulated proposal convolutions and products of the functional objects
for tree clique `cliq`.
"""
function fmcmc!(fgl::AbstractDFG,
                cliq::TreeClique,
                fmsgs::Vector{<:LikelihoodMessage},
                lbls::Vector{Symbol},
                solveKey::Symbol,
                N::Int,
                MCMCIter::Int,
                dbg::Bool=false,
                logger=ConsoleLogger(),
                multithreaded::Bool=false  )
  #
  with_logger(logger) do
    @info "---------- successive fnc approx ------------$(getLabel(cliq))"
  end
  # repeat several iterations of functional Gibbs sampling for fixed point convergence
  if length(lbls) == 1
      MCMCIter=1
  end
  # mcmcdbg = Array{CliqGibbsMC,1}()

  # burn-in loop for outer Gibbs
  for iter in 1:MCMCIter
    # iterate through each of the variables, KL-divergence tolerence would be nice test here
    with_logger(logger) do
      @info "#$(iter)\t -- "
    end
    # dbgvals = !dbg ? nothing : CliqGibbsMC([], Symbol[])

    needFreshMeasurements = iter == 1 || getSolverParams(fgl).alwaysFreshMeasurements

    # outer Gibbs cycle
    for vsym in lbls
        doFMCIteration(fgl, vsym, solveKey, cliq, fmsgs, N, dbg, needFreshMeasurements, logger)
    end
    # !dbg ? nothing : push!(mcmcdbg, dbgvals)
  end

  # populate dictionary for return NBPMessage in multiple dispatch
  msgdict = compileFMCMessages(fgl, lbls, solveKey, logger)

  return msgdict
end

## =============================================================================================
# Up solve
## =============================================================================================

"""
    $(SIGNATURES)

Perform computations required for the upward message passing during belief propation on the Bayes (Junction) tree.
This function is usually called as via remote_call for multiprocess dispatch.

Notes
- `fg` factor graph,
- `tree` Bayes tree,
- `cliq` which cliq to perform the computation on,
- `parent` the parent clique to where the upward message will be sent,
- `childmsgs` is for any incoming messages from child cliques.

DevNotes
- FIXME total rewrite with AMP #41 and RoME #244 in mind
"""
function upGibbsCliqueDensity(dfg::AbstractDFG,
                              cliq::TreeClique,
                              solveKey::Symbol,
                              inmsgs,
                              N::Int=getSolverParams(dfg).N,
                              dbg::Bool=false,
                              iters::Int=3,
                              logger=ConsoleLogger()  ) # where {T, T2}
  #
  with_logger(logger) do
    @info "up w $(length(inmsgs)) msgs"
  end
  # TODO -- some weirdness with: d,. = d = ., nothing
  # mcmcdbg = Array{CliqGibbsMC,1}()
  d = Dict{Symbol,TreeBelief}()

  # priorprods = Vector{CliqGibbsMC}()

  cliqdata = getCliqueData(cliq)

  # use nested structure for more efficient Chapman-Kolmogorov solution approximation
  if false
    IDS = [cliqdata.frontalIDs;cliqdata.separatorIDs] #inp.cliq.attributes["frontalIDs"]
    d = fmcmc!(dfg, cliq, inmsgs, IDS, solveKey, N, iters, dbg, logger)
  else
    # NOTE -- previous mistake, must iterate over directsvarIDs also (or incorporate once at the right time)
    # NOTE -- double up on directs to allow inflation to take proper affect, see #1051
    d = fmcmc!(dfg, cliq, inmsgs, cliqdata.directFrtlMsgIDs, solveKey, N, 1, dbg, logger, true)

    if length(cliqdata.msgskipIDs) > 0
      dd = fmcmc!(dfg, cliq, inmsgs, cliqdata.msgskipIDs, solveKey, N, 1, dbg, logger, true)
      for md in dd d[md[1]] = md[2]; end
    end
    if length(cliqdata.itervarIDs) > 0
      ddd = fmcmc!(dfg, cliq, inmsgs, cliqdata.itervarIDs, solveKey, N, iters, dbg, logger, false)
      for md in ddd d[md[1]] = md[2]; end
    end
    if length(cliqdata.directPriorMsgIDs) > 0
      doids = setdiff(cliqdata.directPriorMsgIDs, cliqdata.msgskipIDs)
      dddd = fmcmc!(dfg, cliq, inmsgs, doids, solveKey, N, 1, dbg, logger, true)
      for md in dddd d[md[1]] = md[2]; end
    end
  end

  return d
end

## =============================================================================================
# Down solve
## =============================================================================================

## ============================================================================
# Initialization is slightly different and likely to be consolidated
## ============================================================================

"""
    $SIGNATURES

Cycle through var order and initialize variables as possible in `subfg::AbstractDFG`.
Return true if something was updated.

Notes:
- assumed `subfg` is a subgraph containing only the factors that can be used.
  - including the required up or down messages
- intended for both up and down initialization operations.

Dev Notes
- Should monitor updates based on the number of inferred & solvable dimensions
"""
function cycleInitByVarOrder!(subfg::AbstractDFG,
                              varorder::Vector{Symbol};
                              solveKey::Symbol=:default,
                              logger=ConsoleLogger()  )
  #
  with_logger(logger) do
    @info "cycleInitByVarOrder! -- varorder=$(varorder)"
  end
  retval = false
  count = 1
  while count > 0
    count = 0
    for vsym in varorder
      var = DFG.getVariable(subfg, vsym)
      isinit = isInitialized(var, solveKey)
      with_logger(logger) do
        @info "var.label=$(var.label) is initialized=$(isinit)"
      end
      doautoinit!(subfg, [var;], solveKey=solveKey, logger=logger)
      if isinit != isInitialized(var, solveKey)
        count += 1
        retval = true
      end
    end
  end
  with_logger(logger) do
    @info "cycleInitByVarOrder!, retval=$(retval)"
  end
  flush(logger.stream)
  return retval
end

#


import Statistics
using LinearAlgebra
using ChainRulesCore
using ChainRules
using NNlib
using Umlaut
import Umlaut: record_primitive!, isprimitive, BaseCtx
import Umlaut: record_or_recurse!, Tracer, trace!, getcode

const V = Umlaut.Variable
const broadcasted = Broadcast.broadcasted

include("helpers.jl")
include("utils.jl")
include("deprecated.jl")
include("cr_api.jl")
include("rulesets.jl")
include("grad.jl")
include("update.jl")
include("gradcheck.jl")

Base.show(io::IO, tape::Tape{GradCtx}) = Umlaut.show_compact(io, tape)


using .JSort
import Base: read, read!, show, length, sum
using DataFrames
import PyPlot; const plt = PyPlot
using LaTeXStrings

macro tostring(arg)
    string(arg)
end

abstract type AbstractDetector end
abstract type AbstractDetector1D <: AbstractDetector end
abstract type AbstractDetector2D <: AbstractDetector end

length(d::AbstractDetector1D) = length(d.ref[])

function read(path::AbstractString, V::Type{W}; T=UInt32) where W <: AbstractDetector
    detector = open(path, "r") do io
        read(io, V, T=T)
    end
end

function read(io::IO, V::Type{<:AbstractDetector1D}; T=UInt32)
    len = stat(io).size / sizeof(T) |> Int
    data = Vector{Float32}(undef, len)
    for i in 1:len
        data[i] = read(io, T) |> Float32
    end
    pattern = r".*chn(?<c>\d+).*"
    m = match(pattern, io.name)
    chn = if isnothing(m) 0 else parse(Int, m[:c]) end
    V(data, chn)
end

function read(io::IO, V::Type{W} where W <: AbstractDetector2D; T=UInt32)
    # Assumes binary format int UInt32
    len = stat(io).size / 2sizeof(T) |> Int
    data = Matrix{Float32}(undef, len, 2)
    for i in 1:len
        data[i, 1] = read(io, T) |> Float32
        data[i, 2] = read(io, T) |> Float32
    end

    pattern = r".*b(?<b>\d)f(?<f>\d).*"
    m = match(pattern, io.name)
    if isnothing(m)
        pattern = r".*f(?<f>\d).*"
        m = match(pattern, io.name)
        if isnothing(m)
            f = b = 0
        else
            f = parse(Int, m[:f])
            b = 0
        end
    else
        f, b = parse.(Int, (m[:f], m[:b]))
    end
    V(data[:, 1], data[:, 2], f, b)
end

function write(path::AbstractString, V::AbstractDetector)
    open(path, "w") do io
        write(io, V)
    end
end

function write(io::IO, V::AbstractDetector1D)
    write(io, V.ref[])
end

function write(io::IO, V::AbstractDetector2D)
    for i in eachindex(V.Xref[])
        write(io, V.Xref[][i], V.Yref[][i])
    end
end

function histogram(X::AbstractDetector2D; nbins=1000)
    edges = histogram_edges(X.Xref[], X.Yref[], nbins=nbins)
    histogram(X, edges...), edges
end

function histogram(X::AbstractDetector2D, xedges, yedges)
    #histogram(X.Xref[], X.Yref[], xedges, yedges)
    histogram(X.Xref[] .+ rand(length(X.Xref[])),
              X.Yref[] .+ rand(length(X.Yref[])),
              xedges, yedges)
end

function histogram(X::AbstractDetector2D, edges)
    histogram(X.Xref[], X.Yref[], edges...)
end

function histogram(X::AbstractDetector1D, edges::AbstractRange)
    # Why do I need to add a random number to remove histogram artifacts?
    histogram(X.ref[] .+ rand(length(X.ref[])), edges)
    #histogram(X.ref[], edges)
end
function histogram(X::AbstractDetector1D; nbins=1000)
    edges = range(minimum(X.ref[]), stop=maximum(X.ref[]), length=nbins)
    histogram(X, edges), edges
end

function (calib::AbstractCalibrator1D)(d::T; inplace::Bool=true)::T where {T<:AbstractDetector1D}
    poly = calib.poly[d.channel]
    if inplace
        d.ref[] .= poly.(d.ref[])
        return d
    else
        d = deepcopy(d)
        d.ref[] .= poly.(d.ref[])
        return d
    end
end

function show(io::IO, V::AbstractDetector1D)
    println("Channel: $(V.channel)")
    show(io, DataFrame(e=V.ref[]), allrows=false)
end
## Concrete implementations

struct ParticleDetector <: AbstractDetector2D
    e::Vector{Float32}
    Δe::Vector{Float32}
    front::Int
    back::Int
    Xref::Ref{Vector{Float32}}
    Yref::Ref{Vector{Float32}}
    function ParticleDetector(a::AbstractVector, b::AbstractVector, c::Number, d::Number)
        new(a, b, c, d, Ref(a), Ref(b))
    end
end
ParticleDetector(path::AbstractString; T=UInt32) = read(path, ParticleDetector, T=T)

# function write(io::IO, d::ParticleDetector)
#     for i in 1:length(d.e)
#         write(io, d.e[i], d.Δe[i])
#     end
# end

function show(io::IO, detector::AbstractDetector2D)
    println("Front: $(detector.front)\nBack: $(detector.back)")
    show(DataFrame(e=detector.Xref[], Δe=detector.Yref[]), allrows=false)
end

function (calib::ParticleCalibrator)(d::ParticleDetector)
    poly = calib.poly[d.front, d.back]
    if poly.var == :e
        map!(poly, d.e, d.e)
    elseif poly.var == :Δe
        map!(poly, d.Δe, d.Δe)
    else
        throw("Unable to deduce how to apply calibration. Specify `e` or `Δe`")
    end
end

function (calib::ParticleCalibrator)(d::ParticleDetector, var::Symbol)
    poly = calib.poly[d.front, d.back]
    if var == :e
        map!(poly, d.e, d.e)
    elseif var == :Δe
        map!(poly, d.Δe, d.Δe)
    else
        throw("Variable must be `e` or `Δe`, not $var")
    end
end

function calibrateall(path::AbstractString; epath="coefficients_e.txt", Δepath="coefficients_de.txt")
    j(x) = joinpath(path, x)
    ce = ParticleCalibrator(epath, var=:e)
    cde = ParticleCalibrator(Δepath, var=:Δe)

    for f in 1:8
        totale = Float32[]
        totalde = Float32[]
        for b in 1:8
            detector = ParticleDetector(j("edeb$(b)f$(f).bin"))
            ce(detector)
            cde(detector)
            append!(totale, detector.e)
            append!(totalde, detector.Δe)
        end
        write(j("edef$(f).bin"), ParticleDetector(totale, totalde, f, 0))
    end
end

function plot(d::ParticleDetector; ax=nothing)
    (fig, ax) = if isnothing(ax)
        plt.subplots()
    else
        ax.figure, ax
    end
    mat, edges = histogram(d, nbins=1000)
    #ax.scatter(d.e, d.Δe, marker=",", s=1, alpha=0.5)
    ax.pcolormesh(edges[1], edges[2], mat |> transpose .|> log10)
    ax.set_xlabel(L"E\quad [keV?]")
    ax.set_ylabel(L"\Delta E\quad [keV?]")
    ax.set_title("f$(d.front)b$(d.back)")
end

### Gamma Detector

struct GammaDetector <: AbstractDetector1D
    e::Vector{Float32}
    channel::Int
    ref::Ref{Vector{Float32}}
    GammaDetector(e::AbstractVector) = new(e, 0, Ref(e))
    GammaDetector(e::AbstractVector, chn::Int) = new(e, chn, Ref(e))
end
GammaDetector(path::AbstractString; T=Float32) = read(path, GammaDetector, T=T)
GammaDetector() = GammaDetector(Float32[])

function plot(D::AbstractDetector1D; ax=nothing, nbins=1000)
    (fig, ax) = if isnothing(ax)
        plt.subplots()
    else
        ax.figure, ax
    end
    hist, edges = histogram(D, nbins=nbins)
    ax.step(edges, hist)
    ax.set_yscale("log")
    ax.set_title("Channel $(D.channel)")
end

### Time 'Detector'

struct TimeDetector <: AbstractDetector1D
    t::Vector{Float32}
    channel::Int
    ref::Ref{Vector{Float32}}
    TimeDetector(t::AbstractVector) = new(t, 0, Ref(t))
    TimeDetector(t::AbstractVector, chn::Int) = new(t, chn, Ref(t))
end
TimeDetector(path::AbstractString; T=Float32) = read(path, TimeDetector, T=T)
TimeDetector() = TimeDetector(Float32[])

### Channel 'Detector'
struct ChannelDetector{T} <: AbstractDetector
    data::Vector{T}
end

function ChannelDetector(name::AbstractString, path="."; T=Float32)
    pattern = Regex("^" * name * "_chn(?<chn>\\d+).*")
    ChannelDetector(pattern, path, T=T)
end

function ChannelDetector(pattern::Regex, path="."; T=Float32)
    data = nothing
    for fname in readdir(path)
        m = match(pattern, fname)
        if !isnothing(m)
            name = splitext(basename(fname))[1]
            types = parsetype(name)
            Tcalib = types[1]
            if isnothing(data)
                data = Tcalib[Tcalib() for i in 1:33]
            end
            chn = parse(Int, m[:chn])
            data[chn] = Tcalib(joinpath(path, fname), T=T)
        end
    end
    ChannelDetector(data)
end

function show(io::IO, d::ChannelDetector)
    for chn in eachindex(d.data)
        println(io, "Channel $chn:\t$(length(d.data[chn]))")
    end
end

function plot(D::ChannelDetector; ax=nothing, nbins=1000)
    (fig, ax) = if isnothing(ax)
        plt.subplots()
    else
        ax.figure, ax
    end
    hists = Vector{Int}[]
    edges = histogram_edges(D.data[1].ref[], nbins=nbins)
    chns = goodchannels(D)
    for chn in chns
        hist = histogram(D.data[chn], edges)
        push!(hists, hist)
    end
    hists = cat(hists'..., dims=1)
    ax.pcolormesh(edges, 1:length(chns)+1, hists .|> log10)
    #ax.matshow(hists .|> log10)
    #ax.pcolormesh(1:length(edges), 1:length(chns), hists .|> log10)
    ax.set_yticks(1.5:length(chns)+1-0.5)
    ax.set_yticklabels(chns)
    ax
end

function goodchannels(D::ChannelDetector; deviationlimit=10)
    good_lengths = [chn for (chn, d) in enumerate(D.data) if length(d) > 100 && chn != 32 && chn != 30]
    lengths = length.(D.data[chn] for chn in good_lengths)
    means = mean.(D.data[chn].ref[] for chn in good_lengths)
    M = mean(means)
    deviation = @. 1/(lengths - 1)*√((means - M)^2)
    good_lengths[deviation .< deviationlimit]
end

function histogram(D::ChannelDetector; nbins=1000)
    chns = goodchannels(D)
    edges = histogram_edges(D.data[1].ref[], nbins=nbins)
    [histogram(D.data[chn], edges) for chn in chns], edges
end

function histogram(D::ChannelDetector, edges::AbstractRange)
    [histogram(D.data[chn], edges) for chn in goodchannels(D)]
end

function align(detector::ChannelDetector; nbins=1000, kwargs...)
    d = deepcopy(detector)
    calib, invcalib = align!(d, nbins=nbins; kwargs...)
    d, calib, invcalib
end

function align!(detector::ChannelDetector{GammaDetector}; nbins=1000, kwargs...)
    signal = pyimport("scipy.signal")
    smoother(x) = signal.savgol_filter(x, 51, 4)

    chns = goodchannels(detector)
    hist, edges = histogram(detector, nbins=nbins)
    coeff, invcoeff = alignspectra(hist, edges; highsmoother=smoother, kwargs...)
    def = if (length(coeff[1]) == 2)
        [0.0 1.0] else [0.0 1.0 0.0] end
    coefficients = repeat(def, outer=(33, 1))
    invcoefficients = copy(coefficients)

    coefficients[chns, :] .= hcat(coeff...)'
    calib = GammaCalibrator(coefficients)
    calib(detector)
    invcalib = GammaCalibrator(coefficients)
    calib, invcalib
end

function align!(detector::ChannelDetector{TimeDetector}; nbins=1000, kwargs...)
    chns = goodchannels(detector)
    hist, edges = histogram(detector, nbins=nbins)
    coeff = aligntime(hist, edges; kwargs...)
    coefficients = repeat([0.0 1.0], outer=(33, 1))
    coefficients[chns, :] .= hcat(coeff...)'

    calib = TimeCalibrator(coefficients)
    calib(detector)
    calib, calib
end

function (c::AbstractCalibrator1D)(d::ChannelDetector; inplace::Bool=true)::ChannelDetector
    if inplace
        map(c, d.data)
        return d
    else
        d = deepcopy(d)
        map(c, d.data)
        return d
    end
end

function sum(d::ChannelDetector{T}) where T
    T(cat([det.ref[] for det in d.data]...; dims=1))
end

getindex(X::ChannelDetector, i) = getindex(X.data, i)
getindex(X::ChannelDetector, I...) = getindex(X.data, I...)
setindex!(X::ChannelDetector, i) = setindex!(X.data, i)
setindex!(X::ChannelDetector, I...) = setindex!(X.data, I...)

### General loading
struct Detector <: AbstractDetector
    data::Array{Float32}
    meta::Dict{Symbol, Int}
end
function Detector(path::AbstractString; T=UInt32)
    data, meta = readres(path, T=T)
    meta = Dict(i => j for (i, j) in meta if !isnothing(j))
    Detector(data, meta)
end

function plot(D::Detector; ax=nothing, nbins=1000)
    if ndims(D.data) == 1
        plot1D(D, ax=ax, nbins=nbins)
    elseif ndims(D.data) == 2
        plot2D(D, ax=ax, nbins=nbins)
    end
end

function plot1D(D::Detector; ax=nothing, nbins=1000)
    (fig, ax) = if isnothing(ax)
        plt.subplots()
    else
        ax.figure, ax
    end
    hist, edges = histogram(D.data, nbins=nbins)
    ax.step(edges, hist)
    ax.set_title(maketitle(D.meta))
end

function plot2D(D::Detector; ax=nothing, nbins=1000)
    (fig, ax) = if isnothing(ax)
        plt.subplots()
    else
        ax.figure, ax
    end
    mat, edges = histogram(D.data, nbins=nbins)
    ax.pcolormesh(edges[1], edges[2], mat |> transpose .|> log10)
    ax.set_title(maketitle(D.meta))
end

function readres(path::AbstractString; T=UInt32)
    open(path, "r") do io
        ndim = filedimensions(io.name)
        data = if ndim == 1
            readres1D(io, T=T)
        elseif ndim == 2
            readres2D(io, T=T)
        else
            error("Impossible dimension $ndim")
        end
        data, parsefilename(path)
    end
end

function readres1D(io::IO; T=UInt32)::Vector{Float32}
    len = stat(io).size / sizeof(T) |> Int
    data = Vector{Float32}(undef, len)
    for i in 1:len
        data[i] = read(io, T) |> Float32
    end
    data
end

function readres2D(io::IO; T=UInt32)::Matrix{Float32}
    len = stat(io).size / 2sizeof(T) |> Int
    data = Matrix{Float32}(undef, len, 2)
    for i in 1:len
        data[i, 1] = read(io, T) |> Float32
        data[i, 2] = read(io, T) |> Float32
    end
    data
end

function parsefilename(fname::AbstractString)
    fb_pattern = r".*b(?<b>\d)f(?<f>\d).*"
    chn_pattern = r".*chn(?<chn>\d+).*"
    f_pattern = r".*f(?<f>\d).*"
    m = match(fb_pattern, fname)
    f = b = chn = nothing
    if isnothing(m)
        m = match(f_pattern, fname)
        if !isnothing(m)
            f = parse(Int, m[:f])
        end
    else
        f, b = parse.(Int, (m[:f], m[:b]))
    end
    m = match(chn_pattern, fname)
    if !isnothing(m)
        chn = parse(Int, m[:chn])
    end
    (:f => f, :b => b, :chn => chn)
end

function filedimensions(fname::AbstractString)::Int
    if occursin("edeb", fname)
        return 2
    end
    underscores = count("_", fname)
    all = if occursin(r"\d", fname) 1 else 0 end
    underscores - all + 1
end

function maketitle(dict::Dict{Symbol, Int})
    @show dict
    title = ""
    k = keys(dict)
    if :f in k
        title *= "f$(dict[:f])"
    end
    if :b in k
        title *= " b$(dict[:b])"
    end
    if :chn in k
        title *= "Channel $(dict[:chn])"
    end
    title
end

function parsetype(fname::AbstractString)::Vector{DataType}
    tokens = split(fname, "_")
    types = DataType[]
    for token in tokens
        if token == "eg"
            push!(types, GammaDetector)
        elseif token == "t" || token == "tc"
            push!(types, TimeDetector)
        end
    end
    types
end


# Path Primitives

# From form.jl:
# Path
# ----

# An implementation of the SVG path mini-language.

abstract type PathOp end

struct MoveAbsPathOp <: PathOp
    to::Vec
end

function assert_pathop_tokens_len(op_type, tokens, i, needed)
    provided = length(tokens) - i + 1
    provided < needed &&
            error("In path $(op_type) requires $(needed) argumens but only $(provided) provided.")
end

function parsepathop(::Type{MoveAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(MoveAbsPathOp, tokens, i, 2)
    op = MoveAbsPathOp((x_measure(tokens[i]), y_measure(tokens[i + 1])))
    return (op, i + 2)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::MoveAbsPathOp) =
        MoveAbsPathOp(resolve(box, units, t, p.to))

struct MoveRelPathOp <: PathOp
    to::Vec
end

function parsepathop(::Type{MoveRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(MoveRelPathOp, tokens, i, 2)
    op = MoveRelPathOp((tokens[i], tokens[i + 1]))
    return (op, i + 2)
end

function resolve_offset(box::AbsoluteBox, units::UnitBox, t::Transform, p::Vec)
    absp = resolve(box, units, t, p)
    zer0 = resolve(box, units, t, (0w, 0h))
    return (absp[1] - zer0[1], absp[2] - zer0[2])
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::MoveRelPathOp) =
        MoveRelPathOp(resolve_offset(box, units, t, p.to))

struct ClosePathOp <: PathOp
end

parsepathop(::Type{ClosePathOp}, tokens::AbstractArray, i) = (ClosePathOp(), i)

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::ClosePathOp) = p

struct LineAbsPathOp <: PathOp
    to::Vec
end

function parsepathop(::Type{LineAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(LineAbsPathOp, tokens, i, 2)
    op = LineAbsPathOp((x_measure(tokens[i]), y_measure(tokens[i + 1])))
    return (op, i + 2)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::LineAbsPathOp) =
        LineAbsPathOp(resolve(box, units, t, p.to))

struct LineRelPathOp <: PathOp
    to::Vec
end

function parsepathop(::Type{LineRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(LineRelPathOp, tokens, i, 2)
    op = LineRelPathOp((x_measure(tokens[i]), y_measure(tokens[i + 1])))
    return (op, i + 2)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::LineRelPathOp) =
        LineRelPathOp(resolve(box, units, t, p.to))

struct HorLineAbsPathOp <: PathOp
    x::Measure
end

function parsepathop(::Type{HorLineAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(HorLineAbsPathOp, tokens, i, 1)
    op = HorLineAbsPathOp(x_measure(tokens[i]))
    return (op, i + 1)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::HorLineAbsPathOp) =
        HorLineAbsPathOp(resolve(box, units, t, (p.x, 0mm))[1])

struct HorLineRelPathOp <: PathOp
    Δx::Measure
end

function parsepathop(::Type{HorLineRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(HorLineRelPathOp, tokens, i, 1)
    op = HorLineRelPathOp(x_measure(tokens[i]))
    return (op, i + 1)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::HorLineRelPathOp) =
        HorLineRelPathOp(resolve(box, units, t, p.Δx))

struct VertLineAbsPathOp <: PathOp
    y::Measure
end

function parsepathop(::Type{VertLineAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(VertLineAbsPathOp, tokens, i, 1)
    op = VertLineAbsPathOp(y_measure(tokens[i]))
    return (op, i + 1)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::VertLineAbsPathOp) =
        VertLineAbsPathOp(resolve(box, units, t, (0mm, p.y))[2])

struct VertLineRelPathOp <: PathOp
    Δy::Measure
end

function parsepathop(::Type{VertLineRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(VertLineRelPathOp, tokens, i, 1)
    op = VertLineRelPathOp(y_measure(tokens[i]))
    return (op, i + 1)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::VertLineRelPathOp) =
        VertLineAbsPathOp(resolve(box, units, t, (0mmm, p.Δy))[2])

struct CubicCurveAbsPathOp <: PathOp
    ctrl1::Vec
    ctrl2::Vec
    to::Vec
end

function parsepathop(::Type{CubicCurveAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(CubicCurveAbsPathOp, tokens, i, 6)
    op = CubicCurveAbsPathOp((tokens[i],     tokens[i + 1]),
                             (tokens[i + 2], tokens[i + 3]),
                             (tokens[i + 4], tokens[i + 5]))
    return (op, i + 6)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::CubicCurveAbsPathOp) =
        CubicCurveAbsPathOp(
            resolve(box, units, t, p.ctrl1),
            resolve(box, units, t, p.ctrl2),
            resolve(box, units, t, p.to))

struct CubicCurveRelPathOp <: PathOp
    ctrl1::Vec
    ctrl2::Vec
    to::Vec
end

function parsepathop(::Type{CubicCurveRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(CubicCurveRelPathOp, tokens, i, 6)
    op = CubicCurveRelPathOp((tokens[i],     tokens[i + 1]),
                             (tokens[i + 2], tokens[i + 3]),
                             (tokens[i + 4], tokens[i + 5]))
    return (op, i + 6)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::CubicCurveRelPathOp) =
        CubicCurveRelPathOp(
            resolve(box, units, t, p.ctrl1),
            resolve(box, units, t, p.ctrl2),
            resolve(box, units, t, p.to))

struct CubicCurveShortAbsPathOp <: PathOp
    ctrl2::Vec
    to::Vec
end

function parsepathop(::Type{CubicCurveShortAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(CubicCurveShortAbsPathOp, tokens, i, 4)
    op = CubicCurveShortAbsPathOp((x_measure(tokens[i]),     y_measure(tokens[i + 1])),
                                  (x_measure(tokens[i + 2]), y_measure(tokens[i + 3])))
    return (op, i + 4)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::CubicCurveShortAbsPathOp) =
        CubicCurveShortAbsPathOp(
            resolve_offset(box, units, t, p.ctrl2),
            resolve_offset(box, units, t, p.to))

struct CubicCurveShortRelPathOp <: PathOp
    ctrl2::Vec
    to::Vec
end

function parsepathop(::Type{CubicCurveShortRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(CubicCurveShortRelPathOp, tokens, i, 4)
    op = CubicCurveShortRelPathOp((x_measure(tokens[i]),     y_measure(tokens[i + 1])),
                                  (x_measure(tokens[i + 2]), y_measure(tokens[i + 3])))
    return (op, i + 4)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::CubicCurveShortRelPathOp) =
        CubicCurveShortRelPathOp(
            resolve(box, units, t, p.ctrl2),
            resolve(box, units, t, p.to))

struct QuadCurveAbsPathOp <: PathOp
    ctrl1::Vec
    to::Vec
end

function parsepathop(::Type{QuadCurveAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(QuadCurveAbsPathOp, tokens, i, 4)
    op = QuadCurveAbsPathOp((tokens[i],     tokens[i + 1]),
                            (tokens[i + 2], tokens[i + 3]))
    return (op, i + 4)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::QuadCurveAbsPathOp) =
        QuadCurveAbsPathOp(
            resolve(box, units, t, p.ctrl1),
            resolve(box, units, t, p.to))

struct QuadCurveRelPathOp <: PathOp
    ctrl1::Vec
    to::Vec
end

function parsepathop(::Type{QuadCurveRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(QuadCurveRelPathOp, tokens, i, 4)
    op = QuadCurveRelPathOp((tokens[i],     tokens[i + 1]),
                            (tokens[i + 2], tokens[i + 3]))
    return (op, i + 4)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::QuadCurveRelPathOp) =
        QuadCurveRelPathOp(
            (resolve(box, units, t, p.ctrl1[1]),
             resolve(box, units, t, p.ctrl1[2])),
            (resolve(box, units, t, p.to[1]),
             resolve(box, units, t, p.to[2])))

struct QuadCurveShortAbsPathOp <: PathOp
    to::Vec
end

function parsepathop(::Type{QuadCurveShortAbsPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(QuadCurveShortAbsPathOp, tokens, i, 2)
    op = QuadCurveShortAbsPathOp((tokens[i], tokens[i + 1]))
    return (op, i + 2)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::QuadCurveShortAbsPathOp) =
        QuadCurveShortAbsPathOp(resolve(box, units, t, p.to))

struct QuadCurveShortRelPathOp <: PathOp
    to::Vec
end

function parsepathop(::Type{QuadCurveShortRelPathOp}, tokens::AbstractArray, i)
    assert_pathop_tokens_len(QuadCurveShortRelPathOp, tokens, i, 2)
    op = QuadCurveShortRelPathOp((tokens[i], tokens[i + 1]))
    return (op, i + 2)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::QuadCurveShortRelPathOp) =
        QuadCurveShortRelPathOp(
            (resolve(box, units, t, p.to[1]),
             resolve(box, units, t, p.to[2])))

struct ArcAbsPathOp <: PathOp
    rx::Measure
    ry::Measure
    rotation::Float64
    largearc::Bool
    sweep::Bool
    to::Vec
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::ArcAbsPathOp) =
        ArcAbsPathOp(
            resolve(box, units, t, p.rx),
            resolve(box, units, t, p.ry),
            p.rotation,
            p.largearc,
            p.sweep,
            resolve(box, units, t, p.to))

struct ArcRelPathOp <: PathOp
    rx::Measure
    ry::Measure
    rotation::Float64
    largearc::Bool
    sweep::Bool
    to::Vec
end

function parsepathop(::Type{T}, tokens::AbstractArray, i) where T <: Union{ArcAbsPathOp, ArcRelPathOp}
    assert_pathop_tokens_len(T, tokens, i, 7)

    if isa(tokens[i + 3], Bool)
        largearc = tokens[i + 3]
    elseif tokens[i + 3] == 0
        largearc = false
    elseif tokens[i + 3] == 1
        largearc = true
    else
        error("largearc argument to the arc path operation must be boolean")
    end

    if isa(tokens[i + 4], Bool)
        sweep = tokens[i + 4]
    elseif tokens[i + 4] == 0
        sweep = false
    elseif tokens[i + 4] == 1
        sweep = true
    else
        error("sweep argument to the arc path operation must be boolean")
    end

    isa(tokens[i + 2], Number) || error("path arc operation requires a numerical rotation")

    op = T(x_measure(tokens[i]),
           y_measure(tokens[i + 1]),
           convert(Float64, tokens[i + 2]),
           largearc, sweep,
           (x_measure(tokens[i + 5]), y_measure(tokens[i + 6])))

    return (op, i + 7)
end

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::ArcRelPathOp) =
        ArcRelPathOp(
            resolve(box, units, t, p.rx),
            resolve(box, units, t, p.ry),
            p.rotation,
            p.largearc,
            p.sweep,
            (resolve(box, units, t, p.to[1]),
             resolve(box, units, t, p.to[2])))

const path_ops = Dict(
     :M => MoveAbsPathOp,
     :m => MoveRelPathOp,
     :Z => ClosePathOp,
     :z => ClosePathOp,
     :L => LineAbsPathOp,
     :l => LineRelPathOp,
     :H => HorLineAbsPathOp,
     :h => HorLineRelPathOp,
     :V => VertLineAbsPathOp,
     :v => VertLineRelPathOp,
     :C => CubicCurveAbsPathOp,
     :c => CubicCurveRelPathOp,
     :S => CubicCurveShortAbsPathOp,
     :s => CubicCurveShortRelPathOp,
     :Q => QuadCurveAbsPathOp,
     :q => QuadCurveRelPathOp,
     :T => QuadCurveShortAbsPathOp,
     :t => QuadCurveShortRelPathOp,
     :A => ArcAbsPathOp,
     :a => ArcRelPathOp
)

# A path is an array of symbols, numbers, and measures following SVGs path
# mini-language.
function parsepath(tokens::AbstractArray)
    ops = PathOp[]
    last_op_type = nothing
    i = 1
    while i <= length(tokens)
        tok = tokens[i]
        strt = i
        if isa(tok, Symbol)
            if !haskey(path_ops, tok)
                error("$(tok) is not a valid path operation")
            else
                op_type = path_ops[tok]
                i += 1
                op, i = parsepathop(op_type, tokens, i)
                push!(ops, op)
                last_op_type = op_type
            end
        else
            op, i = parsepathop(last_op_type, tokens, i)
            push!(ops, op)
        end
    end

    return ops
end

struct PathPrimitive <: FormPrimitive
    ops::Vector{PathOp}
end

const Path = Form{PathPrimitive}

path(tokens::AbstractArray, tag=empty_tag) = Path([PathPrimitive(parsepath(tokens))], tag)

path(tokens::AbstractArray{T}, tag=empty_tag) where T <: AbstractArray =
        Path([PathPrimitive(parsepath(ts)) for ts in tokens], tag)

resolve(box::AbsoluteBox, units::UnitBox, t::Transform, p::PathPrimitive) =
        PathPrimitive([resolve(box, units, t, op) for op in p.ops])

# TODO: boundingbox

# From cairo_backends.jl:

function draw(img::Image, prim::PathPrimitive)
    for op in prim.ops
        draw_path_op(img, op)
    end
    fillstroke(img)
end

draw_path_op(img::Image, op::MoveAbsPathOp) = move_to(img, op.to)
draw_path_op(img::Image, op::MoveRelPathOp) = rel_move_to(img, op.to)
draw_path_op(img::Image, op::ClosePathOp)   = close_path(img)
draw_path_op(img::Image, op::LineAbsPathOp) = line_to(img, op.to)
draw_path_op(img::Image, op::LineRelPathOp) = rel_line_to(img, op.to)

function draw_path_op(img::Image, op::HorLineAbsPathOp)
    pos = current_point(img)
    line_to(img, (op.x, pos.y))
end

draw_path_op(img::Image, op::HorLineRelPathOp) = rel_line_to(img, (op.Δx, 0.0mm))

function draw_path_op(img::Image, op::VertLineAbsPathOp)
    pos = current_point(img)
    line_to(img, (pos.x, op.y))
end

draw_path_op(img::Image, op::VertLineRelPathOp) = rel_line_to(img, (0.0mm, op.Δy))

function draw_path_op(img::Image, op::CubicCurveAbsPathOp)
    curve_to(img, op.ctrl1, op.ctrl2, op.to)
    img.last_ctrl2_point = op.ctrl2
end

function draw_path_op(img::Image, op::CubicCurveRelPathOp)
    xy = current_point(img)
    rel_curve_to(img, op.ctrl1, op.ctrl2, op.to)
    img.last_ctrl2_point = (op.ctrl2[1] + xy[1], op.ctrl2[2] + xy[2])
end

function draw_path_op(img::Image, op::CubicCurveShortAbsPathOp)
    xy = current_point(img)
    ctrl1 = img.last_ctrl2_point
    if ctrl1 === nothing
        ctrl1 = xy
    else
        ctrl1 = (2*xy[1] - ctrl1[1], 2*xy[2] - ctrl1[2])
    end
    curve_to(img, ctrl1, op.ctrl2, op.to)
    img.last_ctrl2_point = op.ctrl2
end

function draw_path_op(img::Image, op::CubicCurveShortRelPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = op.to[1].value, op.to[2].value

    ctrl1 = img.last_ctrl2_point
    if ctrl1 === nothing
        ctrl1 = xy
    else
        ctrl1 = (Measure(abs=(2*x1 - ctrl1[1].value) - x1),
                 Measure(abs=(2*y1 - ctrl1[2].value) - y1))
    end
    cx, cy = ctrl1[1].value, ctrl1[2].value

    rel_curve_to(img, ctrl1, op.ctrl2, op.to)
    img.last_ctrl2_point =
        (Measure(abs=op.ctrl2[1].value + xy.x.abs),
         Measure(abs=op.ctrl2[2].value + xy.y.abs))
end

function draw_path_op(img::Image, op::QuadCurveAbsPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = op.to[1].value, op.to[2].value
    cx, cy = op.ctrl1[1].value, op.ctrl1[2].value
    curve_to(img,
             (Measure(abs=(x1 + 2*cx)/3),
              Measure(abs=(y1 + 2*cy)/3)),
             (Measure(abs=(x2 + 2*cx)/3),
              Measure(abs=(y2 + 2*cy)/3)),
             op.to)
    img.last_ctrl1_point = op.ctrl1
end

function draw_path_op(img::Image, op::QuadCurveRelPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = op.to[1].value, op.to[2].value
    cx, cy = op.ctrl1[1].value, op.ctrl1[2].value
    rel_curve_to(img,
                 (Measure(abs=(x1 + 2*cx)/3),
                  Measure(abs=(y1 + 2*cy)/3)),
                 (Measure(abs=(x2 + 2*cx)/3),
                  Measure(abs=(y2 + 2*cy)/3)),
             op.to)
    img.last_ctrl1_point =
        (Measure(abs=op.ctrl1[1].value + xy.x.abs),
         Measure(abs=op.ctrl1[2].value + xy.y.abs))
end

function draw_path_op(img::Image, op::QuadCurveShortAbsPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = op.to[1].value, op.to[2].value

    ctrl1 = img.last_ctrl1_point
    if img.last_ctrl1_point === nothing
        ctrl1 = xy
    else
        ctrl1 = (Measure(abs=2*x1 - ctrl1[1].value),
                 Measure(abs=2*y1 - ctrl1[2].value))
    end
    cx, cy = ctrl1[1].value, ctrl1[2].value

    curve_to(img,
             (Measure(abs=(x1 + 2*cx)/3),
              Measure(abs=(y1 + 2*cy)/3)),
             (Measure(abs=(x2 + 2*cx)/3),
              Measure(abs=(y2 + 2*cy)/3)),
             (Measure(abs=x2), Measure(abs=y2)))
    img.last_ctrl1_point = ctrl1
end

function draw_path_op(img::Image, op::QuadCurveShortRelPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = x1 + op.to[1].value, y1 + op.to[2].value

    ctrl1 = img.last_ctrl1_point
    if ctrl1 === nothing
        ctrl1 = xy
    else
        ctrl1 = (Measure(abs=(2*x1 - ctrl1[1].value) - x1),
                 Measure(abs=(2*y1 - ctrl1[2].value) - y1))
    end
    cx, cy = ctrl1[1].value, ctrl1[2].value

    rel_curve_to(img,
                 (Measure(abs=(x1 + 2*cx)/3),
                  Measure(abs=(y1 + 2*cy)/3)),
                 (Measure(abs=(x2 + 2*cx)/3),
                  Measure(abs=(y2 + 2*cy)/3)),
                 (Measure(abs=x2), Measure(abs=y2)))
    img.last_ctrl1_point =
        (Measure(abs=op.ctrl1[1].value + x1),
         Measure(abs=op.ctrl1[2].value + y1))
end

function draw_path_op(img::Image, op::ArcAbsPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = op.to[1].value, op.to[2].value
    rx, ry = op.rx.abs, op.ry.abs
    φ = deg2rad(op.rotation)
    draw_endpoint_arc(img, rx, ry, φ, op.largearc, op.sweep, x1, y1, x2, y2)
end

function draw_path_op(img::Image, op::ArcRelPathOp)
    xy = current_point(img)
    x1, y1 = xy[1].value, xy[2].value
    x2, y2 = x1 + op.to[1].value, y1 + op.to[2].value
    rx, ry = op.rx.abs, op.ry.abs
    φ = deg2rad(op.rotation)
    draw_endpoint_arc(img, rx, ry, φ, op.largearc, op.sweep, x1, y1, x2, y2)
end

# Draw an SVG style elliptical arc
function draw_endpoint_arc(img::Image, rx::Float64, ry::Float64, φ::Float64,
                           largearc::Bool, sweep::Bool,
                           x1::Float64, y1::Float64,
                           x2::Float64, y2::Float64)
    function uvangle(ux, uy, vx, vy)
        t = (ux * vx + uy * vy) / (sqrt(ux^2 + uy^2) * sqrt(vx^2 + vy^2))
        t = max(min(t, 1.0), -1.0)
        return (ux * vy - uy * vx < 0.0 ? -1 : 1.0) * acos(t)
    end

    # From: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
    xm, ym = (x1 - x2)/2, (y1 - y2)/2
    x1p =  cos(φ) * xm + sin(φ) * ym
    y1p = -sin(φ) * xm + cos(φ) * ym

    u = (rx^2 * ry^2 - rx^2 * y1p^2 - ry^2 * x1p^2) / (rx^2 * y1p^2 + ry^2 * x1p^2)
    u = u >= 0.0 ? sqrt(u) : 0.0

    cxp =  u * (rx * y1p) / ry
    cyp = -u * (ry * x1p) / rx
    if sweep == largearc
        cxp = -cxp
        cyp = -cyp
    end
    cx = (x1 + x2)/2 + cos(φ) * cxp - sin(φ) * cyp
    cy = (y1 + y2)/2 + sin(φ) * cxp + cos(φ) * cyp

    θ1 = uvangle(1.0, 0.0, (x1p - cxp) / rx, (y1p - cyp) / ry)
    Δθ = uvangle((x1p - cxp) / rx, (y1p - cyp) / ry,
                 (-x1p - cxp) / rx, (-y1p - cyp) / ry) % (2.0*π)
    if Δθ > 0.0 && !sweep
        Δθ -= 2*π
    elseif Δθ < 0.0 && sweep
        Δθ += 2*π
    end

    Cairo.save(img.ctx)
    Cairo.translate(img.ctx,
                    absolute_native_units(img, cx),
                    absolute_native_units(img, cy))
    Cairo.rotate(img.ctx, φ)
    Cairo.scale(img.ctx, rx, ry)
    if sweep
        arc(img, 0.0, 0.0, 1.0, θ1, θ1 + Δθ)
    else
        arc_negative(img, 0.0, 0.0, 1.0, θ1, θ1 + Δθ)
    end
    Cairo.restore(img.ctx)
end

# From svg.jl:

function svg_print_path_op(io::IO, op::MoveAbsPathOp)
    print(io, 'M')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::MoveRelPathOp)
    print(io, 'm')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

svg_print_path_op(io::IO, op::ClosePathOp) = print(io, 'z')

function svg_print_path_op(io::IO, op::LineAbsPathOp)
    print(io, 'L')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::LineRelPathOp)
    print(io, 'l')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::HorLineAbsPathOp)
    print(io, 'H')
    svg_print_float(io, op.x.value)
end

function svg_print_path_op(io::IO, op::HorLineRelPathOp)
    print(io, 'h')
    svg_print_float(io, op.Δx.value)
end

function svg_print_path_op(io::IO, op::VertLineAbsPathOp)
    print(io, 'V')
    svg_print_float(io, op.y.value)
end

function svg_print_path_op(io::IO, op::VertLineRelPathOp)
    print(io, 'v')
    svg_print_float(io, op.Δy.value)
end

function svg_print_path_op(io::IO, op::CubicCurveAbsPathOp)
    print(io, 'C')
    svg_print_float(io, op.ctrl1[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl1[2].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::CubicCurveRelPathOp)
    print(io, 'c')
    svg_print_float(io, op.ctrl1[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl1[2].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::CubicCurveShortAbsPathOp)
    print(io, 'S')
    svg_print_float(io, op.ctrl2[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::CubicCurveShortRelPathOp)
    print(io, 's')
    svg_print_float(io, op.ctrl2[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl2[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::QuadCurveAbsPathOp)
    print(io, 'Q')
    svg_print_float(io, op.ctrl1[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl1[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::QuadCurveRelPathOp)
    print(io, 'q')
    svg_print_float(io, op.ctrl1[1].value)
    print(io, ' ')
    svg_print_float(io, op.ctrl1[2].value)
    print(io, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::QuadCurveShortAbsPathOp)
    print(io, 'T')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::QuadCurveShortRelPathOp)
    print(io, 't')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::ArcAbsPathOp)
    print(io, 'A')
    svg_print_float(io, op.rx.value)
    print(io, ' ')
    svg_print_float(io, op.ry.value)
    print(io, ' ')
    svg_print_float(io, op.rotation)
    print(io, ' ',
          op.largearc ? 1 : 0, ' ',
          op.sweep ? 1 : 0, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function svg_print_path_op(io::IO, op::ArcRelPathOp)
    print(io, 'a')
    svg_print_float(io, op.rx.value)
    print(io, ' ')
    svg_print_float(io, op.ry.value)
    print(io, ' ')
    svg_print_float(io, op.rotation)
    print(io, ' ',
          op.largearc ? 1 : 0, ' ',
          op.sweep ? 1 : 0, ' ')
    svg_print_float(io, op.to[1].value)
    print(io, ' ')
    svg_print_float(io, op.to[2].value)
end

function draw(img::SVG, prim::PathPrimitive, idx::Int)
    indent(img)
    print(img.out, "<path d=\"")
    for op in prim.ops
        svg_print_path_op(img.out, op)
    end
    print(img.out, '"')
    print_vector_properties(img, idx)
    print(img.out, "/>\n")
end


function ImpulseManeuver(arg0::EventDetector, arg1::AttitudeProvider, arg2::Vector3D, arg3::jdouble)
    return ImpulseManeuver((EventDetector, AttitudeProvider, Vector3D, jdouble), arg0, arg1, arg2, arg3)
end

function ImpulseManeuver(arg0::EventDetector, arg1::Vector3D, arg2::jdouble)
    return ImpulseManeuver((EventDetector, Vector3D, jdouble), arg0, arg1, arg2)
end

function event_occurred(obj::AbstractDetector, arg0::SpacecraftState, arg1::jboolean)
    return jcall(obj, "eventOccurred", Action, (SpacecraftState, jboolean), arg0, arg1)
end

function g(obj::ImpulseManeuver, arg0::SpacecraftState)
    return jcall(obj, "g", jdouble, (SpacecraftState,), arg0)
end

function get_attitude_override(obj::ImpulseManeuver)
    return jcall(obj, "getAttitudeOverride", AttitudeProvider, ())
end

function get_delta_v_sat(obj::ImpulseManeuver)
    return jcall(obj, "getDeltaVSat", Vector3D, ())
end

function get_handler(obj::AbstractDetector)
    return jcall(obj, "getHandler", EventHandler, ())
end

function get_isp(obj::ImpulseManeuver)
    return jcall(obj, "getIsp", jdouble, ())
end

function get_max_check_interval(obj::AbstractDetector)
    return jcall(obj, "getMaxCheckInterval", jdouble, ())
end

function get_max_iteration_count(obj::AbstractDetector)
    return jcall(obj, "getMaxIterationCount", jint, ())
end

function get_threshold(obj::AbstractDetector)
    return jcall(obj, "getThreshold", jdouble, ())
end

function get_trigger(obj::ImpulseManeuver)
    return jcall(obj, "getTrigger", EventDetector, ())
end

function init(obj::ImpulseManeuver, arg0::SpacecraftState, arg1::AbsoluteDate)
    return jcall(obj, "init", void, (SpacecraftState, AbsoluteDate), arg0, arg1)
end

function is_forward(obj::AbstractDetector)
    return jcall(obj, "isForward", jboolean, ())
end

function reset_state(obj::AbstractDetector, arg0::SpacecraftState)
    return jcall(obj, "resetState", SpacecraftState, (SpacecraftState,), arg0)
end

function with_handler(obj::AbstractDetector, arg0::EventHandler)
    return jcall(obj, "withHandler", AbstractDetector, (EventHandler,), arg0)
end

function with_max_check(obj::AbstractDetector, arg0::jdouble)
    return jcall(obj, "withMaxCheck", AbstractDetector, (jdouble,), arg0)
end

function with_max_iter(obj::AbstractDetector, arg0::jint)
    return jcall(obj, "withMaxIter", AbstractDetector, (jint,), arg0)
end

function with_threshold(obj::AbstractDetector, arg0::jdouble)
    return jcall(obj, "withThreshold", AbstractDetector, (jdouble,), arg0)
end


############################################################################################
"""
$(TYPEDEF)
Contains information about parameter distributions, transformations and constraints.

# Fields
$(TYPEDFIELDS)
"""
struct ParameterInfo{C,R<:ReConstructor,T<:TransformConstructor}
    "Constraint distribution/boundaries for all model parameter."
    constraint::C
    "Contains information for flatten/unflatten parameter"
    reconstruct::R
    "Contains information for constraining and unconstraining parameter."
    transform::T
    function ParameterInfo(
        constraint::C, val::B, flattendefault::D
    ) where {C<:NamedTuple,B<:NamedTuple,D<:FlattenDefault}
        ## Create flatten constructor
        constructor = ReConstructor(flattendefault, constraint, val)
        ## Assign transformer constraint NamedTuple
        transformer = TransformConstructor(constraint, val)
        ## Return ParameterInfo
        return new{C,typeof(constructor),typeof(transformer)}(
            constraint, constructor, transformer
        )
    end
end

############################################################################################
#export
export ParameterInfo


module HITRAN

using CSV
using Downloads
using DSP
using FileIO
using JLD2
using Polyester
using Printf
using SHA
using SpecialFunctions
using SQLite

const module_path = dirname(pathof(HITRAN))

include("constants.jl")
include("util.jl")
include("meta/components.jl")
include("meta/parameters.jl")
include("meta/environments.jl")
include("meta/tips.jl")
include("database.jl")
include("profiles.jl")
include("spectra.jl")
include("instruments.jl")

export  current_db,
        open_database,
        fetch!,
        iso_id,
        download_HITRAN,
        query_local_db,
        tips,
        α,
        default_environments,
        moist_air,
        absorption_spectrum,
        transmittance_spectrum,
        optical_depth,
        wavelength_to_wavenumber,
        frequency_to_wavenumber,
        apply_instrument_function

function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    Base.precompile(Tuple{typeof(iso_id),Vector{String}})
    Base.precompile(Tuple{Core.kwftype(typeof(α)),NamedTuple{(:components, :temperature, :ν_range, :ν_step), Tuple{Dict{Tuple{Int64, Int64}, Float64}, Float64, Tuple{Int64, Int64}, Float64}},typeof(α),Vector{String}})
    Base.precompile(Tuple{Core.kwftype(typeof(prepare_hartmann_tran_kwargs)),NamedTuple{(:temperature, :diluent, :query), Tuple{Float64, Dict{Tuple{Int64, Int64}, Dict{Symbol, Float64}}, SQLite.Query}},typeof(prepare_hartmann_tran_kwargs)})
    Base.precompile(Tuple{typeof(instrument_lorentzian),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Float64})
    Base.precompile(Tuple{typeof(hartmann_tran_lineshape),SQLite.Query,Dict{Symbol, Float64},Float64,Float64,Float64,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Float64,Float64,Float64,Vector{Float64},HartmannTranProfileArguments})
    Base.precompile(Tuple{typeof(merge_groups),Symbol,Vararg{Symbol, N} where N})
    Base.precompile(Tuple{typeof(fetch!),SQLite.DB,String,Vector{Int64},Int64,Int64,Vector{String}})
    Base.precompile(Tuple{typeof(moist_air),Int64,Float64,Float64})
    Base.precompile(Tuple{typeof(α),Vector{String},Symbol,Dict{Tuple{Int64, Int64}, Float64},Dict{Tuple{Int64, Int64}, Dict{Symbol, Float64}},Float64,Float64,Float64,StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Tuple{Float64, Float64},Float64,Float64,Dict{Tuple{Int64, Int64}, Float64},Dict{Tuple{Int64, Int64}, Float64}})
    Base.precompile(Tuple{typeof(fetch!),String,Vector{Int64},Int64,Int64,Vector{Symbol}})
    Base.precompile(Tuple{typeof(apply_instrument_function),StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}},Vector{Float64},Symbol,Float64,Float64})
end

_precompile_()

end


# MIT License
#
# Copyright (c) 2018 Martin Biel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
    StochasticModel

A mathematical model of a stochastic optimization problem.
"""
struct StochasticModel{N, P <: NTuple{N, StageParameters}}
    parameters::P
    generator::Function
    optimizer::StochasticProgramOptimizer

    function StochasticModel(generator::Function,
                             parameters::Vararg{StageParameters,N}) where N
        return new{N,typeof(parameters)}(parameters, generator, StochasticProgramOptimizer(nothing))
    end
end
num_stages(::StochasticModel{N}) where N = N

# Printing #
# ========================== #
function Base.show(io::IO, stochasticmodel::StochasticModel)
    println(io, "Multi-stage Stochastic Model")
end
function Base.show(io::IO, stochasticmodel::StochasticModel{2})
    modelstr = "minimize f₀(x) + 𝔼[f(x,ξ)]
  x∈𝒳

where

f(x,ξ) = min  f(y; x, ξ)
              y ∈ 𝒴 (x, ξ)"
    print(io, "Two-Stage Stochastic Model\n\n")
    println(io, modelstr)
end

# Getters #
# ========================== #
"""
    optimal_instance(stochasticmodel::StochasticModel)

Return a stochastic programming instance of the stochastic model after a call to [`optimize!`](@ref).
"""
function optimal_instance(stochasticmodel::StochasticModel)
    # Sanity checks
    check_provided_optimizer(stochasticmodel.optimizer)
    if MOI.get(stochasticmodel, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
        throw(OptimizeNotCalled())
    end
    return optimal_instance(optimizer(stochasticmodel))
end

# MOI #
# ========================== #
function MOI.get(stochasticmodel::StochasticModel, attr::Union{MOI.TerminationStatus, MOI.PrimalStatus, MOI.DualStatus})
    return MOI.get(optimizer(stochasticmodel), attr)
end
function MOI.get(stochasticmodel::StochasticModel, attr::MOI.AbstractModelAttribute)
    if MOI.is_set_by_optimize(attr)
        check_provided_optimizer(stochasticmodel.optimizer)
        if MOI.get(stochasticmodel, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
            throw(OptimizeNotCalled())
        end
    end
    return MOI.get(optimizer(stochasticmodel), attr)
end
function MOI.get(stochasticmodel::StochasticModel, attr::MOI.AbstractOptimizerAttribute)
    MOI.get(optimizer(stochasticmodel), attr)
end

MOI.set(sp::StochasticModel, attr::MOI.AbstractOptimizerAttribute, value) = MOI.set(optimizer(sp), attr, value)
MOI.set(sp::StochasticModel, attr::MOI.AbstractModelAttribute, value) = MOI.set(optimizer(sp), attr, value)


module SegParaAlarmProc
    using Dates
    using  Base.Threads
    CalChn1_2Obj = nothing

    include("../../CSVParas/GetSegParaFromCSV.jl")
    include( "../../common/eventL1.jl")

    include("../../RawFileofflineProc/dataStruct.jl")

    include("Init.jl")
    include("cumSumAlg.jl")
    include("../../common/ConfigFileLib.jl")
    include("AlarmGen.jl")
    include("../../RawFileofflineProc/ReadFiberData.jl")
    include("../../RawFileofflineProc/BaseLib.jl")

    include("../../common/SegAlarmIDPara.jl")
    include("timeSchmidtAlg.jl")
    include("spaceSchmidtAlg.jl")

    include( "../../common/AlarmMergeLayer.jl")
    include("../WindProc/WindIDRAW3AlgEntryRT.jl")
    include("../../common/outputConvert.jl")
    function showSegDictData(glbSegAlarmParasDictObj)
        a = collect( keys( glbSegAlarmParasDictObj ) )
        sort!( a )
        for key in a
            print(key,": ")

            valTuple =glbSegAlarmParasDictObj[ key ]
            println(
            valTuple.microtime.StaticThreshold == typemax( Int ),",",
            valTuple.microtime.DynamicThreshold_CalTimePeriod ,",",
            valTuple.microtime.DynamicThresHold_calCFactor ,",",
            valTuple.microtime.minTh ,",",
            valTuple.microtime.maxTh ,",",
            #微观过滤和合并 时间
            valTuple.microtime.microMergeTimePeriod,",",
            valTuple.microtime.microFilterTimePeriod,",",

            #微观过滤和合并 空间
            valTuple.microSpace.microMergeSpaceLength,",",
            valTuple.microSpace.microFilterSpaceLength,",",

            #宏观 空间 限制范围
            valTuple.macroSpace.macroMaxSpaceLength,",",
            valTuple.macroSpace.macroMinSpaceLength,",",
            #宏观 时间  限制范围
            valTuple.macroTime.macroMaxTimeInterval,",",
            valTuple.macroTime.macroMinTimeInterval,"\r\n"
            )
        end

    end

    function AlramProc(CalChn1_2Obj, row,FrameCnt )
        # println( " spaw-----------------------------0-----------------------n loope in  proloop2 ")
         postProcFeatureData(CalChn1_2Obj ,row ) #AlarmGen.jl
        #  println( " spaw-----------------------------1-----------------------n loope in  proloop2 ")
         FeaturetimeSchmidt( CalChn1_2Obj.dt ,FrameCnt)  #timeSchmidtAlg
        #  println( " spaw------------------------2----------------------------n loope in  proloop2 ")
        updateSpaceStatus( ) #spaceSchmidtAlg
        # println( " spaw------------------------3----------------------------n loope in  proloop2 ")
    end
    # ghDLL = nothing
    # Libdl.dlopen("$RawProcDLLName")
    function RAW3AlgEntryC01(isOnline = true)
        global CalChn1_2Obj #,ghDLL
        # ghDLL = Libdl.dlopen("$RawProcDLLName")
        # @info "Data RAW3AlgEntryC01"
        FrameCnt=UInt(0)
        if isOnline
            # @warn "Online Now"
            fecthDataFunc = Main.fetchFiberDatas
        else
            fecthDataFunc = fetchOfflineFiberDatas
        end
        # @warn "---------------------- Completed"
        lastWriteDT= UInt(0)
        cfgObj = cfgStruct()
        # @warn "------------00--------- Completed"

        fdobj = FiberRawData()
        # @warn "------01------------------------------- Completed"#, fdobj
        fecthDataFunc( fdobj )
        # @warn "------fecthDataFunc-02-------------------------------- Completed"
        readin!(cfgObj,fdobj.cfgdata)
        # @warn "--------------03------------------------- Completed" , fdobj.machineName
        Name = getStringFromArray( fdobj.machineName )
        println("Machine name is $Name,-----")
        evs ="machineName =\"$Name\""
        reps = Base.Meta.parse( evs )
        Core.eval( Main,reps )
        # @warn "-----+++++---------2--------------===------------- Completed"

        INIT( cfgObj )

        # @warn "--------------3------------------------- Completed"
        CalChn1_2Obj = spaceCumSumData( )
        # @warn "--------------6-------- Completed"
        InitObj(CalChn1_2Obj, Int(1),Int(cfgObj.SegNumber), Int(1),Int(cfgObj.SegNumber),-1 );
        CalChn1_2Obj.CfgInfoObj = cfgObj
        InitSegAlarms() #AlarmAnalyssys\AlarmPro\spaceSchmidtAlg.jl
        # @warn "---------------------- Completed"
        # println("WindID.Init----- ")
        WindID.Init( cfgObj ) #include("../AlarmAnalyssys/RAW3Alg-RTWind20190417/C01-RAW3AlgEntryRT.jl")
        # @warn "---------------------- Completed"

        EventL1.Init("Event")
        AlarmMerge.AlarmReport.Init("Alarm")
        WindID.fileLog_wind.Init("Wind");
        AlarmMerge.initMergeCfg( MergeWidth1 = UInt(1 ) , MergeTime1 = UInt( 2*60 ) )

        while true
            FrameCnt = FrameCnt + 1
            # println("in 1")
            fdobj = FiberRawData()
               fecthDataFunc( fdobj )
            # println("in 2")
            if fdobj.rawDatatype[] == 0x0003
                a=""
                for i = 1: length(fdobj.startDTStr )
                    a =string(a,string(Char( fdobj.startDTStr[i]) ))
                end
                CalChn1_2Obj.dt = a
                dataNum = fdobj.actualDatabytes[]/2
                row = fdobj.datas
                #
                  WindID.WindProc(row, a ,fdobj.machineName) #include("../RAW3Alg-RTWind20190417/WindIDRAW3AlgEntryRT.jl")

                  AlramProc(CalChn1_2Obj, row,FrameCnt )
                    #  println(" actual algrithm end:----------------------------------------------")
            end
        end
    end
end #module SegParaAlarmProc