#
# Partial order on domains and input/output types.
#

# Is the given type compatible with the expected type?

function fits(act::Domain, exp::Domain)
    if act == exp || isnone(act) || isany(exp)
        return true
    end
    if isdata(exp)
        return isdata(act) && datatype(act) <: datatype(exp)
    elseif isentity(exp)
        return isentity(act) && classname(act) == classname(exp)
    else
        return (
            isrecord(act) &&
            let actfs = fields(act), expfs = fields(exp)
                length(actfs) == length(expfs) &&
                all(fits(actf, expf) for (actf, expf) in zip(actfs, expfs))
            end)
    end
end

fits(act::Output, exp::Output) =
    fits(act.dom, exp.dom) && fits(act.mode, exp.mode)

fits(act::OutputMode, exp::OutputMode) =
    act.optional <= exp.optional && act.plural <= exp.plural

fits(act::Input, exp::Input) =
    fits(act.dom, exp.dom) && fits(act.mode, exp.mode)

function fits(act::InputMode, exp::InputMode)
    if act.relative < exp.relative
        return false
    end
    if isempty(exp.slots) || exp.slots == act.slots
        return true
    end
    pmap = Dict{Symbol,InputSlot}()
    for actp in act.slots
        pmap[actp.first] = actp
    end
    for (n, p) in exp.slots
        if !(n in keys(pmap)) || !fits(pmap[n].second, p)
            return false
        end
    end
    return true
end

fits(act::Domain, exp::Type) = fits(act, convert(Domain, exp))
fits(act::Output, exp::Input) = fits(act.dom, exp.dom)
fits(act::Output, exp::Domain) = fits(act.dom, exp)
fits(act::Output, exp::Type) = fits(act.dom, convert(Domain, exp))

# The greatest lower bound.

ibound{T}(t1::T, ts::T...) = ibound(T, t1, ts...)
ibound{T}(::Type{T}, ts::T...) = foldl(ibound, ibound(T), ts)
ibound{T}(ts::Vector{T}) = foldl(ibound, ibound(T), ts)

ibound(::Type{Domain}) = Domain(Any)

function ibound(dom1::Domain, dom2::Domain)
    if dom1 == dom2
        return dom1
    end
    decors =
        if isempty(dom1.decors) || dom1.decors == dom2.decors
            dom1.decors
        elseif isempty(dom2.decors)
            dom2.decors
        else
            dset1 = Set{Symbol}(d.first for d in dom1.decors)
            dset2 = Set{Symbol}(d.first for d in dom2.decors)
            dmap = Dict{Symbol,Decoration}()
            for d in dom1.decors
                if d.first in dset2 || :* in dset2
                    dmap[d.first] = d
                end
            end
            for d in dom2.decors
                (n, v) = d
                if n in dset1
                    if dmap[n].second === nothing
                        dmap[n] = d
                    elseif v !== nothing && d !== dmap[n]
                        delete!(dmap, n)
                    end
                elseif :* in dset1
                    dmap[n] = d
                end
            end
            Decoration[dmap[n] for n in sort(collect(keys(dmap)))]
        end
    if isany(dom1) || isnone(dom2)
        return Domain(dom2.desc, decors)
    elseif isnone(dom1) || isany(dom2)
        return Domain(dom1.desc, decors)
    elseif isdata(dom1) && isdata(dom2)
        return Domain(typeintersect(dom1.desc::Type, dom2.desc::Type), decors)
    elseif isentity(dom1) && isentity(dom2) && dom1.desc::Symbol == dom2.desc::Symbol
        return Domain(dom1.desc, decors)
    elseif isrecord(dom1) && isrecord(dom2)
        otypes1 = dom1.desc::Vector{Output}
        otypes2 = dom2.desc::Vector{Output}
        if length(otypes1) == length(otypes2)
            return Domain(
                    Output[ibound(otype1, otype2) for (otype1, otype2) in zip(otypes1, otypes2)],
                    decors)
        end
    end
    return Domain(None, decors)
end

ibound(::Type{OutputMode}) = OutputMode(true, true)

ibound(omode1::OutputMode, omode2::OutputMode) =
    OutputMode(omode1.optional && omode2.optional, omode1.plural && omode2.plural)

ibound(::Type{Output}) =
    Output(ibound(Domain), ibound(OutputMode), Decoration[Decoration(:*, nothing)])

function ibound(otype1::Output, otype2::Output)
    if otype1 == otype2
        return otype1
    end
    dom = ibound(otype1.dom, otype2.dom)
    mode = ibound(otype1.mode, otype2.mode)
    return Output(dom, mode)
end

ibound(::Type{InputMode}) = InputMode()

function ibound(imode1::InputMode, imode2::InputMode)
    if imode1 == imode2
        return imode1
    end
    relative = imode1.relative || imode2.relative
    slots =
        if isempty(imode1.slots) || imode1.slots == imode2.slots
            imode2.slots
        elseif isempty(imode2.slots)
            imode1.slots
        else
            pmap1 = Dict{Symbol,Output}()
            for p in imode1.slots
                pmap1[p.first] = p.second
            end
            pmap2 = Dict{Symbol,Output}()
            for p in imode2.slots
                pmap2[p.first] = p.second
            end
            ps = InputSlot[]
            for n in sort(unique([collect(keys(pmap1)); collect(keys(pmap2))]))
                otype =
                    !haskey(pmap1, n) ?
                        pmap2[n] :
                    !haskey(pmap2, n) ?
                        pmap1[n] :
                        ibound(pmap1[n], pmap2[n])
                push!(ps, n => otype)
            end
            ps
        end
    return InputMode(relative, slots)
end

ibound(::Type{Input}) = Input(ibound(Domain), ibound(InputMode))

ibound(itype1::Input, itype2::Input) =
    itype1 == itype2 ?
        itype1 :
        Input(ibound(itype1.dom, itype2.dom), ibound(itype1.mode, itype2.mode))

# The least upper bound.

obound{T}(t1::T, ts::T...) = obound(T, t1, ts...)
obound{T}(::Type{T}, ts::T...) = foldl(obound, obound(T), ts)
obound{T}(ts::Vector{T}) = foldl(obound, obound(T), ts)

obound(::Type{Domain}) = Domain(None)

function obound(dom1::Domain, dom2::Domain)
    if dom1 == dom2
        return dom1
    end
    decors =
        if isempty(dom2.decors) || dom1.decors == dom2.decors
            dom1.decors
        elseif isempty(dom1.decors)
            dom2.decors
        else
            dset1 = Set{Symbol}(d.first for d in dom1.decors)
            dset2 = Set{Symbol}(d.first for d in dom2.decors)
            dmap = Dict{Symbol,Decoration}()
            for d in dom1.decors
                n = d.first
                if !(n in dset2) && :* in dset2
                    dmap[n] = Decoration(n, nothing)
                else
                    dmap[n] = d
                end
            end
            for d in dom2.decors
                n = d.first
                if n in dset1
                    if !(n in dset1)
                        if :* in dset1
                            dmap[n] = Decoration(n, nothing)
                        else
                            dmap[n] = d
                        end
                    elseif d != dmap[n]
                        dmap[n] = Decoration(n, nothing)
                    end
                end
            end
            Decoration[dmap[n] for n in sort(collect(keys(dmap)))]
        end
    if isnone(dom1) || isany(dom2)
        return Domain(dom2.desc, decors)
    elseif isany(dom1) || isnone(dom2)
        return Domain(dom1.desc, decors)
    elseif isdata(dom1) && isdata(dom2)
        return Domain(typejoin(dom1.desc::Type, dom2.desc::Type), decors)
    elseif isrecord(dom1) && isrecord(dom2)
        otypes1 = dom1.desc::Vector{Output}
        otypes2 = dom2.desc::Vector{Output}
        if length(otypes1) == length(otypes2)
            return Domain(
                    Output[obound(otype1, otype2) for (otype1, otype2) in zip(otypes1, otypes2)],
                    decors)
        end
    end
    return Domain(Any, decors)
end

obound(::Type{OutputMode}) = OutputMode(false, false)

obound(omode1::OutputMode, omode2::OutputMode) =
    OutputMode(omode1.optional || omode2.optional, omode1.plural || omode2.plural)

obound(::Type{Output}) =
    Output(obound(Domain), ibound(OutputMode), ())

function obound(otype1::Output, otype2::Output)
    if otype1 == otype2
        return otype1
    end
    dom = obound(otype1.dom, otype2.dom)
    mode = obound(otype1.mode, otype2.mode)
    return Output(dom, mode)
end


# Reduction per Harris #3
function reduce_3(
    accum,
    input,
    num_samples,
)
    # define needed incides
    threads_per_block = blockDim().x
    block_idx = blockIdx().x
    thread_idx = threadIdx().x
    sample_idx = (block_idx - 1) * threads_per_block + thread_idx

    # allocate the shared memory for the partial sum
    shmem = @cuDynamicSharedMem(Float32, threads_per_block)

    # each thread loads one element from global to shared memory
    @inbounds if sample_idx <= num_samples
        shmem[thread_idx] = input[sample_idx]
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = threads_per_block ÷ 2
    @inbounds while s != 0
        sync_threads()
        if thread_idx - 1 < s
            shmem[thread_idx] += shmem[thread_idx + s]
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if thread_idx == 1
        accum[blockIdx().x] = shmem[1]
    end

    return nothing
end

# Complex reduction per Harris #3
function reduce_cplx_3(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
)
    # define needed incides
    threads_per_block = iq_offset = blockDim().x
    block_idx = blockIdx().x
    thread_idx = threadIdx().x
    sample_idx = (block_idx - 1) * threads_per_block + thread_idx

    # allocate the shared memory for the partial sum
    # double the memory for complex values, accessed via
    # iq_offset
    shmem = @cuDynamicSharedMem(Float32, (2 * threads_per_block))

    # each thread loads one element from global to shared memory
    @inbounds if sample_idx <= num_samples
        shmem[thread_idx + 0 * iq_offset] = input_re[sample_idx]
        shmem[thread_idx + 1 * iq_offset] = input_im[sample_idx]
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = threads_per_block ÷ 2
    @inbounds while s != 0
        sync_threads()
        if thread_idx - 1 < s
            shmem[thread_idx + 0 * iq_offset] += shmem[thread_idx + 0 * iq_offset + s]
            shmem[thread_idx + 1 * iq_offset] += shmem[thread_idx + 1 * iq_offset + s]
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if thread_idx == 1
        accum_re[blockIdx().x] = shmem[1 + 0 * iq_offset]
        accum_im[blockIdx().x] = shmem[1 + 1 * iq_offset]
    end

    return nothing
end

# Complex reduction per Harris #3, multi correlator, multi antenna
function reduce_cplx_multi_3(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
    num_ants::NumAnts{NANT},
    correlator_sample_shifts::SVector{NCOR, Int64}
) where {NANT, NCOR}
    # define needed incides
    threads_per_block = iq_offset = blockDim().x
    block_idx = blockIdx().x
    thread_idx = threadIdx().x
    sample_idx = (block_idx - 1) * threads_per_block + thread_idx

    # allocate the shared memory for the partial sum
    # double the memory for complex values, accessed via
    # iq_offset
    shmem = @cuDynamicSharedMem(Float32, (2 * threads_per_block, NANT, NCOR))
    @inbounds for antenna_idx = 1:NANT
        for corr_idx = 1:NCOR
            shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx]
            shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx]
        end
    end

    # each thread loads one element from global to shared memory
    @inbounds if sample_idx <= num_samples
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx] = input_re[sample_idx, antenna_idx, corr_idx]
            	shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx] = input_im[sample_idx, antenna_idx, corr_idx]
            end
        end
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = threads_per_block ÷ 2
    @inbounds while s != 0
        sync_threads()
        if thread_idx - 1 < s
            for antenna_idx = 1:NANT
                for corr_idx = 1:NCOR
                    shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx] += shmem[thread_idx + 0 * iq_offset + s, antenna_idx, corr_idx]
                    shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx] += shmem[thread_idx + 1 * iq_offset + s, antenna_idx, corr_idx]
                end
            end
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if thread_idx == 1
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                accum_re[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 0 * iq_offset, antenna_idx, corr_idx]
                accum_im[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 1 * iq_offset, antenna_idx, corr_idx]
            end
        end
    end

    return nothing
end

# Complex reduction per Harris #3, multi correlator, multi antenna
function reduce_cplx_multi_31(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
    num_ants::NumAnts{NANT},
    correlator_sample_shifts::SVector{NCOR, Int64}
) where {NANT, NCOR}
    # define needed incides
    threads_per_block = iq_offset = blockDim().x
    block_idx = blockIdx().x
    thread_idx = threadIdx().x
    sample_idx = (block_idx - 1) * threads_per_block + thread_idx

    # allocate the shared memory for the partial sum
    # double the memory for complex values, accessed via
    # iq_offset
    shmem = @cuDynamicSharedMem(Float32, (2 * threads_per_block, NANT, NCOR))
    @inbounds for antenna_idx = 1:NANT
        for corr_idx = 1:NCOR

            shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx]
            shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx]

            # each thread loads one element from global to shared memory
            @inbounds if sample_idx <= num_samples
                shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx] = input_re[sample_idx, antenna_idx, corr_idx]
            	shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx] = input_im[sample_idx, antenna_idx, corr_idx]
            end

            # wait until all finished
            sync_threads()

            # do (partial) reduction in shared memory
            s::UInt32 = threads_per_block ÷ 2
            @inbounds while s != 0
                sync_threads()
                if thread_idx - 1 < s
                    shmem[thread_idx + 0 * iq_offset, antenna_idx, corr_idx] += shmem[thread_idx + 0 * iq_offset + s, antenna_idx, corr_idx]
                    shmem[thread_idx + 1 * iq_offset, antenna_idx, corr_idx] += shmem[thread_idx + 1 * iq_offset + s, antenna_idx, corr_idx]
                end
                s ÷= 2
            end

            # first thread returns the result of reduction to global memory
            @inbounds if thread_idx == 1
                accum_re[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 0 * iq_offset, antenna_idx, corr_idx]
                accum_im[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 1 * iq_offset, antenna_idx, corr_idx]
            end
        end
    end

    return nothing
end

# Reduction per Harris #4
function reduce_4(
    accum,
    input,
    num_samples
)
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # global thread index
    i = (blockIdx().x - 1) * (blockDim().x * 2) + threadIdx().x

    # allocate the shared memory for the partial sum
    shmem = @cuDynamicSharedMem(Float32, blockDim().x)

    # local sum variable
    mysum = 0.0f0

    # each thread loads one element from global to shared memory
    # AND
    # does the first level of reduction
    @inbounds if i <= length(input)
        mysum = input[i]
        if i + blockDim().x <= length(input)
            mysum += input[i + blockDim().x]
        end

        shmem[tid] = mysum
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    @inbounds while s != 0
        sync_threads()
        if tid - 1 < s
            shmem[tid] += shmem[tid + s]
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if tid == 1
        accum[blockIdx().x] = shmem[1]
    end

    return nothing
end

# Complex reduction per Harris #4
function reduce_cplx_4(
    accum_re,
    accum_im,
    input_re,
    input_im
)
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # global thread index
    i = (blockIdx().x - 1) * (blockDim().x * 2) + threadIdx().x

    # allocate the shared memory for the partial sum
    shmem = @cuDynamicSharedMem(Float32, 2 * blockDim().x)
    # wipe values
    shmem[tid + 0 * blockDim().x] = 0.0f0
    shmem[tid + 1 * blockDim().x] = 0.0f0

    # local sum variable
    mysum_re = mysum_im = 0.0f0

    # each thread loads one element from global to shared memory
    # AND
    # does the first level of reduction
    @inbounds if i <= length(input_re)
        shmem[tid + 0 * blockDim().x] = input_re[i]
        shmem[tid + 1 * blockDim().x] = input_im[i]

        if i + blockDim().x <= length(input_re)
            shmem[tid + 0 * blockDim().x] += input_re[i + blockDim().x]
            shmem[tid + 1 * blockDim().x] += input_im[i + blockDim().x]
        end
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    @inbounds while s != 0
        sync_threads()
        if tid - 1 < s
            shmem[tid + 0 * blockDim().x] += shmem[tid + s + 0 * blockDim().x]
            shmem[tid + 1 * blockDim().x] += shmem[tid + s + 1 * blockDim().x]
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if tid == 1
        accum_re[blockIdx().x] = shmem[1 + 0 * blockDim().x]
        accum_im[blockIdx().x] = shmem[1 + 1 * blockDim().x]
    end

    return nothing
end

# Complex reduction per Harris #4, multi correlator, multi antenna
function reduce_cplx_multi_4(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
    num_ants::NumAnts{NANT},
    correlator_sample_shifts::SVector{NCOR, Int64}
) where {NANT, NCOR}
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # global thread index
    i = (blockIdx().x - 1) * (blockDim().x * 2) + threadIdx().x

    # allocate the shared memory for the partial sum
    shmem = @cuDynamicSharedMem(Float32, (2 * blockDim().x, NANT, NCOR))
    # wipe values
    @inbounds for antenna_idx = 1:NANT
        for corr_idx = 1:NCOR
            shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] = 0.0f0
            shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] = 0.0f0
        end
    end

    # each thread loads one element from global to shared memory
    # AND
    # does the first level of reduction
    @inbounds if i <= num_samples
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] = input_re[i, antenna_idx, corr_idx]
                shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] = input_im[i, antenna_idx, corr_idx]

                if i + blockDim().x <= num_samples
                    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += input_re[i + blockDim().x, antenna_idx, corr_idx]
                    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += input_im[i + blockDim().x, antenna_idx, corr_idx]
                end
            end
        end
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    @inbounds while s != 0
        sync_threads()
        if tid - 1 < s
            for antenna_idx = 1:NANT
                for corr_idx = 1:NCOR
                    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + s + 0 * blockDim().x, antenna_idx, corr_idx]
                    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + s + 1 * blockDim().x, antenna_idx, corr_idx]
                end
            end
        end

        s ÷= 2
    end

    # first thread returns the result of reduction to global memory
    @inbounds if tid == 1
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                accum_re[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 0 * blockDim().x, antenna_idx, corr_idx]
                accum_im[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 1 * blockDim().x, antenna_idx, corr_idx]
            end
        end
    end

    return nothing
end

@inline function warp_reduce(shmem, tid)
    shmem[tid] += shmem[tid + 32]
    shmem[tid] += shmem[tid + 16]
    shmem[tid] += shmem[tid + 8]
    shmem[tid] += shmem[tid + 4]
    shmem[tid] += shmem[tid + 2]
    shmem[tid] += shmem[tid + 1]
end

function reduce_5(accum, input, num_samples)
    # define indices
    tid = threadIdx().x
    idx = (2 * blockDim().x) * (blockIdx().x - 1) + threadIdx().x

    # define shared memory
    shmem = CuDynamicSharedArray(Float32, blockDim().x)

    # each thread loads one element into the shared memory
    # and performs the first level of reduction
    if idx <= num_samples
        @inbounds shmem[tid] = input[idx]
        if idx + blockDim().x <= num_samples
            @inbounds shmem[tid] += input[idx + blockDim().x]
        end
    end

    # wait untill all threads have finished
    sync_threads()

    # do tree-like (partial) reduction in shared memory
    s = blockDim().x ÷ 2
    while s > 32
        if tid - 1 < s
            @inbounds shmem[tid] += shmem[tid + s]
        end
        sync_threads()
        s ÷= 2
    end

    # once three size = warp size, do warp reduction
    # assumes block size >= 64
    if tid <= 32
        warp_reduce(shmem, tid)
    end

    # first thread holds the result of the (partial) reduction
    if tid == 1
        @inbounds accum[blockIdx().x] = shmem[1]
    end

    return nothing
end

@inline function warp_reduce_cplx(shmem, tid)
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 32]
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 16]
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 8]
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 4]
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 2]
    shmem[tid + 0 * blockDim().x] += shmem[tid + 0 * blockDim().x + 1]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 32]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 16]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 8]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 4]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 2]
    shmem[tid + 1 * blockDim().x] += shmem[tid + 1 * blockDim().x + 1]
end

# Complex reduction per Harris #5
function reduce_cplx_5(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples
)
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # global thread index
    idx = (2 * blockDim().x) * (blockIdx().x - 1) + threadIdx().x

    # define shared memory
    shmem = CuDynamicSharedArray(Float32, 2 * blockDim().x)

    # each thread loads one element into the shared memory
    # and performs the first level of reduction
    if idx <= num_samples
        @inbounds shmem[tid + 0 * blockDim().x] = input_re[idx]
        @inbounds shmem[tid + 1 * blockDim().x] = input_im[idx]

        if idx + blockDim().x <= num_samples
            @inbounds shmem[tid + 0 * blockDim().x] += input_re[idx + blockDim().x]
            @inbounds shmem[tid + 1 * blockDim().x] += input_im[idx + blockDim().x]
        end
    end

    # wait untill all threads have finished
    sync_threads()

    # do tree-like (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    while s > 32
        sync_threads()
        if tid - 1 < s
            @inbounds shmem[tid + 0 * blockDim().x] += shmem[tid + s + 0 * blockDim().x]
            @inbounds shmem[tid + 1 * blockDim().x] += shmem[tid + s + 1 * blockDim().x]
        end

        s ÷= 2
    end

    # once three size = warp size, do warp reduction
    # assumes block size >= 64
    if tid <= 32
        warp_reduce_cplx(shmem, tid)
    end

    # first thread holds the result of the (partial) reduction
    if tid == 1
        @inbounds accum_re[blockIdx().x] = shmem[1 + 0 * blockDim().x]
        @inbounds accum_im[blockIdx().x] = shmem[1 + 1 * blockDim().x]
    end

    return nothing
end

@inline function warp_reduce_cplx_multi(shmem, tid, antenna_idx, corr_idx)
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 32, antenna_idx, corr_idx]
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 16, antenna_idx, corr_idx]
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 8, antenna_idx, corr_idx]
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 4, antenna_idx, corr_idx]
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 2, antenna_idx, corr_idx]
    shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 0 * blockDim().x + 1, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 32, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 16, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 8, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 4, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 2, antenna_idx, corr_idx]
    shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + 1 * blockDim().x + 1, antenna_idx, corr_idx]
end

# Complex reduction per Harris #5, multi correlator, multi antenna
function reduce_cplx_multi_5(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
    num_ants::NumAnts{NANT},
    correlator_sample_shifts::SVector{NCOR, Int64}
) where {NANT, NCOR}
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # global thread index
    idx = (2 * blockDim().x) * (blockIdx().x - 1) + threadIdx().x

    # allocate the shared memory for the partial sum
    shmem = CuDynamicSharedArray(Float32, 2 * blockDim().x)

    # each thread loads one element from global to shared memory
    # AND
    # does the first level of reduction
    if idx <= num_samples
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                @inbounds shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] = input_re[idx, antenna_idx, corr_idx]
                @inbounds shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] = input_im[idx, antenna_idx, corr_idx]

                if idx + blockDim().x <= num_samples
                    @inbounds shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += input_re[idx + blockDim().x, antenna_idx, corr_idx]
                    @inbounds shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += input_im[idx + blockDim().x, antenna_idx, corr_idx]
                end
            end
        end
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    while s > 32
        sync_threads()
        if tid - 1 < s
            for antenna_idx = 1:NANT
                for corr_idx = 1:NCOR
                    @inbounds shmem[tid + 0 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + s + 0 * blockDim().x, antenna_idx, corr_idx]
                    @inbounds shmem[tid + 1 * blockDim().x, antenna_idx, corr_idx] += shmem[tid + s + 1 * blockDim().x, antenna_idx, corr_idx]
                end
            end
        end

        s ÷= 2
    end

    # once three size = warp size, do warp reduction
    # assumes block size >= 64
    if tid <= 32
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                warp_reduce_cplx_multi(shmem, tid, antenna_idx, corr_idx)
                sync_threads()
            end
        end
    end

    # first thread returns the result of reduction to global memory
    if tid == 1
        for antenna_idx = 1:NANT
            for corr_idx = 1:NCOR
                @inbounds accum_re[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 0 * blockDim().x, antenna_idx, corr_idx]
                @inbounds accum_im[blockIdx().x, antenna_idx, corr_idx] = shmem[1 + 1 * blockDim().x, antenna_idx, corr_idx]
            end
        end
    end

    return nothing
end

# Complex reduction per Harris #5, multi correlator, multi antenna
function reduce_cplx_multi_nant_5(
    accum_re,
    accum_im,
    input_re,
    input_im,
    num_samples,
    num_ants::NumAnts{NANT},
) where {NANT}
    ## define needed incides
    # local thread index
    tid = threadIdx().x
    # local antenna index
    antenna_idx = blockIdx().y
    # local sample index
    sample_idx = (blockIdx().x - 1) * (2 * blockDim().x) + threadIdx().x;

    # allocate the shared memory for the partial sum
    shmem = CuDynamicSharedArray(Float32, 2 * blockDim().x)

    # each thread loads one element from global to shared memory
    if sample_idx <= num_samples
        @inbounds shmem[tid + 0 * blockDim().x] = input_re[sample_idx, antenna_idx]
        @inbounds shmem[tid + 1 * blockDim().x] = input_im[sample_idx, antenna_idx]
        if sample_idx + blockDim().x <= num_samples
            @inbounds shmem[tid + 0 * blockDim().x] += input_re[sample_idx + blockDim().x, antenna_idx]
            @inbounds shmem[tid + 1 * blockDim().x] += input_im[sample_idx + blockDim().x, antenna_idx]
        end
    end

    # wait until all finished
    sync_threads()

    # do (partial) reduction in shared memory
    s::UInt32 = blockDim().x ÷ 2
    while s > 32
        sync_threads()
        if tid - 1 < s
            @inbounds shmem[tid + 0 * blockDim().x] += shmem[tid + s + 0 * blockDim().x]
            @inbounds shmem[tid + 1 * blockDim().x] += shmem[tid + s + 1 * blockDim().x]
        end

        s ÷= 2
    end

    if tid <= 32
        warp_reduce_cplx(shmem, tid)
    end

    # first thread returns the result of reduction to global memory
    if tid == 1
        @inbounds accum_re[blockIdx().x, antenna_idx] = shmem[1 + 0 * blockDim().x]
        @inbounds accum_im[blockIdx().x, antenna_idx] = shmem[1 + 1 * blockDim().x]
    end

    return nothing
end


module JToric

# use other Julia packages
using Markdown
using Pkg
using Oscar

import Oscar:
    class_group,
    dim,
    iscomplete,
    isintegral,
    isnormal,
    isprincipal,
    issmooth,
    picard_group,
    projective_space

"""
Initializing function for 'JToric'.
"""
# initialization
function __init__()
    print("Welcome to JToric ")
    printstyled("$version\n", color = :green)
    println("Enjoy this software to perform computations on toric geometry!")
end

const PROJECT_TOML = Pkg.TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml"))
const VERSION_NUMBER = VersionNumber(PROJECT_TOML["version"])
const IS_DEV = isdir(@__DIR__, "..", ".git")

"""
    version

The version number of the loaded `JToric`.
Please mention this number in any bug report.
"""
const version = IS_DEV ? VersionNumber("$(VERSION_NUMBER)-dev") : VERSION_NUMBER

# include files
include("NormalToricVarieties/constructors.jl")
include("NormalToricVarieties/properties.jl")
include("ToricDivisors.jl")
include("AttributesAndMethods.jl")

end


using SPADL

@time test = get_events_data(:wyscout, download = false, path = "wyscout_data")
GC.gc()
# we have to update the loader first
test = get_events(test, "events_Italy.json");

@time events_df = events(test, 2576336);

@time check = convert_to_actions(events_df)

configs = ActionsConfig()
findall(x -> x == "head", configs.bodyparts)

using DataFrames

#### We need to add miliseconds in WyscoutEventFixed
view = []
for i in eachindex(check)
    tmp = (check[i].event_fixed)
    push!(view, tmp)
end
view = DataFrame(view)

# for i in 1:size(view,1)
#     if i > 1 && view[i, :period_id] == view[i-1, :period_id]
#         if view[i, :milliseconds] < view[i-1, :milliseconds]
#             println("we have a problem", i)
#         end
#     end
# end


function fixargs(args::Vector{Any}, stub::Any)
    n = length(args)
    data = Array(Any, n)
    na = BitArray(n)
    for i in 1:n
        if args[i] == :NA
            data[i] = stub
            na[i] = true
        else
            data[i] = args[i]
            na[i] = false
        end
    end
    return data, na
end

# We assume that data has at least one "value" that isn't NA
function findstub_vector(ex::Expr)
    if ex.args[1] != :NA
        return ex.args[1]
    end
    for i in 2:length(ex.args)
        if ex.args[i] != :NA
            return ex.args[i]
        end
    end
    return NA
end

# We assume that data has at least one "value" that isn't NA
function findstub_matrix(ex::Expr)
    if ex.args[1].args[1] != :NA
        return ex.args[1].args[1]
    end
    nrows = length(ex.args)
    for row in 1:nrows
        subex = ex.args[row]
        for i in 1:length(subex.args)
            if subex.args[i] != :NA
                return subex.args[i]
            end
        end
    end
    return NA
end

function parsevector(ex::Expr)
    if ex.head in (:ref, :typed_hcat, :typed_vcat)
        data, na = fixargs(ex.args[2:end], :(zero($(ex.args[1]))))
        return Expr(ex.head, ex.args[1], data...), ex.head == :typed_hcat ? na' : na
    else
        stub = findstub_vector(ex)
        data, na = fixargs(ex.args, stub)
        if ex.head == :hcat
            na = na'
        end

        if isequal(stub, NA)
            return Expr(ex.head == :hcat ? (:typed_hcat) : (:ref), Any, data...), na
        else
            return Expr(ex.head, data...), na
        end
    end
end

function parsematrix(ex::Expr)
    if ex.head == :typed_vcat
        stub = :(zero($(ex.args[1])))
        rows = 2:length(ex.args)
    else
        stub = findstub_matrix(ex)
        rows = 1:length(ex.args)
    end

    nrows = length(rows)
    datarows = Array(Expr, nrows)
    narows = Array(Expr, nrows)
    for irow in 1:nrows
        data, na = fixargs(ex.args[rows[irow]].args, stub)
        datarows[irow] = Expr(:row, data...)
        narows[irow] = Expr(:row, na...)
    end
    if ex.head == :typed_vcat
        return Expr(:typed_vcat, ex.args[1], datarows...), Expr(:vcat, narows...)
    elseif isequal(stub, NA)
        return Expr(:typed_vcat, Any, datarows...), Expr(:vcat, narows...)
    else
        return Expr(:vcat, datarows...), Expr(:vcat, narows...)
    end
end

function parsedata(ex::Expr)
    if length(ex.args) == 0
        return :([]), Expr(:call, :Array, :Bool, 0)
    end
    if ex.head == :typed_vcat || (isa(ex.args[1], Expr) && ex.args[1].head == :row)
        return parsematrix(ex)
    else
        return parsevector(ex)
    end
end

macro data(ex)
    if !(ex.head in (:vect, :vcat, :hcat, :ref, :typed_vcat, :typed_hcat))
        return quote
            tmp = $(esc(ex))
            DataArray(tmp, bitbroadcast(x->isequal(x, NA), tmp))
        end
    end
    dataexpr, naexpr = parsedata(ex)
    return Expr(:call, :DataArray, esc(dataexpr), esc(naexpr))
end

macro pdata(ex)
    if !(ex.head in (:vect, :vcat, :hcat, :ref, :typed_vcat, :typed_hcat))
        return quote
            tmp = $(esc(ex))
            PooledDataArray(tmp, bitbroadcast(x->isequal(x, NA), tmp))
        end
    end
    dataexpr, naexpr = parsedata(ex)
    return Expr(:call, :PooledDataArray, esc(dataexpr), esc(naexpr))
end


for f in (:getindex, :view, :dotview)
    @eval begin
        # Int and CartesianIndex forward to the parent
        @propagate_inbounds Base.$f(l::LookupArray, i::Union{Int,CartesianIndex}) =
            Base.$f(parent(l), i)
        # AbstractArray and Colon the lookup is rebuilt around a new parent
        @propagate_inbounds Base.$f(l::LookupArray, i::Union{AbstractArray,Colon}) =
            rebuild(l; data=Base.$f(parent(l), i))
        # Selector gets processed with `selectindices`
        @propagate_inbounds Base.$f(l::LookupArray, i::SelectorOrInterval) = Base.$f(l, selectindices(l, i))
        # Everything else (like custom indexing from other packages) passes through to the parent
        @propagate_inbounds function Base.$f(l::LookupArray, i)
            Base.$f(parent(l), i)
        end
    end
end


import Base.map

# Utility structure for collections of samples.
mutable struct Particles{C}
    calls::Vector{C}
    lws::Vector{Float64}
    lmle::Float64
end

map(fn::Function, ps::Particles) = map(fn, ps.calls)

include("inference/is.jl")
include("inference/pf.jl")
include("inference/mh.jl")
include("inference/vi.jl")
include("inference/hmc.jl")

const hmc = hamiltonian_monte_carlo
const mh = metropolis_hastings
const mh! = metropolis_hastings!
const is = importance_sampling
const advi = automatic_differentiation_variational_inference

# ------------ Documentation (IS) ------------ #

@doc(
"""
Samples from the model prior.
```julia
particles, normalized_weights = importance_sampling(observations::ConstrainedSelection,
                                                    num_samples::Int,
                                                    model::Function,
                                                    args::Tuple)
```
Samples from a programmer-provided proposal function.
```julia
particles, normalized_weights = importance_sampling(observations::ConstrainedSelection,
                                                    num_samples::Int,
                                                    model::Function,
                                                    args::Tuple,
                                                    proposal::Function,
                                                    proposal_args::Tuple)
```

Run importance sampling on the posterior over unconstrained addresses and values. Returns an instance of `Particles` and normalized weights.
""", importance_sampling)

# ------------ Documentation (PF) ------------ #

@doc(
"""
```julia
particles = initialize_filter(observations::ConstrainedHierarchicalSelection,
                              num_particles::Int,
                              fn::Function,
                              args::Tuple)
```

Instantiate a set of particles using a call to `importance_sampling`.
""", initialize_filter)

@doc(
"""
```julia
filter_step!(observations::ConstrainedHierarchicalSelection,
             ps::Particles,
             new_args::Tuple)
```
Perform a single filter step from an instance `ps` of `Particles`, applying the constraints specified by `observations`.

```julia
filter_step!(observations::ConstrainedHierarchicalSelection,
             ps::Particles,
             new_args::Tuple,
             proposal::Function,
             proposal_args::Tuple)
```
Perform a single filter step using a custom proposal function, applying the constraints specified by `observations`.
""", filter_step!)

@doc(
"""
```julia
check_ess_resample!(ps::Particles)
```
Checks the effective sample size using `ess`, then resamples from an existing instance of `Particles` by mutation in place.
""", check_ess_resample!)

# ------------ Documentation (MH) ------------ #

@doc(
"""
```julia
call, accepted, metropolis_hastings(sel::UnconstrainedSelection,
                                    call::HierarchicalCallSite)
```

Perform a Metropolis-Hastings step by proposing new choices using the prior at addressed specified by `sel`. Returns a call site, as well as a Boolean value `accepted` to indicate if the proposal was accepted or rejected.

```julia
call, accepted = metropolis_hastings(sel::UnconstrainedSelection,
                                     call::HierarchicalCallSite,
                                     proposal::Function,
                                     proposal_args::Tuple)
```

Perform a Metropolis-Hastings step by proposing new choices using a custom proposal at addressed specified by `sel`. Returns a call site, as well as a Boolean value `accepted` to indicate if the proposal was accepted or rejected.
""", metropolis_hastings)

# ------------ Documentation (VI) ------------ #

@doc(
"""
```julia
params, elbows, call_sites =  advi(sel::K,
                                   iters::Int,
                                   v_mod::Function,
                                   v_args::Tuple,
                                   mod::Function,
                                   args::Tuple;
                                   opt = ADAM(),
                                   gs_samples = 100) where K <: ConstrainedSelection
```

Given a selection `sel`, perform _automatic-differentiation variational inference_ with a proposal model `v_mod`. The result is a new set of trained parameters `params` for the variational model, the history of ELBO estimates `elbows`, and the call sites `calls` produced by the gradient estimator computation.
""", advi)


@testset "uniform.jl" begin
    # affine transformations
    test_affine_transformations(Uniform, rand(), 4 + rand())
end


using DrWatson
quickactivate(@__DIR__)
@show projectdir()
##
using RCall
using CSV
using BSON
using DataFrames
using Dates
using Plots
using Underscores
using StatsBase
using ColorSchemes
plotlyjs()
##

contacts = BSON.load( projectdir("data/contacts/int/mean_contact_rates_aggregation=14.bson") )
 = contacts["Denmark"].date
diff(rw_step)|>unique


function ComplexODEState(arg0::jdouble, arg1::Vector{JComplex})
    return ComplexODEState((jdouble, Vector{JComplex}), arg0, arg1)
end

function ComplexODEState(arg0::jdouble, arg1::Vector{JComplex}, arg2::Vector{Vector{JComplex}})
    return ComplexODEState((jdouble, Vector{JComplex}, Vector{Vector{JComplex}}), arg0, arg1, arg2)
end

function get_complete_state(obj::ComplexODEState)
    return jcall(obj, "getCompleteState", Vector{JComplex}, ())
end

function get_complete_state_dimension(obj::ComplexODEState)
    return jcall(obj, "getCompleteStateDimension", jint, ())
end

function get_number_of_secondary_states(obj::ComplexODEState)
    return jcall(obj, "getNumberOfSecondaryStates", jint, ())
end

function get_primary_state(obj::ComplexODEState)
    return jcall(obj, "getPrimaryState", Vector{JComplex}, ())
end

function get_primary_state_dimension(obj::ComplexODEState)
    return jcall(obj, "getPrimaryStateDimension", jint, ())
end

function get_secondary_state(obj::ComplexODEState, arg0::jint)
    return jcall(obj, "getSecondaryState", Vector{JComplex}, (jint,), arg0)
end

function get_secondary_state_dimension(obj::ComplexODEState, arg0::jint)
    return jcall(obj, "getSecondaryStateDimension", jint, (jint,), arg0)
end

function get_time(obj::ComplexODEState)
    return jcall(obj, "getTime", jdouble, ())
end


export State, PositionState

using StaticArrays, NamedTupleTools

import Base: *, +, -, zero, rand, randn, show, promote_rule, rtoldefault,
       isapprox, getproperty, real

import LinearAlgebra: norm, promote_leaf_eltypes

abstract type XState{NT <: NamedTuple} <: AbstractState end

"""
`struct State` the main type for states of input variables (particles).
This type is intended only for storing of information but no arithmetic
should be performed on it. For the latter, we have the DState.
"""
struct State{NT <: NamedTuple} <: XState{NT}
   x::NT

   # standard constructor - SYMS are the same
   State{NT}(t::NT1) where {NT <: NamedTuple{SYMS}, NT1 <: NamedTuple{SYMS}} where {SYMS} =
         new{NT1}(t)

   # if SYMS are not the same we automatically merge them
   State{NT}(t::NT1) where {NT <: NamedTuple, NT1 <: NamedTuple} =
         State( merge( _x(zero(State{NT})), t ) )
end

"""
`struct DState`: A `State`-like variable but acting like vector with arithmetic
operations defined on it, while `State` acts more like a fixed object that cannot
be manipulated. The main application of `DState` is as a derivative of a
`State`; see also `dstate_type`, ``
"""
struct DState{NT <: NamedTuple} <: XState{NT}
   x::NT

   # standard constructor - SYMS are the same
   DState{NT}(t::NT1) where {NT <: NamedTuple{SYMS}, NT1 <: NamedTuple{SYMS}} where {SYMS} =
         new{NT1}(t)

   # if SYMS are not the same we automatically merge them
   DState{NT}(t::NT1) where {NT <: NamedTuple, NT1 <: NamedTuple} =
         DState( merge( _x(zero(DState{NT})), t ) )

   DState{NT}(dX::DState) where {NT} = DState{NT}(_x(dX))

end

# the two standard outward facing constructors
State(nt::NT) where {NT <: NamedTuple} = State{NT}(nt)
State(; kwargs...) = State(NamedTuple(kwargs))

# some variations on the above... which might not be needed anymore
State{NT}(; kwargs...) where {NT <: NamedTuple} = State{NT}(NamedTuple(kwargs))

# accessing X.nt and the fields of X.nt
# this relies heavily on constant propagation
_x(X::XState) = getfield(X, :x)
getproperty(X::XState, sym::Symbol) = getproperty(_x(X), sym)

# ----------- printing / output

const _showdigits = Ref{Int64}(2)

"""
change how many digits are printed in the ACE States
"""
function showdigits!(n::Integer)
   _showdigits[] = n
end

_2str(x) = string(x)
_2str(x::AbstractFloat) = "[$(round(x, digits=_showdigits[]))]"
_2str(x::Complex) = "[$(round(x, digits=_showdigits[]))]"
_2str(x::SVector{N, <: AbstractFloat}) where {N} = string(round.(x, digits=_showdigits[]))
_2str(x::SVector{N, <: Complex}) where {N} = string(round.(x, digits=_showdigits[]))[11:end]

_showsym(X::State) = ""
_showsym(X::DState) = "′"

function show(io::IO, X::XState)
   str = prod( "$(sym):$(_2str(getproperty(_x(X), sym))), "
               for sym in keys(_x(X)) )
   print(io,  "⟨" * str[1:end-2] * "⟩" * _showsym(X))
end

# ----------- some basic manipulations

# extract the symbols and the types
_syms(X::XState) = _syms(typeof(X))
_syms(::Type{<: XState{NamedTuple{SYMS, TT}}}) where {SYMS, TT} = SYMS

_tt(X::XState) = _tt(typeof(X))
_tt(::Type{<: XState{NamedTuple{SYMS, TT}}}) where {SYMS, TT} = TT

_symstt(X::XState) = _symstt(typeof(X))
_symstt(::Type{<: XState{NamedTuple{SYMS, TT}}}) where {SYMS, TT} = SYMS, TT

# which properties are continuous

const CTSTT = Union{AbstractFloat, Complex{<: AbstractFloat},
                    SVector{N, <: AbstractFloat},
                    SVector{N, <: Complex}} where {N}

"""
Find the indices of continuous properties, and return the
indices as well as the symbols and types
"""
_findcts(X::TX) where {TX <: XState} = _findcts(TX)

function _findcts(TX::Type{<: XState})
   SYMS, TT = _symstt(TX)
   icts = findall(T -> T <: CTSTT, TT.types)
end

_ctssyms(X::TX) where {TX <: XState} = _ctssyms(TX)

_ctssyms(TX::Type{<: XState}) = _syms(TX)[_findcts(TX)]

## ----- DState constructors

DState(t::NT) where {NT <: NamedTuple} = DState{NT}(t)

DState(; kwargs...) = DState(NamedTuple(kwargs))

DState(X::TX) where {TX <: State} =
      (dstate_type(X))( select(_x(X), _ctssyms(X)) )

"""
convert a State to a corresponding DState
(basically just remove the discrete variables)
"""
dstate_type(X::DState) = typeof(X)

@generated function dstate_type(X::TX)  where {TX <: State}
   CSYMS = _ctssyms(TX)
   quote
      typeof( DState( select(_x(X), $CSYMS) ) )
   end
end

# the next variant of dstate_type is used to potentially extend
# from real states to complex dstates.

_mypromrl(T::Type{<: Number}, S::Type{<: Number}) =
      promote_type(T, S)
_mypromrl(T::Type{<: Number}, ::Type{<: SVector{N, P}}) where {N, P} =
      SVector{N, promote_type(T, P)}
_mypromrl(::Type{<: SVector{N, P}}, T::Type{<: Number}) where {N, P} =
      promote_type(T, P)
_mypromrl(T::Type{<: SVector{N, P1}}, ::Type{<: SVector{N, P2}}) where {N, P1, P2} =
      SVector{N, promote_type(P1, P2)}

@generated function dstate_type(x::S, X::TX) where {S, TX <: XState}
   SYMS, TT = _symstt(TX)
   icts = _findcts(TX)
   CSYMS = SYMS[icts]
   CTT = [ _mypromrl(S, TT.types[i]) for i in icts ]
   CTTstr = "Tuple{" * "$(tuple(CTT...))"[2:end-1] * "}"
   quote
      $(Meta.parse( "DState{NamedTuple{$(CSYMS), $CTTstr}}" ))
   end
end

dstate_type(S::Type, X::XState) = dstate_type(zero(S), X)

## ---------- explicit real/complex conversion
# this feels a bit like a hack but might be unavoidable;
# real, complex goes to _ace_real, _ace_complex, which is then applied
# in only slightly non-standard fashion recursively to the states

for f in (:real, :imag, :complex, )
   face = Symbol("_ace_$f")
   eval(quote
      import Base: $f
      $face(x::Number) = $f(x)
      $face(x::StaticArrays.StaticArray) = $f.(x)
      function $f(X::TDX) where {TDX <: DState}
         SYMS = _syms(TDX)
         vals = ntuple(i -> $face(getproperty(X, SYMS[i])), length(SYMS))
         return TDX( NamedTuple{SYMS}(vals) )
      end
   end)
end

for f in (:real, :complex, )
   face = Symbol("_ace_$f")
   eval(quote
      $f(TDX::Type{<: DState}) = typeof( $f(zero(TDX)) )
      # import Base: $f
      # $face(x::Type{<: Number}) = $f(x)
      # $face(x::SVector{N, T}) where {N, T} = SVector{N, $f(T)}
      # function $f(TDX::Type{<: DState})
      #    SYMS, TT = _symstt(TDX)
      #    TT1 = ntuple(i -> $face(TT[i]), length(SYMS))
      #    vals = zero.(TT1)
      #    return typeof(TDX( NamedTuple{SYMS}(vals) )
      # end
   end)
end

for f in (:rand, :randn, :zero)
   face = Symbol("_ace_$f")
   eval( quote
      import Base: $f
      $face(T::Type) = $f(T)
      $face(x::Union{Number, AbstractArray}) = $f(typeof(x))

      function $f(x::Union{TX, Type{TX}}) where {TX <: XState}
         SYMS, TT = _symstt(x)
         vals = ntuple(i -> $face(TT.types[i]), length(SYMS))
         return TX( NamedTuple{SYMS}( vals ) )
      end
   end )
end

# an extra for symbols, this is a bit questionable; why do we even need it?
_ace_zero(::Union{Symbol, Type{Symbol}}) = :O

## ----------- Some arithmetic operations

# binary operations

import Base: +, -

# for f in (:+, :-, )
#    eval( quote
#       function $f(X1::TX1, X2::TX2) where {TX1 <: XState, TX2 <: XState}
#          SYMS = _syms(TX1)
#          @assert SYMS == _syms(TX2)
#          vals = ntuple( i -> $f( getproperty(_x(X1), SYMS[i]),
#                                  getproperty(_x(X2), SYMS[i]) ), length(SYMS) )
#          return TX1( NamedTuple{SYMS}(vals) )
#       end
#    end )
# end

for f in (:+, :-, )
   eval( quote
      function $f(X1::TX1, X2::TX2) where {TX1 <: XState, TX2 <: XState}
         SYMS1 = ACE._syms(TX1)
         @assert issubset(ACE._syms(TX2), SYMS1)
         vals = ntuple( i -> begin
                  sym = SYMS1[i]
                  v1 = getproperty(ACE._x(X1), sym)
                  haskey(ACE._x(X2), sym) ? $f(v1, getproperty(ACE._x(X2), sym)) : v1
               end, length(SYMS1))
         return TX1( NamedTuple{SYMS1}(vals) )
      end
   end )
end

# multiplication with a scalar
function *(X1::TX, a::Number) where {TX <: XState}
   SYMS = _syms(TX)
   vals = ntuple( i -> *( getproperty(_x(X1), SYMS[i]), a ), length(SYMS) )
   return TX( NamedTuple{SYMS}(vals) )
end

*(a::Number, X1::XState) = *(X1, a)

*(aa::SVector{N, <: Number}, X1::XState) where {N} = aa .* Ref(X1)
promote_rule(::Type{SVector{N, T}}, ::Type{TX}) where {N, T <: Number, TX <: XState} =
      SVector{N, promote_type(T, TX)}

# unary
import Base: -

for f in (:-, )
   eval(quote
      function $f(X::TX) where {TX <: XState}
         SYMS = _syms(TX)
         vals = ntuple( i -> $f( getproperty(_x(X), SYMS[i]) ), length(SYMS) )
         return TX( NamedTuple{SYMS}(vals) )
      end
   end)
end

# reduction to scalar

import LinearAlgebra: dot
import Base: isapprox

for (f, g) in ( (:dot, :sum), (:isapprox, :all) )  # (:contract, :sum),
   eval( quote
      function $f(X1::TX1, X2::TX2) where {TX1 <: XState, TX2 <: XState}
         SYMS = _syms(TX1)
         @assert SYMS == _syms(TX2)
         return $g( $f( getproperty(_x(X1), sym),
                         getproperty(_x(X2), sym) )   for sym in SYMS)
      end
   end )
end

@generated function contract(X1::TX1, X2::TX2) where {TX1 <: XState, TX2 <: XState}
   SYMS = _syms(TX1)
   @assert SYMS == _syms(TX2)
   code = "contract(X1.$(SYMS[1]), X2.$(SYMS[1]))"
   for sym in SYMS[2:end]
      code *= " + contract(X1.$sym, X2.$sym)"
   end
   return quote
      $(Meta.parse(code))
   end
end

contract(X1::Number, X2::XState) = X1 * X2
contract(X2::XState, X1::Number) = X1 * X2

import LinearAlgebra: norm

for (f, g) in ((:norm, :norm), (:sumsq, :sum), (:normsq, :sum) )
   eval( quote
      function $f(X::TX) where {TX <: XState}
         SYMS = _syms(TX)
         vals = ntuple( i -> $f( getproperty(_x(X), SYMS[i]) ),  length(SYMS))
         return $g(vals)
      end
   end )
end

## --------  not clear where needed; or deleted functionality

function promote_leaf_eltypes(X::TX) where {TX <: XState}
   SYMS = _syms(TX)
   promote_type( ntuple(i -> promote_leaf_eltypes(getproperty(_x(X), SYMS[i])), length(SYMS))... )
end

## ----- Implementation of a Position State, as a basic example

PositionState{T} = State{NamedTuple{(:rr,), Tuple{SVector{3, T}}}}

PositionState(r::AbstractVector{T}) where {T <: AbstractFloat} =
      (@assert length(r) == 3; PositionState{T}(; rr = SVector{3, T}(r)))

promote_rule(::Union{Type{S}, Type{PositionState{S}}},
             ::Type{PositionState{T}}) where {S, T} =
      PositionState{promote_type(S, T)}

# some special functionality for PositionState, mostly needed for testing
*(A::AbstractMatrix, X::TX) where {TX <: PositionState} = TX( (rr = A * X.rr,) )
+(X::TX, u::StaticVector{3}) where {TX <: PositionState} = TX( (rr = X.rr + u,) )

# ------------------ Basic Configurations Code
# TODO: get rid of this?

import ACEbase: AbstractConfiguration
export ACEConfig

"""
`struct ACEConfig`: The canonical implementation of an `AbstractConfiguration`.
Just wraps a `Vector{<: AbstractState}`
"""
struct ACEConfig{STT} <: AbstractConfiguration
   Xs::Vector{STT}   # list of states
end

Base.eltype(cfg::ACEConfig) = eltype(cfg.Xs)

Base.iterate(cfg::ACEConfig, args...) = iterate(cfg.Xs, args...)

Base.length(cfg::ACEConfig) = length(cfg.Xs)

# ---------------- AD code

# TODO: check whether this is still needed
# this function makes sure that gradients w.r.t. a State become a DState
function rrule(::typeof(getproperty), X::XState, sym::Symbol)
   val = getproperty(X, sym)
   return val, w -> ( NoTangent(),
                      dstate_type(w[1], X)( NamedTuple{(sym,)}((w,)) ),
                      NoTangent() )
end

const UConfig = Union{ACEConfig, AbstractVector{<: AbstractState}}


function writebmp(io, img)
    h, w = size(img)
    filesize = 54 + sizeof(img) #w is your image width, h is image height, both int
    # How many bytes of padding to add to each
    # horizontal line - the size of which must
    # be a multiple of 4 bytes.
    extrabytes = 4 - ((w * 3) % 4)
    (extrabytes == 4) && (extrabytes = 0)
    paddedsize = ((w * 3) + extrabytes) * h;

    # file header
    write(io, b"BM")
    write(io, UInt32(paddedsize))
    write(io, UInt32(0)) # app data
    write(io, UInt32(40 + 14)) # offset

    write(io, UInt32(40))
    write(io, Int32(w))
    write(io, Int32(h))

    write(io, UInt16(1)) # number color planes
    write(io, UInt16(24)) # number color planes
    write(io, UInt32(0)) # compression is none
    write(io, UInt32(0)) # image bits size
    write(io, Int32(0)) # horz resoluition in pixel / m
    write(io, Int32(0)) # vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
    write(io, Int32(0)) #colors in pallete
    write(io, Int32(0)) #important colors

    for i in h:-1:1
        for j in 1:w
            @inbounds rgb = RGB{N0f8}(img[i, j])
            write(io, reinterpret(UInt8, blue(rgb)))
            write(io, reinterpret(UInt8, green(rgb)))
            write(io, reinterpret(UInt8, red(rgb)))
        end
        for i = 1:extrabytes
            write(io, UInt8(0))
        end
    end
end

using Images, Colors
using FileIO

x = load(joinpath(homedir(), "Desktop", "test.jpg"))
open("test.bmp", "w") do io
    @time writebmp(io, x)
end


"""
    mineral_specs(mineral::Symbol)

Reads and returns the spectral data of a chosen mineral.

Available `mineral` types:
* `:augite`
* `:goethite`
* `:hematite`
* `:pigeonite`
"""
function mineral_specs(mineral::Symbol)
    mineral == :augite ? filename = "Augite.spec" :
    mineral == :goethite ? filename = "Goethite.spec" :
    mineral == :hematite ? filename = "Hematite.spec" :
    mineral == :pigeonite ? filename = "Pigeonite.spec" : nothing
    fp=@__DIR__
    cd(fp)
    cd("..\\spectra")
    sfile=readlines(filename)
    map(x->parse(Float64, x), sfile)
end

"""
    mineral(mineral::Symbol, spectable = mineral_specs(mineral))

Returns reflectance spectrum of chosen mineral.

Allowed names for `mineral`: `:augite`, `:goethite`, `:hematite`, `:pigeonite`
"""
function mineral(mineral::Symbol, spectable = mineral_specs(mineral))
    len = div(length(spectable), 2)
    0 < idx < 1270 ? (i = (idx - 1) * n + 1) : (throw(DomainError(idx, "Index is outside the range of 1 ≤ idx ≤ 1269.")))
    reflectance_spec(list[map(n -> n * 2 - 1, 1:len)], list[map(n -> n * 2, 1:len)])
end


using NESSie.Format

@testset "readstl" begin
    @test_skip readstl
end

@testset "writestl" begin
    for T in testtypes
        # empty system
        model = Model{T, Triangle{T}}()
        (fname, fh) = mktemp()
        try
            writestl(fh, model)
            seek(fh, 0)
            @test [read(fh, UInt8) for _ in 1:80] ≈ zeros(Float32, 80)
            @test read(fh, UInt32) == 0
            @test_throws EOFError read(fh, UInt8)
        finally
            close(fh)
            rm(fname)
        end

        # small system
        nodes = Vector{T}[[0, 0, 0], [0, 0, 3], [0, 3, 0], [1, -3, 3]]
        model = Model(
            nodes,
            Triangle{T}[
                Triangle(nodes[1], nodes[2], nodes[3]),
                Triangle(nodes[1], nodes[4], nodes[2])
            ]
        )
        (fname, fh) = mktemp()
        try
            writestl(fh, model)
            seek(fh, 0)
            @test [read(fh, UInt8) for _ in 1:80] ≈ zeros(Float32, 80)
            @test read(fh, UInt32) == 2
            for elem in model.elements
                @test [read(fh, Float32) for _ in 1:3] ≈ elem.normal
                @test [read(fh, Float32) for _ in 1:3] ≈ elem.v1
                @test [read(fh, Float32) for _ in 1:3] ≈ elem.v2
                @test [read(fh, Float32) for _ in 1:3] ≈ elem.v3
                @test read(fh, UInt16) == zero(UInt16)
            end
            @test_throws EOFError read(fh, UInt8)
        finally
            close(fh)
            rm(fname)
        end
    end
end


module DynamicHMCModels

using Reexport, Requires

@reexport using DynamicHMC, LogDensityProblems, TransformVariables
@reexport using Distributions, Random, Statistics
@reexport using Parameters, CSV, DataFrames
@reexport using MonteCarloMeasurements

function __init__()
  @require MCMCChains="c7f686f2-ff18-58e9-bc7b-31028e88f75d" include("require/chains.jl")
end

include("particles.jl")

end # module


using Statistics
using Oceananigans.Architectures

using OffsetArrays: OffsetArray

import Base: getindex, size, show, minimum, maximum
import Statistics: mean

import Oceananigans.Fields: AbstractField, AbstractDataField, AbstractReducedField, Field, ReducedField, minimum, maximum, mean, location
import Oceananigans.Grids: new_data
import Oceananigans.BoundaryConditions: FieldBoundaryConditions

struct CubedSphereFaces{E, F}
    faces :: F
end

@inline Base.getindex(f::CubedSphereFaces, i::Int) = @inbounds f.faces[i]

#####
##### Dispatch the world
#####

const CubedSphereData = CubedSphereFaces{<:OffsetArray}

# Flavors of CubedSphereField
const CubedSphereField = Field{X, Y, Z, A, <:CubedSphereData} where {X, Y, Z, A}
const CubedSphereReducedField = ReducedField{X, Y, Z, A, <:CubedSphereData} where {X, Y, Z, A}
const CubedSphereAbstractField = AbstractField{X, Y, Z, A, <:ConformalCubedSphereGrid} where {X, Y, Z, A}
const CubedSphereAbstractDataField = AbstractDataField{X, Y, Z, A, <:ConformalCubedSphereGrid} where {X, Y, Z, A}
const CubedSphereAbstractReducedField = AbstractReducedField{X, Y, Z, A, D, <:ConformalCubedSphereFaceGrid} where {X, Y, Z, A, D}

const AbstractCubedSphereField = Union{CubedSphereAbstractField,
                                       CubedSphereAbstractDataField,
                                       CubedSphereAbstractReducedField,
                                       CubedSphereField}

# Just a single face
const CubedSphereFaceField = AbstractField{X, Y, Z, A, <:ConformalCubedSphereFaceGrid} where {X, Y, Z, A}

# There must be a way to dispatch in one function without ambiguity with `new_data.jl`...

function new_data(FT, arch::AbstractCPUArchitecture, grid::ConformalCubedSphereGrid, (X, Y, Z))
    faces = Tuple(new_data(FT, arch, face_grid, (X, Y, Z)) for face_grid in grid.faces)
    return CubedSphereFaces{typeof(faces[1]), typeof(faces)}(faces)
end

function new_data(FT, arch::AbstractGPUArchitecture, grid::ConformalCubedSphereGrid, (X, Y, Z))
    faces = Tuple(new_data(FT, arch, face_grid, (X, Y, Z)) for face_grid in grid.faces)
    return CubedSphereFaces{typeof(faces[1]), typeof(faces)}(faces)
end

function FieldBoundaryConditions(grid::ConformalCubedSphereGrid, (X, Y, Z); user_defined_bcs...)

    faces = Tuple(
        inject_cubed_sphere_exchange_boundary_conditions(
            FieldBoundaryConditions(face_grid, (X, Y, Z); user_defined_bcs...),
            face_index,
            grid.face_connectivity
        )
        for (face_index, face_grid) in enumerate(grid.faces)
    )

    return CubedSphereFaces{typeof(faces[1]), typeof(faces)}(faces)
end

#####
##### Utils
#####

@inline function interior(field::AbstractCubedSphereField)
    faces = Tuple(interior(face_field_face) for face_field in faces(field))
    return CubedSphereFaces(faces)
end

Base.size(data::CubedSphereData) = (size(data.faces[1])..., length(data.faces))

@inline function get_face(field::AbstractCubedSphereField, face_index)
    X, Y, Z = location(field)

    # Should we define a new lower-level constructor for Field that doesn't call validate_field_data?
    return Field{X, Y, Z}(get_face(field.data, face_index),
                          field.architecture,
                          get_face(field.grid, face_index),
                          get_face(field.boundary_conditions, face_index))
end

@inline function get_face(reduced_field::CubedSphereAbstractReducedField, face_index)
    X, Y, Z = location(reduced_field)

    return ReducedField{X, Y, Z}(get_face(reduced_field.data, face_index),
                                 reduced_field.architecture,
                                 get_face(reduced_field.grid, face_index),
                                 reduced_field.dims,
                                 get_face(reduced_field.boundary_conditions, face_index))
end

faces(field::AbstractCubedSphereField) = Tuple(get_face(field, face_index) for face_index in 1:length(field.data.faces))

minimum(field::AbstractCubedSphereField; dims=:) = minimum(minimum(face_field; dims) for face_field in faces(field))
maximum(field::AbstractCubedSphereField; dims=:) = maximum(maximum(face_field; dims) for face_field in faces(field))
mean(field::AbstractCubedSphereField; dims=:) = mean(mean(face_field; dims) for face_field in faces(field))

minimum(f, field::AbstractCubedSphereField; dims=:) = minimum(minimum(f, face_field; dims) for face_field in faces(field))
maximum(f, field::AbstractCubedSphereField; dims=:) = maximum(maximum(f, face_field; dims) for face_field in faces(field))
mean(f, field::AbstractCubedSphereField; dims=:) = mean(mean(f, face_field; dims) for face_field in faces(field))

λnodes(field::CubedSphereFaceField{LX, LY, LZ}) where {LX, LY, LZ} = λnodes(LX(), LY(), LZ(), field.grid)
φnodes(field::CubedSphereFaceField{LX, LY, LZ}) where {LX, LY, LZ} = φnodes(LX(), LY(), LZ(), field.grid)


using BenchmarkTools, SNOPT7, Plots
using FileIO, MeshIO, GeometryTypes, CoordinateTransformations, MeshCat
using Colors, Plots

#################################
#        VISUALIZATION          #
#################################
function plot_cylinder(vis,c1,c2,radius,mat,name="")
    geom = Cylinder(Point3f0(c1),Point3f0(c2),convert(Float32,radius))
    setobject!(vis["cyl"][name],geom,MeshPhongMaterial(color=RGBA(1, 0, 0, 1.0)))
end

function addcylinders!(vis,cylinders,height=1.5)
    for (i,cyl) in enumerate(cylinders)
        plot_cylinder(vis,[cyl[1],cyl[2],0],[cyl[1],cyl[2],height],cyl[3],MeshPhongMaterial(color=RGBA(0, 0, 1, 1.0)),"cyl_$i")
    end
end

function addspheres!(vis,spheres)
    for (i,sphere) in enumerate(spheres)
        geom = HyperSphere(Point3f0(sphere[1:3]), convert(Float32,sphere[4]))
        setobject!(vis["sph"]["sph_$i"],geom,MeshPhongMaterial(color=RGBA(0, 0, 1, 1.0)))
    end
end

function plot_scene(vis=Visualizer())
    addcylinders!(vis,Problems.cyl_obs,20.0)
    addspheres!(vis,Problems.sph_obs)
end

function plot_quad(x0=prob.x0, xf=prob.xf)
    traj_folder = TrajectoryOptimization.root_dir()
    urdf_folder = joinpath(traj_folder, "dynamics","urdf")
    obj = joinpath(urdf_folder, "quadrotor_base.obj")

    quad_scaling = 0.7
    robot_obj = FileIO.load(obj)
    robot_obj.vertices .= robot_obj.vertices .* quad_scaling

    setobject!(vis["robot"]["quad"],robot_obj,MeshPhongMaterial(color=RGBA(0, 0, 0, 1.0)))
    settransform!(vis["robot"], compose(Translation(x0[1:3]...),LinearMap(Quat(x0[4:7]...))))

    setobject!(vis["robot_goal"]["quad"],robot_obj,MeshPhongMaterial(color=RGBA(0, 0, 0, 0.3)))
    settransform!(vis["robot_goal"], compose(Translation(xf[1:3]...),LinearMap(Quat(xf[4:7]...))))

    settransform!(vis["/Cameras/default"], compose(Translation(-20., 72., 60.),LinearMap(RotX(pi/7.5)*RotZ(pi/2))))
end

function animate_quad!(vis, prob::Problem)
    anim = MeshCat.Animation(24)
    for i = 1:prob.N
        MeshCat.atframe(anim,vis,i) do frame
            settransform!(frame["robot"], compose(Translation(prob.X[i][1:3]...),LinearMap(Quat(prob.X[i][4:7]...))))
        end
    end
    MeshCat.setanimation!(vis,anim)
end

vis = Visualizer()
open(vis)
plot_scene()
plot_quad()

#################################
#      SOLVING THE PROBLEM      #
#################################
T = Float64

# options
max_con_viol = 1.0e-8
verbose=false

prob = copy(Problems.quad_obs)

# iLQR
opts_ilqr = iLQRSolverOptions{T}(verbose=true,
    iterations=300)
@time r0, s0 = solve(prob, opts_ilqr)
animate_quad!(vis, r0)
plot(res.U)
open(joinpath(@__DIR__,"ilqr_times.txt"),"w") do file
    TimerOutputs.print_timer(file, s0.stats[:timer])
end

# AL-iLQR
prob = copy(Problems.quad_obs)
opts_ilqr.verbose = false
opts_al = AugmentedLagrangianSolverOptions{T}(verbose=true,
    opts_uncon=opts_ilqr,
    iterations=25,
    cost_tolerance=1.0e-5,
    cost_tolerance_intermediate=1.0e-3,
    constraint_tolerance=1e-4,
    penalty_scaling=10.,
    penalty_initial=0.1)
@time r1, s1 = solve(prob, opts_al)
plot(r1.U)
animate_quad!(vis, r1)

# ALTRO w/ Newton
opts_pn = ProjectedNewtonSolverOptions{T}(verbose=verbose,
    feasibility_tolerance=max_con_viol,
    solve_type=:feasible)

opts_altro = ALTROSolverOptions{T}(verbose=verbose,
    opts_al=opts_al,
    R_inf=1.0e-8,
    resolve_feasible_problem=false,
    opts_pn=opts_pn,
    projected_newton=true,
    projected_newton_tolerance=1.0e-3)
@time r2, s2 = solve(prob, opts_altro)
open(joinpath(@__DIR__,"altro_times.txt"),"w") do file
    TimerOutputs.print_timer(file, s2.stats[:timer])
end

plot(r2.U)
animate_quad!(vis, r2)
max_violation_direct(p2)

# Ipopt
opts_ipopt = DIRCOLSolverOptions{T}(verbose=verbose,
    nlp=Ipopt.Optimizer(),
    opts=Dict(:max_iter=>10000),
    feasibility_tolerance=max_con_viol)
r3, s3 = solve(prob, opts_ipopt)
plot(r3.U)
animate_quad!(vis, r3)

# SNOPT
opts_snopt = DIRCOLSolverOptions{T}(verbose=verbose,
    nlp=SNOPT7.Optimizer(),
    feasibility_tolerance=max_con_viol,
    opts=Dict(:Iterations_limit=>500000,
        :Major_iterations_limit=>1000))
r4, s4 = solve(prob, opts_snopt)


module TestUtils

using IterTools
using ParameterHandling
using Test

using ParameterHandling: AbstractParameter, value

# Handles equality of scalars, functions, tuples or arbitrary types
function default_equality(a::T, b::T; kwargs...) where {T}
    vals = fieldvalues(a)

    # If we don't have any fields then we're probably dealing with scalars
    if isempty(vals)
        # Only call isapprox for numbers, otherwise we fallback to ==
        return T <: Number ? isapprox(a, b; kwargs...) : a == b
    else
        return all(t -> default_equality(t...; kwargs...), zip(vals, fieldvalues(b)))
    end
end

# Handles extracting elements from arrays.
# Needed because fieldvalues(a) are empty, but we may need to recurse depending on
# the element type
function default_equality(a::T, b::T; kwargs...) where {T<:AbstractArray}
    return all(t -> default_equality(t...; kwargs...), zip(a, b))
end

# Handles extracting values for any dictionary types
function default_equality(a::T, b::T; kwargs...) where {T<:AbstractDict}
    return all(t -> default_equality(t...; kwargs...), zip(values(a), values(b)))
end

# NOTE: May want to make the equality function a kwarg in the future.
function test_flatten_interface(x::T; check_inferred::Bool=true) where {T}
    @testset "flatten($T)" begin
        # Checks default eltype still works and ensure that
        # basic functionality is implemented.
        v, unflatten = flatten(x)
        @test typeof(v) === Vector{Float64}
        @test default_equality(x, unflatten(v))
        @test unflatten(v) isa T

        # Check that everything infers properly.
        check_inferred && @inferred flatten(x)

        # Test with different precisions
        @testset "Float64" begin
            _v, _unflatten = flatten(Float64, x)
            @test typeof(_v) === Vector{Float64}
            @test _v == v
            @test default_equality(x, unflatten(_v))
            @test _unflatten(_v) isa T

            # Check that everything infers properly.
            check_inferred && @inferred flatten(Float64, x)
        end
        @testset "Float32" begin
            _v, _unflatten = flatten(Float32, x)
            @test typeof(_v) === Vector{Float32}
            @test default_equality(x, _unflatten(_v); atol=1e-5)
            @test _unflatten(_v) isa T

            # Check that everything infers properly.
            check_inferred && @inferred flatten(Float32, x)
        end
        @testset "Float16" begin
            _v, _unflatten = flatten(Float16, x)
            @test typeof(_v) === Vector{Float16}
            @test default_equality(x, _unflatten(_v); atol=1e-2)
            @test _unflatten(_v) isa T

            # Check that everything infers properly.
            check_inferred && @inferred flatten(Float16, x)
        end
    end

    return nothing
end

function test_parameter_interface(x; check_inferred::Bool=true)

    # Parameters need to be flatten-able.
    test_flatten_interface(x; check_inferred=check_inferred)

    # Run this to make sure that it doesn't error.
    value(x)

    if check_inferred
        @inferred value(x)
    end
    return nothing
end

end # module


Base.@deprecate MockModel() mock_model()

function mock_model()
    optimizer = MOIU.MockOptimizer(Parametron.ParametronMOIModel{Float64}())
    Parametron.Model(optimizer)
end


#!/usr/bin/env julia

using Filetimes

# Command line programming for converting DateTimes and Filetimes,
# e.g.
# ./filetime.jl 2018-07-19T18:26:00.766
# ./filetime.jl 131764980100450000
# ./filetime.jl

function main(args)

    if length(args) > 0
        x = args[1]
        y = tryparse(Int64,x)
        if isnull(y)
            println("$x\t$(filetime(x))")
        else
            println("$(datetime(get(y)))\t$x")
        end
    else
        x = now()
        println("$x\t$(filetime(x))")
    end
end

main(ARGS)


using DataFrames, MCMCChains, CSV

"""

# read_summary

Read summary output file created by stansummary.

### Method
```julia
read_summary(m::Stanmodel)
```

### Required arguments
```julia
* `m::Stanmodel`    : Stanmodel object
```

"""
function read_summary(m::Stanmodel)

  fname = "$(m.tmpdir)/$(m.name)_summary.csv"
  !isfile(fname) && stan_summary(m)

  df = CSV.read(fname, delim=",", comment="#")

  cnames = lowercase.(convert.(String, String.(names(df))))
  cnames[1] = "parameters"
  cnames[4] = "std"
  cnames[8] = "ess"
  names!(df, Symbol.(cnames))
  df.parameters = Symbol.(df.parameters)

  ChainDataFrame("CmdStan Summary", df)

end   # end of read_samples


#!/usr/bin/env bash
    #=
    exec julia --project="$(realpath $(dirname $0))/" "${BASH_SOURCE[0]}" "$@" -e "include(popfirst!(ARGS))" \
    "${BASH_SOURCE[0]}" "$@"
    =#

println("\033[1;34m===>\033[0;38m\033[1;38m\tLoading required libraries (it will take a moment to precompile if it is your first time doing this)...\033[0;38m")

include(joinpath(dirname(dirname(@__FILE__)), "src", "FaceDetection.jl"))

using .FaceDetection
const FD = FaceDetection
using Images: imresize
using StatsPlots  # StatsPlots required for box plots # plot boxplot @layout :origin savefig
using CSV: write
using DataFrames: DataFrame
using HypothesisTests: UnequalVarianceTTest
using Serialization: deserialize

println("...done")

function main(;
	smart_choose_feats::Bool=false,
	scale::Bool=false,
	scale_to::Tuple=(200, 200))

	include("constants.jl")
	include("main_data.jl")
    pos_testing_path = "/Users/jakeireland/projects/FaceDetection.jl/data/lizzie-testset/faces/"
	neg_testing_path = "/Users/jakeireland/projects/FaceDetection.jl/data/lizzie-testset/nonfaces/"

    # read classifiers from file
	classifiers = deserialize("/Users/jakeireland/Desktop/classifiers_577_577_fixed")

    notify_user("Calculating test face scores and constructing dataset...")

    faces_scores = Vector{Real}(undef, length(filtered_ls(pos_testing_path)))
    non_faces_scores = Vector{Real}(undef, length(filtered_ls(neg_testing_path)))

    faces_scores[:] .= [sum([FD.get_faceness(c, load_image(face, scale=scale, scale_to=scale_to)) for c in classifiers]) for face in filtered_ls(pos_testing_path)]
	non_faces_scores[:] .= [sum([FD.get_faceness(c, load_image(non_face, scale=scale, scale_to=scale_to)) for c in classifiers]) for non_face in filtered_ls(neg_testing_path)]

	face_names = filtered_ls(pos_testing_path)
	non_face_names = filtered_ls(neg_testing_path)

    # filling in the dataset with missing to easily write to csv
    df_faces = faces_scores
    df_non_faces = non_faces_scores
    if length(faces_scores) < length(non_faces_scores)
        to_add = length(filtered_ls(neg_testing_path)) - length(filtered_ls(pos_testing_path))
        df_faces = vcat(df_faces, Matrix{Union{Float64, Missing}}(undef, to_add, 1))
        face_names = vcat(face_names, Matrix{Union{Float64, Missing}}(undef, to_add, 1))
    elseif length(faces_scores) > length(non_faces_scores)
        to_add = length(filtered_ls(pos_testing_path)) - length(filtered_ls(neg_testing_path))
        df_non_faces = vcat(df_non_faces, Matrix{Union{Float64, Missing}}(undef, to_add, 1))
        non_face_names = vcat(non_face_names, Matrix{Union{Float64, Missing}}(undef, to_add, 1))
    end

    # write score data
    write(joinpath(dirname(dirname(@__FILE__)), "data", "faceness-scores.csv"), DataFrame(hcat(face_names, df_faces, non_face_names, df_non_faces)), writeheader=false)

    println("...done.\n")

    notify_user("Computing differences in scores between faces and non-faces...")

    welch_t = UnequalVarianceTTest(faces_scores, non_faces_scores)

    println("...done.  $welch_t\n")

    notify_user("Constructing box plot with said dataset...")

    gr() # set plot backend
    theme(:solarized)
    plot = StatsPlots.plot(
                    StatsPlots.boxplot(faces_scores, xaxis=false, label = false),
                    StatsPlots.boxplot(non_faces_scores, xaxis=false, label = false),
                    title = ["Scores of Faces" "Scores of Non-Faces"],
                    # label = ["faces" "non-faces"],
                    fontfamily = font("Times"),
                    layout = @layout([a b]),
                    # fillcolor = [:blue, :orange],
                    link = :y,
                    # framestyle = [:origin :origin]
                )

    # save plot
    StatsPlots.savefig(plot, joinpath(dirname(dirname(@__FILE__)), "figs", "scores.pdf"))

    println("...done.  Plot created at ", joinpath(dirname(dirname(@__FILE__)), "figs", "scores.pdf"), "\n")
end

@time main(smart_choose_feats=true, scale=true, scale_to=(577, 577))


module CP

include("CP_Exact.jl")
include("CP_EM.jl")
include("CP_VB.jl")
include("CP_MC.jl")
include("BackwardKernel.jl")

using .CP_Exact, .CP_EM, .CP_VB, .CP_MC, .BackwardKernel

export generate, log_marginal
export standard_EM, dual_EM
export standard_VB, online_VB
export Particle, smc_weight, particle_mcmc
export EventQueue, sum, length, eltype, iterate

end


module TestNA

using FactCheck
using DataCubes
using DataCubes: AbstractArrayWrapper, FloatNAArray, simplify_array, wrap_array, naop_plus

facts("NA tests") do
  @fact AbstractArrayWrapper(nalift([1,2,3])) --> AbstractArrayWrapper([Nullable(1), Nullable(2), Nullable(3)])
  @fact map(AbstractArrayWrapper,Any[nalift([1 2 3]),nalift([4,5])]) --> map(AbstractArrayWrapper,Any[[Nullable(1) Nullable(2) Nullable(3)],[Nullable(4), Nullable(5)]])
  @fact AbstractArrayWrapper(simplify_array(nalift(Any[[1 2 3],[4,5]]))) --> AbstractArrayWrapper(simplify_array(Any[Nullable([1 2 3]), Nullable([4,5])]))
  @fact @nalift([1 2 3;4 NA 5]) --> AbstractArrayWrapper([Nullable(1) Nullable(2) Nullable(3);Nullable(4) Nullable{Int}() Nullable(5)])
  @fact @nalift(Any[[1 2 NA],[3,NA]]) --> nalift(map(AbstractArrayWrapper,Any[[Nullable(1) Nullable(2) Nullable{Int}()], [Nullable(3), Nullable{Int}()]]))
  @fact AbstractArrayWrapper([x for x in FloatNAArray([1.0,2.0,NaN])]) --> AbstractArrayWrapper([Nullable(1.0), Nullable(2.0), Nullable{Float64}()])
  @fact AbstractArrayWrapper(FloatNAArray(reverse([1.0,2.0,3.0]))) --> AbstractArrayWrapper(FloatNAArray([3.0,2.0,1.0]))
  @fact simplify_array(nalift([1,2,3])) --> AbstractArrayWrapper([Nullable(1),Nullable(2),Nullable(3)])
  @fact simplify_array(Any[Nullable(3.5), Nullable(2.0), Nullable{Float64}()]) --> AbstractArrayWrapper(FloatNAArray([3.5, 2.0, NaN]))
  @fact (t=@larr(a=[1 2 3;4 5 6], b=[4 5 6;7 8 9]);dcube.setna!(t.data.data[:a],2,2);dcube.setna!(t,2,1);t) --> @larr(a=[1 2 3;NA NA 6], b=[4 5 6;NA 8 9])
  @fact simplify_array(nalift([1.0,2.0,3.0])) --> nalift([1.0,2.0,3.0])
  @fact DataCubes.simplify_floatarray(nalift([1.0,2.0,3.0])) --> nalift([1.0,2.0,3.0])
  @fact sort(nalift([5.0,3.0,4.0])) --> nalift([3.0,4.0,5.0])
  @fact sort!(@nalift([5.0,3.0,4.0])) --> nalift([3.0,4.0,5.0])
  @fact DataCubes.wrap_array(vcat(FloatNAArray(1.0*[1 2 3]),FloatNAArray(1.0*[4 5 6]))) --> DataCubes.wrap_array(FloatNAArray([1 2 3;4 5 6]*1.0))
  @fact DataCubes.wrap_array(hcat(FloatNAArray(1.0*[1 2 3]),FloatNAArray(1.0*[4 5 6]))) --> DataCubes.wrap_array(FloatNAArray([1 2 3 4 5 6]*1.0))
  @fact DataCubes.simplify_floatarray(Nullable{Float64}[]) --> isempty
  @fact typeof(DataCubes.simplify_floatarray(Nullable{Float64}[])) --> DataCubes.FloatNAArray{Float64,1,Array{Float64,1}}
  @fact nalift(FloatNAArray([1.0 2.0 NaN])) --> @nalift([1.0 2.0 NA])
  @fact (t=@nalift([1.0 NA 3]);DataCubes.setna!(t);t) --> nalift(fill(Nullable{Any}(),1,3))
  @fact (t=@nalift([1.0 NA 3]);DataCubes.setna!(t);typeof(t)) --> DataCubes.AbstractArrayWrapper{Nullable,2,Array{Nullable,2}}
  @fact (t=@nalift([1.0 2.0 3.0]);DataCubes.setna!(t);t) --> nalift(fill(NaN,1,3))
  @fact (t=enumeration([:a,:b,:a]);DataCubes.setna!(t);wrap_array(t)) --> wrap_array(fill(Nullable{Symbol}(),3))
  @fact (t=enumeration([:a,:b,:a]);DataCubes.setna!(t,1);wrap_array(t)) --> wrap_array(@enumeration([NA,:b,:a],[:a,:b]))
  @fact (t=enumeration([:a,:b,:a]);DataCubes.setna!(t);typeof(t)) --> EnumerationArray{Symbol,1,DataCubes.AbstractArrayWrapper{Int64,1,Array{Int64,1}},Int64}
  @fact (t=enumeration([:a,:b,:a]);fill!(t,0);wrap_array(t)) --> wrap_array(fill(Nullable{Symbol}(),3))
  @fact (t=enumeration([:a,:b,:a]);fill!(t,Nullable(:c));wrap_array(t)) --> wrap_array(fill(Nullable{Symbol}(),3))
  @fact (t=enumeration([:a,:b,:a]);fill!(t,Nullable{Symbol}());wrap_array(t)) --> wrap_array(fill(Nullable{Symbol}(),3))
  @fact (t=@nalift([1.0 2.0 3.0]);fill!(t,NaN);t) --> @nalift([NA NA NA])
  @fact (t=@nalift([1.0 2.0 3.0]);fill!(t,Nullable{Float64}());t) --> @nalift([NA NA NA])
  @fact (t=@nalift([1.0 2.0 3.0]);fill!(t,Nullable(5.0));t) --> @nalift([5.0 5.0 5.0])
  @fact igna(Nullable{Int}(), 3) --> 3
  @fact igna(Nullable(1), 3) --> 1
  @fact igna(Nullable(1.0), 3.0) --> 1.0
  @fact igna(Array(Nullable{Int},0)) --> Array(Int,0)
  @fact igna(Array(Nullable{Int},0),1) --> Array(Int,0)
  @fact_throws igna(@nalift([1,2,NA]))
  @fact_throws igna(@nalift([1,2.0,NA]))
  @fact igna(@nalift([1 3 NA]), 1) --> [1 3 1]
  @fact igna(@nalift([1 3.0 NA]), 1) --> [1 3.0 1]
  @fact igna(Array(Nullable,0), 1) --> Array(Nullable,0)
  @fact igna(@nalift([1 3 5]), 1) --> [1 3 5]
  @fact igna(@nalift([1.0 3.0 NA]), 1.0) --> [1.0 3.0 1.0]
  @fact igna(@nalift([1.0 3.0 NA]))[3] --> isnan
  @fact igna(@nalift([1.0 3.0 5.0]), 1.0) --> [1.0 3.0 5.0]
  @fact igna([Nullable(1.0) Nullable(3.0) Nullable{Float64}()])[3] --> isnan
  @fact igna([Nullable(1.0) Nullable(3.0) Nullable(5.0)], 1.0) --> [1.0 3.0 5.0]
  @fact igna([Nullable(1.0) Nullable(3.0) Nullable{Float64}()], 1.0) --> [1.0 3.0 1.0]
  @fact isna(Nullable{Int}()) --> true
  @fact isna(Nullable(1)) --> false
  @fact isna(Nullable(1.0)) --> false
  @fact isna(@nalift([1 3 NA])) --> [false false true]
  @fact isna(@nalift([1 3 NA]), 2:3) --> [false false true][2:3]
  @fact isna(@nalift([1 3 5])) --> [false false false]
  @fact isna(@nalift([1.0 3.0 NA])) --> [false false true]
  @fact isna(@nalift([1.0 3.0 5.0])) --> [false false false]
  @fact isna(@nalift([1.0 3.0 5 NA])) --> [false false false true]
  @fact isna(@enumeration([:a :b NA;:d NA :f])) --> [false false true;false true false]
  @fact isna(@enumeration([:a :b NA;:d NA :f]), 1:2, 3) --> [false false true;false true false][1:2, 3]
  @fact isna([Nullable{Float64}() Nullable(1.0)]) --> [true false]
  @fact ignabool(nalift([true, false])) --> [true, false]
  @fact ignabool(Nullable{Bool}()) --> false
  @fact ignabool(Nullable(true)) --> true
  @fact ignabool(Nullable(false)) --> false
  @fact ignabool([Nullable(true), Nullable{Bool}()]) --> [true, false]
  @fact ignabool([Nullable(true)]) --> [true]
  @fact ignabool([Nullable(false)]) --> [false]
  @fact ignabool(true) --> true
  @fact ignabool(false) --> false
  @fact @nalift([NA NA]) --> wrap_array([Nullable{Any}() Nullable{Any}()])
  @fact @nalift([NA,NA]) --> wrap_array([Nullable{Any}(),Nullable{Any}()])
  @fact @nalift([NA NA;NA NA]) --> wrap_array(reshape(fill(Nullable{Any}(),4),2,2))
  @fact @nalift([1,NA]) --> wrap_array([Nullable(1), Nullable{Int}()])
  @fact @nalift([1 NA]) --> wrap_array([Nullable(1)  Nullable{Int}()])
  @fact @nalift([1 NA;NA NA]) --> wrap_array([Nullable(1)  Nullable{Int}();Nullable{Int}() Nullable{Int}()])
  @fact map(x->x, nalift([1.0,2.0,3.0])) --> nalift([1.0,2.0,3.0])
  @fact map((x,y)->naop_plus(x, y), nalift([1.0,2.0,3.0]), nalift([2.0,3.0,4.0])) --> nalift([3.0,5.0,7.0])
  #@fact nalift(DataCubes.simplify_array(Any[larr(a=[1,2,3]), darr(b=[:a,:b,:c])])) --> DataCubes.simplify_array(Any[larr(a=[1,2,3]), darr(b=[:a,:b,:c])])
  #@fact nalift(DataCubes.simplify_array(Any[darr(a=[1,2,3]), darr(b=[:a,:b,:c])])) --> DataCubes.simplify_array(Any[darr(a=[1,2,3]), darr(b=[:a,:b,:c])])
  #@fact nalift(DataCubes.simplify_array(Any[larr(a=[1,2,3]), larr(b=[:a,:b,:c])])) --> DataCubes.simplify_array(Any[larr(a=[1,2,3]), larr(b=[:a,:b,:c])])
  @fact nalift(nalift([1.0,2.0,3.0])) --> nalift([1.0,2.0,3.0])
  @fact pickaxis(larr(a=[1,2,3]),1) == nalift([1,2,3]) --> false
  @fact nalift([1,2,3]) == pickaxis(larr(a=[1,2,3]),1) --> false

  context("FloatNAArray tests") do
    @fact map(identity, FloatNAArray(Array(Float64,0))) --> Array(Nullable{Any},0)
    @fact nalift([1.0,2.0]) --> DataCubes.wrap_array(FloatNAArray([1.0,2.0]))
    @fact FloatNAArray([1.0,2.0,3.0])[1].value --> 1.0
    @fact FloatNAArray([1.0,2.0,3.0])[1:2].data --> [1.0,2.0]
    @fact wrap_array(DataCubes.simplify_floatarray(FloatNAArray([1.0 2.0]))) --> wrap_array(FloatNAArray([1.0 2.0]))
    @fact typeof(DataCubes.simplify_floatarray(FloatNAArray(Array(Float64,0)))) --> FloatNAArray{Float64,1,Array{Float64,1}}
    @fact nalift(nalift([1.0,2.0,3.0])) --> nalift([1.0,2.0,3.0])
    @fact typeof(igna(nalift(Array(Float64,0)))) --> DataCubes.AbstractArrayWrapper{Float64,1,Array{Float64,1}}
    @fact typeof(igna(nalift(Array(Float64,0)),3.0)) --> DataCubes.AbstractArrayWrapper{Float64,1,Array{Float64,1}}
    @fact (arr=nalift([1.0,2.0,3.0]);arr[1]=0.0;arr) --> nalift([0.0,2.0,3.0])
    @fact (arr=nalift([1.0,2.0,3.0]);arr[1]=Nullable{Float64}();arr) --> @nalift([NA,2.0,3.0])
    @fact (arr=nalift([1.0,2.0,3.0]);arr[1:2]=nalift([0.0,-1.0]) ;arr) --> nalift([0.0,-1.0,3.0])
    @fact (arr=nalift([1.0,2.0,3.0]);arr[1:2]=Nullable(1.0) ;arr) --> nalift([1.0,1.0,3.0])
    @fact typeof(1.0*nalift([1 2])) --> AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}}
    @fact sub(1.0*nalift([1 2 3;4 5 6]),1:2,1:1) --> nalift([1.0 4.0]')
    @fact slice(1.0*nalift([1 2 3;4 5 6]),1:2,1) --> nalift([1.0,4.0])
    @fact sub(1.0*nalift([1 2 3;4 5 6]),(1:2,1:1)) --> nalift([1.0 4.0]')
    @fact slice(1.0*nalift([1 2 3;4 5 6]),(1:2,1)) --> nalift([1.0,4.0])
  end
end

end