#changed generate_default_data_dictionary
"""
TODO: Fill me in with some stuff ...
"""
function optimize_specific_growth_rate(data_dictionary::Dict{String,Any})

    # get some stuff from dd -
    number_of_reactions = data_dictionary["number_of_reactions"]

    # update the c-vector -
    objective_coefficient_array = data_dictionary["objective_coefficient_array"]
    objective_coefficient_array[13] = -1

    # return -
    return data_dictionary
end

"""
TODO: Fill me in with some stuff ...
"""
function optimize_flux_at_index(index_array::Array{Int64,1}, data_dictionary::Dict{String,Any})

    # update the c-vector -
    objective_coefficient_array = data_dictionary["objective_coefficient_array"]

    # iterate through the index array, and flip the flag on the coefficient array -
    for index_value in index_array
        objective_coefficient_array[index_value] = -1
    end

    # return -
    return data_dictionary
end

function constrain_measured_metabolites(data_dictionary::Dict{String,Any}, path_to_measurements_file::String)

    # from the data dictionary, get the list of metabolite symbols -
    list_of_metabolite_symbols = data_dictionary["list_of_metabolite_symbols"]

    # load the measurements file -
    measurements_dictionary = JSON.parsefile(path_to_measurements_file)

    # get the stoichiometric_matrix -
    stoichiometric_matrix = copy(data_dictionary["stoichiometric_matrix"])

    # get the metabolite measurement information -
    absolute_metabolite_measurement_dictionary = measurements_dictionary["absolute_metabolite_measurements"]
    for (metabolite_key, local_measurement_dict) in absolute_metabolite_measurement_dictionary

        # ok, so we have a metabolite_key - find the index of this metabolite in the metabolite list -
        idx_metabolite_match = findall(list_of_metabolite_symbols .== metabolite_key)

        # if we have this metabolite, update the STM -
        if (isempty(idx_metabolite_match) == false)

            # get experimental values -
            mean_ratio_value = parse(Float64,local_measurement_dict["mean_value"])
            lower_bound = parse(Float64,local_measurement_dict["lower_bound"])
            upper_bound = parse(Float64,local_measurement_dict["upper_bound"])

            # compute a value -
            r_value = rand()
            value = (1 - r_value)*lower_bound + r_value*(upper_bound)

            # update the stm -
            metabolite_index = (getindex(idx_metabolite_match))[1]
            old_value = stoichiometric_matrix[metabolite_index,growth_rate_reaction_index]
            stoichiometric_matrix[metabolite_index,growth_rate_reaction_index] = (old_value - value)
        end
    end

    data_dictionary["stoichiometric_matrix"] = stoichiometric_matrix
    return data_dictionary
end

function constrain_measured_fluxes(data_dictionary::Dict{String,Any}, path_to_measurements_file::String)

    # TODO: is the path_to_measurements_file legit?
    measurements_dictionary = JSON.parsefile(path_to_measurements_file)

    # get some stuff from the dd -
    rxn_name_list = data_dictionary["list_of_reaction_name_strings"]
    flux_bounds_array = data_dictionary["flux_bounds_array"]
    (number_of_reactions,nc) = size(flux_bounds_array)

    # do we have any flux ratios?
    flux_ratio_dictionary_array = measurements_dictionary["flux_ratio_measurements"]

    # initialize -
    number_of_additional_constraints = length(flux_ratio_dictionary_array)
    constraint_counter = 1
    additional_constraint_array = zeros(number_of_additional_constraints,number_of_reactions)
    for (reaction_key, local_measurement_dict) in flux_ratio_dictionary_array

        # ok, so we have a reaction key - find the index of this reaction in the reaction list -
        idx_reaction_match = findall(rxn_name_list .== reaction_key)

        # if we have this reaction, then update the bounds array -
        if (isempty(idx_reaction_match) == false)

            # all fluxes are relative to glucose -
            glucose_uptake_index = 50   # GLCpts -

            # get ratio value -
            mean_ratio_value = parse(Float64,local_measurement_dict["mean_value"])

            # get the measurement CV -
            measurement_cv = parse(Float64,local_measurement_dict["coefficient_of_variation"])

            # build a distribution, and draw a sample -
            distrubution = Distributions.Normal(mean_ratio_value, measurement_cv*mean_ratio_value)
            sampled_ratio_value = rand(distrubution)

            # get the actual index -
            idx_reaction = (getindex(idx_reaction_match))[1]

            # update the constraint -
            additional_constraint_array[constraint_counter,idx_reaction] = 1.0
            additional_constraint_array[constraint_counter,glucose_uptake_index] = -1*sampled_ratio_value

            # update -
            constraint_counter = constraint_counter + 1
        end
    end

    # cache the additional constraints -
    data_dictionary["additional_constraint_array"] = additional_constraint_array

    # we need to add some additional "species bounds" -
    tmp_species_bounds_array = data_dictionary["species_bounds_array"]
    additional_constraint_block = zeros(number_of_additional_constraints,2)
    species_bounds_array = [tmp_species_bounds_array ; additional_constraint_block]
    data_dictionary["species_bounds_array"] = species_bounds_array

    # iterate through the measurements_dictionary and set values -
    individual_measuerment_dictionaries = measurements_dictionary["exchange_flux_measurements"]
    for (reaction_key, local_measurement_dict) in individual_measuerment_dictionaries

        # ok, so we have a reaction key - find the index of this reaction in the reaction list -
        idx_reaction_match = findall(rxn_name_list .== reaction_key)

        # if we have this reaction, then update the bounds array -
        if (isempty(idx_reaction_match) == false)

            # get the measured value -
            mean_measured_value = parse(Float64,local_measurement_dict["mean_value"])

            # get the CV value -
            coefficient_of_variation = parse(Float64, local_measurement_dict["coefficient_of_variation"])

            # what is the measured value?
            distrubution = Distributions.Normal(mean_measured_value, coefficient_of_variation*mean_measured_value)
            measured_value = abs(rand(distrubution))

            # get the actual index -
            idx_reaction = (getindex(idx_reaction_match))[1]

            # what is the directionality of this measurement?
            directionality = Symbol(local_measurement_dict["directionality"])
            if directionality == :input

                flux_bounds_array[idx_reaction,1] = -1*measured_value
                flux_bounds_array[idx_reaction,2] = 0.0

            elseif directionality == :output

                flux_bounds_array[idx_reaction,1] = 0
                flux_bounds_array[idx_reaction,2] = measured_value

            elseif directionality == :free

                flux_bounds_array[idx_reaction,1] = -1*measured_value
                flux_bounds_array[idx_reaction,2] = measured_value

            elseif directionality == :input_fixed

                flux_bounds_array[idx_reaction,1] = -1*measured_value
                flux_bounds_array[idx_reaction,2] = -1*measured_value

            elseif directionality == :output_fixed

                flux_bounds_array[idx_reaction,1] = measured_value
                flux_bounds_array[idx_reaction,2] = measured_value

            elseif directionality == :nil

                flux_bounds_array[idx_reaction,1] = -1e-6
                flux_bounds_array[idx_reaction,2] = 1e-6

            else
                # TODO: issue a warning ...
            end
        end
    end

    # update -
    data_dictionary["flux_bounds_array"] = flux_bounds_array

    # return modified dictionary -
    return data_dictionary
end

"""
TODO: Fill me in with some stuff ...
"""
function generate_exchange_flux_index_array(reaction_name_array::Array{String,1},pattern::String)

    # initialize -
    exchange_flux_index_array = Int64[]

    # we are looking for a name starting w/EX_
    number_of_reactions = length(reaction_name_array)
    for reaction_index = 1:number_of_reactions

        # grab the name -
        reaction_name = reaction_name_array[reaction_index]

        # check -
        if (startswith(reaction_name,pattern) == true)
            push!(exchange_flux_index_array,reaction_index)
        end
    end

    # return -
    return exchange_flux_index_array
end

"""
TODO: Fill me in with some stuff ...
"""
function generate_default_data_dictionary() #Updted to include rules

    # hardcode paths to stuff (paths internal to package)
    path_to_cobra_mat_file = "$(path_to_package)/cobra/config/matlab_cobra_files/modelReg.mat"
    model_name = "modelReg"
    organism_id = :eco    # we use the KEGG organism symbols

    # Wher are we?
    top_level_path = pwd()

    # load the biophysical_constants dictionary -
    default_biophysical_dictionary = load_default_biophysical_dictionary(organism_id)

    # TODO: check if string is a legit path to the cobra file, and the model name is ok
    # load the *.mat code from the cobra code folder -
    file = matopen(path_to_cobra_mat_file)
    cobra_dictionary = read(file,model_name)
    close(file)

    # Setup: the stoichiometric matrix -
    stoichiometric_matrix = Matrix(cobra_dictionary["S"])
    (number_of_species,number_of_reactions) = size(stoichiometric_matrix)

    # get the species symbol list -
    list_of_metabolite_symbols = cobra_dictionary["mets"]

    # get list of gene symbols -
    list_of_gene_symbols = cobra_dictionary["genes"]

    # setup the objective_coefficient_array -
    objective_coefficient_array = cobra_dictionary["c"]

    #generate list of regulatoryRules and rxn names
    regulatoryRules = cobra_dictionary["regulatoryRules"]
    rxns = cobra_dictionary["rxns"]

    #generate grRules and rules and rxnGeneMat
    grRules = cobra_dictionary["grRules"]
    irules = cobra_dictionary["rules"]
    rxnGeneMat = cobra_dictionary["rxnGeneMat"]

    #Generate regulatoryGenes
    regulatoryGenes = cobra_dictionary["regulatoryGenes"]

    #generate regulatory inputs 1 and 2
    regulatoryInputs1 = cobra_dictionary["regulatoryInputs1"]
    regulatoryInputs2 = cobra_dictionary["regulatoryInputs2"]
    # Setup: default flux bounds array, including the "exhange fluxes"
    default_flux_bounds_array = zeros(number_of_reactions,2)
    lb = cobra_dictionary["lb"] # lower bound -
    ub = cobra_dictionary["ub"] # upper bound -
    for reaction_index = 1:number_of_reactions
        default_flux_bounds_array[reaction_index,1] = lb[reaction_index]
        default_flux_bounds_array[reaction_index,2] = ub[reaction_index]
    end

    # calculate the default vamx (based upon biophysical_constants/literature) -
    (default_vmax, default_enzyme_concentration) = calculate_default_vmax(default_biophysical_dictionary)

    # load the ec_number file -
    path_to_ec_file::String = "$(path_to_package)/cobra/config/data/ec_numbers.dat"
    ec_number_dictionary =  load_ec_mapping_file(path_to_ec_file)

    # load the gene order array -
    path_to_gene_file = "$(path_to_package)/cobra/config/data/gene_list.dat"
    gene_order_array = load_gene_order_file(path_to_gene_file)

    # calculate the vamx array -
    path_to_brenda_data = "$(path_to_package)/cobra/config/data/ECN-EcoliCoreModel-Palsson-EcoSal-Plus-2013.json"
    total_vmax_array = generate_vmax_array(path_to_brenda_data, default_biophysical_dictionary, gene_order_array, ec_number_dictionary)

    # correct the total_vmax_array for this model, using the rules -
    local_data_dictionary = Dict{String,Any}()
    local_data_dictionary["default_vmax_value"] = default_vmax
    model_vmax_array = calculate_rules_vector(local_data_dictionary, total_vmax_array)

    # update the default bounds array w/our "default" biophysical_constants -
    flux_bounds_array = update_default_flux_bounds_array(default_flux_bounds_array, model_vmax_array, cobra_dictionary)
    flux_bounds_array = default_flux_bounds_array #remove this LATER
    # species bounds array - default, everything is bounded 0,0
    # species in the [e] (extracellular) compartment are unbounded
    # we have *updated* the STM w/exchange reactions [] -> a and a -> []
    # all species are bounded to 0
    species_bounds_array = zeros(number_of_species,2)

    # create list of reaction strings -
    list_of_reaction_name_strings = cobra_dictionary["rxns"]

    # What sense do we do? (by default we min)
    is_minimum_flag = true

    # construct the reaction string list -
    list_of_chemical_reaction_strings = reconstruct_reaction_string_list(cobra_dictionary)

    # grab the reversible reaction list -
    reversible_reaction_flag_array = cobra_dictionary["rev"]

    # calculate the reaction name -
    reaction_name_array_tmp = cobra_dictionary["rxns"]
    reaction_name_array = String[]
    for rxn_name in reaction_name_array_tmp
        push!(reaction_name_array,rxn_name)
    end
    exchange_flux_index_array = generate_exchange_flux_index_array(reaction_name_array,"EX_")
    exchange_flux_index_array = [exchange_flux_index_array ; 13]    # add the growth rate -

    # =============================== DO NOT EDIT BELOW THIS LINE ============================== #
	data_dictionary = Dict{String,Any}()
	data_dictionary["stoichiometric_matrix"] = stoichiometric_matrix
    data_dictionary["number_of_species"] = number_of_species
	data_dictionary["number_of_reactions"] = number_of_reactions
    data_dictionary["flux_bounds_array"] = flux_bounds_array
    data_dictionary["species_bounds_array"] = species_bounds_array
    data_dictionary["objective_coefficient_array"] = objective_coefficient_array
	data_dictionary["list_of_metabolite_symbols"] = list_of_metabolite_symbols
    data_dictionary["list_of_gene_symbols"] = list_of_gene_symbols
    data_dictionary["list_of_reaction_name_strings"] = list_of_reaction_name_strings
    data_dictionary["list_of_chemical_reaction_strings"] = list_of_chemical_reaction_strings
	data_dictionary["is_minimum_flag"] = is_minimum_flag
    data_dictionary["reversible_reaction_flag_array"] = reversible_reaction_flag_array
    data_dictionary["exchange_flux_index_array"] = exchange_flux_index_array

    # stuff for rules -
    data_dictionary["default_vmax"] = default_vmax
    data_dictionary["regulatoryRules"] = regulatoryRules
    data_dictionary["grRules"] = grRules
    data_dictionary["irules"] = irules
    data_dictionary["rxnGeneMat"] = rxnGeneMat
    data_dictionary["regulatoryGenes"] = regulatoryGenes
    data_dictionary["regulatoryInputs1"] = regulatoryInputs1
    data_dictionary["regulatoryInputs2"] = regulatoryInputs2
    data_dictionary["rxns"] = rxns

    # in case we need something later -
    data_dictionary["cobra_dictionary"] = cobra_dictionary
    # ========================================================================================= #
    return data_dictionary
end

# - PRIVATE HELPER METHODS -------------------------------------------------------------------- #
function load_gene_order_file(path_to_gene_file)

    # gene array -
    gene_symbol_array = String[]

    # open the gene file -
    # TODO: We need to check to see if this is a legit path -
    open(path_to_gene_file) do f

        # load the file into the buffer -
        buffer = read(f, String)

        # split along new line -
        list_of_records = split(buffer,'\n')

        # how many records do we have?
        number_of_records = length(list_of_records)
        for record_index = 1:number_of_records

            # convert the record to a string -
            record = string(list_of_records[record_index])

            # is the record empty?
            if (isempty(record) == false)

                # split into fields -
                field_array = split(record,',')

                # grab the ec number -
                order_index = field_array[1]
                gene_symbol = field_array[2]

                # add a record -
                push!(gene_symbol_array, gene_symbol)
            end
        end
    end

    # return -
    return gene_symbol_array
end

function load_ec_mapping_file(path_to_ec_file::String)

    # initialize the ec number array -
    ec_number_dictionary = Dict{String,String}()

    # open the ec file -
    open(path_to_ec_file) do f

        # load the file into the buffer -
        buffer = read(f, String)

        # split along new line -
        list_of_records = split(buffer,'\n')

        # how many records do we have?
        number_of_records = length(list_of_records)
        for record_index = 1:number_of_records

            # convert the record to a string -
            record = string(list_of_records[record_index])

            # is the record empty?
            if (isempty(record) == false)

                # split into fields -
                field_array = split(record,'\t')

                # grab the ec number -
                bg_number = field_array[1]
                ec_number = field_array[2]

                # add a record -
                ec_number_dictionary[bg_number] = ec_number

            end
        end
    end

    # return -
    return ec_number_dictionary
end

function generate_vmax_array(path_to_brenda_data::String, biophysical_dictionary::Dict{String,Any}, gene_symbol_array::Array{String,1}, ec_number_dictionary::Dict{String,String})

    # TODO: is the path_to_brenda_data legit?
    kcat_measurements_dictionary = JSON.parsefile(path_to_brenda_data)

    # initialize -
    vmax_array = Float64[]

    # calculate a default value -
    (default_vmax, default_enzyme_concentration) = calculate_default_vmax(biophysical_dictionary)

    # for each gene symbol, lookup an ec number, then get a range of measured kcats -
    for gene_symbol in gene_symbol_array

        # do we have this gene key?
        if (haskey(ec_number_dictionary, gene_symbol) == true)

            # what is the ec number for this key?
            ec_number_key = ec_number_dictionary[gene_symbol]

            # ok, so we have an ec number. Do we have a kcat measurement for this ec number?
            if (haskey(kcat_measurements_dictionary, ec_number_key) == true)

                # ok, we have an ec_number w/data - get the data
                # this will come back as an array of dictionaries
                local_vmax_array = Float64[]
                array_data_dictionaries = kcat_measurements_dictionary[ec_number_key]
                for local_data_dictionary::Dict{String,Any} in array_data_dictionaries

                    # get the turnoverNumber -
                    turnoverNumber = parse(Float64,local_data_dictionary["turnoverNumber"])*(3600) # hr^-1

                    # there is a "strange" data issue w/the Brenda data. They use -999 to mean no value
                    if (turnoverNumber>0)
                        # cache -
                        push!(local_vmax_array, turnoverNumber*default_enzyme_concentration)
                    end
                end

                if isempty(local_vmax_array) == true
                    push!(local_vmax_array, default_vmax)
                end

                # what is the min/max -
                min_value = minimum(local_vmax_array)
                max_value = maximum(local_vmax_array)

                # calculate a value (uniform random between the measurements)
                sampled_vmax_value = min_value + (max_value - min_value)*rand()

                # yes - we have an experimental value -
                push!(vmax_array, sampled_vmax_value)
            else

                # nope - bummer. Use the default value -
                push!(vmax_array, default_vmax)
            end
        else

            # nope - bummer. Use the default value -
            push!(vmax_array, default_vmax)
        end
    end

    # return -
    return vmax_array
end

function calculate_default_vmax(biophysical_dictionary::Dict{String,Any})

    # ok, so we need to get some stuff from the dictionary -
    # TODO: Check these keys are contained in the dictionary
    default_turnover_number = parse(Float64,biophysical_dictionary["biophysical_constants"]["default_turnover_number"]["value"])              # convert to h^-1
    default_enzyme_concentration = parse(Float64,biophysical_dictionary["biophysical_constants"]["default_enzyme_concentation"]["value"])     # mumol/gDW

    # calculate the default VMax -
    default_vmax = (default_turnover_number)*(default_enzyme_concentration)*(3600)

    return (default_vmax, default_enzyme_concentration)
end

function update_default_flux_bounds_array(flux_bounds_array::Array{Float64,2}, vmax_array::Array{Float64,1}, cobra_dictionary::Dict{String,Any})

    # get the reverse flag array from the cobra dictionary -
    rev_flag_array = cobra_dictionary["rev"]

    # how many bounds do we have?
    (number_of_bounds, number_of_columns) = size(flux_bounds_array)

    # initialize and update the array -
    updated_flux_bounds_array = zeros(number_of_bounds,number_of_columns)
    for flux_index = 1:number_of_bounds

        # get vmax value -
        vmax_value = vmax_array[flux_index]

        # setup new bounds -
        updated_flux_bounds_array[flux_index,1] = 0.0
        updated_flux_bounds_array[flux_index,2] = vmax_value
        if (rev_flag_array[flux_index] == 1.0)
            updated_flux_bounds_array[flux_index,1] = -1*vmax_value
        end
    end

    # return -
    return updated_flux_bounds_array
end

function load_default_biophysical_dictionary(organism_id::Symbol)

    # load the organism_id mapping file -
    path_to_mapping_file = "$(path_to_package)/flux/config/Mapping.json"

    # TODO: Check do we have a mapping file?
    organism_id_map = JSON.parsefile(path_to_mapping_file)

    # TODO: Throw an error if we are missing this organism
    local_path_to_biophysical_dictionary = organism_id_map[String(organism_id)]["path_to_default_constants"]

    # TODO: check - does this path point to a file?
    global_path_to_biophysical_dictionary = "$(path_to_package)"*"$(local_path_to_biophysical_dictionary)"
    default_biophysical_dictionary = JSON.parsefile(global_path_to_biophysical_dictionary)

    # return -
    return default_biophysical_dictionary
end


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

@everywhere module TestDecisionVariable

using StochasticPrograms
using Test

function test_decision_no_bound(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y)
    end
    # First-stage
    x = sp[1,:x]
    @test StochasticPrograms.stage(x) == 1
    @test !JuMP.has_lower_bound(x)
    @test !JuMP.has_upper_bound(x)
    @test !JuMP.is_fixed(x)
    @test "x" == @inferred JuMP.name(x)
    @test x == decision_by_name(owner_model(x), 1, "x")
    # Scenario-dependent
    y = sp[2,:y]
    @test StochasticPrograms.stage(y) == 2
    @test_throws ErrorException JuMP.has_lower_bound(y)
    @test !JuMP.has_lower_bound(y, 1)
    @test !JuMP.has_lower_bound(y, 2)
    @test_throws ErrorException JuMP.has_upper_bound(y)
    @test !JuMP.has_upper_bound(y, 1)
    @test !JuMP.has_upper_bound(y, 2)
    @test_throws ErrorException JuMP.is_fixed(y)
    @test !JuMP.is_fixed(y, 1)
    @test !JuMP.is_fixed(y, 2)
    @test_throws ErrorException JuMP.is_integer(y)
    @test !JuMP.is_integer(y, 1)
    @test !JuMP.is_integer(y, 2)
    @test_throws ErrorException JuMP.is_binary(y)
    @test !JuMP.is_binary(y, 1)
    @test !JuMP.is_binary(y, 2)
    @test "y" == @inferred JuMP.name(y)
    @test y == decision_by_name(owner_model(y), 2, "y")
end

function test_decision_lower_bound(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x >= 0, Bin)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @uncertain a
        @variable(sp, z)
        @recourse(sp, y >= a, Bin)
    end
    # First-stage
    x = sp[1,:x]
    @test JuMP.has_lower_bound(x)
    @test 0.0 == @inferred JuMP.lower_bound(x)
    @test !JuMP.has_upper_bound(x)
    @test !JuMP.is_fixed(x)
    @test JuMP.is_binary(x)
    @test !JuMP.is_integer(x)
    JuMP.delete_lower_bound(x)
    @test !JuMP.has_lower_bound(x)
    # Scenario-dependent
    y = sp[2,:y]
    @test JuMP.has_lower_bound(y, 1)
    @test JuMP.has_lower_bound(y, 2)
    @test 1.0 == @inferred JuMP.lower_bound(y, 1)
    @test 2.0 == @inferred JuMP.lower_bound(y, 2)
    @test !JuMP.has_upper_bound(y, 1)
    @test !JuMP.has_upper_bound(y, 2)
    @test !JuMP.is_fixed(y, 1)
    @test !JuMP.is_fixed(y, 2)
    @test JuMP.is_binary(y, 1)
    @test JuMP.is_binary(y, 2)
    @test !JuMP.is_integer(y, 1)
    @test !JuMP.is_integer(y, 2)
    JuMP.delete_lower_bound(y, 1)
    @test !JuMP.has_lower_bound(y, 1)
    JuMP.delete_lower_bound(y, 2)
    @test !JuMP.has_lower_bound(y, 2)
end

function test_decision_upper_bound(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x <= 1.0, Int)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @uncertain a
        @variable(sp, z)
        @recourse(sp, y <= a, Int)
    end
    # First-stage
    x = sp[1,:x]
    @test !JuMP.has_lower_bound(x)
    @test JuMP.has_upper_bound(x)
    @test 1.0 == @inferred JuMP.upper_bound(x)
    @test !JuMP.is_fixed(x)
    @test !JuMP.is_binary(x)
    @test JuMP.is_integer(x)
    JuMP.delete_upper_bound(x)
    @test !JuMP.has_upper_bound(x)
    # Scenario-dependent
    y = sp[2,:y]
    @test !JuMP.has_lower_bound(y, 1)
    @test !JuMP.has_lower_bound(y, 2)
    @test JuMP.has_upper_bound(y, 1)
    @test JuMP.has_upper_bound(y, 2)
    @test 1.0 == @inferred JuMP.upper_bound(y, 1)
    @test 2.0 == @inferred JuMP.upper_bound(y, 2)
    @test !JuMP.is_fixed(y, 1)
    @test !JuMP.is_fixed(y, 2)
    @test !JuMP.is_binary(y, 1)
    @test !JuMP.is_binary(y, 2)
    @test JuMP.is_integer(y, 1)
    @test JuMP.is_integer(y, 2)
    JuMP.delete_upper_bound(y, 1)
    @test !JuMP.has_upper_bound(y, 1)
    JuMP.delete_upper_bound(y, 2)
    @test !JuMP.has_upper_bound(y, 2)
end

function test_decision_fix(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x == 1)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @uncertain a
        @variable(sp, z)
        @recourse(sp, y == a)
    end
    # First-stage
    x = sp[1,:x]
    @test !JuMP.has_lower_bound(x)
    @test !JuMP.has_upper_bound(x)
    @test JuMP.is_fixed(x)
    @test 1.0 == @inferred JuMP.value(x)
    JuMP.unfix(x)
    @test !JuMP.is_fixed(x)
    JuMP.fix(x, 2.0)
    @test !JuMP.has_lower_bound(x)
    @test !JuMP.has_upper_bound(x)
    @test JuMP.is_fixed(x)
    @test 2.0 == @inferred JuMP.value(x)
    # Scenario-dependent
    y = sp[2,:y]
    @test !JuMP.has_lower_bound(y, 1)
    @test !JuMP.has_lower_bound(y, 2)
    @test !JuMP.has_upper_bound(y, 1)
    @test !JuMP.has_upper_bound(y, 2)
    @test JuMP.is_fixed(y, 1)
    @test JuMP.is_fixed(y, 2)
    @test 1.0 == @inferred JuMP.value(y, 1)
    @test 2.0 == @inferred JuMP.value(y, 2)
    JuMP.unfix(y, 1)
    @test !JuMP.is_fixed(y, 1)
    @test JuMP.is_fixed(y, 2)
    JuMP.unfix(y, 2)
    @test !JuMP.is_fixed(y, 2)
    JuMP.fix(y, 1, 2.0)
    @test !JuMP.has_lower_bound(y, 1)
    @test !JuMP.has_upper_bound(y, 1)
    @test JuMP.is_fixed(y, 1)
    @test !JuMP.is_fixed(y, 2)
    @test 2.0 == @inferred JuMP.value(y, 1)
    JuMP.fix(y, 2, 1.0)
    @test !JuMP.has_lower_bound(y, 2)
    @test !JuMP.has_upper_bound(y, 2)
    @test JuMP.is_fixed(y, 2)
    @test 1.0 == @inferred JuMP.value(y, 2)
end

function test_decision_custom_index_sets(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x[0:1, 10:20, 1:1] >= 2)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @uncertain a
        @variable(sp, z)
        @recourse(sp, y[0:1, 10:20, 1:1] >= a)
    end
    # First-stage
    x = sp[1,:x]
    @test JuMP.has_lower_bound(x[0, 15, 1])
    @test 2 == @inferred JuMP.lower_bound(x[0, 15, 1])
    @test !JuMP.has_upper_bound(x[0, 15, 1])
    @first_stage sp = begin
        @decision(sp, x[i in -10:10, s in [:a,:b]] <= 5.5, Int)
        @variable(sp, w)
    end
    generate!(sp)
    x = sp[1,:x]
    @test 5.5 == @inferred JuMP.upper_bound(x[-4, :a])
    @test "x[-10,a]" == @inferred JuMP.name(x[-10,:a])
    @test x[-10,:a] == decision_by_name(sp, 1, "x[-10,a]")
    # Scenario-dependent
    y = sp[2,:y]
    @test JuMP.has_lower_bound(y[0, 15, 1], 1)
    @test JuMP.has_lower_bound(y[0, 15, 1], 2)
    @test 1 == @inferred JuMP.lower_bound(y[0, 15, 1], 1)
    @test 2 == @inferred JuMP.lower_bound(y[0, 15, 1], 2)
    @test !JuMP.has_upper_bound(y[0, 15, 1], 1)
    @test !JuMP.has_upper_bound(y[0, 15, 1], 2)
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y[i in -10:10, s in [:a,:b]] <= 5.5, Int)
    end
    y = sp[2,:y]
    @test 5.5 == @inferred JuMP.upper_bound(y[-4, :a], 1)
    @test 5.5 == @inferred JuMP.upper_bound(y[-4, :a], 2)
    @test "y[-10,a]" == @inferred JuMP.name(y[-10,:a])
    @test nothing === decision_by_name(sp, 1, "y[-10,a]")
    @test y[-10,:a] == decision_by_name(sp, 2, "y[-10,a]")
end

function test_variable_is_valid_delete(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y)
    end
    # First-stage
    x = sp[1,:x]
    @test JuMP.is_valid(sp, x)
    JuMP.delete(sp, x)
    @test !JuMP.is_valid(sp, x)
    @test_throws Exception JuMP.delete(sp, x)
    # Scenario-dependent
    y = sp[2,:y]
    @test_throws ErrorException JuMP.is_valid(sp, y)
    @test JuMP.is_valid(sp, y, 1)
    @test JuMP.is_valid(sp, y, 2)
    @test_throws ErrorException JuMP.delete(sp, y)
    JuMP.delete(sp, y, 1)
    @test !JuMP.is_valid(sp, y, 1)
    @test JuMP.is_valid(sp, y, 2)
    JuMP.delete(sp, y, 2)
    @test !JuMP.is_valid(sp, y, 2)
    @test_throws Exception JuMP.delete(sp, y, 1)
    @first_stage sp = begin
        @decision(sp, x[1:3] >= 1)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y[1:3] >= 1)
    end
    # First-stage
    x = sp[1,:x]
    @test all(is_valid.(sp, x))
    delete(sp, x)
    @test all((!is_valid).(sp, x))
    second_sp = StochasticProgram([Scenario()], Structure...)
    @test_throws Exception JuMP.delete(second_sp, x[1])
    @test_throws Exception JuMP.delete(second_sp, x)
    # Scenario-dependent
    y = sp[2,:y]
    @test_throws ErrorException is_valid.(sp, y)
    @test all(is_valid.(sp, y, 1))
    @test all(is_valid.(sp, y, 2))
    @test_throws ErrorException delete.(sp, y)
    delete(sp, y, 1)
    @test all((!is_valid).(sp, y, 1))
    @test all(is_valid.(sp, y, 2))
    delete(sp, y, 2)
    @test all((!is_valid).(sp, y, 2))
end

function test_variable_bounds_set_get(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, 0 <= x <= 2)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @uncertain a
        @variable(sp, z)
        @recourse(sp, 0 <= y <= a)
    end
    # First-stage
    x = sp[1,:x]
    @test 0 == @inferred JuMP.lower_bound(x)
    @test 2 == @inferred JuMP.upper_bound(x)
    set_lower_bound(x, 1.)
    @test 1. == @inferred JuMP.lower_bound(x)
    set_upper_bound(x, 3.)
    @test 3. == @inferred JuMP.upper_bound(x)
    # Scenario-dependent
    y = sp[2,:y]
    @test_throws ErrorException JuMP.lower_bound(y)
    @test 0 == @inferred JuMP.lower_bound(y, 1)
    @test 0 == @inferred JuMP.lower_bound(y, 2)
    @test_throws ErrorException JuMP.upper_bound(y)
    @test 1 == @inferred JuMP.upper_bound(y, 1)
    @test 2 == @inferred JuMP.upper_bound(y, 2)
    set_lower_bound(y, 1, 1.)
    @test 1. == @inferred JuMP.lower_bound(y, 1)
    set_lower_bound(y, 2, 1.)
    @test 1. == @inferred JuMP.lower_bound(y, 2)
    JuMP.set_lower_bound(y, 2.)
    @test 2. == @inferred JuMP.lower_bound(y, 1)
    @test 2. == @inferred JuMP.lower_bound(y, 2)
    set_upper_bound(y, 1, 3.)
    @test 3. == @inferred JuMP.upper_bound(y, 1)
    set_upper_bound(y, 2, 3.)
    @test 3. == @inferred JuMP.upper_bound(y, 2)
    JuMP.set_upper_bound(y, 2.)
    @test 2. == @inferred JuMP.upper_bound(y, 1)
    @test 2. == @inferred JuMP.upper_bound(y, 2)
end

function test_variable_starts_set_get(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x[1:3])
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y[1:3])
    end
    # First-stage
    x = sp[1,:x]
    x0 = collect(1:3)
    JuMP.set_start_value.(x, x0)
    @test JuMP.start_value.(x) == x0
    @test JuMP.start_value.([x[1],x[2],x[3]]) == x0
    # First-stage
    y = sp[2,:y]
    @test_throws ErrorException JuMP.set_start_value.(y, x0)
    JuMP.set_start_value.(y, 1, x0)
    JuMP.set_start_value.(y, 2, x0)
    @test_throws ErrorException JuMP.start_value.(y)
    @test JuMP.start_value.(y, 1) == x0
    @test JuMP.start_value.(y, 2) == x0
    @test_throws ErrorException JuMP.start_value.([y[1],y[2],y[3]])
    @test JuMP.start_value.([y[1],y[2],y[3]], 1) == x0
    @test JuMP.start_value.([y[1],y[2],y[3]], 2) == x0
end

function test_variable_integrality_set_get(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x[1:3])
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y[1:3])
    end
    # First-stage
    x = sp[1,:x]
    JuMP.set_integer(x[2])
    JuMP.set_integer(x[2])
    @test JuMP.is_integer(x[2])
    JuMP.unset_integer(x[2])
    @test !JuMP.is_integer(x[2])
    JuMP.set_binary(x[1])
    JuMP.set_binary(x[1])
    @test JuMP.is_binary(x[1])
    @test_throws Exception JuMP.set_integer(x[1])
    JuMP.unset_binary(x[1])
    @test !JuMP.is_binary(x[1])
    # Scenario-dependent
    y = sp[2,:y]
    @test_throws ErrorException JuMP.set_integer(y[2])
    JuMP.set_integer(y[2], 1)
    JuMP.set_integer(y[2], 1)
    JuMP.set_integer(y[3], 2)
    JuMP.set_integer(y[3], 2)
    @test_throws ErrorException JuMP.is_integer(y[2])
    @test JuMP.is_integer(y[2], 1)
    @test !JuMP.is_integer(y[2], 2)
    @test JuMP.is_integer(y[3], 2)
    @test !JuMP.is_integer(y[3], 1)
    @test_throws ErrorException JuMP.unset_integer(y[2])
    JuMP.unset_integer(y[2], 1)
    JuMP.unset_integer(y[3], 2)
    @test !JuMP.is_integer(y[2], 1)
    @test_throws ErrorException JuMP.set_binary(y[2])
    JuMP.set_binary(y[1], 1)
    JuMP.set_binary(y[1], 1)
    JuMP.set_binary(y[1], 2)
    JuMP.set_binary(y[1], 2)
    @test_throws ErrorException JuMP.is_binary(y[1])
    @test JuMP.is_binary(y[1], 1)
    @test JuMP.is_binary(y[1], 2)
    @test_throws Exception JuMP.set_integer(y[1], 1)
    @test_throws Exception JuMP.set_integer(y[1], 2)
    @test_throws ErrorException JuMP.unset_binary(y[1])
    JuMP.unset_binary(y[1], 1)
    @test !JuMP.is_binary(y[1], 1)
    JuMP.unset_binary(y[1], 2)
    @test !JuMP.is_binary(y[1], 2)
end

function test_variables_constrained_on_creation(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x[1:2] in SecondOrderCone())
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, r)
        @recourse(sp, y[1:2] in SecondOrderCone())
    end
    x = sp[1,:x]
    y = sp[2,:y]
    @test num_constraints(sp, 1, typeof(x), MOI.SecondOrderCone) == 1
    @test num_constraints(sp, 2, typeof(y), MOI.SecondOrderCone) == 1
    @test name(x[1]) ==  "x[1]"
    @test name(x[2]) ==  "x[2]"
    @test name(y[1]) == "y[1]"
    @test name(y[2]) == "y[2]"
    @first_stage sp = begin
        @decision(sp, x[1:2] in SecondOrderCone())
        @decision(sp, [1:2] in SecondOrderCone())
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, r)
        @recourse(sp, y[1:2] in SecondOrderCone())
        @recourse(sp, [1:2] in SecondOrderCone())
    end
    x = sp[1,:x]
    y = sp[2,:y]
    @test num_constraints(sp, 1, typeof(x), MOI.SecondOrderCone) == 2
    @test num_constraints(sp, 2, typeof(y), MOI.SecondOrderCone) == 2
    @first_stage sp = begin
        @decision(sp, x[1:2] in SecondOrderCone())
        @decision(sp, [1:2] in SecondOrderCone())
        @decision(sp, [1:3] in SecondOrderCone())
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, r)
        @recourse(sp, y[1:2] in SecondOrderCone())
        @recourse(sp, [1:2] in SecondOrderCone())
        @recourse(sp, [1:3] in SecondOrderCone())
    end
    x = sp[1,:x]
    y = sp[2,:y]
    @test num_constraints(sp, 1, typeof(x), MOI.SecondOrderCone) == 3
    @test num_constraints(sp, 2, typeof(y), MOI.SecondOrderCone) == 3
    @first_stage sp = begin
        @decision(sp, z in MOI.Semiinteger(1.0, 2.0))
        @variable(sp, w)
    end
    generate!(sp)
    z = sp[1,:z]
    @test num_constraints(sp, 1, typeof(z), MOI.Semiinteger{Float64}) == 1
    @first_stage sp = begin
        @variable(sp, w)
        @decision(sp, z in MOI.Semiinteger(1.0, 2.0))
        @decision(sp, set = MOI.Semiinteger(1.0, 2.0))
    end
    generate!(sp)
    z = sp[1,:z]
    @test num_constraints(sp, 1, typeof(z), MOI.Semiinteger{Float64}) == 2
end

function test_all_decision_variables(Structure)
    ξ₁ = @scenario a = 1 probability = 0.5
    ξ₂ = @scenario a = 2 probability = 0.5
    sp = StochasticProgram([ξ₁,ξ₂], Structure...)
    @first_stage sp = begin
        @decision(sp, x₁)
        @decision(sp, x₂)
        @variable(sp, w)
    end
    @second_stage sp = begin
        @variable(sp, z)
        @recourse(sp, y₁)
        @recourse(sp, y₂)
    end
    # First-stage
    x₁ = sp[1,:x₁]
    x₂ = sp[1,:x₂]
    @test [x₁, x₂] == @inferred all_decision_variables(sp, 1)
    # Scenario-dependent
    y₁ = sp[2,:y₁]
    y₂ = sp[2,:y₂]
    @test [y₁, y₂] == @inferred all_decision_variables(sp, 2)
end

function runtests()
    @testset "DecisionVariable" begin
        for structure in [(Deterministic(),),
                          (StageDecomposition(),),
                          (ScenarioDecomposition(),),
                          (Deterministic(), () -> MOIU.MockOptimizer(MOIU.Model{Float64}()))]
            name = length(structure) == 1 ? "$(structure[1])" : "$(structure[1]) with decision bridges"
            @testset "$name" begin
                for name in names(@__MODULE__; all = true)
                    if !startswith("$(name)", "test_")
                        continue
                    end
                    f = getfield(@__MODULE__, name)
                    @testset "$(name)" begin
                        f(structure)
                    end
                end
            end
        end
    end
end

function run_dtests()
    @testset "DecisionVariable" begin
        for structure in [(DistributedStageDecomposition(),), (DistributedScenarioDecomposition(),)]
            @testset "$(structure[1])" begin
                for name in names(@__MODULE__; all = true)
                    if !startswith("$(name)", "test_")
                        continue
                    end
                    f = getfield(@__MODULE__, name)
                    @testset "$(name)" begin
                        f(structure)
                    end
                end
            end
        end
    end
end

end


@testset "BondGraph Properties" begin
    bg = BondGraph("newBG")
    @test type(bg) == :BG
    @test name(bg) == :newBG
    @test isempty(vertices(bg))

    @test eltype(BondGraph) == AbstractNode
    @test eltype(bg) == AbstractNode

    @test isempty(edges(bg))
    @test edgetype(BondGraph) == Graphs.AbstractSimpleEdge{Integer}
    @test edgetype(bg) == Graphs.AbstractSimpleEdge{Integer}
    @test is_directed(bg)

    @test size(zero(BondGraph)) == size(BondGraph())
    @test size(zero(bg)) == size(BondGraph())
end

@testset "Adding and removing elements" begin
    c = Component(:C, :C1)
    r = Component(:R, :C1, numports = 1)
    j = EqualEffort()

    bg = BondGraph()

    @test add_vertex!(bg, c)
    b = add_edge!(bg, c, j)

    @test src(b) == vertex(c)
    @test dst(b) == vertex(j)

    add_vertex!(bg, r)
    add_vertex!(bg, j)

    @test ne(bg) == 1
    @test has_edge(bg, vertex(c), vertex(j))
    @test !has_edge(bg, vertex(c), vertex(r))

    @test nv(bg) == 3
    @test has_vertex(bg, j)
    @test has_vertex(bg, 3)
    @test !has_vertex(bg, 0)

    @test components(bg) == [c, r]
    @test junctions(bg) == [j]

    @test inneighbors(bg, vertex(c)) == []
    @test outneighbors(bg, vertex(c)) == [3]

    @test rem_edge!(bg, c, j) == b
    @test ne(bg) == 0
    @test rem_vertex!(bg, r)
    @test nv(bg) == 2
end

@testset "BondGraphNode" begin
    bg = BondGraph(:RCI)
    bgn = BondGraphNode(bg)

    @test bgn.type == :BG
    @test bgn.name == :RCI
    @test bgn.freeports == Bool[]
end

@testset "Printing" begin
    C = Component(:C)
    SS = Component(:SS, :Source)
    J0 = EqualEffort(name = :J)
    b1 = Bond(C, J0)
    b2 = Bond(J0, SS)
    port = b1.srcport
    bg = BondGraph(:newbg)
    bgn = BondGraphNode(bg)

    # repr returns the output of the 'show' function
    @test repr(C) == "C:C"
    @test repr(SS) == "SS:Source"
    @test repr(b1) == "Bond C:C ⇀ J"
    @test repr(b2) == "Bond J ⇀ SS:Source"
    @test repr(port) == "Port C:C (1)"
    @test repr(bg) == "BondGraph BG:newbg (0 Nodes, 0 Bonds)"
    @test repr(bgn) == "BG:newbg"

    add_vertex!(bg, C)
    add_vertex!(bg, SS)
    add_vertex!(bg, J0)
    add_edge!(bg, C, J0)
    add_edge!(bg, J0, SS)
    @test repr(bg) == "BondGraph BG:newbg (3 Nodes, 2 Bonds)"
end

@testset "Graphs.jl Extra Functions" begin
    c1 = Component(:C)
    c2 = Component(:R)
    c3 = Component(:I)
    j = EqualFlow()

    bg = BondGraph()

    add_vertex!(bg, c1)
    add_vertex!(bg, c2)
    add_vertex!(bg, c3)
    add_vertex!(bg, j)

    add_edge!(bg, c1, j)
    add_edge!(bg, j, c2)
    add_edge!(bg, j, c3)

    # Testing on a selection of common graph functions
    @test Δ(bg) == 3
    @test Graphs.density(bg) == 0.25
    @test Array(adjacency_matrix(bg)) == [0 0 0 1; 0 0 0 0; 0 0 0 0; 0 1 1 0]
end


##
## Data Structures
##

# Each separator is associated with a tree like
#
#       1
#       |
#       1
#     / | \
#    1  2  3
#   / \ |  |
#   1 2 3  4
#
# The ids are increasing left-right

# A node a the tree
mutable struct NodeIT
    i::Int64                        # Id
    c::Array{NodeIT,1}              # Childrens
    p::Union{Nothing, NodeIT}       # Parent
    function NodeIT(i::Int64)
        this = new()
        this.i = i
        this.c = NodeIT[]
        this.p = nothing
        this
    end
end
Base.show(io::IO, n::NodeIT) = print(io,n.i)

# The tree
mutable struct IT
    nit::Array{Array{NodeIT,1},1}
    function IT(i::Int64)
        this = new()
        this.nit = [ [ NodeIT(i) ] ]
        this
    end

    function IT()
        this = new()
        this.nit = [ [] ]
        this
    end
end

IP = NamedTuple{(:p, :l, :r),Tuple{Int64,Int64,Int64}} # Parent/Left/Right

function lt_ip(x::IP, y::IP)
    return      (x.p <  y.p) ||
              ( (x.p == y.p) && (x.l <  y.l) ) ||
              ( (x.p == y.p) && (x.l == y.l) && (x.r <  y.r) )
end

ord = Base.ord(isless,identity,false,Base.Forward)

const IP0 = (p=1,l=1,r=1)

# Partition the graph represented by the sparse matrix A
# from level 1 to maxLevel
#
# This performs a 'modified' nested dissection ordering, i.e., a ND ordering but with 'matching' partitions.
#
# By convention, level=1 are the leaf separators and level=maxLevel is the root separator.
#
# Each ND separator is further divided in a hierarchy of clusters
# Separator at level 1 <= l <= maxLevel consists of a l-levels hierarchy of nested clusters.
# Hierarchy is encoded into a integer-tree representing the nested hierarchy
#
# For instance, a given separator's (lvl=3, sep=?) dofs (degrees of freedom, i.e., original unknowns) are numbered
#       subseps[dofs[lvl][sep]] = [1 1 2 3 3 4 5 5 5] (in practice, they are not necessary sorted)
# with a tree stored in
#       hrch[lvl][sep] =        1
#                             /   \
#                            1     2
#                          / | \   | \
#                         1  2  3  4  5
# indicating a 3-levels hierarchy and the way the merging should be performed
# In particular, the hierarchy is
#                       [1 1 2 3 3 4 5 5 5] 5 partitions
#                       [1 1 1 1 1 2 2 2 2] 2 partitions
#                       [1 1 1 1 1 1 1 1 1] 1 partition - root
#
# Outputs
#       * subseps       subseps[i] = 1 ... #partitions, indicates which cluster within its ND separator node i belongs to
#       * hrch          hrch[l][s] a l-levels hierarchy for separator (l,s)
#       * dofs          dofs[l][s] the dofs of separator (l,s)
function mnd(A::SparseMatrixCSC{Tv,Ti}, maxLevel ; verbose::Bool=false) where {Tv, Ti}

    @assert maxLevel >= 1
    if verbose
        println("Algebraic partitionning of graph with $(size(A,1)) vertices, $maxLevel levels")
    end

    n = size(A,1)
    parts = Array{IP,1}(undef, n) # (parent, left, right) for each dof
    fill!(parts,IP0)

    # Prepare stuff to separate
    tosep  = [ collect(Cint, 1:n) ]

    # Temp arrays for Metis
    colptr = Vector{Cint}(undef, n+1)
    rowval = Vector{Cint}(undef, nnz(A))
    pids   = Vector{Cint}(undef, n)

    # Output structures
    dofs    = Vector{Vector{Vector{Int64}}}(undef, maxLevel)
    hrch    = Vector{Vector{IT}}(undef, maxLevel)

    # Do the work
    for lvl = 1:maxLevel

        dofs[lvl] = Vector{Vector{Int64}}(undef, 2^(lvl-1))
        hrch[lvl] = Vector{IT}(undef, 2^(lvl-1))
        for sep = 1:2^(lvl-1)
            hrch[lvl][sep] = IT(1)
        end
        tosep_next = Vector{Vector{Int64}}(undef, 2^lvl)

        if verbose println("Level $lvl, partitioning") end

        for (sep, list) in enumerate(tosep)

            # What is our separator -unique- id
            id = 2^(lvl-1) + (sep - 1)

            # Do the ordering, i.e., cut A[list,list]
            if lvl == maxLevel # Leaf level
                pids[1:length(list)] .= 2
            elseif length(list) <= 1 # Really isn't anything to separate
                pids[1:length(list)] .= 2
            else
                fill_nodiag_list_sorted!(A, colptr, rowval, list)
                vertex_sep_bissect_fast!(length(list), colptr, rowval, pids)
            end

            # Update parts, so that each node has a proper (left, right) pair
            for (i, side) in zip(list, pids)
                p = parts[i]
                if side == 2 && p.r == id && p.l == id # Separator, brand new
                    p = (p=1, l=2*id, r=2*id+1)
                elseif side == 0 # In the left interior
                    if p.l == id && p.r == id # Interior, brand new
                        p = (p=1, l=2*id, r=2*id)
                    elseif p.l == id # Upper level separator, on its left
                        p = (p=p.p, l=2*id, r=p.r)
                    elseif p.r == id # Upper level separator, on its right
                        p = (p=p.p, l=p.l, r=2*id)
                    end
                elseif side == 1 # In the right interior
                    if p.l == id && p.r == id # Interior, brand new
                        p = (p=1, l=2*id+1, r=2*id+1)
                    elseif p.l == id # Upper level separator, on its left
                        p = (p=p.p, l=2*id+1, r=p.r)
                    elseif p.r == id # Upper level separator, on its right
                        p = (p=p.p, l=p.l, r=2*id+1)
                    end
                end
                parts[i] = p
            end

            # Find new sep & create initial hierarchy
            mid = findall(i -> parts[i] == (p=1, l=2*id, r=2*id+1), list)
            dofs[lvl][sep] = list[mid]

            # Define new interiors + boundaries
            left  = findall(i -> parts[i].l == 2*id  , list)
            right = findall(i -> parts[i].r == 2*id+1, list)
            tosep_next[2*sep-1] = list[left]
            tosep_next[2*sep  ] = list[right]
        end

        # Go through edges and update hierarchy & parents
        if lvl < maxLevel
            for lvl2 = 1:lvl
                for sep2 = 1:2^(lvl2-1)
                    vals  = parts[dofs[lvl2][sep2]]
                    lr_srt = sortperm(vals, lt=lt_ip) # Sort by parent first - arbitrary after
                    vals_srt = vals[lr_srt]
                    dofs_srt = dofs[lvl2][sep2][lr_srt]
                    u_ptr = sorted_to_ptr(vals_srt)
                    # Create hierarchy
                    it = hrch[lvl2][sep2]
                    push!(it.nit, NodeIT[]) # Create new level
                    if length(dofs_srt) == 0
                        @assert length(it.nit[end-1]) == 1
                        x = NodeIT(1)
                        x.p = it.nit[end-1][1]
                        push!(it.nit[end-1][1].c,x)
                        push!(it.nit[end], x)
                    else
                        for p = 1:length(u_ptr)-1
                            dofs_p = dofs_srt[u_ptr[p]:u_ptr[p+1]-1] # The dofs - this is non-empty by definition of unique
                            old_p  = parts[dofs_p[1]].p # All same since we sorted by parent
                            left   = parts[dofs_p[1]].l # ..
                            right  = parts[dofs_p[1]].r # ..
                            # Update partition
                            for i in dofs_p
                                parts[i] = (p=p, l=left, r=right)
                            end
                            # Create node & update hierarchy
                            x     = NodeIT(p)
                            x.p   = it.nit[end-1][old_p]
                            push!(it.nit[end-1][old_p].c, x)
                            push!(it.nit[end], x)
                        end
                    end
                end
            end
        end

        # Next clusters to consider
        tosep = tosep_next
    end

    subseps = map(x -> x.p, parts)
    reverse!(hrch)
    reverse!(dofs)

    return subseps, hrch, dofs

end


import brainflow

brainflow.enable_dev_board_logger()

params = brainflow.BrainFlowInputParams()
board_shim = brainflow.BoardShim(Integer(brainflow.SYNTHETIC_BOARD), params)

brainflow.prepare_session(board_shim)
brainflow.start_stream(board_shim)
sleep(5)
brainflow.stop_stream(board_shim)
data = brainflow.get_current_board_data(32, board_shim)
brainflow.release_session(board_shim)

eeg_channels = brainflow.get_eeg_channels(Integer(brainflow.SYNTHETIC_BOARD))
sampling_rate = brainflow.get_sampling_rate(Integer(brainflow.SYNTHETIC_BOARD))

data_first_channel = data[eeg_channels[1], :]
println("Original Data First Channel")
println(data_first_channel)
downsampled_data = brainflow.perform_downsampling(data_first_channel, 3, Integer(brainflow.EACH))
println("After Downsampling")
println(downsampled_data)


# This file is a part of Julia. License is MIT: https://julialang.org/license

"""
Gives a reinterpreted view (of element type T) of the underlying array (of element type S).
If the size of `T` differs from the size of `S`, the array will be compressed/expanded in
the first dimension. The variant `reinterpret(reshape, T, a)` instead adds or consumes the first dimension
depending on the ratio of element sizes.
"""
struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T, N}
    parent::A
    readable::Bool
    writable::Bool

    function throwbits(S::Type, T::Type, U::Type)
        @_noinline_meta
        throw(ArgumentError("cannot reinterpret `$(S)` as `$(T)`, type `$(U)` is not a bits type"))
    end
    function throwsize0(S::Type, T::Type, msg)
        @_noinline_meta
        throw(ArgumentError("cannot reinterpret a zero-dimensional `$(S)` array to `$(T)` which is of a $msg size"))
    end

    global reinterpret
    function reinterpret(::Type{T}, a::A) where {T,N,S,A<:AbstractArray{S, N}}
        function thrownonint(S::Type, T::Type, dim)
            @_noinline_meta
            throw(ArgumentError("""
                cannot reinterpret an `$(S)` array to `$(T)` whose first dimension has size `$(dim)`.
                The resulting array would have non-integral first dimension.
                """))
        end
        function throwaxes1(S::Type, T::Type, ax1)
            @_noinline_meta
            throw(ArgumentError("cannot reinterpret a `$(S)` array to `$(T)` when the first axis is $ax1. Try reshaping first."))
        end
        isbitstype(T) || throwbits(S, T, T)
        isbitstype(S) || throwbits(S, T, S)
        (N != 0 || sizeof(T) == sizeof(S)) || throwsize0(S, T, "different")
        if N != 0 && sizeof(S) != sizeof(T)
            ax1 = axes(a)[1]
            dim = length(ax1)
            rem(dim*sizeof(S),sizeof(T)) == 0 || thrownonint(S, T, dim)
            first(ax1) == 1 || throwaxes1(S, T, ax1)
        end
        readable = array_subpadding(T, S)
        writable = array_subpadding(S, T)
        new{T, N, S, A, false}(a, readable, writable)
    end
    reinterpret(::Type{T}, a::AbstractArray{T}) where {T} = a

    # With reshaping
    function reinterpret(::typeof(reshape), ::Type{T}, a::A) where {T,S,A<:AbstractArray{S}}
        function throwintmult(S::Type, T::Type)
            @_noinline_meta
            throw(ArgumentError("`reinterpret(reshape, T, a)` requires that one of `sizeof(T)` (got $(sizeof(T))) and `sizeof(eltype(a))` (got $(sizeof(S))) be an integer multiple of the other"))
        end
        function throwsize1(a::AbstractArray, T::Type)
            @_noinline_meta
            throw(ArgumentError("`reinterpret(reshape, $T, a)` where `eltype(a)` is $(eltype(a)) requires that `axes(a, 1)` (got $(axes(a, 1))) be equal to 1:$(sizeof(T) ÷ sizeof(eltype(a))) (from the ratio of element sizes)"))
        end
        isbitstype(T) || throwbits(S, T, T)
        isbitstype(S) || throwbits(S, T, S)
        if sizeof(S) == sizeof(T)
            N = ndims(a)
        elseif sizeof(S) > sizeof(T)
            rem(sizeof(S), sizeof(T)) == 0 || throwintmult(S, T)
            N = ndims(a) + 1
        else
            rem(sizeof(T), sizeof(S)) == 0 || throwintmult(S, T)
            N = ndims(a) - 1
            N > -1 || throwsize0(S, T, "larger")
            axes(a, 1) == Base.OneTo(sizeof(T) ÷ sizeof(S)) || throwsize1(a, T)
        end
        readable = array_subpadding(T, S)
        writable = array_subpadding(S, T)
        new{T, N, S, A, true}(a, readable, writable)
    end
    reinterpret(::typeof(reshape), ::Type{T}, a::AbstractArray{T}) where {T} = a
end

ReshapedReinterpretArray{T,N,S,A<:AbstractArray{S}} = ReinterpretArray{T,N,S,A,true}
NonReshapedReinterpretArray{T,N,S,A<:AbstractArray{S, N}} = ReinterpretArray{T,N,S,A,false}

"""
    reinterpret(reshape, T, A::AbstractArray{S}) -> B

Change the type-interpretation of `A` while consuming or adding a "channel dimension."

If `sizeof(T) = n*sizeof(S)` for `n>1`, `A`'s first dimension must be
of size `n` and `B` lacks `A`'s first dimension. Conversely, if `sizeof(S) = n*sizeof(T)` for `n>1`,
`B` gets a new first dimension of size `n`. The dimensionality is unchanged if `sizeof(T) == sizeof(S)`.

# Examples

```jldoctest
julia> A = [1 2; 3 4]
2×2 Matrix{$Int}:
 1  2
 3  4

julia> reinterpret(reshape, Complex{Int}, A)    # the result is a vector
2-element reinterpret(reshape, Complex{$Int}, ::Matrix{$Int}) with eltype Complex{$Int}:
 1 + 3im
 2 + 4im

julia> a = [(1,2,3), (4,5,6)]
2-element Vector{Tuple{$Int, $Int, $Int}}:
 (1, 2, 3)
 (4, 5, 6)

julia> reinterpret(reshape, Int, a)             # the result is a matrix
3×2 reinterpret(reshape, $Int, ::Vector{Tuple{$Int, $Int, $Int}}) with eltype $Int:
 1  4
 2  5
 3  6
```
"""
reinterpret(::typeof(reshape), T::Type, a::AbstractArray)

reinterpret(::Type{T}, a::NonReshapedReinterpretArray) where {T} = reinterpret(T, a.parent)
reinterpret(::typeof(reshape), ::Type{T}, a::ReshapedReinterpretArray) where {T} = reinterpret(reshape, T, a.parent)

# Definition of StridedArray
StridedFastContiguousSubArray{T,N,A<:DenseArray} = FastContiguousSubArray{T,N,A}
StridedReinterpretArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray},IsReshaped} = ReinterpretArray{T,N,S,A,IsReshaped} where S
StridedReshapedArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray,StridedReinterpretArray}} = ReshapedArray{T,N,A}
StridedSubArray{T,N,A<:Union{DenseArray,StridedReshapedArray,StridedReinterpretArray},
    I<:Tuple{Vararg{Union{RangeIndex, ReshapedUnitRange, AbstractCartesianIndex}}}} = SubArray{T,N,A,I}
StridedArray{T,N} = Union{DenseArray{T,N}, StridedSubArray{T,N}, StridedReshapedArray{T,N}, StridedReinterpretArray{T,N}}
StridedVector{T} = StridedArray{T,1}
StridedMatrix{T} = StridedArray{T,2}
StridedVecOrMat{T} = Union{StridedVector{T}, StridedMatrix{T}}

# the definition of strides for Array{T,N} is tuple() if N = 0, otherwise it is
# a tuple containing 1 and a cumulative product of the first N-1 sizes
# this definition is also used for StridedReshapedArray and StridedReinterpretedArray
# which have the same memory storage as Array
stride(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, i::Int) = _stride(a, i)

function stride(a::ReinterpretArray, i::Int)
    a.parent isa StridedArray || ArgumentError("Parent must be strided.") |> throw
    return _stride(a, i)
end

function _stride(a, i)
    if i > ndims(a)
        return length(a)
    end
    s = 1
    for n = 1:(i-1)
        s *= size(a, n)
    end
    return s
end

function strides(a::ReinterpretArray)
    a.parent isa StridedArray || ArgumentError("Parent must be strided.") |> throw
    size_to_strides(1, size(a)...)
end
strides(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}) = size_to_strides(1, size(a)...)

function check_readable(a::ReinterpretArray{T, N, S} where N) where {T,S}
    # See comment in check_writable
    if !a.readable && !array_subpadding(T, S)
        throw(PaddingError(T, S))
    end
end

function check_writable(a::ReinterpretArray{T, N, S} where N) where {T,S}
    # `array_subpadding` is relatively expensive (compared to a simple arrayref),
    # so it is cached in the array. However, it is computable at compile time if,
    # inference has the types available. By using this form of the check, we can
    # get the best of both worlds for the success case. If the types were not
    # available to inference, we simply need to check the field (relatively cheap)
    # and if they were we should be able to fold this check away entirely.
    if !a.writable && !array_subpadding(S, T)
        throw(PaddingError(T, S))
    end
end

## IndexStyle specializations

# For `reinterpret(reshape, T, a)` where we're adding a channel dimension and with
# `IndexStyle(a) == IndexLinear()`, it's advantageous to retain pseudo-linear indexing.
struct IndexSCartesian2{K} <: IndexStyle end   # K = sizeof(S) ÷ sizeof(T), a static-sized 2d cartesian iterator

IndexStyle(::Type{ReinterpretArray{T,N,S,A,false}}) where {T,N,S,A<:AbstractArray{S,N}} = IndexStyle(A)
function IndexStyle(::Type{ReinterpretArray{T,N,S,A,true}}) where {T,N,S,A<:AbstractArray{S}}
    if sizeof(T) < sizeof(S)
        IndexStyle(A) === IndexLinear() && return IndexSCartesian2{sizeof(S) ÷ sizeof(T)}()
        return IndexCartesian()
    end
    return IndexStyle(A)
end
IndexStyle(::IndexSCartesian2{K}, ::IndexSCartesian2{K}) where {K} = IndexSCartesian2{K}()

struct SCartesianIndex2{K}   # can't make <:AbstractCartesianIndex without N, and 2 would be a bit misleading
    i::Int
    j::Int
end
to_index(i::SCartesianIndex2) = i

struct SCartesianIndices2{K,R<:AbstractUnitRange{Int}} <: AbstractMatrix{SCartesianIndex2{K}}
    indices2::R
end
SCartesianIndices2{K}(indices2::AbstractUnitRange{Int}) where {K} = (@assert K::Int > 1; SCartesianIndices2{K,typeof(indices2)}(indices2))

eachindex(::IndexSCartesian2{K}, A::AbstractArray) where {K} = SCartesianIndices2{K}(eachindex(IndexLinear(), parent(A)))

size(iter::SCartesianIndices2{K}) where K = (K, length(iter.indices2))
axes(iter::SCartesianIndices2{K}) where K = (Base.OneTo(K), iter.indices2)

first(iter::SCartesianIndices2{K}) where {K} = SCartesianIndex2{K}(1, first(iter.indices2))
last(iter::SCartesianIndices2{K}) where {K}  = SCartesianIndex2{K}(K, last(iter.indices2))

@inline function getindex(iter::SCartesianIndices2{K}, i::Int, j::Int) where {K}
    @boundscheck checkbounds(iter, i, j)
    return SCartesianIndex2{K}(i, iter.indices2[j])
end

function iterate(iter::SCartesianIndices2{K}) where {K}
    ret = iterate(iter.indices2)
    ret === nothing && return nothing
    item2, state2 = ret
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
end

function iterate(iter::SCartesianIndices2{K}, (state1, item2, state2)) where {K}
    if state1 < K
        item1 = state1 + 1
        return SCartesianIndex2{K}(item1, item2), (item1, item2, state2)
    end
    ret = iterate(iter.indices2, state2)
    ret === nothing && return nothing
    item2, state2 = ret
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
end

SimdLoop.simd_outer_range(iter::SCartesianIndices2) = iter.indices2
SimdLoop.simd_inner_length(::SCartesianIndices2{K}, ::Any) where K = K
@inline function SimdLoop.simd_index(::SCartesianIndices2{K}, Ilast::Int, I1::Int) where {K}
    SCartesianIndex2{K}(I1+1, Ilast)
end

_maybe_reshape(::IndexSCartesian2, A::ReshapedReinterpretArray, I...) = A

# fallbacks
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, I::Vararg{Int, N}) where {T,N}
    @_propagate_inbounds_meta
    getindex(A, I...)
end
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, I::Vararg{Int, N}) where {T,N}
    @_propagate_inbounds_meta
    setindex!(A, v, I...)
end
# fallbacks for array types that use "pass-through" indexing (e.g., `IndexStyle(A) = IndexStyle(parent(A))`)
# but which don't handle SCartesianIndex2
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, ind::SCartesianIndex2) where {T,N}
    @_propagate_inbounds_meta
    I = _to_subscript_indices(A, ind.i, ind.j)
    getindex(A, I...)
end
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, ind::SCartesianIndex2) where {T,N}
    @_propagate_inbounds_meta
    I = _to_subscript_indices(A, ind.i, ind.j)
    setindex!(A, v, I...)
end

## AbstractArray interface

parent(a::ReinterpretArray) = a.parent
dataids(a::ReinterpretArray) = dataids(a.parent)
unaliascopy(a::ReinterpretArray{T}) where {T} = reinterpret(T, unaliascopy(a.parent))

function size(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
    psize = size(a.parent)
    size1 = div(psize[1]*sizeof(S), sizeof(T))
    tuple(size1, tail(psize)...)
end
function size(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
    psize = size(a.parent)
    sizeof(S) > sizeof(T) && return (div(sizeof(S), sizeof(T)), psize...)
    sizeof(S) < sizeof(T) && return Base.tail(psize)
    return psize
end
size(a::NonReshapedReinterpretArray{T,0}) where {T} = ()

function axes(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
    paxs = axes(a.parent)
    f, l = first(paxs[1]), length(paxs[1])
    size1 = div(l*sizeof(S), sizeof(T))
    tuple(oftype(paxs[1], f:f+size1-1), tail(paxs)...)
end
function axes(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
    paxs = axes(a.parent)
    sizeof(S) > sizeof(T) && return (Base.OneTo(div(sizeof(S), sizeof(T))), paxs...)
    sizeof(S) < sizeof(T) && return Base.tail(paxs)
    return paxs
end
axes(a::NonReshapedReinterpretArray{T,0}) where {T} = ()

elsize(::Type{<:ReinterpretArray{T}}) where {T} = sizeof(T)
unsafe_convert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = Ptr{T}(unsafe_convert(Ptr{S},a.parent))

@inline @propagate_inbounds getindex(a::NonReshapedReinterpretArray{T,0}) where {T} = reinterpret(T, a.parent[])
@inline @propagate_inbounds getindex(a::ReinterpretArray) = a[1]

@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S}
    check_readable(a)
    _getindex_ra(a, inds[1], tail(inds))
end

@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, i::Int) where {T,N,S}
    check_readable(a)
    if isa(IndexStyle(a), IndexLinear)
        return _getindex_ra(a, i, ())
    end
    # Convert to full indices here, to avoid needing multiple conversions in
    # the loop in _getindex_ra
    inds = _to_subscript_indices(a, i)
    isempty(inds) ? _getindex_ra(a, 1, ()) : _getindex_ra(a, inds[1], tail(inds))
end

@inline @propagate_inbounds function getindex(a::ReshapedReinterpretArray{T,N,S}, ind::SCartesianIndex2) where {T,N,S}
    check_readable(a)
    n = sizeof(S) ÷ sizeof(T)
    t = Ref{NTuple{n,T}}()
    s = Ref{S}(a.parent[ind.j])
    GC.@preserve t s begin
        tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
        sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
        _memcpy!(tptr, sptr, sizeof(S))
    end
    return t[][ind.i]
end

@inline _memcpy!(dst, src, n) = ccall(:memcpy, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t), dst, src, n)

@inline @propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
    # Make sure to match the scalar reinterpret if that is applicable
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
        return reinterpret(T, a.parent[i1, tailinds...])
    else
        @boundscheck checkbounds(a, i1, tailinds...)
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
        t = Ref{T}()
        s = Ref{S}()
        GC.@preserve t s begin
            tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
            sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
            # Optimizations that avoid branches
            if sizeof(T) % sizeof(S) == 0
                # T is bigger than S and contains an integer number of them
                n = sizeof(T) ÷ sizeof(S)
                for i = 1:n
                    s[] = a.parent[ind_start + i, tailinds...]
                    _memcpy!(tptr + (i-1)*sizeof(S), sptr, sizeof(S))
                end
            elseif sizeof(S) % sizeof(T) == 0
                # S is bigger than T and contains an integer number of them
                s[] = a.parent[ind_start + 1, tailinds...]
                _memcpy!(tptr, sptr + sidx, sizeof(T))
            else
                i = 1
                nbytes_copied = 0
                # This is a bit complicated to deal with partial elements
                # at both the start and the end. LLVM will fold as appropriate,
                # once it knows the data layout
                while nbytes_copied < sizeof(T)
                    s[] = a.parent[ind_start + i, tailinds...]
                    nb = min(sizeof(S) - sidx, sizeof(T)-nbytes_copied)
                    _memcpy!(tptr + nbytes_copied, sptr + sidx, nb)
                    nbytes_copied += nb
                    sidx = 0
                    i += 1
                end
            end
        end
        return t[]
    end
end

@inline @propagate_inbounds function _getindex_ra(a::ReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
    # Make sure to match the scalar reinterpret if that is applicable
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
        return reinterpret(T, a.parent[i1, tailinds...])
    end
    @boundscheck checkbounds(a, i1, tailinds...)
    if sizeof(T) >= sizeof(S)
        t = Ref{T}()
        s = Ref{S}()
        GC.@preserve t s begin
            tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
            sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
            if sizeof(T) > sizeof(S)
                # Extra dimension in the parent array
                n = sizeof(T) ÷ sizeof(S)
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
                    offset = n * (i1 - firstindex(a))
                    for i = 1:n
                        s[] = a.parent[i + offset]
                        _memcpy!(tptr + (i-1)*sizeof(S), sptr, sizeof(S))
                    end
                else
                    for i = 1:n
                        s[] = a.parent[i, i1, tailinds...]
                        _memcpy!(tptr + (i-1)*sizeof(S), sptr, sizeof(S))
                    end
                end
            else
                # No extra dimension
                s[] = a.parent[i1, tailinds...]
                _memcpy!(tptr, sptr, sizeof(S))
            end
        end
        return t[]
    end
    # S is bigger than T and contains an integer number of them
    n = sizeof(S) ÷ sizeof(T)
    t = Ref{NTuple{n,T}}()
    s = Ref{S}()
    GC.@preserve t s begin
        tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
        sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
        s[] = a.parent[tailinds...]
        _memcpy!(tptr, sptr, sizeof(S))
    end
    return t[][i1]
end

@inline @propagate_inbounds setindex!(a::NonReshapedReinterpretArray{T,0,S} where T, v) where {S} = (a.parent[] = reinterpret(S, v))
@inline @propagate_inbounds setindex!(a::ReinterpretArray, v) = (a[1] = v)

@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, inds::Vararg{Int, N}) where {T,N,S}
    check_writable(a)
    _setindex_ra!(a, v, inds[1], tail(inds))
end

@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, i::Int) where {T,N,S}
    check_writable(a)
    if isa(IndexStyle(a), IndexLinear)
        return _setindex_ra!(a, v, i, ())
    end
    inds = _to_subscript_indices(a, i)
    _setindex_ra!(a, v, inds[1], tail(inds))
end

@inline @propagate_inbounds function setindex!(a::ReshapedReinterpretArray{T,N,S}, v, ind::SCartesianIndex2) where {T,N,S}
    check_writable(a)
    v = convert(T, v)::T
    t = Ref{T}(v)
    s = Ref{S}(a.parent[ind.j])
    GC.@preserve t s begin
        tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
        sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
        _memcpy!(sptr + (ind.i-1)*sizeof(T), tptr, sizeof(T))
    end
    a.parent[ind.j] = s[]
    return a
end

@inline @propagate_inbounds function _setindex_ra!(a::NonReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
    v = convert(T, v)::T
    # Make sure to match the scalar reinterpret if that is applicable
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
        return setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
    else
        @boundscheck checkbounds(a, i1, tailinds...)
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
        t = Ref{T}(v)
        s = Ref{S}()
        GC.@preserve t s begin
            tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
            sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
            # Optimizations that avoid branches
            if sizeof(T) % sizeof(S) == 0
                # T is bigger than S and contains an integer number of them
                n = sizeof(T) ÷ sizeof(S)
                for i = 0:n-1
                    _memcpy!(sptr, tptr + i*sizeof(S), sizeof(S))
                    a.parent[ind_start + i + 1, tailinds...] = s[]
                end
            elseif sizeof(S) % sizeof(T) == 0
                # S is bigger than T and contains an integer number of them
                s[] = a.parent[ind_start + 1, tailinds...]
                _memcpy!(sptr + sidx, tptr, sizeof(T))
                a.parent[ind_start + 1, tailinds...] = s[]
            else
                nbytes_copied = 0
                i = 1
                # Deal with any partial elements at the start. We'll have to copy in the
                # element from the original array and overwrite the relevant parts
                if sidx != 0
                    s[] = a.parent[ind_start + i, tailinds...]
                    nb = min((sizeof(S) - sidx) % UInt, sizeof(T) % UInt)
                    _memcpy!(sptr + sidx, tptr, nb)
                    nbytes_copied += nb
                    a.parent[ind_start + i, tailinds...] = s[]
                    i += 1
                    sidx = 0
                end
                # Deal with the main body of elements
                while nbytes_copied < sizeof(T) && (sizeof(T) - nbytes_copied) > sizeof(S)
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
                    _memcpy!(sptr, tptr + nbytes_copied, nb)
                    nbytes_copied += nb
                    a.parent[ind_start + i, tailinds...] = s[]
                    i += 1
                end
                # Deal with trailing partial elements
                if nbytes_copied < sizeof(T)
                    s[] = a.parent[ind_start + i, tailinds...]
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
                    _memcpy!(sptr, tptr + nbytes_copied, nb)
                    a.parent[ind_start + i, tailinds...] = s[]
                end
            end
        end
    end
    return a
end

@inline @propagate_inbounds function _setindex_ra!(a::ReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
    v = convert(T, v)::T
    # Make sure to match the scalar reinterpret if that is applicable
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
        return setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
    end
    @boundscheck checkbounds(a, i1, tailinds...)
    t = Ref{T}(v)
    s = Ref{S}()
    GC.@preserve t s begin
        tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
        sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
        if sizeof(T) >= sizeof(S)
            if sizeof(T) > sizeof(S)
                # Extra dimension in the parent array
                n = sizeof(T) ÷ sizeof(S)
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
                    offset = n * (i1 - firstindex(a))
                    for i = 1:n
                        _memcpy!(sptr, tptr + (i-1)*sizeof(S), sizeof(S))
                        a.parent[i + offset] = s[]
                    end
                else
                    for i = 1:n
                        _memcpy!(sptr, tptr + (i-1)*sizeof(S), sizeof(S))
                        a.parent[i, i1, tailinds...] = s[]
                    end
                end
            else
                # No extra dimension
                _memcpy!(sptr, tptr, sizeof(S))
                a.parent[i1, tailinds...] = s[]
            end
        else
            # S is bigger than T and contains an integer number of them
            s[] = a.parent[tailinds...]
            _memcpy!(sptr + (i1-1)*sizeof(T), tptr, sizeof(T))
            a.parent[tailinds...] = s[]
        end
    end
    return a
end

# Padding
struct Padding
    offset::Int
    size::Int
end
function intersect(p1::Padding, p2::Padding)
    start = max(p1.offset, p2.offset)
    stop = min(p1.offset + p1.size, p2.offset + p2.size)
    Padding(start, max(0, stop-start))
end

struct PaddingError
    S::Type
    T::Type
end

function showerror(io::IO, p::PaddingError)
    print(io, "Padding of type $(p.S) is not compatible with type $(p.T).")
end

"""
    CyclePadding(padding, total_size)

Cylces an iterator of `Padding` structs, restarting the padding at `total_size`.
E.g. if `padding` is all the padding in a struct and `total_size` is the total
aligned size of that array, `CyclePadding` will correspond to the padding in an
infinite vector of such structs.
"""
struct CyclePadding{P}
    padding::P
    total_size::Int
end
eltype(::Type{<:CyclePadding}) = Padding
IteratorSize(::Type{<:CyclePadding}) = IsInfinite()
isempty(cp::CyclePadding) = isempty(cp.padding)
function iterate(cp::CyclePadding)
    y = iterate(cp.padding)
    y === nothing && return nothing
    y[1], (0, y[2])
end
function iterate(cp::CyclePadding, state::Tuple)
    y = iterate(cp.padding, tail(state)...)
    y === nothing && return iterate(cp, (state[1]+cp.total_size,))
    Padding(y[1].offset+state[1], y[1].size), (state[1], tail(y)...)
end

"""
    Compute the location of padding in a type.
"""
function padding(T)
    padding = Padding[]
    last_end::Int = 0
    for i = 1:fieldcount(T)
        offset = fieldoffset(T, i)
        fT = fieldtype(T, i)
        if offset != last_end
            push!(padding, Padding(offset, offset-last_end))
        end
        last_end = offset + sizeof(fT)
    end
    padding
end

function CyclePadding(T::DataType)
    a, s = datatype_alignment(T), sizeof(T)
    as = s + (a - (s % a)) % a
    pad = padding(T)
    s != as && push!(pad, Padding(s, as - s))
    CyclePadding(pad, as)
end

using .Iterators: Stateful
@pure function array_subpadding(S, T)
    checked_size = 0
    lcm_size = lcm(sizeof(S), sizeof(T))
    s, t = Stateful{<:Any, Any}(CyclePadding(S)),
           Stateful{<:Any, Any}(CyclePadding(T))
    isempty(t) && return true
    isempty(s) && return false
    while checked_size < lcm_size
        # Take padding in T
        pad = popfirst!(t)
        # See if there's corresponding padding in S
        while true
            ps = peek(s)
            ps.offset > pad.offset && return false
            intersect(ps, pad) == pad && break
            popfirst!(s)
        end
        checked_size = pad.offset + pad.size
    end
    return true
end

# Reductions with IndexSCartesian2

function _mapreduce(f::F, op::OP, style::IndexSCartesian2{K}, A::AbstractArrayOrBroadcasted) where {F,OP,K}
    inds = eachindex(style, A)
    n = size(inds)[2]
    if n == 0
        return mapreduce_empty_iter(f, op, A, IteratorEltype(A))
    else
        return mapreduce_impl(f, op, A, first(inds), last(inds))
    end
end

@noinline function mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted,
                                  ifirst::SCI, ilast::SCI, blksize::Int) where {F,OP,SCI<:SCartesianIndex2{K}} where K
    if ifirst.j + blksize > ilast.j
        # sequential portion
        @inbounds a1 = A[ifirst]
        @inbounds a2 = A[SCI(2,ifirst.j)]
        v = op(f(a1), f(a2))
        @simd for i = ifirst.i + 2 : K
            @inbounds ai = A[SCI(i,ifirst.j)]
            v = op(v, f(ai))
        end
        # Remaining columns
        for j = ifirst.j+1 : ilast.j
            @simd for i = 1:K
                @inbounds ai = A[SCI(i,j)]
                v = op(v, f(ai))
            end
        end
        return v
    else
        # pairwise portion
        jmid = (ifirst.j + ilast.j) >> 1
        v1 = mapreduce_impl(f, op, A, ifirst, SCI(K,jmid), blksize)
        v2 = mapreduce_impl(f, op, A, SCI(1,jmid+1), ilast, blksize)
        return op(v1, v2)
    end
end

mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted, ifirst::SCartesianIndex2, ilast::SCartesianIndex2) where {F,OP} =
    mapreduce_impl(f, op, A, ifirst, ilast, pairwise_blocksize(f, op))


include("test_filewriter.jl")
include("test_kind0.jl")
include("test_operators.jl")
include("test_show.jl")
include("test_state.jl")
include("test_dicke.jl")
include("test_collective.jl")


# Top-level file

"""
Support for sparse tensors (i.e., multi-dimensional arrays).
Provides `AbstractSparseTensor` and subtypes.
"""
module SparseTensors

# AbstractArray interface
import Base: size, getindex, setindex!
import Base: IndexStyle

# Overloads for specializing outputs
import Base: show, summary, similar

# Overloads for improving efficiency
import Base: findall, iterate

# Exported types
export AbstractSparseTensor, SparseTensorCOO, SparseTensorDOK

# AbstractSparseTensor interface
export dropstored!, numstored, storedindices, storedvalues, storedpairs

# Exported functions
export indtype

include("abstract.jl")
include("coo.jl")
include("dok.jl")

end


# ---------------------------------------------------------------------------- #
#
#   writeTecplot.jl
#
#   Several functions to write Tecplot output
#
#   λυτέος
#   Fall 2017
#
#   Max Opgenoord
#
# ---------------------------------------------------------------------------- #

"""
    writeTecplot( flname::String, prob::Elas, mesh::Mesh3D,
                  uh::Array{Float64,3}, σh::Array{Float64,3};
                  σmises = Array{Float64,3}(0,0,0) )

Outputs tecplot data file to `flname` for 3D structural elasticity problems.
"""
function writeTecplotP1( flname::String, prob::luteos.Elas, mesh::Mesh3D,
                         uh::Matrix{Float64}, σh::Matrix{Float64}; σmises = Matrix{Float64}(0,0) )

    nelem = size( mesh.nodes, 3 )
    ndat  = 2*mesh.dim + mesh.dim^2
    if size(σmises,1) > 0
        ndat += 1
    end

    # Open file
    fid = open( flname, "w" )

    ### Write header
    @printf( fid, "TITLE = \"%s\"\n", prob.name )
    @printf( fid, "VARIABLES = \"X\", \"Y\", \"Z\"" )
    for jj in 1:mesh.dim
        @printf( fid, ", \"U%i\"",jj )
    end
    for ii in 1:mesh.dim, jj in 1:mesh.dim
        @printf( fid, ", \"<greek>s</greek>%i%i\"", ii, jj )
    end
    if size( σmises, 1 ) > 0
        @printf( fid, ", \"<greek>s</greek>mises\"" )
    end
    @printf( fid, "\n" )
    @printf( fid, "ZONE, DATAPACKING=POINT, NODES=%i, ELEMENTS=%i, ZONETYPE=FETETRAHEDRON\n", mesh.n, nelem )

    ### Write values
    for pp in 1:mesh.n
        if size( σmises, 1 ) == 0
            wrtval = [mesh.p[pp,:]; uh[pp,:]; σh[pp,:] ]
        else
            wrtval = [mesh.p[pp,:]; uh[pp,:]; σh[pp,:]; σmises[pp,:] ]
        end

    for ii in 1:ndat
      @printf(fid, "%16.15e\t", wrtval[ii] )
    end
    @printf(fid,"\n")

    end

    ### Write connectivity
    for ee in 1:nelem

        for kk in 1:(mesh.dim+1)
            @printf(fid, "%i\t", mesh.t[ee,kk] )
        end
        @printf(fid,"\n")

    end

    close(fid)

end

"""
    writeTecplot( flname::String, prob::Elas, mesh::Mesh3D,
                  uh::Array{Float64,3}, σh::Array{Float64,3};
                  σmises = Array{Float64,3}(0,0,0) )

Outputs tecplot data file to `flname` for 3D structural elasticity problems.
"""
function writeTecplotP1( flname::String, prob::luteos.Problem, mesh::Mesh3D, solution::Matrix{Float64} )

    nelem   = size( mesh.nodes, 3 )
    nnodes  = size( mesh.nodes, 1 )
    ndat    = size( solution, 2 )

    @assert size(solution,1) == mesh.n

    # Open file
    fid = open( flname, "w" )

    ### Write header
    @printf( fid, "TITLE = \"%s\"\n", prob.name )
    @printf( fid, "VARIABLES = \"X\", \"Y\", \"Z\"" )
    for jj in 1:ndat
        @printf( fid, ", \"U%i\"",jj )
    end
    @printf( fid, "\n" )
    @printf( fid, "ZONE, DATAPACKING=POINT, NODES=%i, ELEMENTS=%i, ZONETYPE=FETETRAHEDRON\n", mesh.n, nelem )

    ### Write values
    for pp in 1:mesh.n

        wrtval = [mesh.p[pp,:] solution[pp,:] ]

        for ii in 1:ndat+3
            @printf(fid, "%16.15e\t", wrtval[ii] )
        end
        @printf(fid,"\n")

    end

    ### Write connectivity
    for ee in 1:nelem

        for kk in 1:(mesh.dim+1)
            @printf(fid, "%i\t", mesh.t[ee,kk] )
        end
        @printf(fid,"\n")

    end

    close(fid)

end

"""
    writeTecplot( flname::String, prob::Elas, mesh::Mesh3D,
                  uh::Array{Float64,3}, σh::Array{Float64,3};
                  σmises = Array{Float64,3}(0,0,0) )

Outputs tecplot data file to `flname` for 3D structural elasticity problems.
"""
function writeTecplotP1Defl( flname::String, prob::luteos.Problem, mesh::Mesh3D, solution::Matrix{Float64} )

  nelem   = size( mesh.nodes, 3 )
  nnodes  = size( mesh.nodes, 1 )
  ndat    = size( solution, 2 )

  @assert size(solution,1) == mesh.n

  # defl = solution ./ maximum(solution) * maximum( mesh.p ) * 0.25
  defl = solution

  # Open file
  fid = open( flname, "w" )

  ### Write header
  @printf( fid, "TITLE = \"%s\"\n", prob.name )
  @printf( fid, "VARIABLES = \"X\", \"Y\", \"Z\"" )
  for jj in 1:ndat
    @printf( fid, ", \"U%i\"",jj )
  end
  @printf( fid, "\n" )
  @printf( fid, "ZONE, DATAPACKING=POINT, NODES=%i, ELEMENTS=%i, ZONETYPE=FETETRAHEDRON\n",
    mesh.n, nelem )

  ### Write values
  for pp in 1:mesh.n

    wrtval = [mesh.p[pp,:]+defl[pp,:] solution[pp,:] ]

    for ii in 1:ndat+3
      @printf(fid, "%16.15e\t", wrtval[ii] )
    end
    @printf(fid,"\n")

  end

  ### Write connectivity
  for ee in 1:nelem

    for kk in 1:(mesh.dim+1)
      @printf(fid, "%i\t", mesh.t[ee,kk] )
    end
    @printf(fid,"\n")

  end

  close(fid)

end

function convertToNodal( mesh::Mesh3D, prob::luteos.Elas, uh::Array{Float64,3}, σh::Array{Float64,3} )

    @assert size(mesh.nodes,1) == 4

    nelem = size( mesh.nodes, 3 )

    ncounts = fill( 0, mesh.n )

    σhmises = luteos.compMises3D( σh )

    uhvec    = fill( 0., mesh.n, mesh.dim )
    σhvec    = fill( 0., mesh.n, mesh.dim^2 )
    σhmisvec = fill( 0., mesh.n, 1 )

    for kk in 1:nelem

        indsp = mesh.t[kk,:]

        ncounts[indsp] .+= 1

        for ii in 1:(mesh.dim+1)
            for jj in 1:mesh.dim
                uhvec[indsp[ii],jj] += uh[ii,jj,kk]
            end
            for jj in 1:mesh.dim^2
                σhvec[indsp[ii],jj] += σh[ii,jj,kk]
            end
            σhmisvec[indsp[ii],1] += σhmises[ii,1,kk]
        end

    end

    for jj in 1:mesh.n
        uhvec[jj,:]   ./= ncounts[jj]
        σhvec[jj,:]   ./= ncounts[jj]
        σhmisvec[jj,:] ./= ncounts[jj]
    end

    return uhvec, σhvec, σhmisvec

end

# TODO: Need to add surface output


function fstep(state::Union{SimState, Nothing}, agent::Agent)
    #print(string(agent.id,"\n"))
end

a1 = Agent(fstep,nothing)
a2 = Agent(fstep,nothing)

f = Field2D(100.0,100.0,0.7,true)

setObjectLocation!(f,a1,Real2D(10.5,10.0))

@test getObjectLocation(f,a1) == Real2D(10.5,10.0)

setObjectLocation!(f,a1,Real2D(10.0,10.0))

@test getObjectLocation(f,a1) == Real2D(10.0,10.0)

setObjectLocation!(f,a2,Real2D(11.0,11.0))

@test getObjectLocation(f,a2) == Real2D(11.0,11.0)

@test length(getAllObjects(f)) == 2

clean!(f)

@test length(getAllObjects(f)) == 0

setObjectLocation!(f,a1,Real2D(1.0,1.0))
setObjectLocation!(f,a2,Real2D(1.0,1.399999))

@test length(getObjectsAtLocation(f,Real2D(1.0,1.0))) == 1

setObjectLocation!(f,a1, getObjectLocation(f,a2))

@test getObjectLocation(f,a1) == Real2D(1.0,1.399999)

@test length(getObjectsAtLocation(f, getObjectLocation(f,a1))) == 2

@test numObjectsAtLocation(f, getObjectLocation(f,a1)) == 2

f2 = Field2D(10.0,10.0,0.5,false)

pos = Real2D(5.2,5.2)
__distance = 3.0

setObjectLocation!(f2,Patch(nothing),Real2D(5.5,5.5))
setObjectLocation!(f2,Patch(nothing),Real2D(4.0,4.0))
setObjectLocation!(f2,Patch(nothing),Real2D(5.2,5.2))
setObjectLocation!(f2,Patch(nothing),Real2D(5.2,2.2))
setObjectLocation!(f2,Patch(nothing),Real2D(5.2,2.1))

@test length(getNeighborsWithinDistance(f2, pos , __distance)) == 4

setObjectLocation!(f2,Patch(nothing),Real2D(0.1,0.1))
__distance = 5.0

@test length(getNeighborsWithinDistance(f2, pos , __distance)) == 5

f3 = Field2D(10.0,10.0,0.5,true)

pos = Real2D(8.1,8.1)
__distance = 3.0

setObjectLocation!(f3,Patch(nothing),Real2D(7.0,8.1))
setObjectLocation!(f3,Patch(nothing),Real2D(8.1,1.0))
setObjectLocation!(f3,Patch(nothing),Real2D(8.1,1.3))
setObjectLocation!(f3,Patch(nothing),Real2D(8.1,9.9))

@test length(getNeighborsWithinDistance(f3, pos , __distance)) == 3

f4 = Field2D(10.0,10.0,0.6,true)

setObjectLocation!(f4,Patch(nothing),Real2D(7.0,8.1))
setObjectLocation!(f4,Patch(nothing),Real2D(8.1,1.0))
setObjectLocation!(f4,Patch(nothing),Real2D(8.1,1.3))
setObjectLocation!(f4,Patch(nothing),Real2D(8.1,9.9))

@test length(getNeighborsWithinDistance(f4, pos , __distance)) == 3

setObjectLocation!(f4,Patch(nothing),Real2D(1.0,9.7))

@test length(getNeighborsWithinDistance(f4, pos , __distance)) == 3

setObjectLocation!(f4,Patch(nothing),Real2D(1.1,8.1))

@test length(getNeighborsWithinDistance(f4, pos , __distance)) == 4

@test length(getNeighborsWithinDistance(f4,  Real2D(0.1,5.0) , __distance)) == 0

setObjectLocation!(f4,Patch(nothing),Real2D(9.9,2.1))
setObjectLocation!(f4,Patch(nothing),Real2D(2.9,4.5))

@test length(getNeighborsWithinDistance(f4,  Real2D(0.1,5.0) , __distance)) == 2

setObjectLocation!(f4,Patch(nothing),Real2D(2.9,8.0))

@test length(getNeighborsWithinDistance(f4,  Real2D(0.1,5.0) , __distance)) == 2


module TestSources

using MLJBase
using Test

X = 7

Xs = source(X)
@test Xs() == X
@test Xs(8) == 8
@test elscitype(Xs) == Count
@test scitype(Xs) == MLJBase.CallableReturning{Count}
rebind!(Xs, nothing)
@test isempty(Xs)
@test Xs.scitype == Nothing

n = MLJBase.ErrorNode(ArgumentError("Stop!"))
@test_throws ArgumentError("Stop!") n(rows=1:3)
@test_throws ArgumentError("Stop!") n(1:3)

end
true


time_average_outputs(schedule, outputs, model, field_slicer) = schedule, outputs # fallback

"""
    time_average_outputs(schedule::AveragedTimeInterval, outputs, model, field_slicer)

Wrap each `output` in a `WindowedTimeAverage` on the time-averaged `schedule` and with `field_slicer`.

Returns the `TimeInterval` associated with `schedule` and a `NamedTuple` or `Dict` of the wrapped
outputs.
"""
function time_average_outputs(schedule::AveragedTimeInterval, outputs::Dict, model, field_slicer)
    averaged_outputs = Dict(name => WindowedTimeAverage(output, model; schedule=schedule, field_slicer=field_slicer)
                            for (name, output) in outputs)

    return TimeInterval(schedule), averaged_outputs
end

function time_average_outputs(schedule::AveragedTimeInterval, outputs::NamedTuple, model, field_slicer)
    output_names = Tuple(keys(outputs))

    output_values = Tuple(WindowedTimeAverage(outputs[name], model; schedule=schedule, field_slicer=field_slicer)
                          for name in output_names)

    averaged_outputs = NamedTuple{output_names}(output_values)

    return TimeInterval(schedule), averaged_outputs
end


using Flux, Gym

# Load game environment
env = make("Pendulum-v0")
ctx = Ctx(env, :human_window)
reset!(env)

actions = [sample(env._env.action_space) for i=1:1000]
i = 1
done = false
reset!(env)
while i <= length(actions) && !done
    global i, done
    print("Iteration: $i, ")
    a, b, done, d = step!(env, actions[i])
    println("θ = $(env._env.state[1])")

    render!(env)
    i += 1
    sleep(0.01) # to see an animation
end


module ArrayStreams

using ImageMetadata, ImageCore, StaticArrays, Distributions, ColorTypes
import AxisArrays
using AxisArrays: AxisArray, axisnames, permutation
using GeometryTypes: Triangle, Point
using Rotations: Quat

export AbstractArrayStream, ArrayStream, stream

function fixendian(A::AbstractArray, needswap::Bool)
    if needswap
        return mappedarray((ntoh, hton), A)
    else
        return A
    end
end

expand2indices(A::AbstractArray{T,NA}, idx::Tuple{Vararg{<:Any,NI}}) where {T,NA,NI} =
    to_indices(A,(idx..., ntuple(i->Colon(), NA-NI)...))
expand2indices(A::AbstractArray{T,N}, idx::Tuple{Vararg{<:Any,N}}) where {T,N} =
    to_indices(A,idx)
BitTypes = Union{Integer,AbstractFloat}

include("iotypes/iotypes.jl")
include("abstractarraystream.jl")
include("arraystream.jl")
include("imagemeta.jl")
include("axisarray.jl")

# TODO
# - write IOBuffer (i.e. write(s) where there's not Array parameters but buffer contains
# - documentation
#   - write should be explained in the context of sinks because ArrayStream acts as the sink in this case
# - check extractquat for proper output shape
#   - Distributions, Quat, SArray
# - iterators for chunkedstream
# - constructors for chunkedstream
# - Impliment Colors
# - append! operations (maybe, as ArrayStreams are more like StaticArrays)

end


using Test, NiLangCore
using NiLangCore: SymbolTable, allocate!, deallocate!, operate!, swapvars!, variable_analysis_ex

@testset "variable analysis" begin
    st = SymbolTable()
    # allocate! : not exist
    allocate!(st, :x)
    allocate!(st, :y)
    @test st.existing == [:x, :y]
    # allocate! : existing
    @test_throws InvertibilityError allocate!(st, :x)
    @test st.existing == [:x, :y]
    # deallocate! : not exist
    @test_throws InvertibilityError deallocate!(st, :z)
    # deallocate! : existing
    deallocate!(st, :y)
    @test st.existing == [:x]
    @test st.deallocated == [:y]
    # deallocate! : deallocated
    @test_throws InvertibilityError deallocate!(st, :y)
    # operate! : deallocated
    @test_throws InvertibilityError operate!(st, :y)
    # allocate! : deallocated
    allocate!(st, :y)
    @test st.existing == [:x, :y]
    @test st.deallocated == []
    # operate! : not exist
    operate!(st, :j)
    @test st.unclassified == [:j]
    # operate! : existing
    operate!(st, :y)
    @test st.unclassified == [:j]
    # allocate! unclassified
    @test_throws InvertibilityError allocate!(st, :j)
    # operate! : unclassified
    operate!(st, :j)
    @test st.unclassified == [:j]
    # deallocate! : unclassified
    @test_throws InvertibilityError deallocate!(st, :j)

    # swap both existing
    swapvars!(st, :j, :x)
    @test st.unclassified == [:x]
    @test st.existing == [:j, :y]

    # swap existing - nonexisting
    swapvars!(st, :j, :k)
    @test st.unclassified == [:x, :j]
    @test st.existing == [:k, :y]

    # swap nonexisting - existing
    swapvars!(st, :o, :x)
    @test st.unclassified == [:o, :j, :x]
    @test st.existing == [:k, :y]

    # swap both not existing
    swapvars!(st, :m, :n)
    @test st.unclassified == [:o, :j, :x, :m, :n]

    # push and pop variables
end

@testset "variable analysis" begin
    st = SymbolTable([:x, :y], [], [])
    ex = :((x,y) ↔ (a, b))
    variable_analysis_ex(ex, st)
    @test st.existing == [:a, :b]
    @test st.unclassified == [:x, :y]
    st = SymbolTable([:x, :y], [], [])
    ex = :((x,y) ↔ b)
    variable_analysis_ex(ex, st)
    @test st.existing == [:b]
    @test st.unclassified == [:x, :y]
    ex = :(b ↔ (x,y))
    variable_analysis_ex(ex, st)
    @test st.existing == [:x, :y]
    @test st.unclassified == [:b]

    st = SymbolTable([:x, :y], [], [])
    ex = :(b ↔ x)
    variable_analysis_ex(ex, st)
    @test st.existing == [:b, :y]
    @test st.unclassified == [:x]

    st = SymbolTable([], [], [])
    ex = :(b ↔ (x, y))
    variable_analysis_ex(ex, st)
    @test st.existing == []
    @test st.unclassified == [:b, :x, :y]
end


struct CartesianVIndex{N,T} <: Base.AbstractCartesianIndex{N}
    I::T
    @inline CartesianVIndex(I::T) where {N, T <: Tuple{Vararg{Integer,N}}} = new{N,T}(I)
end
Base.length(::CartesianVIndex{N}) where {N} = N
ArrayInterface.known_length(::Type{<:CartesianVIndex{N}}) where {N} = N
Base.Tuple(i::CartesianVIndex) = i.I
function Base.:(:)(I::CartesianVIndex{N}, J::CartesianVIndex{N}) where {N}
   CartesianIndices(map((i,j) -> i:j, I.I, J.I))
end
Base.@propagate_inbounds Base.getindex(I::CartesianVIndex, i) = I.I[i]
_ndim(::Type{<:Base.AbstractCartesianIndex{N}}) where {N} = N
# _ndim(::Type{<:AbstractArray{N}}) where {N} = N
@generated function CartesianVIndex(I::T) where {T <: Tuple{Vararg{Union{Integer,CartesianIndex,CartesianVIndex}}}}
    iexpr = Expr(:tuple)
    Tp = T.parameters
    q = Expr(:block)
    for i in eachindex(Tp)
        I_i = Symbol(:I_, i)
        push!(q.args, Expr(:(=), I_i, Expr(:ref, :I, i)))
        if Tp[i] <: Base.AbstractCartesianIndex
            for n in 1:_ndim(Tp[i])
                push!(iexpr.args, Expr(:ref, I_i, n))
            end
        else
            push!(iexpr.args, I_i)
        end
    end
    push!(q.args, Expr(:call, :CartesianVIndex, iexpr))
    Expr(:block, Expr(:meta, :inline), Expr(:macrocall, Symbol("@inbounds"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), q))
end

# @inline Base.CartesianIndex(I::Tuple{Vararg{Union{Integer,CartesianIndex,CartesianVIndex,StaticInt}}}) = CartesianVIndex(I)

@generated function _maybestaticfirst(a::Tuple{Vararg{Any,N}}) where {N}
    quote
        $(Expr(:meta,:inline))
        Base.Cartesian.@ntuple $N n -> maybestaticfirst(a[n])
    end
end
@generated function _maybestaticlast(a::Tuple{Vararg{Any,N}}) where {N}
    quote
        $(Expr(:meta,:inline))
        Base.Cartesian.@ntuple $N n -> maybestaticlast(a[n])
    end
end
@inline maybestaticfirst(A::CartesianIndices) = CartesianVIndex(_maybestaticfirst(A.indices))
@inline maybestaticlast(A::CartesianIndices) = CartesianVIndex(_maybestaticlast(A.indices))


# abstract type AbstractAllocator end
# abstract type StaticAllocator <: AbstractAllocator end
# include("PseudoInverseAllocator.jl")
include("ConstrainedAllocator.jl")
include("AdaptiveAllocator.jl")