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

#=
Adapted from https://github.com/Simon-Hohberg/Viola-Jones/
=#

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

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

using .FaceDetection
const FD = FaceDetection
using Printf: @printf
using Images: imresize
using Serialization: deserialize

println("...done\n")

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

	include("constants.jl")
	include("main_data.jl")

	max_feature_width, max_feature_height, min_feature_height, min_feature_width, min_size_img = determine_feature_size(pos_testing_path, neg_testing_path; scale = scale, scale_to = scale_to)
	img_size = scale ? scale_to : min_size_img
	img_width, img_height = min_size_img
	data_file = joinpath(@__DIR__, "data", "feature_votes_$(img_size)")

	if ! isfile(data_file)
		error(throw("You do not have a data file.  Ensure you run \"write.jl\" to obtain your feature votes before running this programme."))
	end

	# read classifiers from file
	votes, features = deserialize(data_file)
	classifiers = FD.learn(pos_training_path, neg_training_path, features, votes, num_classifiers)

	FD.notify_user("Testing selected classifiers...")
	num_faces = length(filtered_ls(pos_testing_path))
	num_non_faces = length(filtered_ls(neg_testing_path))

	correct_faces = sum(FD.ensemble_vote_all(pos_testing_path, classifiers, scale=scale, scale_to=scale_to))
	correct_non_faces = num_non_faces - sum(FD.ensemble_vote_all(neg_testing_path, classifiers, scale=scale, scale_to=scale_to))
	correct_faces_percent = (correct_faces / num_faces) * 100
	correct_non_faces_percent = (correct_non_faces / num_non_faces) * 100

    faces_frac = string(correct_faces, "/", num_faces)
    faces_percent = string("(", correct_faces_percent, "% of faces were recognised as faces)")
    non_faces_frac = string(correct_non_faces, "/", num_non_faces)
    non_faces_percent = string("(", correct_non_faces_percent, "% of non-faces were identified as non-faces)")

    println("...done.\n")
    FD.notify_user("Result:\n")

    @printf("%10.9s %10.15s %15s\n", "Faces:", faces_frac, faces_percent)
    @printf("%10.9s %10.15s %15s\n\n", "Non-faces:", non_faces_frac, non_faces_percent)
end

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


# Bukdu v0.4.4
using Bukdu # ApplicationController Conn render HTML routes get Router
using Documenter.Utilities.DOM: @tags

struct Calculator <: ApplicationController
    conn::Conn
end

function index(c::Calculator)
    @tags h3 div a

    x = get(c.params, :x, 0)
    y = get(c.params, :y, 0)

    rand_x = rand(1:10)
    rand_y = rand(1:10)

    render(HTML, div(
        h3("Calculator"),
        div(
            string(x, +, y, '=', x+y),
        ),
        div(
            a[:href => "/?x=$rand_x&y=$rand_y"]("/?x=$rand_x&y=$rand_y")
        )
    ))
end

if PROGRAM_FILE == basename(@__FILE__)

routes() do
    get("/", Calculator, index)
end

Bukdu.start(8080)

Router.call(get, "/") #
# CLI.routes()

Base.JLOptions().isinteractive==0 && wait()

# Bukdu.stop()

end # if


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

using PackageCompiler

using DataStructures
using JSON
using JuMP
using MathOptInterface
using SparseArrays

pkg = [:DataStructures,
       :JSON,
       :JuMP,
       :MathOptInterface,
       :SparseArrays,
       ]

@info "Building system image..."
create_sysimage(pkg,
                precompile_statements_file="build/precompile.jl",
                sysimage_path="build/sysimage.so")


#Written By Johnathan Bizzano
include("../src/Computation/Computation.jl")
using Main.Computation

f(a, b) = 3 * (a + b)

function test_kernels(device)
    a = alloc(device, collect(1:10))
    b = alloc(device, collect(2:11))
    broadcast!(f, a, a, b)
    return Array(a)
end

function test_device_kernels()
    println("Starting Device Kernel Operation Test....")
    true_array = [f(i, i + 1) for i in 1:10]
    for device in get_devices()
        println("\tChecking $device")
        @assert test_kernels(device) == true_array "Failed $device Kernel Operation Test"
        println("\tSuccesfully Completed $device")
    end
    println("Successfully Compeleted Device Kernel Operation Tes!")
end

test_device_kernels()


module BayesOpt

export bayesopt

if isfile(joinpath(dirname(@__FILE__), "..", "deps", "deps.jl"))
    include("../deps/deps.jl")
else
    error("BayesOpt was not properly installed. Please run Pkg.build(\"BayesOpt\")")
end

include("params.jl")
include("opt.jl")

# This doesn't work on v0.4 since closures aren't c-callable functions.
function bayesopt(
        f::Function,
        x0::Vector{Float64},
        lb::Vector{Float64},
        ub::Vector{Float64};
        n_iterations::Integer=190,
        verbose_level::Integer=1)
    # wrap the function f (assumed to take an argument of Vector{Float64}) with a closure
    function f′(n, x, grad, fdata)
        return f(unsafe_wrap(Array, x, n))
    end
    params = initialize_parameters_to_default()
    params.n_iterations = n_iterations
    params.verbose_level = verbose_level
    minf = Ref{Float64}(0)
    x = copy(x0)
    r = bayes_optimization(length(x), f′, C_NULL, lb, ub, x, minf, params)
    if r != 0
        error("optimization failed")
    end
    return minf[], x
end

end # module


const mu_dmu_dict = Dict(
    "uniform"=>[
        0.01068 0.01017 0.00877 0.00981 0.01028 0.0 0.00984 0.0099 0.00955 0.01017 0.01054;
        0.00987 0.00953 0.00898 0.00913 0.01002 0.0 0.01034 0.01027 0.00905 0.00942 0.01055;
        0.00963 0.00852 0.00801 0.0089 0.00909 0.00972 0.00865 0.00859 0.00869 0.00914 0.00934;
        0.01034 0.00943 0.00879 0.00893 0.00982 0.0 0.01003 0.00886 0.00828 0.00955 0.01016;
        0.01049 0.00923 0.00969 0.00962 0.01039 0.0 0.0099 0.00877 0.00956 0.00937 0.00998;
        0.0 0.01101 0.0 0.0 0.0 0.0 0.01028 0.00981 0.00907 0.00994 0.01056;
        0.0106 0.00949 0.00964 0.0101 0.01029 0.0 0.0 0.0 0.01042 0.0 0.0;
        0.00923 0.00924 0.00817 0.00923 0.0097 0.0 0.01026 0.0098 0.00868 0.01029 0.01074;
        0.0093 0.00913 0.00834 0.00849 0.00957 0.0 0.00946 0.00925 0.00858 0.00889 0.00994;
        0.01005 0.00954 0.00918 0.00987 0.00904 0.01095 0.00953 0.00873 0.00938 0.00917 0.01026;
        0.01039 0.01007 0.0099 0.01002 0.01025 0.0 0.01013 0.00991 0.0093 0.00985 0.01064],
    "random_state_variant"=>[
        0.0731 0.06118 0.04944 0.04841 0.06033 0.0 0.02796 0.02958 0.03639 0.03986 0.03665;
        0.02722 0.02042 0.02538 0.02442 0.01502 0.0 0.02416 0.02196 0.01886 0.02062 0.02693;
        0.00801 0.01088 0.01536 0.01625 0.01471 0.01353 0.0154 0.01263 0.00435 0.00436 0.00808;
        0.00538 0.00642 0.0103 0.01385 0.01405 0.0 0.00623 0.00492 0.00236 0.00263 0.00168;
        0.00292 0.00392 0.00934 0.01188 0.01428 0.0 0.00087 0.00121 0.00183 0.0022 0.00178;
        0.0 0.00074 0.0 0.0 0.0 0.0 0.00055 0.00107 0.00148 0.00202 0.00211;
        0.00151 0.00091 0.00146 0.00235 0.0032 0.0 0.0 0.0 0.00116 0.0 0.0;
        0.00111 0.00125 0.00146 0.00161 0.00111 0.0 0.00213 0.00212 0.00179 0.00262 0.00299;
        0.00057 0.0007 0.00094 0.00082 0.00099 0.0 0.00184 0.00194 0.00229 0.00215 0.00249;
        0.00069 0.00056 0.00054 0.00059 0.00073 0.00141 0.00153 0.00161 0.0012 0.00157 0.00154;
        0.00031 0.00055 0.00059 0.00079 0.00092 0.0 0.00127 0.00077 0.00067 0.00068 0.0005],
    "favor_down"=>[
        0.09853  0.09109  0.08863  0.09136  0.09845  0.0      0.095    0.0893   0.08658  0.08963  0.09608;
        0.00731  0.00712  0.00639  0.0067   0.00764  0.0      0.00719  0.00679  0.00667  0.00699  0.00676;
        0.00054  0.00035  0.00046  0.00041  0.00042  0.00043  0.00055  0.00049  0.00045  0.00048  0.00053;
        4.0e-5   6.0e-5   7.0e-5   5.0e-5   5.0e-5   0.0      2.0e-5   5.0e-5   6.0e-5   3.0e-5   5.0e-5;
        2.0e-5   1.0e-5   0.0      1.0e-5   0.0      0.0      0.0      0.0      1.0e-5   1.0e-5   1.0e-5;
        0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      1.0e-5   0.0;
        0.0      2.0e-5   4.0e-5   2.0e-5   2.0e-5   0.0      0.0      0.0      1.0e-5   0.0      0.0;
        0.0      0.0      0.0      1.0e-5   0.0      0.0      0.0      0.0      0.0      0.0      0.0;
        0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0;
        0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0;
        0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0      0.0
    ],
    "corners"=>[
        0.0105   0.01014  0.00938  0.01074  0.01053  0.0      0.01033  0.00939  0.00931  0.00997  0.01063;
        0.00964  0.00976  0.00898  0.00882  0.01014  0.0      0.01051  0.00866  0.00893  0.00933  0.00965;
        0.00943  0.0092   0.00829  0.00898  0.00919  0.01025  0.00878  0.00921  0.00842  0.0091   0.00937;
        0.00958  0.00883  0.00877  0.00959  0.01033  0.0      0.0095   0.00897  0.00847  0.00881  0.00973;
        0.01015  0.00913  0.00941  0.01018  0.01122  0.0      0.01021  0.00961  0.00887  0.00919  0.00985;
        0.0      0.01096  0.0      0.0      0.0      0.0      0.01077  0.00971  0.00938  0.00951  0.01048;
        0.01027  0.00919  0.00942  0.00946  0.01037  0.0      0.0      0.0      0.01045  0.0      0.0;
        0.00935  0.00907  0.00863  0.00934  0.01012  0.0      0.01044  0.00994  0.00846  0.00992  0.01077;
        0.0089   0.00901  0.00792  0.00849  0.00922  0.0      0.01012  0.00911  0.00897  0.00959  0.00979;
        0.00952  0.00992  0.00936  0.00925  0.00942  0.01066  0.00913  0.00917  0.00908  0.01018  0.01046;
        0.0109   0.00944  0.00974  0.01007  0.01044  0.0      0.01028  0.01003  0.01017  0.00946  0.01023;
    ],
    "random_state_weight_variant"=>[
        0.00041  0.00577  0.00095  0.00069  0.00026  0.0      0.02119  0.02458  0.0358   0.037    0.02396;
        0.00019  0.00021  0.0008   0.00125  0.0014   0.0      0.01625  0.0125   0.00618  0.00641  0.008;
        0.00019  0.00108  0.00142  0.00146  0.00109  0.00223  0.01206  0.01124  0.0023   0.00296  0.00255;
        0.0034   0.01087  0.00341  0.00189  0.00177  0.0      0.10153  0.0996   0.00163  0.00399  0.00424;
        0.01356  0.00385  0.00166  0.00197  0.00215  0.0      0.00694  0.00552  0.00366  0.00812  0.00888;
        0.0      0.00045  0.0      0.0      0.0      0.0      0.03168  0.00552  0.00392  0.01613  0.01213;
        0.00068  0.00156  0.00171  0.00236  0.11574  0.0      0.0      0.0      0.00146  0.0      0.0;
        0.00183  0.00241  0.00199  0.00109  0.00074  0.0      0.00229  0.00219  0.00219  0.00476  0.00675;
        0.01277  0.00538  0.00238  0.00169  0.00196  0.0      0.00173  0.00169  0.00193  0.00479  0.00835;
        0.00277  0.00289  0.00356  0.00533  0.00318  0.00307  0.00201  0.00092  0.0018   0.00549  0.08244;
        0.00113  0.00269  0.00384  0.00472  0.0049   0.0      0.0033   0.05392  0.00326  0.00579  0.00972;
    ]

)


using ModernGL, GeometryTypes, GLAbstraction, GLWindow, Images, FileIO

# Load our textures. See "downloads.jl" to get the images.
kitten = load(GLAbstraction.dir("tutorials", "images", "kitten.png"))
puppy  = load(GLAbstraction.dir("tutorials", "images", "puppy.png"))

# create_glcontext creates a "bare" window with no depth or stencil
# buffer. So we set window hints to override this.
windowhints = [(GLFW.DEPTH_BITS, 32), (GLFW.STENCIL_BITS, 8)]
window = create_glcontext("Depth and stencils 1",
                          resolution=(600,600),
                          windowhints=windowhints)

vao = glGenVertexArrays()
glBindVertexArray(vao)

# The cube. This could be more efficiently represented using indexes,
# but the tutorial doesn't do it that way.
vertex_positions = Vec3f0[
    (-0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    (-0.5f0,  0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),

    (-0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),

    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),

    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),

    (-0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),

    (-0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0, -0.5f0),
]

vertex_texcoords = Vec2f0[
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),

                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),

                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),

                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),

                          (0.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (0.0f0, 0.0f0),
                          (0.0f0, 1.0f0),

                          (0.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (0.0f0, 0.0f0),
                          (0.0f0, 1.0f0)]

vertex_colors = fill(Vec3f0(1,1,1), 36)

vertex_shader = vert"""
#version 150

in vec3 position;
in vec3 color;
in vec2 texcoord;

out vec3 Color;
out vec2 Texcoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main()
{
    Color = color;
    Texcoord = texcoord;
    gl_Position = proj * view * model * vec4(position, 1.0);
}
"""
fragment_shader = load(joinpath(dirname(@__FILE__), "shaders", "puppykitten_color.frag"))

model = eye(Mat{4,4,Float32})
view = lookat(Vec3((1.2f0, 1.2f0, 1.2f0)), Vec3((0f0, 0f0, 0f0)), Vec3((0f0, 0f0, 1f0)))
proj = perspectiveprojection(Float32, 45, 800/600, 1, 10)

# Link everything together, using the corresponding shader variable as
# the Dict key
bufferdict = Dict(:position=>GLBuffer(vertex_positions),
                  :texcoord=>GLBuffer(vertex_texcoords),
                  :color=>GLBuffer(vertex_colors),
                  :texKitten=>Texture(kitten'),
                  :texPuppy=>Texture(puppy'),
                  :model=>model,
                  :view=>view,
                  :proj=>proj)

ro = std_renderobject(bufferdict,
                      LazyShader(vertex_shader, fragment_shader))

# Do the rendering: note that GLAbstraction automatically sets GL_DEPTH_TEST
glClearColor(0,0,0,1)
while !GLFW.WindowShouldClose(window)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    GLAbstraction.render(ro)
    GLFW.SwapBuffers(window)
    GLFW.PollEvents()
    if GLFW.GetKey(window, GLFW.KEY_ESCAPE) == GLFW.PRESS
        GLFW.SetWindowShouldClose(window, true)
    end
end

GLFW.DestroyWindow(window)  # needed if you're running this from the REPL


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

"""
	rameau(paths::Tuple,switches::Tuple;input_properties=('\t',0),prediction_coef=[0.0059;0.0005],temperature=23.0,laser=532.0,lb_break=1600.,hb_start=2600.,roi_hf_external = [3000. 3100.; 3800. 3900.],basetype="gcvspline",mmap_switch=true)

	The RAMEAU function in Spectra is mostly experimental. For a robust use of internal calibrations, please see the rampy.rameau() class built in Python which is more up to date.

INPUTS:

    paths: Tuple{Strings}, it contains the following strings:

		in_liste: the relative path of your liste of spectra (see the description of this liste below), e.g. "./liste_2012.csv"

		in_path = the relative path of the repertory where your raw spectra are stored, e.g. "./raw/"

		out_path = the relative path of the repertory where you want to output the corrected spectra, e.g. "./treated/" (THIS FOLDER MUST EXIST PRIOR TO RUN)

		fig_path= the relative path of the repertory for saving the figures, e.g. "./figures/"

		rws_save_file = the relative path of the csv file where the results will be stored, e.g. "rws_calculated.csv". If calibration is set to "yes", it outputs an array containing the Rws, the provided water content and the calculated water content in columns 1, 2 and 3, respectively. If calibration is set to anything else, it outputs the rws and the water content predicted with the provided prediction_coef

		rws_save_fig = the relative path of the figure for the calibration, e.g. "rws_calculated.jpg". Note: It only works when calibration is on "yes", and Type_of_calibration is set on "internal". Just set it to "trash.jpg" if you are not going to use it.

	input_properties: Tuple, it contains the delimiter of the columns in your spectra files, and the number of starting lines to skip. For instance, use ('\t',1) if your files have one header line and the columns are separated  by tabulations, or (',',0) if you use CSV files without header.

	switches: Tuple, it contains the different switches as (Type_of_calibration, calibration?, experimental?, temperature_laser_correction?).

		Type_of_calibration: should be set to "internal" or "external". The external mode requires standards, as described in Behrens et al. (2006) and Thomas et al. (2008). If you use the external mode, you can leave blanks for the other switches. For instance, enter ("external","","","").

		calibration? :  use only with the "internal" mode, this should be equal to "yes" or "no". Enter yes if you are setting up a new calibration, or "no" if you want to use a previously determined calibration coefficient with providing the later in the prediction_coef variable (see options).

		experimental? : use only with the "internal" mode, this is an experimental code. For now, only the "double" feature is advised. It allows using different smoothing spline coefficients for the water and silicate bands, delimited by the lb_break and hb_start variables (see below).

		temperature_laser_correction? : use only with the "internal" mode, this should be equal to "yes" or "no". This asks if you want to use the temperature-laser wavelength correction as done in Le Losq et al. (2012). If you use the double baseline mode, this correction is applied after removing the background under the water peak.

OPTIONS:

	prediction_coef: Array{Float64}, this array contains the calibration coefficient with its error bar. Those values will be used in the predictions, if you use the predictive mode (i.g., calibration switch is not set to "yes"). Default = [0.0059;0.0005].

	temperature: Float64, the temperature for the temperature-laser wavelength correction in Celsius. Default = 23.0.

	laser: Float64, the laser wavelength in nm for the temperature-laser wavelength correction. Default = 532.0.

	lb_break: Float64, for double baseline correction, the breaking point before which the software will consider the BIRs in the low frequency region. Default = 2010.0.

	hb_start: Float64, for double baseline correction, the breaking point after which the software will consider the BIRs in the high frequency region. Default = 1000.0.

  roi_hf_external: Array{Float64}, the roi for fitting the linear baseline in the external calibration mode. Default = [3000. 3100.; 3800. 3900.].

	basetype: String, the type of baseline you want to fit. Corresponds to the "basetype" parameter of the baseline function.  Default = "gcvspline".

    mmap_switch: false or true, this allows to switch on or off the memory mapping in the `readcsv`/`readdlm` functions that `rameau` uses. Default = "true".

OUTPUTS:

	Rameau does not provide any outputs directly in Julia, but saves everything in the folders you indicate in the variable "paths".

"""
function rameau(paths::Tuple,switches::Tuple;input_properties=('\t',0),prediction_coef=[0.0062;0.0005],temperature=23.0,laser=532.0,lb_break=1600.,hb_start=2600.,roi_hf_external = [3000. 3100.; 3800. 3900.],basetype="gcvspline",mmap_switch=true,lambda=10.0^12,p=0.01)

	# some function definition
	calibration_model(x, p) = p[1].*x
	baseline_lfmodel(x, p) = p[1].*x + p[2].*x.^2 + p[3].*x.^3

	if switches[1] == "internal"

		scale = 1 # no scaling, keeping that for history

		# reading the list of data to get the names, smoothing factors and roi
		liste=readcsv(paths[1], skipstart=1,use_mmap = mmap_switch)
		names = liste[:,1]
		water = Array{Float64}(liste[:,3])
		smo_lf = Array{Float64}(liste[:,4])
		smo_hf = Array{Float64}(liste[:,5])
		roi = ones(Int((size(liste,2)-5)/2),2,size(liste,1))
		for j = 1:size(liste,1)
			roi[:,1,j] = (liste[j,6:2:end])
			roi[:,2,j] = (liste[j,7:2:end])
		end

		rws = ones(size(liste,1),3) # output array of ratios

		for i = 1:size(liste,1) # starting a loop other the data list\

			# reading the spectra
			spectra = Array{Float64}(readdlm(string(paths[2],names[i]),input_properties[1],skipstart=input_properties[2],use_mmap = mmap_switch))

			# Making sure that the frequencies are in increasing order
			if spectra[end,1] < spectra[1,1]
				spectra = flipdim(spectra,1)
			end

			# Assigning things, primary treatments
			x = spectra[:,1]
			spectra[:,2] = spectra[:,2] - minimum(spectra[:,2]) + 0.01 #we avoid any 0 values by putting the smallest value to 0.01
			y = spectra[:,2]./maximum(spectra[:,2]).*scale # intensity normalisation
			ratio_bkg = minimum(y)/maximum(y) # to keep a record of the ratio of maximum signal intensity over minimum background intensity

			#### PRELIMINARY STEP: FIRST WE GRAB THE GOOD SIGNAL IN THE ROI
			interest_index::Array{Int64} = find(roi[1,1,i] .<= x[:,1] .<= roi[1,2,i])
			if size(roi)[1] > 1
				for j = 2:size(roi)[1]
					interest_index = vcat(interest_index,  find(roi[j,1,i] .<= x[:,1] .<= roi[j,2,i]))
				end
			end
			interest_x = x[interest_index,1]
			interest_y = y[interest_index,1]

			if switches[3] == "double" # if user asks for using two different smoothing factors for the silicate and water regions

				if switches[4] == "yes"

					# If the Long correction is asked, then it will use the two smoothing spline factors and perform a "traditional" internal baseline fit with gcvspline
					x, y_long, ~ = tlcorrection([x[:] y[:]],temperature,laser) # Long correction
					y_long=y_long./maximum(y_long).*scale

					lb = roi[:,1,i]
					hb = roi[:,2,i]

					roi_hf =  [lb[hb.>hb_start] hb[hb.>hb_start]]
					roi_lf =  [lb[hb.<lb_break] hb[hb.<lb_break]]

					y_hf = y_long[x.>hb_start]
					y_lf = y_long[x.<lb_break]

					# A first baseline in signals between ~1300 and ~ 2000 cm-1
					y_calc_hf, baseline_hf = baseline(x,y_long,roi_hf,basetype,p=smo_hf[i])
					y_calc_lf, baseline_lf = baseline(x,y_long,roi_lf,basetype,p=smo_lf[i])

					# putting back the signals together
					y_calc2 = [y_calc_lf[x.<=lb_break];y_calc_hf[x.>lb_break]]
					bas2 = [baseline_lf[x.<=lb_break];baseline_hf[x.>lb_break]]

					# making a nice figure
					figure(figsize=(20,20))
					suptitle("LL2012 method, sp. $(names[i])")

					subplot(3,2,(1,2))
					plot(x,y,"black",label="Raw sp.")
					plot(x,y_long,"blue",label="Long corr. sp.")
					scatter(interest_x,y_long[interest_index,1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x,bas2,"red",label="Baseline")
					legend(loc="best",fancybox="true")

					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,3)
					plot(x[x.<1500],y_long[x.<1500],"blue",label="Long corr. sp.")
					scatter(interest_x[interest_x.<1500],y_long[interest_index[interest_x.<1500],1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x[x.<1500],bas2[x.<1500],"red",label="Baseline")
					xlim(0,1500)
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,4)
					plot(x[x.>2800],y_long[x.>2800],"blue",label="Long corr. sp.")
					scatter(interest_x[interest_x.>2800],y_long[interest_index[interest_x.>2800],1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x[x.>2800],bas2[x.>2800],"red",label="Baseline")
					xlim(2600,4000)
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,(5,6))
					plot(x,y_calc2,"cyan",label="Final sp.")
					xlabel(L"Raman shift, cm$^{-1}$")
					ylabel("Intensity, area normalised")
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					tight_layout()

				else
					# ALSO FULLY EXPERIMENTAL!!!! IT DOES NOT WORK!
					y_hf = y[x.>hb_start]
					y_lf = y[x.<lb_break]

					# A first linear baseline in signals between ~1300 and ~ 2000 cm-1
					y_calc_hf, baseline_hf = baseline(x,y,[2600. 2900.;3775. 3900.],"poly",p=3.0)

				    #A first linear baseline in signals between ~1300 and ~ 2000 cm-1
				   	y_calc1_part1, baseline1 = baseline(x,y,[1300 1800.],"poly",p=1.0)

					# we get a mean signal for background below 1300 cm-1
					X_low_lf = x[y .== minimum(y[100 .<x.<1400])]
					b_low_lf = mean(y[X_low_lf[1]-10 .< x .< X_low_lf[1]+10])

					baseline1[x.<1250.] = b_low_lf
					y_calc1_part1 = y - baseline1

					bas2 = [baseline1[x.<lb_break];baseline_hf[x.>lb_break]]
					y_calc2 = [y_calc1_part1[x.<lb_break];y_calc_hf[x.>lb_break]]

					figure()
					plot(x,y,color="black")
					plot(x,baseline_hf,color="red")
					plot(x,y_calc2,color="blue")
					tight_layout()
				end

			else # only a single baseline treatment is asked

				# we test if the Long correction is asked, and react accordingly for baseline substraction
				if switches[4] == "yes"
					x, y_long, ~ = tlcorrection([x[:] y[:]],temperature,laser) # Long correction
					y_long=y_long./maximum(y_long).*scale

					if basetype == "arPLS" || basetype == "whittaker"
						y_calc2, bas2 = baseline(x,y_long,roi[:,:,i],basetype,lambda = lambda, p = p)
					else
						y_calc2, bas2 = baseline(x,y_long,roi[:,:,i],basetype,p=smo_lf[i])
					end

					y_calc2 = y_calc2[:,1]./trapz(x[:],y_calc2[:,1]).*(scale./10) # area normalisation

					figure(figsize=(20,20))
					suptitle("LL2012 method, sp. $(names[i])")

					subplot(3,2,(1,2))
					plot(x,y,"black",label="Raw sp.")
					plot(x,y_long,"blue",label="Long corr. sp.")
					scatter(interest_x,y_long[interest_index,1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x,bas2,"red",label="Baseline")
					legend(loc="best",fancybox="true")

					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,3)
					plot(x[x.<1500],y_long[x.<1500],"blue",label="Long corr. sp.")
					scatter(interest_x[interest_x.<1500],y_long[interest_index[interest_x.<1500],1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x[x.<1500],bas2[x.<1500],"red",label="Baseline")
					xlim(0,1500)
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,4)
					plot(x[x.>2800],y_long[x.>2800],"blue",label="Long corr. sp.")
					scatter(interest_x[interest_x.>2800],y_long[interest_index[interest_x.>2800],1],s=10.,color="red",label="ROI") # the ROI signals after first baseline and long correction
					plot(x[x.>2800],bas2[x.>2800],"red",label="Baseline")
					xlim(2600,4000)
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					subplot(3,2,(5,6))
					plot(x,y_calc2,"cyan",label="Final sp.")
					xlabel(L"Raman shift, cm$^{-1}$")
					ylabel("Intensity, area normalised")
					legend(loc="best",fancybox="true")
					xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
					ylabel("Intensity,a. u.",fontsize=18,fontname="Arial")

					tight_layout()

				else
					if basetype == "arPLS" || basetype == "whittaker"
						y_calc2, bas2 = baseline(x,y_long,roi[:,:,i],basetype,lambda = lambda, p = p)
					else
						y_calc2, bas2 = baseline(x,y_long,roi[:,:,i],basetype,p=smo_lf[i])
					end

					y_calc2 = y_calc2[:,1]./trapz(x[:],y_calc2[:,1]).*scale # area normalisation

					figure()
					plot(x,y.*10,"black",label="Raw sp.")
					scatter(interest_x,interest_y.*10,s=10.,color="red",label="ROI")
					plot(x,bas2.*10,"red",label="Baseline")
					plot(x,y_calc2,"cyan",label="Final sp.")
					xlabel(L"Raman shift, cm$^{-1}$")
					ylabel("Intensity, area normalised")
					title("Baseline sub., NO Long corr.")
					legend(loc="best",fancybox="true")
					tight_layout()
				end

			end

			# saving the corrected spectrum
			writecsv(string(paths[3],names[i]),[x y_calc2 bas2])

			#Saving the generated figure (specified directory should exist)
			savefig(string(paths[4],"baseline_",names[i],".pdf"))
    		close()
			if switches[3] == "double"
				# calc=ulating the areas under silicate and water bands
				As = trapz(x[150 .< x .<1300],y_calc2[150 .< x .<1300])
				Aw = trapz(x[2800 .< x .<3800],y_calc2[2800 .< x .<3800])
			else
				# calc=ulating the areas under silicate and water bands
				As = trapz(x[150 .< x .<1300],y_calc2[150 .< x .<1300])
				Aw = trapz(x[2800 .< x .<3800],y_calc2[2800 .< x .<3800])
			end

			# recording them
			rws[i,1] = As;
			rws[i,2] = Aw;
			rws[i,3] = Aw./(As);
			println("Spectrum $(names[i]), the rws is $(rws[i,3])")
		end

		if switches[2] == "yes"
			fit = curve_fit(calibration_model, rws[:,3], water[:]./(100.0 -water[:]), [0.5])
			coef = fit.param
			sigma = estimate_errors(fit, 0.95)

			rws_calibration = collect(0:0.01:round(maximum(rws[:,3]),2))
			water_ratio_calibration = calibration_model(rws_calibration,coef)
			water_compare =  100.0 .*calibration_model(rws[:,3],coef)./(calibration_model(rws[:,3],coef)+1.0) # eq. 3 Le Losq et al. (2012)

			rmse_calibration = sqrt(1.0 ./(size(rws,1)-1.0).*sum((water_compare-water).^2))
			rmse_ratio_ws = rmse_calibration./100.0 .*(rmse_calibration./(100.0-rmse_calibration)+1.0)

			figure()
			scatter(rws[:,3],water[:]./(100.0 -water[:]))
			plot(rws_calibration,water_ratio_calibration,color="red",linewidth=2.0)
			plot(rws_calibration,water_ratio_calibration+rmse_ratio_ws,color="red",linestyle="--",linewidth=1.0)
			plot(rws_calibration,water_ratio_calibration-rmse_ratio_ws,color="red",linestyle="--",linewidth=1.0)
			plot(rws_calibration,water_ratio_calibration,color="red")
			xlim(0,maximum(rws_calibration)+1.0 ./4.0 .*maximum(rws_calibration))
			ylim(0,maximum(water_ratio_calibration)+1.0 ./4.0 .*maximum(water_ratio_calibration))

			xlabel(L"A$_{water}$/A$_{silicates}$, area ratio",fontsize=18,fontname="Arial")
			ylabel("Water/Glass, weight ratio",fontsize=18,fontname="Arial")
			annotate(L"R$_{ws}$=",xy=(0.3,0.9),xycoords="axes fraction",fontsize=18,fontname="Arial",horizontalalignment="center")
			annotate("$(round(coef[1],5)) +/- $(round(sigma[1],5))",xy=(0.3,0.8),xycoords="axes fraction",fontsize=18,fontname="Arial",horizontalalignment="center")
			annotate("Standard deviation\n= $(round(rmse_calibration,2)) wt%",xy=(0.7,0.3),xycoords="axes fraction",horizontalalignment="center",fontsize=18,fontname="Arial")
			tight_layout()
			savefig(paths[6])

			writecsv(string(paths[5]), ["spectrum" "product" "Rws" "Water input" "Water Raman";liste[:,1] liste[:,2] rws[:,3] water water_compare])
			#return [rws[:,3] water water_compare]
		else
			water_predicted = 100.0 .*(rws[:,3].*prediction_coef[1]./(rws[:,3].*prediction_coef[1]+1.0))
			water_predicted_high = 100.0 .*(rws[:,3].*sum(prediction_coef)./(rws[:,3].*sum(prediction_coef)+1.0))
			writecsv(string(paths[5]), ["spectrum" "product" "Rws" "Water Raman" "Error";liste[:,1] liste[:,2] rws[:,3] water_predicted water_predicted_high-water_predicted])
			#return [rws[:,3] water_predicted]
		end

	elseif switches[1] == "external"
		liste=readcsv(paths[1], skipstart=1,use_mmap = mmap_switch)

		reference = liste[:,1]
		# reference product names are in first column for record
		reference_density = liste[:,3]
		reference_water = liste[:,4]

		sample = liste[:,5]
		# sample product names are in column 6 for record
		sample_density = liste[:,7]

		water = ones(size(liste,1),1) # the array to record water content

		for i = 1:size(liste,1)

			#importing data
			reference_sp = readdlm(string(paths[2],reference[i]),input_properties[1],skipstart=input_properties[2],use_mmap = mmap_switch)
			sample_sp = readdlm(string(paths[2],sample[i]),input_properties[1],skipstart=input_properties[2],use_mmap = mmap_switch)

			if reference_sp[end,1] < reference_sp[1,1]
				reference_sp = flipdim(reference_sp,1)
			end

			if sample_sp[end,1] < sample_sp[1,1]
				sample_sp = flipdim(sample_sp,1)
			end

			# temperature - excitation line correction with the Hehlen formula
			x_ref, reference_sp_long, ~ = tlcorrection(reference_sp,temperature,laser,correction="hehlen",normalisation="no",density=reference_density[i])
			x_sp, sample_sp_long, ~ = tlcorrection(sample_sp,temperature,laser,correction="hehlen",normalisation="no",density=sample_density[i])

			# Linear baseline subtraction
			reference_sp_corr2, reference_baseline = baseline(x_ref,reference_sp_long,roi_hf_external,"poly",p=1.0)
			sample_sp_corr2, sample_baseline = baseline(x_sp,sample_sp_long,roi_hf_external,"poly",p=1.0)

			# Area calculation between 3100 and 3800 cm-1
			Area_reference = trapz(x_ref[roi_hf_external[1,2] .<x_ref[:,1].<roi_hf_external[2,1]],reference_sp_corr2[roi_hf_external[1,2] .<x_ref[:,1].<roi_hf_external[2,1]])
			Area_sample = trapz(x_sp[roi_hf_external[1,2] .<x_sp[:,1].<roi_hf_external[2,1]],sample_sp_corr2[roi_hf_external[1,2] .<x_sp[:,1].<roi_hf_external[2,1]])

			# With the intensity
			Intensity_reference = maximum(reference_sp_corr2[roi_hf_external[1,2] .<x_ref[:,1].<roi_hf_external[2,1]])
			Intensity_sample = maximum(sample_sp_corr2[roi_hf_external[1,2] .<x_ref[:,1].<roi_hf_external[2,1]])

			# Water calculation
			water[i] = (reference_water[i]./1.8*reference_density[i]) .*Area_sample./Area_reference # water is in mol/L
			#water[i] = (reference_water[i]./1.8*reference_density[i]) .*Intensity_sample./Intensity_reference # water is in mol/L

			water[i] = water[i] *1.8./sample_density[i] # water converted in wt%

			figure(figsize=(7.5,15))

			subplot(311)
			title("External calibration: $(round(water[i],2)) wt% water,\n sp. $(sample[i]) with ref. $(reference[i]) ")
			plot(reference_sp[:,1],reference_sp[:,2],color="black",label="Reference")
			plot(sample_sp[:,1],sample_sp[:,2],color="blue",label="Sample")
			xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
			legend(loc="best",frameon=false)

			subplot(312)
			plot(x_ref[:],reference_sp_long[:],color="black",label=L"T-$\nu$ corrected reference")
			plot(x_ref[:],reference_baseline,color="grey",label="Reference baseline")
			plot(x_sp[:],sample_sp_long[:],color="blue",label=L"T-$\nu$ corrected sample")
			plot(x_sp[:],sample_baseline,color="cyan",label="Sample baseline")
			xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
			ylabel("Normalized intensity, a. u.",fontsize=18,fontname="Arial")
			legend(loc="best",frameon=false)

			subplot(313)
			plot(x_ref,reference_sp_corr2,color="black",label="Ref. spectrum")
			plot(x_sp,sample_sp_corr2,color="blue",label="Sample spectrum")
			xlabel(L"Raman shift, cm$^{-1}$",fontsize=18,fontname="Arial")
			legend(loc="best",frameon=false)
			tight_layout()
			# saving the water content
			writecsv(string(paths[5]), ["Spectrum" "Sample Name" "Estimated water content";liste[:,5] liste[:,6] water])

			#Saving the generated figure (specified directory should exist)
			savefig(string(paths[4],"External_Std_",sample[i],".pdf"))

		end

	else # or error message
		error("The first switch indicates if you want to use an internal or external calibration mode. Please choose between both.")
	end
end


@inline function Base.getproperty(ϕ::GridDeformation, name::Symbol)
    if name === :u
        return getfield(ϕ, :u)
    elseif name === :nodes
        return getfield(ϕ, :nodes)
    elseif name === :knots
        Base.depwarn("the field name of `GridDeformation` has changed from `knots` to `nodes`.", :getproperty)
        return getfield(ϕ, :nodes)
    end
    error("field ", name, " is not available")
end

function GridDeformation(u::AbstractArray{FV,N},
                         sz::NTuple{N,L}) where {FV<:SVector,N,L<:Integer}
    Base.depwarn("`GridDeformation(u, size(fixed))` is deprecated, use `GridDeformation(u, axes(fixed))` instead.", :GridDeformation)
    return GridDeformation(u, map(Base.OneTo, sz))
end

# The above causes an ambiguity, resolve it
function GridDeformation(u::AbstractArray{FV,0}, nodes::Tuple{}) where FV<:SVector
    error("node tuple cannot be empty")
end

function GridDeformation(u, nodes::AbstractVector{<:Integer})
    Base.depwarn("`GridDeformation(u, [size(fixed)...])` is deprecated, pass the axes of `fixed` instead.", :GridDeformation)
    GridDeformation(u, map(Base.OneTo, (nodes...,)))
end

function tform2deformation(tform::AffineMap{M,V}, arraysize::DimsLike, gridsize) where {M,V}
    Base.depwarn("""
    `tform2deformation(tform, arraysize, gridsize)` is deprecated.
    Formerly, `tform` was defined so as to apply to `centered(img)`, which shifts
    the axes of `img` to put 0 at the midpoint of `img`.
    Now you should call this as `tform2deformation(tform, axs, gridsize)`, where
    `axs` represents the desired domain of `tform`.
    If you are transitioning old code, either use `axs = axes(fixed)` if `fixed`
    is already centered, or `axs = centeraxes(axes(fixed))` if not.
    """, :tform2deformation)
    axs = map((arraysize...,)) do sz
        halfsz = sz ÷ 2
        IdentityUnitRange(1-halfsz:sz-halfsz)
    end
    return tform2deformation(tform, axs, (gridsize...,))
end

import Base: getindex
@deprecate getindex(ϕ::GridDeformation{T,N,A}, xs::Vararg{Number,N}) where {T,N,A<:AbstractInterpolation} ϕ(xs...)

Base.@deprecate_binding eachknot eachnode
Base.@deprecate_binding knotgrid nodegrid


using DrWatson, DataFrames, Plots, Printf, Parameters

include(srcdir("load.jl"))
include(srcdir("binning.jl"))

function binnedgif(df)
    @unpack drug, phase, trial, gw, gh, videopath = df

    basepath = projectdir("plots", drug, string(trial), @sprintf "%dx%d" gh gw)
    mkpath(basepath)

    greenchannel = datadir(videopath) |> frames |> fs -> crop(fs, 400, 400, :) |> green
    greenbinned = coarse(greenchannel, gh, gw) |> rescale |> bin

    anim = @animate for t in 1:size(greenbinned, 3)
        title = @sprintf "Binned %s %s, Trial %d; Frame: %d" titlecase(phase) drug trial t
        heatmap(greenbinned[:,:,t] .- 1,
                title = title,
                titleloc = :left,
                clim = (0,1))
    end

    giffile = joinpath(basepath, phase * "-binned.gif")
    gif(anim, giffile, fps=10)
end

function coarsegif(df)
    @unpack drug, phase, trial, gw, gh, videopath = df

    basepath = projectdir("plots", drug, string(trial), @sprintf "%dx%d" gh gw)
    mkpath(basepath)

    greenchannel = datadir(videopath) |> frames |> fs -> crop(fs, 400, 400, :) |> green
    greengrid = coarse(greenchannel, gh, gw) |> rescale

    anim = @animate for t in 1:size(greengrid, 3)
        title = @sprintf "%s %s, Trial %d; Frame: %d" titlecase(phase) drug trial t
        heatmap(greengrid[:,:,t],
                title = title,
                titleloc = :left,
                clim = (0,1))
    end

    giffile = joinpath(basepath, phase * ".gif")
    gif(anim, giffile, fps=10)
end

function migif(df)
    simdata = first(unique(select(df, :drug, :phase, :trial, :gw, :gh, :nperms)))
    @unpack drug, phase, trial, gw, gh, nperms = simdata

    grid = @sprintf "%dx%d" gh gw
    basepath = projectdir("plots", drug, string(trial), grid, string(nperms))
    mkpath(basepath)
    mkpath(joinpath(basepath, "frames", "mi"))

    mis = sort(select(df, :lag, :mi), :lag)

    miframes = []
    anim = @animate for row in eachrow(mis)
        @unpack lag, mi = row

        title = if lag == 0
            @sprintf "Mutual Info %s %s" titlecase(phase) drug
        elseif lag == 1
            @sprintf "Mutual Info %s %s; Lagged %d Frame" titlecase(phase) drug lag
        else
            @sprintf "Mutual Info %s %s; Lagged %d Frames" titlecase(phase) drug lag
        end

        p = heatmap(linearize(mi),
                    title=title,
                    titleloc=:left,
                    xlabel="target cell",
                    ylabel="source cell",
                    clim=(0,1))
        push!(miframes, p)
        p
    end

    for (i, p) in enumerate(miframes)
        framename = @sprintf "%d-%s.png" (i-1) phase
        framefile = joinpath(basepath, "frames", "mi", framename)
        savefig(p, framefile)
    end

    giffile = joinpath(basepath, phase * "-mi.gif")
    gif(anim, giffile, fps=0.5)
end

function tegif(df)
    simdata = first(unique(select(df, :drug, :phase, :trial, :gw, :gh, :nperms)))
    @unpack drug, phase, trial, gw, gh, nperms = simdata

    grid = @sprintf "%dx%d" gh gw
    basepath = projectdir("plots", drug, string(trial), grid, string(nperms))
    mkpath(basepath)
    mkpath(joinpath(basepath, "frames", "te"))

    tes = sort(select(df, :k, :te), :k)

    teframes = []
    anim = @animate for row in eachrow(tes)
        @unpack k, te = row

        title = @sprintf "Transfer Entropy %s %s; k=%d" titlecase(phase) drug k

        p = heatmap(linearize(te),
                    title=title,
                    titleloc=:left,
                    xlabel="target cell",
                    ylabel="source cell",
                    clim=(0,1))
        push!(teframes, p)
        p
    end

    for (i, p) in enumerate(teframes)
        framename = @sprintf "%d-%s.png" i phase
        framefile = joinpath(basepath, "frames", "te", framename)
        savefig(p, framefile)
    end

    giffile = joinpath(basepath, phase * "-te.gif")
    gif(anim, giffile, fps=0.5)
end

function main()
    df = collect_results(datadir("info", "mi"))
    for group in groupby(df, [:drug, :trial, :phase, :gh, :gw])
        migif(group)
    end
    df = collect_results(datadir("info", "te"))
    for group in groupby(df, [:drug, :trial, :phase, :gh, :gw])
        tegif(group)
    end
    for group in eachrow(unique(select(df, :drug, :trial, :phase, :gh, :gw, :videopath)))
        coarsegif(group)
        binnedgif(group)
    end
end

@time main()


mutable struct MainState
    src::CodeInfo
    pc::Int
    stmt::Any
end

mutable struct GateState
    src::CodeInfo
    pc::Int
    stmt::Any
    name::Symbol
    cargs::Dict{Int, Int}
    cargnames::Vector{SubString{String}}
end

function YaoCompiler.compile(target::OpenQASMTarget, f, tt::Type, options::HardwareFreeOptions)
    v"2.0" ≤ target.version < v"3.0" || error("only QASM 2.x is supported")
    interp = YaoInterpreter(;target, options)
    mi = method_instance(f, tt)
    src = Core.Compiler.typeinf_ext_toplevel(interp, mi)
    # we need to remove code coverage effect to pass validate
    src = CompilerPluginTools.rm_code_coverage_effect(src)
    validate(target, src)

    if target.toplevel
        st = MainState(src, 0, nothing)
    else
        op = tt.parameters[2]
        op.parameters[1] <: GenericRoutine || error("OpenQASM gate target does not support callable object")
        name = routine_name(op)
        cargnames = retrieve_cargnames(op)
        cargs = extract_cargs_map(src)
        st = GateState(src, 0, nothing, name, cargs, cargnames)
    end
    return emit_qasm(target, src, RegInfo(target, src), st)
end

function push_stmt!(list::Vector, stmt)
    isnothing(stmt) && return list
    if stmt isa Vector
        append!(list, stmt)
    else
        push!(list, stmt)
    end
end

function emit_qasm(target::OpenQASMTarget, ci::CodeInfo, ri::RegInfo, st::MainState)
    cregs, qregs = emit_registers(ri)
    prog = []
    if target.include_qelib1
        push!(prog, Include(qasm_str("qelib1.inc")))
    end
    append!(prog, cregs)
    append!(prog, qregs)

    st.pc = 1
    while st.pc <= length(ci.code)
        push_stmt!(prog, emit_stmt(target, ci.code[st.pc], ri, st))
        st.pc += 1
    end

    return MainProgram(target.version, prog)
end

function emit_qasm(target::OpenQASMTarget, ci::CodeInfo, ri::RegInfo, st::GateState)
    qargs = emit_qargs(ri)
    cargs = qasm_id.(st.cargnames)
    decl = GateDecl(qasm_id(st.name), cargs, qargs)
    prog = []
    st.pc = 1
    while st.pc <= length(ci.code)
        push_stmt!(prog, emit_stmt(target, ci.code[st.pc], ri, st))
        st.pc += 1
    end
    return Gate(decl, prog)
end

qreg_name(k::Int) = string("qreg_", k)

function emit_registers(ri::RegInfo)
    cregs = []
    for (name, size) in ri.creg_size
        push!(cregs, RegDecl(kw_creg, qasm_id(name), qasm_int(size)))
    end

    qregs = []
    for (qreg, locs) in ri.qreg_to_locs
        qreg_id = qasm_id(qreg_name(qreg))
        push!(qregs, RegDecl(kw_qreg, qreg_id, qasm_int(length(locs))))
    end
    return cregs, qregs
end

function emit_stmt(target::OpenQASMTarget, stmt, ri::RegInfo, st)
    @switch stmt begin
        @case Expr(:invoke, _, GlobalRef(Intrinsics, :apply), _, gate, QuoteNode(locs))
            emit_gate(target, gate, locs, ri, st)
        @case Expr(:invoke, _, GlobalRef(Intrinsics, :apply), _, gate, QuoteNode(locs), QuoteNode(ctrl))
            emit_ctrl(target, gate, locs, ctrl, ri, st)
        @case Expr(:invoke, _, GlobalRef(Intrinsics, :measure), _, QuoteNode(locs))
            emit_measure(target, locs, ri, st)
        @case Expr(:invoke, _, GlobalRef(Intrinsics, :barrier), _, QuoteNode(locs))
            emit_barrier(target, locs, ri, st)
        @case GotoIfNot(SSAValue(id), dest)
            emit_goto_ifnot(target, id, dest, ri, st)
        @case _ # skip other statement
            nothing
    end
end

emit_qargs(ri::RegInfo) = [qasm_id(qreg_name(qreg)) for (qreg, _) in ri.qreg_to_locs]

function emit_exp(target::OpenQASMTarget, exp, ri::RegInfo, st)
    @switch exp begin
        @case ::Int
            qasm_int(exp)
        @case ::Irrational{:π}
            pi_token
        @case ::Real # convert other read number to f64
            qasm_f64(exp)
        @case SSAValue(id)
            emit_exp(target, st.src.code[id], ri, st)

        @case Expr(:invoke, mi, GlobalRef(_, :sin), arg) || Expr(:invoke, mi, &(QASM.sin), arg)
            Call(:sin, emit_exp(target, arg, ri, st))

        @case Expr(:invoke, mi, GlobalRef(_, :cos), arg) || Expr(:invoke, mi, &(QASM.cos), arg)
            Call(:cos, emit_exp(target, arg, ri, st))

        @case Expr(:invoke, mi, GlobalRef(_, :tan), arg) || Expr(:invoke, mi, &(QASM.tan), arg)
            Call(:tan, emit_exp(target, arg, ri, st))

        @case Expr(:invoke, mi, GlobalRef(_, :exp), arg) || Expr(:invoke, mi, &(QASM.exp), arg)
            Call(:exp, emit_exp(target, arg, ri, st))

        @case Expr(:invoke, mi, GlobalRef(_, :log), arg) || Expr(:invoke, mi, &(QASM.log), arg)
            Call(:ln, emit_exp(target, arg, ri, st))

        @case Expr(:invoke, mi, GlobalRef(_, :sqrt), arg) || Expr(:invoke, mi, &(QASM.sqrt), arg)
            Call(:sqrt, emit_exp(target, arg, ri, st))

        # unary
        @case Expr(:call, GlobalRef(mod, name), x)
            f = Core.Compiler.abstract_eval_global(mod, name)::Const
            sym = qasm_compatible_intrinsics[f.val]
            token = Token{:reserved}(sym)
            if sym == "-"
                Neg(emit_exp(target, x, ri, st))
            else
                Call(token, emit_exp(target, x, ri, st))
            end
        # binop
        @case Expr(:call, GlobalRef(mod, name), lhs, rhs)
            f = Core.Compiler.abstract_eval_global(mod, name)::Const
            token = Token{:reserved}(qasm_compatible_intrinsics[f.val])
            (emit_exp(target, lhs, ri, st), token, emit_exp(target, rhs, ri, st))
        # get k-th carg
        @case Expr(:call, GlobalRef(&Base, :getfield), &(SSAValue(1)), k::Int, true)
            Token{:id}(st.cargnames[k])
        @case _
            error("unknown expression: $exp")
    end
end

function emit_gate(target::OpenQASMTarget, @nospecialize(gate), locs::Locations, ri::RegInfo, st)
    qargs = [ri[loc] for loc in locs]
    if target.inline_intrinsics
        emit_gate_inline(target, gate, qargs, ri, st)
    else
        emit_gate_noinline(target, gate, qargs, ri, st)
    end
end

function emit_gate_noinline(target::OpenQASMTarget, @nospecialize(gate), qargs, ri::RegInfo, st)
    @switch gate begin
        @case QuoteNode(shift(θ))
            cargs = Any[emit_exp(target, θ, ri, st)]
            Instruction("shift", cargs, qargs)
        @case QuoteNode(Rx(θ))
            cargs = Any[emit_exp(target, θ, ri, st)]
            Instruction("rx", cargs, qargs)
        @case QuoteNode(Ry(θ))
            cargs = Any[emit_exp(target, θ, ri, st)]
            Instruction("ry", cargs, qargs)
        @case QuoteNode(Rz(θ))
            cargs = Any[emit_exp(target, θ, ri, st)]
            Instruction("rz", cargs, qargs)

        @case QuoteNode(val::IntrinsicRoutine)
            name = qasm_gate_name(typeof(val))
            Instruction(name, [], qargs)
        @case QuoteNode(val::Operation)
            name = string(routine_name(val))
            cargs = [emit_exp(target, x, ri, st) for x in val.args]
            Instruction(name, cargs, qargs)
        @case SSAValue(id) # parametric intrinsic gate
            emit_gate_parametric(target, id, qargs, ri, st)
        @case _
            error("unknown gate value: $gate")
    end
end

function emit_gate_inline(target::OpenQASMTarget, @nospecialize(gate), qargs, ri::RegInfo, st)
    @switch gate begin
        @case QuoteNode(shift(θ))
            intrinsic_qasm(typeof(shift), θ, qargs)
        @case QuoteNode(Rx(θ))
            intrinsic_qasm(typeof(Rx), θ, qargs)
        @case QuoteNode(Ry(θ))
            intrinsic_qasm(typeof(Ry), θ, qargs)
        @case QuoteNode(Rz(θ))
            intrinsic_qasm(typeof(Rz), θ, qargs)
        @case QuoteNode(val) # non parametric intrinsic
            intrinsic_qasm(typeof(val), qargs)
        @case SSAValue(id) # parametric intrinsic gate
            emit_gate_parametric(target, id, qargs, ri, st)
        @case _
            error("unknown gate value: $gate")
    end
end

function emit_gate_parametric(target::OpenQASMTarget, id, qargs, ri::RegInfo, st)
    target.toplevel && error("toplevel program cannot have non-constant gate parameter")
    stmt = st.src.code[id]

    if target.inline_intrinsics
        @switch stmt begin
            @case Expr(:new, gt::Type{<:IntrinsicRoutine}, theta)
                carg = emit_exp(target, theta, ri, st)
                return intrinsic_qasm(gt, carg, qargs)
            @case _
                error("expect gate definition, got $stmt")
        end
    else
        @switch stmt begin
            @case Expr(:new, gt::Type{<:IntrinsicRoutine}, theta)
                name = qasm_gate_name(gt)
            @case _
                error("expect gate definition, got $stmt")
        end
        carg = emit_exp(target, theta, ri, st)
        return Instruction(name, Any[carg], qargs)
    end
end

function emit_ctrl(::OpenQASMTarget, @nospecialize(gate), locs::Locations, ctrl::CtrlLocations, ri::RegInfo, st)
    @switch gate begin
        @case QuoteNode(&X)
            if length(ctrl) == 1 && length(locs) == 1
                return CXGate(ri[ctrl], ri[locs])
            elseif length(ctrl) == 2 && length(locs) == 1
                error(
                    "ccx is not valid intrinsic QASM instruction. posssible fix:\n",
                    "1. include a qasm stdlib e.g qelib<version>.inc and use ccx gate from it\n",
                    "2. define your own ccx gate via @device using CX and single qubit gate",
                )
            end
        @case _
            error("control statement is not compatible with QASM")
    end
end

function emit_measure(::OpenQASMTarget, locs::Locations, ri::RegInfo, st)
    cname = string(ri.ssa_creg_map[st.pc])
    return [Measure(ri[loc], Bit(cname, k-1)) for (k, loc) in enumerate(locs)]
end

function emit_barrier(::OpenQASMTarget, locs::Locations, ri::RegInfo, st)
    qargs = []; args = Dict{Int, Vector{Int}}()

    # scan location addrs to remove extra
    # index on qreg
    for loc in locs
        plain_loc = plain(loc)
        qreg = ri.locs_to_qreg[plain_loc]
        addr = ri.locs_to_addr[plain_loc]
        addrs = get!(args, qreg, Int[])
        push!(addrs, addr)
    end

    for (qreg, addrs) in args
        # do not index qreg explicitly if barrier size
        # is the same with register size
        if length(ri.qreg_to_locs[qreg]) == length(addrs)
            push!(qargs, Bit(qreg_name(qreg)))
        else
            for addr in addrs
                push!(qargs, Bit(qreg_name(qreg), addr))
            end
        end
    end
    return Barrier(qargs)
end

function emit_goto_ifnot(target::OpenQASMTarget, cond::Int, dest::Int, ri::RegInfo, st)
    @switch st.src.code[cond] begin
        @case Expr(:invoke, mi, GlobalRef(YaoCompiler, :measure_cmp), SSAValue(lhs), rhs::Int)
            creg, int = lhs, rhs
        @case Expr(:invoke, mi, GlobalRef(YaoCompiler, :measure_cmp), lhs::Int, SSAValue(rhs))
            int, creg = lhs, rhs
        @case _
            error("invalid statement $(st.src.code[cond])")
    end

    haskey(ri.ssa_creg_map, creg) || error("cannot have classical statement in toplevel QASM")
    cname = ri.ssa_creg_map[creg]
    stmt = move_to_next_quantum_statement(st)
    body = emit_stmt(target, stmt, ri, st)
    return IfStmt(qasm_id(cname), qasm_int(int), body)
end

function move_to_next_quantum_statement(st)
    ci = st.src
    pc′ = st.pc + 1
    local stmt
    while pc′ <= length(ci.code)
        stmt = ci.code[pc′]
        @switch stmt begin
            @case Expr(:invoke, _, GlobalRef(&Intrinsics, _), _...)
                break
            @case ::GotoNode
                pc′ = (stmt::Core.GotoNode).label
            @case _
                # NOTE:
                # we can allow some constant statements
                # but not control flows or other nodes
                error("unexpected statement inside control flow: $stmt")
        end
        pc′ += 1
    end

    st.pc = pc′
    st.stmt = stmt
    return stmt
end

function retrieve_cargnames(op)
    mi = method_instance(op.parameters[1].instance, op.parameters[2])
    return split(chop(mi.def.slot_syms), '\0')[2:end]
end

function extract_cargs_map(ci::CodeInfo)
    vars = findfirst(ci.code) do stmt
        @match stmt begin
            Expr(:call, GlobalRef(&Base, :getfield), Argument(3), QuoteNode(:args)) => true
            _ => false
        end
    end

    ssa_cargs = Dict{Int, Int}()
    isnothing(vars) && return ssa_cargs

    for (v, stmt) in enumerate(ci.code)
        @switch stmt begin
            @case Expr(:call, getfield, SSAValue(&vars), k, true)
                ssa_cargs[v] = k
            @case _
                continue
        end
    end
    return ssa_cargs
end


using Flux
using Random

mutable struct ImageDQNAgent_old{M, TN, O, LU, AP<:AbstractValuePolicy, Φ, ER<:AbstractImageReplay} <: AbstractAgent
    model::M
    target_network::TN
    opt::O
    lu::LU
    ap::AP
    er::ER
    batch_size::Int
    tn_counter_init::Int
    target_network_counter::Int
    wait_time::Int
    wait_time_counter::Int
    exp_wait_size::Int
    action::Int
    prev_s::Φ
end

ImageDQNAgent_old(model,
              target_network,
              image_replay,
              opt,
              lu,
              ap,
              size_buffer,
              batch_size,
              tn_counter_init,
              wait_time,
              exp_wait_size) =
    ImageDQNAgent_old(model,
                  target_network,
                  opt,
                  lu,
                  ap,
                  image_replay,
                  batch_size,
                  tn_counter_init,
                  tn_counter_init,
                  wait_time,
                  0,
                  exp_wait_size,
                  0,
                  zeros(Int, image_replay.hist))

function get_action(agent::ImageDQNAgent_old, s)

end

function _start!(agent::ImageDQNAgent_old,
                       env_s_tp1,
                       rng::AbstractRNG;)

    # Start an Episode
    agent.prev_s .= add!(agent.er, env_s_tp1)

    prev_s =
        cat(getindex(agent.er.image_buffer, agent.prev_s)./256f0;
            dims=4) |> gpu

    agent.action = sample(agent.ap,
                          cpu(agent.model(prev_s)),
                          rng)

    return agent.action
end

function _step!(agent::ImageDQNAgent_old,
                      env_s_tp1,
                      r,
                      terminal,
                      rng::AbstractRNG;)

    cur_s = add!(agent.er,
                 (agent.prev_s,
                  findfirst((a)->a==agent.action,
                            agent.ap.action_set),
                  env_s_tp1,
                  r,
                  terminal))

    update_params!(agent, rng)

    agent.prev_s .= cur_s

    prev_s =
        cat(getindex(agent.er.image_buffer, agent.prev_s)./256f0;
            dims=4) |> gpu

    agent.action = sample(agent.ap,
                          cpu(agent.model(prev_s)),
                          rng)

    return agent.action
end

function update_params!(agent::ImageDQNAgent_old, rng)

    if size(agent.er)[1] > agent.exp_wait_size
        agent.wait_time_counter -= 1
        if agent.wait_time_counter <= 0

            e = sample(agent.er,
                       agent.batch_size;
                       rng=rng)
            s = gpu(e.s)
            r = gpu(e.r)
            t = gpu(e.t)
            sp = gpu(e.sp)

            update!(agent.model,
                    agent.lu,
                    agent.opt,
                    s,
                    e.a,
                    sp,
                    r,
                    t,
                    agent.target_network)

            agent.wait_time_counter = agent.wait_time
        end
    end

    # Target network updates
    if !(agent.target_network isa Nothing)
        if agent.target_network_counter == 1
            agent.target_network_counter = agent.tn_counter_init
            for ps ∈ zip(params(agent.model),
                         params(agent.target_network))
                ps[2] .= ps[1]
            end
        else
            agent.target_network_counter -= 1
        end
    end

    return nothing
end


module BorgC

const borglib = joinpath(normpath(joinpath(dirname(Base.source_path()), "..", "deps", "usr", "lib")),"borg")

typealias BORG_Problem Ptr{Void}
typealias BORG_Archive Ptr{Void}

function BORG_Problem_create(numberOfVariables::Int, numberOfObjectives::Int, numberOfConstraints::Int, fn::Function, userParams)
    c_fn = cfunction(fn, Void, (Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, Ptr{Void}))

    ret = ccall(
        (:BORG_Problem_create, borglib),
        BORG_Problem,
        (Int32, Int32, Int32, Ptr{Void}, Any),
        numberOfVariables, numberOfObjectives, numberOfConstraints, c_fn, userParams)
    return ret
end

function BORG_Problem_set_bounds(problem::BORG_Problem, index::Int, lowerBound::Float64, upperBound::Float64)
    ccall(
        (:BORG_Problem_set_bounds, borglib),
        Void,
        (BORG_Problem, Int32, Float64, Float64),
        problem, index-1, lowerBound, upperBound)
end

function BORG_Problem_set_epsilon(problem::BORG_Problem, index::Int, epsilon::Float64)
    ccall(
        (:BORG_Problem_set_epsilon, "borg"),
        Void,
        (BORG_Problem, Int32, Float64),
        problem, index-1, epsilon)
end

function BORG_Algorithm_run(problem::BORG_Problem,maxEvaluations::Int)
    ret = ccall(
        (:BORG_Algorithm_run, "borg"),
        BORG_Archive,
        (BORG_Problem, Int32),
        problem, maxEvaluations)
    return ret
end

function BORG_Archive_print(archive::BORG_Archive, fp)
    ccall(
        (:BORG_Archive_print, "borg"),
        Void,
        (BORG_Archive, Ptr{Void}),
        archive, C_NULL)
end

end # module


#phase :verify
function yaotoqobj(qc::Array{ChainBlock{N}}, device::String; nshots=1024) where N
    nslots = 1
    main_header = Dict("description"=>"Set of Experiments 1", "backend_name" => "$(device)")
    main_config = Dict("shots"=>nshots, "memory_slots"=>nslots, "init_qubits"=> true)
    experiments = collect(generate_experiment(i) for i in qc)
    data = Dict("qobj_id" => "foo", "schema_version"=>"1.0.0", "type"=>"QASM", "header"=>main_header, "config"=>main_config, "experiments"=>experiments)
    Qobj(data)
end

function generate_experiment(qc::ChainBlock)
    n_qubits = nqubits(qc)
    n_classical_reg = 2 # figure out this info
    nslots=1
    c_label = [["c", i] for i in 0:n_classical_reg-1]
    q_label = [["q", i] for i in 0:n_qubits-1]
    exp_inst = generate_inst(qc)
    exp_header = Dict("memory_slots"=>nslots, "n_qubits"=>n_qubits, "clbit_labels"=>c_label, "qubit_labels"=>q_label)
    experiment = Dict("header"=>exp_header, "config"=>Dict(), "instructions"=>exp_inst)
    return experiment
end

function generate_inst(qc::ChainBlock)
    inst = []
    for block in subblocks(qc)
        if block isa Union{PutBlock{N, M, ChainBlock{M}}, ChainBlock{O}} where {N, M, O}
            push!(inst, generate_inst(block)...)
        else
            push!(inst, generate_inst(block))
        end
    end
    return inst
end

function generate_inst(blk::Union{PutBlock, RepeatedBlock})
    gate = blk.content
    if gate isa HGate
        nm = "h"
    elseif gate isa I2Gate
        nm = "id"
    elseif gate isa TGate
        nm = "t"
    elseif gate isa SWAPGate
        nm = "swap"
    elseif gate isa Measure
        nm = "measure"
    end

    if nm == "measure"
        return Dict("name"=>"$(nm)", "qubits"=>[blk.locs...], "memory"=>[0])# memory:  List of memory slots in which to store the measurement results (mustbe the same length as qubits).
        #todo: generalize "memory" for all cases
    else
        return Dict("name"=>"$(nm)", "qubits"=>[blk.locs...])
    end
end

function generate_inst(blk::PutBlock{N, M, ChainBlock{M}}) where {N, M}
    data = []
    for gate in blk.content
        super_blk = gate.block
        if super_blk isa ZGate
            nm = "rz"
        elseif super_blk isa YGate
            nm = "ry"
        elseif super_blk isa XGate
            nm = "rx"
        end
        inst = Dict("name"=>"$(nm)", "qubits"=>[blk.locs...], "params"=>[gate.theta])
        push!(data, inst)
    end
    return Tuple(data)
end

function generate_inst(blk::ControlBlock)
    gate = blk.content
    if gate isa XGate
        nm = "cx"
    elseif gate isa YGate
        nm = "cy"
    elseif gate isa ZGate
        nm = "cz"
    elseif gate isa ShiftGate
        nm = "cu1"
        angle = gate.theta  #use Base.propertynames here
    elseif gate isa PhaseGate   #fix this
        nm = "s"
    end

    if nm == "cu1"
        return Dict("name"=>"$(nm)", "qubits"=>[blk.locs..., blk.ctrl_locs...], "params"=>[angle])
    else
        return Dict("name"=>"$(nm)", "qubits"=>[blk.locs..., blk.ctrl_locs...])
    end
end

# memory_slots: Number of classical memory slots used in this job.  Memory slotsare used to record the results of
#                 qubit measurements and read out at the end of an experiment. They cannot be used for feedback (those are the registers).

# experiments:  List  of  mexperiment  sequences  to  run.
#             Each  experiment  is  an experiment data structure.
#             Each experiment is run once in the order that they are  specified  in  this  list
#             and  then  the  sequence  is  repeated  until  the  specifiednumber of shots has been performed.

# header: User-defined structure that contains metadata on the job andis notusedby the backend.  The headerwill be passed through to the result data structure unchanged.
#         For example, this may contain a description of the full job and/orthe backend that the experiments were compiled for.

#         // --- QE Standard Gates ---

#         // Pauli gate: bit-flip
#         gate x a { u3(pi,0,pi) a; }
#         // Pauli gate: bit and phase flip
#         gate y a { u3(pi,pi/2,pi/2) a; }
#         // Pauli gate: phase flip
#         gate z a { u1(pi) a; }
#         // Clifford gate: Hadamard
#         gate h a { u2(0,pi) a; }
#         // Clifford gate: sqrt(Z) phase gate
#         gate s a { u1(pi/2) a; }
#         // Clifford gate: conjugate of sqrt(Z)
#         gate sdg a { u1(-pi/2) a; }
#         // C3 gate: sqrt(S) phase gate
#         gate t a { u1(pi/4) a; }
#         // C3 gate: conjugate of sqrt(S)
#         gate tdg a { u1(-pi/4) a; }

#         // --- Standard rotations ---
#         // Rotation around X-axis
#         gate rx(theta) a { u3(theta,-pi/2,pi/2) a; }
#         // rotation around Y-axis
#         gate ry(theta) a { u3(theta,0,0) a; }
#         // rotation around Z axis
#         gate rz(phi) a { u1(phi) a; }

#         // --- QE Standard User-Defined Gates  ---

#         // controlled-Phase
#         gate cz a,b { h b; cx a,b; h b; }
#         // controlled-Y
#         gate cy a,b { sdg b; cx a,b; s b; }
#         // controlled-H
#         gate ch a,b {
#         h b; sdg b;
#         cx a,b;
#         h b; t b;
#         cx a,b;
#         t b; h b; s b; x b; s a;
#         }
#         // C3 gate: Toffoli
#         gate ccx a,b,c
#         {
#           h c;
#           cx b,c; tdg c;
#           cx a,c; t c;
#           cx b,c; tdg c;
#           cx a,c; t b; t c; h c;
#           cx a,b; t a; tdg b;
#           cx a,b;
#         }
#         // controlled rz rotation
#         gate crz(lambda) a,b
#         {
#           u1(lambda/2) b;
#           cx a,b;
#           u1(-lambda/2) b;
#           cx a,b;
#         }
#         // controlled phase rotation
#         gate cu1(lambda) a,b
#         {
#           u1(lambda/2) a;
#           cx a,b;
#           u1(-lambda/2) b;
#           cx a,b;
#           u1(lambda/2) b;
#         }
#         // controlled-U
#         gate cu3(theta,phi,lambda) c, t
#         {
#           // implements controlled-U(theta,phi,lambda) with  target t and control c
#           u1((lambda-phi)/2) t;
#           cx c,t;
#           u3(-theta/2,0,-(phi+lambda)/2) t;
#           cx c,t;
#           u3(theta/2,phi,0) t;
#         }


register_color_library(:colorbrewer, ColorLibrary(Dict(:default => :sequential, :sequential => :Spectral, :divergent => :RdBu)))

register_gradient_colors(:Spectral, [
    RGB(158/255, 1/255, 66/255),
    RGB(213/255, 62/255, 79/255),
    RGB(244/255, 109/255, 67/255),
    RGB(253/255, 174/255, 97/255),
    RGB(254/255, 224/255, 139/255),
    RGB(255/255, 255/255, 191/255),
    RGB(230/255, 245/255, 152/255),
    RGB(171/255, 221/255, 164/255),
    RGB(102/255, 194/255, 165/255),
    RGB(50/255, 136/255, 189/255),
    RGB(94/255, 79/255, 162/255)], :colorbrewer)

register_gradient_colors(:RdYlGn, [
    RGB(165/255, 0/255, 38/255),
    RGB(215/255, 48/255, 39/255),
    RGB(244/255, 109/255, 67/255),
    RGB(253/255, 174/255, 97/255),
    RGB(254/255, 224/255, 139/255),
    RGB(255/255, 255/255, 191/255),
    RGB(217/255, 239/255, 139/255),
    RGB(166/255, 217/255, 106/255),
    RGB(102/255, 189/255, 99/255),
    RGB(26/255, 152/255, 80/255),
    RGB(0/255, 104/255, 55/255)], :colorbrewer)

register_gradient_colors(:RdBu, [
    RGB(103/255, 0/255, 31/255),
    RGB(178/255, 24/255, 43/255),
    RGB(214/255, 96/255, 77/255),
    RGB(244/255, 165/255, 130/255),
    RGB(253/255, 219/255, 199/255),
    RGB(247/255, 247/255, 247/255),
    RGB(209/255, 229/255, 240/255),
    RGB(146/255, 197/255, 222/255),
    RGB(67/255, 147/255, 195/255),
    RGB(33/255, 102/255, 172/255),
    RGB(5/255, 48/255, 97/255)], :colorbrewer)

register_gradient_colors(:PiYG, [
    RGB(142/255, 1/255, 82/255),
    RGB(197/255, 27/255, 125/255),
    RGB(222/255, 119/255, 174/255),
    RGB(241/255, 182/255, 218/255),
    RGB(253/255, 224/255, 239/255),
    RGB(247/255, 247/255, 247/255),
    RGB(230/255, 245/255, 208/255),
    RGB(184/255, 225/255, 134/255),
    RGB(127/255, 188/255, 65/255),
    RGB(77/255, 146/255, 33/255),
    RGB(39/255, 100/255, 25/255)], :colorbrewer)

register_gradient_colors(:PRGn, [
    RGB(64/255, 0/255, 75/255),
    RGB(118/255, 42/255, 131/255),
    RGB(153/255, 112/255, 171/255),
    RGB(194/255, 165/255, 207/255),
    RGB(231/255, 212/255, 232/255),
    RGB(247/255, 247/255, 247/255),
    RGB(217/255, 240/255, 211/255),
    RGB(166/255, 219/255, 160/255),
    RGB(90/255, 174/255, 97/255),
    RGB(27/255, 120/255, 55/255),
    RGB(0/255, 68/255, 27/255)], :colorbrewer)

register_gradient_colors(:RdYlBu, [
    RGB(165/255, 0/255, 38/255),
    RGB(215/255, 48/255, 39/255),
    RGB(244/255, 109/255, 67/255),
    RGB(253/255, 174/255, 97/255),
    RGB(254/255, 224/255, 144/255),
    RGB(255/255, 255/255, 191/255),
    RGB(224/255, 243/255, 248/255),
    RGB(171/255, 217/255, 233/255),
    RGB(116/255, 173/255, 209/255),
    RGB(69/255, 117/255, 180/255),
    RGB(49/255, 54/255, 149/255)], :colorbrewer)

register_gradient_colors(:BrBG, [
    RGB(84/255, 48/255, 5/255),
    RGB(140/255, 81/255, 10/255),
    RGB(191/255, 129/255, 45/255),
    RGB(223/255, 194/255, 125/255),
    RGB(246/255, 232/255, 195/255),
    RGB(245/255, 245/255, 245/255),
    RGB(199/255, 234/255, 229/255),
    RGB(128/255, 205/255, 193/255),
    RGB(53/255, 151/255, 143/255),
    RGB(1/255, 102/255, 94/255),
    RGB(0/255, 60/255, 48/255)], :colorbrewer)

register_gradient_colors(:RdGy, [
    RGB(103/255, 0/255, 31/255),
    RGB(178/255, 24/255, 43/255),
    RGB(214/255, 96/255, 77/255),
    RGB(244/255, 165/255, 130/255),
    RGB(253/255, 219/255, 199/255),
    RGB(255/255, 255/255, 255/255),
    RGB(224/255, 224/255, 224/255),
    RGB(186/255, 186/255, 186/255),
    RGB(135/255, 135/255, 135/255),
    RGB(77/255, 77/255, 77/255),
    RGB(26/255, 26/255, 26/255)], :colorbrewer)

# These are commented out as they constitute non-continuous gradients, and are thus used differently by Plots
# register_gradient_colors(:Set2, [
#     RGB(102/255, 194/255, 165/255),
#     RGB(252/255, 141/255, 98/255),
#     RGB(141/255, 160/255, 203/255),
#     RGB(231/255, 138/255, 195/255),
#     RGB(166/255, 216/255, 84/255),
#     RGB(255/255, 217/255, 47/255),
#     RGB(229/255, 196/255, 148/255),
#     RGB(179/255, 179/255, 179/255)], :colorbrewer)
#
# register_gradient_colors(:Accent, [
#     RGB(127/255, 201/255, 127/255),
#     RGB(190/255, 174/255, 212/255),
#     RGB(253/255, 192/255, 134/255),
#     RGB(255/255, 255/255, 153/255),
#     RGB(56/255, 108/255, 176/255),
#     RGB(240/255, 2/255, 127/255),
#     RGB(191/255, 91/255, 23/255),
#     RGB(102/255, 102/255, 102/255)], :colorbrewer)
#
# register_gradient_colors(:Set1, [
#     RGB(228/255, 26/255, 28/255),
#     RGB(55/255, 126/255, 184/255),
#     RGB(77/255, 175/255, 74/255),
#     RGB(152/255, 78/255, 163/255),
#     RGB(255/255, 127/255, 0/255),
#     RGB(255/255, 255/255, 51/255),
#     RGB(166/255, 86/255, 40/255),
#     RGB(247/255, 129/255, 191/255),
#     RGB(153/255, 153/255, 153/255)], :colorbrewer)
#
# register_gradient_colors(:Set3, [
#     RGB(141/255, 211/255, 199/255),
#     RGB(255/255, 255/255, 179/255),
#     RGB(190/255, 186/255, 218/255),
#     RGB(251/255, 128/255, 114/255),
#     RGB(128/255, 177/255, 211/255),
#     RGB(253/255, 180/255, 98/255),
#     RGB(179/255, 222/255, 105/255),
#     RGB(252/255, 205/255, 229/255),
#     RGB(217/255, 217/255, 217/255),
#     RGB(188/255, 128/255, 189/255),
#     RGB(204/255, 235/255, 197/255),
#     RGB(255/255, 237/255, 111/255)], :colorbrewer)
#
# register_gradient_colors(:Dark2, [
#     RGB(27/255, 158/255, 119/255),
#     RGB(217/255, 95/255, 2/255),
#     RGB(117/255, 112/255, 179/255),
#     RGB(231/255, 41/255, 138/255),
#     RGB(102/255, 166/255, 30/255),
#     RGB(230/255, 171/255, 2/255),
#     RGB(166/255, 118/255, 29/255),
#     RGB(102/255, 102/255, 102/255)], :colorbrewer)
#
# register_gradient_colors(:Paired, [
#     RGB(166/255, 206/255, 227/255),
#     RGB(31/255, 120/255, 180/255),
#     RGB(178/255, 223/255, 138/255),
#     RGB(51/255, 160/255, 44/255),
#     RGB(251/255, 154/255, 153/255),
#     RGB(227/255, 26/255, 28/255),
#     RGB(253/255, 191/255, 111/255),
#     RGB(255/255, 127/255, 0/255),
#     RGB(202/255, 178/255, 214/255),
#     RGB(106/255, 61/255, 154/255),
#     RGB(255/255, 255/255, 153/255),
#     RGB(177/255, 89/255, 40/255)], :colorbrewer)
#
# register_gradient_colors(:Pastel2, [
#     RGB(179/255, 226/255, 205/255),
#     RGB(253/255, 205/255, 172/255),
#     RGB(203/255, 213/255, 232/255),
#     RGB(244/255, 202/255, 228/255),
#     RGB(230/255, 245/255, 201/255),
#     RGB(255/255, 242/255, 174/255),
#     RGB(241/255, 226/255, 204/255),
#     RGB(204/255, 204/255, 204/255)], :colorbrewer)
#
# register_gradient_colors(:Pastel1, [
#     RGB(251/255, 180/255, 174/255),
#     RGB(179/255, 205/255, 227/255),
#     RGB(204/255, 235/255, 197/255),
#     RGB(222/255, 203/255, 228/255),
#     RGB(254/255, 217/255, 166/255),
#     RGB(255/255, 255/255, 204/255),
#     RGB(229/255, 216/255, 189/255),
#     RGB(253/255, 218/255, 236/255),
#     RGB(242/255, 242/255, 242/255)], :colorbrewer)

register_gradient_colors(:PuOr, [
    RGB(127/255, 59/255, 8/255),
    RGB(179/255, 88/255, 6/255),
    RGB(224/255, 130/255, 20/255),
    RGB(253/255, 184/255, 99/255),
    RGB(254/255, 224/255, 182/255),
    RGB(247/255, 247/255, 247/255),
    RGB(216/255, 218/255, 235/255),
    RGB(178/255, 171/255, 210/255),
    RGB(128/255, 115/255, 172/255),
    RGB(84/255, 39/255, 136/255),
    RGB(45/255, 0/255, 75/255)], :colorbrewer)

register_gradient_colors(:OrRd, [
    RGB(255/255, 247/255, 236/255),
    RGB(254/255, 232/255, 200/255),
    RGB(253/255, 212/255, 158/255),
    RGB(253/255, 187/255, 132/255),
    RGB(252/255, 141/255, 89/255),
    RGB(239/255, 101/255, 72/255),
    RGB(215/255, 48/255, 31/255),
    RGB(179/255, 0/255, 0/255),
    RGB(127/255, 0/255, 0/255)], :colorbrewer)

register_gradient_colors(:PuBu, [
    RGB(255/255, 247/255, 251/255),
    RGB(236/255, 231/255, 242/255),
    RGB(208/255, 209/255, 230/255),
    RGB(166/255, 189/255, 219/255),
    RGB(116/255, 169/255, 207/255),
    RGB(54/255, 144/255, 192/255),
    RGB(5/255, 112/255, 176/255),
    RGB(4/255, 90/255, 141/255),
    RGB(2/255, 56/255, 88/255)], :colorbrewer)

register_gradient_colors(:BuPu, [
    RGB(247/255, 252/255, 253/255),
    RGB(224/255, 236/255, 244/255),
    RGB(191/255, 211/255, 230/255),
    RGB(158/255, 188/255, 218/255),
    RGB(140/255, 150/255, 198/255),
    RGB(140/255, 107/255, 177/255),
    RGB(136/255, 65/255, 157/255),
    RGB(129/255, 15/255, 124/255),
    RGB(77/255, 0/255, 75/255)], :colorbrewer)

register_gradient_colors(:Oranges, [
    RGB(255/255, 245/255, 235/255),
    RGB(254/255, 230/255, 206/255),
    RGB(253/255, 208/255, 162/255),
    RGB(253/255, 174/255, 107/255),
    RGB(253/255, 141/255, 60/255),
    RGB(241/255, 105/255, 19/255),
    RGB(217/255, 72/255, 1/255),
    RGB(166/255, 54/255, 3/255),
    RGB(127/255, 39/255, 4/255)], :colorbrewer)

register_gradient_colors(:BuGn, [
    RGB(247/255, 252/255, 253/255),
    RGB(229/255, 245/255, 249/255),
    RGB(204/255, 236/255, 230/255),
    RGB(153/255, 216/255, 201/255),
    RGB(102/255, 194/255, 164/255),
    RGB(65/255, 174/255, 118/255),
    RGB(35/255, 139/255, 69/255),
    RGB(0/255, 109/255, 44/255),
    RGB(0/255, 68/255, 27/255)], :colorbrewer)

register_gradient_colors(:YlOrBr, [
    RGB(255/255, 255/255, 229/255),
    RGB(255/255, 247/255, 188/255),
    RGB(254/255, 227/255, 145/255),
    RGB(254/255, 196/255, 79/255),
    RGB(254/255, 153/255, 41/255),
    RGB(236/255, 112/255, 20/255),
    RGB(204/255, 76/255, 2/255),
    RGB(153/255, 52/255, 4/255),
    RGB(102/255, 37/255, 6/255)], :colorbrewer)

register_gradient_colors(:YlGn, [
    RGB(255/255, 255/255, 229/255),
    RGB(247/255, 252/255, 185/255),
    RGB(217/255, 240/255, 163/255),
    RGB(173/255, 221/255, 142/255),
    RGB(120/255, 198/255, 121/255),
    RGB(65/255, 171/255, 93/255),
    RGB(35/255, 132/255, 67/255),
    RGB(0/255, 104/255, 55/255),
    RGB(0/255, 69/255, 41/255)], :colorbrewer)

register_gradient_colors(:Reds, [
    RGB(255/255, 245/255, 240/255),
    RGB(254/255, 224/255, 210/255),
    RGB(252/255, 187/255, 161/255),
    RGB(252/255, 146/255, 114/255),
    RGB(251/255, 106/255, 74/255),
    RGB(239/255, 59/255, 44/255),
    RGB(203/255, 24/255, 29/255),
    RGB(165/255, 15/255, 21/255),
    RGB(103/255, 0/255, 13/255)], :colorbrewer)

register_gradient_colors(:RdPu, [
    RGB(255/255, 247/255, 243/255),
    RGB(253/255, 224/255, 221/255),
    RGB(252/255, 197/255, 192/255),
    RGB(250/255, 159/255, 181/255),
    RGB(247/255, 104/255, 161/255),
    RGB(221/255, 52/255, 151/255),
    RGB(174/255, 1/255, 126/255),
    RGB(122/255, 1/255, 119/255),
    RGB(73/255, 0/255, 106/255)], :colorbrewer)

register_gradient_colors(:Greens, [
    RGB(247/255, 252/255, 245/255),
    RGB(229/255, 245/255, 224/255),
    RGB(199/255, 233/255, 192/255),
    RGB(161/255, 217/255, 155/255),
    RGB(116/255, 196/255, 118/255),
    RGB(65/255, 171/255, 93/255),
    RGB(35/255, 139/255, 69/255),
    RGB(0/255, 109/255, 44/255),
    RGB(0/255, 68/255, 27/255)], :colorbrewer)

register_gradient_colors(:YlGnBu, [
    RGB(255/255, 255/255, 217/255),
    RGB(237/255, 248/255, 177/255),
    RGB(199/255, 233/255, 180/255),
    RGB(127/255, 205/255, 187/255),
    RGB(65/255, 182/255, 196/255),
    RGB(29/255, 145/255, 192/255),
    RGB(34/255, 94/255, 168/255),
    RGB(37/255, 52/255, 148/255),
    RGB(8/255, 29/255, 88/255)], :colorbrewer)

register_gradient_colors(:Purples, [
    RGB(252/255, 251/255, 253/255),
    RGB(239/255, 237/255, 245/255),
    RGB(218/255, 218/255, 235/255),
    RGB(188/255, 189/255, 220/255),
    RGB(158/255, 154/255, 200/255),
    RGB(128/255, 125/255, 186/255),
    RGB(106/255, 81/255, 163/255),
    RGB(84/255, 39/255, 143/255),
    RGB(63/255, 0/255, 125/255)], :colorbrewer)

register_gradient_colors(:GnBu, [
    RGB(247/255, 252/255, 240/255),
    RGB(224/255, 243/255, 219/255),
    RGB(204/255, 235/255, 197/255),
    RGB(168/255, 221/255, 181/255),
    RGB(123/255, 204/255, 196/255),
    RGB(78/255, 179/255, 211/255),
    RGB(43/255, 140/255, 190/255),
    RGB(8/255, 104/255, 172/255),
    RGB(8/255, 64/255, 129/255)], :colorbrewer)

register_gradient_colors(:Greys, [
    RGB(255/255, 255/255, 255/255),
    RGB(240/255, 240/255, 240/255),
    RGB(217/255, 217/255, 217/255),
    RGB(189/255, 189/255, 189/255),
    RGB(150/255, 150/255, 150/255),
    RGB(115/255, 115/255, 115/255),
    RGB(82/255, 82/255, 82/255),
    RGB(37/255, 37/255, 37/255),
    RGB(0/255, 0/255, 0/255)], :colorbrewer)

register_gradient_colors(:YlOrRd, [
    RGB(255/255, 255/255, 204/255),
    RGB(255/255, 237/255, 160/255),
    RGB(254/255, 217/255, 118/255),
    RGB(254/255, 178/255, 76/255),
    RGB(253/255, 141/255, 60/255),
    RGB(252/255, 78/255, 42/255),
    RGB(227/255, 26/255, 28/255),
    RGB(177/255, 0/255, 38/255)], :colorbrewer)

register_gradient_colors(:PuRd, [
    RGB(247/255, 244/255, 249/255),
    RGB(231/255, 225/255, 239/255),
    RGB(212/255, 185/255, 218/255),
    RGB(201/255, 148/255, 199/255),
    RGB(223/255, 101/255, 176/255),
    RGB(231/255, 41/255, 138/255),
    RGB(206/255, 18/255, 86/255),
    RGB(152/255, 0/255, 67/255),
    RGB(103/255, 0/255, 31/255)], :colorbrewer)

register_gradient_colors(:Blues, [
    RGB(247/255, 251/255, 255/255),
    RGB(222/255, 235/255, 247/255),
    RGB(198/255, 219/255, 239/255),
    RGB(158/255, 202/255, 225/255),
    RGB(107/255, 174/255, 214/255),
    RGB(66/255, 146/255, 198/255),
    RGB(33/255, 113/255, 181/255),
    RGB(8/255, 81/255, 156/255),
    RGB(8/255, 48/255, 107/255)], :colorbrewer)

register_gradient_colors(:PuBuGn, [
    RGB(255/255, 247/255, 251/255),
    RGB(236/255, 226/255, 240/255),
    RGB(208/255, 209/255, 230/255),
    RGB(166/255, 189/255, 219/255),
    RGB(103/255, 169/255, 207/255),
    RGB(54/255, 144/255, 192/255),
    RGB(2/255, 129/255, 138/255),
    RGB(1/255, 108/255, 89/255),
    RGB(1/255, 70/255, 54/255)], :colorbrewer)


digits_prec(prec::Integer) = floor(Int, prec * log(2) / log(10))

Base.show(io::IO, x::Union{Arb, Acb}) = print(io, string_nice(x))
Base.show(io::IO, x::Arf) = print(io, string_decimal(x))

for ArbT in (Arf, Arb, Acb, Mag)
    arbf = Symbol(cprefix(ArbT), :_, :print)
    @eval begin
        function _string(x::$ArbT)
            Libc.flush_cstdio()
            Base.flush(stdout)
            original_stdout = stdout
            out_rd, out_wr = redirect_stdout()
            try
                ccall(@libarb($arbf), Cvoid, (Ref{$ArbT},), x)
                Libc.flush_cstdio()
            finally
                redirect_stdout(original_stdout)
                close(out_wr)
            end
            return read(out_rd, String)
        end
    end

    ArbT == Mag && continue # no mag_printd and mag_printn

    arbf = Symbol(cprefix(ArbT), :_printd)
    @eval begin
        function string_decimal(x::$ArbT, digits::Integer=digits_prec(precision(x)))
            Libc.flush_cstdio()
            Base.flush(stdout)
            original_stdout = stdout
            out_rd, out_wr = redirect_stdout()
            try
                ccall(@libarb($arbf), Cvoid, (Ref{$ArbT}, Clong), x, digits)
                Libc.flush_cstdio()
            finally
                redirect_stdout(original_stdout)
                close(out_wr)
            end
            return read(out_rd, String)
        end
    end

    ArbT == Arf && continue #no arf_printn

    arbf = Symbol(cprefix(ArbT), :_, :printn)
    @eval begin
        function string_nice(x::$ArbT, digits::Integer=digits_prec(precision(x)), flags::UInt=UInt(0))
            Libc.flush_cstdio()
            Base.flush(stdout)
            original_stdout = stdout
            out_rd, out_wr = redirect_stdout()
            try
                ccall(@libarb($arbf), Cvoid, (Ref{$ArbT}, Clong, Culong), x, digits, flags)
                Libc.flush_cstdio()
            finally
                redirect_stdout(original_stdout)
                close(out_wr)
            end
            return read(out_rd, String)
        end
    end
end

for ArbT in (Arf, Arb, Mag)
    jlf = :_load_string!
    arbf = Symbol(cprefix(ArbT), :_load_str)
    @eval begin
        function $(jlf)(x::$ArbT, str::AbstractString)
            res = ccall(@libarb($arbf), Cint, (Ref{$ArbT}, Cstring), x, str)
            iszero(res) || throw(ArgumentError("arblib could not load_str $str as "*$(string(ArbT))))
            return x
        end
    end

    jlf = :_dump_string
    arbf = Symbol(cprefix(ArbT), :_dump_str)
    @eval begin
        function $(jlf)(x::$ArbT)
            char_ptr = ccall(@libarb($arbf), Cstring, (Ref{$ArbT},), x)
            str = unsafe_string(char_ptr)
            ccall((:flint_free, libflint), Cvoid, (Cstring,), char_ptr)
            return str
        end
    end
end


const six_three_one_one_2plus_d_p_J = Dict(
"H" =>
" S 3
 1    118753.0892189              0.00000034
 2      5170.23631942             0.00001503
 3       340.31345127             0.00037853
 S   1
 1        33.8650000              1.00000000
 S   1
 1         5.0947900              1.00000000
 S   1
 1         1.1587900              1.00000000
 S   1
 1         0.3258400              1.00000000
 S   1
 1         0.1027410              1.00000000
 S   1
 1         0.0360000              1.00000000
 P   1
 1         0.7500000              1.00000000
",

"C" =>
" S   7
 1  16876193.1394200              0.00000006
 2    717812.0184378              0.00000265
 3     46416.99275025             0.00006807
 4      4563.2400000              0.00105552
 5       682.0240000              0.00866603
 6       154.9730000              0.04301032
 7         1.8277300              0.43340136
 S   1
 1        44.4553000              1.00000000
 S   1
 1        20.9642000              1.00000000
 S   1
 1        13.0290000              1.00000000
 S   1
 1         4.8033100              1.00000000
 S   1
 1         1.4593300              1.00000000
 S   1
 1         0.4834560              1.00000000
 S   1
 1         0.1455850              1.00000000
 S   1
 1         0.0438000              1.00000000
 P   4
 1       121.32953805             0.00075096
 2        20.9642000              0.01110197
 3         4.8033100              0.07021793
 4         1.4593300              0.23201981
 P   1
 1         0.4834560              1.00000000
 P   1
 1         0.1455850              1.00000000
 P   1
 1         0.0438000              1.00000000
 D   1
 1         0.6260000              1.00000000
",

"N" =>
" S   7
 1  23421322.3838400              0.00000006
 2    988674.6264273              0.00000262
 3     63798.90182836             0.00006752
 4      6293.4800000              0.00103995
 5       949.0440000              0.00840880
 6       218.7760000              0.04094490
 7         2.7202300              0.43311373
 S   1
 1        63.6916000              1.00000000
 S   1
 1        30.6331000              1.00000000
 S   1
 1        18.8282000              1.00000000
 S   1
 1         7.0261400              1.00000000
 S   1
 1         2.1120500              1.00000000
 S   1
 1         0.6840090              1.00000000
 S   1
 1         0.2008780              1.00000000
 S   1
 1         0.0639000              1.00000000
 P   4
 1       175.03570330             0.00074214
 2        30.6331000              0.01117529
 3         7.0261400              0.07350563
 4         2.1120500              0.24667585
 P   1
 1         0.6840090              1.00000000
 P   1
 1         0.2008780              1.00000000
 P   1
 1         0.0639000              1.00000000
 D   1
 1         0.9130000              1.00000000
",

"O" =>
" S   7
 1  31660948.4326500              0.00000005
 2   1341747.4801660              0.00000251
 3     86856.41933057             0.00006440
 4      8588.5000000              0.00098884
 5      1297.2300000              0.00798975
 6       299.2960000              0.03894177
 7         3.7400400              0.43596773
 S   1
 1        87.3771000              1.00000000
 S   1
 1        42.1175000              1.00000000
 S   1
 1        25.6789000              1.00000000
 S   1
 1         9.6283700              1.00000000
 S   1
 1         2.8533200              1.00000000
 S   1
 1         0.9056610              1.00000000
 S   1
 1         0.2556110              1.00000000
 S   1
 1         0.0845000              1.00000000
 P   4
 1       238.82507403             0.00071107
 2        42.1175000              0.01088929
 3         9.6283700              0.07471358
 4         2.8533200              0.25205556
 P   1
 1         0.9056610              1.00000000
 P   1
 1         0.2556110              1.00000000
 P   1
 1         0.0845000              1.00000000
 D   1
 1         1.2920000              1.00000000
"
)


module Octavian

using VectorizationBase, ArrayInterface, LoopVectorization

using VectorizationBase: align, AbstractStridedPointer, zstridedpointer,
    static_sizeof, lazymul, StridedPointer, gesp, pause, pick_vector_width, has_feature,
    num_cache_levels, cache_size, num_cores, num_cores, cache_inclusive, cache_linesize, ifelse
using LoopVectorization: maybestaticsize, matmul_params, preserve_buffer, CloseOpen
using ArrayInterface: OptionallyStaticUnitRange, size, strides, offsets, indices,
    static_length, static_first, static_last, axes, dense_dims, stride_rank

using Static: StaticInt, Zero, One, StaticBool, True, False, gt, eq, StaticFloat64,
    roundtostaticint, floortostaticint
using StrideArraysCore: MemoryBuffer

using ThreadingUtilities:
    _atomic_add!, _atomic_load, _atomic_store!,
    launch, wait, load, store!

export StaticInt
export matmul!
export matmul
export matmul_serial!
export matmul_serial

include("global_constants.jl")
include("types.jl")
include("integerdivision.jl")
include("memory_buffer.jl")
include("block_sizes.jl")
include("funcptrs.jl")
include("macrokernels.jl")
include("utils.jl")
include("matmul.jl")
include("complex_matmul.jl")

include("init.jl") # `Octavian.__init__()` is defined in this file

end # module Octavian