# RGI-based runs for validation

using BSON, VAWTools, DataStructures
region = [3, 4, 14, 11, 7][end-1]
runtyp = ["test", "testmid", "prodlow", "prod"][3]
runtyp == :test && println("\n\nTEST RUN !!!!!!!!\n\n")
parallel = true
all_glaciers = parallel
save_geotiff = true
save_sols = false # makes huge files and did not work with NamedTuples...
fit_sigma = false # not implemented yet

today = Dates.today()
region_str = VAWTools.int2str2(region)

if runtyp in ["test", "testmid"]
    dir_output = "output/validation-test/$(lowercase(Dates.monthabbr(today)))-$(VAWTools.int2str2(Dates.day(today)))/"
else
    if fit_sigma
        dir_output = "output/validation-$(region_str)_fit-sigma/$(lowercase(Dates.monthabbr(today)))-$(VAWTools.int2str2(Dates.day(today)))/"
    else
        dir_output = "output/validation-$(region_str)/$(lowercase(Dates.monthabbr(today)))-$(VAWTools.int2str2(Dates.day(today)))/"
    end
end
!isdir(dir_output) && mkpath(dir_output)

@show region, runtyp, parallel, dir_output

include("rgi-setup.jl") # makes sure all is pre-compiles

tmp = BM.get_git_shas(error_on_dirty=false)
description = """
    bugfix with expectation value

    fsl uniform and 0.5

    Landmask for adjacent glacier now activated

    Validation on Svalbard

    Now after some major bug-fix in expectation-prior update calculation.

    BITEModel commit $(tmp["BITEModel"][1])
              branch $(tmp["BITEModel"][2])
    """

if parallel && nprocs()<2
    # if Sys.CPU_CORES>=17

    #     addprocs(17) # /2 to get to physical cores
    # else
    #     addprocs(Sys.CPU_CORES÷2 + 1) # /2 to get to physical cores
    # end
    # addprocs(Sys.CPU_CORES÷5) # five regions
    addprocs(Sys.CPU_CORES)
end

@everywhere begin
    const debugging = true
    include("rgi-setup.jl")
    using NamedTuples
    const progress_bar = true
end

update_cache = true
glnrs_with_radar = get_glaciers(region)[1] #[8:end] # drop the first few, too big
# delete the ones with a bad DEM
if region==7
    for x in [00025, 00028, 00037, 00042, 00228, 00246, 00279, 00409, 00474, 00529, 00531, 00570, 01425, 01466, 01481, 01523, 01559]
        i = findfirst(glnrs_with_radar, x)
        if i>0
            deleteat!(glnrs_with_radar, i)
        end
    end

    # also delete the ones which error (none found currently)

    # Svalbard glaciers with dhdt data as hand-selected in qgis
    svalbard_gls_with_dhdt = [133, 168, 170, 174, 175, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,
                              197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 210, 213, 225, 266, 267, 268, 269, 270, 271, 272, 273, 274,
                              275, 276, 277, 278, 279, 280, 281, 284, 293, 294, 296, 313, 314, 315, 316, 325, 326, 327, 328, 329, 330, 331, 332, 333,
                              334, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358,
                              359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381,
                              382, 383, 384, 385, 386, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406,
                              407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 426, 427, 428, 429, 430, 431, 432, 433,
                              434, 435, 436, 437, 1403, 1414, 1415, 1461, 1467, 1468, 1469, 1470, 1475, 1476, 1478, 1480, 2, 3, 4, 6, 8, 9, 10, 11,
                              12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 24, 86, 87, 88, 89, 90, 91, 93, 95, 96, 97, 100, 101, 102, 103, 104, 105, 106,
                              107, 108, 109, 112, 113, 114, 115, 116, 118, 119, 120, 121, 124, 125, 126, 128, 129, 130, 131, 132, 134, 135, 136, 137,
                              138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
                              161, 162, 163, 164, 165, 166, 167, 169, 172, 173, 176, 177, 179, 208, 209, 226, 227, 228, 229, 230, 231, 232, 233, 234
                              , 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 252, 253, 254, 255, 256, 257, 258, 259, 260,
                              261, 262, 263, 284, 295, 297, 301, 441, 442, 443, 446, 1402, 1406, 1419, 1420, 1421, 1422, 1423, 1438, 1439]
    # 39 have both:
    svalbard_gls_with_dhdt_and_radar = [s for s in svalbard_gls_with_dhdt if s in glnrs_with_radar]
elseif region==11
    # in the Alps not all have IV
    alps_gl_with_iv_n_radar = [1450, 1478, 1328, 2766, 1238, 2739, 1198, 2773, 2630, 1275, 2801, 2709, 1698, 2704, 2715, 2624, 1144, 2596,
                               2793, 2890, 2740, 2507, 2787, 2774, 2746, 1876, 2490, 1791, 1509, 2584, 2006, 2558, 2249, 2755, 1576, 1296, 2884, 2679, 1987, 1986, 1067,
                               2869, 2909, 1786, 1857, 2600, 1928, 2448, 2673, 1492, 2583, 2244, 1894, 2549, 2027, 1199, 2745, 1367, 1344, 1376]
end

glnrs_fit = glnrs_with_radar[1:2:end]
glnrs_test = glnrs_with_radar[2:2:end]
if length(glnrs_fit)>length(glnrs_test)
    # set to same length
    glnrs_fit = glnrs_fit[1:end-1]
end

# To reduce to just a few glacier
if !all_glaciers
    st = [17, 34, 100][2] # 5, 3, 1
    glnrs_fit = glnrs_fit[1:st:end]
    glnrs_test = glnrs_test[1:st:end]
else
    # st = 3
    # glnrs_fit = glnrs_fit[1:st:end][1:20]
    # glnrs_test = glnrs_test[1:st:end][1:20]
end

if region==7
    # 22:
    glnrs_fit_with_dhdt = [s for s in glnrs_fit if s in svalbard_gls_with_dhdt_and_radar]
    # 17:
    glnrs_test_with_dhdt = [s for s in glnrs_test if s in svalbard_gls_with_dhdt_and_radar]
elseif region==11
    # 30 each
    glnrs_fit_with_iv = [s for s in glnrs_fit if s in alps_gl_with_iv_n_radar]
    glnrs_test_with_iv = [s for s in glnrs_test if s in alps_gl_with_iv_n_radar]
end

## MCMC

# parameters to play with
sigma_of_model = @NT(sigma_h_model = 20.0,
                     sigma_iv_model = 20.0)

# number of divisions in elevation band variables
fit_vars = @NT(n_1d_btilde=3,
               n_1d_fsl=3,
               n_1d_temp=1)

@show region, debugging, runtyp, length(glnrs_fit)

# Method errors contain their arguments, which in our case can be huge.
# Remove them in case of error in pmap
@everywhere strip_MethodErrors(err) = err isa MethodError ? MethodError(0, err.f, err.world) : err

# Write description of model run
write(dir_output*"description.txt", description)

# Full fitting Mode 1
#####################

tic = time()

# runtyp=:prodlow
# nr = 2819
# out,sol = fit_it(nr, region, sigma_of_model, fit_vars, fit_target, runtyp, Val(1), dir_output="", retsol=true)
# error()

# out,sol = fit_it(nr, region, sigma_of_model, fit_vars, fit_target, runtyp, Val(1), dir_output="", retsol=true)
# out_fit = map(nr -> fit_it(nr, region, sigma_of_model, fit_vars,
#                         fit_target, runtyp, Val(1)), glnrs_fit)

expect_mean = Dict()
expect_median = Dict()
stdev = Dict()

out_ = 1 # to get "debug" access to below calculation in case of errors
for fit_target = [BM.FitTarget.h, BM.FitTarget.iv, BM.FitTarget.h_iv, BM.FitTarget.length]

    glnrs_fit_ = if region==11 && fit_target==BM.FitTarget.iv
        #  for the Alps only use the ones with IV
        glnrs_fit_with_iv
    else
        glnrs_fit
    end

    out_ = pmap(nr -> fit_it(nr, region, sigma_of_model, fit_vars,
                                fit_target, runtyp, Val(1),
                                dir_output=dir_output, save_geotiff=save_geotiff,
                                save_sol=false, retsol=save_sols, flroot="FIT_$(fit_target)",
                                fit_sigma=fit_sigma,
                                ),
                glnrs_fit_, on_error=strip_MethodErrors, retry_delays = zeros(3));
    out_fit = save_sols ? [ (o isa Exception) ? o : o[1] for o in out_] : out_;

    @assert length(out_fit)>0 "All fitting glaciers errored!"

    toc1 = time() - tic
    println("Iteration over fit glaciers $fit_target completed in $(toc1/60)min")

    # Make the priors of the fitted parameters
    ##########################################
    drop_sigma = true
    _, expect_median_, expect_mean_, stdev_ = calculate_prior_update(out_fit, drop_sigma)
    expect_mean[Symbol(fit_target)] = expect_mean_
    expect_median[Symbol(fit_target)] = expect_median_
    stdev[Symbol(fit_target)] = stdev_

    ## store
    fl = joinpath(dir_output, "reg$(region_str)_FIT_$(fit_target)")
    println("Storing $fl.bson ...")
    @time bson(fl*".bson", Dict(:out_fit => deepcopy(out_fit),
                                :description => description,
                                :commit => BM.get_git_shas(error_on_dirty=false),
                                :glnrs_fit => glnrs_fit_,
                                :glnrs_test => glnrs_test,
                                :runtyp => runtyp,
                                :expect_median => expect_median_,
                                :expect_mean => expect_mean_,
                                :stdev => stdev_,
                                ))

    println("... done.")
    if save_sols
        sols = [o[2] for o in out_ if !(o isa Exception)]

        println("Storing $fl.jls ...")
        sols_fit = OrderedDict(BM.getrgi_nr(o[:rgi])=>s for (o,s) in zip(out_fit,sols))
        open(fl*".jls", "w") do io
            serialize(io, sols_fit)
        end
        println("... done.")
    end

end
#  Base case: no change in prior
ln = length(first(expect_median)[2])
expect_median[:base] = zeros(ln)
expect_mean[:base] = zeros(ln)
stdev[:base] = Inf.*ones(ln)

fl = joinpath(dir_output, "reg$(region_str)_priors")
bson(fl*".bson", Dict(:expect_median => deepcopy(expect_median),
                      :expect_mean => deepcopy(expect_mean),
                      :stdev => deepcopy(stdev),
                      ))

# keys(expect_mean) == [:base, :h, :h_iv, :iv, :length]

# Validation: posterior1d only fitting
############################

tic2 = time()
# validation using updated priors both against test-set and fit-set (the latter is for double-checking)
for (fl_str, glnrs_) in zip(["testgls", "fitgls"], [glnrs_test, glnrs_fit])
    for k in keys(expect_median)
        fit_target = BM.FitTarget.length
        expect = expect_median[k]
        stdev_ = stdev[k]
        if k!=:base
            @assert expect!=nothing
            @assert stdev_!=nothing
        end

        #out,sol = fit_it(nr, region, sigma_of_model, fit_vars, fit_target, runtyp, Val(2), expect, stdev_, dir_output=dir_output, retsol=true)
        out_ = pmap(nr -> fit_it(nr, region, sigma_of_model, fit_vars,
                                 fit_target, runtyp, Val(2),
                                 expect, stdev_,
                                 dir_output=dir_output, save_geotiff=save_geotiff,
                                 flroot="PRIOR_$k-$fl_str",
                                 fit_sigma=false,
                                 save_sol=false, retsol=save_sols),
                    glnrs_, on_error=strip_MethodErrors, retry_delays = zeros(3))
        out_test = save_sols ? [ (o isa Exception) ? o : o[1] for o in out_] : out_
        @assert length(out_test)>0 "All testing glaciers errored!"

        toc2 = time() - tic2

        println("Iteration over validation glaciers $k completed in $(toc2/60)min")

        fl = joinpath(dir_output, "reg$(region_str)_PRIOR_$k-$fl_str")
        println("Storing $fl.bson ...")
        @time bson(fl*".bson", Dict(:out_test => deepcopy(out_test),
                                    :description => description,
                                    :commit => BM.get_git_shas(error_on_dirty=false),
                                    :expect => expect,
                                    :stdev => stdev_,
                                    ))
        println("... done.")
        if save_sols
            println("Storing $fl.jls ...")
            sols = [o[2] for o in out_ if !(o isa Exception)]
            sols_test = OrderedDict(BM.getrgi_nr(o[:rgi])=>s for (o,s) in zip(out_fit,sols))
            open(fl*".jls", "w") do io
                serialize(io, sols_test)
            end
            println("... done.")
        end
    end
end

#####
# Last IV fitting with priors from h_iv and base (all else doesn't make sense)
fit_target = BM.FitTarget.iv
for k in [:base, :h_iv]
    # Here we need to add back the stripped sigma_iv term.  With stdev==Inf it
    # does not modify the prior.
    expect = [expect_median[k]..., 0]
    stdev_ = [stdev[k]..., Inf]

    glnrs_test_ = if region==11 && fit_target==BM.FitTarget.iv
        #  for the Alps only use the ones with IV
        glnrs_test_with_iv
    else
        glnrs_test
    end

    out_ = pmap(nr -> fit_it(nr, region, sigma_of_model, fit_vars,
                             fit_target, runtyp, Val(1),
                             expect, stdev_,
                             dir_output=dir_output, save_geotiff=save_geotiff,
                             flroot="PRIOR_$(k)_FIT_iv",
                             fit_sigma=fit_sigma,
                             save_sol=false, retsol=save_sols),
                glnrs_test_, on_error=strip_MethodErrors, retry_delays = zeros(3))
    out_test = save_sols ? [ (o isa Exception) ? o : o[1] for o in out_] : out_
    @assert length(out_test)>0 "All IV-testing glaciers errored!"

    toc1 = time() - tic

    fl = joinpath(dir_output, "reg$(region_str)_PRIOR_$(k)_FIT_$(fit_target)")
    println("Storing $fl.bson ...")
    @time bson(fl*".bson", Dict(:out_test => deepcopy(out_test),
                                :description => description,
                                :commit => BM.get_git_shas(error_on_dirty=false),
                                :glnrs_fit => glnrs_fit,
                                :glnrs_test => glnrs_test_,
                                :runtyp => runtyp,
                                :expect => expect,
                                :stdev => stdev_,
                                ))

    println("... done.")
    if save_sols
        sols = [o[2] for o in out_ if !(o isa Exception)]
        println("Storing $fl.jls ...")
        sols_test_iv = OrderedDict(BM.getrgi_nr(o[:rgi])=>s for (o,s) in zip(out_fit,sols))
        open(fl*".jls", "w") do io
            serialize(io, sols_test_iv)
        end
        println("... done.")
    end
end

toc = time() - tic
println("Total time $(toc/60/60) hours")


# This file is a part of ForwardDiffPullbacks.jl, licensed under the MIT License (MIT).

using ForwardDiffPullbacks
using Test

using ChainRulesCore

include("testfuncs.jl")

@testset "ChainRulesCore" begin
    @test @tinferred(ChainRulesCore.rrule(fwddiff(f), xs...)) isa Tuple{Tuple, Function}
    @test @tinferred((ChainRulesCore.rrule(fwddiff(f), xs...)[2])(ΔΩ)) isa Tuple{NoTangent, ForwardDiffPullbacks.FwdDiffPullbackThunk, ForwardDiffPullbacks.FwdDiffPullbackThunk,ForwardDiffPullbacks.FwdDiffPullbackThunk}
    @test @tinferred(map(unthunk, (ChainRulesCore.rrule(fwddiff(f), xs...)[2])(ΔΩ))) == (NoTangent(), 280, (600, 1040), SVector(1600, 2280, 3080))
end


include("resTime.jl")

resTime(spawnRateSet=30,
        CASType=:correctedSector,
        policyFile="LOWFI_mid3_maxdist_2km_nmac_150m_Discount_0.95_ActWeight_0.5_IfSample_false_PenAction_3.0_PenConflict_50.0_PenCloseness_500.0_PenNmac_7000.0_Sigma_v_5.0_alert_5.0_theta_3.0_phi_7.0_coc_3.0_2018-11-13T18\:55\:43.491",
        numEvalEposide=1,
        maxEpisodeLen=1000,
        render=false)


# This file is a part of LegendUpROOTIO.jl, licensed under the MIT License (MIT).

@enum EWFEncScheme EWFEncScheme_kUndefined=0 EWFEncScheme_kDouble=1 EWFEncScheme_kShort=2 EWFEncScheme_kDiffVarInt=3 EWFEncScheme_kUShort=4

@enum EWFType EWFType_kNoType=0 EWFType_kCharge=1 EWFType_kCurrent=2 EWFType_kADC=3 EWFType_kVoltage=4

@enum MGEventType MGEventType_kReal=0 MGEventType_kPulser=1 MGEventType_kMC=2 MGEventType_kUndefined=3 MGEventType_kBaseline=4

@enum EWaveformTag EWaveformTag_kNormal=0 EWaveformTag_kUnderflow=1 EWaveformTag_kOverflow=2 EWaveformTag_kUndefined=3 EWaveformTag_kCorrupted=4

struct MGTDataObjectContent{S<:AbstractString}
    tnamed::TNamedContent{S}
end

function Base.read(io::ROOTIOBuffer, ::Type{MGTDataObjectContent})
    ver = read(io, ROOTClassVersion)
    @assert ver.version == 2
    tnamed = read(io, TNamedContent)
    MGTDataObjectContent(tnamed)
end

function read_mgtclonesarray_header(io::ROOTIOBuffer)
    unknown_ver = read(io, ROOTClassVersion)
    if unknown_ver.version == -1
        # Interpretation of unknown_ver.version == -1 unclear
        @assert String(read(io.buffer, length("\xff\xffMGTClonesArray\0"))) == "\xff\xffMGTClonesArray\0"
    elseif (unknown_ver.version & 0xff00) == 0x8000
        # Interpretation of unknown_ver.version == 0x80.. unclear
        unknown_short = read(io, UInt16)
    else
        throw(ErrorException("Unexpected class version $(unknown_ver.version)"))
    end
    MGTClonesArray_wf_ver = read(io, ROOTClassVersion)
    @assert MGTClonesArray_wf_ver.version == 2
end

function _read_wfsamples_noenc(io::ROOTIOBuffer, T::Type{<:Real}, n::Int)
    samples = Vector{T}(undef, n)
    read!(io.buffer, samples)
    samples .= ntoh.(samples)
    samples
end

@inline function _read_vl_int(io::IO, T::Type{<:Unsigned})
    maxPos = 8 * sizeof(T)
    x::T = 0
    pos::Int = 0
    while true
        (pos >= maxPos) && throw(ErrorException("Overflow during decoding of variable-length encoded number."))
        b = read(io, UInt8)
        x = x | (T(b & 0x7f) << pos)
        if ((b & 0x80) == 0)
            return x
        else
            pos += 7
        end
    end
end

@inline _read_vl_int(io::IO, T::Type{<:Signed}) = _zigzagdec(_read_vl_int(io, unsigned(T)))

@inline _zigzagdec(x::Unsigned) = signed(xor((x >>> 1), (-(x & 1))))

function _read_wfsamples_diffvarint(io::ROOTIOBuffer, T::Type{<:Real}, n::Int)
    T = Int32
    buf = io.buffer
    x::T = 0
    samples = Vector{T}(undef, n)
    for i in eachindex(samples)
        x = _read_vl_int(buf, T) + x
        samples[i] = x
    end
    samples
end

function _read_wfsamples(io::ROOTIOBuffer, enc::EWFEncScheme, n::Integer)
    if enc == EWFEncScheme_kUndefined || enc == EWFEncScheme_kDouble
        _read_wfsamples_noenc(io, Float64, n)
    elseif (enc == EWFEncScheme_kShort)
        _read_wfsamples_noenc(io, Int16, n)
    elseif (enc == EWFEncScheme_kUShort)
        _read_wfsamples_noenc(io, UInt16, n)
    elseif (enc == EWFEncScheme_kDiffVarInt)
        _read_wfsamples_diffvarint(io, Int32, n)
    else
        throw(ErrorException("Sample encoding scheme $enc not supported"))
    end
end

function read_MGTWaveform(io::ROOTIOBuffer)
    MGTWaveform_ver = read(io, ROOTClassVersion)
    @assert MGTWaveform_ver.version == 5
    mgtdataobject = read(io, MGTDataObjectContent)

    fSampFreq = read(io, Float64)u"GHz"
    fTOffset = read(io, Float64)u"ns"
    fWFType = EWFType(read(io, Int32))
    fData_size = Int(read(io, UInt64))
    fWFEncScheme = EWFEncScheme(read(io, Int32))
    fData = _read_wfsamples(io, fWFEncScheme, fData_size)
    fID = read(io, Int32)

    unknown_byte = read(io, UInt8)
    @assert unknown_byte == 0x01

    time_units = u"ns"

    dt = Int32(uconvert(time_units, inv(fSampFreq)) / time_units) * time_units
    t0 = Int32(uconvert(time_units, fTOffset) / time_units) * time_units
    t_idxs = Int32(0):Int32(size(fData,1) - 1)
    t = t0 .+ t_idxs .* dt

    (
        ch = fID,
        wf = RDWaveform(t, fData),
    )
end

function read_tca_MGTWaveform(io::ROOTIOBuffer)
    read_mgtclonesarray_header(io)
    tcahdr = read(io, TClonesArrayHeader)
    @assert tcahdr.fName == "MGTWaveforms"
    @assert tcahdr.fClass == "MGTWaveform;5"

    wfdata_1 = read_MGTWaveform(io)
    ch = [wfdata_1.ch]
    wf_t = StructArray([wfdata_1.wf.time])
    wf_v = VectorOfVectors([wfdata_1.wf.value])

    for i in 2:tcahdr.nobjects
        wfdata = read_MGTWaveform(io)
        push!(ch, wfdata.ch)
        push!(wf_t, wfdata.wf.time)
        push!(wf_v, wfdata.wf.value)
    end

    TypedTables.Table(
        ch = ch,
        wf = StructArray{RDWaveform}((wf_t, wf_v))
    )
end

#=
# Possible digitizer data fields, depending on class:

bool fADCNMinusOneTriggerFlag;
bool fADCNPlusOneTriggerFlag;
bool fCFDCrossingOccurred;
bool fExternalTriggerFlag;
bool fIsInverted;
bool fIsMuVetoed;
bool fIsWrapModeEnabled;
bool fLEDCrossingSignIsNegative;
bool fPileupFlag;
bool fRetriggerFlag;
bool fTriggerFlag;
bool fTTCLTimeoutFlag;
double fClockFrequency;
double fEnergy;
int32_t fCFDPoint1;
int32_t fCFDPoint2;
int fEventNumber;
MGWaveformTag::EWaveformTag fWaveformTag;
size_t fPretrigger;
size_t fTriggerNumber;
uint32_t fNMaxChannels;
uint32_t fBitResolution;
uint32_t fBoardID;
uint32_t fEnergyInitial;
uint32_t fEnergyMax;
uint32_t fFastTriggerCounter;
uint32_t fID;
uint32_t fNChannels;
uint32_t fNofWrapSamples;
uint32_t fWrapStartIndex;
uint64_t fCFDTimeStamp;
uint64_t fIndex;
uint64_t fTimeStamp;
unsigned int fDecimalTimeStamp;
unsigned int fMuVetoSample;
unsigned short fBoardSerialNumber;
=#

# Use fnv1a32 hash of names for enum int values:

function read_MGTVDigitizerData(io::ROOTIOBuffer)
    MGTVDigitizerData_ver = read(io, ROOTClassVersion)
    @assert MGTVDigitizerData_ver.version == 1

    mgtdataobject = read(io, MGTDataObjectContent)

    fEnergy = read(io, Float64)
    fTimeStamp = read(io, UInt64)
    fID = read(io, Int32)
    fIndex = read(io, UInt64)

    (
        fEnergy = fEnergy,
        fTimeStamp = fTimeStamp,
        fID = fID,
        fIndex = fIndex,
    )
end

function read_GETVGERDADigitizerData(io::ROOTIOBuffer)
    GETVGERDADigitizerData_ver = read(io, ROOTClassVersion)
    @assert GETVGERDADigitizerData_ver.version == 2

    parent_content = read_MGTVDigitizerData(io)

    fNMaxChannels = UInt32(4) # meaningful?

    parent_content
end

function read_GETGERDADigitizerData(io::ROOTIOBuffer)
    GETGERDADigitizerData_ver = read(io, ROOTClassVersion)
    @assert GETGERDADigitizerData_ver.version == 1

    parent_content = read_GETVGERDADigitizerData(io)

    fClockFrequency = read(io, Float64)
    fEventNumber = read(io, Int32)
    fPretrigger = read(io, UInt64)
    fTriggerNumber = read(io, UInt64)
    fDecimalTimeStamp = read(io, UInt32)
    fIsMuVetoed = read(io, Bool)
    fMuVetoSample = read(io, UInt32)
    fIsInverted = read(io, Bool)
    fWaveformTag = EWaveformTag(read(io, Int32))

    unknown_byte = read(io, UInt8)
    @assert unknown_byte == 0x01

    content = (
        type = Val(:GETGERDADigitizerData),

        fClockFrequency = fClockFrequency,
        fEventNumber = fEventNumber,
        fPretrigger = fPretrigger,
        fTriggerNumber = fTriggerNumber,
        fDecimalTimeStamp = fDecimalTimeStamp,
        fIsMuVetoed = fIsMuVetoed,
        fMuVetoSample = fMuVetoSample,
        fIsInverted = fIsInverted,
        fWaveformTag = fWaveformTag,
    )
    merge(parent_content, content)
end

function read_tca_MGTVDigitizerData(io::ROOTIOBuffer)
    read_mgtclonesarray_header(io)
    tcahdr = read(io, TClonesArrayHeader)

    # ToDo: Support more digitizer classes

    @assert tcahdr.fName == "GETGERDADigitizerDatas"
    @assert tcahdr.fClass == "GETGERDADigitizerData;1"

    TypedTables.Table([read_GETGERDADigitizerData(io) for i in 1:tcahdr.nobjects])
end

function read_mgtevent(io::ROOTIOBuffer)
    MGTEvent_header = read(io, ROOTClassHeader)
    @assert MGTEvent_header.classname == "MGTEvent"

    version_MGTEvent = read(io, ROOTClassVersion)
    @assert version_MGTEvent.version == 9

    mgtdataobject = read(io, MGTDataObjectContent)

    fMGEventType = read(io, Int32)
    fETotal = read(io, Float64)
    fTime = read(io, Float64)

    fWaveforms = read_tca_MGTWaveform(io)

    fDigitizerData = read_tca_MGTVDigitizerData(io)

    fActiveID = let
        unknown_ver = read(io, ROOTClassVersion)
        @assert unknown_ver.version == -1
        @assert String(read(io.buffer, length("\xff\xffvector<bool>\0"))) == "\xff\xffvector<bool>\0"
        read(io, Vector{Bool})
    end

    fUseAuxWaveformArray = read(io, Bool)

    fAuxWaveforms = read_tca_MGTWaveform(io)

    fEventNumber = read(io, Int32)

    (
        fMGEventType = MGEventType(fMGEventType),
        fETotal = fETotal,
        fTime = fTime,
        fWaveforms = fWaveforms,
        fDigitizerData = fDigitizerData,
        fActiveID = fActiveID,
        fUseAuxWaveformArray = fUseAuxWaveformArray,
        fAuxWaveforms = fAuxWaveforms,
        fEventNumber = fEventNumber,
    )
end

function raw2mgtevent(evt_raw::UpROOT.OpaqueObject{:MGTEvent})
    io = ROOTIOBuffer(evt_raw.data)
    event = read_mgtevent(io)
    @assert eof(io)
    event
end

const _dict_EWaveformTag2daqflags = IdDict(
    EWaveformTag_kNormal => daq_noflags,
    EWaveformTag_kUnderflow => daq_underflow,
    EWaveformTag_kOverflow => daq_overflow,
    # EWaveformTag_kUndefined => ??,
    EWaveformTag_kCorrupted => daq_corrupt,
)

mgdo2legend(x::EWaveformTag) = _dict_EWaveformTag2daqflags[x]

const _dict_MGEventType2EventType = IdDict(
    MGEventType_kReal => evt_real,
    MGEventType_kPulser => evt_pulser,
    MGEventType_kMC => evt_mc,
    MGEventType_kUndefined => evt_undef,
    MGEventType_kBaseline => evt_baseline,
)

mgdo2legend(x::MGEventType) = _dict_MGEventType2EventType[x]

function mgdo2legend(::Val{:GETGERDADigitizerData}, x::TypedTables.Table)
    TypedTables.Table(
        ch = x.fID,

        daqclk = x.fTimeStamp,
        index = x.fIndex, # Meaning?

        psa_energy = x.fEnergy,

        # ignore = x.fClockFrequency, # better kept in metadata/config files
        daqevtno = x.fEventNumber, # Meaning?
        # ignore = x.fPretrigger, # use in waveform
        trigno = x.fTriggerNumber, # Meaning?
        muveto = BitArray(x.fIsMuVetoed),
        muveto_sample = x.fMuVetoSample, # Meaning?
        decimal_timestamp = x.fDecimalTimeStamp, # Meaning?
        inverted = BitArray(x.fIsInverted), #
        #flags = mgdo2legend.(x.fWaveformTag),
    )
end

function mgdo2legend(evt::NamedTuple)
    @argcheck evt.fWaveforms.ch == evt.fDigitizerData.fID
    @argcheck isempty(evt.fAuxWaveforms) || evt.fWaveforms.ch == evt.fAuxWaveforms.ch
    @argcheck evt.fETotal ≈ sum(evt.fDigitizerData.fEnergy)
    # EWFType_kADC

    digidata = mgdo2legend(eltype(evt.fDigitizerData.type)(), evt.fDigitizerData)

    n = length(eachindex(digidata))

    evtdata = (
        evttype = fill(mgdo2legend(evt.fMGEventType), n),
        unixtime = fill(evt.fTime, n),
        evtno = fill(evt.fEventNumber, n), # LEGEND daq files will typically not have this
        # evt.fActiveID, # better kept in metadata/config files
        # evt.fETotal # Don't need to store this
        # evt.fUseAuxWaveformArray # Don't need to store this
    )

    wfdata = (
        waveform_lf = evt.fWaveforms.wf,
        waveform_hf = evt.fAuxWaveforms.wf,
    )

    TypedTables.Table(merge(evtdata, Tables.columns(digidata), wfdata))
end

function mgdo2legend(events::AbstractVector{<:NamedTuple})
    tbl = mgdo2legend(deepcopy(events[firstindex(events)]))
    for i in (firstindex(events) + 1):lastindex(events)
        append!(tbl, mgdo2legend(events[i]))
    end
    tbl
end


type OpusArray
    encoded_stream::IO
    function OpusArray(raw_audio, fs=48000)
        buf = IOBuffer()
        Opus.save(buf, Float32[Float32(x) for x in raw_audio], fs)
        return new(buf)
    end
end

function writemime(io::IO, ::MIME"text/html", x::OpusArray)
    data = base64encode(bytestring(x.encoded_stream))
    markup = """<audio controls="controls" {autoplay}>
                <source src="data:audio/ogg;base64,$data" type="audio/ogg" />
                Your browser does not support the audio element.
                </audio>"""
    print(io, markup)
end


struct ODECompositeSolution{T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE} <: DiffEqBase.AbstractODESolution{T,N,uType}
  u::uType
  u_analytic::uType2
  errors::EType
  t::tType
  k::rateType
  prob::P
  alg::A
  interp::IType
  alg_choice::Vector{Int}
  dense::Bool
  tslocation::Int
  destats::DE
  retcode::Symbol
end
(sol::ODECompositeSolution)(t,deriv::Type=Val{0};idxs=nothing,continuity=:left) = sol.interp(t,idxs,deriv,sol.prob.p,continuity)
(sol::ODECompositeSolution)(v,t,deriv::Type=Val{0};idxs=nothing,continuity=:left) = sol.interp(v,t,idxs,deriv,sol.prob.p,continuity)

function DiffEqBase.build_solution(
        prob::Union{DiffEqBase.AbstractODEProblem,DiffEqBase.AbstractDDEProblem},
        alg::OrdinaryDiffEqCompositeAlgorithm,t,u;
        timeseries_errors=length(u)>2,
        dense=false,dense_errors=dense,
        calculate_error = true,
        k=[],
        du=[],
        interp = !isempty(du) ? HermiteInterpolation(t,u,du) : LinearInterpolation(t,u),
        alg_choice=[1], retcode = :Default, destats=DiffEqBase.DEStats(), kwargs...)

  T = eltype(eltype(u))
  if typeof(prob.u0) <: Tuple
    N = length((size(ArrayPartition(prob.u0))..., length(u)))
  else
    N = length((size(prob.u0)..., length(u)))
  end

  f = prob.f

  if DiffEqBase.has_analytic(f)
    if !(typeof(prob.u0) <: Tuple)
      u_analytic = Vector{typeof(prob.u0)}(undef, 0)
      errors = Dict{Symbol,real(eltype(prob.u0))}()
    else
      u_analytic = Vector{typeof(ArrayPartition(prob.u0))}(undef, 0)
      errors = Dict{Symbol,real(eltype(prob.u0[1]))}()
    end

    sol = ODECompositeSolution{T,N,typeof(u),typeof(u_analytic),typeof(errors),typeof(t),typeof(k),
                               typeof(prob),typeof(alg),typeof(interp),typeof(destats)}(u,u_analytic,errors,t,k,prob,alg,interp,alg_choice,dense,0,destats,retcode)
    if calculate_error
      DiffEqBase.calculate_solution_errors!(sol;timeseries_errors=timeseries_errors,dense_errors=dense_errors)
    end
    return sol
  else
    return ODECompositeSolution{T,N,typeof(u),typeof(nothing),typeof(nothing),typeof(t),typeof(k),
                                typeof(prob),typeof(alg),typeof(interp),typeof(destats)}(u,nothing,nothing,t,k,prob,alg,interp,alg_choice,dense,0,destats,retcode)
  end
end

function DiffEqBase.solution_new_retcode(sol::ODECompositeSolution{T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE},retcode) where {T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE}
  ODECompositeSolution{T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE}(
                     sol.u,sol.u_analytic,sol.errors,sol.t,sol.k,sol.prob,
                     sol.alg,sol.interp,sol.alg_choice,sol.dense,sol.tslocation,
                     sol.destats,retcode)
end

function DiffEqBase.solution_new_tslocation(sol::ODECompositeSolution{
   T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE},tslocation) where {T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE}
   ODECompositeSolution{T,N,uType,uType2,EType,tType,rateType,P,A,IType,DE}(
                      sol.u,sol.u_analytic,sol.errors,sol.t,sol.k,sol.prob,
                      sol.alg,sol.interp,sol.alg_choice,sol.dense,tslocation,
                      sol.destats,sol.retcode)
end

function DiffEqBase.sensitivity_solution(sol::ODECompositeSolution,u,t)
    T = eltype(eltype(u))
    N = length((size(sol.prob.u0)..., length(u)))
    interp = if !sol.dense
      sol.interp
    else
      DiffEqBase.SensitivityInterpolation(t,u)
    end

    ODECompositeSolution{T,N,typeof(u),typeof(sol.u_analytic),typeof(sol.errors),
                typeof(t),Nothing,typeof(sol.prob),typeof(sol.alg),
                typeof(interp),typeof(sol.destats)}(
                u,sol.u_analytic,sol.errors,t,nothing,sol.prob,
                sol.alg,interp,sol.alg_choice,
                sol.dense,sol.tslocation,
                sol.destats,sol.retcode)
end


using Bisquit
using Genie, Genie.Router, Genie.Renderer.Json, Genie.Requests
using HTTP
using UUIDs

import JSON

# initialize an unique id for the new node instance
node_identifier = uuid4( ) |> string

# initialize a genesis block at startup
blockchain = create_genesis( )

# setup route adress on localhost
route("/mine") do
  # define some utiled variables
  last_block = blockchain.chain[ end ]
  last_proof = last_block.proof
  proof = proof_of_work( last_proof )

  # after finding the proof we'll get a reward
  # "0" to identify that the current node has mined a new block
  new_transaction( blockchain, "0", node_identifier, 1 )

  previous_hash = Bisquit.hash( last_block )
  block = Bisquit.new_block

  response = Dict(
    "message"=> "Mined new Block",
    "index"=> block.index,
    "transactions"=> block.transactions,
    "proof"=> block.proof,
    "previous_hash"=> block.previous_hash,
  )
  json( response )
end

# setup route adress on localhost
# view the current chain transaction
route("/chain") do
  response = Dict(
    "chain" => blockchain.chain,
    "length" => length(blockchain.chain)
  )

  # format the output
  response |> JSON.json
end

route("/transactions/new", method = POST) do
  # starting the json request
  data = jsonpayload( )

  # show the current sha256 key for following objects
  print( keys( data ) )

  # add array to the requested keys
  requested_keys = [ "sender", "recipient", "amount" ]

  # check if the current transaction contains the required keys. If not do: give a shoutout error message
  if !( length( keys( data ) ) == length( requested_keys ) && all( k -> in( k, requested_keys ), keys( data ) ) )
    # format the error message in json format
    "Missing valuess" |> json
  end

  # initialize a new transaction
  index = new_transaction( blockchain, data[ "sender" ], data[ "recipipient" ], data[ "amount" ])

  # shout out the success message and format in json
  response = Dict("message" => "Transaction is beeing added to Block $index")
  json( response )
end

# setup route on localhost
# view current chain and send transactions
route("/send") do
  response = HTTP.request("POST", "http://localhost:8000/transactions/new", [("Content-Type", "application/json")], """{"sender":"hello", "recipient":"thanks", "amount":1}""")

  # format body output
  response.body |> String |> JSON
end

# run the server
Genie.startup(async = false)


# TODO: KEEP
# function compareAll(Al::T1, Bl::T2; show::Bool=true, skip::Vector{Symbol}=Symbol[])::Bool where {T1 <: Union{SingleThreaded, MultiThreaded}, T2 <: Union{SingleThreaded, MultiThreaded}}
#   return T1 == T2
# end
import DistributedFactorGraphs: compare, compareAllSpecial

# These are now moved to DistributedFactorGraphs, with the exceptions of
# the functions with IIF-specific parameters.
# To extend these, import the relevant DFG compareX function and overload it.

function Base.isapprox( p1::Union{<:BallTreeDensity, <:ManifoldKernelDensity},
                        p2::Union{<:BallTreeDensity, <:ManifoldKernelDensity};
                        atol::Real=1e-6 )
  #
  mmd(p1,p2) < atol
end

function Base.isapprox( a::ProductRepr,
                        b::ProductRepr;
                        atol::Real=1e-6 )
  #
  for (i,a_) in enumerate(a.parts)
    isapprox(a_, b.parts[i]; atol=atol) || (return false)
  end
  return true
end

## FIXME, FIGURE OUT HOW TO DEPRECATE BELOW ==============================================

function compareAllSpecial( A::T1, B::T2;
                            skip=Symbol[], show::Bool=true) where {T1 <: CommonConvWrapper, T2 <: CommonConvWrapper}
  #
  if T1 != T2
    @warn "CCW types T1 and T2 not equal=>" T1 T2
    # return false
  end
  # FIXME still issues with compare, skipping :vartypes https://github.com/JuliaRobotics/DistributedFactorGraphs.jl/issues/434
  return compareAll(A, B, skip=union(skip, [:vartypes]), show=show)
end

function compare( p1::Union{<:BallTreeDensity,<:ManifoldKernelDensity},
                  p2::Union{<:BallTreeDensity,<:ManifoldKernelDensity} )
  #
  return  compareAll(p1.bt,p2.bt, skip=[:calcStatsHandle; :data]) &&
          compareAll(p1,p2, skip=[:calcStatsHandle; :bt])
end

function compare( c1::TreeClique,
                  c2::TreeClique )
  #
  TP = true
  TP = TP && c1.id == c2.id
  # data
  @warn "skipping ::TreeClique compare of data"
  # TP = TP && compare(c1.data, c2.data)

  # attributes
  @warn "only comparing keys of TreeClique attributes"
  TP = TP && collect(keys(c1.attributes)) == collect(keys(c2.attributes))

  return TP
end

#


export crossover

function crossover(parent1::NEATIndiv, parent2::NEATIndiv, cfg::Dict)
    if cfg["hyperNEAT"] # HyperNEAT
        n_in = 4
        n_out = 1
    else # NEAT
        n_in = cfg["n_in"]
        n_out = cfg["n_out"]
    end
    # get p1 parent with best fitness to keep its genes
    p1, p2 = sort([parent1, parent2], by=x -> x.fitness[1], rev=true)
    child = NEATIndiv(p1)

    # Child connections
    child_genes=Dict()
    neuron_pos::Array{Float64}=[]
    for i in keys(merge(p1.genes, p2.genes))
        g = nothing
        if i in keys(p1.genes)
            # Both parents have the gene: pick random
            if  i in keys(p1.genes)
                if rand()>0.5
                    g = Gene(p1.genes[i])
                else
                    g = Gene(p1.genes[i])
                end
            # Only parent 1 has it: add it
            else
                g = Gene(p1_genes[i])
            end
        end

        # Add the connection to the child, add the neuron IDs
        if g != nothing
            child_genes[g.inno_nb] = g
            if !(g.origin in neuron_pos)
                push!(neuron_pos, g.origin)
            end
            # Add out_neuron if not already in
            if !(g.destination in neuron_pos)
                push!(neuron_pos, g.destination)
            end
        end
    end

    for i in -n_in:n_out
        if !(1.0*i in neuron_pos)
            push!(neuron_pos, 1.0*i)
        end
    end

    # Set child fields
    child.genes = child_genes
    child.neuron_pos = neuron_pos
    child.fitness = -Inf .* ones(cfg["d_fitness"])
    child.network = Network(n_in, n_out, Dict())
    child.activ_functions = merge(p2.activ_functions, p1.activ_functions)
    build!(child)
    child
end


using Printf
# the header implemented based on LAS 1.2
# TODO: check compatibility other LAS versions

#=
COMPATIBILITY WITH LAS 1.2:
One unavoidable change has been made to the Public Header Block; Start of Waveform
Data Packet Record. This long, long has been added to the end of the block and thus
little or no change will be needed in LAS 1.2 readers that do not need waveform data.
There are no changes to Point Data Record types 0 through 3. The waveform encoded
data types have been added as Point Data Record types 4 and 5.

THE ADDITIONS OF LAS 1.4 INCLUDE:
Backward compatibility with LAS 1.1 – LAS 1.3 when payloads consist of only legacy
content
=#

mutable struct LasHeader
    file_source_id::UInt16
    global_encoding::UInt16
    guid_1::UInt32
    guid_2::UInt16
    guid_3::UInt16
    guid_4::AbstractString
    version_major::UInt8
    version_minor::UInt8
    system_id::AbstractString
    software_id::AbstractString
    creation_dayofyear::UInt16
    creation_year::UInt16
    header_size::UInt16
    data_offset::UInt32
    n_vlr::UInt32
    data_format_id::UInt8
    data_record_length::UInt16
    records_count::UInt32
    point_return_count::Vector{UInt32}
    x_scale::Float64
    y_scale::Float64
    z_scale::Float64
    x_offset::Float64
    y_offset::Float64
    z_offset::Float64
    x_max::Float64
    x_min::Float64
    y_max::Float64
    y_min::Float64
    z_max::Float64
    z_min::Float64
    start_of_waveform_data_packet_record::UInt64
    start_of_first_extended_variable_length_record::UInt64
    number_of_extended_variable_length_records::UInt32
    extended_number_of_point_records::UInt64
    extended_number_of_points_by_return::Vector{UInt64}
    variable_length_records::Vector{LasVariableLengthRecord}
    user_defined_bytes::Vector{UInt8}
end

function Base.getproperty(h::LasHeader, s::Symbol)
    if s == :creation_doy
        Base.depwarn("LasHeader.creation_doy is deprecated, use LasHeader.creation_dayofyear", :getproperty)
        return getfield(h, :creation_dayofyear)
    else
        return getfield(h, s)
    end
end

function Base.show(io::IO, header::LasHeader)
    n = header.extended_number_of_point_records > 0 ? Int(header.extended_number_of_point_records) : Int(header.records_count)
    println(io, "LasHeader with $(n) points.")
    println(io, string("\tfile_source_id = ", header.file_source_id))
    println(io, string("\tglobal_encoding = ", header.global_encoding))
    println(io, string("\tguid_1 = ", header.guid_1))
    println(io, string("\tguid_2 = ", header.guid_2))
    println(io, string("\tguid_3 = ", header.guid_3))
    println(io, string("\tguid_4 = ", header.guid_4))
    println(io, string("\tversion_major = ", header.version_major))
    println(io, string("\tversion_minor = ", header.version_minor))
    println(io, string("\tsystem_id = ", header.system_id))
    println(io, string("\tsoftware_id = ", header.software_id))
    println(io, string("\tcreation_dayofyear = ", header.creation_dayofyear))
    println(io, string("\tcreation_year = ", header.creation_year))
    println(io, string("\theader_size = ", header.header_size))
    println(io, string("\tdata_offset = ", header.data_offset))
    println(io, string("\tn_vlr = ", header.n_vlr))
    println(io, string("\tdata_format_id = ", header.data_format_id))
    println(io, string("\tdata_record_length = ", header.data_record_length))
    println(io, string("\trecords_count = ", header.records_count))
    println(io, string("\tpoint_return_count = ", [Int(i) for i in header.point_return_count]))
    println(io, string("\tx_scale = ", header.x_scale))
    println(io, string("\ty_scale = ", header.y_scale))
    println(io, string("\tz_scale = ", header.z_scale))
    println(io, string("\tx_offset = ", header.x_offset))
    println(io, string("\ty_offset = ", header.y_offset))
    println(io, string("\tz_offset = ", header.z_offset))
    println(io, @sprintf "\tx_max = %.7f" header.x_max)
    println(io, @sprintf "\tx_min = %.7f" header.x_min)
    println(io, @sprintf "\ty_max = %.7f" header.y_max)
    println(io, @sprintf "\ty_min = %.7f" header.y_min)
    println(io, @sprintf "\tz_max = %.7f" header.z_max)
    println(io, @sprintf "\tz_min = %.7f" header.z_min)

    if header.version_minor > 3
        println(io, string("\tnumber_of_extended_variable_length_records = ", header.number_of_extended_variable_length_records))
        println(io, string("\textended_number_of_point_records = ", header.extended_number_of_point_records))
        println(io, string("\textended_number_of_points_by_return = ", [Int(i) for i in header.extended_number_of_points_by_return]))
    end
    if !isempty(header.variable_length_records)
        nrecords = min(10, size(header.variable_length_records, 1))

        println(io, string("\tvariable_length_records (showing first $(nrecords)/$(size(header.variable_length_records, 1))) = "))
        for vlr in header.variable_length_records[1:nrecords]
            println(io, "\t\t($(vlr.user_id), $(vlr.record_id)) => ($(vlr.description), $(sizeof(vlr.data)) bytes...)")
        end
        println("\t\t...")
    end
end

function readstring(io, nb::Integer)
    bytes = read(io, nb)
    # strip possible null bytes
    lastchar = findlast(bytes .!= 0)
    if lastchar == nothing
        return ""
    else
        return String(bytes[1:lastchar])
    end
end

function writestring(io, str::AbstractString, nb::Integer)
    n = length(str)
    npad = nb - n
    if npad < 0
        error("string too long")
    elseif npad == 0
        write(io, str)
    else
        writestr = string(str * "\0"^npad)
        write(io, writestr)
    end
end

function Base.read(io::IO, ::Type{LasHeader})
    file_source_id = read(io, UInt16)
    global_encoding = read(io, UInt16)
    guid_1 = read(io, UInt32)
    guid_2 = read(io, UInt16)
    guid_3 = read(io, UInt16)
    guid_4 = readstring(io, 8)
    version_major = read(io, UInt8)
    version_minor = read(io, UInt8)
    system_id = readstring(io, 32)
    software_id = readstring(io, 32)
    creation_dayofyear = read(io, UInt16)
    creation_year = read(io, UInt16)
    header_size = read(io, UInt16)
    data_offset = read(io, UInt32)
    n_vlr = read(io, UInt32)
    data_format_id = read(io, UInt8)
    data_record_length = read(io, UInt16)
    records_count = read(io, UInt32)
    point_return_count = read!(io, Vector{UInt32}(undef, 5))
    x_scale = read(io, Float64)
    y_scale = read(io, Float64)
    z_scale = read(io, Float64)
    x_offset = read(io, Float64)
    y_offset = read(io, Float64)
    z_offset = read(io, Float64)
    x_max = read(io, Float64)
    x_min = read(io, Float64)
    y_max = read(io, Float64)
    y_min = read(io, Float64)
    z_max = read(io, Float64)
    z_min = read(io, Float64)
    lasversion = VersionNumber(version_major, version_minor)
    start_of_waveform_data_packet_record = UInt64(0)
    start_of_first_extended_variable_length_record = UInt64(0)
    number_of_extended_variable_length_records = UInt32(0)
    extended_number_of_point_records = UInt64(0)
    extended_number_of_points_by_return = fill(UInt32(0), 15)

    if lasversion >= v"1.3"
        # start of waveform data record (unsupported)
        start_of_waveform_data_packet_record = read(io, UInt64)
        start_of_first_extended_variable_length_record = read(io, UInt64)
        number_of_extended_variable_length_records = read(io, UInt32)
        extended_number_of_point_records = read(io, UInt64) # this is a UInt64 which is different from records_count which is UInt32
        extended_number_of_points_by_return = read!(io, Vector{UInt64}(undef, 15))
    end

    vlrs = [read(io, LasVariableLengthRecord, false) for i=1:n_vlr]

    # From here until the data_offset everything is read in
    # as user_defined_bytes. To avoid a seek that we cannot do on STDIN,
    # we calculate how much to read in.
    vlrlength = n_vlr == 0 ? 0 : sum(sizeof, vlrs)
    pos = header_size + vlrlength
    user_defined_bytes = read(io, data_offset - pos)

    # put it all in a type
    header = LasHeader(
        file_source_id,
        global_encoding,
        guid_1,
        guid_2,
        guid_3,
        guid_4,
        version_major,
        version_minor,
        system_id,
        software_id,
        creation_dayofyear,
        creation_year,
        header_size,
        data_offset,
        n_vlr,
        data_format_id,
        data_record_length,
        records_count,
        point_return_count,
        x_scale,
        y_scale,
        z_scale,
        x_offset,
        y_offset,
        z_offset,
        x_max,
        x_min,
        y_max,
        y_min,
        z_max,
        z_min,
        start_of_waveform_data_packet_record,
        start_of_first_extended_variable_length_record,
        number_of_extended_variable_length_records,
        extended_number_of_point_records,
        extended_number_of_points_by_return,
        vlrs,
        user_defined_bytes,
    )
end

function Base.write(io::IO, h::LasHeader)
    write(io, h.file_source_id)
    write(io, h.global_encoding)
    write(io, h.guid_1)
    write(io, h.guid_2)
    write(io, h.guid_3)
    writestring(io, h.guid_4, 8)
    write(io, h.version_major)
    write(io, h.version_minor)
    writestring(io, h.system_id, 32)
    writestring(io, h.software_id, 32)
    write(io, h.creation_dayofyear)
    write(io, h.creation_year)
    write(io, h.header_size)
    write(io, h.data_offset)
    @assert length(h.variable_length_records) == h.n_vlr
    write(io, h.n_vlr)
    write(io, h.data_format_id)
    write(io, h.data_record_length)
    write(io, h.records_count)
    @assert length(h.point_return_count) == 5
    write(io, h.point_return_count)
    write(io, h.x_scale)
    write(io, h.y_scale)
    write(io, h.z_scale)
    write(io, h.x_offset)
    write(io, h.y_offset)
    write(io, h.z_offset)
    write(io, h.x_max)
    write(io, h.x_min)
    write(io, h.y_max)
    write(io, h.y_min)
    write(io, h.z_max)
    write(io, h.z_min)
    lasversion = VersionNumber(h.version_major, h.version_minor)
    if lasversion >= v"1.3"
        # start of waveform data record (unsupported)
        write(io, h.start_of_waveform_data_packet_record) # start_of_waveform_data_packet_record
        write(io, h.start_of_first_extended_variable_length_record) # start_of_first_extended_variable_length_record
        write(io, h.number_of_extended_variable_length_records) # number_of_extended_variable_length_records
        write(io, h.extended_number_of_point_records)  # extended_number_of_point_records
        write(io, h.extended_number_of_points_by_return) # extended_number_of_points_by_return
    end
    for i in 1:h.n_vlr
        write(io, h.variable_length_records[i])
    end
    write(io, h.user_defined_bytes)
    # note that for LAS 1.4 a few new parts need to be written
    # possibly introduce typed headers like the points
    nothing
end

"""If true, GPS Time is standard GPS Time (satellite GPS Time) minus 1e9.
If false, GPS Time is GPS Week Time.

Note that not all software sets this encoding correctly."""
is_standard_gps(h::LasHeader) = isodd(h.global_encoding)

"Check if the projection information is in WKT format (true) or GeoTIFF (false)"
function is_wkt(h::LasHeader)
    wkit_bit = Bool((h.global_encoding & 0x0010) >> 4)
    if !wkit_bit && h.data_format_id > 5
        throw(DomainError("WKT bit must be true for point types higher than 5"))
    end
    wkit_bit
end

"Retrieve the bounding box from a LasHeader as a NamedTuple"
boundingbox(h::LasHeader) = (xmin = h.x_min, ymin = h.y_min, zmin = h.z_min,
    xmax = h.x_max, ymax = h.y_max, zmax = h.z_max)

"Get the boundingbox in the same stored integer form as the raw point coordinates"
boundingbox_unscaled(h::LasHeader) = (
    xmin = round(Int32, (h.x_min - h.x_offset) / h.x_scale),
    ymin = round(Int32, (h.y_min - h.y_offset) / h.y_scale),
    zmin = round(Int32, (h.z_min - h.z_offset) / h.z_scale),
    xmax = round(Int32, (h.x_max - h.x_offset) / h.x_scale),
    ymax = round(Int32, (h.y_max - h.y_offset) / h.y_scale),
    zmax = round(Int32, (h.z_max - h.z_offset) / h.z_scale)
)


# Copyright (c) 2018: Matthew Wilhelm & Matthew Stuber.
# This code is licensed under MIT license (see LICENSE.md for full details)
#############################################################################
# EAGO
# A development environment for robust and global optimization
# See https://github.com/PSORLab/EAGO.jl
#############################################################################
# src/eago_optimizer/optimize/optimize.jl
# Load subproblem type optimization routines and define general optimize!
# throw_optimize_hook!.
#############################################################################

include("optimize_lp.jl")
include("optimize_conic.jl")
include("optimize_convex.jl")
include("optimize_nonconvex.jl")

function throw_optimize_hook!(m::Optimizer{Q,S,T}) where {Q,S,T}
    optimize_hook!(_ext(m), m)
end

function MOI.optimize!(m::Optimizer{Q,S,T}) where {Q,S,T}

    m._global_optimizer._start_time = time()
    # Runs the branch and bound routine
    if !m.enable_optimize_hook

        # Need to eliminate fixed variables prior to checking
        # for convex quadratic constraints
        initial_parse!(m)

        # Determines if the problem is an LP, MILP, SOCP, MISCOP,
        # CONVEX, OF MINCVX PROBLEM TYPE
        parse_classify_problem!(m._global_optimizer)

        m._global_optimizer._parse_time = m._global_optimizer._start_time - time()

        # Throws the problem to the appropriate solution routine
        optimize!(m._working_problem._problem_type, m)
    else

        # throws to user-defined optimization hook
        throw_optimize_hook!(m)
    end

    return
end


"""
     FciqmcRunStrategy{T}
Abstract type representing the strategy for running and terminating
[`fciqmc!()`](@ref). The type parameter `T` is relevant for reporting the shift
and the norm.

Implemented strategies:

   * [`RunTillLastStep`](@ref)
"""
abstract type FciqmcRunStrategy{T} end

"""
    RunTillLastStep(step::Int = 0 # number of current/starting timestep
                 laststep::Int = 100 # number of final timestep
                 shiftMode::Bool = false # whether to adjust shift
                 shift = 0.0 # starting/current value of shift
                 dτ::Float64 = 0.01 # current value of time step
    ) <: FciqmcRunStrategy
Parameters for running [`fciqmc!()`](@ref) for a fixed number of time steps.
For alternative strategies, see [`FciqmcRunStrategy`](@ref).
"""
@with_kw mutable struct RunTillLastStep{T} <: FciqmcRunStrategy{T}
    step::Int = 0 # number of current/starting timestep
    laststep::Int = 100 # number of final timestep
    shiftMode::Bool = false # whether to adjust shift
    shift::T = 0.0 # starting/current value of shift
    dτ::Float64 = 0.01 # time step
end


#! format: off

using PowerSystems
using PowerSimulations
using PowerSystemCaseBuilder

using Cbc #solver
solver = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 1, "ratioGap" => 0.5)

sys = build_system(PSITestSystems, "test_RTS_GMLC_sys")

for line in get_components(Line, sys)
    if (get_base_voltage(get_from(get_arc(line))) >= 230.0) &&
       (get_base_voltage(get_to(get_arc(line))) >= 230.0)
        #if get_area(get_from(get_arc(line))) != get_area(get_to(get_arc(line)))
        @info "Changing $(get_name(line)) to MonitoredLine"
        convert_component!(MonitoredLine, line, sys)
    end
end

template = template_unit_commitment(transmission = PTDFPowerModel)

set_device_model!(template, MonitoredLine, StaticBranch)

set_device_model!(template, Line, StaticBranchUnbounded)

uc_prob = OperationsProblem(template, sys, horizon = 24, optimizer = solver)
build!(uc_prob, output_dir = mktempdir())

solve!(uc_prob)

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


#################### Phylogenetic No-U-Turn Sampler ####################

#################### Types and Constructors ####################

mutable struct PNUTSTune <: SamplerTune
  logfgrad::Union{Function, Missing}
  adapt::Bool
  alpha::Float64
  epsilon::Float64
  epsilonbar::Float64
  gamma::Float64
  Hbar::Float64
  kappa::Float64
  m::Int
  mu::Float64
  nalpha::Int
  t0::Float64
  delta::Float64
  target::Float64
  moves::Int
  tree_depth::Int
  nniprime::Int
  targetNNI::Int

  PNUTSTune() = new()

  function PNUTSTune(x::Vector{T}, epsilon::Float64, logfgrad::Union{Function, Missing};
                    target::Real=0.6, tree_depth::Int=10, targetNNI::Int=5) where T<:Node
    new(logfgrad, false, 0.0, epsilon, 1.0, 0.05, 0.0, 0.75, 0, NaN, 0, 10.0,0.003,
        target,0, tree_depth,0, targetNNI)
  end
end

PNUTSTune(x::Vector{T}, logfgrad::Function, ::NullFunction, delta::Float64=0.003; args...) where T<:Node =
  PNUTSTune(x, nutsepsilon(x[1], logfgrad, delta), logfgrad; args...)

PNUTSTune(x::Vector{T}, logfgrad::Function, delta::Float64; args...) where T<:Node =
  PNUTSTune(x, nutsepsilon(x[1], logfgrad, delta), logfgrad; args...)

PNUTSTune(x::Vector; epsilon::Real, args...) =
    NUTSTune(x, epsilon, missing, args...)

const PNUTSVariate = SamplerVariate{PNUTSTune}

#################### Sampler Constructor ####################

"""
    PNUTS(params::ElementOrVector{Symbol}; args...)

Construct a `Sampler` object for PNUTS sampling. The Parameter is assumed to be
a tree.

Returns a `Sampler{PNUTSTune}` type object.

* params: stochastic node to be updated with the sampler.

* args...: additional keyword arguments to be passed to the PNUTSVariate constructor.
"""
function PNUTS(params::ElementOrVector{Symbol}; args...)
  samplerfx = function(model::Model, block::Integer)
    block = SamplingBlock(model, block, true)
    f = (x, sz, ll, gr) -> mlogpdfgrad!(block, x, sz, ll, gr)
    v = SamplerVariate(block, f, NullFunction(); args...)

    sample!(v::PNUTSVariate, f, adapt=model.iter <= model.burnin)

    relist(block, v)
  end
  Sampler(params, samplerfx, PNUTSTune())
end

#################### Sampling Functions ####################

function mlogpdfgrad!(block::SamplingBlock, x::T, sz::Int64, ll::Bool=false, gr::Bool=false)::Tuple{Float64, Vector{Float64}}  where T<:Node
  grad = Vector{Float64}(undef, sz)
  lp = zero(Float64)

  if gr
    lp, grad = gradlogpdf!(block, x)::Tuple{Float64, Vector{Float64}}
  elseif ll
    lp = logpdf!(block, x)::Float64
  end
  lp, grad
end
sample!(v::PNUTSVariate; args...) = sample!(v, v.tune.logfgrad; args...)

function sample!(v::PNUTSVariate, logfgrad::Function; adapt::Bool=false)

  tune = v.tune
  setadapt!(v, adapt)
  if tune.adapt
     tune.m += 1
     tune.nniprime = 0

     nuts_sub!(v, tune.epsilon, logfgrad)
     Ht = (tune.target - tune.alpha / tune.nalpha)
     avgnni = tune.targetNNI - tune.nniprime/tune.nalpha
     HT2 = - avgnni / (1 + abs(avgnni))
     HT = (Ht + HT2)/2
     p = 1.0 / (tune.m + tune.t0)
     tune.Hbar = (1.0 - p) * tune.Hbar +
                 p * HT
     tune.epsilon = exp(tune.mu - sqrt(tune.m) * tune.Hbar / tune.gamma)
     p = tune.m^-tune.kappa
     tune.epsilonbar = exp(p * log(tune.epsilon) +
                           (1.0 - p) * log(tune.epsilonbar))
  else
     if (tune.m > 0) tune.epsilon = tune.epsilonbar end

     nuts_sub!(v, tune.epsilon, logfgrad)
  end
  v
end

function setadapt!(v::PNUTSVariate, adapt::Bool)
  tune = v.tune
  if adapt && !tune.adapt
    tune.m = 0
    tune.mu = log(10.0 * tune.epsilon)
  end
  tune.adapt = adapt
  v
end

function nuts_sub!(v::PNUTSVariate, epsilon::Float64, logfgrad::Function)

  mt = v.value[1]
  nl = size(mt)[1]-1
  delta = v.tune.delta

  r = randn(nl)
  g = zeros(nl)

  x, r , logf, grad, nni = refraction(mt, r, 1, g, epsilon, logfgrad, delta, nl)

  lu = log(rand())

  logp0 = logf - 0.5 * dot(r)
  logu0 = logp0 + lu
  rminus = rplus = r
  gradminus = gradplus = grad

  xminus = xplus = x

  j = 0
  n = 1
  s = true

  while s && j < v.tune.tree_depth

    pm =2 * (rand() > 0.5) - 1

    if pm == -1

      xminus, rminus, gradminus, _, _, _, xprime, nprime, sprime, alpha,
        nalpha, nni1, lpp, nniprime = buildtree(xminus, rminus, gradminus, pm, j, epsilon, logfgrad,
                           logp0, logu0, delta, nl,lu)

    else

      _, _, _, xplus, rplus, gradplus, xprime, nprime, sprime, alpha, nalpha, nni1, lpp, nniprime =
        buildtree(xplus, rplus, gradplus, pm, j, epsilon, logfgrad, logp0,
                  logu0, delta, nl, lu)

    end#if pm

    if sprime && rand() < nprime / n
        v.value[1]= xprime
    end
    j += 1
    n += nprime
    s = sprime && nouturn(xminus, xplus, rminus, rplus, gradminus, gradplus, epsilon, logfgrad, delta, nl, j)
    v.tune.alpha, v.tune.nalpha, v.tune.nniprime = alpha, nalpha, nniprime
    nni += nni1
  end
  #println(j)
  v.tune.moves += nni
  v
end

function refraction(v::T, r::Vector{Float64}, pm::Int64,
                    grad::Vector{Float64}, epsilon::Float64, logfgrad::Function,
                    delta::Float64, sz::Int64)  where T<:Node

    v1 = deepcopy(v)

    ref_r = pm*r

    blenvec = get_branchlength_vector(v1)
    fac = scale_fac.(blenvec, delta)

    ref_r = @. ref_r + (epsilon * 0.5) * (grad * fac)
    tmpB = @. blenvec + (epsilon * ref_r)

    nni = 0

    if minimum(tmpB) <= 0
        v1, tmpB, ref_r, nni = ref_NNI(v1, tmpB, ref_r, epsilon, blenvec, delta, logfgrad, sz)

    end

    blenvec = molifier.(tmpB, delta)

    set_branchlength_vector!(v1, blenvec)

    logf, grad = logfgrad(v1, sz, true, true)

    fac = scale_fac.(blenvec, delta)
    ref_r = @. ref_r + (epsilon * 0.5) * (grad * fac)

    r = pm*ref_r

    return v1, r, logf, grad, nni
end

function ref_NNI(v::T, tmpB::Vector{Float64}, r::Vector{Float64}, epsilon::Float64, blv::Vector{Float64},
                 delta::Float64, logfgrad::Function, sz::Int64)  where T<:Node

  intext = internal_external(v)
  t = 0.0
  nni = 0

  while minimum(tmpB)<=0.0

     timelist = tmpB./abs.(r)
     ref_index = argmin(timelist)

     temp = epsilon-t+timelist[ref_index]
     blv = @. blv + (temp * r)

     r[ref_index] *= -1.0

     if intext[ref_index] == 1

       blv1 =molifier.(blv, delta)
       set_branchlength_vector!(v, blv1)

       # use thread parallelism
       res_before = @spawn logfgrad(v, sz, true, false) # still with molified branch length
       #U_before_nni = logfgrad(v, sz, true, false) # still with molified branch length
       v_copy = deepcopy(v)
       tmp_NNI_made = NNI!(v_copy, ref_index)

       # fetch the results from the parallel part
       U_before_nni, _ = fetch(res_before)
       U_before_nni *= -1

       if tmp_NNI_made != 0

            U_after_nni, _ = logfgrad(v_copy, sz, true, false)
            U_after_nni *= -1
            delta_U::Float64 = 2.0*(U_after_nni - U_before_nni)
            my_v::Float64 = r[ref_index]^2
            if my_v > delta_U
              nni += tmp_NNI_made
              r[ref_index] = sqrt(my_v - delta_U)
              v = v_copy
            end # if my_v
       end #if NNI
      end #non leave
      t = epsilon+timelist[ref_index]

      tmpB = @. blv + (epsilon-t) * r

  end #while

  v, tmpB, r, nni
end

function buildtree(x::T, r::Vector{Float64},
                   grad::Vector{Float64}, pm::Int64, j::Integer,
                   epsilon::Float64, logfgrad::Function, logp0::Real, logu0::Real,
                   delta::Float64, sz::Int64, lu::Float64)  where T<:Node

  if j == 0

    xprime, rprime, logfprime, gradprime, nni = refraction(x, r, pm, grad, epsilon,
                                          logfgrad, delta, sz)

    logpprime = logfprime - 0.5 * dot(rprime)

    nprime = Int(logu0 < logpprime)

    sprime = logu0 < logpprime + 1000.0
    xminus = xplus = xprime
    rminus = rplus = rprime
    gradminus = gradplus = gradprime
    alphaprime = min(1.0, exp(logpprime - logp0))
    nniprime = nni
    nalphaprime = 1

  else
    xminus, rminus, gradminus, xplus, rplus, gradplus, xprime, nprime, sprime,
      alphaprime, nalphaprime, nni, logpprime, nniprime = buildtree(x, r, grad, pm, j - 1, epsilon,
                                          logfgrad, logp0, logu0, delta,  sz, lu)
    if sprime

      if pm == -1

        xminus, rminus, gradminus, _, _, _, xprime2, nprime2, sprime2,
          alphaprime2, nalphaprime2 , nni, logpprime, nniprime2 = buildtree(xminus, rminus, gradminus, pm,
                                                j - 1, epsilon, logfgrad, logp0,
                                                logu0, delta,  sz, lu)
      else
        _, _, _, xplus, rplus, gradplus, xprime2, nprime2, sprime2,
          alphaprime2, nalphaprime2, nni, logpprime, nniprime2 = buildtree(xplus, rplus, gradplus, pm,
                                                j - 1, epsilon, logfgrad, logp0,
                                                logu0, delta, sz,lu)
      end # if pm

      if rand() < nprime2 / (nprime + nprime2)
        xprime = xprime2
      end
      nprime += nprime2
      sprime = sprime2 && nouturn(xminus, xplus, rminus, rplus, gradminus, gradplus, epsilon, logfgrad, delta, sz, j)
      alphaprime += alphaprime2
      nalphaprime += nalphaprime2
      nniprime += nniprime2
    end #if sprime
  end #if j

  xminus, rminus, gradminus, xplus, rplus, gradplus, xprime, nprime, sprime,
    alphaprime, nalphaprime, nni, logpprime, nniprime
end

function nouturn(xminus::T, xplus::T,
                rminus::Vector{Float64}, rplus::Vector{Float64}, gradminus::Vector{Float64},gradplus::Vector{Float64},
                epsilon::Float64, logfgrad::Function, delta::Float64, sz::Int64, j::Int64)  where T<:Node

        curr_l, curr_h = BHV_bounds(xminus, xplus)

        # use thread parallelism to calculuate both directions at once
        res_minus = Base.Threads.@spawn refraction(deepcopy(xminus), deepcopy(rminus), -1, gradminus, epsilon, logfgrad, delta, sz)
        xplus_bar,_,_,_,_ = refraction(deepcopy(xplus), deepcopy(rplus),1, gradplus, epsilon, logfgrad, delta,sz)

        # fetch the results
        xminus_bar,_,_,_,_ = fetch(res_minus)

        curr_t_l, curr_t_h = BHV_bounds(xminus_bar, xplus_bar)
        return curr_h < curr_t_l
end

#################### Auxilliary Functions ####################

function nutsepsilon(x::Node, logfgrad::Function, delta::Float64)

  x0 = deepcopy(x)
  n = size(x)[1] - 1

  _, r0, logf0, grad0,_ = refraction(x0, randn(n), 1, zeros(n), 0.0, logfgrad, delta, n)

  x0 = deepcopy(x)
  epsilon = 1.0
  _, rprime, logfprime, gradprime,_ = refraction(x0, r0, 1, grad0,  epsilon, logfgrad, delta, n)

  prob = exp(logfprime - logf0 - 0.5 * (dot(rprime) - dot(r0)))

  pm = 2 * (prob > 0.5) - 1
  while prob^pm > 0.5^pm
    epsilon *= 2.0^pm
    x0 = deepcopy(x)
    _, rprime, logfprime, _ ,_ = refraction(x0, r0, 1, grad0, epsilon, logfgrad, delta, n)

    prob = exp(logfprime - logf0 - 0.5 * (dot(rprime) - dot(r0)))

  end

  epsilon
end

@inline function scale_fac(x::T, delta::T) where T<:Float64
  x < delta ? x/delta : 1.0
end

@inline function molifier(x::Float64, delta::Float64)::Float64
    x >= delta ? x : (x^2+delta^2)/(2.0*delta)
end # function


module OCIM1
#=
This module serves to load the OCIM1 matrix and grid.
These are loaded from the public, persistant URL in FigShare.
The Julia BSON format version of the OCIM1 was created with the code
in the GitHub repository https://github.com/briochemc/OceanCirculations.
=#

using SparseArrays          # For sparse matrix
using DataDeps              # For storage location of data
using BSON                  # For saving circulation as BSON format
using Unitful, UnitfulAstro # for units
using Reexport
@reexport using OceanGrids            # To store the grid

function fallback_download(remotepath, localdir)
    @assert(isdir(localdir))
    filename = basename(remotepath)  # only works for URLs with filename as last part of name
    localpath = joinpath(localdir, filename)
    Base.download(remotepath, localpath)
    return localpath
end

# Create registry entry for OCIM in JLD2 format
function register_OCIM1()
    register(
        DataDep(
            "AIBECS_OCIM1",
            """
            References:
            - DeVries, T., 2014: The oceanic anthropogenic CO2 sink: Storage, air‐sea fluxes, and transports over the industrial era, Global Biogeochem. Cycles, 28, 631–647, doi:10.1002/2013GB004739.
            - DeVries, T. and F. Primeau, 2011: Dynamically and Observationally Constrained Estimates of Water-Mass Distributions and Ages in the Global Ocean. J. Phys. Oceanogr., 41, 2381–2401, https://doi.org/10.1175/JPO-D-10-05011.1
            """,
            "https://files.figshare.com/17302703/OCIM1.bson",
            "45ebe60e166dcd1b5bdbdf787713b41cb1d5bb2bf4c61a57c8497367a68354e6",
            fetch_method = fallback_download
        )
    )
    return nothing
end

"""
    load

Returns wet3d, grd, and T (in that order) from FigShare repository.
"""
function load()
    print("Loading OCIM1")
    register_OCIM1()
    bson_file = @datadep_str string("AIBECS_OCIM1/", "OCIM1.bson")
    BSON.@load bson_file T grid wet3D
    println(" ✔")
    @info """You are about to use OCIM1 model.
          If you use it for research, please cite:

          - DeVries, T., 2014: The oceanic anthropogenic CO2 sink: Storage, air‐sea fluxes, and transports over the industrial era, Global Biogeochem. Cycles, 28, 631–647, doi:10.1002/2013GB004739.
          - DeVries, T. and F. Primeau, 2011: Dynamically and Observationally Constrained Estimates of Water-Mass Distributions and Ages in the Global Ocean. J. Phys. Oceanogr., 41, 2381–2401, doi:10.1175/JPO-D-10-05011.1

          You can find the corresponding BibTeX entries in the CITATION.bib file
          at the root of the AIBECS.jl package repository.
          (Look for the "DeVries_Primeau_2011" and "DeVries_2014" keys.)
          """
    return wet3D, grid, ustrip.(T)
end

end # end module

export OCIM1


module Player

import GL

type State
	position::GL.Vec3
	orientation::GL.Vec3   # pitch, yaw, and roll in degrees
	look_velocity::GL.Vec3
	move_velocity::GL.Vec3
	speed::Real
	last_think::Real
end
State() = State(GL.Vec3(0,0,0), GL.Vec3(0,0,0), GL.Vec3(0,0,0), GL.Vec3(0,0,0), 100, 0)

function lookdir!(player::State, delta::GL.Vec3, apply::Bool)
	if apply
		player.look_velocity += delta
	else
		player.look_velocity -= delta
	end
end

function lookdir!(player::State, pitch::Real, yaw::Real)
	pitch = player.orientation[1] + pitch
	pitch = clamp(pitch, -89, 89)
	yaw = (player.orientation[2] + yaw) % 360
	player.orientation = GL.Vec3(pitch, yaw, 0)
end

export sphere2cartesian # TODO: put this in a better spot
function sphere2cartesian(orientation::GL.Vec3)
	orientation = degrees2radians(orientation)
	x = cos(orientation[1]) * cos(orientation[2])
	y = cos(orientation[1]) * sin(orientation[2])
	z = sin(orientation[1])
	eyedir = GL.Vec3(x, y, z)
	eyedir /= norm(eyedir)
	updir = GL.Vec3(0, 0, 1)
	rightdir = cross(eyedir, updir)
	rightdir /= norm(rightdir)
	return eyedir, updir, rightdir
end

function movedir!(player::State, delta::GL.Vec3, apply::Bool)
	if apply
		player.move_velocity += delta
	else
		player.move_velocity -= delta
	end
end

# update position using eyedir, move_velocity, and speed
function move!(player::State)
	now = time_ns()
	time = (now - player.last_think) / 1000000000
	dist = player.speed * time
	lookdir!(player, player.look_velocity[1] * time, player.look_velocity[2] * time)
	eyedir, updir, rightdir = sphere2cartesian(player.orientation)
	player.position += dist * player.move_velocity[1] * eyedir
	player.position += dist * player.move_velocity[2] * rightdir
	player.position += dist * player.move_velocity[3] * updir
	player.last_think = now
end

self = State()

export forward, back, moveleft, moveright, moveup, movedown, speed, left, right, lookup, lookdown

forward(apply::Bool)   = movedir!(self, GL.Vec3( 1,  0,  0), apply)
back(apply::Bool)      = movedir!(self, GL.Vec3(-1,  0,  0), apply)
moveleft(apply::Bool)  = movedir!(self, GL.Vec3( 0, -1,  0), apply)
moveright(apply::Bool) = movedir!(self, GL.Vec3( 0,  1,  0), apply)
moveup(apply::Bool)    = movedir!(self, GL.Vec3( 0,  0,  1), apply)
movedown(apply::Bool)  = movedir!(self, GL.Vec3( 0,  0, -1), apply)

function speed(apply::Bool)
	if apply
		self.speed *= 5
	else
		self.speed /= 5
	end
end

left(apply::Bool)     = lookdir!(self, GL.Vec3(  0,  60,  0), apply)
right(apply::Bool)    = lookdir!(self, GL.Vec3(  0, -60,  0), apply)
lookup(apply::Bool)   = lookdir!(self, GL.Vec3( 60,   0,  0), apply)
lookdown(apply::Bool) = lookdir!(self, GL.Vec3(-60,   0,  0), apply)

end


module TestDeprecated

using CUDA
using FoldsCUDA
using Test
using Transducers

function test_foldx_cuda()
    result = Ref{Any}(nothing)
    xs = CUDA.ones(3)
    @test_deprecated result[] = foldx_cuda(TeeRF(min, max), xs)
    if result[] !== nothing
        @test result[] == (1, 1)
    end
end

function test_transduce_cuda()
    result = Ref{Any}(nothing)
    xs = CUDA.ones(3)
    @test_deprecated result[] =
        transduce_cuda(Map(identity), TeeRF(min, max), (0.0f0, 2.0f0), xs)
    if result[] !== nothing
        @test result[] == (0, 2)
    end
end

end  # module