using ArcadeLearningEnvironment
using CGP
using Logging
using ArgParse
import Images

include("../graphing/graph_utils.jl")

CGP.Config.init("cfg/atari.yaml")

max_reward_dict = Dict()

function stop_playing_check(cur_reward::Float64, frame_step::Int64)
    global max_reward_dict

    if haskey(max_reward_dict, frame_step)
        print("[", frame_step, "] Comparing: ", cur_reward, " to ", max_reward_dict[frame_step])

        if max_reward_dict[frame_step] == 0
            println()
            return false
        end

        val = ((max_reward_dict[frame_step] - cur_reward) / max_reward_dict[frame_step])
        println(" -- ", val)

        if val > 0.5
            return true
        end
    end

    false
end

function score_velocity_score(reward_dict::Dict)
    score_vel = 0.0
    prev = 0.0
    step_scale = 1000

    for step in reward_dict
        if step[1] > 0 && step[2] != -Inf
            cur = step[2]

            diff = cur - prev
            score_vel += diff / step_scale

            prev = cur
        end
    end

    score_vel
end

function play_atari(c::Chromosome, id::String, seed::Int64;
                    render::Bool=false, folder::String=".", max_frames=18000,
                    step_size=200)

    score_dict = Dict()
    # Get the game with ID and seed
    game = Game(id, seed)

    # Reset the seed
    seed_reset = rand(0:100000)
    srand(seed)

    # Reset all parameters
    reward = 0.0
    frames = 0
    p_action = game.actions[1]
    outputs = zeros(Int64, c.nout)

    # Keep playing, while the game isn't over
    while ~game_over(game.ale)

        # Have the Chromosome process the current inputs
        output = process(c, get_rgb(game))

        # Log the move count
        outputs[indmax(output)] += 1

        # Do that action
        action = game.actions[indmax(output)]
        reward += act(game.ale, action)

        # Possibly repeat action randomly
        if rand() < 0.25
            reward += act(game.ale, action) # repeat action here for seeding
        end

        # If we are rendering the game, save the screen to a file
        if render
            screen = draw(game)
            filename = string(folder, "/", @sprintf("frame_%06d.png", frames))
            Images.save(filename, screen)
        end

        if (frames % step_size) == 0
            score_dict[frames] = reward

            if 0.5 * max_frames < frames && stop_playing_check(reward, frames)
                println("Exit due to early stopping.")
                break
            end
        end

        # Always log the frames
        frames += 1
        if frames > max_frames
            Logging.debug(string("Termination due to frame count on ", id))
            break
        end
    end

    # Close the game and reset the seed
    close!(game)
    srand(seed_reset)

    score_dict[frames] = reward

    vel_score = score_velocity_score(score_dict)

    println("Final score: ", reward)
    println("Final velocity: ", vel_score)

    global max_reward_dict

    if vel_score > score_velocity_score(max_reward_dict)
        max_reward_dict = score_dict
        println("Updated the local top fit!")
    end

    # Return the reward and the list of outputs
    reward, outputs, vel_score
end

# Gets the best Chromrosomes from a log file
function get_bests(logfile::String)
    bests = []
    for line in readlines(logfile)
        if contains(line, ":C:")
            genes = eval(parse(split(line, ":C: ")[2]))
            append!(bests, [genes])
        end
    end
    bests
end

# Gets the input/output parameters
function get_params(args::Dict)
    game = Game(args["id"], args["seed"])
    nin = 3 # r g b
    nout = length(game.actions)
    close!(game)
    nin, nout
end

# Renders a gene
function render_genes(genes::Array{Float64}, args::Dict;
                    ctype::DataType=CGPChromo, id::Int64=0)

    # Gets the Chromrosome
    nin, nout = get_params(args)
    chromo = ctype(genes, nin, nout)

    # Selects the folder
    folder = string("frames/", args["id"], "_", args["seed"], "_", id)
    mkpath(folder)

    # Plays the Atari with that individual
    reward, out_counts, fit_score = play_atari(chromo, args["id"], args["seed"];
                                        render=false, folder=folder,
                                        max_frames=args["frames"])

    # Logs that individual's performance
    println(@sprintf("R: %s %d %d %0.5f %d %d",
                     args["id"], args["seed"], id, reward,
                     sum([n.active for n in chromo.nodes]),
                     length(chromo.nodes)))

    Logging.info(string("Top individual scores: "))
    Logging.info(string("\tFitness Score: ", fit_score))
    Logging.info(string("\tGame Score: ", reward))

    println("Top individual scores: ")
    println("\tFitness Score: ", fit_score)
    println("\tGame Score: ", reward)

    # Copy our genes
    new_genes = deepcopy(chromo.genes)

    # Not sure what this all is about
    for o in 1:nout
        if out_counts[o] == 0.0
            # disable this section of the graph by forcing
            # the output to connect to an input node
            new_genes[nin+o] = 0.00001
        end
    end

    # Check how many active outputs we actually had
    active_outputs = out_counts .> 0

    # Take the new Chromosome with slight modification to draw
    chromo2 = ctype(new_genes, nin, nout)
    file =  string("graphs/", args["id"], "_", args["seed"], "_", id, ".pdf");
    chromo_draw(chromo2, file, active_outputs=active_outputs)
    # chromo_draw(chromo2)
end

# Parses all the command line arguments
function get_args()
    s = ArgParseSettings()

    @add_arg_table(
        s,
        "--seed", arg_type = Int, default = 0,
        "--log", arg_type = String, default = "atari.log",
        "--id", arg_type = String, default = "centipede",
        "--ea", arg_type = String, default = "oneplus",
        "--chromosome", arg_type = String, default = "CGPChromo",
        "--frames", arg_type = Int, default = 18000,
        "--render", action = :store_const, constant = true, default = false,
    )

    parse_args(CGP.Config.add_arg_settings!(s))
end

# We run this as a non-interactive experiment
if ~isinteractive()

    # Get the args
    args = get_args()

    # Configure the CGP experiment
    CGP.Config.init(Dict([k=>args[k] for k in setdiff(
        keys(args), ["seed", "log", "id", "ea", "chromosome"])]...))

    # Seed the random generator
    srand(args["seed"])

    # Set up logging
    Logging.configure(filename=string(args["id"], ".log"), level=INFO)

    # Get the inputs/outputs
    @everywhere nin, nout = get_params(args)

    # Select evolution schema
    ea = eval(parse(args["ea"]))

    # Select chromosome type
    ctype = eval(parse(args["chromosome"]))

    # Define the fitness function
    # We use [1] here because it selects the reward as the fitness
    # measure
    fit = x->play_atari(x, args["id"], args["seed"];
                            max_frames=args["frames"])[3]

    # Actually run the EA experiment
    maxfit, best = ea(nin, nout, fit;
                      seed=args["seed"], id=args["id"], ctype=ctype)

    # Log the maxfit individual
    Logging.info(@sprintf("E%0.6f", -maxfit))

    # If we are rendering, render this best Chromosome
    if args["render"]
        render_genes(best, args; ctype=ctype)
    end
end


function remove_tasks(gamma::Float64, LS::LoadingSequence, CTS::Constants)
    new_LS = deepcopy(LS)
    mod_gamma = Int(floor(CTS.P*gamma))
    if new_LS.order[mod_gamma+1].task.p == 0 && new_LS.order[mod_gamma+1].task.c == 0
        mod_gamma -= 1
    end
    for t = 1:mod_gamma
        if new_LS.order[end].task.p != 0 && new_LS.order[end].task.c != 0
            new_LS.len -= 1
            new_LS.tasks_left += 1
        end
        deleteat!(new_LS.filled_pos, findall(x->x==new_LS.order[length(new_LS.order)].task.p, new_LS.filled_pos))
        deleteat!(new_LS.loaded_cont, findall(x->x==new_LS.order[length(new_LS.order)].task.c, new_LS.loaded_cont))
        deleteat!(new_LS.order, length(new_LS.order))
    end
    return(new_LS)
end

function last_qc_moves(LS::LoadingSequence, CTS::Constants)
    QC_MOVES=Dict{Int, Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}}()
    min_q = (q=0, end_time=CTS.H, start_time=CTS.H, time=CTS.H)

    flag = Array{Int,1}()
    while Set(collect(1:CTS.Q)) != Set(flag)
        for t in reverse(LS.order)
            for q = 1:CTS.Q
                if t.qc == q && !(q in flag)
                    push!(flag, q)
                    if t.start_time + 2*t.task.t < min_q.end_time
                        min_q = (q=q, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time = t.task.t)
                        break
                    end
                end
            end
        end
    end

    flag = Array{Int,1}()
    while Set(collect(1:CTS.Q)) != Set(flag)
        for q = 1:CTS.Q
            l=Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}()
            for t in reverse(LS.order)
                if t.qc == q
                    if t.start_time > min_q.end_time
                        push!(l, (bay=t.task.b, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time=t.task.t))
                    else
                        push!(l, (bay=t.task.b, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time=t.task.t))
                        push!(flag, q)
                        break
                    end
                end
            end
            QC_MOVES[q]=reverse(l)
        end
    end
    return(min_q, QC_MOVES)
end

function get_current_state(LS::LoadingSequence, CTS::Constants)
    TIME = init_timer(CTS)
    TIME.available_cranes = Array{Int, 1}()
    QC = init_qc(CTS)
    min_q, QC_MOVES = last_qc_moves(LS, CTS)
    QC_TRAVEL = get_qc_travel(QC_MOVES, CTS)

    TIME.period = min_q.end_time
    TIME.available_cranes = Array{Int, 1}()

    for q = 1:CTS.Q
        QC[q].task_buffer = Array{LTask, 1}()
        #idle crane
        if QC_MOVES[q][end].end_time == TIME.period && length(QC_MOVES[q]) == 1
            QC[q].status = "idle"
            QC[q].time_left = 0
            QC[q].current_bay = QC_MOVES[q][end].bay
            QC[q].next_bay = QC_MOVES[q][end].bay
            push!(TIME.available_cranes, q)
        #loading crane
        elseif TIME.period >= QC_MOVES[q][end].start_time
            QC[q].status = "loading"
            QC[q].time_left = QC_MOVES[q][end].end_time - TIME.period
            QC[q].current_bay = QC_MOVES[q][end].bay
            QC[q].next_bay = QC_MOVES[q][end].bay
        #moving or waiting to move/load crane
        elseif QC_MOVES[q][end-1].end_time <= TIME.period && TIME.period < QC_MOVES[q][end].start_time
            QC[q].next_bay = QC_MOVES[q][end].bay
            push!(QC[q].task_buffer, LTask(0, QC_MOVES[q][end].bay, 0, QC_MOVES[q][end].time))
            if QC_MOVES[q][end-1].bay == QC_MOVES[q][end].bay
                QC[q].status = "waiting to load"
                QC[q].time_left = QC_MOVES[q][end].start_time - TIME.period
                QC[q].current_bay = QC_MOVES[q][end].bay
            elseif QC_TRAVEL[q][1].start_time <= TIME.period
                QC[q].status = "moving"
                QC[q].time_left = QC_TRAVEL[q][1].end_time - TIME.period
                QC[q].current_bay = QC_MOVES[q][end].bay - QC[q].time_left/CTS.tt
            else
                QC[q].status = "waiting to move"
                QC[q].time_left = QC_TRAVEL[q][1].start_time - TIME.period
                QC[q].current_bay = QC_MOVES[q][end-1].bay
            end
        #still loading the previous task
        elseif  QC_MOVES[q][end-1].start_time <= TIME.period
            if length(QC_MOVES[q]) == 2
                QC[q].status = "loading"
                QC[q].time_left = QC_MOVES[q][end-1].end_time - TIME.period
                QC[q].current_bay = QC_MOVES[q][end-1].bay
                QC[q].next_bay = QC_MOVES[q][end].bay
                push!(QC[q].task_buffer, LTask(0, QC_MOVES[q][end].bay, 0, QC_MOVES[q][end].time))
            end
        else
            for i = 1:length(QC_MOVES[q])-1
                #moving or waiting to move/load crane
                if QC_MOVES[q][i].end_time <= TIME.period && TIME.period < QC_MOVES[q][i+1].start_time
                    QC[q].next_bay = QC_MOVES[q][i+1].bay
                    for i = 1:length(QC_MOVES[q])-1
                        push!(QC[q].task_buffer, LTask(0, QC_MOVES[q][i+1].bay, 0, QC_MOVES[q][i+1].time))
                    end
                    if QC_MOVES[q][i].bay == QC_MOVES[q][i+1].bay
                        QC[q].status = "waiting to load"
                        QC[q].time_left = QC_MOVES[q][i+1].start_time - TIME.period
                        QC[q].current_bay = QC_MOVES[q][i+1].bay
                    elseif QC_TRAVEL[q][1].start_time <= TIME.period
                        QC[q].status = "moving"
                        QC[q].time_left = QC_TRAVEL[q][1].end_time - TIME.period
                        QC[q].current_bay = QC_MOVES[q][i+1].bay - QC[q].time_left/CTS.tt
                    else
                        QC[q].status = "waiting to move"
                        QC[q].time_left = QC_TRAVEL[q][1].start_time - TIME.period
                        QC[q].current_bay = QC_MOVES[q][i].bay
                    end
                #still loading the previous task
                elseif  QC_MOVES[q][i].start_time <= TIME.period
                    QC[q].status = "loading"
                    QC[q].time_left = QC_MOVES[q][i].end_time - TIME.period
                    QC[q].current_bay = QC_MOVES[q][i].bay
                    QC[q].next_bay = QC_MOVES[q][i].bay
                    for i = 1:length(QC_MOVES[q])-1
                        push!(QC[q].task_buffer, LTask(0, QC_MOVES[q][i+1].bay, 0, QC_MOVES[q][i+1].time))
                    end
                end
            end
        end
    end
    return(true, TIME, QC)
end

function get_single_moves(QC_MOVES::Dict{Int, Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}}, CTS::Constants)
    single_moves = Array{NamedTuple{(:qc, :bay, :start_time, :time),Tuple{Int64, Int64, Int64, Int64}},1}()
    check = false
    for q = 1:CTS.Q
        for t = 2:length(QC_MOVES[q])-1
            if QC_MOVES[q][t-1].bay < QC_MOVES[q][t].bay && QC_MOVES[q][t].bay > QC_MOVES[q][t+1].bay
                if QC_MOVES[q][t].time != 0
                    push!(single_moves, (qc=q, bay=QC_MOVES[q][t].bay, start_time = QC_MOVES[q][t].start_time, time = QC_MOVES[q][t].time))
                    check = true
                end
            elseif QC_MOVES[q][t-1].bay > QC_MOVES[q][t].bay && QC_MOVES[q][t].bay < QC_MOVES[q][t+1].bay
                if QC_MOVES[q][t].time != 0
                    push!(single_moves, (qc=q, bay=QC_MOVES[q][t].bay, start_time = QC_MOVES[q][t].start_time, time = QC_MOVES[q][t].time))
                    check = true
                end
            end
        end

        if QC_MOVES[q][end-1].bay != QC_MOVES[q][end-2].bay && QC_MOVES[q][end-1].time != 0
            push!(single_moves, (qc=q, bay=QC_MOVES[q][end-1].bay, start_time = QC_MOVES[q][end-1].start_time, time = QC_MOVES[q][end-1].time))
            check = true
        end
        if QC_MOVES[q][1].bay != init_bay(q,CTS) && QC_MOVES[q][1].bay != QC_MOVES[q][2].bay && QC_MOVES[q][2].time != 0
            push!(single_moves, (qc=q, bay=QC_MOVES[q][1].bay, start_time = QC_MOVES[q][1].start_time, time = QC_MOVES[q][1].time))
            check = true
        end
    end

    if check == false
        return(false)
    else
        return(single_moves)
    end
end

function get_single_task(single::NamedTuple{(:qc, :bay, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, LS::LoadingSequence, CTS::Constants)
    single_task = (task=LTask(0,0,0,0), start_time=0, qc=0)
    left_flag = false
    right_flag = false
    l = Array{NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}},1}()
    r = Array{NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}},1}()
    for t in LS.order
        #single taks
        if t.start_time == single.start_time && t.qc == single.qc && t.task.b == single.bay && t.task.t == single.time
                single_task = t
        end
        if t.start_time < single.start_time && t.task.b == single.bay && t.qc == single.qc
            if left_flag == false
                left_flag = true
            elseif l[end].start_time + 2*l[end].task.t == t.start_time
                deleteat!(l, length(l))
            end
            push!(l, t)
        elseif single.start_time < t.start_time && t.task.b == single.bay && t.qc == single.qc
            if right_flag == false
                right_flag = true
            elseif r[end].start_time + 2*r[end].task.t == t.start_time
                deleteat!(r, length(r))
            end
            push!(r, t)
        end
    end

    return(single_task, left_flag, right_flag, l, r)
end

function trim_left(single::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, left::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, LS::LoadingSequence, CTS::Constants)
    LS1 = deepcopy(LS)
    LS2 = deepcopy(LS)
    LS3 = deepcopy(LS)

    #delete single_task
    deleteat!(LS1.filled_pos, findall(x->x==single.task.p, LS1.filled_pos))
    deleteat!(LS1.loaded_cont, findall(x->x==single.task.c, LS1.loaded_cont))
    deleteat!(LS1.order, findall(x->x==single, LS1.order))
    deleteat!(LS2.filled_pos, findall(x->x==single.task.p, LS2.filled_pos))
    deleteat!(LS2.loaded_cont, findall(x->x==single.task.c, LS2.loaded_cont))
    deleteat!(LS2.order, findall(x->x==single, LS2.order))
    deleteat!(LS3.filled_pos, findall(x->x==single.task.p, LS3.filled_pos))
    deleteat!(LS3.loaded_cont, findall(x->x==single.task.c, LS3.loaded_cont))
    deleteat!(LS3.order, findall(x->x==single, LS3.order))

    #trim LS
    for t in LS.order
        if  left.start_time < t.start_time
            deleteat!(LS1.filled_pos, findall(x->x==t.task.p, LS1.filled_pos))
            deleteat!(LS1.loaded_cont, findall(x->x==t.task.c, LS1.loaded_cont))
            deleteat!(LS1.order, findall(x->x==t, LS1.order))
            if single.start_time <= t.start_time
                deleteat!(LS2.filled_pos, findall(x->x==t.task.p, LS2.filled_pos))
                deleteat!(LS2.loaded_cont, findall(x->x==t.task.c, LS2.loaded_cont))
                deleteat!(LS2.order, findall(x->x==t, LS2.order))
            elseif t.start_time <= single.start_time + 2*single.task.t
                deleteat!(LS3.filled_pos, findall(x->x==t.task.p, LS3.filled_pos))
                deleteat!(LS3.loaded_cont, findall(x->x==t.task.c, LS3.loaded_cont))
                deleteat!(LS3.order, findall(x->x==t, LS3.order))
            end
        elseif t.start_time <= left.start_time
            deleteat!(LS2.filled_pos, findall(x->x==t.task.p, LS2.filled_pos))
            deleteat!(LS2.loaded_cont, findall(x->x==t.task.c, LS2.loaded_cont))
            deleteat!(LS2.order, findall(x->x==t, LS2.order))
            deleteat!(LS3.filled_pos, findall(x->x==t.task.p, LS3.filled_pos))
            deleteat!(LS3.loaded_cont, findall(x->x==t.task.c, LS3.loaded_cont))
            deleteat!(LS3.order, findall(x->x==t, LS3.order))
        end
    end

    return(LS1, LS2, LS3)
end

function trim_right(single::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, right::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, LS::LoadingSequence, CTS::Constants)
    LS1 = deepcopy(LS)
    LS2 = deepcopy(LS)
    LS3 = deepcopy(LS)

    #delete single_task
    deleteat!(LS1.filled_pos, findall(x->x==single.task.p, LS1.filled_pos))
    deleteat!(LS1.loaded_cont, findall(x->x==single.task.c, LS1.loaded_cont))
    deleteat!(LS1.order, findall(x->x==single, LS1.order))
    deleteat!(LS2.filled_pos, findall(x->x==single.task.p, LS2.filled_pos))
    deleteat!(LS2.loaded_cont, findall(x->x==single.task.c, LS2.loaded_cont))
    deleteat!(LS2.order, findall(x->x==single, LS2.order))
    deleteat!(LS3.filled_pos, findall(x->x==single.task.p, LS3.filled_pos))
    deleteat!(LS3.loaded_cont, findall(x->x==single.task.c, LS3.loaded_cont))
    deleteat!(LS3.order, findall(x->x==single, LS3.order))

    #trim LS
    for t in LS.order
        if  t.start_time >= single.start_time
            deleteat!(LS1.filled_pos, findall(x->x==t.task.p, LS1.filled_pos))
            deleteat!(LS1.loaded_cont, findall(x->x==t.task.c, LS1.loaded_cont))
            deleteat!(LS1.order, findall(x->x==t, LS1.order))
            if t.start_time >= right.start_time + 2*right.task.t
                deleteat!(LS2.filled_pos, findall(x->x==t.task.p, LS2.filled_pos))
                deleteat!(LS2.loaded_cont, findall(x->x==t.task.c, LS2.loaded_cont))
                deleteat!(LS2.order, findall(x->x==t, LS2.order))
            elseif t.start_time <= right.start_time
                deleteat!(LS3.filled_pos, findall(x->x==t.task.p, LS3.filled_pos))
                deleteat!(LS3.loaded_cont, findall(x->x==t.task.c, LS3.loaded_cont))
                deleteat!(LS3.order, findall(x->x==t, LS3.order))
            end
        elseif t.start_time < single.start_time
            deleteat!(LS2.filled_pos, findall(x->x==t.task.p, LS2.filled_pos))
            deleteat!(LS2.loaded_cont, findall(x->x==t.task.c, LS2.loaded_cont))
            deleteat!(LS2.order, findall(x->x==t, LS2.order))
            deleteat!(LS3.filled_pos, findall(x->x==t.task.p, LS3.filled_pos))
            deleteat!(LS3.loaded_cont, findall(x->x==t.task.c, LS3.loaded_cont))
            deleteat!(LS3.order, findall(x->x==t, LS3.order))
        end
    end
    return(LS1, LS2, LS3)
end

function merge_left(single::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, LS1::LoadingSequence, LS2::LoadingSequence, LS3::LoadingSequence, CTS::Constants)
    #merge single task
    push!(LS1.order, (task=single.task, start_time = get_qc_last_time(single.qc, LS1) + travel_time(get_qc_last_bay(single.qc, LS1), single.task.b, CTS), qc=single.qc))
    push!(LS1.filled_pos, single.task.p)
    push!(LS1.loaded_cont, single.task.c)
    #update and merge LS2
    for t in LS2.order
        if t.qc == single.qc
            nt = (task=t.task, start_time = t.start_time + 2*single.task.t, qc=single.qc)
            push!(LS1.order, nt)
        else
            push!(LS1.order, t)
        end
        push!(LS1.filled_pos, t.task.p)
        push!(LS1.loaded_cont, t.task.c)
    end
    #update and merge LS3
    if length(LS3.order) > 0
    dif = 2*travel_time(get_qc_last_bay(single.qc, LS2), single.task.b, CTS)
        for t in LS3.order
            if t.qc == single.qc
                nt = (task=t.task, start_time = t.start_time - dif, qc=single.qc)
                push!(LS1.order, nt)
            else
                push!(LS1.order, t)
            end
            push!(LS1.filled_pos, t.task.p)
            push!(LS1.loaded_cont, t.task.c)
        end
    end
    return(LS1)
end

function merge_right(single::NamedTuple{(:task, :start_time, :qc),Tuple{LTask,Int64,Int64}}, LS1::LoadingSequence, LS2::LoadingSequence, LS3::LoadingSequence, CTS::Constants)
    #update and merge LS2
    if length(LS1.order) > 0
        dif = 2*travel_time(get_qc_last_bay(single.qc, LS1), single.task.b, CTS) + 2*single.task.t
    else
        dif = 2*travel_time(init_bay(single.qc, CTS), single.task.b, CTS) + 2*single.task.t
    end
    for t in LS2.order
        if t.qc == single.qc
            nt = (task=t.task, start_time = t.start_time - dif, qc=single.qc)
            push!(LS1.order, nt)
        else
            push!(LS1.order, t)
        end
        push!(LS1.filled_pos, t.task.p)
        push!(LS1.loaded_cont, t.task.c)
    end

    #merge single task
    push!(LS1.order, (task=single.task, start_time = get_qc_last_time(single.qc, LS1) + travel_time(get_qc_last_bay(single.qc, LS1), single.task.b, CTS), qc=single.qc))
    push!(LS1.filled_pos, single.task.p)
    push!(LS1.loaded_cont, single.task.c)

    #update and merge LS3
    dif = dif - 2*single.task.t
    for t in LS3.order
        if t.qc == single.qc
            nt = (task=t.task, start_time = t.start_time - dif, qc=single.qc)
            push!(LS1.order, nt)
        else
            push!(LS1.order, t)
        end
        push!(LS1.filled_pos, t.task.p)
        push!(LS1.loaded_cont, t.task.c)
    end
    return(LS1)
end

function remove_single_moves(prec::Dict{Int, Array}, makespan::Int, LS::LoadingSequence, CTS::Constants)
    QC_MOVES = get_qc_moves(LS, CTS)
    single_moves = get_single_moves(QC_MOVES, CTS)
    if single_moves == false
        return(LS)
    else
        for single in single_moves
            if single.qc == LS.order[end].qc
                single_task, left_flag, right_flag, l, r = get_single_task(single, LS, CTS)
                if left_flag == true
                    for l_task in l
                        LS1, LS2, LS3 = trim_left(single_task, l_task, LS, CTS)
                        new_LS = merge_left(single_task, LS1, LS2, LS3, CTS)
                        if check_solution(prec, new_LS, CTS) == true
                            if total_makespan(new_LS, CTS) < makespan
                                return(new_LS)
                            end
                        end
                    end
                end
                if right_flag == true
                    for r_task in r
                        LS1, LS2, LS3 = trim_right(single_task, r_task, LS, CTS)
                        new_LS = merge_right(single_task, LS1, LS2, LS3, CTS)
                        if check_solution(prec, new_LS, CTS) == true
                            if total_makespan(new_LS, CTS) < makespan
                                return(new_LS)
                            end
                        end
                    end
                end
            end
        end
        return(LS)
    end
end

function remove_useless_travel(LS::LoadingSequence)
    if LS.order[end].task.t == 0
        deleteat!(LS.order, length(LS.order))
    end
end

function get_final_state(LS::LoadingSequence, CTS::Constants)
    TIME = init_timer(CTS)
    TIME.available_cranes = Array{Int, 1}()
    QC = init_qc(CTS)
    min_q, moves, QC_MOVES = ls_qc_moves(best_LS, CTS)
    QC_TRAVEL = get_qc_travel(QC_MOVES, CTS)

    TIME.period = min_q.end_time
    TIME.available_cranes = Array{Int, 1}()

    for q = 1:CTS.Q
        QC[q].task_buffer = Array{LTask, 1}()
        #idle crane
        if QC_MOVES[q][1].end_time == TIME.period
            QC[q].status = "idle"
            QC[q].time_left = 0
            QC[q].current_bay = QC_MOVES[q][1].bay
            QC[q].next_bay = QC_MOVES[q][1].bay
            push!(TIME.available_cranes, q)
        #loading crane
        elseif QC_MOVES[q][1].start_time <= TIME.period && TIME.period < QC_MOVES[q][1].end_time
            QC[q].status = "loading"
            QC[q].time_left = QC_MOVES[q][1].end_time - TIME.period
            QC[q].current_bay = QC_MOVES[q][1].bay
            QC[q].next_bay = QC_MOVES[q][1].bay
        else
            QC[q].status = "idle"
            QC[q].time_left = 0
            QC[q].current_bay = QC_MOVES[q][1].bay
            QC[q].next_bay = QC_MOVES[q][1].bay
            push!(TIME.available_cranes, q)
        end
    end
    return(min_q.q, moves, TIME, QC)
end

function remove_final_tasks(moves::Int, LS::LoadingSequence, CTS::Constants)
    new_LS = deepcopy(LS)

    if moves > 2
        while new_LS.len >= LS.len - (moves - 2)
            if new_LS.order[end].task.p != 0 && new_LS.order[end].task.c != 0
                new_LS.len -= 1
                new_LS.tasks_left += 1
            end
            deleteat!(new_LS.filled_pos, findall(x->x==new_LS.order[length(new_LS.order)].task.p, new_LS.filled_pos))
            deleteat!(new_LS.loaded_cont, findall(x->x==new_LS.order[length(new_LS.order)].task.c, new_LS.loaded_cont))
            deleteat!(new_LS.order, length(new_LS.order))
        end
        if new_LS.order[end].task.p == 0 && new_LS.order[end].task.c == 0
            deleteat!(new_LS.order, length(new_LS.order))
        end
    end
    return(new_LS)
end

function ls_qc_moves(LS::LoadingSequence, CTS::Constants)
    QC_MOVES=Dict{Int, Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}}()
    min_q = (q=0, end_time=CTS.H, start_time=CTS.H, time=CTS.H)
    moves = 0
    flag = Array{Int,1}()
    while Set(collect(1:CTS.Q)) != Set(flag)
        for t in reverse(LS.order)
            for q = 1:CTS.Q
                if t.qc == q && !(q in flag) && t.task.t != 0
                    push!(flag, q)
                    if t.start_time + 2*t.task.t < min_q.end_time
                        min_q = (q=q, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time = t.task.t)
                        break
                    end
                end
            end
        end
    end

    flag = Array{Int,1}()
    while Set(collect(1:CTS.Q)) != Set(flag)
        for q = 1:CTS.Q
            l=Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}()
            for t in reverse(LS.order)
                if t.qc == q
                    if t.start_time > min_q.end_time
                        push!(l, (bay=t.task.b, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time=t.task.t))
                        if t.task.t != 0
                            moves += 1
                        end
                    elseif t.start_time < min_q.start_time && t.start_time + 2*t.task.t < min_q.start_time
                        push!(l, (bay=t.task.b, end_time=t.start_time, start_time=t.start_time, time=0))
                        push!(flag, q)
                        break
                    else
                        push!(l, (bay=t.task.b, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time=t.task.t))
                        push!(flag, q)
                        if t.task.t != 0
                            moves += 1
                        end
                        break
                    end
                end
            end
            QC_MOVES[q]=reverse(l)
        end
    end
    return(min_q, moves, QC_MOVES)
end


import Base: sum, prod, maximum, minimum, sum!, prod!, maximum!, minimum!
for (fname, _fname, fname!, mpiop) in [(:sum, :_sum, :sum!, :(MPI.SUM)), (:prod, :_prod, :prod!, :(MPI.PROD)), (:maximum,:_maximum, :maximum!, :(MPI.MAX)), (:minimum, :_minimum, :minimum!, :(MPI.MIN))]

    @eval begin
        function ($_fname)(f::Function, a::MPIArray{T,N,A}, ::Colon) where {T,N,A}
            partial = ($fname)(f, a.localarray)
            sync()
            MPI.Allreduce(partial, $mpiop, MPI.COMM_WORLD)
        end

        function ($fname!)(r::MPIArray{T,N,A}, arr::MPIArray{T,N,A}) where {T,N,A}
            for (dr, darr) in zip(size(r), size(arr))
                @assert dr == darr || dr == 1
            end
            @assert size(r)[end] == size(arr)[end]
            ($fname!)(r.localarray, arr.localarray)
            r
        end

        function ($fname!)(r::AbstractArray{T,N}, arr::MPIArray{T,N,A}) where {T,N,A}
            @assert size(r)[end] == 1
            ($fname!)(r, arr.localarray)
            sync()
            MPI.Allreduce!(r, $mpiop, MPI.COMM_WORLD)
            r
        end

        function ($_fname)(a::MPIArray{T,N,A}, dims::NTuple{N2, Integer}) where {T,N,A,N2}
            outsize = map(x -> x in dims ? 1 : size(a, x), 1:N)
            if N in dims
                out = A{T}(undef, outsize...)
            else
                out = similar(a, outsize...)
            end
            ($fname!)(out, a)
            out
        end

        ($_fname)(f::Function, a::MPIArray{T,N,A}, dims::Integer) where {T,N,A} = ($_fname)(f, a, (dims,))
        ($fname)(f::Function, a::MPIArray{T,N,A}; dims=:) where {T,N,A} = ($_fname)(f, a, dims)
        ($_fname)(a::MPIArray{T,N,A}, ::Colon) where {T,N,A} = ($_fname)(identity, a, :)
        ($_fname)(a::MPIArray{T,N,A}, dims::Integer) where {T,N,A} = ($_fname)(a, (dims,))
        ($fname)(a::MPIArray{T,N,A}; dims=:) where {T,N,A} = ($_fname)(a, dims)
    end
end


module Network
using SHA
using Dates
using Random

include("./Ledger.jl")
using .Ledger: Transaction, Blockchain, Block, appendblock, gethash, checkdifficulty

mutable struct Node
    pool::Dict{String,Transaction}
    blockchain::Blockchain
    Node() = new(Dict{String,Transaction}(), Blockchain("litcoin"))
    #Node() = new(Dict{String,Transaction}())
end

# Initialize a fixed number of nodes
nodes = [Node() for i in 1:10]

function getNTransactions(node::Node, n::Int)
    sorted = sort(collect(values(node.pool)), by=x -> x.fee, rev=true)
    return sorted[1:min(n, size(sorted, 1))]
end

function getRandomNode()
    return nodes[rand(1:size(nodes, 1))]
end

function addTransaction(node::Node, tx::Transaction)
    node.pool[tx.signature] = tx
end

function addBlock(node::Node, parent_hash::String, transactions::Vector{Transaction}, nonce::String)
    node.blockchain.append_block(parent_hash, transactions, nonce)
end

function mine()
    # Select a random Node for getting the blockchain data
    node = getRandomNode()

    # Start mining
    while true
        # Get transactions to verify
        transactions = getNTransactions(node, 5)

        # Get the previous block hash
        parent_hash = node.blockchain.blocks[1].hash

        # Mine by trying different nonces
        max_nonce = 2 ^ 32
        mined = false
        block = 0
        for i in 0:max_nonce
            nonce = randstring(64)
            block = Block(0, Dates.now(), transactions, parent_hash, nonce)
            if checkdifficulty(node.blockchain, block)
                mined = true
                break
            end
        end

        # If succesful, alert the network
        if mined
            accepted = appendblock(node.blockchain, block)
            if accepted
                #println("MINED")
            else
                println("NOPE")
            end
        end

        # Sleep a while
        sleep(0.5)
    end
end

function transact()
    # Select a random Node for inserting new transactions
    node = getRandomNode()

    while true
        # Create new transaction
        tx = Transaction(0, "aki", 0, "marc", 0, "hi", randstring(12), 1)

        # Add transaction to pool
        addTransaction(node, tx)

        # Sleep a while
        sleep(1)
    end
end

function observe()
    while true
        n_nodes = size(nodes, 1)
        n_tx_all = 0
        longest_block = 0

        for n in nodes
            n_tx_all += length(n.pool)
            longest_block = max(longest_block, size(n.blockchain.blocks, 1))
        end

        println("Number of nodes: $(lpad(n_nodes, 7))")
        println("Number of pooled transactions: $(lpad(n_tx_all, 7))")
        println("Longest block: $(lpad(longest_block, 7))")

        # Sleep a while
        sleep(0.5)
        run(`clear`)
    end
end

end

# Init a transaction loop
@async Network.transact()

# Init an observer loop
@async Network.observe()

# Init a mining loop
@sync @async Network.mine()


###############################################################################
#
# Make g_min and g_max control
#
###############################################################################
"""
    gsw_control!(
                photo_set::AbstractPhotoModelParaSet{FT},
                canopyi::CanopyLayer{FT},
                envir::AirLayer{FT},
                ind::Int
    ) where {FT<:AbstractFloat}
    gsw_control!(
                photo_set::AbstractPhotoModelParaSet{FT},
                canopyi::CanopyLayer{FT},
                envir::AirLayer{FT}
    ) where {FT<:AbstractFloat}

make sure g_sw is in its physiological range limited by diffusion, given
- `photo_set` [`C3ParaSet`] or [`C4ParaSet`] type parameter set
- `canopyi` [`CanopyLayer`](@ref) type struct
- `envir` [`AirLayer`] type struct
- `ind` Nth leaf

Note that this function is meant to use jointly with gas_exchange! when
    computing optimal stomtal conductance.
"""
function gsw_control!(
            photo_set::AbstractPhotoModelParaSet{FT},
            canopyi::CanopyLayer{FT},
            envir::AirLayer{FT},
            ind::Int
) where {FT<:AbstractFloat}
    # if g_sw is low than g_min
    if canopyi.g_sw[ind] < canopyi.g_min
        gas_exchange!(photo_set, canopyi, envir, GswDrive(), ind,
                      canopyi.g_min);
    elseif canopyi.g_sw[ind] > canopyi.g_max
        gas_exchange!(photo_set, canopyi, envir, GswDrive(), ind,
                      canopyi.g_max);
    end

    return nothing
end

function gsw_control!(
            photo_set::AbstractPhotoModelParaSet{FT},
            canopyi::CanopyLayer{FT},
            envir::AirLayer{FT}
) where {FT<:AbstractFloat}
    # if g_sw is low than g_min
    for i in eachindex(canopyi.g_sw)
        canopyi.ps.APAR = canopyi.APAR[i];
        leaf_ETR!(photo_set, canopyi.ps);
        gsw_control!(photo_set, canopyi, envir, i);
    end

    return nothing
end


import Cairo
using Colors
using Base.Test
using Winston
using Compat

module ImageComparisons
    using Winston, Colors
    include("examples.jl")
    include("issues.jl")
    include("plot.jl")
end

function read_png_data(fn::ASCIIString)
    surface = Cairo.read_from_png(fn)
    w = Cairo.width(surface)
    h = Cairo.height(surface)
    p = ccall((:cairo_image_surface_get_data,Cairo._jl_libcairo),
              Ptr{UInt8}, (Ptr{Void},), surface.ptr)
    a = pointer_to_array(convert(Ptr{UInt32},p), (convert(Int,w),convert(Int,h)))
    copy(a)
end

function img_dist{T<:Array{UInt32,2}}(img1::T, img2::T)
    @assert size(img1) == size(img2)
    s = 0.
    for i = 1:length(img1)
        a = convert(RGB, RGB24(img1[i]))
        b = convert(RGB, RGB24(img2[i]))
        s += colordiff(a, b)
    end
    s
end

function main()
    root = dirname(task_local_storage()[:SOURCE_PATH])
    dir1,dir2 = "$root/_baseline","$root/_current"
    isdir(dir1) || mkdir(dir1)
    isdir(dir2) || mkdir(dir2)

    for name in sort(names(ImageComparisons))

        name == :ImageComparisons && continue
        p = eval(:((ImageComparisons.$name)()))

        fn1 = "$dir1/$name.png"
        fn2 = "$dir2/$name.png"
        isfile(fn1) || savefig(p, fn1)
        savefig(p, fn2)

        img1 = read_png_data(fn1)
        img2 = read_png_data(fn2)

        d = img_dist(img1, img2)
        println("$name $d")
        d < 0.1 || error("$name failed")
    end
end

main()


module IntervalTrees

export
    IntervalTree,
    IntervalMap,
    AbstractInterval,
    Interval,
    IntervalValue,
    depth,
    hasintersection,
    from,
    value

using Base: notnothing

include("slice.jl")

"""
Types deriving from `AbstractInterval{T}` must have a `first` and `last`
function each returning a value of type `T`, and `first(i) <= last(i)`
must always be true.
"""
abstract type AbstractInterval{T} end

Base.first(i::AbstractInterval{T}) where T = i.first
Base.last(i::AbstractInterval{T}) where T = i.last

function basic_isless(u::AbstractInterval, v::AbstractInterval)
    return first(u) < first(v) || (first(u) == first(v) && last(u) < last(v))
end

function Base.isless(u::AbstractInterval, v::AbstractInterval)
    return basic_isless(u, v)
end

"""
A basic interval.
"""
struct Interval{T} <: AbstractInterval{T}
    first::T
    last::T
end
Base.convert(::Type{Interval{T}}, range::AbstractRange{T}) where T =
    Interval(first(range), last(range))
Interval(range::AbstractRange{T}) where T = convert(Interval{T}, range)

"""
An interval with some associated data.
"""
struct IntervalValue{K, V} <: AbstractInterval{K}
    first::K
    last::K
    value::V
end

IntervalValue(range::AbstractRange{K}, value::V) where {K, V} =
    IntervalValue(first(range), last(range), value)

valtype(::Type{IntervalValue{K,V}}) where {K, V} = V

value(i::IntervalValue{K, V}) where {K, V} = i.value

Base.print(io::IO, x::Interval) = print(io, "\n($(first(x)),$(last(x)))")
function Base.show(io::IO, x::Interval)
    show(io, typeof(x))
    print(io, x)
end

Base.print(io::IO, x::IntervalValue) = print(io, "\n($(first(x)),$(last(x))) => $(value(x))")
function Base.show(io::IO, x::IntervalValue)
    show(io, typeof(x))
    print(io, x)
end

# Each of these types is indexes by K, V, B, where
#   K : Interval type. Intervals are represented as (K, K) tuples.
#   V : Interval value type. This an interval type that may have associated
#        data. It must have `first`, `last`, and `isless` methods.
#   B : Integer giving the B-tree order.

abstract type Node{K, V, B} end

mutable struct InternalNode{K, V, B} <: Node{K, V, B}
    # Internal nodes are keyed by the minimum interval in the right subtree.  We
    # need internal node keys to be intervals themselves, since ordering by
    # start value alone only works if start values are unique. We don't
    # force that restriction, so we must break ties in order to split nodes that
    # are full of intervals with the same start value.
    keys::Slice{Interval{K}, B}

    # Maximum end ofd this node
    maxend::K

    # Maximum child subtree end-points
    maxends::Slice{K, B}

    children::Slice{Node{K, V, B}, B}

    parent::Union{Nothing, InternalNode{K, V, B}}

    # Sibling/cousin pointers.
    left::Union{Nothing, InternalNode{K, V, B}}
    right::Union{Nothing, InternalNode{K, V, B}}

    function InternalNode{K,V,B}() where {K,V,B}
        return new{K,V,B}(
            Slice{Interval{K}, B}(), zero(K), Slice{K, B}(), Slice{Node{K, V, B}, B}(),
            nothing,
            nothing,
            nothing)
    end
end

function minstart(t::InternalNode)
    return t.keys[1].first
end

mutable struct LeafNode{K, V, B} <: Node{K, V, B}
    entries::Vector{V}
    keys::Vector{Interval{K}}
    count::UInt32 # number of stored entries

    # maximum interval end-point in this leaf node.
    maxend::K

    parent::Union{Nothing, InternalNode{K, V, B}}

    # Sibling/cousin pointers.
    left::Union{Nothing, LeafNode{K, V, B}}
    right::Union{Nothing, LeafNode{K, V, B}}

    function LeafNode{K,V,B}() where {K,V,B}
        return new{K,V,B}(
            Array{V}(undef, B), Array{Interval{K}}(undef, B), 0,
            zero(K),
            nothing,
            nothing,
            nothing)
    end
end

function minstart(t::LeafNode)
    return first(t.keys[1])
end

function Base.resize!(leaf::LeafNode{K, V, B}, n) where {K, V, B}
    @assert 0 <= n <= B
    leaf.count = n
end

function Base.splice!(leaf::LeafNode{K, V, B}, i::Integer) where {K, V, B}
    slice_splice!(leaf.keys, leaf.count, i)
    x, leaf.count = slice_splice!(leaf.entries, leaf.count, i)
    return x
end

function Base.insert!(leaf::LeafNode{K, V, B}, i::Integer, value::V) where {K, V, B}
    slice_insert!(leaf.keys, leaf.count, i, Interval{K}(first(value), last(value)))
    slice_insert!(leaf.entries, leaf.count, i, value)
    leaf.count += 1
end

function Base.push!(leaf::LeafNode{K, V, B}, value::V) where {K, V, B}
    leaf.count += 1
    leaf.entries[leaf.count] = value
    leaf.keys[leaf.count] = Interval{K}(first(value), last(value))
end

function Base.pop!(leaf::LeafNode{K, V, B}) where {K, V, B}
    x = leaf.entries[leaf.count]
    leaf.count -= 1
    return x
end

function Base.iterate(leaf::LeafNode, i::Int=1)
    return i ≤ length(leaf) ? (leaf.entries[i], i + 1) : nothing
end

mutable struct IntervalBTree{K, V, B}
    root::Node{K, V, B}
    n::Int # Number of entries

    function IntervalBTree{K,V,B}() where {K,V,B}
        return new{K,V,B}(LeafNode{K, V, B}(), 0)
    end

    # Construct an interval tree from a sorted array of intervals.
    #
    # This is generally much more efficient than constructing the same tree by
    # inserting intervals one by one.
    #
    # Args:
    #   entries: Interval entry values in sorted order.
    #
    function IntervalBTree{K,V,B}(entries::AbstractVector{V}) where {K,V,B}
        if !issorted(entries, lt=basic_isless)
            error("Intervals must be sorted to construct an IntervalTree")
        end

        # Here's the plan: figure out how many leaf nodes we need, allocate
        # them all up front. Copy in the keys and values, then work up towards
        # the root.

        n = length(entries)

        if n == 0
            return new{K,V,B}(LeafNode{K, V, B}(), 0)
        end

        numleaves = cld(n, B - 2)
        leaves = [LeafNode{K, V, B}() for _ in 1:numleaves]

        maxends = Vector{K}(undef, numleaves)
        minkeys = Vector{Interval{K}}(undef, numleaves)

        # divy up the keys and values among the leaves
        keys_per_leaf = cld(n, numleaves)
        for i in 1:numleaves
            u = (i - 1) * keys_per_leaf + 1
            v = min(n, i * keys_per_leaf)
            minkeys[i] = Interval{K}(first(entries[u]), last(entries[u]))
            maxends[i] = last(entries[u])
            for j in u:v
                push!(leaves[i], entries[j])
                maxends[i] = max(maxends[i], last(entries[j]))
            end
            leaves[i].maxend = maxends[i]
        end

        children = leaves
        while length(children) > 1
            # sibling pointers
            for i in 1:length(children)-1
                children[i].right = children[i+1]
                children[i+1].left = children[i]
            end

            # make parents
            numparents = cld(length(children), B - 2)
            parents = [InternalNode{K, V, B}() for _ in 1:numparents]

            # divy up children among parents
            children_per_parent = cld(length(children), numparents)
            for i in 1:numparents
                u = (i - 1) * keys_per_leaf + 1
                v = min(length(children), i * keys_per_leaf)
                maxend = maxends[u]
                for j in u:v
                    push!(parents[i].children, children[j])
                    push!(parents[i].maxends, maxends[j])
                    children[j].parent = parents[i]
                    maxend = max(maxend, maxends[j])
                    if j > u
                        push!(parents[i].keys, minkeys[j])
                    end
                end
                minkeys[i] = minkeys[u]
                parents[i].maxend = maxend
                maxends[i] = maxend
            end

            children = parents
        end
        @assert length(children) == 1

        return new{K,V,B}(children[1], n)
    end
end

# Default B-tree order
const IntervalTree{K, V} = IntervalBTree{K, V, 64}

# Show

function Base.show(io::IO, it::IntervalTree)
    show(io, typeof(it))
    n = length(it)
    if length(it) > 6
        # Hacky random access ...
        for (i, x) in enumerate(it)
            i < 4 && print(x)
        end
        print("\n\u22EE") # Vertical ellipsis
        for (i, x) in enumerate(it)
            i > (n-3) && print(x)
        end
    else
        for x in it
            print(x)
        end
    end
end

# Length
# ------

function Base.length(t::IntervalBTree)
    return t.n
end

function depth(t::IntervalBTree)
    k = 1
    node = t.root
    while !isa(node, LeafNode)
        k += 1
        node = node.children[1]
    end
    return k
end

function Base.length(t::InternalNode)
    return length(t.children)
end

function Base.length(t::LeafNode)
    return t.count
end

function Base.isempty(t::IntervalBTree)
    return t.n == 0
end

function Base.isempty(t::InternalNode)
    return isempty(t.children)
end

function Base.isempty(t::LeafNode)
    return t.count == 0
end

# Iterating
# ---------

mutable struct IntervalBTreeIteratorState{K, V, B}
    leaf::Union{Nothing, LeafNode{K, V, B}}
    i::Int
end

function Base.iterate(
        t::IntervalBTree{K, V, B},
        state::IntervalBTreeIteratorState{K, V, B}=iterinitstate(t)) where {K, V, B}
    if state.leaf === nothing || isempty(state.leaf)
        return nothing
    end
    leaf = notnothing(state.leaf)
    entry = leaf.entries[state.i]
    if state.i < length(leaf)
        state.i += 1
    else
        state.leaf = leaf.right
        state.i = 1
    end
    return entry, state
end

function iterinitstate(t::IntervalBTree{K,V,B}) where {K, V, B}
    # traverse to the first leaf node
    node = t.root
    while !isa(node, LeafNode{K, V, B})
        node = node.children[1]
    end
    return IntervalBTreeIteratorState(node, 1)
end

# Iterate from a given starting from the first interval that intersects a given point.
struct IntervalFromIterator{K, V, B}
    t::IntervalBTree{K, V, B}
    p::K
end

Base.eltype(::Type{IntervalFromIterator{K, V, B}}) where {K, V, B} = V
Base.IteratorSize(::Type{IntervalFromIterator{K, V, B}}) where {K, V, B} = Base.SizeUnknown()

function from(t::IntervalBTree{K, V, B}, p) where {K, V, B}
    return IntervalFromIterator{K, V, B}(t, convert(K, p))
end

function Base.iterate(
        it::IntervalFromIterator{K, V, B},
        state::IntervalBTreeIteratorState{K, V, B}=iterinitstate(it)) where {K, V, B}
    return iterate(it.t, state)
end

function iterinitstate(it::IntervalFromIterator{K, V, B}) where {K, V, B}
    node, i = firstfrom(it.t, it.p)
    if i == 0
        return IntervalBTreeIteratorState{K, V, B}(nothing, i)
    else
        return IntervalBTreeIteratorState{K, V, B}(node, i)
    end
end

# Inserting
# ---------

# Split a leaf into two, returning (leftnode, rightnode)
function split!(left::LeafNode{K, V, B}) where {K, V, B}
    right = LeafNode{K, V, B}()
    right.right = left.right
    right.left = left
    left.right = right
    if right.right !== nothing
        right.right = notnothing(right.right)
        right.right.left = right
    end
    right.parent = left.parent

    m = length(left)
    resize!(right, m - div(m, 2))
    copyto!(right.entries, 1, left.entries, div(m, 2) + 1, length(right))
    copyto!(right.keys, 1, left.keys, div(m, 2) + 1, length(right))
    resize!(left, div(m, 2))

    left.maxend = last(left.keys[1])
    for entry in left
        left.maxend = max(left.maxend, last(entry))
    end

    right.maxend = last(right.keys[1])
    for entry in right
        right.maxend = max(right.maxend, last(entry))
    end

    return left, right
end

# Split an internal node in two, returning (leftnode, rightnode)
function split!(left::InternalNode{K, V, B}) where {K, V, B}
    right = InternalNode{K, V, B}()
    right.right = left.right
    right.left = left
    left.right = right
    if right.right !== nothing
        right.right = notnothing(right.right)
        right.right.left = right
    end
    right.parent = left.parent

    m = length(left)

    resize!(right.children, m - div(m, 2))
    resize!(right.maxends, m - div(m, 2))
    resize!(right.keys, m - div(m, 2) - 1)

    copyto!(right.children, 1, left.children, div(m, 2)+1, length(right.children))
    copyto!(right.maxends, 1, left.maxends, div(m, 2)+1, length(right.maxends))
    copyto!(right.keys, 1, left.keys, div(m, 2)+1, length(right.keys))

    resize!(left.children, div(m, 2))
    resize!(left.maxends, div(m, 2))
    resize!(left.keys, div(m, 2) - 1)

    left.maxend = left.children[1].maxend
    for child in left.children
        left.maxend = max(left.maxend, child.maxend)
    end

    right.maxend = right.children[1].maxend
    for child in right.children
        right.maxend = max(right.maxend, child.maxend)
    end

    for child in right.children
        child.parent = right
    end

    return left, right
end

# Find the maximum interval end point is a subtree
function nodemaxend(t::InternalNode{K, V, B}) where {K, V, B}
    maxend = zero(K)
    for i in 1:length(t.children)
        maxend = max(maxend, t.children[i].maxend)
    end
    return maxend
end

function nodemaxend(t::LeafNode{K, V, B}) where {K, V, B}
    maxend = zero(K)
    for i in 1:length(t)
        maxend = max(maxend, last(t.keys[i]))
    end
    return maxend
end

sameparent(u::Node{K, V, B}, v::Node{K, V, B}) where {K, V, B} = (u.parent == v.parent)

# Find the first leaf node in the tree
function firstleaf(t::IntervalBTree)
    return firstleaf(t.root)
end

function firstleaf(t::InternalNode)
    return firstleaf(t.children[1])
end

function firstleaf(t::LeafNode)
    return t
end

# Find the root node
function root(t::Node)
    while t.parent !== nothing
        t = notnothing(t.parent)
    end
    return t
end

# This version of nextleafkey is used in nextintersection at aggressively avoid
# allocation, and dereferecing nullables more than once.
macro nextleafkey(leaf, nleaf, i)
    quote
        if $(esc(i)) < length($(esc(leaf)))
            $(esc(i)) += 1
        else
            $(esc(i)) = 1
            $(esc(nleaf)) = $(esc(leaf)).right
        end
    end
end

# Find the minimum interval in a subtree
function minkey(t::InternalNode)
    return minkey(t.children[1])
end

function minkey(t::LeafNode{K, V, B}) where {K, V, B}
    return t.keys[1]
end

function Base.push!(t::IntervalBTree{K, V, B}, entry::V,
                    unique_key::Bool=false, update::Bool=true) where {K, V, B}
    return _push!(t, t.root, entry, unique_key, update)
end

function _push!(t::IntervalBTree{K, V, B},
                node::InternalNode{K, V, B},
                entry::V, unique_key::Bool, update::Bool) where {K, V, B}
    i = findidx(node, entry) # key index
    j = i <= length(node) - 1 && entry < node.keys[i] ? i : i + 1 # child index
    return _push!(t, node.children[j], entry, unique_key, update)
end

function _push!(t::IntervalBTree{K, V, B},
                node::LeafNode{K, V, B}, entry::V,
                unique_key::Bool, update::Bool) where {K, V, B}
    i = max(1, findidx(node, entry))

    # key exists and we are replacing it
    if i <= length(node) && unique_key &&
        first(node.keys[i]) == first(entry) && last(node.keys[i]) == last(entry)
        if update
            node.entries[i] = entry
            return entry
        else
            return node.entries[i]
        end
    end

    insert!(node, i, entry)
    if length(node) == 1 || last(entry) > node.maxend
        node.maxend = last(entry)
    end
    t.n += 1

    # split when full
    if length(node) == B
        maxend = node.maxend
        leftleaf, rightleaf = split!(node)
        median = rightleaf.keys[1]

        # travel back up the tree setting maxend values and splitting as needed
        parent = node.parent
        leftnode = leftleaf
        child = leftleaf
        rightnode = rightleaf
        hassplit = true
        while parent !== nothing
            p = notnothing(parent)
            if hassplit
                i = findidx(p, entry) # key index
                j = i <= length(p) - 1 && entry < p.keys[i] ? i : i + 1 # child index
                p.maxends[j] = p.children[j].maxend
                insert!(p.children, j + 1, rightnode)
                insert!(p.maxends, j + 1, p.children[j+1].maxend)
                p.maxend = max(p.maxend, maxend)
                insert!(p.keys, j, median)

                # split when full
                if length(p) == B
                    leftnode, rightnode = split!(p)
                    median = minkey(rightnode)
                    maxend = max(leftnode.maxend, rightnode.maxend)
                else
                    hassplit = false
                end
            else
                p.maxend = max(p.maxend, last(entry))
                ifind = findfirst(isequal(child), p.children)
                if ifind === nothing
                    ifind = 0
                end
                p.maxends[ifind] = child.maxend
            end
            child = p
            parent = p.parent
        end

        if hassplit
            t.root = InternalNode{K, V, B}()
            push!(t.root.keys, median)
            push!(t.root.children, leftnode)
            push!(t.root.children, rightnode)
            t.root.maxend = maxend
            push!(t.root.maxends, leftnode.maxend)
            push!(t.root.maxends, rightnode.maxend)
            leftnode.parent = t.root
            rightnode.parent = t.root
        end
    else
        # travel back up the tree setting maxend values
        parent = node.parent
        child = node
        while parent !== nothing
            p = notnothing(parent)
            p.maxend = max(p.maxend, last(entry))
            ifind = findfirst(isequal(child), p.children)
            if ifind === nothing
                ifind = 0
            end
            p.maxends[ifind] = child.maxend
            parent = p.parent
            child = p
        end
    end

    return entry
end

# Deleting
# --------

function deletefirst!(t::IntervalBTree{K, V, B}, first::K, last::K) where {K, V, B}
    return deletefirst!(t, Interval{K}(first, last))
end

function deletefirst!(t::IntervalBTree{K, V, B}, key::Tuple{K, K}) where {K, V, B}
    return deletefirst!(t, Interval{K}(key[1], key[2]))
end

function deletefirst!(t::IntervalBTree{K, V, B}, key::Interval{K}) where {K, V, B}
    result = _deletefirst!(t.root, key)
    if result.keyfound
        t.n -= 1
    end

    # if the root has only one child, promote the child
    if isa(t.root, InternalNode) && length(t.root) == 1
        t.root = t.root.children[1]
        t.root.parent = nothing
    end

    return t
end

# Indicate what steps are need to account for an updated child.
struct KeyFate
    value::UInt8
end

const KEYFATE_NONE         = KeyFate(0) # no changes
const KEYFATE_DELETE       = KeyFate(1) # delete the child node
const KEYFATE_DELETE_RIGHT = KeyFate(2) # delete the child's right sibling
const KEYFATE_UPDATE_LEFT  = KeyFate(3) # update the key separating the node
                                        # from its left sibling
const KEYFATE_UPDATE_RIGHT = KeyFate(4) # update the key separating the node
                                        # from its right sibling

# Information returned from a call to _delete
struct DeleteResult
    keyfound::Bool # true if the key was found

    # Indicate what steps are need to account for an updated child. One of:
    fate::KeyFate
end

# Delete key from the subtree if present. Return a DeleteResult object.
function _deletefirst!(t::InternalNode{K, V, B}, key::Interval{K}) where {K, V, B}
    i = findidx(t, key)
    j = i <= length(t) - 1 && key >= t.keys[i] ? i + 1 : i # child index

    ans = _deletefirst!(t.children[j], key)

    if !ans.keyfound
        return DeleteResult(false, KEYFATE_NONE)
    end

    if ans.fate == KEYFATE_UPDATE_LEFT
        t.maxend = nodemaxend(t)
        t.maxends[j] = t.children[j].maxend
        if j > 1
            t.maxends[j-1] = t.children[j-1].maxend
            t.keys[j-1] = minkey(t.children[j]);
            return DeleteResult(true, KEYFATE_NONE)
        else
            return DeleteResult(true, KEYFATE_UPDATE_LEFT)
        end
    elseif ans.fate == KEYFATE_UPDATE_RIGHT
        t.maxend = nodemaxend(t)
        t.maxends[j] = t.children[j].maxend
        if j < length(t)
            t.maxends[j+1] = t.children[j+1].maxend
            t.keys[j] = minkey(t.children[j+1])
        end

        if j > 1
            t.keys[j - 1] = minkey(t.children[j]);
            return DeleteResult(true, KEYFATE_NONE)
        else
            return DeleteResult(true, KEYFATE_UPDATE_LEFT)
        end
    elseif ans.fate == KEYFATE_DELETE || ans.fate == KEYFATE_DELETE_RIGHT
        deleteidx = ans.fate == KEYFATE_DELETE ? j : j + 1

        # deleteidx == 1 would only happen if we had only one child, but if
        # that were the case it would have been promoted.
        @assert deleteidx > 1

        splice!(t.keys, deleteidx - 1)
        splice!(t.children, deleteidx)
        splice!(t.maxends, deleteidx)
        t.maxend = nodemaxend(t)
        if ans.fate == KEYFATE_DELETE
            t.maxends[j-1] = t.children[j-1].maxend
        elseif ans.fate == KEYFATE_DELETE_RIGHT
            t.maxends[j] = t.children[j].maxend
        end

        # not underfull
        minsize = div(B, 2)
        if length(t) >= minsize
            return DeleteResult(true, deleteidx == 2 ? KEYFATE_UPDATE_LEFT : KEYFATE_NONE)
        end

        # borrow right
        if t.right !== nothing
            right = notnothing(t.right)
            if sameparent(right, t) && length(right) > minsize
                splice!(right.keys, 1)
                push!(t.children, splice!(right.children, 1))
                push!(t.maxends, splice!(right.maxends, 1))
                push!(t.keys, minkey(t.children[end]))
                t.maxend = max(t.maxend, nodemaxend(t.children[end]))
                right.maxend = nodemaxend(right)
                t.children[end].parent = t

                return DeleteResult(true, KEYFATE_UPDATE_RIGHT)
            end
        end

        # borrow left
        if t.left !== nothing
            left = notnothing(t.left)
            if sameparent(left, t) && length(left) > minsize
                insert!(t.children, 1, pop!(left.children))
                insert!(t.maxends, 1, pop!(left.maxends))
                pop!(left.keys)
                insert!(t.keys, 1, minkey(t.children[2]))
                t.maxend = max(t.maxend, nodemaxend(t.children[1]))
                left.maxend = nodemaxend(left)
                t.children[1].parent = t

                return DeleteResult(true, KEYFATE_UPDATE_LEFT)
            end
        end

        # merge with left
        if t.left !== nothing
            left = notnothing(t.left)
            if sameparent(left, t)
                merge!(left, t)
                left.right = t.right
                if t.right !== nothing
                    t.right = notnothing(t.right)
                    t.right.left = left
                end

                return DeleteResult(true, KEYFATE_DELETE)
            end
        end

        # merge with right
        if t.right !== nothing
            right = notnothing(t.right)
            if sameparent(right, t)
                merge!(t, right)
                if right.right !== nothing
                    right.right = notnothing(right.right)
                    right.right.left = t
                end
                t.right = right.right

                return DeleteResult(true, KEYFATE_DELETE_RIGHT)
            end
        end

        # Allow the root node to be underfull
        return DeleteResult(true, KEYFATE_NONE)
    else # KEYFATE_NONE
        t.maxend = nodemaxend(t)
        t.maxends[j] = t.children[j].maxend
        return DeleteResult(true, KEYFATE_NONE)
    end
end

function _deletefirst!(t::LeafNode{K, V, B}, key::Interval{K}) where {K, V, B}
    i = findidx(t, key)

    # do nothing if the key isn't present
    if i < 1 || i > length(t) ||
        first(t.keys[i]) != key.first || last(t.keys[i]) != key.last
        return DeleteResult(false, KEYFATE_NONE)
    end

    splice!(t, i)

    # This is the root node. Allow it to be empty.
    if isempty(t)
        return DeleteResult(true, KEYFATE_NONE)
    end

    minsize = div(B, 2)

    t.maxend = nodemaxend(t)

    # not underfull
    if length(t) >= minsize
        return DeleteResult(true, i == 1 ? KEYFATE_UPDATE_LEFT : KEYFATE_NONE)
    end

    # borrow right
    if t.right !== nothing
        right = notnothing(t.right)
        if sameparent(right, t) && length(right) > minsize
            push!(t, splice!(right, 1))
            t.maxend = max(t.maxend, last(t.keys[t.count]))
            right.maxend = nodemaxend(right)

            return DeleteResult(true, KEYFATE_UPDATE_RIGHT)
        end
    end

    # borrow left
    if t.left !== nothing
        left = notnothing(t.left)
        if sameparent(left, t) && length(left) > minsize
            insert!(t, 1, pop!(left))
            t.maxend = max(t.maxend, last(t.keys[1]))
            left.maxend = nodemaxend(left)

            return DeleteResult(true, KEYFATE_UPDATE_LEFT)
        end
    end

    # merge with left
    if t.left !== nothing
        left = notnothing(t.left)
        if sameparent(left, t)
            merge!(left, t)
            left.right = t.right
            if t.right !== nothing
                t.right = notnothing(t.right)
                t.right.left = left
            end
            return DeleteResult(true, KEYFATE_DELETE)
        end
    end

    # merge with right
    if t.right !== nothing
        right = notnothing(t.right)
        if sameparent(right, t)
            merge!(t, right)
            if right.right !== nothing
                right.right = notnothing(right.right)
                right.right.left = t
            end
            t.right = right.right
            return DeleteResult(true, KEYFATE_DELETE_RIGHT)
        end
    end

    # This must be the root node. Allow it to be underfull.
    return DeleteResult(true, KEYFATE_NONE)
end

# Join two leaf nodes into one.
function merge!(left::LeafNode{K, V, B}, right::LeafNode{K, V, B}) where {K, V, B}
    leftlen, rightlen = length(left), length(right)
    @assert leftlen + rightlen <= B
    resize!(left, leftlen + rightlen)
    copyto!(left.entries, leftlen+1, right.entries, 1, length(right))
    copyto!(left.keys, leftlen+1, right.keys, 1, length(right))
    left.maxend = nodemaxend(left)
    return
end

# Join two internal nodes into one
function merge!(left::InternalNode{K, V, B}, right::InternalNode{K, V, B}) where {K, V, B}
    leftlen, rightlen = length(left), length(right)
    @assert length(left) + length(right) <= B
    resize!(left.keys, leftlen + rightlen - 1)
    resize!(left.children, leftlen + rightlen)
    resize!(left.maxends, leftlen + rightlen)
    copyto!(left.children, leftlen+1, right.children, 1, length(right.children))
    copyto!(left.maxends, leftlen+1, right.maxends, 1, length(right.maxends))
    for i in leftlen+1:length(left.children)
        left.children[i].parent = left
    end
    left.keys[leftlen] = minkey(left.children[leftlen+1])
    copyto!(left.keys, leftlen+1, right.keys, 1, length(right.keys))
    left.maxend = nodemaxend(left)
    return
end

# Searching
# ---------

function findidx(t::LeafNode{K, V, B}, key::AbstractInterval{K}) where {K, V, B}
    if isempty(t)
        return 0
    end
    # TODO: search keys
    return searchsortedfirst(t.entries, key, 1, Int(t.count),
                             Base.ord(basic_isless, identity, false))
end

function findidx(t::InternalNode{K, V, B}, key::AbstractInterval{K}) where {K, V, B}
    if isempty(t.keys)
        return 0
    end
    i = searchsortedfirst(t.keys, key)
    return min(i, length(t.keys))
end

function Base.haskey(t::LeafNode{K, V, B}, key::AbstractInterval{K}) where {K, V, B}
    i = findidx(t, key)
    return 1 <= i <= length(t) && first(t.keys[i]) == first(key) &&
           last(t.keys[i]) == last(key)
end

function Base.haskey(t::InternalNode{K, V, B}, key::AbstractInterval{K}) where {K, V, B}
    i = findidx(t, key)
    if i <= length(t) - 1 && !(key < t.keys[i])
        return haskey(t.children[i+1], key)
    else
        return haskey(t.children[i], key)
    end
end

function Base.haskey(t::IntervalBTree{K, V, B}, key0::Tuple{Any, Any}) where {K, V, B}
    key = Interval{K}(key0[1], key0[2])
    return haskey(t.root, key)
end

function Base.findfirst(t::LeafNode{K, V, B}, key::AbstractInterval{K}, f) where {K, V, B}
    i = findidx(t, key)
    while 1 <= i <= length(t) &&
          first(t.keys[i]) == first(key) &&
          last(t.keys[i]) == last(key)
        if f(t.entries[i], key)
            return t.entries[i]
        end
        i += 1
        if i > length(t) && t.right !== nothing
            t = notnothing(t.right)
            i = 1
        end
    end

    return nothing
end

function Base.findfirst(t::InternalNode{K, V, B}, key::AbstractInterval{K}, f) where {K, V, B}
    i = findidx(t, key)
    if i <= length(t) - 1 && !(key < t.keys[i])
        return findfirst(t.children[i+1], key, f)
    else
        return findfirst(t.children[i], key, f)
    end
end

true_cmp(a, b) = true

function Base.findfirst(t::IntervalBTree{K, V, B}, key::AbstractInterval{K},
                        f=true_cmp) where {K, V, B}
    return findfirst(t.root, key, f)
end

function Base.findfirst(t::IntervalBTree{K, V, B}, key0::Tuple{Any, Any},
                        f=true_cmp) where {K, V, B}
    return findfirst(t, Interval{K}(key0[1], key0[2]), f)
end

# Intersection
# ------------

# There are two ways we can tackle intersection: traversal (search the tree for
# intervals that intersect) and iteration (iterate through the tree's intervals
# in sorted order).
#
# Traversal is roughly O(log(n)) while iteration is roughly O(n). Obviously
# if we are doing a single intersection, we would choose traversal, but when
# intersecting two trees it becomes more interesting.
#
# If the second tree is of size m, then intersection by traversal is
# O(min(m * log(n), n * log(m))) while interesction by iteration is O(m + n).
# When m and n are both large, it becomes more efficient to intersect by
# iteration, but deciding when requires some heuristics.
#

# Return true iff two key1 and key2 intersect.
@inline function intersects(key1::AbstractInterval{K}, key2::AbstractInterval{K}) where K
    return first(key1) <= last(key2) && first(key2) <= last(key1)
end

# Return true if the tree has an intersection with the given position
function hasintersection(t::IntervalBTree{K, V, B}, query) where {K, V, B}
    return hasintersection(t.root, convert(K, query))
end

function hasintersection(t::InternalNode{K, V, B}, query::K) where {K, V, B}
    if isempty(t) || t.maxend < query
        return false
    end

    for (i, child) in enumerate(t.children)
        if child.maxend >= query && (i == 1 || t.keys[i-1].first <= query)
            if hasintersection(child, query)
                return true
            end
        elseif minstart(child) > query
            break
        end
    end

    return false
end

function hasintersection(t::LeafNode{K, V, B}, query::K) where {K, V, B}
    if isempty(t) || t.maxend < query
        return false
    end

    for i in 1:length(t)
        if first(t.keys[i]) <= query <= last(t.keys[i])
            return true
        elseif query < first(t.keys[i])
            break
        end
    end

    return false
end

# Represent an intersection in an IntervalTree by pointing to a LeafNode
# and an index within that leaf node. No intersection is represented with
# index == 0.
mutable struct Intersection{K, V, B}
    index::Int
    node::LeafNode{K, V, B}

    Intersection{K,V,B}(index, node) where {K,V,B} = new{K,V,B}(index, node)
    Intersection{K,V,B}() where {K,V,B} = new{K,V,B}(0)
end

# Find the first interval in the tree that intersects the query and return
# as a (leafnode, index) pair, indicating that leafnode.keys[index] intersects.
# If no intersection is found, index is 0 and leafnode is the last node
# searched.
function firstintersection!(t::IntervalBTree{K, V, B},
                            query::AbstractInterval{K},
                            lower::Union{Nothing, V},
                            out::Intersection{K, V, B},
                            filter::F) where {F, K, V, B}
    return firstintersection!(t.root, query, lower, out, filter)
end

function firstintersection!(t::InternalNode{K, V, B},
                            query::AbstractInterval{K},
                            lower::Union{Nothing, V},
                            out::Intersection{K, V, B},
                            filter::F) where {F, K, V, B}
    if isempty(t) || t.maxend < first(query)
        out.index = 0
        return
    end

    (query_first, query_last) = (first(query), last(query))

    i = lower === nothing ? 1 : searchsortedfirst(t.keys, notnothing(lower))

    @inbounds while i <= length(t.children)
        if i > 1 && t.keys[i-1].first > query_last
            break
        end
        if t.maxends[i] >= query_first
            firstintersection!(t.children[i], query, lower, out, filter)
            if out.index > 0
                return
            end
        end
        i += 1
    end

    out.index = 0
    return
end

function firstintersection!(t::LeafNode{K, V, B},
                            query::AbstractInterval{K},
                            lower::Union{Nothing, V},
                            out::Intersection{K, V, B},
                            filter::F) where {F, K, V, B}
    if isempty(t) || t.maxend < first(query)
        out.index = 0
        return
    end

    # TODO: search keys
    i = lower === nothing ? 1 :
        searchsortedfirst(t.entries, notnothing(lower), 1, Int(t.count),
                          Base.ord(basic_isless, identity, false))

    while i <= length(t)
        if intersects(t.entries[i], query) && filter(t.entries[i], query)
            out.index = i
            out.node = t
            return
        elseif last(query) < first(t.keys[i])
            break
        end
        i += 1
    end

    out.index = 0
    return
end

function firstintersection(t::InternalNode{K, V, B},
                           query::Interval{K},
                           lower::Union{Nothing, Interval{K}}) where {K, V, B}
    if isempty(t) || t.maxend < first(query)
        return (nothing, 1)
    end

    i = lower === nothing ? 1 : searchsortedfirst(t.keys, notnothing(lower))

    @inbounds while i <= length(t.children)
        if i > 1 && t.keys[i-1].first > last(query)
            break
        end
        if t.maxends[i] >= first(query)
            w, k = firstintersection(t.children[i], query, lower)
            w !== nothing && return notnothing(w), k
        end
        i += 1
    end

    return (nothing, 1)
end

function firstintersection(t::LeafNode{K, V, B}, query::Interval{K},
                           lower::Union{Nothing, Interval{K}}) where {K, V, B}

    if isempty(t) || t.maxend < first(query)
        return (nothing, 1)
    end

    i = lower === nothing ? 1 : searchsortedfirst(t.keys, notnothing(lower))

    while i <= length(t)
        if intersects(t.keys[i], query) &&
           (lower === nothing || !basic_isless(t.keys[i], notnothing(lower)))
            return (t, i)
        elseif last(query) < first(t.keys[i])
            break
        end
        i += 1
    end

    return (nothing, 1)
end

# Find the first key with an end point >= query
function firstfrom(t::IntervalBTree{K, V, B}, query::K) where {K, V, B}
    return firstfrom(t.root, query)
end

function firstfrom(t::InternalNode{K, V, B}, query::K) where {K, V, B}
    if isempty(t)
        return (t, 0)
    end

    for (i, child) in enumerate(t.children)
        if child.maxend >= query
            return firstfrom(child, query)
        end
    end

    return (t, 0)
end

function firstfrom(t::LeafNode{K, V, B}, query::K) where {K, V, B}
    if isempty(t)
        return (t, 0)
    end

    for i in 1:length(t)
        if last(t.keys[i]) >= query
            return (t, i)
        end
    end

    return (t, 0)
end

# If query intersects node.keys[i], return the next intersecting key as
# a (leafnode, index) pair.
function nextintersection!(t::LeafNode{K, V, B}, i::Integer,
                           query::AbstractInterval{K},
                           out::Intersection{K, V, B},
                           filter::F) where {F, K, V, B}
    j = i + 1
    while true
        while j <= length(t)
            if intersects(t.keys[j], query) && filter(t.entries[j], query)
                out.index = j
                out.node = t
                return
            end
            j += 1
        end
        j = 1

        t.right === nothing && break

        t = notnothing(t.right)

        if minstart(t) > last(query)
            break
        end

        # When we hit a leaf node with no possible intersections, we start over
        # from the top of the leaf. This is a heuristic to avoid linear search
        # behavior in the presence of extremely long intervals.
        if t.maxend < first(query)
            return firstintersection!(root(t), query,
                                      t.entries[t.count], out, filter)
        end
    end

    out.index = 0
    return
end

mutable struct IntervalIntersectionIterator{F, K, V, B}
    filter::F
    intersection::Intersection{K, V, B}
    t::IntervalBTree{K, V, B}
    query::AbstractInterval{K}

    function IntervalIntersectionIterator{F,K,V,B}(filter, intersection, t, query) where {F,K,V,B}
        return new{F,K,V,B}(filter, intersection, t, query)
    end

    function IntervalIntersectionIterator{F,K,V,B}() where {F,K,V,B}
        return new{F,K,V,B}(Intersection{F, K, V, B}())
    end
end

Base.eltype(::Type{IntervalIntersectionIterator{F,K,V,B}}) where {F,K,V,B} = V
Base.IteratorSize(::Type{IntervalIntersectionIterator{F,K,V,B}}) where {F,K,V,B} = Base.SizeUnknown()

# Intersect an interval tree t with a single interval, returning an iterator
# over the intersecting (key, value) pairs in t.
function Base.intersect(t::IntervalBTree{K, V, B}, query0::Tuple{Any, Any},
                        filter::F=true_cmp) where {F, K, V, B}
    query = Interval{K}(query0[1], query0[2])
    return intersect(t, query, filter)
end

function Base.intersect(t::IntervalBTree{K, V, B}, first::K, last::K,
                        filter::F=true_cmp) where {F, K, V, B}
    query = Interval{K}(first, last)
    return intersect(t, query, filter)
end

function Base.intersect(t::IntervalBTree{K, V, B}, query::AbstractInterval{K},
                        filter::F=true_cmp) where {F, K, V, B}
    return IntervalIntersectionIterator{F, K, V, B}(filter, Intersection{K, V, B}(), t, query)
end

function Base.iterate(it::IntervalIntersectionIterator{F, K, V, B}) where {F, K, V, B}
    firstintersection!(it.t, it.query, nothing, it.intersection, it.filter)
    return iterate(it, nothing)
end

function Base.iterate(it::IntervalIntersectionIterator{F, K, V, B}, _) where {F, K, V, B}
    intersection = it.intersection
    if intersection.index == 0
        return nothing
    end
    entry = intersection.node.entries[intersection.index]
    nextintersection!(intersection.node, intersection.index,
                      it.query, intersection, it.filter)
    return entry, nothing
end

mutable struct IntersectionIterator{F, K, V1, B1, V2, B2}
    filter::F

    t1::IntervalBTree{K, V1, B1}
    t2::IntervalBTree{K, V2, B2}

    # if true, use successive intersection, if false iterative.
    successive::Bool

    # true if done
    isdone::Bool

    # intersection state
    u::Union{Nothing, LeafNode{K, V1, B1}}
    v::Union{Nothing, LeafNode{K, V2, B2}}
    w::Union{Nothing, LeafNode{K, V2, B2}}
    i::Int
    j::Int
    k::Int

    function IntersectionIterator{F,K,V1,B1,V2,B2}(filter::F, t1, t2, successive::Bool) where {F,K,V1,B1,V2,B2}
        return new{F,K,V1,B1,V2,B2}(filter, t1, t2, successive)
    end
end

Base.eltype(::Type{IntersectionIterator{F,K,V1,B1,V2,B2}}) where {F,K,V1,B1,V2,B2} = Tuple{V1,V2}
Base.IteratorSize(::Type{IntersectionIterator{F,K,V1,B1,V2,B2}}) where {F,K,V1,B1,V2,B2} = Base.SizeUnknown()

function Base.iterate(it::IntersectionIterator, _=iterinitstate(it))
    if it.isdone
        return nothing
    end
    if it.successive
        u = it.u
        w = it.w
        value = (u.entries[it.i], w.entries[it.k])
        @nextleafkey(w, it.w, it.k)
        successive_nextintersection!(it)
    else
        u = it.u
        w = it.w
        value = (u.entries[it.i], w.entries[it.k])
        @nextleafkey(w, it.w, it.k)
        iterative_nextintersection!(it)
    end
    return value, nothing
end

function iterinitstate(it::IntersectionIterator)
    it.isdone = true
    if it.successive
        it.u = isempty(it.t1) ? nothing : firstleaf(it.t1)
        it.w = isempty(it.t2) ? nothing : firstleaf(it.t2)
        it.i, it.k = 1, 1
        successive_nextintersection!(it)
    else
        # Iterative Intersection: Intersect by iterating through the two
        # collections in unison.
        #
        # Notation notes. We proceed by finding all the the intervals
        # in t2 that intersect u.keys[i], before proceeding to the next
        # key in t1.
        #
        # We use v to keep track of the first leaf node in t2 that might contain an
        # intersecting interval, # and with (w, j) the current intersecting entry in
        # t2. We need to hang onto v since we will need to jump back when the
        # entry in t1 gets incremented.
        #
        # The thing to keep in mind is that this is just like the merge operation in
        # mergesort, except that some backtracking is needed since intersection
        # isn't as simple as ordering.

        it.u = isempty(it.t1) ? nothing : firstleaf(it.t1)
        it.v = isempty(it.t2) ? nothing : firstleaf(it.t2)
        it.w = it.v
        it.i, it.j, it.k = 1, 1, 1
        iterative_nextintersection!(it)
    end
    return nothing  # iterator is stateful
end

function iterative_nextintersection!(
    it::IntersectionIterator{F, K, V1, B1, V2, B2}) where {F, K, V1, B1, V2, B2}

    u, v, w, i, j, k = it.u, it.v, it.w, it.i, it.j, it.k

    it.isdone = true

    while true
        u === nothing && return
        unode = notnothing(u)
        ukey = unode.keys[i]

        v === nothing && return
        vnode = notnothing(v)
        vkey = vnode.keys[j]

        # find next intersection w
        while w !== nothing
            wnode = notnothing(w)
            wkey = wnode.keys[k]

            if first(wkey) <= last(ukey)
                if first(ukey) <= last(wkey) && it.filter(unode.entries[i], wnode.entries[k])
                    it.isdone = false
                    break
                end
                @nextleafkey(wnode, w, k)
            else
                break
            end
        end

        # intersection was found
        if !it.isdone
            break
        end

        # if no intersection found, advance u and start again
        @nextleafkey(unode, u, i)

        u === nothing && break

        # find new v corresponding to new u
        unode = notnothing(u)
        ukey = unode.keys[i]
        while v !== nothing
            vnode = notnothing(v)
            vkey = vnode.keys[j]

            if last(vkey) < first(ukey)
                @nextleafkey(vnode, v, j)
            else
                break
            end
        end

        w, k = v, j
    end

    it.u = u
    it.v = v
    it.w = w
    it.i = i
    it.j = j
    it.k = k
    return
end

function successive_nextintersection!(
    it::IntersectionIterator{F, K, V1, B1, V2, B2}) where {F, K, V1, B1, V2, B2}

    u, w, i, k = it.u, it.w, it.i, it.k

    it.isdone = true

    while true
        u === nothing && return
        unode = notnothing(u)
        ukey = unode.keys[i]

        # find next intersection w
        while w !== nothing
            wnode = notnothing(w)
            wkey = wnode.keys[k]

            if first(wkey) <= last(ukey)
                if first(ukey) <= last(wkey) && it.filter(unode.entries[i], wnode.entries[k])
                    it.isdone = false
                    break
                end

                # next w key
                if k < length(wnode)
                    k += 1
                else
                    k = 1
                    w = wnode.right

                    # When we hit a leaf node with no possible intersections, we
                    # start over from the top of the leaf. This is a heuristic
                    # to avoid linear search behavior in the presence of
                    # extremely long intervals.
                    if w !== nothing && w.maxend < first(ukey)
                        wnode = notnothing(w)
                        w, k = firstintersection(it.t2.root, ukey,
                                                 wnode.keys[wnode.count])
                    end
                end
            else
                break
            end
        end

        # intersection was found
        if !it.isdone
            break
        end

        # if no intersection found, advance u and start again
        @nextleafkey(unode, u, i)

        u === nothing && break

        # find u's first intersection
        unode = notnothing(u)
        ukey = unode.keys[i]

        w, k = firstintersection(it.t2.root, ukey, nothing)
    end

    it.u = u
    it.w = w
    it.i = i
    it.k = k
    return
end

# Intersect two interval trees, returning an iterator yielding values of the
# form:
#   ((key1, value1), (key2, value2))
#
# Where key1 is from the first tree and key2 from the second, and they
# intersect.
function Base.intersect(t1::IntervalBTree{K, V1, B1},
                        t2::IntervalBTree{K, V2, B2},
                        filter::F=true_cmp; method=:auto) where {F, K, V1, B1, V2, B2}
    # We decide heuristically which intersection algorithm to use.
    m = length(t1)
    n = length(t2)

    if method == :auto
        iterative_cost  = n + m
        successive_cost = 0.25 * m * log(1 + n) + 1e5
        if iterative_cost < successive_cost
            return IntersectionIterator{F, K, V1, B1, V2, B2}(filter, t1, t2, false)
        else
            return IntersectionIterator{F, K, V1, B1, V2, B2}(filter, t1, t2, true)
        end
    elseif method == :successive
        return IntersectionIterator{F, K, V1, B1, V2, B2}(filter, t1, t2, true)
    elseif method == :iterative
        return IntersectionIterator{F, K, V1, B1, V2, B2}(filter, t1, t2, false)
    else
        error("No such intersection method: $method")
    end
end

# Diagnostics
# -----------

# Dumb tree printing, useful only for debuging.
#function showtree(io::IO, t::IntervalBTree)
    #showtree(io, t.root, 0)
#end

#function showtree(t::IntervalBTree)
    #showtree(STDOUT, t)
#end

#function showtree(io::IO, t::InternalNode, indent::Int)
    #for (i, child) in enumerate(t.children)
        #showtree(io, child, indent+1)
        #if i <= length(t.keys)
            #print(io, repeat("  ", indent), t.keys[i], ":\n")
        #end
    #end
#end

#function showtree(io::IO, t::LeafNode, indent::Int)
    #for (k, v) in zip(t.keys, t.values)
        #print(io, repeat("  ", indent), k, ": ", v, "\n")
    #end
#end

include("map.jl")

end  # module IntervalTrees


include("abstractfeatures.jl")
using DataFrames
using ..ChemistryFeaturization: elements
using ..ChemistryFeaturization.AbstractType: AbstractCodec, AbstractAtoms
using ..ChemistryFeaturization.Codec: SimpleCodec
using ..ChemistryFeaturization.Utils.OrbitalFeatureUtils:
    _indexorbital, _econf_to_name, _orbitalsparse
using ..ChemistryFeaturization.Data: valenceshell_conf_df
using SparseArrays

"""
    OrbitalFeatureDescriptor

Describes the orbital configuration of an element.
"""
struct OrbitalFeatureDescriptor <: AbstractEnvironmentFeatureDescriptor
    encoder_decoder::AbstractCodec
    function OrbitalFeatureDescriptor()
        # trim down the DataFrame to have only the Symbol, and valence shell configuration (extracted from "Electronic Structure")
        new(SimpleCodec(default_ofd_encode, default_ofd_decode))
    end
end

function get_value(ofd::OrbitalFeatureDescriptor, el::String)
    @assert el in valenceshell_conf_df[:, :Symbol] "All elements must be valid and accounted for in the periodic table!"

    # pull the value for this element from the "Electronic Structure" column of the orbital data table
    getproperty(
        valenceshell_conf_df[valenceshell_conf_df.Symbol.==el, :][1, :],
        Symbol("Electronic Structure"),
    )
end

get_value(ofd::OrbitalFeatureDescriptor, a::AbstractAtoms) =
    map(e -> get_value(ofd, e), elements(a))

(ofd::OrbitalFeatureDescriptor)(el::String) = get_value(ofd, el)

function output_shape(ofd::OrbitalFeatureDescriptor, sc::SimpleCodec)
    # return the number
end

Base.show(io::IO, efd::OrbitalFeatureDescriptor) =
    print(io, "OrbitalFeatureDescriptor created.")

function Base.show(io::IO, ::MIME"text/plain", efd::OrbitalFeatureDescriptor)
    st = "OrbitalFeatureDescriptor created."
    print(io, st)
end

"""
    default_ofd_encode(ofd::OrbitalFeatureDescriptor, a::AbstractAtoms)

Default encoding scheme for OrbitalFeatureDescriptor, which returns a
sparse matrix, where column_i is a sparse representation of the electronic
configuration of the ith element in the [Atoms](@ref atoms) object.
"""
function default_ofd_encode(elec_configs::Vector{String})
    I = Vector{Int16}()
    J = Vector{Int16}()
    V = Vector{Int16}()
    col = 0
    for s in elec_configs
        # X, Y are equivalent to I, V for a SparseVector
        X, Y = _orbitalsparse(s)
        col += 1    # column number

        for i = 1:length(X)
            append!(I, X[i])
            append!(J, col)
            append!(V, Y[i])
        end
    end
    sparse(I, J, V) # create a SparseMatrix with the valence shell configuration of each element as a column vector
end

"""
    default_ofd_decode(
        ofd::OrbitalFeatureDescriptor,
        encoded_features::SparseArrays.AbstractSparseMatrixCSC{Tv,Ti}
    ) where {Tv,Ti}

Default decoding scheme for OrbitalFeatureDescriptor, which returns the
vector of elements encoded, given the encoded sparse matrix.
"""
function default_ofd_decode(
    encoded_features::SparseArrays.AbstractSparseMatrixCSC{Tv,Ti},
) where {Tv,Ti}
    elements = String[]
    for i = 1:size(encoded_features)[2]
        push!(elements, _econf_to_name(encoded_features[:, i]))
    end
    return elements
end


using CPIMC, CPIMC.PlaneWaves, CPIMC.UniformElectronGas, CPIMC.DefaultUpdates, DataStructures

import CPIMC: update!, orbs, adjacent_kinks_affecting_orbs, kinks_affecting_orbs, prev, next, next_affecting, prev_affecting, τ_prev_affecting, τ_next_affecting, τ_borders, isunaffected, isunaffected_in_interval, time_ordered_orbs, occupations_at, longest_type_1_chain_length, right_type_1_count, kinks_from_periodic_interval, times_from_periodic_interval, Δ, Woffdiag_element, ΔWoffdiag_element, ΔWdiag_element, ΔW_diag, add_orbs, add_orbs!, drop_orbs, drop_orbs!, drop_kinks, drop_kinks!, add_kinks, add_kinks!

S = sphere_with_same_spin(PlaneWave((0,0,0)),dk=1)
a = PlaneWave((-2,0,0))
b = PlaneWave((3,0,0))
c = PlaneWave((0,0,0))
d = PlaneWave((1,0,0))
e = PlaneWave((5,9,9))
f = PlaneWave((1,1,1))

g = PlaneWave(a.vec + e.vec - f.vec, Up)
h = PlaneWave(c.vec + d.vec - g.vec, Up)

sd = Kinks( ImgTime(0.2) => T4(a,b,c,d),
       ImgTime(0.5) => T4(c,d,a,b),
       ImgTime(0.6) => T4(b,a,d,c),
       ImgTime(0.8) => T4(d,c,b,a) )

conf = Configuration(S,sd)

@testset "apply_step" begin
    @test apply_step(conf, Step()) == conf
    @test apply_step(conf, Step((c,d),nothing,(e,f),nothing)).occupations == union(setdiff(S,(c,d)),(e,f))
end

@testset "apply_step!" begin
    c2 = Configuration(S,sd)
    apply_step!(c2, Step())
    @test c2 == conf
    apply_step!(c2, Step((c,d),nothing,(e,f),nothing))
    @test c2.occupations == union(setdiff(S,(c,d)),(e,f))
end

@testset "apply_step" for _ in (1:200)
    c1 = Configuration(sphere(PlaneWave((0,0,0),Up),dk=1))
    c2 = Configuration(sphere(PlaneWave((0,0,0),Up),dk=1))
    m = UEG()
    ens = CEnsemble(2,2,7)
    step_list = Array{Step,1}()
    for _ in (1:5)
        dv, Δ = add_type_B!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = add_type_C!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = add_type_D!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = add_type_E!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
    end
    for i in (1:3)
        dv, Δ = remove_type_B!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = remove_type_C!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = remove_type_D!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
        dv, Δ = remove_type_E!(m, ens, c1)

        if dv == 0
            apply_back_step!(c1, Δ)
        else
            push!(step_list, Δ)
        end
    end
    @test apply_step(c2,step_list).kinks == c1.kinks
    @test apply_step(c2,step_list).occupations == c1.occupations
end

@testset "update!" begin
    ens = CEnsemble(2.0, 5.680898543560106, 7)# θ: 0.125, λ: 0.09945178864947428
    c1 = Configuration(sphere_with_same_spin(PlaneWave((0,0,0)),dk=1))
    for _ in 1:100
        old_occs = copy(c1.occupations)
        old_kinks = copy(c1.kinks)
        coin = last(update!(UEG(), ens, c1, [move_particle!, add_type_B!, remove_type_B!, add_type_C!, remove_type_C!, add_type_D!, remove_type_D!, add_type_E!, remove_type_E!, shuffle_indices!]))
        if coin == :accept
            @test ((c1.occupations != old_occs) || (c1.kinks != old_kinks))
        elseif coin == :reject
            @test c1.occupations == old_occs
            @test c1.kinks == old_kinks
        elseif coin == :trivial
            @test c1.occupations == old_occs
            @test c1.kinks == old_kinks
        else
            @test(false)
        end
    end
end


import Gurobi
const GRBENV = Gurobi.Env()

function run_rmp_gurobi(finst)
    grb = Gurobi.Model(GRBENV, "")
    Gurobi.read_model(grb, finst)

    # Set parameters
    Gurobi.set_int_param!(grb, "OutputFlag", 1)
    Gurobi.set_int_param!(grb, "Threads", 1)
    Gurobi.set_int_param!(grb, "Method", 2)
    Gurobi.set_int_param!(grb, "Crossover", 0)
    Gurobi.set_int_param!(grb, "Presolve", 0)

    # Solve model
    t = @elapsed Gurobi.optimize(grb)

    println("Solve time: $t")
    println("Barrier iterations: ", Gurobi.get_intattr(grb, "BarIterCount"))
    return nothing
end

if abspath(PROGRAM_FILE) == @__FILE__
    run_rmp_gurobi(ARGS[1])
end


# auxiliary function to perform timings for action
function time_kernels!(
    to :: TimerOutput,
    r  :: Reduced_lattice,
    m  :: Mesh,
    a  :: Action,
    ap :: Action
    )  :: Nothing

    # get timer parameters
    Λ   = rand()
    v   = rand()
    dv  = rand()
    wc  = rand()
    vc  = rand()
    vcp = rand()

    # init buffers for evaluation of rhs
    buff = zeros(Float64, length(a.Γ), length(r.sites))
    temp = zeros(Float64, length(r.sites), length(a.Γ), 2)

    # fill self energy with random values
    a.Σ  .= rand(31)
    ap.Σ .= rand(31)

    # fill channels with random values
    for i in eachindex(a.Γ)
        a.Γ[i].ch_s.q1 .= rand(length(r.sites), 31)
        a.Γ[i].ch_t.q1 .= rand(length(r.sites), 31)
        a.Γ[i].ch_u.q1 .= rand(length(r.sites), 31)

        a.Γ[i].ch_s.q2_1 .= rand(length(r.sites), 31, 31)
        a.Γ[i].ch_t.q2_1 .= rand(length(r.sites), 31, 31)
        a.Γ[i].ch_u.q2_1 .= rand(length(r.sites), 31, 31)

        a.Γ[i].ch_s.q2_2 .= rand(length(r.sites), 31, 31)
        a.Γ[i].ch_t.q2_2 .= rand(length(r.sites), 31, 31)
        a.Γ[i].ch_u.q2_2 .= rand(length(r.sites), 31, 31)

        a.Γ[i].ch_s.q3 .= rand(length(r.sites), 31, 31, 31)
        a.Γ[i].ch_t.q3 .= rand(length(r.sites), 31, 31, 31)
        a.Γ[i].ch_u.q3 .= rand(length(r.sites), 31, 31, 31)

        ap.Γ[i].ch_s.q1 .= rand(length(r.sites), 31)
        ap.Γ[i].ch_t.q1 .= rand(length(r.sites), 31)
        ap.Γ[i].ch_u.q1 .= rand(length(r.sites), 31)

        ap.Γ[i].ch_s.q2_1 .= rand(length(r.sites), 31, 31)
        ap.Γ[i].ch_t.q2_1 .= rand(length(r.sites), 31, 31)
        ap.Γ[i].ch_u.q2_1 .= rand(length(r.sites), 31, 31)

        ap.Γ[i].ch_s.q2_2 .= rand(length(r.sites), 31, 31)
        ap.Γ[i].ch_t.q2_2 .= rand(length(r.sites), 31, 31)
        ap.Γ[i].ch_u.q2_2 .= rand(length(r.sites), 31, 31)

        ap.Γ[i].ch_s.q3 .= rand(length(r.sites), 31, 31, 31)
        ap.Γ[i].ch_t.q3 .= rand(length(r.sites), 31, 31, 31)
        ap.Γ[i].ch_u.q3 .= rand(length(r.sites), 31, 31, 31)
    end

    for rep in 1 : 10
        # time parquet equations
        @timeit to "=> parquet" begin
            # time SDE
            @timeit to "-> SDE" compute_Σ_kernel(Λ, v, wc, r, m, a, ap)

            # time BSE in all channels
            @timeit to "-> BSE s channel" compute_s_BSE!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, temp)
            @timeit to "-> BSE t channel" compute_t_BSE!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, temp)
            @timeit to "-> BSE u channel" compute_u_BSE!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, temp)
        end

        # time flow equations
        @timeit to "=> FRG" begin
            # time self energy
            @timeit to "-> Σ one loop" compute_dΣ_kernel(Λ, v, wc, r, m, a)
            @timeit to "-> Σ corr 1"   compute_dΣ_kernel_corr1(Λ, v, wc, r, m, a, ap)
            @timeit to "-> Σ corr 2"   compute_dΣ_kernel_corr2(Λ, v, wc, r, m, a, ap)

            # time Katanin part in all channels
            @timeit to "-> Katanin s channel" compute_s_kat!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> Katanin t channel" compute_t_kat!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> Katanin u channel" compute_u_kat!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)

            # time left part in all channels
            @timeit to "-> left s channel" compute_s_left!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> left t channel" compute_t_left!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> left u channel" compute_u_left!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)

            # time central part in all channels
            @timeit to "-> central s channel" compute_s_central!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> central t channel" compute_t_central!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
            @timeit to "-> central u channel" compute_u_central!(Λ, buff, v, dv, wc, vc, vcp, r, m, a, ap, temp)
        end
    end

    return nothing
end

"""
    get_flow_timers() :: Nothing

Time current implementation of flow equations by running the different integration kernels.
"""
function get_flow_timers() :: Nothing

    println("NOTE: TIMERS BETWEEN DIFFERENT SYMMETRIES SHALL NOT BE COMPARED (RESULTS ARE MODEL DEPENDENT).")
    println()

    # init test dummys
    list = get_mesh(rand(), 1.0, 30, 0.4)
    m    = Mesh(31, 31, 31, 31, list, list, list, list, list, list)

    # init timer
    to = TimerOutput()

    # time evals of integration kernels for action_su2
    @timeit to "=> action_su2" begin
        # generate action dummy for square lattice Heisenberg model
        l  = get_lattice("square", 5, verbose = false)
        r  = get_reduced_lattice("heisenberg", [[1.0]], l, verbose = false)
        a  = get_action_empty("su2", r, m); init_action!(l, r, a)
        ap = get_action_empty("su2", r, m)

        # time integration kernels
        time_kernels!(to, r, m, a, ap)
    end

    # time evals of integration kernels for action_u1_dm
    @timeit to "=> action_u1_dm" begin
        # generate action dummy for triangular lattice dm-c3 model
        l  = get_lattice("triangular", 5, verbose = false)
        r  = get_reduced_lattice("triangular-dm-c3", [[1.0, 1.0, 1.0]], l, verbose = false)
        a  = get_action_empty("u1-dm", r, m); init_action!(l, r, a)
        ap = get_action_empty("u1-dm", r, m)

        # time integration kernels
        time_kernels!(to, r, m, a, ap)
    end

    show(to)

    return nothing
end