@testset "Standard synapse tests" begin
    test_net="""
spike_duration: 0.1
refractory_duration: 0.2
delay: 0.3
plateau_duration: 50.0

# Comments should work, tool
inputs:
    - id: i1 # even inline comments
    - id: i2
    - id: i3

outputs:
    - id: o1
    - id: o2

random_unsupported_network_stuff1: nothing_of_relevance
random_unsupported_network_stuff2: nothing_of_relevance

neurons:
    - id: n1
      random_unsupported_neuron_stuff: nothing_of_relevance
    - id: n2
      refractory_duration: 0.4
      θ_syn: 2
      θ_seg: 2
      branches:
        - id: s1
          θ_syn: 10
        - id: s2
          branches:
            - id: s21
              plateau_duration: 20.0
              random_unsupported_segment_stuff: nothing_of_relevance
              θ_seg: 0
            - id: s22

synapses:
    - {id: syn1, source: i1, target: n1}
    - {id: syn2, source: i2, target: s22}
    - {id: syn3, source: i3, target:  s2, spike_duration: 3.0, random_unsupported_synapse_stuff: nothing_of_relevance}
    - {id: syn4, source: n1, target:  o1, delay: 10.0, weight: 2}
    """

    (net,obj) = (
        @test_logs (
                :warn, "The following network parameters could not be parsed: random_unsupported_network_stuff1, random_unsupported_network_stuff2"
            ) (
                :warn, "The following neuron parameters could not be parsed: random_unsupported_neuron_stuff"
            ) (
                :warn, "The following segment parameters could not be parsed: random_unsupported_segment_stuff"
            ) (
                :warn, "The following synapse parameters could not be parsed: random_unsupported_synapse_stuff"
            ) load_network(YAML_source=test_net)
        )

    @testset "Network structure tests" begin
        @test net.neurons[1] == obj[:n1]
        @test net.neurons[2] == obj[:n2]
        @test net.neurons[2].next_upstream[1] == obj[:s1]
        @test net.neurons[2].next_upstream[2].next_upstream[1] == obj[:s21]
        @test net.inputs[1] == obj[:i1]
        @test net.inputs[1].synapses[1] == obj[:syn1]
        @test net.neurons[1].synapses[1] == obj[:syn4]
        @test net.outputs[1] == obj[:o1]
    end

    @testset "Neuron parameters tests" begin
        @test obj[:n1].id == :n1
        @test obj[:n1].refractory_duration == 0.2
        @test obj[:n1].θ_syn == 1
        @test obj[:n1].θ_seg == 1
        @test obj[:n2].refractory_duration == 0.4
        @test obj[:n2].θ_syn == 2
        @test obj[:n2].θ_seg == 2
    end

    @testset "Segment parameters tests" begin
        @test obj[:s1].id == :s1
        @test obj[:s1].plateau_duration == 50.0
        @test obj[:s1].θ_syn == 10
        @test obj[:s1].θ_seg == 1
        @test obj[:s21].plateau_duration == 20.0
        @test obj[:s21].θ_syn == 1
        @test obj[:s21].θ_seg == 0
    end

    @testset "Synapse parameters tests" begin
        @test obj[:syn3].id == :syn3
        @test obj[:syn3].target == obj[:s2]
        @test obj[:syn3].delay == 0.3
        @test obj[:syn3].spike_duration == 3.0
        @test obj[:syn3].weight == 1
        @test obj[:syn4].target == obj[:o1]
        @test obj[:syn4].delay == 10.0
        @test obj[:syn4].weight == 2
    end

    @testset "Input parameters tests" begin
        @test obj[:i1].id == :i1
    end

    @testset "Output parameters tests" begin
        @test obj[:o1].id == :o1
    end

    @testset "Saving tests" begin
        tmp_str = save_network(net)
        (net2,obj2) = @test_logs  load_network(YAML_source=tmp_str)
        @test all(pairs(obj2)) do (key,value)
            key ∈ keys(obj)
        end
    end
end

@testset "Bernoulli synapse tests" begin
    test_net="""
    spike_duration: 0.1
    delay: 0.3
    plateau_duration: 50.0

    inputs:
        - id: i1
        - id: i2

    neurons:
        - id: n1

    synapses:
        - {id: syn1, source: i1, target: n1}
        - {id: syn2, source: i2, target: n1, weight: 1.2345, spike_duration: 3.0, random_unsupported_synapse_stuff: nothing_of_relevance}
        - {id: syn3, source: n1, target: n1, delay: 10.0, weight: [1.2345, 0.75]}
    """

    (net,obj) = (
        @test_logs (
                :warn, "The following synapse parameters could not be parsed: random_unsupported_synapse_stuff"
            ) load_network(YAML_source=test_net, weight_type=BernoulliSynapseWeight{Float64})
        )

    @testset "Synapse parameters tests" begin
        @test obj[:syn1].id == :syn1
        @test obj[:syn1].target == obj[:n1]
        @test obj[:syn1].delay == 0.3
        @test obj[:syn1].spike_duration == 0.1
        @test isa(obj[:syn1].weight, BernoulliSynapseWeight{Float64}) && obj[:syn1].weight.magnitude ≈ 1.0 && obj[:syn1].weight.probability ≈ 1.0
        @test obj[:syn2].id == :syn2
        @test obj[:syn2].target == obj[:n1]
        @test obj[:syn2].delay == 0.3
        @test obj[:syn2].spike_duration == 3.0
        @test isa(obj[:syn2].weight, BernoulliSynapseWeight{Float64}) && obj[:syn2].weight.magnitude ≈ 1.2345 && obj[:syn2].weight.probability ≈ 1.0
        @test obj[:syn3].id == :syn3
        @test obj[:syn3].target == obj[:n1]
        @test obj[:syn3].delay == 10.0
        @test obj[:syn3].spike_duration == 0.1
        @test isa(obj[:syn3].weight, BernoulliSynapseWeight{Float64}) && obj[:syn3].weight.magnitude ≈ 1.2345 && obj[:syn3].weight.probability ≈ 0.75
    end

    @testset "Saving tests" begin
        tmp_str = save_network(net)
        (net2,obj2) = @test_logs  load_network(YAML_source=tmp_str, weight_type=BernoulliSynapseWeight{Float64})
        @test all(pairs(obj2)) do (key,value)
            key ∈ keys(obj)
        end
    end
end


#__precompile__()

module HDR

using Images, ImageView, Plots, ImageFiltering, FileIO

# Currently N0f16 and others are NOT supported
HDRSequence = Vector{Tuple{Matrix{RGB{N0f8}}, Float64}}

include("tonemapper.jl")
include("synthesizer.jl")
include("reader.jl")

include("test_synthesizer.jl")

export read_hdrgen_file
export image_synthesis
export hdr_sequence_downsample
export pseudo_color_visualizer

end # module HDR


module LForaging

using Reexport
#add CSV@0.8.5
@reexport using DataFrames, CategoricalArrays, StatsPlots, BrowseTables, StatsBase, Bootstrap, MixedModels, Optim, Distributions
#CSV,
import Statistics: median, std

include("Analysis_fun.jl")
include("preprocess.jl")
include("RewRateUpdates.jl")

export survivalrate_algorythm, cumulative_algorythm, hazardrate_algorythm, function_analysis
export median, std
export preprocess_pokes, trial_info!, bout_info!, travel_info, leave_info!
export preprocess_bouts
export get_rew_rate

end # module


# Sun 30 Dec 2012 01:22:44 PM EST
#
# NeHe Tut 17 - 2D texture font
#
# Q - quit
# L - turn lights on/off
# F - change texture filter (linear, nearest, mipmap)
# PageUp/Down - move camera closer/further away
# Up/Down - increase/decrease x-rotation speed
# Left/Right - increase/decrease y-rotation speed

# TODO: This example runs, but it produces a very glitchy output.

# load necessary GL/SDL routines

using OpenGL
@OpenGL.version "1.0"
@OpenGL.load
using SDL

# initialize variables

bpp                = 16
wintitle           = "NeHe Tut 17"
icontitle          = "NeHe Tut 17"
width              = 640
height             = 480

T0                 = 0
Frames             = 0

cnt1               = 0
cnt2               = 0

keystate_checked   = false
lastkeycheckTime   = 0
key_repeatinterval = 75 #ms

# open SDL window with an OpenGL context

SDL_Init(SDL_INIT_VIDEO)
#videoInfo = SDL_GetVideoInfo()
videoFlags = (SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE | SDL_RESIZABLE)
#if videoInfo.hw_available
    videoFlags = (videoFlags | SDL_HWSURFACE)
#else
    #videoFlags = (videoFlags | SDL_SWSURFACE)
#end
#if videoInfo.blit_hw
    videoFlags = (videoFlags | SDL_HWACCEL)
#end
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_SetVideoMode(width, height, bpp, videoFlags)
SDL_WM_SetCaption(wintitle, icontitle)

glViewport(0, 0, width, height)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glShadeModel(GL_SMOOTH)
glEnable(GL_DEPTH_TEST)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()

gluPerspective(45.0,width/height,0.1,100.0)

glMatrixMode(GL_MODELVIEW)

### auxiliary functions

function glPrint(x::Integer, y::Integer, string::String, set::Integer)
    if set > 1
        set = 1
    end

    glBindTexture(GL_TEXTURE_2D, tex[1])
    glDisable(GL_DEPTH_TEST)

    glMatrixMode(GL_PROJECTION)
    glpushmatrix()

    glLoadIdentity()
    glOrtho(0, 640, 0, 480, -1, 1)

    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glLoadIdentity()

    glTranslate(x, y, 0)
    glListBase(uint32(base-32+(128*set)))
    glCallLists(strlen(string), GL_BYTE, string)

    glMatrixMode(GL_PROJECTION)
    glPopMatrix()

    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()
end

### end of auxiliary functions

# load textures from images

tex       = Array(Uint32,2) # generating 2 textures

imgFont, wFont, hFont = glimread(expanduser("~/.julia/SDL/Examples/NeHe/tut17/font.bmp"))

imgBumps, wBumps, hBumps = glimread(expanduser("~/.julia/SDL/Examples/NeHe/tut17/bumps.bmp"))

glGenTextures(2,tex)
glBindTexture(GL_TEXTURE_2D,tex[1])
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, 3, wFont, hFont, 0, GL_RGB, GL_UNSIGNED_BYTE, imgFont)

glBindTexture(GL_TEXTURE_2D,tex[2])
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, 3, wBumps, hBumps, 0, GL_RGB, GL_UNSIGNED_BYTE, imgBumps)

# enable texture mapping & blending

glEnable(GL_TEXTURE_2D)
glBlendFunc(GL_SRC_ALPHA, GL_ONE)

# build the fonts

base = glGenLists(256)
glBindTexture(GL_TEXTURE_2D, tex[1])

for loop = 1:256
    cx = (loop%16)/16
    cy = (loop/16)/16

    glNewList(uint32(base+(loop-1)), GL_COMPILE)
        glBegin(GL_QUADS)
            glTexCoord(cx, 1-cy-0.0625)
            glVertex(0, 0)
            glTexCoord(cx+0.0625, 1-cy-0.0625)
            glVertex(16, 0)
            glTexCoord(cx+0.0625, 1-cy)
            glVertex(16, 16)
            glTexCoord(cx, 1-cy)
            glVertex(0, 16)
        glEnd()
        glTranslate(10, 0, 0)
    glEndList()
end

# main drawing loop

while true
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()

    glBindTexture(GL_TEXTURE_2D,tex[2])

    glTranslate(0.0,0.0,-5.0)
    glRotate(45.0, 0.0, 0.0, 1.0)
    glRotate(cnt1*30.0, 1.0, 1.0, 0.0)

    glDisable(GL_BLEND)
    glColor(1.0, 1.0, 1.0)

    glBegin(GL_QUADS)
        glTexCoord(0.0, 0.0)
        glVertex(-1.0, 1.0)
        glTexCoord(1.0, 0.0)
        glVertex(1.0, 1.0)
        glTexCoord(1.0, 1.0)
        glVertex(1.0, -1.0)
        glTexCoord(0.0, 1.0)
        glVertex(-1.0, -1.0)
    glEnd()

    glRotate(90.0, 1.0, 1.0, 0.0)
    glBegin(GL_QUADS)
        glTexCoord(0.0, 0.0)
        glVertex(-1.0, 1.0)
        glTexCoord(1.0, 0.0)
        glVertex(1.0, 1.0)
        glTexCoord(1.0, 1.0)
        glVertex(1.0, -1.0)
        glTexCoord(0.0, 1.0)
        glVertex(-1.0, -1.0)
    glEnd()

    glEnable(GL_BLEND)
    glLoadIdentity()

    glColor(1.0cos(cnt1), 1.0sin(cnt2), 1.0-0.5cos(cnt1+cnt2))
    glPrint(int(280+250cos(cnt1)), int(235+200sin(cnt2)), "NeHe", 0)
    glColor(1.0sin(cnt2), 1.0-0.5cos(cnt1+cnt2), 1.0cos(cnt1))
    glPrint(int(280+230cos(cnt2)), int(235+200sin(cnt1)), "OpenGL", 1)

    glColor(0.0, 0.0, 1.0)
    glPrint(int(240+200cos((cnt2+cnt1)/5)), 2, "JuliaLang", 0)
    glColor(1.0, 1.0, 1.0)
    glPrint(int(242+200cos((cnt2+cnt1)/5)), 2, "JuliaLang", 0)

    cnt1 +=0.01
    cnt2 +=0.0081

    SDL_GL_SwapBuffers()

    SDL_PumpEvents()
    if SDL_GetTicks() - lastkeycheckTime >= key_repeatinterval
        keystate         = SDL_GetKeyState()
        keystate_checked = true
        lastkeycheckTime = SDL_GetTicks()
    end

    # Sampling rates for event processing are so fast that a single key press
    # lasts through several iterations of this loop.  This means that one press
    # can be seen as 50 or more presses by the SDL event system.  To correct
    # for this, we only check keypresses every 75ms.

    if keystate_checked == true
        if keystate[SDLK_q] == true
            glDeleteLists(base,256)
            glDeleteTextures(2,tex)
            break
        end
        keystate_checked = false
    end
end


using TimeSeries, MarketData
FactCheck.setstyle(:compact)
FactCheck.onlystats(true)

facts("readwrite parses csv file correctly") do

    context("1d values array works") do
        @fact typeof(cl.values) --> Array{Float64,1}
    end

    context("2d values array works") do
        @fact typeof(ohlc.values) --> Array{Float64,2}
    end

    context("Specifying DateTime string format for reading") do
        tm = readtimearray(joinpath(dirname(@__FILE__), "data/datetime3.csv"), format="yyyy/mm/dd|HH:MM:SS")
        @fact length(tm) --> 5
        @fact size(tm.values) --> (5,1)
        @fact tm.timestamp[4] --> DateTime(2010,1,4,9,4)
        @fact_throws readtimearray(joinpath(dirname(@__FILE__), "data/datetime3.csv"))
    end

    context("timestamp parses to correct type") do
        @fact typeof(cl.timestamp)        --> Vector{Date}
        @fact typeof(datetime1.timestamp) --> Vector{DateTime}
    end

    context("readtimearray accepts meta field") do
        tm = readtimearray(joinpath(dirname(@__FILE__), "data/datetime3.csv"), format="yyyy/mm/dd|HH:MM:SS", meta="foo")
        @fact tm.meta --> "foo"
    end

    context("readtimearray works with arbitrary delimiters") do
        tm = readtimearray(joinpath(dirname(@__FILE__), "data/read_example_delim.csv"), format="dd/mm/yyyy HH:MM", delim=';')
        @fact length(tm) --> 2
        @fact size(tm.values) --> (2,2)
        @fact tm.timestamp[2] --> DateTime(2015,1,1,1,0)
        @fact tm.values[1,1] --> 10.42
    end

    context("writetimearray output can be round-tripped") do
        filename = "$(randstring()).csv"
        uohlc = uniformspace(ohlc)
        writetimearray(uohlc, filename)
        readback = readtimearray(filename)
        @fact uohlc.colnames                         --> readback.colnames
        @fact uohlc.timestamp                        --> readback.timestamp
        @fact isequal(uohlc.values, readback.values) --> true
        rm(filename)
    end

end


using JetPackWaveFD, Random, Test

function test_ginsu_sub(g::Ginsu{2}, x, xsub, interior, extend)
    N1 = size(x,1)
    N2 = size(x,2)

    I1 = lextents(g, interior=interior)[1]
    I2 = lextents(g, interior=interior)[2]

    for i2 = 1:N2, i1 = 1:N1
        if in(i1, I1) && in(i2, I2)
            @test x[i1,i2] ≈ xsub[i1-I1[1]+1,i2-I2[1]+1]
        end
    end
end

function test_ginsu_sub(g::Ginsu{3}, x, xsub, interior, extend)
    N1 = size(x,1)
    N2 = size(x,2)
    N3 = size(x,3)

    I1 = lextents(g, interior=interior)[1]
    I2 = lextents(g, interior=interior)[2]
    I3 = lextents(g, interior=interior)[3]

    for i3 = 1:N3, i2 = 1:N2, i1 = 1:N1
        if in(i1, I1) && in(i2, I2) && in(i3, I3)
            @test x[i1,i2,i3] ≈ xsub[i1-I1[1]+1,i2-I2[1]+1,i3-I3[1]+1]
        end
    end
end

function test_ginsu_super(g::Ginsu{2}, x, xsuper, interior)
    N1 = size(x,1)
    N2 = size(x,2)

    I1 = lextents(g, interior=interior)[1]
    I2 = lextents(g, interior=interior)[2]

    for i2 = 1:N2, i1 = 1:N1
        if in(i1,I1) && in(i2,I2)
            @test x[i1,i2] ≈ xsuper[i1,i2]
        else
            @test xsuper[i1,i2] ≈ 0
        end
    end
end

function test_ginsu_super(g::Ginsu{3}, x, xsuper, interior)
    N1 = size(x,1)
    N2 = size(x,2)
    N3 = size(x,3)

    I1 = lextents(g, interior=interior)[1]
    I2 = lextents(g, interior=interior)[2]
    I3 = lextents(g, interior=interior)[3]

    for i3 = 1:N3, i2 = 1:N2, i1 = 1:N1
        if in(i1,I1) && in(i2,I2) && in(i3,I3)
            @test x[i1,i2,i3] ≈ xsuper[i1,i2,i3]
        else
            @test xsuper[i1,i2,i3] ≈ 0
        end
    end
end

@testset "Ginsu 2D" begin
    nz, nx = 100, 200
    dz, dx = 10.0, 20.0
    z0, x0 = 0.0, 0.0

    sz, sx = [10.0], [2000.0]
    rz, rx = [0.0, 0.0], [0.0, 1990.0]
    padz, padx = 100.0, 200.0
    ndamp = 10

    ginsu = Ginsu((z0,x0), (dz,dx), (nz,nx), (sz,sx), (rz,rx), ((padz,padz),(padx,padx)), ((0,0),(ndamp,ndamp)), dims=(:z,:x), T=Float64)
    @test lextents(ginsu, interior=false) == (-9:110,-19:124)
    @test lextents(ginsu, interior=true) == (-9:110,-9:114)

    map(i->@test(pextents(ginsu, interior=false)[i] ≈ (-100.0:10.0:1090.0,-400.0:20.0:2460.0)[i]), 1:2)
    map(i->@test(pextents(ginsu, interior=true)[i] ≈ (-100.0:10.0:1090.0,-200.0:20.0:2260.0)[i]), 1:2)

    for interior in (false, true), extend in (false, true)
        x = rand(nz,nx)
        z = zeros(nz,nx)

        y = sub(ginsu, x, interior=interior, extend=extend)
        test_ginsu_sub(ginsu, x, y, interior, extend)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=true)
        test_ginsu_super(ginsu, 2x, z, interior)
    end
end

@testset "Ginsu 2D Array Src&Rec" begin
    nz, nx = 100, 200
    dz, dx = 10.0, 20.0
    z0, x0 = 0.0, 0.0

    sz, sx = [[10.0]], [[2000.0]]
    rz, rx = [[0.0, 0.0]], [[0.0, 1990.0]]
    padz, padx = 100.0, 200.0
    ndamp = 10

    ginsu = Ginsu((z0,x0), (dz,dx), (nz,nx), (sz,sx), (rz,rx), ((padz,padz),(padx,padx)), ((0,0),(ndamp,ndamp)), dims=(:z,:x), T=Float64)
    @test lextents(ginsu, interior=false) == (-9:110,-19:124)
    @test lextents(ginsu, interior=true) == (-9:110,-9:114)

    map(i->@test(pextents(ginsu, interior=false)[i] ≈ (-100.0:10.0:1090.0,-400.0:20.0:2460.0)[i]), 1:2)
    map(i->@test(pextents(ginsu, interior=true)[i] ≈ (-100.0:10.0:1090.0,-200.0:20.0:2260.0)[i]), 1:2)

    for interior in (false, true), extend in (false, true)
        x = rand(nz,nx)
        z = zeros(nz,nx)

        y = sub(ginsu, x, interior=interior, extend=extend)
        test_ginsu_sub(ginsu, x, y, interior, extend)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=true)
        test_ginsu_super(ginsu, 2x, z, interior)
    end
end

@testset "Ginsu 3D" begin
    nz, ny, nx = 100, 30, 200
    dz, dy, dx = 10.0, 10.0, 20.0
    z0, y0, x0 = 0.0, 0.0, 0.0

    sz, sy, sx = [10.0], [150.0], [2000.0]
    rz, ry, rx = [0.0, 0.0], [0.0,270.0], [0.0, 1990.0]
    padz, pady, padx = 100.0, 50.0, 200.0
    ndamp = 10

    ginsu = Ginsu((z0,y0,x0), (dz,dy,dx), (nz,ny,nx), (sz,sy,sx), (rz,ry,rx), ((padz,padz),(pady,pady),(padx,padx)), ((0,0),(ndamp,ndamp),(ndamp,ndamp)), dims=(:z,:y,:x), T=Float64)
    @test lextents(ginsu, interior=false) == (-9:110,-14:49,-19:124)
    @test lextents(ginsu, interior=true) == (-9:110,-4:39,-9:114)

    map(i->@test(pextents(ginsu, interior=false)[i] ≈ (-100.0:10.0:1090.0,-150.0:10.0:480.0,-400.0:20.0:2460.0)[i]), 1:2)
    map(i->@test(pextents(ginsu, interior=true)[i] ≈ (-100.0:10.0:1090.0,-50.0:10.0:380.0,-200.0:20.0:2260.0)[i]), 1:2)

    for interior in (false, true), extend in (false, true)
        x = rand(nz,ny,nx)
        z = zeros(nz,ny,nx)

        y = sub(ginsu, x, interior=interior, extend=extend)
        test_ginsu_sub(ginsu, x, y, interior, extend)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=true)
        test_ginsu_super(ginsu, 2x, z, interior)
    end
end

@testset "Ginsu 3D Array Src&Rec" begin
    nz, ny, nx = 100, 30, 200
    dz, dy, dx = 10.0, 10.0, 20.0
    z0, y0, x0 = 0.0, 0.0, 0.0

    sz, sy, sx = [[10.0]], [[150.0]], [[2000.0]]
    rz, ry, rx = [[0.0, 0.0]], [[0.0,270.0]], [[0.0, 1990.0]]
    padz, pady, padx = 100.0, 50.0, 200.0
    ndamp = 10

    ginsu = Ginsu((z0,y0,x0), (dz,dy,dx), (nz,ny,nx), (sz,sy,sx), (rz,ry,rx), ((padz,padz),(pady,pady),(padx,padx)), ((0,0),(ndamp,ndamp),(ndamp,ndamp)), dims=(:z,:y,:x), T=Float64)
    @test lextents(ginsu, interior=false) == (-9:110,-14:49,-19:124)
    @test lextents(ginsu, interior=true) == (-9:110,-4:39,-9:114)

    map(i->@test(pextents(ginsu, interior=false)[i] ≈ (-100.0:10.0:1090.0,-150.0:10.0:480.0,-400.0:20.0:2460.0)[i]), 1:2)
    map(i->@test(pextents(ginsu, interior=true)[i] ≈ (-100.0:10.0:1090.0,-50.0:10.0:380.0,-200.0:20.0:2260.0)[i]), 1:2)

    for interior in (false, true), extend in (false, true)
        x = rand(nz,ny,nx)
        z = zeros(nz,ny,nx)

        y = sub(ginsu, x, interior=interior, extend=extend)
        test_ginsu_sub(ginsu, x, y, interior, extend)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=false)
        test_ginsu_super(ginsu, x, z, interior)

        super!(z, ginsu, y, interior=interior, accumulate=true)
        test_ginsu_super(ginsu, 2x, z, interior)
    end
end

@testset "Ginsu Vector Width" begin
    nz, nx = 100, 200
    dz, dx = 10.0, 20.0
    z0, x0 = 0.0, 0.0

    sz, sx = [10.0], [2000.0]
    rz, rx = [0.0, 0.0], [0.0, 1990.0]
    padz, padx = 100.0, 200.0
    ndamp = 10
    for vector_width = (1,2,4,8,16,32)
        ginsu = Ginsu((z0,x0), (dz,dx), (nz,nx), (sz,sx), (rz,rx), ((padz,padz),(padx,padx)), ((0,0),(ndamp,ndamp)), dims=(:z,:x), T=Float64, vector_width=vector_width)
        @test size(ginsu) .% vector_width == (0,0)
    end
end

@testset "Ginsu dot product" begin
    rz = 750.0*ones(size(75:125))
    rx = 10.0*collect(75:125)
    ginsu = Ginsu((0.0,0.0), (10.0,10.0), (201,201), ([1000.0],[1000.0]), (rz,rx), ((500.0,500.0),(500.0,500.0)), ((50,50),(50,50)), T=Float64)
    m = rand(201,201)
    d = rand(size(ginsu)...)
    ds = sub(ginsu, m, extend=false)
    ms = zeros(201,201)
    super!(ms, ginsu, d)
    lhs = sum(d .* ds)
    rhs = sum(m .* ms)
    err = (lhs - rhs) / (lhs + rhs)
    @test isapprox(err, 0.0, atol=100*eps(Float32))

    rz = 10.0*ones(size(1:201))
    rx = 10.0*collect(1:201)
    ginsu = Ginsu((0.0,0.0), (10.0,10.0), (201,201), ([10.0],[1000.0]), (rz,rx), ((500.0,500.0),(500.0,500.0)), ((50,50),(50,50)), T=Float64)
    m = rand(201,201)
    d = rand(size(ginsu)...)
    ds = sub(ginsu, m, extend=false)
    ms = zeros(201,201)
    super!(ms, ginsu, d)
    lhs = sum(d .* ds)
    rhs = sum(m .* ms)
    @test lhs ≈ rhs rtol=1e-5
end

@testset "Ginsu, absolute constructor" begin
    ginsu = Ginsu((0.0,0.0), (10.0,10.0), (201,201), (-100.0:200.0,-50.0:150.0), ((50,50),(50,50)))
    @test lextents(ginsu, interior=false) == (-59:76,-54:73)
    @test lextents(ginsu, interior=true) == (-9:26,-4:23)

    map(i->@test(pextents(ginsu, interior=false)[i] ≈ (-600.0:10.0:750.0,-550.0:10.0:720.0)[i]), 1:2)
    map(i->@test(pextents(ginsu, interior=true)[i] ≈ (-100.0:10.0:250.0,-50.0:10.0:220.0)[i]), 1:2)
end


#####
##### Utility Functions
#####

# Construct a `shape` vector from `x`.
# Since Julia is column major while C++ is row major, we need to be careful about the order
# we pass things.
#
# In general, the C++ code will not do any reversing schenanigans.
# Thus, it's up to the Julia interfacing code to make sure the appropriate conversions
# between column major and row major are made.
#
# nGraph Ordered Types shadowed
_reversed(x) = [Int64(i) for i in reverse(x)]
shape(x) = _reversed(x)
strides(x) = _reversed(x)

# It is convenient to construct Shape, Strides etc. from Tuples, Numbers, Vectors etc.
# Here, is the machinery that does the dispatch.
_expand(N, x) = fill(Int(x), N)

# Numbers
shape(N, x::Number) = _expand(N, x)
strides(N, x::Number) = _expand(N, x)

# Tuples and Vectors
maybecollect(x::Vector) = x
maybecollect(x) = collect(x)

function ordered(N, x)
    # Determine how many repetitions we need
    repitions, remainder = divrem(length(x), N)
    if !iszero(remainder)
        error("The length of `x` must be divisible by $N")
    end

    return _reversed(repeat(maybecollect(x), repitions))
end
shape(N, x::Union{Tuple,Vector}) = ordered(N, x)
strides(N, x::Union{Tuple,Vector}) = ordered(N, x)

#####
##### Element Type Maps
#####

# This should be kept inline with `ngraph/type/element_type.hpp`
@enum NGElements::Int32 begin
    ng_undefined=0
    ng_dynamic
    ng_boolean
    ng_bf16
    ng_f16
    ng_f32
    ng_f64
    ng_i8
    ng_i16
    ng_i32
    ng_i64
    ng_u1
    ng_u8
    ng_u16
    ng_u32
    ng_u64
end

# This is kind of a gross way of mapping Julia types to ngraph types.
# TODO: Think of a better way of doing this.
const TYPEMAPS = Dict(
   ng_boolean => Bool,
   ng_f32     => Float32,
   ng_f64     => Float64,
   ng_i8      => Int8,
   ng_i16     => Int16,
   ng_i32     => Int32,
   ng_i64     => Int64,
   ng_u8      => UInt8,
   ng_u16     => UInt16,
   ng_u32     => UInt32,
   ng_u64     => UInt64,
)

const Element = Lib.CxxWrap.CxxWrapCore.ConstCxxPtr{Lib.Element}

# Mapping from Julia => nGraph
Element(::Type{T}) where {T} = error("No translation defined for $T")
for (S,T) in TYPEMAPS
    @eval Element(::Type{$T}) = Lib.ngraph_type($(Int32(S)))
end

back(x) = TYPEMAPS[NGElements(@ngraphcall Type_t(x[]))]
ngraph_convert(::Type{T}) where {T <: Number} = Element(T)[]

#####
##### Nodes
#####

const NodeCppType = Lib.CxxWrap.StdLib.SharedPtrAllocated{nGraph.Lib.Node}
Base.ndims(x::NodeCppType) = length(@ngraphcall get_output_shape(x, 0))
Base.eltype(x::NodeCppType) = back(@ngraphcall get_output_element_type(x, 0))

# Subtype AbstractArray to get all of the nice fallbacks.
struct Node{T,N} <: AbstractArray{T,N}
    obj::NodeCppType
end
ngraph_convert(x::Node) = x.obj

Base.show(io::IO, x::Node{T,N}) where {T,N} = println(io, "Node{$T,$N} - $(name(x))")
Base.display(x::Node) = show(stdout, x)

Node{T}(x::NodeCppType) where {T} = Node{T,ndims(x)}(x)
Node(x::NodeCppType) = Node{eltype(x),ndims(x)}(x)

Node(x::AbstractArray{T,N}) where {T,N} = Node{T,N}(x)
Node{T,N}(x::AbstractArray{T,N}) where {T,N} = parameter(T, size(x))

Node(x::T) where {T <: Number} = Node{T}(x)
Node{T}(x::T) where {T <: Number} = Node{T,0}(fill(x))

# Array style arguments
Base.ndims(x::Node) = ndims(x.obj)

function Base.size(x::Node)
    nd = ndims(x)
    dims = @ngraphcall get_output_shape(x, 0)
    # Reversing is done on the ngraph side.
    return Tuple(Int.(reverse(dims)))
end

Base.size(x::Node, i::Integer) = size(x)[i]
Base.length(x) = prod(size(x))
Base.eltype(x::Node{T}) where {T} = T
Base.IndexStyle(::Node) = Base.IndexLinear()

name(x::Node) = String(@ngraphcall get_name(x))
description(x::Node) = String(@ngraphcall description(x))

# So these can be used as keys in a Dict
Base.:(==)(x::T, y::T) where {T <: Node} = name(x) == name(y)
Base.hash(x::Node, h::UInt = UInt(0x209348)) = hash(name(x), h)

function Base.getindex(::Node, i::Int)
    error("Yeah, yeah, I know \"Node\" is an AbstractArray ... but please don't index into it.")
end

#####
##### NGFunction
#####

mutable struct NGFunction
    ptr::Lib.CxxWrap.StdLib.SharedPtrAllocated{nGraph.Lib.NGFunction}
end
ngraph_convert(x::NGFunction) = x.ptr

function NGFunction(parameters::Vector, results::Vector)
    ptr = make_function(parameters, results)
    return NGFunction(ptr)
end

function make_function(inputs::Vector, outputs::Vector)
    return @ngraphcall make_function(outputs, inputs)
end

name(f::NGFunction) = String(@ngraphcall get_name(f))
poolsize(f::NGFunction) = Int(@ngraphcall get_temporary_pool_size(f))

#####
##### Backend
#####

struct Backend{T}
    ptr::Lib.CxxWrap.StdLib.SharedPtrAllocated{nGraph.Lib.Backend}
end
ngraph_convert(x::Backend) = x.ptr

#Backend(str::String = "CPU") = Backend(Lib.create(str))
Backend{T}() where {T} = Backend{T}(@ngraphcall create(string(T)))
version(x::Backend) = String(Lib.get_version(ngraph_convert(x)))

#####
##### Tensor
#####

struct Tensor
    ptr::Lib.CxxWrap.StdLib.SharedPtrAllocated{nGraph.Lib.RuntimeTensor}
    backend::Backend

    # Keep track of the base array to avoid GC
    # TODO: Rethink the whole - "casting to pointer" thing.
    parent::AbstractArray

    function Tensor(backend::Backend, v::Array{T}) where {T}
        # This is kind of scary - we ... just have to make sure that the parent array
        # doesn't get moved (i.e. resized ... )
        vptr = Base.unsafe_convert(Ptr{Cvoid}, pointer(v))
        ptr = Lib.create_tensor(
            backend.ptr,
            Element(T)[],
            shape(size(v)),
            vptr
        )

        return new(ptr, backend, v)
    end
end
ngraph_convert(x::Tensor) = x.ptr

Base.eltype(x::Tensor) = back(Lib.get_element_type(ngraph_convert(x)))
Base.sizeof(x::Tensor) = convert(Int, Lib.get_size_in_bytes(ngraph_convert(x)))

Tensor(backend, x::Tensor) = x

# Capture scalars in a 0-dimensional array
function Tensor(backend, x::T) where {T}
    A = Array{T,0}(undef)
    A[] = x
    return Tensor(backend, A)
end

# If we're trying to create a tensor view from a node
# create an undefined array and return a view of that.
function Tensor(backend::Backend, v::Node{T,N}) where {T,N}
    A = Array{T}(undef, size(v))
    return Tensor(backend, A)
end

function Base.size(t::Tensor)
    shape = Lib.get_shape(ngraph_convert(t))
    return Tuple(reverse(shape))
end

Base.parent(t::Tensor) = t.parent

function Base.show(io::IO, TV::Tensor)
    printstyled(io, "Tensor View\n"; color = :yellow)
    println(io, parent(TV))
end

#####
##### Executable
#####

mutable struct Executable
    ptr::Lib.CxxWrap.StdLib.SharedPtrAllocated{nGraph.Lib.Executable}
    ngraph_function::NGFunction
    backend::Backend

    function Executable(ptr, ngraph_function::NGFunction, backend::Backend)
        ex = new(ptr, ngraph_function, backend)

        # On cleanup - remove the compiled function.
        finalizer(ex) do x
            Lib.remove_compiled_function(x.backend.ptr, x.ptr)
        end
        return ex
    end
end
ngraph_convert(ex::Executable) = ex.ptr

function (ex::Executable)(
        inputs::Vector{Tensor},
        outputs::Vector{Tensor}
    )
    return @ngraphcall call(ex, outputs, inputs)
end

function compile(backend::Backend, inputs::Vector, outputs::Vector; kw...)
    # Gather up the raw shared pointers
    fn = NGFunction(inputs, outputs)
    compile(backend::Backend, fn; kw...)
end

function compile(
        backend::Backend,
        ngraph_function::NGFunction;
        emit_timing::Bool = false,
    )

    @time pointer = Lib.compile(backend.ptr, ngraph_function.ptr, emit_timing)

    # Indicate that the compiler has been invoked.
    return Executable(pointer, ngraph_function, backend)
end

# Conversions for vectors of Nodes and Tensors
ngraph_convert(x::Vector{<:Node}) = Lib.CxxWrap.CxxRef.(ngraph_convert.(x))
ngraph_convert(x::Vector{<:Tensor}) = Lib.CxxWrap.CxxRef.(ngraph_convert.(x))

#####
##### Extract performance data
#####

# TODO: Fix This
# function get_performance(ex::Executable)
#     # Construct a PerfCounterTranslator
#     translator = Lib.PerfCounterTranslator(getpointer(ex))
#
#     # Create a dictionary of timing results. Iterate through the CounterTranslator to
#     # construct the dict.
#     times = Dict{String,Int}()
#     for i in 1:Lib._length(translator)
#         name, time = Lib._getindex(translator, i-1)
#         times[name] = convert(Int, time)
#     end
#     return times
# end


# download the NMRData repository at https://github.com/AI4DBiological-Systems/NMRData

import NMRDataSetup

using FFTW
import PyPlot
import BSON

PyPlot.close("all")
fig_num = 1

PyPlot.matplotlib["rcParams"][:update](["font.size" => 22, "font.family" => "serif"])

# creates the save path if it doesn't exist.
function loadbatchexperimentsinsubfolders(root_path::String, save_dir::String, solvent_ppm_guess::T, solvent_window_ppm::T) where T <: Real

    tmp = readdir(root_path, join = true)
    inds = findall(xx->isdir(xx), tmp)

    experiment_full_paths = tmp[inds]
    experiment_names = readdir(root_path)[inds]
    project_names = collect( "$(experiment_names[i])" for i = 1:length(experiment_names))

    isdir(save_dir) || mkdir(save_dir); # make save folder if it doesn't exist.

    for i = 1:length(experiment_names)
        project_name = project_names[i]
        experiment_full_path = experiment_full_paths[i]

        ## load.
        s_t, S, hz2ppmfunc, ppm2hzfunc, ν_0ppm, fs, SW, α_0ppm, β_0ppm, λ_0ppm, Ω_0ppm,
            results_0ppm, dic, α_solvent, β_solvent, λ_solvent, Ω_solvent,
            results_solvent = NMRDataSetup.loadspectrum(experiment_full_path;
            solvent_ppm = solvent_ppm_guess,
            solvent_window_ppm = solvent_window_ppm)

        ## store.
        save_folder_path = joinpath(save_dir, project_name)
        isdir(save_folder_path) || mkpath(save_folder_path); # make save folder if it doesn't exist.

        save_path = joinpath(save_folder_path, "experiment.bson")
        BSON.bson(save_path,
        s_t = s_t,
        fs = fs,
        SW = SW,
        ν_0ppm = ν_0ppm,
        α_0ppm = α_0ppm,
        β_0ppm = β_0ppm,
        λ_0ppm = λ_0ppm,
        dic = dic)

    end
end

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/BMRB/similar_settings/BMRB-500-0.5mM/" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/BMRB-500-0.5mM" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/BMRB/similar_settings/BMRB-500-2mM/" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/BMRB-500-2mM" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/BMRB/similar_settings/BMRB-700-20mM/" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/BMRB-700-20mM" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/NRC/bioreactor/HEK293/2011-06-06" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/NRC/bioreactor-HEK293-2011" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/NRC/bioreactor/Feng-2015-11-30" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/NRC/bioreactor-2015" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)

### user inputs.
root_path = "/home/roy/Documents/repo/NMRData/experiments_1D1H/NRC/NRC_8_amino_acid_mixutre_Apr_2021" # the folder that contain the experiment folders that you want to load.
save_dir = "/home/roy/MEGAsync/outputs/NMR/experiments/NRC/NRC-8_amino_acid-Apr2021" # root path where the experiment BSON files will be stored.

solvent_ppm_guess = 4.7 # in units ppm.
solvent_window_ppm = 0.1 # in units ppm.
### end inputs.
loadbatchexperimentsinsubfolders(root_path, save_dir, solvent_ppm_guess, solvent_window_ppm)


mutable struct ExtendedJumpArray{T,T2} <: AbstractArray{Float64,1}
  u::T
  jump_u::T2
end

Base.length(A::ExtendedJumpArray) = length(A.u) + length(A.jump_u)
Base.size(A::ExtendedJumpArray) = (length(A),)
@inline function Base.getindex(A::ExtendedJumpArray,i::Int)
  i <= length(A.u) ? A.u[i] : A.jump_u[i-length(A.u)]
end
@inline function Base.getindex(A::ExtendedJumpArray,I...)
  A[CartesianIndices(A.u,I...)]
end
@inline function Base.getindex(A::ExtendedJumpArray,I::CartesianIndex{1})
  A[I[1]]
end
@inline Base.setindex!(A::ExtendedJumpArray,v,I...) = (A[CartesianIndices(A.u,I...)] = v)
@inline Base.setindex!(A::ExtendedJumpArray,v,I::CartesianIndex{1}) = (A[I[1]] = v)
@inline function Base.setindex!(A::ExtendedJumpArray,v,i::Int)
  i <= length(A.u) ? (A.u[i] = v) : (A.jump_u[i-length(A.u)] = v)
end

Base.IndexStyle(::Type{<:ExtendedJumpArray}) = IndexLinear()
Base.similar(A::ExtendedJumpArray) = ExtendedJumpArray(similar(A.u),similar(A.jump_u))
Base.similar(A::ExtendedJumpArray,::Type{S}) where {S} = ExtendedJumpArray(similar(A.u,S),similar(A.jump_u,S))
Base.zero(A::ExtendedJumpArray) = fill!(similar(A),0)

# Required for non-diagonal noise
LinearAlgebra.mul!(c::ExtendedJumpArray,A::AbstractVecOrMat,u::AbstractVector) = mul!(c.u,A,u)

# Ignore axes
Base.similar(A::ExtendedJumpArray,::Type{S},axes::Tuple{Base.OneTo{Int}}) where {S} = ExtendedJumpArray(similar(A.u,S),similar(A.jump_u,S))

function recursivecopy!(dest::T, src::T) where T<:ExtendedJumpArray
  recursivecopy!(dest.u,src.u)
  recursivecopy!(dest.jump_u,src.jump_u)
end
Base.show(io::IO,A::ExtendedJumpArray) = show(io,A.u)
TreeViews.hastreeview(x::ExtendedJumpArray) = true
plot_indices(A::ExtendedJumpArray) = eachindex(A.u)

###### Broadcast overloading

const ExtendedJumpArrayStyle = Broadcast.ArrayStyle{ExtendedJumpArray}
Base.BroadcastStyle(::Type{<:ExtendedJumpArray}) = Broadcast.ArrayStyle{ExtendedJumpArray}()
Base.BroadcastStyle(::Broadcast.ArrayStyle{ExtendedJumpArray},::Broadcast.ArrayStyle) = Broadcast.ArrayStyle{ExtendedJumpArray}()
Base.BroadcastStyle(::Broadcast.ArrayStyle,::Broadcast.ArrayStyle{ExtendedJumpArray}) = Broadcast.ArrayStyle{ExtendedJumpArray}()
Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ExtendedJumpArray}},::Type{ElType}) where ElType = similar(bc)

add_idxs1(A,x,expr) = expr
add_idxs1(A,::Type{T},expr) where {T<:ExtendedJumpArray} = :($(expr).u)
add_idxs1(A,::Type{T},expr) where {T<:AbstractArray} = :(@view($(expr)[1:length(A.u)]))

add_idxs2(A,x,expr) = expr
add_idxs2(A,::Type{T},expr) where {T<:ExtendedJumpArray} = :($(expr).jump_u)
add_idxs2(A,::Type{T},expr) where {T<:AbstractArray} = :(@view($(expr)[1:length(A.jump_u)]))

function Base.copy(bc::Broadcast.Broadcasted{ExtendedJumpArrayStyle})
    ret = Broadcast.flatten(bc)
    __broadcast(ret.f,ret.args...)
end

function Base.copyto!(dest::AbstractArray, bc::Broadcast.Broadcasted{ExtendedJumpArrayStyle})
    ret = Broadcast.flatten(bc)
    __broadcast!(ret.f,dest,ret.args...)
end

@generated function __broadcast(f,A::ExtendedJumpArray,B...)
  exs1 = ((add_idxs1(A,B[i],:(B[$i])) for i in eachindex(B))...,)
  exs2 = ((add_idxs2(A,B[i],:(B[$i])) for i in eachindex(B))...,)
  res = quote
      ExtendedJumpArray(broadcast(f,A.u,$(exs1...)),broadcast(f,A.jump_u,$(exs2...)))
  end
  res
end

@generated function __broadcast!(f,A::ExtendedJumpArray,B...)
  exs1 = ((add_idxs1(A,B[i],:(B[$i])) for i in eachindex(B))...,)
  exs2 = ((add_idxs2(A,B[i],:(B[$i])) for i in eachindex(B))...,)
  res = quote
      broadcast!(f,A.u,$(exs1...));broadcast!(f,A.jump_u,$(exs2...))
      A
  end
  res
end


# __precompile__()

module LightDarkPOMDPs

importall POMDPs

using StaticArrays
using Plots
using POMDPToolbox
using Parameters # for @with_kw
using ParticleFilters # for AbstractParticleBelief

export
    AbstractLD2,
    LightDark2D,
    LightDark2DTarget,
    LightDark2DKalman,
    SymmetricNormal2,
    Vec2

include("lightdark2d.jl")
include("lightdark2dtarget.jl")
include("lightdark2dfilter.jl")
include("lightdark2dvis.jl")

end # module


include("../src/TRS.jl")
using Main.TRS
using Test

@testset "All Unit Tests" begin
  include("./trs.jl")
  include("./trs_constraints.jl")
  include("./trs_ellipsoid.jl")
  include("./special_cases.jl")
end
nothing


using ..ChemistryFeaturization.AbstractType: AbstractCodec
import ..ChemistryFeaturization.encode
import ..ChemistryFeaturization.decode

"""
    DirectCodec

Codec type whose encoding function is some constant * the identity function.
"""
struct DirectCodec <: AbstractCodec
    scale::Number
end

encode(dc::DirectCodec, val) = dc.scale .* val
decode(dc::DirectCodec, encoded) = encoded ./ dc.scale


## Result - data structure interaction

# These functions allow to pass investment decisions between two problems: the investment decision
# results of the first problem are copied into the data structure of the second problem; an
# appropriate constraint could be necessary in the model of the second problem to read the data
# prepared by these functions.

function add_ne_branch_indicator!(mn_data::Dict{String,Any}, result::Dict{String,Any})
    for (n, data_nw) in mn_data["nw"]
        sol_nw = result["solution"]["nw"][n]
        for (b, data_branch) in data_nw["ne_branch"]
            if data_branch["br_status"] == 1
                data_branch["built"] = sol_nw["ne_branch"][b]["built"]
            end
        end
    end
end

function add_ne_storage_indicator!(mn_data::Dict{String,Any}, result::Dict{String,Any})
    for (n, data_nw) in mn_data["nw"]
        sol_nw = result["solution"]["nw"][n]
        for (s, data_storage) in data_nw["ne_storage"]
            data_storage["isbuilt"] = sol_nw["ne_storage"][s]["isbuilt"]
        end
    end
end

function add_td_coupling_power_active!(mn_data::Dict{String,Any}, result::Dict{String,Any})
    for (n, data_nw) in mn_data["nw"]
        p = result["solution"]["nw"][n]["td_coupling"]["p"]
        d_gen_id = _FP.dim_prop(mn_data, parse(Int,n), :sub_nw, "d_gen")
        d_gen = data_nw["gen"]["$d_gen_id"] = deepcopy(data_nw["gen"]["$d_gen_id"]) # Gen data is shared among nws originally.
        d_gen["pmax"] = p
        d_gen["pmin"] = p
    end
end

function add_max_cost!(mn_data::Dict{String,Any}, max_cost::Float64)
    mn_data["max_cost"] = max_cost
end


using Test
using LocalScopeBenchmarks
using BenchmarkTools
using Statistics

function judge_loosely(t1, t2)
    judge(ratio(median(t1), median(t2)), time_tolerance=0.2)
end

global_x = 1.0

@testset "LocalScopeBenchmarks" begin
    @testset "Basic benchmarks" begin
        x = 1.0
        t1 = @benchmark(sin($x))
        t2 = @localbenchmark(sin(x))
        j = judge_loosely(t1, t2)
        @show j
        @test isinvariant(j)

        t1 = @benchmark($sin($x))
        t2 = @localbenchmark(sin(x))
        j = judge_loosely(t1, t2)
        @show j
        @test isinvariant(j)

        f = sin
        x = 1.0
        t1 = @benchmark($f($x))
        t2 = @localbenchmark(f(x))
        j = judge_loosely(t1, t2)
        @show j
        @test isinvariant(j)
    end

    @testset "Benchmarks with setup" begin
        @testset "Single setup" begin
            t1 = @benchmark sin(x) setup=(x = 2.0)
            t2 = @localbenchmark sin(x) setup=(x = 2.0)
            j = judge_loosely(t1, t2)
            @show j
            @test isinvariant(j)
        end

        @testset "Multiple setups" begin
            t1 = @benchmark atan(x, y) setup=(x = 2.0; y = 1.5)
            t2 = @localbenchmark atan(x, y) setup=(x = 2.0; y = 1.5)
            j = judge_loosely(t1, t2)
            @show j
            @test isinvariant(j)
        end

        @testset "Setups override local vars" begin
            x = 1.0
            @localbenchmark (@assert x == 2.0) setup=(x = 2.0) evals=1
        end

        @testset "Mixed setup and local vars" begin
            x = 1.0
            t1 = @benchmark atan($x, y) setup=(y = 2.0)
            t2 = @localbenchmark atan(x, y) setup=(y = 2.0)
            j = judge_loosely(t1, t2)
            @show j
            @test isinvariant(j)
        end
    end

    @testset "Additional kwargs" begin
        @testset "evals kwarg" begin
            x = 1.0
            t1 = @benchmark sin($x) evals=5
            t2 = @localbenchmark sin(x) evals=5
            j = judge_loosely(t1, t2)
            @show j
            @test isinvariant(j)
        end

        @testset "evals and setup kwargs" begin
            t1 = @benchmark sin(x) setup=(x = 2.0) evals=5
            t2 = @localbenchmark sin(x) setup=(x = 2.0) evals=5
            j = judge_loosely(t1, t2)
            @show j
            @test isinvariant(j)
        end
    end

    @testset "Test that local benchmarks are faster than globals" begin
        t1 = @benchmark sin(global_x) evals=5  # note the lack of $
        t2 = @localbenchmark sin(global_x) evals=5
        j = judge_loosely(t1, t2)
        @show j
        @test isregression(j)
    end

    @testset "Other macros" begin
        x = 1.0
        @localbtime sin(x)
        @localbelapsed sin(x)
    end

    @testset "Interpolated values" begin
        t1 = @benchmark sum($(rand(1000)))
        t2 = @localbenchmark sum($(rand(1000)))
        j = judge_loosely(t1, t2)
        @show j
        @test isinvariant(j)
    end
end


module SolverBenchmarking

export summarize_results, run_solver_benchmark, BenchmarkParams

using JSON, DataFrames, CSV
using MINLPLib, JuMP, EAGO

"""
Holds parameters for solver benchmarking.
"""
Base.@kwdef mutable struct BenchmarkParams
    time::Float64 = 200.0
    threads_per::Int = 1
    rerun::Bool = true
    has_obj_bnd::Bool = false
end

"""
Custom error type to handle exceptions
and log information that occur when solving.
"""
struct InstanceError <: Exception
    path::String
    solver_name::String
    model_name::String
end

"""
Defines storage routine when an instance error occurs.
"""
function store_error(d::InstanceError)
    data = Dict{String,Any}()
    data["SolverName"] = d.solver_name
    data["InstanceName"] = d.model_name
    data["TerminationStatus"] = string(MOI.OTHER_ERROR)
    data["PrimalStatus"] = string(MOI.OTHER_RESULT_STATUS)
    data["ObjBound"] = 0.0
    data["ObjValue"] = 0.0
    data["SolveTime"] = 200.0
    data["CompletedSolveTime"] = 200.0
    data["Error"] = true

    file_path = joinpath(d.path, "$(d.solver_name)_$(d.model_name).json")
    open(file_path,"w") do f
        JSON.print(f, data)
    end
    return
end

#=
function capture_stdout(f)
    stdout_orig = stdout
    (rd, wr) = redirect_stdout()
    f()
    close(wr)
    redirect_stdout(stdout_orig)
    read(rd, String)
end
=#

"""
Fetches the model at "lib\\model_name", attaches the optimizers factory "opt_factory", sets benchmark parameters,
solves the optimization problem, then tidies up the memory space by finalizing the background then garbage collecting.
"""
function fetch_and_optimize(lib::String, model_name::String, opt_factory::Any, params::BenchmarkParams)
    m = fetch_model(lib, model_name)
    set_optimizer(m, opt_factory)

    set_time_limit_sec(m, params.time)
    set_silent(m)

    t = time()
    try
        JuMP.optimize!(m)
    catch
    end
    t = time() - t

    finalize(backend(m))
    GC.gc()
    return m, t
end

"""
If the rerun parameter is set to `true` or no JSON file ("p\\\$(solver_name)_\$(model_name).json") exists
then fetch the model, optimize it and store results to a JSON file at the path ("p\\\$(solver_name)_\$(model_name).json").
"""
function benchmark_problem(opt_factory::Any, solver_name::String, lib::String, libout::String, model_name::String, p::String, params::BenchmarkParams)
    file_path = joinpath(p, libout, "$(solver_name)_$(model_name).json")
    if !isfile(file_path) || params.rerun
        m, t = fetch_and_optimize(lib, model_name, opt_factory, params)

        obj_val = Inf
        s_time = params.time
        t_status = MOI.OPTIMIZE_NOT_CALLED
        p_status = MOI.NO_SOLUTION
        try
            obj_val = objective_value(m)
            s_time = solve_time(m)
            t_status = termination_status(m)
            p_status = primal_status(m)
        catch e
            obj_val = Inf
            s_time = params.time
            t_status = MOI.OPTIMIZE_NOT_CALLED
            p_status = MOI.NO_SOLUTION
        end

        obj_bnd = -Inf
        try
            obj_bnd = objective_bound(m)
        catch e
            obj_bnd = -Inf
        end

        data = Dict{String,Any}()
        data["SolverName"] = solver_name
        data["InstanceName"] = model_name
        data["TerminationStatus"] = string(t_status)
        data["PrimalStatus"] = string(p_status)
        data["ObjBound"] = params.has_obj_bnd ? obj_bnd : -Inf
        data["ObjValue"] = obj_val
        data["SolveTime"] = s_time
        data["SolveTimeRaw"] = t
        data["CompletedSolveTime"] = (t_status == MOI.OPTIMAL) ? s_time : 100.0
        data["Error"] = false

        open(file_path,"w") do f
            JSON.print(f, data)
        end
    end
    return
end

"""
Main routine for running solver benchmark.
- If no directory `p` exists, make it.
- If no directory `p\\lib` exists, make it.
- Write the parameter file to the file path.
For each solver factory in "s", and each problem in "lib".
"""
function run_solver_benchmark(p::String, s::Dict{String,Any}, lib::String, libout::String; params = BenchmarkParams())

    # store parameter file
    !isdir(p) && mkdir(p)
    !isdir(joinpath(p, lib)) && mkdir(joinpath(p, lib))
    file_path = joinpath(p, libout, "parameters.json")
    data = Dict{String,Any}()
    data["time"] = params.time
    data["threads_per"] = params.threads_per
    data["rerun"] = params.rerun
    open(file_path,"w") do f
        JSON.print(f, data)
    end

    # benchmark solvers
    n = MINLPLib.fetch_names(lib)
    for (k,v) in s
        println("Running all benchmarks problems for solver = $(k)")
        @show length(n)
        for ni in n
            println("Running problem = $ni in $lib")
            benchmark_problem(v, k, lib, libout, ni, p, params)
        end
    end
    println("Benchmark Suite Complete")
    return
end

"""
"""
function summarize_results(lib::String, p::String)
    folder_path = joinpath(p, lib)
    df = DataFrame(SolverName = String[], InstanceName = String[], TerminationStatus = String[], PrimalStatus = String[],
                   ObjBound = Float64[], ObjValue = Float64[], SolveTime = Float64[], SolveTimeRaw = Float64[],
                   CompletedSolveTime = Float64[], Error = Bool[])
    for file in readdir(folder_path)
        if !endswith(file, ".json") || file === "parameters.json"
            continue
        end
        d = joinpath(folder_path, file) |> open |> JSON.parse
        obj_bnd = isnothing(d["ObjBound"]) ? -Inf : d["ObjBound"]
        obj_val = isnothing(d["ObjValue"]) ? Inf : d["ObjValue"]
        push!(df, (d["SolverName"], d["InstanceName"], d["TerminationStatus"], d["PrimalStatus"], obj_bnd, obj_val, d["SolveTime"], d["SolveTimeRaw"], d["CompletedSolveTime"], d["Error"]))
    end

    param_dict = joinpath(p, lib, "parameters.json") |> open |> JSON.parse
    df.RelativeGap = abs.(df.ObjValue - df.ObjBound)./min.(abs.(df.ObjValue), abs.(df.ObjBound))
    df.AbsoluteGap = df.ObjValue - df.ObjBound
    df.SolvedInTime = df.SolveTime .< param_dict["time"]
    CSV.write(joinpath(folder_path, "result_summary.csv"), df)
    return
end

end


export Neuron, Network

mutable struct Neuron
    position::Float64
    activ_func::Function
    output::Float64
    connections::Array{Gene}
end

struct Network
    n_in::Int64
    n_out::Int64
    neurons::Dict
end

function Neuron(neur::Neuron)
    deepcopy(neur)
end

function Network(net::Network)
    deepcopy(net)
end


#
# Types supporting parameterized Timestep and Clock objects
#

abstract type AbstractTimestep <: MimiStruct end

struct FixedTimestep{FIRST, STEP, LAST} <: AbstractTimestep
    t::Int
end

struct VariableTimestep{TIMES} <: AbstractTimestep
    t::Int
    current::Int

    function VariableTimestep{TIMES}(t::Int = 1) where {TIMES}
        # The special case below handles when functions like next_step step beyond
        # the end of the TIMES array.  The assumption is that the length of this
        # last timestep, starting at TIMES[end], is 1.
        current::Int = t > length(TIMES) ? TIMES[end] + 1 : TIMES[t]

        return new(t, current)
    end
end

struct TimestepValue{T}
    value::T
    offset::Int

    function TimestepValue(v::T; offset::Int = 0) where T
        return new{T}(v, offset)
    end
end

struct TimestepIndex
    index::Int
end

mutable struct Clock{T <: AbstractTimestep} <: MimiStruct
	ts::T

	function Clock{T}(FIRST::Int, STEP::Int, LAST::Int) where T
		return new(FixedTimestep{FIRST, STEP, LAST}(1))
    end

    function Clock{T}(TIMES::NTuple{N, Int} where N) where T
        return new(VariableTimestep{TIMES}())
    end
end

mutable struct TimestepArray{T_TS <: AbstractTimestep, T, N, ti} <: MimiStruct
	data::Array{T, N}

    function TimestepArray{T_TS, T, N, ti}(d::Array{T, N}) where {T_TS, T, N, ti}
		return new(d)
	end

    function TimestepArray{T_TS, T, N, ti}(lengths::Int...) where {T_TS, T, N, ti}
		return new(Array{T, N}(undef, lengths...))
	end
end

# Since these are the most common cases, we define methods (in time.jl)
# specific to these type aliases, avoiding some of the inefficiencies
# associated with an arbitrary number of dimensions.
const TimestepMatrix{T_TS, T, ti} = TimestepArray{T_TS, T, 2, ti}
const TimestepVector{T_TS, T} = TimestepArray{T_TS, T, 1, 1}


using SimpleSDMDataSources, Test, Dates

include("worldclim-bioclim.jl")
include("worldclim-weather.jl")
include("earthenv-landcover.jl")
include("earthenv-heterogeneity.jl")
include("chelsa-bioclim.jl")
include("alwb.jl")
include("awap.jl")


module ArrayFire

using Libdl,Random,SparseArrays,LinearAlgebra,FFTW,DSP,Statistics

include("common.jl")
include("array.jl")
include("util.jl")
include("wrap.jl")
include("random.jl")
include("graphics.jl")
include("indexing.jl")
include("fft.jl")

end


export SimplePolyhedraLibrary, SimplePolyhedron

"""
    SimplePolyhedraLibrary{T}

Default library for polyhedra of dimension larger than 1 ([`IntervalLibrary`](@ref) is the default for polyhedra of dimension 1).
The library implements the bare minimum and uses the fallback implementation for all operations.
"""
struct SimplePolyhedraLibrary{T} <: PolyhedraLibrary
    solver::Union{Nothing, MPB.AbstractMathProgSolver}
    function SimplePolyhedraLibrary{T}(solver=nothing) where T
        new{T}(solver)
    end
end

similar_library(lib::SimplePolyhedraLibrary, d::FullDim, ::Type{T}) where T = default_library(d, T) # default_library allows to fallback to Interval if d is FullDim{1}

mutable struct SimplePolyhedron{N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}} <: Polyhedron{N, T}
    hrep::Union{HRepT, Nothing}
    vrep::Union{VRepT, Nothing}
    solver::Union{Nothing, MPB.AbstractMathProgSolver}
    function SimplePolyhedron{N, T, HRepT, VRepT}(hrep::Union{HRepT, Nothing}, vrep::Union{VRepT, Nothing}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}}
        new{N, T, HRepT, VRepT}(hrep, vrep, solver)
    end
end
function SimplePolyhedron{N, T, HRepT, VRepT}(hrep::HRepT, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}}
    SimplePolyhedron{N, T, HRepT, VRepT}(hrep, nothing, solver)
end
function SimplePolyhedron{N, T, HRepT, VRepT}(vrep::VRepT, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}}
    SimplePolyhedron{N, T, HRepT, VRepT}(nothing, vrep, solver)
end
function SimplePolyhedron{N, T, HRepT, VRepT}(hrep::HRepresentation{N}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}}
    SimplePolyhedron{N, T, HRepT, VRepT}(HRepT(hrep), solver)
end
function SimplePolyhedron{N, T, HRepT, VRepT}(vrep::VRepresentation{N}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T, HRepT<:HRepresentation{N, T}, VRepT<:VRepresentation{N, T}}
    SimplePolyhedron{N, T, HRepT, VRepT}(VRepT(vrep), solver)
end

library(::Union{SimplePolyhedron{N, T}, Type{<:SimplePolyhedron{N, T}}}) where {N, T} = SimplePolyhedraLibrary{T}()
default_solver(p::SimplePolyhedron) = p.solver
supportssolver(::Type{<:SimplePolyhedron}) = true

hvectortype(::Type{<:SimplePolyhedron{N, T, HRepT}}) where {N, T, HRepT} = hvectortype(HRepT)
vvectortype(::Type{SimplePolyhedron{N, T, HRepT, VRepT}}) where {N, T, HRepT, VRepT} = vvectortype(VRepT)

similar_type(::Type{<:SimplePolyhedron{M, S, HRepT, VRepT}}, d::FullDim{N}, ::Type{T}) where {M, S, HRepT, VRepT, N, T} = SimplePolyhedron{N, T, similar_type(HRepT, d, T), similar_type(VRepT, d, T)}

function SimplePolyhedron{N, T, HRepT, VRepT}(hits::HIt...; solver=nothing) where {N, T, HRepT, VRepT}
    SimplePolyhedron{N, T, HRepT, VRepT}(HRepT(hits...), solver)
end
function SimplePolyhedron{N, T, HRepT, VRepT}(vits::VIt...; solver=nothing) where {N, T, HRepT, VRepT}
    SimplePolyhedron{N, T, HRepT, VRepT}(VRepT(vits...), solver)
end

# Need fulltype in case the use does `intersect!` with another element
SimplePolyhedron{N, T}(rep::Representation{N}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T} = SimplePolyhedron{N, T}(MultivariatePolynomials.changecoefficienttype(rep, T), solver)
function SimplePolyhedron{N, T}(rep::HRepresentation{N, T}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T}
    HRepT = fulltype(typeof(rep))
    VRepT = dualtype(HRepT)
    SimplePolyhedron{N, T, HRepT, VRepT}(rep, solver)
end
function SimplePolyhedron{N, T}(rep::VRepresentation{N, T}, solver::Union{Nothing, MPB.AbstractMathProgSolver}) where {N, T}
    VRepT = fulltype(typeof(rep))
    HRepT = dualtype(VRepT)
    SimplePolyhedron{N, T, HRepT, VRepT}(rep, solver)
end

function polyhedron(rep::Representation{N}, lib::SimplePolyhedraLibrary{T}) where {N, T}
    SimplePolyhedron{N, polytypefor(T)}(rep, lib.solver)
end

function Base.copy(p::SimplePolyhedron{N, T}) where {N, T}
    if p.hrep !== nothing
        SimplePolyhedron{N, T}(p.hrep, p.solver)
    else
        SimplePolyhedron{N, T}(p.vrep, p.solver)
    end
end

hrepiscomputed(p::SimplePolyhedron) = p.hrep !== nothing
function computehrep!(p::SimplePolyhedron)
    # vrep(p) could trigger an infinite loop if both vrep and hrep are null
    p.hrep = doubledescription(p.vrep)
end
function hrep(p::SimplePolyhedron)
    if !hrepiscomputed(p)
        computehrep!(p)
    end
    return p.hrep
end
vrepiscomputed(p::SimplePolyhedron) = p.vrep !== nothing
function computevrep!(p::SimplePolyhedron)
    # hrep(p) could trigger an infinite loop if both vrep and hrep are null
    p.vrep = doubledescription(p.hrep)
end
function vrep(p::SimplePolyhedron)
    if !vrepiscomputed(p)
        computevrep!(p)
    end
    return p.vrep
end

function sethrep!(p::SimplePolyhedron, h::HRepresentation)
    p.hrep = h
end
function setvrep!(p::SimplePolyhedron, v::VRepresentation)
    p.vrep = v
end
function resethrep!(p::SimplePolyhedron, h::HRepresentation)
    p.hrep = h
    p.vrep = nothing
end
function resetvrep!(p::SimplePolyhedron, v::VRepresentation)
    p.vrep = v
    p.hrep = nothing
end


#####
##### AbstractShape
#####

abstract type AbstractShape end

is_valid(::AbstractShape) = true

#####
##### functions for identifying where the shape lies with respect to the bounds of the image
#####

get_i_min(image::AbstractMatrix) = firstindex(image, 1)
get_i_max(image::AbstractMatrix) = lastindex(image, 1)
get_i_extrema(x) = (get_i_min(x), get_i_max(x))

get_j_min(image::AbstractMatrix) = firstindex(image, 2)
get_j_max(image::AbstractMatrix) = lastindex(image, 2)
get_j_extrema(x) = (get_j_min(x), get_j_max(x))

function is_outbounds(image::AbstractMatrix, shape::AbstractShape)
    i_min_shape, i_max_shape = get_i_extrema(shape)
    i_min_image, i_max_image = get_i_extrema(image)

    j_min_shape, j_max_shape = get_j_extrema(shape)
    j_min_image, j_max_image = get_j_extrema(image)

    return i_max_shape < i_min_image || i_min_shape > i_max_image || j_max_shape < j_min_image || j_min_shape > j_max_image
end

function is_inbounds(image::AbstractMatrix, shape::AbstractShape)
    i_min_shape, i_max_shape = get_i_extrema(shape)
    i_min_image, i_max_image = get_i_extrema(image)

    j_min_shape, j_max_shape = get_j_extrema(shape)
    j_min_image, j_max_image = get_j_extrema(image)

    return i_min_shape >= i_min_image && i_max_shape <= i_max_image && j_min_shape >= j_min_image && j_max_shape <= j_max_image
end

#####
##### drawing a single pixel on the image
#####

function put_pixel!(image::AbstractMatrix, i, j, color)
    if checkbounds(Bool, image, i, j)
        put_pixel_inbounds!(image, i, j, color)
    end

    return nothing
end

function put_pixel_inbounds!(image::AbstractMatrix, i, j, color)
    @inbounds image[i, j] = color
    return nothing
end

#####
##### DrawingOptimizationStyle to define generic draw! methods with different levels of optimization for drawing shapes
#####

abstract type DrawingOptimizationStyle end

get_drawing_optimization_style(shape) = PUT_PIXEL

draw!(image::AbstractMatrix, shape::AbstractShape, color) = draw!(get_drawing_optimization_style(shape), image, shape, color)

#####
##### PutPixel (least optimized)
#####

struct PutPixel <: DrawingOptimizationStyle end
const PUT_PIXEL = PutPixel()

draw!(::PutPixel, image::AbstractMatrix, shape::AbstractShape, color) = draw!(put_pixel!, image, shape, color)

#####
##### CheckBounds (checks outbounds and inbounds conditions and dispatches accordingly)
#####

struct CheckBounds <: DrawingOptimizationStyle end
const CHECK_BOUNDS = CheckBounds()

function draw!(::CheckBounds, image::AbstractMatrix, shape::AbstractShape, color)
    @assert is_valid(shape) "Cannot draw invalid shape $(shape)"

    if is_outbounds(image, shape)
        return nothing
    end

    if is_inbounds(image, shape)
        draw!(put_pixel_inbounds!, image, shape, color)
    else
        draw!(put_pixel!, image, shape, color)
    end

    return nothing
end

#####
##### Clip (clips the shapes that can be clipped easily and draw them with inbounds optimization)
#####

struct Clip <: DrawingOptimizationStyle end
const CLIP = Clip()

function draw!(::Clip, image::AbstractMatrix, shape::AbstractShape, color)
    @assert is_valid(shape) "Cannot draw invalid shape $(shape)"

    if is_outbounds(image, shape)
        return nothing
    end

    draw!(put_pixel_inbounds!, image, clip(image, shape), color)

    return nothing
end

#####
##### PutPixelInbounds (directly draw assuming shape is inbounds)
#####

struct PutPixelInbounds <: DrawingOptimizationStyle end
const PUT_PIXEL_INBOUNDS = PutPixelInbounds()

draw!(::PutPixelInbounds, image::AbstractMatrix, shape::AbstractShape, color) = draw!(put_pixel_inbounds!, image, shape, color)


include("analyse_closed_order.jl")
include("check_for_new_closed.jl")
include("check_grid_buy.jl")
include("check_grid_sell.jl")
include("create_trade_grid.jl")
include("finish_grid_bot.jl")
include("freeze_orders.jl")
include("get_buy_value.jl")
include("get_current_volume.jl")
include("get_grid_bots.jl")
include("get_next_gridpoints.jl")
include("init_grid_bot.jl")
include("place_grid_order.jl")
include("place_init_grid_order.jl")
include("update_status.jl")


using TrueRandom
using Distributions
using Test

@testset "ANU QRNG" begin
    r = ANUQRNG(1024, 768; prefill=false)
    TrueRandom.prefillcache(r)

    @test length(r.cache) == r.batchsize
    @test length(r.cacheprefill) == 0
    @test r.pointer == 1

    @test rand(r, UInt8) isa UInt8
    @test rand(r, UInt8, 2) isa Vector{UInt8}
    @test rand(r, UInt16) isa UInt16
    @test rand(r, UInt16, 2) isa Vector{UInt16}
    @test rand(r, UInt32) isa UInt32
    @test rand(r, UInt32, 2) isa Vector{UInt32}
    @test rand(r, UInt64) isa UInt64
    @test rand(r, UInt64, 2) isa Vector{UInt64}
    @test rand(r, UInt128) isa UInt128
    @test rand(r, UInt128, 2) isa Vector{UInt128}

    @test r.pointer == 48

    @test rand(r, Int8) isa Int8
    @test rand(r, Int8, 2) isa Vector{Int8}
    @test rand(r, Int16) isa Int16
    @test rand(r, Int16, 2) isa Vector{Int16}
    @test rand(r, Int32) isa Int32
    @test rand(r, Int32, 2) isa Vector{Int32}
    @test rand(r, Int64) isa Int64
    @test rand(r, Int64, 2) isa Vector{Int64}
    @test rand(r, Int128) isa Int128
    @test rand(r, Int128, 2) isa Vector{Int128}

    @test r.pointer == 95

    @test rand(r, Float32) isa Float32
    @test rand(r, Float32, 2) isa Vector{Float32}
    @test rand(r, Float64) isa Float64
    @test rand(r, Float64, 2) isa Vector{Float64}

    @test r.pointer == 113
    @test length(r.cacheprefill) == 0

    m = rand(r, Float64, 5, 8)
    @test m isa Matrix{Float64}
    @test 0 <= minimum(m) <= 1
    @test 0 <= maximum(m) <= 1

    p1 = r.pointer
    @test rand(r, UInt16, 1024) isa Any
    @test r.pointer == p1

    @test rand(r, UInt16, 2048) isa Any
    @test r.pointer == p1

    @test randn(r) isa Any
    @test rand(r, Normal()) isa Any
end


module TermInterface

using ..Util
using Base.Meta

# TODO document this interface for extending for custom types
# Interface for Expr. Implement these methods for your own type to
# Use it instead of Expr in egraphs!
gethead(e::Expr) = isexpr(e, :call) ? e.args[1] : e.head
getargs(e::Expr) = @view e.args[(isexpr(e, :call) ? 2 : 1):end]
istree(e::Expr) = true
getmetadata(e::Expr) = (iscall=isexpr(e, :call),)
arity(e::Expr) = length(getargs(e)) # optional
preprocess(e::Expr) = cleanast(e)

# Fallback implementation for all other types
gethead(e) = e
getargs(e) = []
istree(a) = false
getmetadata(e) = nothing
arity(e) = 0 # optional
preprocess(e) = e

export gethead
export getargs
export istree
export getmetadata
export preprocess
export arity
end


# TODO: extensibility of parsing and Terms types.  Allow for ranef terms etc.
# Use dispatch in `term_ex_from_formula_ex`

mutable struct Formula
    ex::Expr
    ex_lowered::Expr
    term::Terms.FormulaTerm
    schema_set::Bool
end

Base.copy(f::Formula) = Formula(copy(f.lhs), copy(f.rhs))

function _formula!(ex::Expr)
    raise_tilde!(ex)
    @argcheck is_call(ex, :~) "expected formula separator ~, got $(ex.head)"
    @argcheck 2 <= length(ex.args) <= 3 "malformed formula: $ex"

    ex_orig = copy(ex)
    length(ex.args) == 2 && insert!(ex.args, 2, nothing)
    sort_terms!(parse!(ex))

    term_ex = Terms.term_ex_from_formula_ex(ex)
    return Expr(:call, :Formula, Meta.quot(ex_orig), Meta.quot(ex), term_ex, false)
end

macro formula(ex)
    _formula!(ex)
end

function Base.show(io::IO, f::Formula)
    print(io, "formula: ")
    lhs, rhs = f.ex_lowered.args[2:3]
    lhs === nothing || print(io, "$lhs ")
    print(io, "~ $rhs")
end

"""
    raise_tilde!(ex::Expr)

"Correct" the parser's handling of non-infix ~ in order to support one-sided
formulas.  The parser treats it as a unary operator, so the tilde call ends
up as the first argument of the central expression.

That is, the one-sided formula ~1+a parses as :(~(1)+a), which needs to be
converted to :(~(1+a)).

"""
function raise_tilde!(ex::Expr)
    if is_call(ex) && is_call(ex.args[2], :~)
        length(ex.args[2].args) == 2 || throw(ArgumentError("malformed formula: $ex"))
        ex.args[2] = ex.args[2].args[2]
        ex.args = [:~, deepcopy(ex)]
    end
    ex
end

Base.:(==)(f1::Formula, f2::Formula) = all(getfield(f1, f)==getfield(f2, f) for f in fieldnames(f1))

check_call(ex) = Meta.isexpr(ex, :call) || throw(ArgumentError("non-call expression encountered: $ex"))

# expression re-write rules:
is_star(a::Expr, b) = false
is_star(a::Expr, b::Expr) = is_call(b, :*)
expand_star(a, b) = Expr(:call, :+, a, b, Expr(:call, :&, a, b))
function expand_star!(ex::Expr)
    @debug "  expand star: $ex -> "
    ex.args = reduce(expand_star, ex.args[2:end]).args
    @debug "               $ex"
    ex
end
function expand_star!(ex::Expr, child_idx::Int)
    expand_star!(ex.args[child_idx])
    child_idx
end

const ASSOCIATIVE = Set([:+, :&, :*])
associative(a, b) =
    Meta.isexpr(a, :call) &&
    Meta.isexpr(b, :call) &&
    a.args[1] in ASSOCIATIVE &&
    a.args[1] == b.args[1]

function associate!(ex::Expr, child_idx::Integer)
    @debug "    associative: $ex -> "
    splice!(ex.args, child_idx, ex.args[child_idx].args[2:end])
    @debug "                 $ex"
    child_idx
end

# Distributive property
# &(a..., +(b...), c...) -> +(&(a..., b_i, c...)_i...)
#
# replace outer call (:&) with inner call (:+), whose arguments are copies of
# the outer call, one for each argument of the inner call.  For the ith new
# child, the original inner call is replaced with the ith argument of the inner
# call.
const DISTRIBUTIVE = Set([:& => :+])
distributive(a, b) =
    Meta.isexpr(a, :call) &&
    Meta.isexpr(b, :call) &&
    (a.args[1] => b.args[1]) in DISTRIBUTIVE

function distribute!(ex::Expr, child_idx::Integer)
    @debug "    distributive: $ex -> "
    new_args = deepcopy(ex.args[child_idx].args)
    for i in 2:length(new_args)
        new_child = deepcopy(ex)
        new_child.args[child_idx] = new_args[i]
        new_args[i] = new_child
    end
    ex.args = new_args
    @debug "                  $ex"
    2
end

parse!(s::Symbol) = s
parse!(i::Integer) = i ∈ [-1, 0, 1] ? i :
    throw(ArgumentError("invalid integer term $i (only -1, 0, and 1 allowed)"))

## TODO: re-write this in terms of generic re-write rules to make more extensible
## the rules have a form like
## 1. check if rule applies, given ex and (idx of) child being parsed.
## 2. re-write expression starting from idx of child.
## 3. return idx of next child to check.
##
## might need to differentiate between pre-walk and post-walk rules?  or change
## where the star expansion happens (should be able to check it...)
function parse!(ex::Expr)
    @debug "parsing $ex"
    check_call(ex)
    # iterate over children, checking for special rules
    child_idx = 2
    while child_idx <= length(ex.args)
        @debug "  ($(ex.args[1])) i=$child_idx: $(ex.args[child_idx])"
        # depth first: parse each child first
        parse!(ex.args[child_idx])
        if associative(ex, ex.args[child_idx])
            child_idx = associate!(ex, child_idx)
        elseif is_star(ex, ex.args[child_idx])
            child_idx = expand_star!(ex, child_idx)
        elseif distributive(ex, ex.args[child_idx])
            child_idx = distribute!(ex, child_idx)
        else
            # no special rules apply, move onto next
            child_idx += 1
        end
    end
    @debug "done: $ex"
    ex
end

parse!(x) = x
Base.copy(x::Nothing) = x
Base.copy(x::Symbol) = x

function sort_terms!(ex::Expr)
    check_call(ex)
    if ex.args[1] ∈ ASSOCIATIVE
        sort!(view(ex.args, 2:length(ex.args)), by=degree)
    elseif ex.args[1] == :|
        # sort mini terms inside ranef term
        sort_terms!(ex.args[2])
    end
    ex
end
sort_terms!(x) = x

degree(i::Integer) = 0
degree(::Symbol) = 1
# degree(s::Union{Symbol, ContinuousTerm, CategoricalTerm}) = 1
function degree(ex::Expr)
    check_call(ex)
    if ex.args[1] == :&
        length(ex.args) - 1
    elseif ex.args[1] == :|
        # put ranef terms at end
        Inf
    else
        # arbitrary functions are treated as main effect terms
        1
    end
end


using Test

using MIToS.Utils
using MIToS.MSA
using MIToS.Information
using MIToS.PDB
using MIToS.SIFTS
using MIToS.Pfam
using LinearAlgebra
using Random
using Statistics            # mean
using DelimitedFiles        # readdlm
using ROCAnalysis           # AUC
using Clustering            # test/MSA/Hobohm.jl
using NamedArrays           # array
using StatsBase             # WeightVec
using PairwiseListMatrices  # getlist

const DATA = joinpath(pwd(), "data")

# Utils
@testset "Utils" begin
    include(joinpath("Utils", "GeneralUtils.jl"))
end

# MSA
@testset "MSA" begin
    include(joinpath("MSA", "Residues.jl"))
    include(joinpath("MSA", "Alphabet.jl"))
    include(joinpath("MSA", "ThreeLetters.jl"))
    include(joinpath("MSA", "Annotations.jl"))
    include(joinpath("MSA", "MultipleSequenceAlignment.jl"))
    include(joinpath("MSA", "GeneralParserMethods.jl"))
    include(joinpath("MSA", "IO.jl"))
    include(joinpath("MSA", "General.jl"))
    include(joinpath("MSA", "MSAEditing.jl"))
    include(joinpath("MSA", "MSAStats.jl"))
    include(joinpath("MSA", "Shuffle.jl"))
    include(joinpath("MSA", "Identity.jl"))
    include(joinpath("MSA", "Hobohm.jl"))
    include(joinpath("MSA", "MSAAnnotations.jl"))
end

# Information
@testset "Information" begin
    include(joinpath("Information", "ContingencyTables.jl"))
    include(joinpath("Information", "Counters.jl"))
    include(joinpath("Information", "InformationMeasures.jl"))
    include(joinpath("Information", "Iterations.jl"))
    include(joinpath("Information", "CorrectedMutualInformation.jl"))
    include(joinpath("Information", "Gaps.jl"))
    include(joinpath("Information", "Externals.jl"))
end

# PDB
@testset "PDB" begin
    include(joinpath("PDB", "PDB.jl"))
    include(joinpath("PDB", "Contacts.jl"))
    include(joinpath("PDB", "Kabsch.jl"))
    include(joinpath("PDB", "Internals.jl"))
end

# SIFTS
@testset "SIFTS" begin
    include(joinpath("SIFTS", "SIFTS.jl"))
end

# Pfam
@testset "Pfam" begin
    include(joinpath("Pfam", "Pfam.jl"))
end

# Scripts
@testset "Scripts" begin
    include(joinpath("Scripts", "Template.jl"))
    include(joinpath("Scripts", "Scripts.jl"))
end

print("""

----- =D -----

""")


"""
    PreprocessedDataCausalityTest

A causality test where the data is subjected to some preprocessing before
applying the test.
"""
abstract type PreprocessedDataCausalityTest end

export PreprocessedDataCausalityTest


export QdldlKKTSolver, CholmodKKTSolver
# -------------------------------------
# abstract type defs
# -------------------------------------
abstract type AbstractKKTSolver end
abstract type AbstractKKTShape end

# NB: all concrete examples of this type should
# implement refactor! and solve! methods and should
# implement a constructor taking (P,A,sigma,rho) as
# arguments

# -------------------------------------
# some internal utility functions
# -------------------------------------

"Check the dimensions of the problem data used to assemble the KKT matrix `K`."
function _kktutils_check_dims(P::SparseMatrixCSC{Tv, Ti}, A::AbstractMatrix{Tv}, sigma::Tv, rho::Union{Tv, AbstractVector{Tv}}) where {Tv <: AbstractFloat, Ti <: Integer}

    n = size(P, 1)
    m = size(A, 1)

    size(A,2) == n || throw(DimensionMismatch())

    length(rho)   == m || length(rho)   == 1 || throw(DimensionMismatch())
    length(sigma) == n || length(sigma) == 1 || throw(DimensionMismatch())

    return m, n
end

"Count the number of nnz in each column of the lower triangle of the KKT matrix `K`."
function _count_lower_triangle!(Kcolnz::Vector{Ti}, P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, n::Ti) where {Tv <: AbstractFloat, Ti <: Integer}
    # count strict lower triangular values of P
    @inbounds for cidx = 1:n
        for j = (P.colptr[cidx]):(P.colptr[cidx+1]-1)
            ridx = P.rowval[j]
            if (ridx > cidx)
                Kcolnz[cidx] += 1
            end
        end
    end
    # add number of column entries of A
    @inbounds for cidx = 1:n
        Kcolnz[cidx] += A.colptr[cidx+1] - A.colptr[cidx]
    end

    #every element on the diagonal is also nonzero
    Kcolnz .+= 1
end

"Count the number of nnz in each column of the upper triangle of the KKT matrix `K`."
function _count_upper_triangle!(Kcolnz::Vector{Ti}, P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, n::Ti) where {Tv <: AbstractFloat, Ti <: Integer}
    # count nonzeros in the strict upper triangle of (P)
    @inbounds for cidx = 1:n
        for j = (P.colptr[cidx]):(P.colptr[cidx+1]-1)
            ridx = P.rowval[j]
            if (ridx < cidx)
                Kcolnz[cidx] += 1
            else
                break
            end
        end
    end

    #count the nonzeros in columns in A'
    @inbounds for idx in A.rowval
        Kcolnz[idx + n] += 1
    end

    #every element on the diagonal is also nonzero
    Kcolnz .+= 1
end

"Determine and fill in the rowvals `Ki` and nzvals `Kx` of the lower triangle of `K`."
function _fill_lower_triangle!(Ki::Vector{Ti}, Kp::Vector{Ti}, Kx::Vector{Tv}, P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, R::Union{Tv, AbstractVector{Tv}}, S::Union{Tv, AbstractVector{Tv}}, KNextInCol::Vector{Ti}, m::Ti, n::Ti) where {Tv <: AbstractFloat, Ti <: Integer}
    # tril(K) = | tril(P) + σI |         |
    #           |   A          | - 1/ρ I |

    #fill in the Sigma part
    @inbounds for cidx = 1:n
            Kx[KNextInCol[cidx]]      = S[cidx]
            Ki[KNextInCol[cidx]]      = cidx
            KNextInCol[cidx] += 1
    end

    #fill in tril(P)
    @inbounds for cidx = 1:n
        for j = (P.colptr[cidx]):(P.colptr[cidx+1]-1)
            ridx = P.rowval[j]
            if ridx > cidx
                Kidx = KNextInCol[cidx]
                Ki[Kidx] = ridx
                Kx[Kidx] = P.nzval[j]
                KNextInCol[cidx] += 1
            elseif ridx == cidx # add diagonal entries of P onto the sigma value
                Kidx = KNextInCol[cidx] - 1
                Kx[Kidx] += P.nzval[j]
            end
        end
    end

    #fill in A in the lower LHC
    @inbounds for cidx = 1:n
        for j = (A.colptr[cidx]):(A.colptr[cidx+1]-1)
            ridx = A.rowval[j]
            ridxpn = ridx + n
            Kidx = KNextInCol[cidx]
            Ki[Kidx] = ridxpn
            Kx[Kidx] = A.nzval[j]
            KNextInCol[cidx] += 1
        end
    end

    #fill in the rho part
    @inbounds for idx = 1:m
        kidx = KNextInCol[idx + n]
        Kx[kidx] = R[idx]
        Ki[kidx] = idx + n
    end
end

"Determine and fill in the rowvals `Ki` and nzvals `Kx` of the upper triangle of `K`."
function _fill_upper_triangle!(Ki::Vector{Ti}, Kp::Vector{Ti}, Kx::Vector{Tv}, P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, R::Union{Tv, AbstractVector{Tv}}, S::Union{Tv, AbstractVector{Tv}}, KNextInCol::Vector{Ti}, m::Ti, n::Ti) where {Tv <: AbstractFloat, Ti <: Integer}
    # triu(K) = | triu(P) + σI |    A'   |
    #           |              | - 1/ρ I |

    #fill in triu(P)
    @inbounds for cidx = 1:n
        for j = (P.colptr[cidx]):(P.colptr[cidx+1]-1)
            ridx = P.rowval[j]
            if ridx <= cidx
                Kidx = KNextInCol[cidx]
                Ki[Kidx] = ridx
                Kx[Kidx] = P.nzval[j]
                KNextInCol[cidx] += 1
            else
                break
            end
        end
    end

    #fill in A' in the upper RHS
    @inbounds for cidx = 1:n
        for j = (A.colptr[cidx]):(A.colptr[cidx+1]-1)
            ridx = A.rowval[j]
            ridxpn = ridx + n
            Kidx = KNextInCol[ridxpn]
            KNextInCol[ridxpn] += 1
            Ki[Kidx] = cidx
            Kx[Kidx] = A.nzval[j]
        end
    end

    #fill in the Sigma part
    @inbounds for cidx = 1:n
        if KNextInCol[cidx] == Kp[cidx+1]       #P had diagonal term at cidx)
            Kx[KNextInCol[cidx] - 1] += S[cidx]
        else
            Kx[KNextInCol[cidx]]      = S[cidx]
            Ki[KNextInCol[cidx]]      = cidx
        end
    end
    #fill in the rho part
    @inbounds for idx = 1:m
        kidx = KNextInCol[idx+n]
        Kx[kidx] = R[idx]
        Ki[kidx] = idx+n
    end
end

# handle the different forms of inputs for sigma and rho: here both are vectors (*)
function assemble_kkt_triangle(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::AbstractVector{Tv}, rho::AbstractVector{Tv}, shape::Symbol = :U) where {Tv <: AbstractFloat, Ti <: Integer}
        S = sigma
        R  = Array{Tv}(undef, size(A, 1))
     @. R  = -one(Tv) / rho
     # pass on to main function
    return _assemble_kkt_triangle(P, A, S, R, shape)
end
# only sigma is a scalar
function assemble_kkt_triangle(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::Tv, rho::AbstractVector{Tv}, shape::Symbol = :U) where {Tv <: AbstractFloat, Ti <: Integer}
    S = Vector{Tv}(undef, size(P, 1))
    fill!(S, sigma)
    # put again through (*)
    return assemble_kkt_triangle(P, A, S, rho, shape)
end

# only rho is a scalar
function assemble_kkt_triangle(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::AbstractVector{Tv}, rho::Tv, shape::Symbol = :U) where {Tv <: AbstractFloat, Ti <: Integer}
    R  = Array{Tv}(undef, size(A, 1))
    fill!(R, -one(Tv) ./ rho)
    # pass on to main function
    return _assemble_kkt_triangle(P, A, sigma, R, shape)
end

# rho and sigma are scalars
function assemble_kkt_triangle(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::Tv, rho::Tv, shape::Symbol = :U) where {Tv <: AbstractFloat, Ti <: Integer}
    S = Vector{Tv}(undef, size(P, 1))
    fill!(S, sigma)

    R  = Array{Tv}(undef, size(A, 1))
    fill!(R, -one(Tv) ./ rho)
    # pass on to main function
    return _assemble_kkt_triangle(P, A, S, R, shape)
end
"Given `P`, `A`, `sigma` and `rho` return the upper / lower triangle, defined by `shape`,  of the KKT condition matrix `K`."
function _assemble_kkt_triangle(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, S::AbstractVector{Tv}, R::AbstractVector{Tv}, shape::Symbol = :U) where {Tv <: AbstractFloat, Ti <: Integer}

    #   K = | P + σI |  A'     |
    #       |   A    | - 1/ρ I |
    #         left      right
    n = size(P, 1)
    m = size(A, 1)

    # make the column counter of K
    Kcolnz = zeros(Ti, m + n)

    #work out how many nonzeros are in K.
    if shape == :U
        _count_upper_triangle!(Kcolnz, P, A, n)
    elseif shape == :L
        _count_lower_triangle!(Kcolnz, P, A, n)
    end

    #make the column pointer and nonzero count
    Kp   = Vector{Ti}(undef, m + n + 1)
    Kp[1] = 1
    @inbounds for i = 1:(m+n)
        Kp[i+1] = Kp[i] + Kcolnz[i]
    end

    KNextInCol = copy(Kp);        #next value in column goes here
    nnzK = Kp[end] - 1
    Ki   = Vector{Ti}(undef, nnzK)
    Kx   = Vector{Tv}(undef, nnzK)

    # fill in the rowvals and nzvals
    if shape == :U
        _fill_upper_triangle!(Ki, Kp, Kx, P, A, R, S, KNextInCol, m, n)
    elseif shape == :L
        _fill_lower_triangle!(Ki, Kp, Kx, P, A, R, S, KNextInCol, m, n)
    end

    #assemble the matrix
    K = SparseMatrixCSC(m + n, m + n, Kp, Ki, Kx)

    return K
end

"Given `P`, `A`, `sigma` and `rho` return the full KKT condition matrix `K`."
function _assemble_kkt_full(P::SparseMatrixCSC{Tv, Ti}, A::AbstractMatrix{Tv}, sigma::Tv, rho::Union{Tv, AbstractVector{Tv}}) where {Tv <: AbstractFloat, Ti <: Integer}

    n = size(P, 1)
    m = size(A, 1)
    S = length(sigma) == 1 ? (sigma[1]) * I : Diagonal(sigma)
    rhoinv  = Vector{Tv}(undef, m)
    rhoinv .= (-one(Tv)./rho)
    D       = SparseMatrixCSC(Diagonal(rhoinv))

    #compute the full KKT matrix
    K = [P + S SparseMatrixCSC(A'); A D]

    return K
end

"Update the diagonal `rho` entries in the lower right hand corner of the KKT matrix `K`."
function update_kkt_matrix!(K::SparseMatrixCSC{Tv, Ti}, n::Ti, m::Ti, rho::Tv) where {Tv <: AbstractFloat, Ti <: Integer}
    @inbounds @simd for i = (n + 1):(n + m)
        K[i, i] = -one(Tv) / rho
    end
end

function update_kkt_matrix!(K::SparseMatrixCSC{Tv, Ti}, n::Ti, m::Ti, rho_vec::AbstractVector{Tv}) where {Tv <: AbstractFloat, Ti <: Integer}
    @inbounds @simd for i = (n + 1):(n + m)
        K[i, i] = -one(Tv) / rho_vec[i - n]
    end
end

# -------------------------------------
# QDLDL solver
# -------------------------------------

struct QdldlKKTSolver{Tv, Ti} <: AbstractKKTSolver

    m::Ti
    n::Ti
    ldlfact::QDLDL.QDLDLFactorisation{Tv, Ti}

    function QdldlKKTSolver(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::Tv, rho::Union{Tv, AbstractVector{Tv}}) where {Tv <: AbstractFloat, Ti <: Integer}
        m, n = _kktutils_check_dims(P, A, sigma, rho)
        K    = assemble_kkt_triangle(P, A, sigma, rho, :U)
        ldlfact = qdldl(K)

        #check for exactly n positive eigenvalues
        positive_inertia(ldlfact) == n || error("Objective function is not convex.")

        new{Tv, Ti}(m, n, ldlfact)
    end
end

function solve!(s::QdldlKKTSolver, lhs, rhs)
    @. lhs = rhs
    QDLDL.solve!(s.ldlfact, lhs)
end

function update_rho!(s::QdldlKKTSolver{Tv, Ti}, rho::Union{Tv, AbstractVector{Tv}}) where {Tv <: AbstractFloat, Ti <: Integer}
    QDLDL.update_diagonal!(s.ldlfact, (s.n+1):(s.n+s.m), (-one(Ti) ./ rho))
end

# -------------------------------------
# Julia Native solver (CHOLMOD based)
# -------------------------------------
mutable struct CholmodKKTSolver{Tv, Ti} <: AbstractKKTSolver

    fact::SuiteSparse.CHOLMOD.Factor{Float64} # SuiteSparse's cholmod only supports doubles
    K::SparseMatrixCSC{Tv, Ti}
    m::Ti
    n::Ti

    function CholmodKKTSolver(P::SparseMatrixCSC{Tv, Ti}, A::SparseMatrixCSC{Tv, Ti}, sigma::Tv, rho) where {Tv <: AbstractFloat, Ti <: Integer}

         m,n  = _kktutils_check_dims(P, A, sigma, rho)
         K    = _assemble_kkt_full(P, A, sigma, rho)
         fact = ldlt(K)

        return new{Tv, Ti}(fact, K, m, n)

    end
end

solve!(s::CholmodKKTSolver, lhs, rhs) = lhs .= s.fact \ rhs

function update_rho!(s::CholmodKKTSolver{Tv, Ti}, rho::Union{Tv, AbstractVector{Tv}}) where {Tv <: AbstractFloat, Ti <: Integer}
    update_kkt_matrix!(s.K, s.n, s.m, rho)
    #complete restart
    s.fact = ldlt(s.K)
end

free_memory!(s::AbstractKKTSolver) = nothing


# script for sending and decoding requests for image computations

"""
    parse_npy_header(hdr::AbstractString)

Function to parse npy header information to extract dimensions and array dtypes.
Customized implementation of `NPY.parseheader` to handle numpy structured arrays which EE always returns.
See https://numpy.org/devdocs/reference/generated/numpy.lib.format.html#format-version-1-0 for spec.
"""
function parse_npy_header(hdr::AbstractString)
    # rework of the NPY.parseheader function to allow for structured arrays
    # https://numpy.org/doc/stable/user/basics.rec.html#structured-datatypes
    s = NPZ.parsechar(hdr, '{')

    dict = Dict{String,Any}()
    # loop through and get the three key info
    # description, fortran_order, and shape
    for _ = 1:3
        s = strip(s)
        key, s = NPZ.parsestring(s)
        s = strip(s)
        s = NPZ.parsechar(s, ':')
        s = strip(s)
        if key == "descr"
            x, s = split(s, ", 'for")
            # clean up the string info to be able to easily parse
            y = split(
                replace(
                    replace(
                        replace(
                            replace(replace(x, "[" => ""), "]" => ""),
                            "(" => "",
                        ),
                        ")" => "",
                    ),
                    "'" => "",
                ),
                ",",
            )

            bnames = []
            dtypes = []

            # loop through the band info to get datatypes
            for z in y
                if occursin("<", z)
                    push!(dtypes, replace(strip(z), "<" => ""))
                elseif occursin(">", z)
                    push!(dtypes, replace(strip(z), ">" => ""))
                else
                    push!(bnames, strip(z))
                end
            end

            dict[key] = Dict(bnames .=> dtypes)

            # kinda hacky way of reconstructing the header info after splitting...
            s = ", 'for$(s)"

        elseif key == "fortran_order"
            dict[key], s = NPZ.parsebool(s)
        elseif key == "shape"
            dict[key], s = NPZ.parsetuple(s)
        else
            error("parsing header failed: bad dictionary key")
        end
        s = strip(s)
        if s[firstindex(s)] == '}'
            break
        end
        s = NPZ.parsechar(s, ',')
    end
    return dict
end

"""
    decode_npy_response(response::PyObject)

Function to take a response object of a npy bytes and convert to Julia array
"""
function decode_npy_response(response::PyObject)
    foo = response.content

    hdr = parse_npy_header(strip(split(foo, "\n")[1])[11:end])

    banddtypes = map(x -> NPZ.Numpy2Julia[x], values(hdr["descr"]))

    sametyped = all(x -> x == banddtypes[1], banddtypes)

    if ~sametyped
        @error(
            "Resulting bands have different data types, creating arrays with different band dtypes is currently not support ...Try explicitly casting the image to the same data type (ie `ee.Image().toFloat()`)."
        )
    end

    dtype = banddtypes[1]

    # create an empty in-memory buffer to temporarily write data to
    buffer = IOBuffer()
    write(buffer, foo)
    # convert data to correct dtype
    bar = reinterpret(dtype, take!(buffer))
    close(buffer) # close the io buffer for clean memory management

    # get the data shape of the bytes
    height, width = hdr["shape"]
    channels = length(banddtypes)

    # find the number of pixels we expect based on img
    data_size::Int64 = width * height * channels
    # find the offset of what was returned vs what is expected
    offset::Int64 = size(bar, 1) - data_size + 1

    arr::Array{dtype,1} = dtype.(bar)[offset:end]

    if channels > 1
        # reshape array from 1-d to correct 3-d dimensions
        arr3d::Array{dtype,3} = reshape(arr, (channels, width, height))
        # reorder the dimensions so it is W,H,C
        img = permutedims(arr3d, (2, 3, 1))
    else
        arr2d::Array{dtype,2} = reshape(arr, (width, height))
        img = arr2d #rotl90(arr2d)
    end

    return img
end

"""
    computepixels(session::EESession, pixelgrid::PixelGrid, image::EE.AbstractEEObject; format::AbstractString = "NPY")

Function to take an EarthEngine computed image and return an Array with geographic information (i.e. GeoArray).
This signature will return all of the bands within the image.
Currently on the "NPY" format is available.
See https://developers.google.com/earth-engine/reference/rest/v1beta/projects.image/computePixels
"""
function computepixels(
    session::EESession,
    pixelgrid::PixelGrid,
    image::EE.AbstractEEObject;
    format::AbstractString = "NPY",
)
    # get the band information
    bands = computevalue(session, bandNames(image))

    # pass to computepixels with band info
    computepixels(session, pixelgrid, image, bands; format = format)

end

"""
    computepixels(session::EESession, pixelgrid::PixelGrid, image::EE.AbstractEEObject, bands::AbstractVector{String}; format::AbstractString = "NPY")

Function to take an EarthEngine computed image and return an Array with geographic information (i.e. GeoArray).
Currently on the "NPY" format is available.
See https://developers.google.com/earth-engine/reference/rest/v1beta/projects.image/computePixels
"""
function computepixels(
    session::EESession,
    pixelgrid::PixelGrid,
    image::EE.AbstractEEObject,
    bands::AbstractVector{String};
    format::AbstractString = "NPY",
)
    endpoint = "image:computePixels"

    serialized = ee.serializer.encode(image, for_cloud_api = true)

    payload = Dict(
        :expression => serialized,
        :grid => typetodict(pixelgrid),
        :fileFormat => format,
        :bandIds => bands,
    )

    response = _sendrequest(session, endpoint, payload)

    result = decode_npy_response(response)

    affine = extract_affinemap(pixelgrid)

    output = GeoArray(result[:,end:-1:1], affine, pixelgrid.crsCode)

    return output
end


module GaPLAC

export invnormaltransform

# GP Packages
using AbstractGPs
using KernelFunctions
using GPLikelihoods
using Distances: Euclidean

# Bayesian stuff
using Turing
using Distributions
using Turing: Variational

# Data stuff
using CSV
using DataFrames
using CairoMakie
using StatsBase

# Other stuff
using ReTest

@testset "Module" begin
    @test true
end

include("gp_parts.jl")
include("liklihoods.jl")
include("abstractgp_translations.jl")
include("utils.jl")
include("interface.jl")

end # module


facts("constructors") do
  ticker_str = "TICKER"
#   context("inner constructor from TimeArray") do
#     fts = FinancialSeries.FinancialTimeSeries(
#       ohlc, FinancialSeries.Stock(FinancialSeries.Ticker(ticker_str)))
#     @fact values(fts.series["Open"])[1] => roughly(104.88)
#   end
#   context("from TimeArray components and instrument object") do
#     @fact_throws FinancialSeries.FinancialTimeSeries(
#       [DateTime(1795,10,31)], [0.0 0.0], ["col-1" "col-2"], "just-a-string")
#     fts = FinancialSeries.FinancialTimeSeries(
#       [DateTime(1795,10,31)], [0.0 0.0], ["col-1", "col-2"],
#       FinancialSeries.Stock(FinancialSeries.Ticker(ticker_str)))
#     @fact isa(fts.instrument, FinancialSeries.Stock) => true
#     @fact string(fts.instrument.ticker) => ticker_str
#   end

  ### TODO: redo these tests (or delete?) now that TimeArray has meta
  @fact 0 => 1
end

facts("element and time series method wrappers") do
  inst = FinancialSeries.Stock(FinancialSeries.Ticker("AAPL"))
  fts_cl = FinancialSeries.FinancialTimeSeries(cl, inst)
  fts_ohlc = FinancialSeries.FinancialTimeSeries(ohlc, inst)
#   context("element wrappers isolate elements") do
#     # correct types
#     @fact isa(timestamp(fts_cl), Array{Date,1}) => true
#     @fact isa(values(fts_cl), Array{Float64,1}) => true
#     @fact isa(values(fts_ohlc), Array{Float64,2}) => true
#     @fact isa(colnames(cl), Array{UTF8String, 1}) => true
#     @fact isa(instrument(fts_cl), FinancialSeries.Stock) => true
#     # proper indexing
#     @fact values(fts_cl)[2] => roughly(102.5)
#     @fact timestamp(fts_cl)[3] => DateTime.Date(2000,1,5)
#   end

  ### TODO: fix wrapper functions
  @fact 0 => 1
end

facts("time series method wrappers") do
#   inst = FinancialSeries.Stock(FinancialSeries.Ticker("AAPL"))
#   fts_cl = FinancialSeries.FinancialTimeSeries(cl, inst)
#   context("correct simple return value") do
#     @fact percentchange(fts_cl).values[1] => roughly((102.5-111.94)/111.94)
#   end
#   context("correct log return value") do
#     @fact percentchange(fts_cl, method="log").values[1] => roughly(log(102.5) - log(111.94))
#   end

  ### TODO: fix wrapper functions

  ## TODO: add tests for preserving meta after percentchange etc. calls
  @fact 0 => 1
end


module Foo

struct Data
  name::String
  values::Vector{Float64}
end

function doSomething(data::Vector{Data}, io=STDOUT)
  for d in data
    println(io, uppercase(d.name), " ", BLAS.asum(length(d.values), d.values, 1))
  end
end

function bar(data::Vector{Data})
  isempty(data) && return 1
  doSomething(data)
  return 0
end

function baz(data::Vector{Data}, logPath::String)
  isempty(data) && return 1
  open(logPath, "w") do io
    doSomething(data, io)
  end
  return 0
end

end


doc"""
  unlike the other files, generate_posit_class_h creates a separate file for
  each header file.
"""
function generate_posit_class_h(io, n, es)
  write(io, """
  #ifndef POSIT_$(n)_$(es)_H
  #define POSIT_$(n)_$(es)_H

  #include "posit.h"

  class $(c(n,es)){
    public:
      uint$(n)_t data;

      //various public constructors
      $(c(n,es))();             //defaults to zero
      $(c(n,es))(const float);        //conversion constructor from IEEE32
      $(c(n,es))(const double);       //conversion constructor from IEEE64
      $(c(n,es))(const $(c(n,es)) &); //copy constructor
      $(c(n,es))(const $(p(n,es)));   //bridge constructor from c functionality.

      //public operators
      $(c(n,es)) operator -() const;
      $(c(n,es)) operator +(const $(c(n,es)) rhs) const;
      $(c(n,es)) operator -(const $(c(n,es)) rhs) const;
      $(c(n,es)) operator *(const $(c(n,es)) rhs) const;
      $(c(n,es)) &operator +=(const $(c(n,es)) rhs);
      $(c(n,es)) &operator -=(const $(c(n,es)) rhs);
      $(c(n,es)) &operator *=(const $(c(n,es)) rhs);
      bool operator <(const $(c(n,es)) rhs) const;
      bool operator <=(const $(c(n,es)) rhs) const;
      bool operator >(const $(c(n,es)) rhs) const;
      bool operator >=(const $(c(n,es)) rhs) const;
      bool operator ==(const $(c(n,es)) rhs) const;
      $(c(n,es)) operator /(const $(c(n,es)) rhs) const;
      $(c(n,es)) &operator /=(const $(c(n,es)) rhs);

      operator float() const;
      operator double() const;
      operator $(p(n,es))() const;
  };

  bool isequal(const $(c(n,es)) a, const $(c(n,es)) b);
  $(c(n,es)) mulinv(const $(c(n,es)) arg);
  $(c(n,es)) log2(const $(c(n,es)) arg);
  $(c(n,es)) exp2(const $(c(n,es)) arg);
  $(c(n,es)) fma(const $(c(n,es)) a, const $(c(n,es)) b, const $(c(n,es)) c);
  $(c(n,es)) fms(const $(c(n,es)) a, const $(c(n,es)) b, const $(c(n,es)) c);
  $(c(n,es)) nfma(const $(c(n,es)) a, const $(c(n,es)) b, const $(c(n,es)) c);
  $(c(n,es)) nfms(const $(c(n,es)) a, const $(c(n,es)) b, const $(c(n,es)) c);
  $(c(n,es)) sqrt(const $(c(n,es)) arg);
  $(c(n,es)) log1p(const $(c(n,es)) arg);
  $(c(n,es)) log(const $(c(n,es)) arg);
  $(c(n,es)) log10(const $(c(n,es)) arg);
  $(c(n,es)) exp(const $(c(n,es)) arg);
  $(c(n,es)) pow(const $(c(n,es)) lhs, const $(c(n,es)) rhs);
  $(c(n,es)) sin(const $(c(n,es)) arg);
  $(c(n,es)) cos(const $(c(n,es)) arg);
  $(c(n,es)) atan(const $(c(n,es)) arg);
  $(c(n,es)) atan2(const $(c(n,es)) y, const $(c(n,es)) x);

  #endif

  """)
end


using DataStructures

head = HalfPath()
tail = HalfPath()
for x in 3:-1:1; enqueue!(head, x); end
for x in 4:6; enqueue!(tail, x); end
path = Path(head, tail)
@test collect(path) == [x for x in 1:6]


dists = [
    "constraints",
    "expfam",
    "HMM",
    "matrixnormal",
    "LKJ"
]

for dname in dists
    include(joinpath("distributions", "$(dname).jl"))
end