text:
using SolidStateDetectors

T = Float32
sim = Simulation{T}(SSD_examples[:CGD])
calculate_electric_potential!(sim,
    max_refinements = 0,
    max_n_iterations = 10,
    init_grid_spacing =  T.((0.01, 0.01, 0.01)),
)

g = sim.electric_potential.grid
ax1, ax2, ax3 = g.axes
user_additional_ticks_ax1 = T[0.002123, 0.002133] # here add your ticks of interest
user_additional_ticks_ax2 = T[0.003123] # here add your ticks of interest
user_additional_ticks_ax3 = T[0.004123] # here add your ticks of interest
user_ax1 = typeof(ax1)(ax1.interval, sort(vcat(ax1.ticks, user_additional_ticks_ax1)))
user_ax2 = typeof(ax2)(ax2.interval, sort(vcat(ax2.ticks, user_additional_ticks_ax2)))
user_ax3 = typeof(ax3)(ax3.interval, sort(vcat(ax3.ticks, user_additional_ticks_ax3)))
user_g = typeof(g)((user_ax1, user_ax2, user_ax3))

calculate_electric_potential!(sim, grid = user_g)

max_stars_repo_name: leenderthayen/NDP

max_issues_repo_name: leenderthayen/NDP

max_forks_repo_name: leenderthayen/NDP


text:
using Logging
using Unicode

using DataFrames
using CSV
using JSON

function loadplants()::DataFrame
    plants = CSV.read("data/plants.csv")
    @info "Loaded $(size(plants, 1)) plants"
    plants.name = Symbol.(plants.name)
    plants
end

function loadgarden(plants::Vector{Symbol})::Tuple{Matrix{Int}, Matrix{Bool}}
    garden = CSV.read("data/garden.csv")
    garden = coalesce.(garden, "")
    mask = convert(Matrix, garden .== "empty")
    garden = Unicode.normalize.(garden, casefold=true, stripmark=true)
    garden = indexin(convert(Matrix, garden), String.(plants))
    garden = replace(garden, nothing=>0)
    @assert size(garden) == size(mask)
    @info "Loaded garden of size $(size(garden))"
    garden, mask
end

function loadclassification()::Classification
    clf = JSON.parsefile("data/classification.json")
    clf = Classification(clf)
    @debug "loaded classification of type $(clf.type)"
    clf
end

function loadaffinitiesdf()::DataFrame
    df = CSV.read("data/associations.csv", copycols=true)
    colnames = String.(names(df))
    colnames = Symbol.(Unicode.normalize.(colnames, casefold=true, stripmark=true))
    rename!(df, colnames)
    df.name = colnames[2:end]
    # df = coalesce.(df, 0.0)
    @info "Loaded affinity matrix for $(size(df, 1)) plants"
    df
end

"Compute the cost between two plants, using their families if necessary."
function computecost(plant1::Symbol, plant2::Symbol, affinities_df::DataFrame, classification::Classification)::Float64
    @debug "computecost($plant1, $plant2)"
    if plant1 in names(affinities_df) && plant2 in names(affinities_df)
        affinity = affinities_df[affinities_df.name .== plant1, plant2][1]
    else
        affinity = missing
    end
    if !ismissing(affinity)
        return -affinity
    end

    parent1 = getfirstparent(plant1, classification)
    parent2 = getfirstparent(plant2, classification)
    if isnothing(parent1) || isnothing(parent2)
        return 0.0
    end

    @debug "computecost($(parent1.name), $plant2)"
    if parent1.name in names(affinities_df) && plant2 in names(affinities_df)
        affinity = affinities_df[affinities_df.name .== parent1.name, plant2][1]
    end
    if !ismissing(affinity)
        return -affinity
    end

    @debug "computecost($plant1, $(parent2.name))"
    if plant1 in names(affinities_df) && parent2.name in names(affinities_df)
        affinity = affinities_df[affinities_df.name .== plant1, parent2.name][1]
    end
    if !ismissing(affinity)
        return -affinity
    end

    @debug "computecost($(parent1.name), $(parent2.name))"
    if parent1.name in names(affinities_df) && parent2.name in names(affinities_df)
        affinity = affinities_df[affinities_df.name .== parent1.name, parent2.name][1]
    end
    if !ismissing(affinity)
        return -affinity
    end

    return 0.0
end

"Compute the costs matrix for all plants"
function costsmatrix(plants::Vector{Symbol}, affinities_df::DataFrame, classification::Classification)::Matrix{Float64}
    [computecost(plant1, plant2, affinities_df, classification) for plant1 in plants, plant2 in plants]
end

function loadcosts(plants::Vector{Symbol})
    clf = loadclassification()
    affinities_df = loadaffinitiesdf()
    costs = costsmatrix(plants, affinities_df, clf)
    @info "Computed costs matrix for $(size(costs, 1)) plants"
    costs
end

ext: jl

max_stars_repo_name: dlozeve/GardenOptim

max_issues_repo_name: dlozeve/GardenOptim

max_forks_repo_name: dlozeve/GardenOptim


text:
@discord_object struct VoiceRegion
    id::String
    name::String
    vip::Bool
    optimal::Bool
    deprecated::Bool
    custom::Bool
end


text:
using NewOptimizer
using Test
using Base.Meta

function undef_vars(x::Bool)
    if x
        a = 1
    end
    a
end

let ir = NewOptimizer.run_passes(undef_vars, Tuple{Bool})
    phi = first(filter(stmt->isa(stmt, NewOptimizer.PhiNode), ir.stmts))
    @test 1 == count(i->!isassigned(phi.values, i), 1:length(phi.values))
end

function loop()
    a = 1
    for i = 1:10
        a = i
    end
    a
end

let ir = NewOptimizer.run_passes(loop, Tuple{})
    # The first block is completely dead. Make
    # sure we maintain it nevertheless for CFG integrity
    @test length(ir.cfg.blocks) == 5
    @test length(ir.cfg.blocks[1].stmts) == 1
    @test ir.stmts[1] == nothing
end

# Test from base
struct A15259
    x
    y
end
# check that allocation was ellided
@eval f15259(x,y) = (a = $(Expr(:new, :A15259, :x, :y)); (a.x, a.y, getfield(a,1), getfield(a, 2)))
let ir = NewOptimizer.run_passes(f15259, Tuple{Any, Any})
    @test !any(expr->isexpr(expr, :new), ir.stmts)
end

# Motivating example (25663)
@noinline foo() = rand(Bool) ? 2 : nothing
@inline baz(x) = x ? (foo(), 1) : nothing
function bar(arg)
    x = baz(arg)
    x === nothing && return 0
    a, b = x
    a === nothing && return 1
    return a
end

let ir = NewOptimizer.run_passes(loop, Tuple{})
    @test !any(expr->NewOptimizer.is_call(expr, :tuple), ir.stmts)
end

struct Wrapper{T}
    x::T
end

@eval function foo(x::T) where {T}
    $(Expr(:new, Wrapper, :x)).x
end

let ir = NewOptimizer.run_passes(foo, Tuple{Any})
    @test !any(expr->NewOptimizer.is_call(expr, :getfield), ir.stmts)
end

using NewOptimizer
using NotInferenceDontLookHere

function bar()
    if rand(Bool)
        x = nothing
    else
        x = missing
    end
    x
end
@NI.code_typed bar()

function baz()
    if rand(Bool)
        x = 1
    else
    end
    x
end
@NI.code_typed baz()
baz()

function foo()
    if rand(Bool)
        x = 1
    else
    end
    @isdefined x
end
@NI.code_typed foo()
foo()

function funion(x,y)
    if y
       z = x[1] in (:isequal, :isapprox, :≈)
    else
       z = false
    end
    if z
       return 1
    else
       return 2
    end
end
@NI.code_typed funion(Any[:isequal], true)
funion(Any[:isequal], true)

function tuple_phi()
    x = (1, 1)
    while x[1] <= 10
        ccall(:jl_, Cvoid, (Any,), x[2])
        x = (1, x[2]+1)
    end
    x
end

function arg_tuple(x::Bool, y::Tuple{Int, Int})
    if x
        y = (1, 1)
    end
    y[2]
end
@test arg_tuple(true, (2, 2)) == 1
@test arg_tuple(false, (2, 2)) == 2


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule StarPU_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("StarPU")
JLLWrappers.@generate_main_file("StarPU", UUID("e3ad0b27-b140-5312-a56e-059adfc55eb4"))
end  # module StarPU_jll


text:
# SteinProbitRegression
#
# We assume we have the following model:
#
# y_i = \Psi (beta_1 x_1 + ... + beta_p x_p)
#
# where \Psi is assumed to be the noraml CDF (and a flat prior for beta).
# We sample this by introducing the auxiliary variables
# y_i = I{z_i > 0}. Hence we have
#
# beta | Z, y ~ N((X^t X)^-1 (X_t Z), (X^t X)^-1)
# Z | beta, y ~ TN(X beta, I, y)
#
# where TN(mu, sigma, t) is a truncated Gaussian
# with mean mu, variance sigma and truncated to be
# positive if t == 1 and negative if t == 0.

using Distributions: TruncatedNormal, Normal
import Distributions

type SteinProbitRegression <: SteinPosterior
    # the X data
    X::Array{Float64,2}
    # the 0/1 y values
    y::Array{Float64,1}
    # sigma2 is the variance of Z given beta
    sigma2::Float64
end

# Values used in Seth Tribble's thesis:
defaultsigma2 = 1.0

SteinProbitRegression(X, y) =
    SteinProbitRegression(X, y, defaultsigma2)

# Returns lower bound of support
function supportlowerbound(d::SteinProbitRegression, i::Int)
    -Inf
end

# Returns upper bound of support
function supportupperbound(d::SteinProbitRegression, i::Int)
    Inf
end

function rungibbs(d::SteinProbitRegression,
                  n::Int;
                  qmcvariates=[])
    X = d.X
    m, p = size(X)
    betas = Array(Float64, n+1, p)
    Zs = Array(Float64, n+1, m)
    gsn = Normal()
    # work with qmc variates or iid ones
    if size(qmcvariates,1) != 0
        @assert size(qmcvariates) == (n, p+m)
        randvariates = qmcvariates
    else
        randvariates = rand(n, p+m)
    end
    # initialize the samples (z_i = I{y > 0})
    Zs[1,:] = round(Int, d.y .> 0.0)
    betas[1,:] = 0.0
    # do the gibbs!
    for i in 1:n
        # sample the betas
        xtxinv = inv(X' * X)
        xtxinvchol = chol(xtxinv)
        xtz = X' * vec(Zs[i,:])
        beta_mean = vec(xtxinv * xtz)

        noise = zeros(p)
        for j in 1:p
            noise[j] = Distributions.quantile(gsn, randvariates[i,j])
        end
        betas[i+1,:] = beta_mean + vec(xtxinvchol * noise)
        # now sample the z's
        zmean = vec(X * vec(betas[i+1,:]))
        for j in 1:m
            if d.y[j] > 0.0
                g = TruncatedNormal(zmean[j], d.sigma2, 0.0, Inf)
            else
                g = TruncatedNormal(zmean[j], d.sigma2, -Inf, 0.0)
            end
            Zs[i+1,j] = Distributions.quantile(g, randvariates[i,p+j])
        end
    end
    # discard the first point
    betas[2:end,:]
end

# Returns the log prior log pi(beta)
function logprior(d::SteinProbitRegression,
                  beta::Array{Float64, 1})
    0.0
end

# Returns log pi(d.y[idx] | beta)
function loglikelihood(d::SteinProbitRegression,
                       beta::Array{Float64, 1};
                       idx=1:length(d.y))
    y = d.y[idx]
    X = d.X[idx,:]
    ypos = find(y .> 0.0)
    yneg = find(y .<= 0.0)
    u = vec(X * beta)
    gsn = Normal()

    logprobpos = Distributions.logcdf(gsn, u[ypos])
    logprobneg = log(1.0 - Distributions.cdf(gsn, u[yneg]))
    sum(logprobpos) + sum(logprobneg)
end

# Returns the log prior log pi(beta)
function gradlogprior(d::SteinProbitRegression,
                      beta::Array{Float64, 1})
    zeros(size(beta,1))
end

# Returns grad_{beta} log pi(d.y[idx] | beta)
function gradloglikelihood(d::SteinProbitRegression,
                           beta::Array{Float64,1};
                           idx=1:length(d.y))
    y = d.y[idx]
    X = d.X[idx,:]
    ypos = find(y .> 0.0)
    yneg = find(y .<= 0.0)
    u = vec(X * beta)
    gsn = Normal()

    posweights = Distributions.pdf(gsn, u[ypos]) ./ Distributions.cdf(gsn, u[ypos])
    negweights = -Distributions.pdf(gsn, u[yneg]) ./ (1.0 - Distributions.cdf(gsn, u[yneg]))

    xpos = vec(sum(broadcast(*, X[ypos,:], posweights), 1))
    xneg = vec(sum(broadcast(*, X[yneg,:], negweights), 1))

    xneg + xpos
end

function numdimensions(d::SteinProbitRegression)
    size(d.X, 2)
end


text:
@testset "Missing ex" begin
    # these tests only serve as complement to the rest to exemplify
    # the behaviour when there are missing values / any types
    # these tests are more meant to showcase behaviour than thorough testing
    # the rest of the tests are more thorough.

    ## Arr{Union{Missing,T}} --> FINITE
    v  = [1, 2, missing]
    cv = coerce(v, Union{Missing,OrderedFactor})
    @test elscitype(cv) == Union{Missing,OrderedFactor{2}}
    cv = coerce(v[1:2], Union{Missing,OrderedFactor})
    @test elscitype(cv) == Union{Missing,OrderedFactor{2}}

    ## CArr{Union{Missing,T}} --> FINITE
    v  = categorical(['a', 'b', missing])
    cv = coerce(v, Union{Missing,Multiclass})
    @test elscitype(cv) == Union{Missing,Multiclass{2}}
    cv = coerce(v[1:2], Union{Missing,Multiclass}) # no true missing
    @test elscitype(cv) == Union{Missing,Multiclass{2}}

    ## Arr{Union{Missing,T}} --> INFINITE
    v  = [1, 2, 3, missing]
    cv = coerce(v, Union{Missing,Count})
    @test elscitype(cv) == Union{Missing,Count}
    cv = coerce(v[1:3], Union{Missing,Count})
    @test elscitype(cv) == Union{Missing,Count}

    ## CArr{Union{Missing,T}} --> INFINITE
    v  = categorical([1, 2, 3, missing])
    cv = coerce(v, Union{Missing,Count})
    @test elscitype(cv) == Union{Missing,Count}
    cv = coerce(v[1:3], Count)   # NOTE: broadcast
    @test elscitype(cv) == Count

    ## WARNINGs
    v = categorical(['a', 'b', missing])
    @test_logs (:warn,
        "Trying to coerce from `Union{Missing, Char}` to `Multiclass`.\nCoerced to `Union{Missing,Multiclass}` instead."
        ) (cv = coerce(v, Multiclass))
    @test elscitype(cv) == Union{Missing,Multiclass{2}}
end

@testset "Any ex" begin
    # Arr{Any} --> FINITE
    v  = Any[1, 2, 3]
    cv = coerce(v, Union{Missing,OrderedFactor})
    @test elscitype(cv) == Union{Missing,OrderedFactor{3}}
    v  = Any[1, 2, 3, missing]
    cv = coerce(v, Union{Missing,OrderedFactor})
    @test elscitype(cv) == Union{Missing,OrderedFactor{3}}

    # CArr{Any} --> FINITE
    v  = categorical(Any['a', 'b', 'c'])
    cv = coerce(v, Union{Missing,Multiclass})
    @test elscitype(cv) == Union{Missing,Multiclass{3}}
    v  = categorical(Any['a', 'b', missing])
    cv = coerce(v, Union{Missing,Multiclass})
    @test elscitype(cv) == Union{Missing,Multiclass{2}}
    cv = coerce(v[1:2], Union{Missing,Multiclass}) # no true missing
    @test elscitype(cv) == Union{Missing,Multiclass{2}}

    # Arr{Any} --> Infinite
    v  = Any[1.0, 2.0, 5.3]
    cv = coerce(v, Continuous)
    @test elscitype(cv) == Continuous

    # CArr{Any} --> Infinite
    v  = categorical(Any[1.0, 2.0, 3.0])
    cv = coerce(v, Continuous)
    @test elscitype(cv) == Continuous

    ## Warnings
    v = Any['A', 1, 2]
    @test_logs (:warn,
        "Char value encountered, such value will be coerced according to the corresponding numeric value (e.g. 'A' to 65)."
        ) (cv = coerce(v, Count))
    @test cv == [65, 1, 2]
    @test elscitype(cv) == Count

    v = categorical(Any[1,2,3])
    @test_logs (:warn,
        "Trying to coerce from `Any` to `OrderedFactor` with categoricals.\nCoerced to `Union{Missing,OrderedFactor}` instead."
        ) (cv = coerce(v, OrderedFactor))
    @test elscitype(cv) == Union{Missing,OrderedFactor{3}}
end

@testset "(MLJ)->Finite" begin
    # char / string
    char    = ['a','b','c']
    char_m  = [char..., missing]
    char_nm = char_m[1:end-1]
    str     = ["a", "b", "c"]
    str_m   = [str..., missing]
    str_nm  = str_m[1:end-1]
    # real
    int     = [1, 2, 3]
    int_m   = [int..., missing]
    int_nm  = int_m[1:end-1]
    fl      = [1.0,2.0,3.0]
    fl_m    = [fl..., missing]
    fl_nm   = fl_m[1:end-1]
    frac    = [1//2, 2//2, 3//4]
    frac_m  = [frac..., missing]
    frac_nm = frac_m[1:end-1]
    # any
    any_int    = Any[1,2,3]
    any_int_m  = Any[1,2,3,missing]
    any_int_nm = any_int_m[1:end-1]
    any_str    = Any["a","b","c"]
    any_str_m  = Any["a","b","c",missing]
    any_str_nm = any_str_m[1:end-1]
    # categorical
    cat_int    = categorical([1,2,3])
    cat_int_m  = categorical([1,2,3,missing])
    cat_int_nm = cat_int_m[1:end-1]
    cat_any    = categorical(Any[1,2,3])
    cat_any_m  = categorical(Any[1,2,3,missing])
    cat_any_nm = cat_int_m[1:end-1]

    for T in (Multiclass, OrderedFactor)
        # coercion without missing
        for ex in (char, int, fl, frac, cat_int)
            c = coerce(ex, T)
            @test elscitype(c) == T{3}
        end
        # coercion with missing or any
        for ex in (char_m, int_m, fl_m, frac_m,
                   any_int, any_int_m, any_str, any_str_m,
                   cat_int_m, cat_any, cat_any_m)
            c = coerce(ex, Union{Missing,T})
            @test elscitype(c) == Union{Missing,T{3}}
        end
        # coercion without true missing, tight=false
        for ex in (char_nm, str_nm, int_nm, fl_nm, frac_nm,
                   any_int_nm, any_str_nm, cat_int_nm, cat_any_nm)
            c = coerce(ex, Union{Missing,T})
            @test elscitype(c) == Union{Missing,T{3}}
        end
        #  coercion without true missing, tight=true
        for ex in (char_nm, str_nm, int_nm, fl_nm, frac_nm,
                   any_int_nm, any_str_nm, cat_int_nm, cat_any_nm)
            c = coerce(ex, T, tight=true)
            @test elscitype(c) == T{3}
        end
    end
end

@testset "(MLJ)->Infinite" begin
    int     = [1, 2, 3]
    int_m   = [int..., missing]
    int_nm  = int_m[1:end-1]
    fl      = [1.0,2.0,3.0]
    fl_m    = [fl..., missing]
    fl_nm   = fl_m[1:end-1]
    frac    = [1//2, 2//2, 3//4]
    frac_m  = [frac..., missing]
    frac_nm = frac_m[1:end-1]
    # any
    any_int    = Any[1,2,3]
    any_int_m  = Any[1,2,3,missing]
    any_int_nm = any_int_m[1:end-1]
    # categorical
    cat_int    = categorical([1,2,3])
    cat_int_m  = categorical([1,2,3,missing])
    cat_int_nm = cat_int_m[1:end-1]
    cat_any    = categorical(Any[1,2,3])
    cat_any_m  = categorical(Any[1,2,3,missing])
    cat_any_nm = cat_int_m[1:end-1]

    ## COUNT

    # coercion without missing, note that with cat and any
    # int gets broadcasted
    for ex in (int, any_int, cat_int, cat_any)
        c = coerce(ex, Count)
        @test elscitype(c) == Count
    end
    # coercion with missing
    for ex in (int_m, any_int_m, cat_int_m, cat_any_m)
        c = coerce(ex, Union{Missing,Count})
        @test elscitype(c) == Union{Missing,Count}
    end
    # coercion without true missing and tight=false
    begin
        # Note that for any and categorical, the `int` is broadcasted
        c = coerce(int_nm, Union{Missing,Count})
        @test elscitype(c) == Union{Missing,Count}
        c = coerce(any_int_nm, Count) # broadcasted
        @test elscitype(c) == Count
        c = coerce(cat_int_nm, Count) # broadcasted
        @test elscitype(c) == Count
        c = coerce(cat_any_nm, Count) # broadcasted
        @test elscitype(c) == Count
    end
    # coercion without true missing and tight=true
    c = coerce(int_nm, Count, tight=true)
    @test elscitype(c) == Count

    ## CONTINUOUS

    # coercion without missing, similar story as for Count
    for ex in (int, fl, frac, any_int, cat_int, cat_any)
        c = coerce(ex, Continuous)
        @test elscitype(c) == Continuous
    end
    # coercion with missing
    for ex in (int_m, fl_m, frac_m, any_int_m, cat_int_m, cat_any_m)
        c = coerce(ex, Union{Missing,Continuous})
        @test elscitype(c) == Union{Missing,Continuous}
    end
    # coercion without true missing and tight=false
    # similar story as with Count
    begin
        for ex in (int_nm, fl_nm, frac_nm)
            c = coerce(ex, Union{Missing,Continuous})
            @test elscitype(c) == Union{Missing,Continuous}
        end
        for ex in (any_int_nm, cat_int_nm, cat_any_nm)
            c = coerce(ex, Continuous)
            @test elscitype(c) == Continuous
        end
    end
    # coercion without true missing and tight=true
    for ex in (int_nm, fl_nm, frac_nm)
        c = coerce(ex, Continuous, tight=true)
        @test elscitype(c) == Continuous
    end
end

@testset "Autotype+tight" begin
    x = [1,2,3,missing];
    x = x[1:3]
    y = randn(3)
    X = (x = x, y = y)
    d = autotype(X, :discrete_to_continuous)
    Xc = coerce(X, d)
    @test elscitype(Xc.x) == Union{Missing,Continuous}
    Xc = coerce(X, d, tight=true)
    @test elscitype(Xc.x) == Continuous
end

@testset "Extra tight" begin
    a = categorical(Any[1,2,3])
    # calls get.
    c = coerce(a, Multiclass; tight=true)
    @test elscitype(c) == Multiclass{3}
end

@testset "Scitype/tight" begin
    x = [1,2,3,missing]
    @test elscitype(x) == Union{Missing,Count}
    @test elscitype(x[1:3]) == Union{Missing,Count}
    @test elscitype(x[1:3], tight=true) == Count

    df = DataFrame(X = [1,2,3,missing], Y = [1.0,2.0,3.0,missing])
    sch = schema(df)
    @test sch.scitypes[1] == Union{Missing,Count}
    @test sch.scitypes[2] == Union{Missing,Continuous}
    df2 = df[1:3,:]
    sch = schema(df2)
    @test sch.scitypes[1] == Union{Missing,Count}
    @test sch.scitypes[2] == Union{Missing,Continuous}
    sch = schema(df2, tight=true)
    @test sch.scitypes[1] == Count
    @test sch.scitypes[2] == Continuous
end


text:
module Phonetics

include("PhoneticEncodings.jl")
include("PhoneticComparisons.jl")
include("PhoneticUtils.jl")
include("PhoneticMeasures.jl")

end


text:
# # module PlanningPredicates
# #
# # using Parameters

export
    AbstractPlanningPredicate,
    AbstractRobotAction,
	AbstractSingleRobotAction,
    get_object_id,
    get_location_id,
    get_location_ids,
	get_robot_id,
	get_initial_location_id,
	get_destination_location_id,
	robot_type,
	graph_key,
	has_object_id,
	check_object_id,
	has_robot_id,
	get_default_robot_id,
	robot_ids_match,
	get_default_initial_location_id,
	get_default_final_location_id,
	replace_robot_id,
	replace_destination,
	replace_initial_location,
	resources_reserved,
	align_with_predecessor,
	align_with_successor,
	split_node,
	matches_node_type,
	id_type,
	title_string

abstract type AbstractPlanningPredicate end
abstract type AbstractRobotAction{R<:AbstractRobotType} <: AbstractPlanningPredicate end
abstract type AbstractSingleRobotAction{R<:AbstractRobotType} <: AbstractRobotAction{R} end
abstract type AbstractTeamRobotAction{R<:AbstractRobotType} <: AbstractRobotAction{R} end

function get_object_id end
function get_robot_id end
function get_initial_location_id end
function get_destination_location_id end
function get_location_id end
function get_location_ids end

robot_type(a::AbstractRobotAction{R}) where {R} = R
robot_type(a) = Nothing
graph_key() = Symbol(DefaultRobotType)
function graph_key(a)
	if robot_type(a) == Nothing
		return graph_key()
	else
		return Symbol(robot_type(a))
	end
end

has_object_id(a) = false

"""
    Check if a node is associated with objectid
"""
function check_object_id(node,o)
    if has_object_id(node)
        if get_object_id(node) == o
            return true
        end
    end
    return false
end

has_robot_id(a) = robot_type(a) == Nothing ? false : true
get_default_robot_id(a) = has_robot_id(a) ? get_robot_id(a) : RobotID()
get_default_initial_location_id(a) = has_robot_id(a) ? get_initial_location_id(a) : LocationID()
get_default_destination_location_id(a) = has_robot_id(a) ? get_destination_location_id(a) : LocationID()

"""
	robot_ids_match(node,node2)

Checks if robot_ids match between the nodes
"""
function robot_ids_match(node,node2)
	if has_robot_id(node) && has_robot_id(node2)
		if get_id(get_robot_id(node)) != -1 && get_id(get_robot_id(node2)) != -1
			status = (get_robot_id(node) == get_robot_id(node2)) ? true : false
			return status
		end
	end
	return true
end

function replace_robot_id end
function replace_destination end
function replace_initial_location end

"""
	resources_reserved(node)

Identifies the resources reserved by a particular `node` for its duration.
For example, `resources_reserved(node::COLLECT) = AbstractID[get_location_id(node)]`
"""
resources_reserved(node) = AbstractID[]

"""
	align_with_predecessor(node,succ)

Modifies a node to match the information encoded by its predecessor. This is
how e.g., robot ids are propagated through an existing operating schedule
after assignments (or re-assignments) have been made.
"""
align_with_predecessor(node,pred) = node

CRCBS.is_valid(id::A) where {A<:AbstractID} = valid_id(id) #get_id(id) != -1
first_valid(a,b) = CRCBS.is_valid(a) ? a : b

"""
	align_with_successor(node,succ)

Modifies a node to match the information encoded by its successor. This is
how e.g., robot ids are propagated through an existing operating schedule
after assignments (or re-assignments) have been made.
"""
align_with_successor(node,succ) = node

"""
	split_node(node::N,x::LocationID)

Creates two new nodes of type `N`, where the destination of the first node
and the starting location of the second node are both set to `x`.
"""
function split_node end

"""
    matches_node_type(::A,::Type{B}) where {A<:AbstractPlanningPredicate,B}

Returns true if {A <: B}
"""
matches_node_type(::A,::Type{B}) where {A,B} = A <: B

title_string(a) = string(a)

const predicate_accessor_interface = [
	:get_initial_location_id,
	:get_destination_location_id,
	:get_robot_id,
	:get_default_robot_id,
	:get_default_initial_location_id,
	:get_default_final_location_id,
	:get_object_id,
	:has_object_id,
	:has_robot_id,
	:sub_nodes,
	:(GraphUtils.required_successors),
	:(GraphUtils.required_predecessors),
	:(GraphUtils.eligible_successors),
	:(GraphUtils.eligible_predecessors),
	:(GraphUtils.num_required_successors),
	:(GraphUtils.num_required_predecessors),
	:(GraphUtils.num_eligible_successors),
	:(GraphUtils.num_eligible_predecessors),
	:resources_reserved,
	:id_type,
	:title_string
]
const predicate_comparison_interface = [
	:(GraphUtils.matches_template),
	:(GraphUtils.validate_edge),
]

abstract type PredicateTrait end
struct HasObject <: PredicateTrait end
struct HasRobot <: PredicateTrait end

# single robot actions: BOT_GO, BOT_COLLECT, BOT_CARRY, BOT_DEPOSIT
# initial condition preds: OBJECT_AT, ROBOT_AT
# event preds: Operation

export
	OBJECT_AT,
	BOT_AT,
    ROBOT_AT

"""
	OBJECT_AT <: AbstractPlanningPredicate

Encodes the location and shape of the object with id `o`
"""
struct OBJECT_AT <: AbstractPlanningPredicate
    o::ObjectID
    x::Vector{LocationID} # can occupy multiple locations
	# n::Int # number of robots required for transport
	shape::Tuple{Int,Int}
end
OBJECT_AT(o,x) = OBJECT_AT(o,x,(1,1))
OBJECT_AT(o,x::Union{Int,LocationID},args...) = OBJECT_AT(o,[x],args...)
get_object_id(pred::OBJECT_AT) = pred.o
get_location_id(pred::OBJECT_AT) = pred.x[1]
get_location_ids(pred::OBJECT_AT) = pred.x

"""
	BOT_AT <: AbstractPlanningPredicate

Encodes the location of robot with id `r`
"""
struct BOT_AT{R<:AbstractRobotType} <: AbstractPlanningPredicate
    r::BotID{R}
    x::LocationID
end
robot_type(a::BOT_AT{R}) where {R} = R
get_location_id(pred::BOT_AT) 	= pred.x
get_robot_id(pred::BOT_AT) 		= pred.r

const ROBOT_AT = BOT_AT{DeliveryBot}

export
    Operation,
	get_input_ids,
	get_output_ids,
	get_operation_id,
    preconditions,
	postconditions,
	set_precondition!,
	set_postcondition!,
	get_dropoff,
	get_dropoffs,
    duration

"""
	Operation <: AbstractPlanningPredicate

A manufacturing operation.
"""
@with_kw struct Operation <: AbstractPlanningPredicate
    pre::Dict{ObjectID,OBJECT_AT}     = Dict{ObjectID,OBJECT_AT}()
    post::Dict{ObjectID,OBJECT_AT}    = Dict{ObjectID,OBJECT_AT}()
    Δt::Int 					= 0
    station_id::LocationID   	= LocationID(-1)
    id::OperationID         	= get_unique_id(OperationID) #OperationID(-1)
end
get_location_id(op::Operation) = op.station_id
get_operation_id(op::Operation) = op.id
duration(op::Operation) = op.Δt
preconditions(op::Operation) = op.pre
postconditions(op::Operation) = op.post
get_condition(op::Operation,o::ObjectID,dict) = get(dict,o,OBJECT_AT(-1,-1))
function get_precondition(op::Operation,o::ObjectID)
	@assert haskey(preconditions(op),o) "Object $o is not an input to operation $(get_operation_id(op))"
	return preconditions(op)[o]
end
function get_postcondition(op::Operation,o::ObjectID)
	@assert haskey(posconditions(op),o) "Object $o is not an output of operation $(get_operation_id(op))"
	return postconditions(op)[o]
end
function set_precondition!(op::Operation,o::ObjectID,pred::OBJECT_AT)
	preconditions(op)[o] = pred
end
function set_postcondition!(op::Operation,o::ObjectID,pred::OBJECT_AT)
	postconditions(op)[o] = pred
end
for op in (:set_precondition!,:set_postcondition!)
	@eval $op(o::Operation,pred::OBJECT_AT) = $op(o,get_object_id(pred),pred)
	@eval begin
		function $op(o::Operation,preds::Vector{OBJECT_AT})
			for pred in preds
				$op(o,pred)
			end
			return o
		end
	end
end
get_input_ids(op::Operation) = sort(collect(keys(preconditions(op))))
get_output_ids(op::Operation) = sort(collect(keys(postconditions(op))))
GraphUtils.get_id(op::Operation) = get_id(op.id)

get_dropoff(op::Operation,o::ObjectID) = get_location_id(get_precondition(op,o))
get_dropoffs(op::Operation,o::ObjectID) = get_location_ids(get_precondition(op,o))

id_type(::BOT_AT{R}) where {R} = BotID{R}
id_type(::AbstractRobotAction{R}) where {R} = ActionID
id_type(::OBJECT_AT) = ObjectID
id_type(::Operation) = OperationID

export
	BOT_GO,BOT_CARRY,BOT_COLLECT,BOT_DEPOSIT,
    GO,COLLECT,CARRY,DEPOSIT

"""
	BOT_GO <: AbstractRobotAction

Encodes the event "robot `r` goes from `x1` to `x2`"
"""
@with_kw struct BOT_GO{R} <: AbstractSingleRobotAction{R} # go to position x
    r::BotID{R} 		= BotID{R}()
    x1::LocationID	= LocationID()
    x2::LocationID	= LocationID()
end
const GO = BOT_GO{DeliveryBot}

"""
	BOT_CARRY <: AbstractRobotAction

Encodes the event "robot `r` carries object `o` from `x1` to `x2`"
"""
@with_kw struct BOT_CARRY{R} <: AbstractSingleRobotAction{R} # carry object o to position x
    r::BotID{R}		= BotID{R}()
    o::ObjectID		= ObjectID()
    x1::LocationID	= LocationID()
    x2::LocationID	= LocationID()
end
const CARRY = BOT_CARRY{DeliveryBot}

"""
	BOT_COLLECT <: AbstractRobotAction

Encodes the event "robot `r` collects object `o` from `x`
"""
@with_kw struct BOT_COLLECT{R} <: AbstractSingleRobotAction{R}
    r::BotID{R}     = BotID{R}()
    o::ObjectID 	= ObjectID()
    x::LocationID 	= LocationID()
end
const COLLECT = BOT_COLLECT{DeliveryBot}

"""
	BOT_DEPOSIT <: AbstractRobotAction

Encodes the event "robot `r` collects object `o` from `x`
"""
@with_kw struct BOT_DEPOSIT{R} <: AbstractSingleRobotAction{R}
    r::BotID{R}     = BotID{R}()
    o::ObjectID 	= ObjectID()
    x::LocationID 	= LocationID()
end
const DEPOSIT = BOT_DEPOSIT{DeliveryBot}

export
	CleanUpBot,
	CleanUpBotID,
	CUB_AT,
	CUB_GO,
	CLEAN_UP

"""
	CleanUpBot <: AbstractRobotType

A robot type for picking up dropped objects, cleaning up spills, and taking
care of dead robots
"""
struct CleanUpBot <: AbstractRobotType end
const CleanUpBotID = BotID{CleanUpBot}

export
	CUB_AT,
	CUB_GO,
	CUB_COLLECT,
	CUB_CARRY,
	CUB_DEPOSIT

const CUB_AT = BOT_AT{CleanUpBot}
const CUB_GO = BOT_GO{CleanUpBot}
const CUB_COLLECT = BOT_COLLECT{CleanUpBot}
const CUB_CARRY = BOT_CARRY{CleanUpBot}
const CUB_DEPOSIT = BOT_DEPOSIT{CleanUpBot}

"""
	CLEAN_UP <: AbstractRobotAction

Encodes the event "robot `r` cleans up locations vtxs`
"""
@with_kw struct CLEAN_UP <: AbstractSingleRobotAction{CleanUpBot}
	r::BotID{CleanUpBot} = BotID{CleanUpBot}()
	vtxs::Vector{LocationID} = Vector{LocationID}()
end

"""
	UNDERTAKE <: AbstractRobotAction{CleanUpBot}

Encodes the task of collecting, carrying, and depositing a dead robot
"""
@with_kw struct UNDERTAKE <: AbstractSingleRobotAction{CleanUpBot}
	r::BotID{CleanUpBot}	= BotID{CleanUpBot}()
	dr::BotID 				= RobotID()
	x1::LocationID 			= LocationID()
	x2::LocationID			= LocationID()
end

export
	TEAM_ACTION,
	sub_nodes,
	team_configuration,
	robot_type,
	team_action_type

"""
	TEAM_ACTION{R<:AbstractRobotType,A<:AbstractRobotAction{R}}

For collaborative tasks.

[GO, ...] -> TEAM_COLLECT -> TEAM_CARRY -> TEAM_DEPOSIT -> [GO, ...]
"""
@with_kw struct TEAM_ACTION{R,A<:AbstractRobotAction{R}} <: AbstractTeamRobotAction{R}
    instructions::Vector{A} = Vector{A}()
	shape::Tuple{Int,Int} 	= (1,1)
    n::Int 					= length(instructions) # number of robots
    # config::Matrix{Int} = ones(n) # defines configuration of agents relative to each other during collaborative task
end
sub_nodes(n) = [n]
sub_nodes(n::TEAM_ACTION) = n.instructions
team_configuration(n) = (1,1)
team_configuration(n::TEAM_ACTION) = n.shape
team_action_type(n::TEAM_ACTION{R,A}) where {R,A} = A

export
	TEAM_GO,
	TEAM_COLLECT,
	TEAM_CARRY,
	TEAM_DEPOSIT

const TEAM_GO= TEAM_ACTION{DeliveryBot,GO}
const TEAM_COLLECT= TEAM_ACTION{DeliveryBot,COLLECT}
const TEAM_CARRY= TEAM_ACTION{DeliveryBot,CARRY}
const TEAM_DEPOSIT= TEAM_ACTION{DeliveryBot,DEPOSIT}

get_location_id(a::A) where {A<:Union{BOT_COLLECT,BOT_DEPOSIT}}             				= a.x
get_initial_location_id(a::A) where {A<:Union{BOT_GO,BOT_CARRY}}        					= a.x1
get_destination_location_id(a::A) where {A<:Union{BOT_GO,BOT_CARRY}}    					= a.x2
get_initial_location_id(a::A) where {A<:Union{BOT_COLLECT,BOT_DEPOSIT,BOT_AT,OBJECT_AT}}    = get_location_id(a)
get_destination_location_id(a::A) where {A<:Union{BOT_COLLECT,BOT_DEPOSIT,BOT_AT,OBJECT_AT}}= get_location_id(a)
get_object_id(a::A) where {A<:Union{BOT_CARRY,BOT_COLLECT,BOT_DEPOSIT}}         			= a.o
get_robot_id(a::A) where {A<:AbstractRobotAction} 											= a.r

has_object_id(a::Union{OBJECT_AT,BOT_COLLECT,BOT_CARRY,BOT_DEPOSIT}) = true
has_object_id(a::TEAM_ACTION{R,A} where {R,A<:Union{BOT_COLLECT,BOT_CARRY,BOT_DEPOSIT}}) = true

for T in [:BOT_AT,:BOT_GO,:BOT_COLLECT,:BOT_CARRY,:BOT_DEPOSIT]
	@eval replace_robot_id(node::$T,id) = $T(node,r=id)
end
function replace_robot_id(node::A,id) where {A<:TEAM_ACTION}
	return A(node,instructions=map(n->replace_robot_id(n,id), sub_nodes(node)))
end
for T in [:BOT_GO,:BOT_CARRY]
	@eval replace_destination(node::$T,id) = $T(node,x2=id)
	@eval replace_initial_location(node::$T,id) = $T(node,x1=id)
end
for T in [:BOT_AT,:BOT_COLLECT,:BOT_DEPOSIT]
	@eval replace_destination(node::$T,id) = $T(node,x=id)
	@eval replace_initial_location(node::$T,id) = $T(node,x=id)
end

split_node(node::BOT_GO,x::LocationID) = BOT_GO(node, x2=x), BOT_GO(node, x1=x)
split_node(node::BOT_CARRY,x::LocationID) = BOT_CARRY(node, x2=x), BOT_CARRY(node, x1=x)
split_node(node::BOT_COLLECT,x::LocationID) = BOT_COLLECT(node, x=x), BOT_COLLECT(node, x=x)
split_node(node::BOT_DEPOSIT,x::LocationID) = BOT_DEPOSIT(node, x=x), BOT_DEPOSIT(node, x=x)

get_object_id(a::TEAM_ACTION) = get_object_id(get(sub_nodes(a),1,OBJECT_AT(-1,-1)))

GraphUtils.required_predecessors(node::BOT_GO{R}) where {R} 		= Dict((BOT_GO{R},BOT_AT{R},BOT_DEPOSIT{R},TEAM_ACTION{R,BOT_DEPOSIT{R}})=>1)
GraphUtils.required_successors(node::BOT_GO{R}) where {R}       	= Dict()
GraphUtils.required_predecessors(node::BOT_COLLECT{R}) where {R} 	= Dict(OBJECT_AT=>1,BOT_GO{R}=>1)
GraphUtils.required_successors(node::BOT_COLLECT{R}) where {R} 	= Dict(BOT_CARRY{R}=>1)
GraphUtils.required_predecessors(node::BOT_CARRY{R}) where {R} 	= Dict(BOT_COLLECT{R}=>1)
GraphUtils.required_successors(node::BOT_CARRY{R}) where {R} 		= Dict(BOT_DEPOSIT{R}=>1)
GraphUtils.required_predecessors(node::BOT_DEPOSIT{R}) where {R} 	= Dict(BOT_CARRY{R}=>1)
GraphUtils.required_successors(node::BOT_DEPOSIT{R}) where {R} 	= Dict(Operation=>1,BOT_GO{R}=>1)
GraphUtils.required_predecessors(node::Operation)  				= Dict((BOT_DEPOSIT{DeliveryBot},OBJECT_AT)=>length(preconditions(node)))
GraphUtils.required_successors(node::Operation)    				= Dict(OBJECT_AT=>length(postconditions(node)))
GraphUtils.required_predecessors(node::OBJECT_AT)  				= Dict()
GraphUtils.required_successors(node::OBJECT_AT)    				= Dict(BOT_COLLECT{DeliveryBot}=>1)
GraphUtils.required_predecessors(node::BOT_AT{R}) where {R}   		= Dict()
GraphUtils.required_successors(node::BOT_AT{R}) where {R}     		= Dict(BOT_GO{R}=>1)

GraphUtils.required_predecessors(node::TEAM_ACTION{R,GO}) where {R} 		= Dict(GO=>length(node.instructions))
GraphUtils.required_predecessors(node::TEAM_ACTION{R,COLLECT}) where {R} 	= Dict(GO=>length(node.instructions),OBJECT_AT=>1)
GraphUtils.required_predecessors(node::TEAM_ACTION{R,CARRY}) where {R} 	= Dict(TEAM_ACTION{R,COLLECT}=>1)
GraphUtils.required_predecessors(node::TEAM_ACTION{R,DEPOSIT}) where {R} 	= Dict(TEAM_ACTION{R,CARRY}=>1)

GraphUtils.required_successors(node::TEAM_ACTION{R,GO}) where {R}        	= Dict(TEAM_ACTION{R,COLLECT}=>1)
GraphUtils.required_successors(node::TEAM_ACTION{R,COLLECT}) where {R}   	= Dict(TEAM_ACTION{R,CARRY}=>1)
GraphUtils.required_successors(node::TEAM_ACTION{R,CARRY}) where {R}     	= Dict(TEAM_ACTION{R,DEPOSIT}=>1)
GraphUtils.required_successors(node::TEAM_ACTION{R,DEPOSIT}) where {R}   	= Dict(GO=>length(node.instructions),Operation=>1)

GraphUtils.eligible_predecessors(node) 			= required_predecessors(node)
GraphUtils.eligible_successors(node) 				= required_successors(node)
GraphUtils.eligible_successors(node::BOT_GO{R}) where {R} = Dict((BOT_GO{R},TEAM_ACTION{R,BOT_COLLECT{R}},TEAM_ACTION{R,BOT_GO{R}},BOT_COLLECT{R})=>1)
GraphUtils.eligible_predecessors(node::OBJECT_AT)  = Dict(Operation=>1)

resources_reserved(node::BOT_COLLECT)	= AbstractID[get_location_id(node)]
resources_reserved(node::BOT_DEPOSIT)	= AbstractID[get_location_id(node)]
resources_reserved(node::TEAM_ACTION)	= union(map(pred->resources_reserved(pred), node.instructions)...)

align_with_predecessor(node::BOT_GO,pred::BOT_AT)			= BOT_GO(first_valid(node.r,pred.r), first_valid(node.x1,pred.x), node.x2)
align_with_predecessor(node::BOT_GO,pred::BOT_GO)			= BOT_GO(first_valid(node.r,pred.r), first_valid(node.x1,pred.x2), node.x2)
align_with_predecessor(node::BOT_GO,pred::BOT_DEPOSIT)		= BOT_GO(first_valid(node.r,pred.r), first_valid(node.x1,pred.x), node.x2)
align_with_predecessor(node::BOT_COLLECT,pred::OBJECT_AT)	= BOT_COLLECT(node.r, first_valid(node.o,pred.o), node.x) # NOTE: Because the object could occupy multiple vertices, we do not want to dispatch first_valid between BOT_COLLECT.x and OBJECT_AT.x
align_with_predecessor(node::BOT_COLLECT,pred::BOT_GO)		= BOT_COLLECT(first_valid(node.r,pred.r), node.o, first_valid(node.x,pred.x2))
align_with_predecessor(node::BOT_CARRY,pred::BOT_COLLECT)	= BOT_CARRY(first_valid(node.r,pred.r), first_valid(node.o,pred.o), first_valid(node.x1,pred.x), node.x2)
align_with_predecessor(node::BOT_CARRY,pred::BOT_CARRY)		= BOT_CARRY(first_valid(node.r,pred.r), first_valid(node.o,pred.o), first_valid(node.x1,pred.x2), node.x2)
align_with_predecessor(node::BOT_DEPOSIT,pred::BOT_CARRY)	= BOT_DEPOSIT(first_valid(node.r,pred.r), first_valid(node.o,pred.o), first_valid(node.x,pred.x2))

# NOTE job shop constraints were wreaking havoc with id propagation between BOT_COLLECT nodes and BOT_DEPOSIT nodes! Hence the alignment functions have been removed
# align_with_predecessor(node::BOT_COLLECT,pred::BOT_COLLECT) 	= BOT_COLLECT(node.r, node.o, first_valid(node.x,pred.x))
# align_with_predecessor(node::BOT_DEPOSIT,pred::BOT_DEPOSIT) 	= BOT_DEPOSIT(node.r, node.o, first_valid(node.x,pred.x))

function align_with_predecessor(node::TEAM_ACTION,pred)
	for i in 1:length(node.instructions)
		p = node.instructions[i]
		if get_destination_location_id(pred) == get_initial_location_id(p)
			node.instructions[i] = align_with_predecessor(p,pred)
		end
	end
	node
end
function align_with_predecessor(node::AbstractRobotAction,pred::TEAM_ACTION)
	for i in 1:length(pred.instructions)
		p = pred.instructions[i]
		if get_destination_location_id(p) == get_initial_location_id(node)
			return align_with_predecessor(node,p)
		end
	end
	return node
end
function align_with_predecessor(node::TEAM_ACTION,pred::TEAM_ACTION)
	for i in 1:length(node.instructions)
		p = node.instructions[i]
		for (j,pj) in enumerate(pred.instructions)
			if get_destination_location_id(pj) == get_initial_location_id(p)
				@assert(i == j, "TEAM_ACTION indices $i and $j do not match")
				node.instructions[i] = align_with_predecessor(p,pj)
			end
		end
	end
	node
end

GraphUtils.validate_edge(n1::AbstractPlanningPredicate,n2::AbstractPlanningPredicate) = true

align_with_successor(node::BOT_GO,succ::BOT_COLLECT) 			= BOT_GO(first_valid(node.r,succ.r), node.x1, first_valid(node.x2,succ.x))
align_with_successor(node::BOT_GO,succ::BOT_GO) 				= BOT_GO(first_valid(node.r,succ.r), node.x1, first_valid(node.x2,succ.x1))

GraphUtils.validate_edge(n1::N1,n2::N2) where {N1<:Union{BOT_AT,OBJECT_AT},N2<:Union{BOT_AT,OBJECT_AT}} = false
GraphUtils.validate_edge(n1::BOT_AT,		n2::BOT_GO			) = (n1.x 	== n2.x1) && (n1.r == n2.r)
GraphUtils.validate_edge(n1::BOT_GO,			n2::BOT_GO			) = (n1.x2 	== n2.x1) && (n1.r == n2.r)
GraphUtils.validate_edge(n1::BOT_GO,			n2::BOT_COLLECT		) = (n1.x2 	== n2.x ) && (n1.r == n2.r)
GraphUtils.validate_edge(n1::BOT_COLLECT,		n2::BOT_CARRY		) = (n1.x 	== n2.x1) && (n1.o == n2.o)
GraphUtils.validate_edge(n1::BOT_CARRY,		n2::BOT_COLLECT		) = false
GraphUtils.validate_edge(n1::BOT_CARRY,		n2::BOT_DEPOSIT		) = (n1.x2 	== n2.x) && (n1.o == n2.o)
GraphUtils.validate_edge(n1::BOT_DEPOSIT,		n2::BOT_CARRY		) = false
GraphUtils.validate_edge(n1::BOT_DEPOSIT,		n2::BOT_GO			) = (n1.x 	== n2.x1)
GraphUtils.validate_edge(n1::N,n2::N) where {N<:Union{BOT_COLLECT,BOT_DEPOSIT}} = (n1.x == n2.x) # job shop edges are valid

Base.string(pred::OBJECT_AT)	=  string("O(",get_id(get_object_id(pred)),",",map(x->get_id(x), get_location_ids(pred)),")")
Base.string(pred::BOT_AT)  		=  string("R(",get_id(get_robot_id(pred)),",",get_id(get_location_id(pred)),")")
Base.string(a::BOT_GO)        	=  string("GO(",get_id(get_robot_id(a)),",",get_id(get_initial_location_id(a)),"->",get_id(get_destination_location_id(a)),")")
Base.string(a::BOT_COLLECT)   	=  string("COLLECT(",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_location_id(a)),")")
Base.string(a::BOT_CARRY)     	=  string("CARRY(",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_initial_location_id(a)),"->",get_id(get_destination_location_id(a)),")")
Base.string(a::BOT_DEPOSIT)   	=  string("DEPOSIT(",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_location_id(a)),")")
Base.string(op::Operation)    	=  string("OP(",get_id(get_operation_id(op)),")")
Base.string(a::TEAM_ACTION)   	=  string("TEAM_ACTION( ",map(i->string(string(i), ","), a.instructions)...," )")

title_string(pred::OBJECT_AT,verbose=true) = verbose ? string("O",get_id(get_object_id(pred)),"-",get_id(get_location_id(pred))) : string("O",get_id(get_object_id(pred)));
title_string(pred::BOT_AT,verbose=true)  = verbose ? string("R",get_id(get_robot_id(pred)),"-",get_id(get_location_id(pred))) : string("R",get_id(get_robot_id(pred)));
title_string(a::BOT_GO,verbose=true)        = verbose ? string("go\n",get_id(get_robot_id(a)),",",get_id(get_initial_location_id(a)),",",get_id(get_destination_location_id(a))) : "go";
title_string(a::BOT_COLLECT,verbose=true)   = verbose ? string("collect\n",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_location_id(a))) : "collect";
title_string(a::BOT_CARRY,verbose=true)     = verbose ? string("carry\n",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_destination_location_id(a))) : "carry";
title_string(a::BOT_DEPOSIT,verbose=true)   = verbose ? string("deposit\n",get_id(get_robot_id(a)),",",get_id(get_object_id(a)),",",get_id(get_location_id(a))) : "deposit";
title_string(op::Operation,verbose=true)= verbose ? string("op",get_id(get_operation_id(op))) : "op";
title_string(a::TEAM_ACTION,verbose=true) where {R,A} = verbose ? string("T-", team_action_type(a), "\n","r: (",map(i->string(get_id(get_robot_id(i)), ","), a.instructions)...,")") : string("TEAM","\n",title_string(team_action_type(a)(),verbose)) #string("TEAM\n", string(team_action_type(a)))


text:
#--------------------General Utilities---------------------

# Just a hack to add the guided relu functions and replace the relu
# Resnet model is not supported
# Google Net is partially supported
function change_activation(model::Chain, activation)
  updated_model = []
  for (i, l) in enumerate(model.layers)
    T = typeof(l)
    if T <: Dense
      push!(updated_model, Dense(l.W, l.b, identity))
      push!(updated_model, activation)
    elseif T <: Conv
      push!(updated_model, Conv(identity, l.weight, l.bias, l.stride, l.pad, l.dilation))
      push!(updated_model, activation)
    elseif T <: BatchNorm
      push!(updated_model, BatchNorm(identity, l.β, l.γ, l.μ, l.σ, l.ϵ, l.momentum, l.active))
      push!(updated_model, activation)
    elseif T <: Chain
      push!(updated_model, change_activation(l, activation))
    # elseif T<: Bottleneck # To handle the DenseNet Model
    #   push!(updated_model, Bottleneck(change_activation(l.layer, activation)))
    else
      push!(updated_model, l)
    end
  end
  Chain(updated_model...)
end

#--------------------BackPropagation----------------------

struct Backprop
  model::Chain
end

function (m::Backprop)(img, top = 1)
  if !Tracker.istracked(img)
    img = param(img) |> gpu
  end
  preds = m.model(img)
  probs = softmax(preds)
  prob, inds = get_topk(probs.data, top)
  grads = []
  for (i, idx) in enumerate(inds)
    Flux.back!(preds, one_hot_encode(preds, idx))
    push!(grads, (img.grad |> cpu, prob[i], idx))
    img.grad .= 0.0
  end
  grads
end

#-----------------Vanilla BackPropagation-----------------

VanillaBackprop(model::Chain) = Backprop(model[1:end-1] |> gpu)

#------------------Guided BackPropagation-----------------

guided_relu1(x) = max.(zero(x), x)

guided_relu1(x::TrackedArray) = Tracker.track(guided_relu1, x)

Tracker.back(::typeof(guided_relu1), Δ, x) = Tracker.@back(x, Int.(x .> zero(x)) .* max.(zero(Δ), Δ))

function GuidedBackprop(model::Chain)
  model = change_activation(model, guided_relu1)
  Backprop(model[1:end-1] |> gpu)
end

#---------------------Deconvolution-----------------------

guided_relu2(x) = max.(zero(x), x)

guided_relu2(x::TrackedArray) = Tracker.track(guided_relu2, x)

Tracker.back(::typeof(guided_relu2), Δ, x) = Tracker.@back(x, max.(zero(Δ), Δ))

function Deconvolution(model::Chain)
  model = change_activation(model, guided_relu2)
  Backprop(model[1:end-1] |> gpu)
end


text:
# Autogenerated wrapper script for URCU_jll for aarch64-apple-darwin
export liburcu, liburcu_bp, liburcu_cds, liburcu_common, liburcu_mb, liburcu_memb, liburcu_qsbr, liburcu_signal

JLLWrappers.@generate_wrapper_header("URCU")
JLLWrappers.@declare_library_product(liburcu, "@rpath/liburcu.8.dylib")
JLLWrappers.@declare_library_product(liburcu_bp, "@rpath/liburcu-bp.8.dylib")
JLLWrappers.@declare_library_product(liburcu_cds, "@rpath/liburcu-cds.8.dylib")
JLLWrappers.@declare_library_product(liburcu_common, "@rpath/liburcu-common.8.dylib")
JLLWrappers.@declare_library_product(liburcu_mb, "@rpath/liburcu-mb.8.dylib")
JLLWrappers.@declare_library_product(liburcu_memb, "@rpath/liburcu-memb.8.dylib")
JLLWrappers.@declare_library_product(liburcu_qsbr, "@rpath/liburcu-qsbr.8.dylib")
JLLWrappers.@declare_library_product(liburcu_signal, "@rpath/liburcu-signal.8.dylib")
function __init__()
    JLLWrappers.@generate_init_header()
    JLLWrappers.@init_library_product(
        liburcu,
        "lib/liburcu.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_bp,
        "lib/liburcu-bp.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_cds,
        "lib/liburcu-cds.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_common,
        "lib/liburcu-common.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_mb,
        "lib/liburcu-mb.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_memb,
        "lib/liburcu-memb.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_qsbr,
        "lib/liburcu-qsbr.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liburcu_signal,
        "lib/liburcu-signal.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
module DecompPlots

using Distributed
using Serialization
using JuliaDB
using LightGraphs, MetaGraphs
using Memoize

import Dates
import ScikitLearn
import Plots
import Statistics
import Random

# For the CANDECOMP definition needed for deserializing decomps
import TensorDecompositions

# Maybe comment this out if you're using
include("MatlabDecomp.jl")

const PLATFORMS = readdir("../data/processed/pacman")

### Generate summary statistics

# Both of these are pretty slow and would probably be faster implemented in Julia.
# AMI in particular looks like it might be spending a lot of time converting to python objects?

ScikitLearn.@sk_import metrics: adjusted_mutual_info_score

#= @memoize function amis_from_repeats1(D_s) =#
#=     amis_from_repeats(D_s) =#
#= end =#

# Community membership by max
factors_to_k_memb_by_max(factors) = map(x->x[2],findmax(factors[2];dims=2)[2])[:]

"""
    amis_from_repeats(Ds)

Calculate the Adjusted Mutual Information of an array of decompositions/ktensors.
"""
function amis_from_repeats(D_s)
    D_sfacts = map(x->factors_to_k_memb_by_max(x.factors),D_s)

    # This is a little bit slow.
    # Could filter it first and then randomly sample.

    # Make it triangular
    Ds_filt = values(filter(x->x[1][1] < x[1][2],pairs(IndexCartesian(),Iterators.product(D_sfacts,D_sfacts)|>collect)))

    amis = [adjusted_mutual_info_score(d1,d2) for (d1,d2) in Ds_filt]
end

function _cache_amis(platform, dir, r)
    amis = Dict()
    # Ensure consistent indices by lexically sorting the filenames.
    decomps = vcat([deserialize("$dir/$r/$file") for file in sort(readdir("$dir/$r"))]...)
    amis[parse(Int, r)] = amis_from_repeats(decomps)
    amis
end

function cache_amis(platform)
    # For each r
    # Collect all decomps
    # Calculate AMI
    # Write to a file
    #
    # Not done:
    #   if time in filename is greater or equal to decomp times, then it's valid.
    dir = "../data/processed/pacman/$platform/decomps"
    amidir = "../data/processed/pacman/$platform/amis"
    now = Dates.now()
    rs = readdir(dir) |> Random.shuffle
    amis = @distributed merge for r in rs
        _cache_amis(platform, dir, r)
    end
    run(`mkdir -p $amidir`)
    #= existing = readdir(amidir) =#
    serialize("$amidir/$now.jls", amis)
    #= map(fn->rm("$amidir/$fn"), existing) =#
    amis
end

#= function relerror(adj_mats, decomps) =#
#=     rs = sort([keys(decomps)...]) =#
#=     MatlabDecomp.matlab_load_sptensor(adj_mats) =#
#=     errors = [[MatlabDecomp.relerror_loaded(D) for D in decomps[r]] for r in rs]; =#
#= end =#

function cache_nrss(platform, rs)
    dir = "../data/processed/pacman/$platform"
    rssdir = "$dir/nrss"
    rsss = Dict()

    adj_mats = deserialize("$dir/adj-mats.jls")
    MatlabDecomp.matlab_load_sptensor(adj_mats)

    now = Dates.now()
    for r in rs
        # Ensure consistent indices by lexically sorting the filenames.
        decomps = vcat([deserialize("$dir/decomps/$r/$file") for file in sort(readdir("$dir/decomps/$r"))]...)
        rsss[r] = [MatlabDecomp.relerror_loaded(D) for D in decomps]
    end
    run(`mkdir -p $rssdir`)
    #= existing = readdir(rssdir) =#
    serialize("$rssdir/$now.jls", rsss)
    #= map(fn->rm("$rssdir/$fn"), existing) =#
    rsss
end

function cache_nrss(platform)
    dir = "../data/processed/pacman/$platform"
    rssdir = "$dir/nrss"
    rsss = Dict()

    adj_mats = deserialize("$dir/adj-mats.jls")
    MatlabDecomp.matlab_load_sptensor(adj_mats)

    now = Dates.now()
    for r in readdir("$dir/decomps")
        # Ensure consistent indices by lexically sorting the filenames.
        decomps = vcat([deserialize("$dir/decomps/$r/$file") for file in sort(readdir("$dir/decomps/$r"))]...)
        rsss[parse(Int, r)] = [MatlabDecomp.relerror_loaded(D) for D in decomps]
    end
    run(`mkdir -p $rssdir`)
    existing = readdir(rssdir)
    serialize("$rssdir/$now.jls", rsss)
    map(fn->rm("$rssdir/$fn"), existing)
    rsss
end

function cache_ami_rss(platforms = PLATFORMS)
    job1 = @distributed for p in platforms
        DecompPlots.cache_amis(p)
    end
    job2 = @distributed for p in platforms
        DecompPlots.cache_nrss(p)
    end
    job1, job2
end

### Retrieve cached data

function loaddecomps(platform, r)
    dir = "../data/processed/pacman/$platform/decomps/"
    # Ensure consistent indices by lexically sorting the filenames.
    vcat([deserialize("$dir$r/$file") for file in sort(readdir("$dir$r"))]...)
end

function loaddecomps(platform)
    dir = "../data/processed/pacman/$platform/decomps/"
    decomps = Dict()
    for r in readdir(dir)
        decomps[parse(Int, r)] = loaddecomps(platform, r)
    end
    decomps
end

function get_amis(platform)
    dir = "../data/processed/pacman/$platform/amis/"
    deserialize("$dir/$(sort(readdir(dir))[end])")
end

function get_nrss(platform)
    dir = "../data/processed/pacman/$platform/nrss/"
    deserialize("$dir/$(sort(readdir(dir))[end])")
end

### indegree and outdegree

function get_graph(platform)
    adj_mats = deserialize("../data/processed/pacman/$platform/adj-mats.jls")
    DiGraph(adj_mats[end])
end

### Plot helpers

@memoize function name_lookup(platform::AbstractString)
    meta = deserialize("../data/processed/pacman/$platform/meta.jls")
    name_lookup(meta)
end

@memoize function name_lookup(meta)
    merge(
        groupby(x -> x[1], meta, :Project_Node, select=:Project_Name) |>
            Dict{Int, String},
        groupby(x -> x[1], meta, :Dependency_Project_Node, select=:Dependency_Name) |>
            Dict{Int, String})
end

name_components(factors, names) = begin
    hubs, followers, time = factors

    hubs.*=(hubs.==maximum(hubs,dims=2))

    hubnames = map(component -> findmax(component)[2], hubs |> eachcol) |>
        hubprojs -> map(p -> names[p], hubprojs)
end

function check_names(decomps, names)
    cnames = [name_components(decomps[i].factors, names) |> sort for i in 1:length(decomps)]
    hcat(cnames...) |> permutedims
end

function plot_times(decomps; fudge=1)
    Plots.plot(
        [DecompPlots.plot_time(ds[1]; legend=false) for ds in values(sort(decomps))]...)
end

function plot_time_fudge_panel(decomps)
    Plots.plot(
        [DecompPlots.plot_time(decomps[f]; legend=false) for f in 1:length(decomps)]...)
end

sortedkeys(d) = sort([keys(d)...])

### Plotting

"""
    errorplot(xs, ys; kwargs...)

Plot 2 * (standard error of the mean of `ys`).
"""
errorplot(xs, ys; kwargs...) = Plots.plot(
    xs,
    Statistics.mean.(ys);
    ribbon=(Statistics.std.(ys) .* 2) ./ sqrt(length(ys[1])),
    fillalpha=0.3,
    xticks=xs,
    legend=false,
    kwargs...)

function plot_ami(amis, rs=sortedkeys(amis))
    amis = [amis[r] for r in rs]
    errorplot(
        xlabel="Number of communities",
        ylabel="Pairwise AMI",
        rs,
        amis,)
end

function plot_rss(nrsss, rs=sortedkeys(nrsss))
    nrsss = [nrsss[r] for r in rs]
    errorplot(
        rs,
        nrsss;
        xlabel="Number of communities",
        ylabel="NRSS",)
end

plot_time(D, names; kwargs...) = begin
    hubs, followers, time = D.factors
    # Normalise time factor to between 0 and 1 because
    # absolute magnitude is meaningless without context
    time = time .- minimum(time)
    time = time ./ maximum(time)

    hubnames = name_components(D.factors, names)

    # sort time component by hubnames
    time = time[1:end,sortperm(hubnames)]
    hubnames = sort(hubnames)

    Plots.plot(time; legend=:topleft, labels=hubnames, yticks=[0,1], xlabel="Month", ylabel="Relative activity", kwargs...)
end

plot_time(D; kwargs...) = begin
    hubs, followers, time = D.factors
    # Normalise time factor to between 0 and 1 because
    # absolute magnitude is meaningless without context
    time = time .- minimum(time)
    time = time ./ maximum(time)

    Plots.plot(time, legend=:topleft; kwargs...)
end

# Probably broken!
function plot_time_grid(decomps; kwargs...)
    plts = [plot_time(Ds[1]) for Ds in decomps]
    plot(plts..., layout=(5,2), size=(700,600), legend=false, yticks=0:1; kwargs...)
end

### Gephi plot

function gephi_viz(r=4, fudge=10)
    # Use decomps[4][10] as a representative sample
    # Add component numbers, h and k, names

    meta = deserialize("../data/processed/pacman/Elm/meta.jls")
    adj_mats = deserialize("../data/processed/pacman/Elm/adj-mats.jls")

    g = MetaDiGraph(adj_mats[end])
    decomps = loaddecomps("Elm")
    names = name_lookup(meta)

    [set_prop!(g, i, :name, name) for (i,name) in enumerate([values(sort(names))...][1:1462])]
    [set_prop!(g, i, :component, c) for (i,c) in enumerate(DecompPlots.factors_to_k_memb_by_max(decomps[r][fudge].factors))]

    g # Save with MetaGraphs.savedot, if you like.
end

### Save plots

GOOD_DECOMPS = Dict(
        "Cargo" => (8, 5),
        "NPM" => (3, 1),
        "Elm" => (4, 10), # 10, 2
        "CRAN" => (7, 1), # 2, 1
        "Pypi" => (6, 2), # 18, 1
        "Maven" => (5, 1),
        )

function pick_fudge(platform, r)
    decomps = loaddecomps(platform, r)
    names = name_lookup(platform)

    backend = Plots.backend()
    Plots.unicodeplots()

    display(plot_time_fudge_panel(decomps))
    Plots.backend(backend)
    display(check_names(decomps, names))

end

function save_ami_rss(platforms = PLATFORMS)
    for platform in platforms
        @info platform
        amis = get_amis(platform)
        if platform == "NPM"
            Plots.savefig(plot_ami(amis, 2:14), "../figures/results/$platform-ami.pdf")
        else
            nrss = get_nrss(platform)
            Plots.savefig(Plots.plot(plot_ami(amis), plot_rss(nrss), size=(1200,400)), "../figures/results/$platform-ami-rss.pdf")
            #= Plots.savefig(plot_ami(amis), "../figures/results/$platform-ami.pdf") =#
            #= Plots.savefig(plot_rss(nrss), "../figures/results/$platform-rss.pdf") =#
        end
        @info "done"
    end
end

function update_ami_rss(platforms = PLATFORMS)
    for p in platforms
        cache_amis(p)
        cache_nrss(p)
    end
    save_ami_rss(platforms)
end

function save_time_plots(platform, r, fudge=1; filename="../figures/results/$platform-best-r-time.pdf", kwargs...)
    decomps = loaddecomps(platform, r)
    names = name_lookup(platform)

    Plots.savefig(
        plot_time(decomps[fudge], names; kwargs...), filename)
end

function save_time_plots(platforms=PLATFORMS)
    for p in platforms
        r, f = GOOD_DECOMPS[p]
        save_time_plots(p, r, f)
    end
end

end


text:
nodeselect(ns, i, I...) = ns[i][I...]
nodechild(ns, i, j) = ns[i].nodes[j]

function bisect_search(a, i)
    first(searchsorted(a,i))
end

Base.IndexStyle(::Type{<:AbstractMultiScaleArray}) = IndexLinear()

@inline function getindex(m::AbstractMultiScaleArray, i::Int)
    idx = bisect_search(m.end_idxs, i)
    idx > 1 && (i -= m.end_idxs[idx-1]) # also works with values
    (isempty(m.values) || idx < length(m.end_idxs)) ? nodeselect(m.nodes, idx, i) : m.values[i]
end

@inline function setindex!(m::AbstractMultiScaleArray, nodes, i::Int)
    idx = bisect_search(m.end_idxs, i) # +1 for 1-based indexing
    idx > 1 && (i -= m.end_idxs[idx-1])
    if isempty(m.values) || idx < length(m.end_idxs)
        m.nodes[idx][i] = nodes
    else
        m.values[i] = nodes
    end
end

@inline getindex(m::AbstractMultiScaleArrayLeaf, i::Int) = m.values[i]
@inline getindex(m::AbstractMultiScaleArrayLeaf, i::Int...) = m.values[i[1]]

@inline function getindex(m::AbstractMultiScaleArray, i, I...)
    if isempty(m.values) || i < length(m.end_idxs)
        length(I) == 1 ? nodechild(m.nodes, i, I[1]) : nodeselect(m.nodes, i, I...)
    else
        m.values[I...]
    end
end

@inline getindex(m::AbstractMultiScaleArrayLeaf, i...) = m.values[i[1]]
@inline getindex(m::AbstractMultiScaleArrayLeaf, i::CartesianIndex{1}) = m.values[i[1]]

@inline setindex!(m::AbstractMultiScaleArrayLeaf, values, i::Int) = (m.values[i] = values)
@inline setindex!(m::AbstractMultiScaleArrayLeaf, values, i::Int...) = (m.values[i[1]] = values)

@inline setindex!(m::AbstractMultiScaleArray, values, i::CartesianIndex{1}) = (m[i[1]] = values)

@inline function setindex!(m::AbstractMultiScaleArray, values, i, I::Int...)
    if isempty(m.values) || i < length(m.end_idxs)
        mx = m.nodes[i]
        length(I) == 1 ? (mx[I[1]] = values) : (mx[I...] = values)
    else
        m.values[I...] = values
    end
end

@inline getindex(m::AbstractMultiScaleArray, ::Colon) = [m[i] for i in 1:length(m)]
@inline getindex(m::AbstractMultiScaleArrayLeaf, ::Colon) = m.values
@inline getindex(m::AbstractMultiScaleArray, i::CartesianIndex{1}) = m[i[1]] # (i, )

eachindex(m::AbstractMultiScaleArray) = 1:length(m)
endof(m::AbstractMultiScaleArray) = length(m)

Base.eltype(::Type{<:AbstractMultiScaleArray{B}}) where {B<:Number} = B
# For now, default to Float64 (like v0.5 version did) if not specified
function Base.eltype(::Type{T}) where {T<:AbstractMultiScaleArray}
    eltype(fieldtype(T, :values)) === Any && return Float64
    error("Invalid AbstractMultiScaleArray, type of values not specified")
end
Base.eltype(::T) where {T<:AbstractMultiScaleArray} = eltype(T)

#broadcast_getindex(m::AbstractMultiScaleArrayLeaf, i::Int)    =  (println("here");m[i])
#broadcast_getindex(m::AbstractMultiScaleArray, i::Int)    =  (println("here");m[i])
#broadcast_getindex(m::AbstractMultiScaleArray, i::Int...) = m[i]

getindices(m::AbstractMultiScaleArrayHead) = 1:length(m)

getindices(m::AbstractMultiScaleArrayHead, i::Int) =
    ((i > 1) ? (m.end_idxs[i-1] + 1) : 1) : m.end_idxs[i]

getindices(m::AbstractMultiScaleArrayHead, i, I::Int...) =
    getindices(m.nodes[i], ((i > 1) ? (m.end_idxs[i-1] + 1) : 1), m.end_idxs[i], I...)

getindices(m::AbstractMultiScaleArray, bot_idx, top_idx, i::Int) =
    (bot_idx + ((i > 1) ? m.end_idxs[i-1] : 0)) : (top_idx + m.end_idxs[i] - length(m))

getindices(m::AbstractMultiScaleArray, bot_idx, top_idx, i, I::Int...) =
    getindices(m.nodes[i],
               bot_idx + ((i > 1) ? m.end_idxs[i-1] : 0),
               top_idx + m.end_idxs[i] - length(m),
               I...)

function getindices(m::AbstractMultiScaleArrayLeaf, bot_idx, top_idx, i::Int)
    i > length(m) && error("Final index is larger than length of leaf")
    top_idx -= length(m) - i
    top_idx:top_idx
end

getindices(m::AbstractMultiScaleArrayLeaf, bot_idx, top_idx, I::Int...) =
    error("Indexes past the bottom.")


text:
#=--- CpuId / CpuInstructions.jl ------------------------------------------=#

"""
# Module 'CpuInstructions'

The module 'CpuInstructions' is part of the package 'CpuId', and provides a
selection of wrapped low-level assembly functions to diagnose potential
computational efficiency issues.

Though primarily intended as a helper module to 'CpuId', the functions may be
used directly in other code e.g. for benchmarking purposes.  Just include the
file directly, or copy & paste.
"""
module CpuInstructions

export cpuid, rdtsc, rdtscp

using Base: llvmcall

"""
    cpuid( [leaf], [subleaf]) ::NTuple{4, UInt32}

Invoke the cpu's hardware instruction `cpuid` with the values of the arguments
stored as registers *EAX = leaf*, *ECX = subleaf*, respectively. Returns a
tuple of the response of registers EAX, EBX, ECX, EDX.  Input values may be
given as individual `UInt32` arguments, or converted from any `Integer`.
Unspecified arguments are assumed zero.

This function is primarily intended as a low-level interface to the CPU.

Note: Expected to work on all CPUs that implement the assembly instruction
      `cpuid`, which is at least Intel and AMD.
"""
function cpuid end

# Low level cpuid call, taking eax=leaf and ecx=subleaf,
# returning eax, ebx, ecx, edx as NTuple(4,UInt32)
@noinline cpuid_llvm(leaf::UInt32, subleaf::UInt32) =
    llvmcall("""
        ; leaf = %0, subleaf = %1, %2 is some label
        ; call 'cpuid' with arguments loaded into registers EAX = leaf, ECX = subleaf
        %3 = tail call { i32, i32, i32, i32 } asm sideeffect "cpuid",
            "={ax},={bx},={cx},={dx},{ax},{cx},~{dirflag},~{fpsr},~{flags}"
            (i32 %0, i32 %1) #2
        ; retrieve the result values and convert to vector [4 x i32]
        %4 = extractvalue { i32, i32, i32, i32 } %3, 0
        %5 = extractvalue { i32, i32, i32, i32 } %3, 1
        %6 = extractvalue { i32, i32, i32, i32 } %3, 2
        %7 = extractvalue { i32, i32, i32, i32 } %3, 3
        ; return the values as a new tuple
        %8  = insertvalue [4 x i32] undef, i32 %4, 0
        %9  = insertvalue [4 x i32]   %8 , i32 %5, 1
        %10 = insertvalue [4 x i32]   %9 , i32 %6, 2
        %11 = insertvalue [4 x i32]  %10 , i32 %7, 3
        ret [4 x i32] %11
    """
    # llvmcall requires actual types, rather than the usual (...) tuple
    , NTuple{4,UInt32}, Tuple{UInt32,UInt32}
    , leaf, subleaf)

# Convenience function allowing passing other than UInt32 values
function cpuid(leaf=0, subleaf=0)
    # for some reason, we need a dedicated local
    # variable of UInt32 for llvmcall to succeed
    l, s = UInt32(leaf), UInt32(subleaf)
    cpuid_llvm(l, s) ::NTuple{4,UInt32}
end

@inline rdtsc() =
    llvmcall("""
        %1 = tail call { i32, i32 } asm sideeffect "rdtsc", "={ax},={dx},~{dirflag},~{fpsr},~{flags}"() #2
        %2 = extractvalue { i32, i32 } %1, 0
        %3 = extractvalue { i32, i32 } %1, 1
        %4 = zext i32 %2 to i64
        %5 = zext i32 %3 to i64
        %6 = shl nuw i64 %5, 32
        %7 = or i64 %6, %4
        ret i64 %7
    """
    , UInt64, Tuple{})

@inline rdtscp() =
    llvmcall("""
        %1 = tail call { i32, i32, i32 } asm sideeffect "rdtscp", "={ax},={dx},={cx},~{dirflag},~{fpsr},~{flags}"() #2
        %2 = extractvalue { i32, i32, i32 } %1, 0
        %3 = extractvalue { i32, i32, i32 } %1, 1
        %4 = zext i32 %2 to i64
        %5 = zext i32 %3 to i64
        %6 = shl nuw i64 %5, 32
        %7 = or i64 %6, %4

        %8 = extractvalue { i32, i32, i32 } %1, 2
        %9 = zext i32 %8 to i64

        %10 = insertvalue [2 x i64] undef, i64  %7, 0
        %11 = insertvalue [2 x i64]  %10 , i64  %9, 1
        ret [2 x i64] %11
    """
    , Tuple{UInt64,UInt64}, Tuple{})

end # module CpuInstructions

#=--- end of file ---------------------------------------------------------=#


text:
@testset "Format repo" begin
    sandbox_dir = joinpath(tempdir(), "format_repo_test")
    mkdir(sandbox_dir)
    try
        cp(@__DIR__, sandbox_dir; force = true)

        # initial format
        format(sandbox_dir)

        # follow up formats should be the same
        @test format(sandbox_dir, join_lines_based_on_source = true) == true
        @test format(sandbox_dir, join_lines_based_on_source = true, margin = 10_000) ==
              true
    finally
        rm(sandbox_dir; recursive = true)
    end
end


text:
using Gadfly

set_default_plot_size(6inch, 6inch)

plot(x=rand(100), y=rand(100), shape=rand(1:8, 100), Geom.point)


text:
@testset "deterministic topologies" begin
@test PhyloNetworks.startree_newick(3) == "(t1:1.0,t2:1.0,t3:1.0);"
@test PhyloNetworks.symmetrictree_newick(2, 0.1, 2) == "((A2:0.1,A3:0.1):0.1,(A4:0.1,A5:0.1):0.1);"
@test PhyloNetworks.symmetricnet_newick(3, 2, 1, 0.1, 1) ==
    "(((#H:1::0.1,(A1:1,A2:1):0.5):0.5,((A3:0.5)#H:0.5::0.9,A4:1):1):1,((A5:1,A6:1):1,(A7:1,A8:1):1):1);"
@test PhyloNetworks.symmetricnet_newick(3, 3, 3, 0.1, 1) ==
    "((#H:1::0.1,((A1:1,A2:1):1,(A3:1,A4:1):1):0.5):0.5,(((A5:1,A6:1):1,(A7:1,A8:1):1):0.5)#H:0.5::0.9);"
@test PhyloNetworks.symmetricnet_newick(4, 2, 0.1) ==
    "((((#H1:0.25::0.1,(A1:0.25,A2:0.25):0.125):0.125,((A3:0.125)#H1:0.125::0.9,A4:0.25):0.25):0.25,((#H2:0.25::0.1,(A5:0.25,A6:0.25):0.125):0.125,((A7:0.125)#H2:0.125::0.9,A8:0.25):0.25):0.25):0.25,(((#H3:0.25::0.1,(A9:0.25,A10:0.25):0.125):0.125,((A11:0.125)#H3:0.125::0.9,A12:0.25):0.25):0.25,((#H4:0.25::0.1,(A13:0.25,A14:0.25):0.125):0.125,((A15:0.125)#H4:0.125::0.9,A16:0.25):0.25):0.25):0.25);"
end


text:
# Translation from our variables to Nocedal and Wright's
# JMW's dx <=> NW's s
# JMW's dg <=> NW' y
immutable BFGS{L<:Function, H<:Function} <: Optimizer
    linesearch!::L
    initial_invH::H
end
#= uncomment for v0.8.0
BFGS(; linesearch = LineSearches.hagerzhang!, initial_invH = x -> eye(eltype(x), length(x))) =
  BFGS(linesearch, initial_invH)
=#
function BFGS(; linesearch! = nothing,
                linesearch = LineSearches.hagerzhang!,
                initial_invH = x -> eye(eltype(x), length(x)))
    linesearch = get_linesearch(linesearch!, linesearch)
    BFGS(linesearch, initial_invH)
end

type BFGSState{T}
    @add_generic_fields()
    x_previous::Array{T}
    g::Array{T}
    g_previous::Array{T}
    f_x_previous::T
    dx::Array{T}
    dg::Array{T}
    u::Array{T}
    I::Array{T}
    invH::Array{T}
    s::Array{T}
    @add_linesearch_fields()
end

function initial_state{T}(method::BFGS, options, d, initial_x::Array{T})
    n = length(initial_x)
    g = Array(T, n)
    f_x = d.fg!(initial_x, g)
    invH = method.initial_invH(initial_x)
    # Maintain a cache for line search results
    # Trace the history of states visited
    BFGSState("BFGS",
              n,
              copy(initial_x), # Maintain current state in state.x
              f_x, # Store current f in state.f_x
              1, # Track f calls in state.f_calls
              1, # Track g calls in state.g_calls
              0, # Track h calls in state.h_calls
              copy(initial_x), # Maintain current state in state.x_previous
              g, # Store current gradient in state.g
              copy(g), # Store previous gradient in state.g_previous
              T(NaN), # Store previous f in state.f_x_previous
              Array{T}(n), # Store changes in position in state.dx
              Array{T}(n), # Store changes in gradient in state.dg
              Array{T}(n), # Buffer stored in state.u
              eye(T, size(invH)...),
              invH, # Store current invH in state.invH
              Array{T}(n), # Store current search direction in state.s
              @initial_linesearch()...) # Maintain a cache for line search results in state.lsr
end

function update_state!{T}(d, state::BFGSState{T}, method::BFGS)
    lssuccess = true
    # Set the search direction
    # Search direction is the negative gradient divided by the approximate Hessian
    A_mul_B!(state.s, state.invH, state.g)
    scale!(state.s, -1)

    # Refresh the line search cache
    dphi0 = vecdot(state.g, state.s)
    # If invH is not positive definite, reset it to I
    if dphi0 > 0.0
        copy!(state.invH, state.I)
        @simd for i in 1:state.n
            @inbounds state.s[i] = -state.g[i]
        end
        dphi0 = vecdot(state.g, state.s)
    end
    LineSearches.clear!(state.lsr)
    push!(state.lsr, zero(T), state.f_x, dphi0)

    # Determine the distance of movement along the search line
    try
        state.alpha, f_update, g_update =
            method.linesearch!(d, state.x, state.s, state.x_ls, state.g_ls, state.lsr,
                               state.alpha, state.mayterminate)
        state.f_calls, state.g_calls = state.f_calls + f_update, state.g_calls + g_update
    catch ex
        if isa(ex, LineSearches.LineSearchException)
            lssuccess = false
            state.f_calls, state.g_calls = state.f_calls + ex.f_update, state.g_calls + ex.g_update
            state.alpha = ex.alpha
            Base.warn("Linesearch failed, using alpha = $(state.alpha) and exiting optimization.")
        else
            rethrow(ex)
        end
    end

    # Maintain a record of previous position
    copy!(state.x_previous, state.x)

    # Update current position
    @simd for i in 1:state.n
        @inbounds state.dx[i] = state.alpha * state.s[i]
        @inbounds state.x[i] = state.x[i] + state.dx[i]
    end

    # Maintain a record of the previous gradient
    copy!(state.g_previous, state.g)
    (lssuccess == false) # break on linesearch error
end

function update_h!(d, state, mehtod::BFGS)
    # Measure the change in the gradient
    @simd for i in 1:state.n
        @inbounds state.dg[i] = state.g[i] - state.g_previous[i]
    end

    # Update the inverse Hessian approximation using Sherman-Morrison
    dx_dg = vecdot(state.dx, state.dg)
    if dx_dg == 0.0
        return true # force stop
    end
    A_mul_B!(state.u, state.invH, state.dg)

    c1 = (dx_dg + vecdot(state.dg, state.u)) / (dx_dg * dx_dg)
    c2 = 1 / dx_dg

    # invH = invH + c1 * (s * s') - c2 * (u * s' + s * u')
    for i in 1:state.n
        @simd for j in 1:state.n
            @inbounds state.invH[i, j] += c1 * state.dx[i] * state.dx[j] - c2 * (state.u[i] * state.dx[j] + state.u[j] * state.dx[i])
        end
    end
end


text:
#=Given an Array you need to find the maximum sum of all the
subarrays. Subarrays are consecutive elements of the array.
There are a total of n*(n+1)/2 subarrays in an array with
n elements. Out of all these sub arrays find the subarray with
the maximum sum. Kadane's Algorithm helps us to do this in
the least time complexity.=#

## Function

function maxSubArray(array)
    best = 0
    sum = 0
    for i in array
        sum = max(i, sum + i)
        best = max(sum, best)
    end
    return best
end

## Input

n = readline()
n = parse(Int64, n)
arr = Int64[]
for i = 1:n
    temp = readline()
    temp = parse(Int64, temp)
    push!(arr, temp)
end

## Calling the function

print(maxSubArray(arr))

#=
Sample test case:
Input:
    n = 5
    array = 2 -3 5 4 -7
Output:
    9

Time complexity: O(N)
=#


text:
using DataFrames
using DataFramesMeta
using Gadfly
using Schrute
using Statistics

df = Schrute.theOffice()

# what is the mean score per season?
@linq df |>
    by(:season, mean_score = mean(:imdb_rating)) |>
    select(:season, :mean_score) |>
    display

@linq df |>
    by(:season, mean_score = mean(:imdb_rating)) |>
    select(:season, :mean_score) |>
    plot(x=:season, y=:mean_score, Geom.line, Guide.xticks(ticks=collect(1:9)), Guide.title("Mean IMDB Score by Season"))

# who has the most lines in the series?
foo = by(df, :character,  nrow)[1:8,:]
sort!(foo, :x1, rev = true)
rename!(foo, Dict(:x1 => "Number of Lines"))

mytheme = Theme(bar_spacing=25pt);

plot(foo,
     x=:character,
     y=:"Number of Lines",
     Geom.bar,
     Guide.yticks(ticks=[2000,4000,6000,8000,10000,12000]),
     Guide.title("Lines by Character"),
     mytheme
)

# that's what she said?

twss = []
for line in df[:,:text]
    # println(line)
    try
        foo = occursin("what she said", line)
        # println(foo)
        push!(twss, string(foo))
    catch
        push!(twss, "false")
        continue
    end
end

df[:,:twss] = twss;

twss_df = @linq df |>
    where(:twss .== "true")
    by(:season, count(:twss))

twss_toplot =  by(twss_df, :character, nrow)
rename!(twss_toplot, Dict(:x1 => "Occurrences"))

plot(twss_toplot,
     x=:character,
     y=:Occurrences,
     Geom.bar,
     Guide.yticks(ticks=[5,10,15,20,25]),
     Guide.title("That's What She Said by Character"),
     mytheme
)

# writer vs. imdb score?
epi_hist = @linq df |>
    by(:season, mean_rating = mean(:imdb_rating))

set_default_plot_size(21cm, 8cm)
p = plot(df, xgroup=:season,  x=:episode, y=:imdb_rating,
    Geom.subplot_grid(Geom.line), Guide.title("IMDB Rating by Episode"))

draw(PNG("foo.png", 6inch, 4inch), p)

set_default_plot_size(9cm, 8cm)

plot(df,
    x=:season,
    y=:imdb_rating,
    Geom.boxplot,
    Guide.xticks(ticks=collect(1:9)),
    Guide.title("IMDB Rating Distribution by Season")
)

# what were those outliers in season 6 and 8?
@linq df |>
    by(:season, worst = minimum(:imdb_rating))

df_worst_a = @linq df |>
    # groupby([:episode_name, :season]) |>
    by([:episode_name, :season], score = mean(:imdb_rating))

df_worst_b = @linq df_worst_a |>
    groupby(:season) |>
    transform(min_sc = minimum(:score)) |>
    where(:min_sc .== :score) |>
    select(:season, :episode_name, :score)


text:
# https://github.com/nolta/Winston.jl

# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation

supportedArgs(::WinstonBackend) = [
    :annotation,
    # :args,
    # :axis,
    # :background_color,
    :linecolor,
    :color_palette,
    :fillrange,
    :fillcolor,
    # :foreground_color,
    :group,
    # :heatmap_c,
    # :kwargs,
    :label,
    # :layout,
    :legend,
    :seriescolor, :seriesalpha,
    :linestyle,
    :linetype,
    :linewidth,
    :markershape,
    :markercolor,
    :markersize,
    # :markerstrokewidth,
    # :markerstrokecolor,
    # :markerstrokestyle,
    # :n,
    :bins,
    # :nc,
    # :nr,
    # :pos,
    :smooth,
    # :ribbon,
    :show,
    :size,
    :title,
    :windowtitle,
    :x,
    :xlabel,
    :xlims,
    # :xticks,
    :y,
    :ylabel,
    :ylims,
    # :yrightlabel,
    # :yticks,
    :xscale,
    :yscale,
    # :xflip,
    # :yflip,
    # :z,
  ]
supportedAxes(::WinstonBackend) = [:auto, :left]
supportedTypes(::WinstonBackend) = [:none, :line, :path, :sticks, :scatter, :hist, :bar]
supportedStyles(::WinstonBackend) = [:auto, :solid, :dash, :dot, :dashdot]
supportedMarkers(::WinstonBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
supportedScales(::WinstonBackend) = [:identity, :log10]
subplotSupported(::WinstonBackend) = false

# --------------------------------------------------------------------------------------

function _initialize_backend(::WinstonBackend; kw...)
  @eval begin
    # ENV["WINSTON_OUTPUT"] = "gtk"
    warn("Winston is no longer supported... many features will likely be broken.")
    import Winston, Gtk
    export Winston, Gtk
  end
end

# ---------------------------------------------------------------------------

## dictionaries for conversion of Plots.jl names to Winston ones.
@compat const winston_linestyle =  KW(:solid=>"solid",
                                :dash=>"dash",
                                :dot=>"dotted",
                                :dashdot=>"dotdashed"
                               )

@compat const winston_marker = KW(:none=>".",
                            :rect => "square",
                            :ellipse=>"circle",
                            :diamond=>"diamond",
                            :utriangle=>"triangle",
                            :dtriangle=>"down-triangle",
                            :cross => "plus",
                            :xcross => "cross",
                            :star5 => "asterisk"
                           )

function _before_add_series(plt::Plot{WinstonBackend})
  Winston.ghf(plt.o)
end

# ---------------------------------------------------------------------------

function _create_plot(pkg::WinstonBackend, d::KW)
  wplt = Winston.FramedPlot(title = d[:title], xlabel = d[:xlabel], ylabel = d[:ylabel])

  Plot(wplt, pkg, 0, d, KW[])
end

copy_remove(d::KW, s::Symbol) = delete!(copy(d), s)

function addRegressionLineWinston(d::KW, wplt)
  xs, ys = regressionXY(d[:x], d[:y])
  Winston.add(wplt, Winston.Curve(xs, ys, kind="dotted"))
end

function getWinstonItems(plt::Plot)
  if isa(plt.o, Winston.FramedPlot)
    wplt = plt.o
    window, canvas = nothing, nothing
  else
    window, canvas, wplt = plt.o
  end
  window, canvas, wplt
end

function _add_series(::WinstonBackend, plt::Plot, d::KW)
  window, canvas, wplt = getWinstonItems(plt)

  # until we call it normally, do the hack
  if d[:linetype] == :bar
    d = barHack(;d...)
  end

  e = KW()
  e[:color] = getColor(d[:linecolor])
  e[:linewidth] = d[:linewidth]
  e[:kind] = winston_linestyle[d[:linestyle]]
  e[:symbolkind] = winston_marker[d[:markershape]]
  # markercolor     # same choices as `color`, or :match will set the color to be the same as `color`
  e[:symbolsize] = d[:markersize] / 5

  # pos             # (Int,Int), move the enclosing window to this position
  # screen          # Integer, move enclosing window to this screen number (for multiscreen desktops)

  ## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :hist2d, :hexbin, :hist, :bar
  if d[:linetype] == :none
    Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))

  elseif d[:linetype] == :path
    x, y = d[:x], d[:y]
    Winston.add(wplt, Winston.Curve(x, y; e...))

    fillrange = d[:fillrange]
    if fillrange != nothing
      if isa(fillrange, AbstractVector)
        y2 = fillrange
      else
        y2 = Float64[fillrange for yi in y]
      end
      Winston.add(wplt, Winston.FillBetween(x, y, x, y2, fillcolor=getColor(d[:fillcolor])))
    end

  elseif d[:linetype] == :scatter
    if d[:markershape] == :none
      d[:markershape] = :ellipse
    end

  # elseif d[:linetype] == :step
  #     fn = Winston.XXX

  # elseif d[:linetype] == :stepinverted
  #     fn = Winston.XXX

  elseif d[:linetype] == :sticks
      Winston.add(wplt, Winston.Stems(d[:x], d[:y]; e...))

  # elseif d[:linetype] == :dots
  #     fn = Winston.XXX

  # elseif d[:linetype] == :hist2d
  #     fn = Winston.XXX

  # elseif d[:linetype] == :hexbin
  #     fn = Winston.XXX

  elseif d[:linetype] == :hist
      hst = hist(d[:y], d[:bins])
      Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :bins)...))

  # elseif d[:linetype] == :bar
  #     # fn = Winston.XXX

  else
    error("linetype $(d[:linetype]) not supported by Winston.")

  end

  # markershape
  if d[:markershape] != :none
    Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))
  end

  # optionally add a regression line
  d[:smooth] && d[:linetype] != :hist && addRegressionLineWinston(d, wplt)

  push!(plt.seriesargs, d)
  plt
end

# ----------------------------------------------------------------

@compat const _winstonNames = KW(
    :xlims => :xrange,
    :ylims => :yrange,
    :xscale => :xlog,
    :yscale => :ylog,
  )

function _update_plot(plt::Plot{WinstonBackend}, d::KW)
  window, canvas, wplt = getWinstonItems(plt)
  for k in (:xlabel, :ylabel, :title, :xlims, :ylims)
    if haskey(d, k)
      Winston.setattr(wplt, string(get(_winstonNames, k, k)), d[k])
    end
  end

  for k in (:xscale, :yscale)
    if haskey(d, k)
      islogscale = d[k] == :log10
      Winston.setattr(wplt, (k == :xscale ? :xlog : :ylog), islogscale)
    end
  end

end

# ----------------------------------------------------------------

function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::@compat(AbstractString))
  Winston.text(x, y, val)
end

function _add_annotations{X,Y,V}(plt::Plot{WinstonBackend}, anns::AVec{@compat(Tuple{X,Y,V})})
  for ann in anns
    createWinstonAnnotationObject(plt, ann...)
  end
end

# ----------------------------------------------------------------

function _create_subplot(subplt::Subplot{WinstonBackend}, isbefore::Bool)
  # TODO: build the underlying Subplot object.  this is where you might layout the panes within a GUI window, for example
end

# ----------------------------------------------------------------

function addWinstonLegend(plt::Plot, wplt)
  if plt.plotargs[:legend] != :none
    Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs])
  end
end

function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{WinstonBackend})
  window, canvas, wplt = getWinstonItems(plt)
  addWinstonLegend(plt, wplt)
  writemime(io, "image/png", wplt)
end

function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend})

  window, canvas, wplt = getWinstonItems(plt)

  if window == nothing
    if Winston.output_surface != :gtk
      error("Gtk is the only supported display for Winston in Plots.  Set `output_surface = gtk` in src/Winston.ini")
    end
    # initialize window
    w,h = plt.plotargs[:size]
    canvas = Gtk.GtkCanvasLeaf()
    window = Gtk.GtkWindowLeaf(canvas, plt.plotargs[:windowtitle], w, h)
    plt.o = (window, canvas, wplt)
  end

  addWinstonLegend(plt, wplt)

  Winston.display(canvas, wplt)
  Gtk.showall(window)
end

function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend})
  # TODO: display/show the Subplot object
end


text:
using Multilane
using JLD
using Multilane
using POMDPs
using MCTS
using DataFrames
using DataFramesMeta
using Plots
using StatPlots

# results = load("results_Aug_22_23_26.jld")
# results = load("combined_results_Aug_25_10_10.jld")
# results = load("combined_results_Aug_26_19_53.jld")
# results = load("combined_results_Aug_27_14_05.jld")
results = load("combined_results_Aug_29_10_08.jld")

stats = results["stats"]
tests = results["tests"]

mean_performance = by(stats, [:lambda, :test_key]) do df
    DataFrame(steps_in_lane=mean(df[:steps_in_lane]),
              nb_brakes=mean(df[:nb_brakes]),
              time_to_lane=mean(df[:time_to_lane]),
              steps=mean(df[:steps]),
              test_key=first(df[:test_key])
              )
end
mean_performance[:brakes_per_sec] = mean_performance[:nb_brakes]./mean_performance[:time_to_lane]

plts = []
# for p in linspace(0., 1., 5)
# for p in linspace(0., 3/4, 4)
for p in linspace(0., 1.25, 6)
    ubg = @where(mean_performance, :test_key.==@sprintf("upper_bound_%03d", 100*p))
    plot(ubg, :time_to_lane, :nb_brakes, group=:test_key)
    ang = @where(mean_performance, :test_key.==@sprintf("assume_normal_%03d", 100*p))
    push!(plts, plot!(ang, :time_to_lane, :nb_brakes, group=:test_key, title="Parameter Range = $p", xlim=(0,25), ylim=(0,2.5)))
end
plot(plts...)
gui()