##########################################################################
# Postprocess results from the simulate function.
function postprocess(raw_results, simulation_params, plot_params)
    methods = get_methods_to_plot(simulation_params, plot_params)

    # Main independent variable
    idp_vals_length = length(simulation_params["independent_variable"][2])

    # Auxiliary independent variables
    if haskey(simulation_params, "aux_independent_variables")
        Naux = length(simulation_params["aux_independent_variables"])
        aux_idp_vals_length = length(simulation_params["aux_independent_variables"][1][2])
        for n = 2:Naux
            aux_idp_vals_length == length(simulation_params["aux_independent_variables"][n][2]) ? nothing : Lumberjack.error("Auxiliary independent variable vectors must have equal length.")
        end
    else
        Naux = 0; aux_idp_vals_length = 1
    end

    # Compact raw results into result matrices
    Ndrops = size(raw_results.simulation_results, 1); Nsim = size(raw_results.simulation_results, 2)
    results = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param

                result_dimensions = size(raw_results[1, 1, 1, 1][method_name][result_name])
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)

                result_dimensions = size(raw_results[1, 1, 1, 1][method_name][calculate_from])
            end
            result_ranges = [ 1:s for s in result_dimensions ]
            results[method_name][result_name] = Array(Float64, Ndrops, Nsim, idp_vals_length, aux_idp_vals_length, result_dimensions...)
            for Ndrops_idx = 1:Ndrops; for Nsim_idx = 1:Nsim; for idp_vals_idx = 1:idp_vals_length; for aux_idp_vals_idx = 1:aux_idp_vals_length
                if isa(result_param, ASCIIString)
                    result = raw_results[Ndrops_idx, Nsim_idx, idp_vals_idx, aux_idp_vals_idx][method_name][result_name]
                else
                    result = calculator(raw_results[Ndrops_idx, Nsim_idx, idp_vals_idx, aux_idp_vals_idx][method_name][calculate_from], idp_vals_idx)
                end
                results[method_name][result_name][Ndrops_idx, Nsim_idx, idp_vals_idx, aux_idp_vals_idx, result_ranges...] = result
            end; end; end; end
        end
    end

    # Calculate statistics on result matrices
    plot_params["objective"] == :sum && (objective = (r -> sum(r, 5:6)))
    plot_params["objective"] == :mean && (objective = (r -> mean(sum(r, 6), 5)))
    plot_params["objective"] == :min && (objective = (r -> minimum(sum(r, 6), 5)))
    plot_params["objective"] == :none && (objective = (r -> r))

    results_mean = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    results_var = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)
            end

            # mean: average over drops and sims
            results_mean[method_name][result_name] = squeeze(mean(objective(results[method_name][result_name]), 1:2), (1,2,5,6))

            # var: average over sims, estimate var over drops
            results_var[method_name][result_name] = squeeze(var(mean(objective(results[method_name][result_name]), 2), 1), (1,2,5,6))
        end
    end

    return results, results_mean, results_var
end

##########################################################################
# Plot results from the postprocess function.
function plot(processed_results, simulation_params, plot_params)
    results_mean = processed_results[2]; results_var = processed_results[3]

    fig = PyPlot.figure(;plot_params["figure"]...)
    ax = fig[:add_subplot](1, 1, 1; plot_params["axes"]...)

    plot_methods(ax, results_mean, results_var, simulation_params, plot_params;
        xvals=(haskey(plot_params, "xvals") ? plot_params["xvals"] : simulation_params["independent_variable"][2]))
    haskey(plot_params, "legend") && ax[:legend](;plot_params["legend"]...)

    if displayable("application/pdf") || displayable("image/png")
        display(fig)
    else
        open("$(simulation_params["simulation_name"])_$(plot_params["plot_name"]).pdf", "w") do file
            writemime(file, "application/pdf", fig)
        end
    end
    PyPlot.close(fig)
end

##########################################################################
# Post-process results from the simulate_precoding_convergence function.
function postprocess_precoding_convergence(raw_results, simulation_params, plot_params)
    methods = get_methods_to_plot(simulation_params, plot_params)

    # Auxiliary independent variables
    if haskey(simulation_params, "aux_independent_variables")
        Naux = length(simulation_params["aux_independent_variables"])
        aux_idp_vals_length = length(simulation_params["aux_independent_variables"][1][2])
        for n = 2:Naux
            aux_idp_vals_length == length(simulation_params["aux_independent_variables"][n][2]) ? nothing : Lumberjack.error("Auxiliary independent variable vectors must have equal length.")
        end
    else
        Naux = 0; aux_idp_vals_length = 1
    end

    # Compact raw results into result matrices
    Ndrops = size(raw_results.simulation_results, 1); Nsim = size(raw_results.simulation_results, 2)
    results = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param

                result_dimensions = size(raw_results[1, 1, 1][method_name][result_name])
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)

                result_dimensions = size(raw_results[1, 1, 1][method_name][calculate_from])
            end

            result_ranges = [ 1:s for s in result_dimensions ]
            results[method_name][result_name] = Array(Float64, Ndrops, Nsim, aux_idp_vals_length, result_dimensions...)
            for Ndrops_idx = 1:Ndrops; for Nsim_idx = 1:Nsim; for aux_idp_vals_idx = 1:aux_idp_vals_length
                if isa(result_param, ASCIIString)
                    result = raw_results[Ndrops_idx, Nsim_idx, aux_idp_vals_idx][method_name][result_name]
                else
                    result = calculator(raw_results[Ndrops_idx, Nsim_idx, aux_idp_vals_idx][method_name][calculate_from])
                end
                results[method_name][result_name][Ndrops_idx, Nsim_idx, aux_idp_vals_idx, result_ranges...] = result
            end; end; end
        end
    end

    # Calculate statistics on result matrices
    plot_params["objective"] == :sum && (objective = (r -> sum(r, 4:5)))
    plot_params["objective"] == :mean && (objective = (r -> mean(sum(r, 5), 4)))
    plot_params["objective"] == :min && (objective = (r -> minimum(sum(r, 5), 4)))
    plot_params["objective"] == :none && (objective = (r -> r))

    results_mean = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    results_var = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)
            end

            # mean: average over drops and sims
            results_mean[method_name][result_name] = transpose(squeeze(mean(objective(results[method_name][result_name]), 1:2), (1,2,4,5)))

            # var: average over sims, estimate var over drops
            results_var[method_name][result_name] = transpose(squeeze(var(mean(objective(results[method_name][result_name]), 2), 1), (1,2,4,5)))
        end
    end

    return results, results_mean, results_var
end

##########################################################################
# Post-process results from the simulate_assignment_convergence function.
function postprocess_assignment_convergence(raw_results, simulation_params, plot_params)
    methods = get_methods_to_plot(simulation_params, plot_params)

    # Auxiliary independent variables
    if haskey(simulation_params, "aux_independent_variables")
        Naux = length(simulation_params["aux_independent_variables"])
        aux_idp_vals_length = length(simulation_params["aux_independent_variables"][1][2])
        for n = 2:Naux
            aux_idp_vals_length == length(simulation_params["aux_independent_variables"][n][2]) ? nothing : Lumberjack.error("Auxiliary independent variable vectors must have equal length.")
        end
    else
        Naux = 0; aux_idp_vals_length = 1
    end

    # Compact raw results into result matrices
    Ndrops = size(raw_results.simulation_results, 1)
    results = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param

                result_dimensions = size(raw_results[1, 1, 1][method_name][result_name])
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)

                result_dimensions = size(raw_results[1, 1, 1][method_name][calculate_from])
            end

            result_ranges = [ 1:s for s in result_dimensions ]
            results[method_name][result_name] = Array(Float64, Ndrops, 1, aux_idp_vals_length, result_dimensions...)
            for Ndrops_idx = 1:Ndrops; for aux_idp_vals_idx = 1:aux_idp_vals_length
                if isa(result_param, ASCIIString)
                    result = raw_results[Ndrops_idx, 1, aux_idp_vals_idx][method_name][result_name]
                else
                    result = calculator(raw_results[Ndrops_idx, 1, aux_idp_vals_idx][method_name][calculate_from])
                end
                results[method_name][result_name][Ndrops_idx, 1, aux_idp_vals_idx, result_ranges...] = result
            end; end
        end
    end

    # Calculate statistics on result matrices
    plot_params["objective"] == :sum && (objective = (r -> sum(r, 4:5)))
    plot_params["objective"] == :mean && (objective = (r -> mean(sum(r, 5), 4)))
    plot_params["objective"] == :min && (objective = (r -> minimum(sum(r, 5), 4)))
    plot_params["objective"] == :none && (objective = (r -> r))

    results_mean = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    results_var = [ method => Dict{ASCIIString, Array{Float64}}() for method in methods ]
    for method_name in methods
        for (result_param,) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)
            end

            # mean: average over drops and sims
            results_mean[method_name][result_name] = transpose(squeeze(mean(objective(results[method_name][result_name]), 1:2), (1,2,4,5)))

            # var: average over sims, estimate var over drops
            results_var[method_name][result_name] = transpose(squeeze(var(mean(objective(results[method_name][result_name]), 2), 1), (1,2,4,5)))
        end
    end

    return results, results_mean, results_var
end

##########################################################################
# Plots results from the postprocess_precoding_convergence function.
function plot_precoding_convergence(processed_results, simulation_params, plot_params; user_plots::Bool=true)
    methods = get_methods_to_plot(simulation_params, plot_params)

    results = processed_results[1]
    results_mean = processed_results[2]
    results_var = processed_results[3]

    ### SYSTEM-LEVEL OBJECTIVE ###
    fig = PyPlot.figure(;plot_params["figure"]...)
    ax = fig[:add_subplot](1, 1, 1; plot_params["axes"]...)

    if haskey(plot_params, "xvals")
        plot_methods(ax, results_mean, results_var, simulation_params, plot_params;
            xvals=plot_params["xvals"])
    else
        plot_methods(ax, results_mean, results_var, simulation_params, plot_params)
    end
    haskey(plot_params, "legend") && ax[:legend](;plot_params["legend"]...)

    if displayable("application/pdf") || displayable("image/png")
        display(fig)
    else
        open("$(simulation_params["simulation_name"])_$(plot_params["plot_name"]).pdf", "w") do file
            writemime(file, "application/pdf", fig)
        end
    end
    PyPlot.close(fig)

    if user_plots
        ### USER UTILITIES ###
        K_method_name = collect(keys(results))[1]
        K_result_name = collect(keys(results[K_method_name]))[1]
        K = size(results[K_method_name][K_result_name], 4) # ugly shit. This is really not stable.

        fig = PyPlot.figure(figsize=(6*K,3*length(methods)))
        subplot_ind = 1

        for method_name in methods
            for k = 1:K
                ax = fig[:add_subplot](length(methods), K, subplot_ind); subplot_ind += 1

                for (result_param, line_params) in plot_params["methods"][method_name]
                    if isa(result_param, ASCIIString)
                        result = results[method_name][result_param]
                    else
                        (calculator, calculate_from) = result_param
                        result = calculator(results[method_name][calculate_from])
                    end

                    ax[:plot](1:size(result, 6),
                              transpose(squeeze(mean(sum(result[:,:,:,k,:,:], 5), 1:2), (1,2,4,5)));
                              line_params...)
                end

                if k == 1
                    ax[:set_ylabel](method_name)
                end
                if subplot_ind-1 <= K
                    ax[:set_title]("User $k")
                end
                if subplot_ind-1 > (K-1)*length(methods)
                    ax[:set_xlabel]("Iteration")
                end
            end
        end

        if displayable("application/pdf") || displayable("image/png")
            display(fig)
        else
            open("$(simulation_params["simulation_name"])_$(plot_params["plot_name"])_peruser.pdf", "w") do file
                writemime(file, "application/pdf", fig)
            end
        end
        PyPlot.close(fig)

        ### STREAM UTILITIES ###
        fig = PyPlot.figure(figsize=(6*simulation_params["d"],3*K))
        subplot_ind = 1

        for k = 1:K; for n = 1:simulation_params["d"]
            ax = fig[:add_subplot](K, simulation_params["d"], subplot_ind); subplot_ind += 1

            for method_name in methods
                for (result_param, line_params) in plot_params["methods"][method_name]
                    if isa(result_param, ASCIIString)
                        result = results[method_name][result_param]
                    else
                        (calculator, calculate_from) = result_param
                        result = calculator(results[method_name][calculate_from])
                    end

                    ax[:plot](1:size(result, 6),
                              transpose(squeeze(mean(result[:,:,:,k,n,:], 1:2), (1,2,4,5)));
                              line_params...)
                end
            end

            if n == 1
                ax[:set_ylabel]("User $k")
            end
            if k == 1
                ax[:set_title]("Stream $n")
            end
            if k == K
                ax[:set_xlabel]("Iteration")
            end
        end; end

        if displayable("application/pdf") || displayable("image/png")
            display(fig)
        else
            open("$(simulation_params["simulation_name"])_$(plot_params["plot_name"])_perstream.pdf", "w") do file
                writemime(file, "application/pdf", fig)
            end
        end
        PyPlot.close(fig)
    end
end

##########################################################################
# Plots results from the postprocess_assignment_convergence function.
plot_assignment_convergence(processed_results, simulation_params, plot_params; user_plots::Bool=true) =
    plot_precoding_convergence(processed_results, simulation_params, plot_params, user_plots=user_plots)

##########################################################################
# Helper methods

# Plot lines for all methods into an axes
function plot_methods(ax, results_mean, results_var, simulation_params, plot_params; xvals=[])
    methods = get_methods_to_plot(simulation_params, plot_params)

    for method_name in methods
        for (result_param, line_params) in plot_params["methods"][method_name]
            if isa(result_param, ASCIIString)
                result_name = result_param
            else
                (calculator, calculate_from) = result_param
                result_name = string(string(calculator), "_", calculate_from)
            end

            xvals = (xvals != []) ? xvals : 1:size(results_mean[method_name][result_name], 1)
            ax[:plot](xvals, results_mean[method_name][result_name]; line_params...)

            if haskey(plot_params, "confidence_interval_zalpha2")
                for n = 1:size(results_mean[method_name][result_name], 2)
                    m = results_mean[method_name][result_name][:,n]
                    s = sqrt(results_var[method_name][result_name][:,n])
                    e = plot_params["confidence_interval_zalpha2"]*s/sqrt(simulation_params["Ndrops"])
                    ax[:fill_between](xvals, m + e, m - e; alpha=0.5, color=line_params[:color])
                end
            end
        end
    end
end

# We can only plot the methods that have been simulated (and thus specified
# in simulation_params), and those we have plotting parameters for (and
# thus specified in plot_params).
function get_methods_to_plot(simulation_params, plot_params)
    precoding_methods = get(simulation_params, "precoding_methods", Function[])
    assignment_methods = get(simulation_params, "assignment_methods", Function[])

    return intersect(map(stringify_method, union(precoding_methods, assignment_methods)), keys(plot_params["methods"]))
end


"""
GenX: An Configurable Capacity Expansion Model
Copyright (C) 2021,  Massachusetts Institute of Technology
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
A complete copy of the GNU General Public License v2 (GPLv2) is available
in LICENSE.txt.  Users uncompressing this from an archive may not have
received this license file.  If not, see <http://www.gnu.org/licenses/>.
"""

@doc raw"""
	write_power(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the different values of power generated by the different technologies in operation.
"""
function write_power(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)
	dfGen = inputs["dfGen"]
	G = inputs["G"]     # Number of resources (generators, storage, DR, and DERs)
	T = inputs["T"]     # Number of time steps (hours)

	# Power injected by each resource in each time step
	dfPower = DataFrame(Resource = inputs["RESOURCES"], Zone = dfGen[!,:Zone], AnnualSum = Array{Union{Missing,Float32}}(undef, G))
	if setup["ParameterScale"] ==1
		for i in 1:G
			dfPower[!,:AnnualSum][i] = sum(inputs["omega"].* (value.(EP[:vP])[i,:])) * ModelScalingFactor
		end
		dfPower = hcat(dfPower, DataFrame((value.(EP[:vP]))* ModelScalingFactor, :auto))
	else
		for i in 1:G
			dfPower[!,:AnnualSum][i] = sum(inputs["omega"].* (value.(EP[:vP])[i,:]))
		end
		dfPower = hcat(dfPower, DataFrame(value.(EP[:vP]), :auto))
	end

	auxNew_Names=[Symbol("Resource");Symbol("Zone");Symbol("AnnualSum");[Symbol("t$t") for t in 1:T]]
	rename!(dfPower,auxNew_Names)

	total = DataFrame(["Total" 0 sum(dfPower[!,:AnnualSum]) fill(0.0, (1,T))], :auto)
	for t in 1:T
		total[:,t+3] .= sum(dfPower[:,Symbol("t$t")][1:G])
	end
	rename!(total,auxNew_Names)
	dfPower = vcat(dfPower, total)
 	CSV.write(joinpath(path, "power.csv"), dftranspose(dfPower, false), writeheader=false)
	return dfPower
end


using Knet
include("../bilstm.jl")
function test_convert()
    atype = (gpu() >= 0 ? KnetArray{Float32} : Array{Float32})
    batchsize = 25; hiddens = [280]; embedding = 256; vocabsize = 10000
    m = initmodel(atype, hiddens, embedding, vocabsize)
    mco = convertmodel(m)
    for k in keys(mco)
        @assert(mco[k] == m[k])
    end
    info("Conversion tast passed")
end
!isinteractive() && test_convert()


using Random
include("../simulation/simulation.jl")

function move_causes_collision(new_x, new_y, simulation::Simulation)
    for other_bi in 1:length(simulation.bikes)
        if new_x == simulation.bikes[other_bi].x_position && new_y == simulation.bikes[other_bi].y_position
            return true
        end
    end
    return false
end

function get_new_random_position(current)
    if bitrand()[1]
        possible_new_position =  current + rand((-1, 1))
        if possible_new_position > -1 && possible_new_position < 100 + 1
            return possible_new_position
        end
    end
    return current
end

function move_bike(bike::Bike, simulation::Simulation)
    new_x = get_new_random_position(bike.x_position)
    new_y = get_new_random_position(bike.y_position)

    collision_occured = move_causes_collision(new_x, new_y, simulation)

    if !collision_occured
        bike.x_position = new_x
        bike.y_position = new_y
    end
end


println("Dm) Test of the RadiativeAuger module with ASF from an internally generated initial- and final-state multiplet.")
#
setDefaults("print summary: open", "zzz-RadiativeAuger.sum")
setDefaults("method: continuum, Galerkin")           ## setDefaults("method: continuum, Galerkin")  "method: continuum, asymptotic Coulomb"
                                                     ## setDefaults("method: normalization, Ong-Russek")
setDefaults("method: normalization, pure sine")      ## setDefaults("method: normalization, pure Coulomb")    setDefaults("method: normalization, pure sine")
setDefaults("unit: rate", "a.u.")

grid = Radial.Grid(Radial.Grid(false), rnt = 4.0e-6, h = 5.0e-2, hp = 1.0e-2, rbox = 10.0)

if  false
    # Green function for radiative Auger decay of 1s photoionized neon
    name             = "radiative Auger decay of 1s photoionized neon"
    refConfigs       = [Configuration("1s 2s^2 2p^6")]
    levelSymmetries  = [LevelSymmetry(1//2, Basics.minus), LevelSymmetry(1//2, Basics.plus)]
    greenSettings    = GreenSettings(3, [0, 1], 0.01, true, LevelSelection())
    greenRep         = Representation(name, Nuclear.Model(10.), Radial.Grid(true), refConfigs,
                                      GreenExpansion( AtomicState.DampedSpaceCI(), Basics.DeExciteSingleElectron(), levelSymmetries, 3, greenSettings) )
    greenOut         = generate(greenRep, output=true)
    raGreen          = greenOut["Green channels"]

elseif true
    # Radiative Auger decay of 1s photoionized neon
    raSettings = RadiativeAuger.Settings([E1, M1], [JAC.UseCoulomb, JAC.UseBabushkin], raGreen, 2, 3, true, CoulombInteraction(),
                                         LineSelection(true, indexPairs=[(1,1), (1,2)]))

    wa = Atomic.Computation(Atomic.Computation(), name="xx", grid=JAC.Radial.Grid(true), nuclearModel=Nuclear.Model(10.),
                            initialConfigs=[Configuration("1s 2s^2 2p^6")],
                            finalConfigs  =[Configuration("1s^2 2s^2 2p^4"), Configuration("1s^2 2s 2p^5"), Configuration("1s^2 2p^6") ],
                            process = RAuger(), processSettings = raSettings )

    wb = perform(wa)
end
setDefaults("print summary: close", "")


## basic modifier expressors

"""
	A(v::Float64, z)

Accelirate `z`::Expi; multiplies all atom velocities by `v`.
"""
A(v::Float64, z) = 0 < v ? accel(v,atomOrExp(z)) : throw(DomainError())

"""
	D(v::IR, z)

Dilate `z`; multiplies all atom durations and offsets by `v`.
"""
D(v::IR, z) = 0 < v ? dilate(Rat(v),atomOrExp(z)) : throw(DomainError())

"""
	F(v::IR, z)

Shift `z`: adds `v` to all atom offsets
"""
F(v::IR, z) = sshift(Rat(v),atomOrExp(z))

"""
	T(v::Int, z)

Translate `z`; adds `v` to all note interval values.
"""
T(v::Int, z) = transl(v,atomOrExp(z))

"""
	V(v::Int, z)

Octave `z`; sets all note octave values to `v`.
"""
V(v::Int, z) = octave(v,atomOrExp(z))

"""
	Cha(v::Int, z)

Channel `z`; sets all channel values to `v`.
"""
Cha(v::Int, z) = channel(v,atomOrExp(z))

"""
	Sca(v::Function, z)

Scale `z`; sets all note scales to `v`.  See SirenSeq.Scales
"""
Sca(v::Scales.Scale, z) = sscale(v,atomOrExp(z))

## used by constructor N
defaultScale = Scales.cMaj
defaultOctave = 3

"""
	setDefaultScale(sca::Function)

Sets the default scale for `N` constructor (defualt value is cMaj).
"""
setDefaultScale(sca::Scales.Scale) = ( global defaultScale = sca ; )

"""
	setDefaultOctave(ocv::Function)

Sets the default octave for `N` constructor (defualt value is 3).
"""
setDefaultOctave(ocv::Function) = ( global defaultOctave = ocv ; )

## basic constructor expressors

"""
	B(dur::IR=1//1)

An empty `Exp` with specified duration
"""
B(dur::IR=1//1) = ( @assert dur >= 0 ; Exp(dur,Atom[]) )

"""
	N(itv::Int)

A whole-note `Atom`; `itv` is the interval value on its scale. \\
Duration is `1//1`, offset is `0//1`, channel is `1`, velocity is `1.0`. \\
Uses default octave and scale (see `setDefaultScale` and `setDefaultOctave`).
"""
N(itv::Int) = Note(0//1,1//1,1,itv,defaultOctave,1.0,defaultScale)

## integers are converted to the default note
Base.convert(::Type{Atom}, v::Int) = N(v)

"""
	Bank(v::Int)

Bank select `Atom`; bank number is `v`.
"""
Bank(v::Int) = BankSel(0//1,1,v)

"""
	Prog(v::Int)

Program select `Atom`; program number is `v`.
"""
Prog(v::Int) = ProgSel(0//1,1,v)

"""
	Vol(v::Float64)

Volume set `Atom`; volume is `v`.
"""
Vol(v::Float64) = VolSet(0//1,1,v)

"""
	Wh()
	Wh(v::Float64)

Picth wheel bend `Atom`; bend value is `v` [-1,1], use Wh() for no bend (i.e. reset).
"""
Wh() = PitchWheel(0//1,1,2^13)
Wh(v::Float64) = ( v = clamp(v,-1,1) ; w = v==0 ? 2^13 : clamp(convert(Int,round(2^13*(1.0+v))),0,2^14-1) ; PitchWheel(0//1,1,w) )

"""
	Aft(v::Float64)

Channel aftertouch `Atom`; `v` [0,1] is the value.
"""
Aft(v::Float64) = ( w = clamp(convert(Int,round(v*127)),0,127) ; ChanAfter(0//1,1,w) )

"""
	Cn7(num::Int, v::Float64)
	Cn7(num::Int, v::Int)

7bit channel control `Atom`; `num` is the contol number, `v` is value ([0,1] if Float, {0..127} if Int).
"""
Cn7(num::Int, v::Float64) = ( w = clamp(convert(Int,round(v*127)),0,127) ; Control7(0//1,1,num,w) )
Cn7(num::Int, v::Int) = Control7(0//1,1,num,clamp(v,0,127))

"""
	Cn14(num::Int, v::Float64)
	Cn14(num::Int, v::Int)

14bit channel control `Atom`; `num` is the contol number, `v` is value ([0,1] if Float, {0..2^14-1} if Int).
"""
Cn14(num::Int, v::Float64) = ( w = clamp(convert(Int,round(v*(2^14-1))),0,2^14-1) ; Control14(0//1,1,num,w) )
Cn14(num::Int, v::Int) = Control14(0//1,1,num,clamp(v,0,2^14-1))

"""
	Pan()
	Pan(v::Float64)
	Pan(v::Int)

Channel pan `Atom`; use `Pan()` for center, `v` is the value ([-1,1] if Float, {0..2^14-1} if Int).
"""
Pan() = Ch14(10,2^13)
Pan(v::Float64) = ( v = clamp(v,-1,1) ; w = v==0 ? 2^13 : clamp(convert(Int,round(2^13*(1.0+v))),0,2^14-1) ;  Cn14(10,w) )
Pan(v::Int) = Cn14(10,v)

"channel sustain off `Atom`"
Sus0() = Cn7(64,0)

"channel sustain on `Atom`"
Sus1() = Cn7(64,127)

"all sounds off system message `Atom`"
SoundsOff() = Cn7(120,0)

"all contols reset system message `Atom`"
CntrlOff() = Cn7(121,0)

"all notes off system message `Atom`"
NotesOff() = Cn7(123,0)

"""
	Mark(v::AbstractString)

Track marker `Atom`; `v` is the marker name.
"""
Mark(v::AbstractString) = Marker(0//1,v)

"""
	Sig(num::Int=4, den::Int=3, clocks::Int=24, sub::Int=8)

Track time signature set `Atom`; \\
`num` is the numerator (defaults to 4), \\
`den` is the denumerator as a logarithm of 2 (defuaults to 3), \\
`clocks` is the divisions per quarter note (defualts to 24), \\
`sub` us the subdivisions of `clocks` (defualts to 8).
"""
Sig(num::Int=4, den::Int=3, clocks::Int=24, sub::Int=8) = TimeSignature(0//1,num,den,clocks,sub)

"""
	Tpo(bpm::Int)

Track tempo `Atom`; `bpm` is the beats per minute.
"""
Tpo(bpm::Int) = Tempo(0//1,bpm)

"""
	Inst(pg::Tuple{Int,Int}, [v::Float64])

Sets instrument to bank `pg[1]` program `pg[2]`. \\
If `v` is specified it also sets the volume to `v` which shoud be in the range `[0,1]`. \\
Named (bank,program) tuple constants can be found in the `SirenSeq.GenFluidSF144` and `SirenSeq.GenMuseScoreSF144` modules.
For example, `Instr(SirenSeq.GenMuseScoreSF144.iTubularBells,0.8)` selects the tubular-bells instrument from the general musescore 144 soundfont file and sets its volume to `0.8`.
"""
Inst(pg::Tuple{Int,Int}) = S(Bank(pg[1]),Prog(pg[2]))
Inst(pg::Tuple{Int,Int}, v::Float64) = S(Bank(pg[1]),Prog(pg[2]),Vol(v))

"""
	Da(v1::IR, v2::Float64, z) = D(v1,A(v2,z))

Dilate `z` by `v1` and accelirate it by `v2`.
"""
Da(v1::IR, v2::Float64, z) = D(v1,A(v2,z))

"""
	Con(z, b1::IR, b2::IR)
	Con(z, b2::IR) = Con(0,b2)
	Con(z) = Con(0,z.dur)

Contain `z`; this function makes sure that no events happen outside the time interval [`b1`,`b2`].
Events that cross this interval may be removed or shortened.
"""
Con(z) = ( x = atomOrExp(z); Con(x,0//1,x.dur) )
Con(z, b2::IR) = ( @assert b2>0 ; Con(z,0//1,b2) )
Con(z, b1::IR, b2::IR) = ( @assert b2>b1 ; contain(convert(Exp,atomOrExp(z)),Rat(b1),Rat(b2)) )

contain(x::Exp, b1::Rational{Int}, b2::Rational{Int}) = Exp(x.dur,atomsOp(a->contain(a,b1,b2),x))

function contain(a::Atom, b1::Rational{Int}, b2::Rational{Int})
	dur = a.dur ; ofs = a.ofs
	t1 = ofs ; t2 = ofs+dur
	if t1 < b1 ; dt = b1-t1 ; ofs += dt ; dur -= dt ; end
	if t2 > b2 ; dt = t2-b2 ; dur -= dt ; end
	if dur < 0 ; return Exp(0//1,Atom[]) ; end
	if dur == 0 && isa(a,Duratom) ; return Exp(0//1,Atom[]) ; end
	y = deepcopy(a) ; y.ofs=ofs ; y.dur=dur
	y
end

"""
	Kl(v::Rational{Int}, z)

Klip `z` by `v`.  The duration of all notes in `z` is multiplied by `v`.
A rest is inserted after each note where its length is `(1-v)` times the duration of the original note.
`0 < v < 1` must hold.
"""
Kl(v::Rational{Int}, z) = ( @assert 0<v<1 ; klip(v,atomOrExp(z)) )

klip(v::Rational{Int}, a::Note) = ( y = deepcopy(a) ; y.dur*=v ; Exp(a.dur,Atom[y]) )
klip(v::Rational{Int}, a::Atom) = a
klip(v::Rational{Int}, x::Exp) = Exp(x.dur,atomsOp(z->klip(v,z),x))

"""
	Spl(v::Int, z)

Split `z` `v` times.  Replaces each note in `z` by the same note repeated `v` times in the same time interval.
"""
Spl(v::Int, z) = ( @assert 1<v ; splitt(v,atomOrExp(x)) )

splitt(v::Int, a::Note) = ( y = deepcopy(a) ; y.dur/=v ; y.ofs=0//1 ; F(x.ofs,R(v,y)) )
splitt(v::Int, a::Atom) = a
splitt(v::Int, x::Exp) = atomsOp(z->splitt(v,z),x)


using ONNX, Flux, ProtoBuf
include("ops_tests.jl")

#Test Constant
main_test("$ONNX_TEST_PATH/test_constant", read_output("$ONNX_TEST_PATH/test_constant"))

#Test BatchNorm epsilon
main_test("$ONNX_TEST_PATH/test_batchnorm_epsilon", read_output("$ONNX_TEST_PATH/test_batchnorm_epsilon"),
    read_input("$ONNX_TEST_PATH/test_batchnorm_epsilon")[1] ,
    read_input("$ONNX_TEST_PATH/test_batchnorm_epsilon")[2],
    read_input("$ONNX_TEST_PATH/test_batchnorm_epsilon")[3],
    read_input("$ONNX_TEST_PATH/test_batchnorm_epsilon")[4],
    read_input("$ONNX_TEST_PATH/test_batchnorm_epsilon")[5])
"""
# Test BatchNorm example
main_test("$ONNX_TEST_PATH/test_batchnorm_example",
    read_output("$ONNX_TEST_PATH/test_batchnorm_example"),
    read_input("$ONNX_TEST_PATH/test_batchnorm_example")[1] ,
    read_input("$ONNX_TEST_PATH/test_batchnorm_example")[2],
    read_input("$ONNX_TEST_PATH/test_batchnorm_example")[3],
    read_input("$ONNX_TEST_PATH/test_batchnorm_example")[4],
    read_input("$ONNX_TEST_PATH/test_batchnorm_example")[5])
"""


function obimPravougaonika(a,b)
    return 2*a+2*b
end

function matrice(A,B)
    return A + B, A - B, A * B
end

a = 10
if a < 5
    print("oops")
    print("ppps")
else
    print("aahahahah")
end


function SeisProcess(in::AbstractString,out::AbstractString,operators,parameters;key=[])
    # Run processing flows that read and write from disk
    #
    # f is a function that has the following syntax: d2,h2 = f(d1,h1,param),
    # where
    # param is list of keyword arguments for the function.
    # note that f can be a vector of functions. They will be executed
    # sequentially on the same group of traces.
    #
    # get list of gather lengths
    ext = ReadTextHeader(in)
    nx = ext.n2*ext.n3*ext.n4*ext.n5
    filename_headers = ParseHeaderName(in)
    stream_h = open(filename_headers)
    curr = zeros(length(key),1)
    itrace = 1
    h1 = GrabHeader(stream_h,1)
    for ikey = 1 : length(key)
	curr[ikey] = getfield(h1,Symbol(key[ikey]))
    end
    prev = 1*curr
    L = []
    for j = 1 : nx
	h1 = GrabHeader(stream_h,j)
	for ikey = 1 : length(key)
	    curr[ikey] = getfield(h1,Symbol(key[ikey]))
	end
	if curr != prev
	    push!(L,j - itrace)
	    itrace = j
	end
	prev = 1*curr
    end
    push!(L,nx - itrace + 1)
    close(stream_h)

    itrace_in = 1
    itrace_out = 1
    for igather = 1 : length(L)
	d1,h1,e1 = SeisRead(in, group="some",itrace=itrace_in,ntrace=L[igather])
	num_traces_in = size(d1,2)
	for j = 1 : length(operators)
	    op = operators[j]
	    d2,h2 = op(d1,h1,e1;parameters[j]...)
	    d1 = copy(d2)
	    h1 = copy(h2)
	end
	num_traces_out = size(d1,2)
	SeisWrite(out,d1,h1,e1,itrace=itrace_out)
	itrace_in += num_traces_in
	itrace_out += num_traces_out
    end

end

function SeisProcess(in::Array{AbstractString,1}, out::Array{AbstractString,1}, operators,
                     parameters;key=[])

    for j = 1 : length(in)
	SeisProcess(in[j],out[j],operators,parameters;key=key)
    end

end

function SeisProcess(in1::AbstractString, in2::AbstractString, out::AbstractString, operators,
                     parameters; key=[])
    # Run processing flows that read 2 inputs and write 1 output
    #
    # f is a function that has the following syntax:
    # d3,h3 = f(d1,d2,h1,h2,param), where
    # param is list of keyword arguments for the function.
    # note that f can be a vector of functions.
    # They will be executed sequentially on the same group of traces.

    # get list of gather lengths
    ext = ReadTextHeader(in1)
    nx = ext.n2*ext.n3*ext.n4*ext.n5
    filename_headers = ParseHeaderName(in1)
    stream_h = open(filename_headers)
    curr = zeros(length(key),1)
    itrace = 1
    h1 = GrabHeader(stream_h,1)
    for ikey = 1 : length(key)
	curr[ikey] = getfield(h1,Symbol(key[ikey]))
    end
    prev = 1*curr
    L = []
    for j = 1 : nx
	h1 = GrabHeader(stream_h,j)
	for ikey = 1 : length(key)
	    curr[ikey] = getfield(h1,Symbol(key[ikey]))
	end
	if curr != prev
	    push!(L,j - itrace)
	    itrace = j
	end
	prev = 1*curr
    end
    push!(L,nx - itrace + 1)
    close(stream_h)

    itrace_in = 1
    itrace_out = 1
    for igather = 1 : length(L)
	d1,h1,e1 = SeisRead(in1,group="some",itrace=itrace_in,ntrace=L[igather])
	d2,h2,e2 = SeisRead(in2,group="some",itrace=itrace_in,ntrace=L[igather])
	num_traces_in = size(d1,2)
	for j = 1 : length(operators)
	    op = operators[j]
	    d3,h3 = op(d1,d2,h1,h2;parameters[j]...)
	    d1 = copy(d3)
	    h1 = copy(h3)
	end
	num_traces_out = size(d1,2)
	SeisWrite(out,d1,h1,e1,itrace=itrace_out)
	itrace_in += num_traces_in
	itrace_out += num_traces_out
    end

end


using IRTools: blocks, returnvalue, branches, isreturn, predecessors

isvar(v) = typeof(v) == Variable

# zt -- this is far too long for what it does
function returnvars(b)
  if isreturn(b)
    ret = returnvalue(b)
    if ret isa Variable
      return [ret]
    else
      return Variable[]
    end
  else
    return Variable[]
  end
end

"Vars that are used in branching in block `b`"
branchvars(b) =
  reduce(vcat, [filter(isvar, [br.args; br.condition]) for br in  branches(b)], init = Variable[])

stmtvars(s::Statement) =
  filter(isvar, s.expr.args)

"vars used in statements of `b`"
stmts(b) =
  reduce(vcat, [filter(isvar, b[v].expr.args) for v in keys(b)], init = Variable[])

"vars used in some form by block `b`"
usedvars(b) = stmts(b) ∪ branchvars(b) ∪ returnvars(b)

"All"
branchpreds(block) =
  filter(br -> br.block == block, branches(predecessors(block)))

"Variables that are produced by `b`: inputs or lhs of statements"
producedvars(b) = arguments(b) ∪ keys(b)

"Variables that are used in block `b` but neither defined in `b` nor inputs to `b`"
locallyundefinedvars(b) = setdiff(usedvars(b), producedvars(b))

"Repalce all occurances of `x` with `y` variable `v1` with variable `v2` in block `b`"
function varreplace!(b, v1, v2)
  IRTools.prewalk!(b) do x
    if x isa Variable && x == v1
      v2
    else
      x
    end
  end
end

"""
`passvars!(ir)`

Removes implicit use of variable.
Updates `ir` such that if a block uses some variable `v` then `v` is an input to that block.
"""
function passvars!(ir)
  blockidss_ = Set{Int}([b.id for b in IRTools.blocks(ir)]) ## zt: do we want to be putting ir into
  m = Dict{Variable, Set{Variable}}()  # Mapping from vars to equivalence class
  while !isempty(blockidss_)
    bid = pop!(blockidss_)
    b = IRTools.block(ir, bid)
    undefvars = locallyundefinedvars(b)

    # Nothing to do if no undefined vars, skip
    if !isempty(undefvars)
      newargs = Variable[]
      for v in undefvars
        # Is `v` truly undefined (locally), or is it equal to one of the inputs
        res = findfirst(arg -> arg in keys(m) && v in m[arg], arguments(b))
        # If so : add a new argument
        if isnothing(res)
          arg = argument!(b)      # add the enecessary inputs
          push!(newargs, v)
          # zt -- doing multiple passes, could do more efficiently with one
          varreplace!(b, v, arg)  #
          push!(get!(m, arg, Set{Variable}()), v)
        else
          varreplace!(b, v, arguments(b)[res])
        end
      end

      isempty(newargs) && continue
      # If we have added new arguments we need to update all the branch points that lead here
      for pa in predecessors(b)
        # For each block which branches to `b`, need to update the branch poins
        for br in branches(pa)
          if br.block == b.id
            offset = length(br.args) - length(newargs)
            for i = 1:length(newargs)
              br.args[i + offset] = newargs[i]
            end
            @assert !any(isnothing, br.args)
          end
        end
        push!(blockidss_, pa.id)    # Add to queue (since we've updated it and it may now have undefvars vars)
      end
    end
  end
  ir
end


#=
    Figure S3
=#

using Model # To get colours

using CSV
using ColorSchemes
using DataFrames
using DataFramesMeta
using Plots
using StatsPlots

include("../figure_defaults.jl")

##############################################################
## Data
##############################################################

data = @subset(CSV.read("Data/CSV/DualMarker.csv",DataFrame), :Temp .== 37.0, :Time .> 0.0)

##############################################################
## Univariate
##############################################################

T = sort(unique(data.Time))

plts_q = [plot() for t in T]
plts_u = [plot() for t in T]

for (i,t) in enumerate(T)

    @df @subset(data,:Time .== t,(!).(:Quenched)) density!(plts_q[i],:Signal_Cy5 / 1e4,c=col_Q,lw=2.0,frange=0.0,fα=0.4,label="Not quenched")
    @df @subset(data,:Time .== t,:Quenched) density!(plts_q[i],:Signal_Cy5 / 1e4,c=col_Q̄,lw=2.0,frange=0.0,fα=0.4,ls=:dash,label="Quenched")
    plot!(plts_q[i],xlim=(0.0,3.0),widen=:false,yticks=[],xlabel="Cy5 (10⁴)",axis=:x,title="$(Int(t)) min")

    @df @subset(data,:Time .== t,(!).(:Quenched)) density!(plts_u[i],:Signal_BDP,c=col_U,lw=2.0,frange=0.0,fα=0.4,label="Not quenched")
    @df @subset(data,:Time .== t,:Quenched) density!(plts_u[i],:Signal_BDP,c=col_Ū,lw=2.0,frange=0.0,fα=0.4,ls=:dash,label="Quenched")
    plot!(plts_u[i],xlim=(0.0,120.0),widen=:false,yticks=[],xlabel="BDP",axis=:x)

    if i > 1
        plot!(plts_q[i],legend=:none)
        plot!(plts_u[i],legend=:none)
    end

end

figS3 = plot(plts_q...,plts_u...,layout=grid(2,7),size=(800,250))

##############################################################
## Save...
##############################################################

savefig("$(@__DIR__)/FigS3.svg")


@testset "CSV loading" begin
    files = [refDataPath * "/refBatchDfCodes.csv", refDataPath * "/refParallelDfCodes.csv"]

    rows, cols = getCSVSize(files[1], header = true)
    @test rows == 10
    @test cols == 10

    data1 = loadCSV(files[1], header = true)
    data2 = loadCSV(files[2], header = true)
    @test typeof(data1) == Matrix{Float64}
    @test size(data1) == (10, 10)

    sizes = loadCSVSizes(files, header = true)
    @test sizes == [10, 10]

    W = addprocs(3)
    @everywhere using GigaSOM
    di = loadCSVSet(:csvTest, files, W, header = true)
    @test gather_array(di) == vcat(data1, data2)
    sizes = dmapreduce(di, size, vcat)
    dims = map(last, sizes)
    counts = map(first, sizes)
    @test all(dims .== 10)
    @test minimum(counts) + 1 >= maximum(counts)
    rmprocs(W)
end


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

__precompile__(true)

module BAT

using Base.Threads

import Base.Math.JuliaLibm

using Compat
using Compat.Markdown
using Compat: axes

using Clustering
using Colors
using Distributions
using DoubleDouble
using ElasticArrays
using FunctionWrappers
using IntervalSets
using MultiThreadingTools
using Parameters
using PDMats
using RecipesBase
using StatsBase

import RandomNumbers

include("logging.jl")
using BAT.Logging

include("shims.jl")
include("rng.jl")
include("distributions.jl")
include("util.jl")
include("execcontext.jl")
include("onlineuvstats.jl")
include("onlinemvstats.jl")
include("spatialvolume.jl")
include("parambounds.jl")
include("proposaldist.jl")
include("density.jl")
include("const_density.jl")
include("density_product.jl")
include("product_density.jl")
include("mvdist_density.jl")
include("algorithms.jl")
include("data_vector.jl")
include("density_sample.jl")
include("mcmc_algorithm.jl")
include("mcmc_sampleid.jl")
include("mcmc_stats.jl")
include("mcmc_convergence.jl")
include("mcmc_tuner.jl")
include("mh_sampler.jl")
include("mh_tuner.jl")
include("direct_sampler.jl")
include("mcmc_rand.jl")
include("plots_recipes.jl")

Logging.@enable_logging

end # module


function encode(msg::Vector{UInt8})
    tuptuo = [0x00]
    lastzero = 0x01
    for x in reverse(msg)
        if iszero(x)
            push!(tuptuo, lastzero)
            lastzero = 0x00
        else
            push!(tuptuo, x)
        end
        lastzero += 0x01
    end
    push!(tuptuo, lastzero)
    return reverse(tuptuo)
end
encode(x::UInt8) = encode([x])
encode(output, msg) = write(output, encode(msg)...)

next!(x) = read(x, UInt8)

function decode!(msg, input, n)
    code = next!(input)
    iszero(code) && return decode!(msg, input, n)
    i = 1
    while true
        code - 1 > n - i + 1 && return decode!(msg, input, n)
        for _ in 1:code - 1
            tmp = next!(input)
            iszero(tmp) && return decode!(msg, input, n)
            msg[i] = tmp
            i += 1
        end
        code = next!(input)
        if iszero(code)
            if i == n + 1
                return msg
            else
                return decode!(msg, input, n)
            end
        else
            if i > n
                return decode!(msg, input, n)
            else
                msg[i] = 0x00
                i += 1
            end
        end
    end
end


using Knet, CUDA

pd_knet(y::KnetArray, x::KnetArray, perm) = permutedims!(y,x,perm)
pd_cpux(y::KnetArray, x::KnetArray, perm) = copyto!(y, permutedims(Array(x),perm))
pd_cuxx(y::KnetArray, x::KnetArray, perm) = (permutedims!(cu(y),cu(x),perm); y)
pd_kern(y::KnetArray, x::KnetArray, perm) = nothing # call specific kernel
pd_tran(y::KnetArray, x::KnetArray, perm) = Knet._transpose!(y,x)

function CUDA.cu(x::KnetArray{T}) where {T}
    p = CUDA.CuPtr{T}(UInt(x.ptr.ptr))
    Base.unsafe_wrap(CuArray{T}, p, size(x); own=false)
end

# ## best not to use CUDA.jl memory manager
# function Knet.ka(x::CuArray{T,N}) where {T,N}
#     p = Base.bitcast(Knet.Cptr, x.buf.ptr)
#     k = Knet.KnetPtr(p, sizeof(x), gpu(), x)
#     # finalizer(identity, k) # hacky way to avoid gc? gives error in running finalizer
#     KnetArray{T,N}(k, size(x))
# end

function pd_time(f, x, p, a=nothing, n=max(10,div(1000000,length(x))))
    println(f,size(x),p,n)
    y = similar(x, size(x)[[p...]])
    f(y,x,p)
    if a != nothing; @assert y == a; end
    @time begin
        for i in 1:n
            f(y,x,p)
        end
        Knet.cudaDeviceSynchronize()
    end
    @time begin
        for i in 1:n
            f(y,x,p)
        end
        Knet.cudaDeviceSynchronize()
    end
    return y
end

@info "Simple transpose"
p = (2,1)
x = KnetArray(rand(Float32,100,100))
a = pd_time(pd_cpux, x, p)
pd_time(pd_tran, x, p, a)
pd_time(pd_knet, x, p, a) # confirm this uses transpose
pd_time(pd_cuxx, x, p, a)
#pd_time(pd_kern, x, p)
x = y = a = nothing; Knet.gc(); #Knet.gpuinfo()

@info "ILKER-DRAW"
p = (2,1,3)
x = KnetArray(rand(Float32,28,5,100))
a = pd_time(pd_cpux, x, p)
pd_time(pd_knet, x, p, a)
pd_time(pd_cuxx, x, p, a)
#pd_time(pd_kern, x, p, a)
x = y = a = nothing; Knet.gc(); #Knet.gpuinfo()

@info "ILKER-ATTN"
p = (2,1,3)
x = KnetArray(rand(Float32,196,512,32))
a = pd_time(pd_cpux, x, p)
pd_time(pd_knet, x, p, a)
pd_time(pd_cuxx, x, p, a)
#pd_time(pd_kern, x, p, a)
x = y = a = nothing; Knet.gc(); #Knet.gpuinfo()

@info "OSMAN-BERT-1"
p = (2,1,3)
x = KnetArray(rand(Float32,768,512,16))
a = pd_time(pd_cpux, x, p)
pd_time(pd_knet, x, p, a)
pd_time(pd_cuxx, x, p, a)
#pd_time(pd_kern, x, p, a)
x = a = nothing; Knet.gc(); #Knet.gpuinfo()

@info "OSMAN-BERT-2"
x = KnetArray(rand(Float32,64,512,12,16))
p = (1,3,2,4)
a = pd_time(pd_cpux, x, p)
pd_time(pd_knet, x, p, a)
pd_time(pd_cuxx, x, p, a)
#pd_time(pd_kern, x, p, a)
x = a = nothing; Knet.gc(); #Knet.gpuinfo()

nothing


function update!(sim::Simulation{T, M},
                 event::Event{T},
                 transmission::Transmission) where {T <: DiseaseStateSequence, M <: ILM}
  if _time(event) < sim.time
    error("Time of a new event must be >= that of the previous event")
  elseif _time(event) < Inf
    update!(sim.events, event)
    update!(sim.transmission_network, transmission)
    update!(sim.disease_states, event)
    update!(sim.transmission_rates,
            sim.event_rates,
            event,
            sim.disease_states,
            sim.population,
            sim.risk_functions,
            sim.risk_parameters)
    # Count the iteration as having occurred
    sim.iterations += 1
  end
  # Update simulation time
  sim.time = _time(event)
  return sim
end


get_free_memory_size(model::TierraModel) = model.memory_size - model.used_memory
normalize_address(x) = mod(x, 2^16)

function read_memory(model::TierraModel, address)
    model.memory[mod(address, model.memory_size)+one(UInt16)]
end
function read_memory(model::TierraModel, address, length)
    return [
        read_memory(model, curr_addr) for
        curr_addr = address:(address+length-one(UInt16))
    ]
end
function write_memory(
    model::TierraModel,
    organism::TierrianOrganism,
    address,
    instruction::UInt8,
)
    if organism_has_access(organism, address, 1)
        if MUTATE && rand() < FLAW_PROBABILITY
            instruction_to_write = get_mutated_instruction(instruction)
        else
            instruction_to_write = instruction
        end

        model.memory[mod(address, model.memory_size)+one(UInt16)] = instruction_to_write
    else # organism has no write access to this address
        organism.error_flag = true
    end
end
function write_memory(
    model::TierraModel,
    organism::TierrianOrganism,
    start_address,
    instructions::Vector{UInt8},
)
    for (i, instrucion) in enumerate(instructions)
        write_memory(model, organism, start_address + i - 1, instrucion)
    end
end

function organism_has_access(organism::TierrianOrganism, address, length)
    if _is_within_range(
        address,
        length,
        organism.start_address,
        organism.length,
    )
        true
    elseif organism.has_daughter && _is_within_range(
        address,
        length,
        organism.daughter_address,
        organism.daughter_length,
    )
        true
    else
        false
    end
end

function clear_memory!(model::TierraModel, start_address, length)
    length = min(length, model.memory_size - start_address)

    # first, we test if there are existing overlapping blocks

    for i = 1:Base.length(model.free_blocks)
        free_block = model.free_blocks[i]

        overlap = _determine_overlap(
            start_address,
            length,
            free_block.start_address,
            free_block.length,
        )

        if overlap in [ExactMatch(), Within(), WithinFromStart(), WithinToEnd()]
            return 0

        elseif overlap == LeftOverlap()
            return clear_memory!(
                model,
                start_address,
                free_block.start_address - start_address,
            )

        elseif overlap == RightOverlap()
            return clear_memory!(
                model,
                free_block.start_address + free_block.length,
                start_address + length - free_block.start_address -
                free_block.length,
            )

        elseif overlap == CompleteOverlap()
            return sum([
                clear_memory!(
                    model,
                    start_address,
                    free_block.start_address - start_address,
                ),
                clear_memory!(
                    model,
                    free_block.start_address + free_block.length,
                    start_address + length - free_block.start_address -
                    free_block.length,
                ),
            ])
        end
    end

    # if there is no overlapping block, we serach for potential neighboring free blocks to merge

    index_left_neighbor = -1
    index_right_neighbor = -1

    for i = 1:Base.length(model.free_blocks)
        free_block = model.free_blocks[i]

        overlap = _determine_overlap(
            start_address,
            length,
            free_block.start_address,
            free_block.length,
        )

        if overlap == LeftNeighbor()
            @assert index_left_neighbor == -1
            index_left_neighbor = i
        elseif overlap == RightNeighbor()
            @assert index_right_neighbor == -1
            index_right_neighbor = i
        end
    end

    if index_left_neighbor != -1 && index_right_neighbor == -1
        free_block = model.free_blocks[index_left_neighbor]

        model.free_blocks[index_left_neighbor] =
            FreeMemoryBlock(start_address, free_block.length + length)
        model.used_memory -= length

        return length

    elseif index_left_neighbor == -1 && index_right_neighbor != -1
        free_block = model.free_blocks[index_right_neighbor]

        model.free_blocks[index_right_neighbor] = FreeMemoryBlock(
            free_block.start_address,
            free_block.length + length,
        )
        model.used_memory -= length

        return length

    elseif index_left_neighbor != -1 && index_right_neighbor != -1
        free_block_right = model.free_blocks[index_left_neighbor] # the free block where the block to clear is the left neighbor is its right neighbor!!
        free_block_left = model.free_blocks[index_right_neighbor] # TODO

        model.free_blocks[index_left_neighbor] = FreeMemoryBlock(
            free_block_left.start_address,
            free_block_left.length + free_block_right.length + length,
        )
        deleteat!(model.free_blocks, index_right_neighbor)

        model.used_memory -= length

        return length
    end

    # no overlapping or neighboring free memory blocks => create a new one

    push!(model.free_blocks, FreeMemoryBlock(start_address, length))
    model.used_memory -= length
    return length
end

function allocate_free_memory!(model::TierraModel, length)
    if get_free_memory_size(model) < length # no memory left
        return -1
    end

    for _ = 1:100
        block_index = rand(1:Base.length(model.free_blocks))
        block = model.free_blocks[block_index]

        if length <= block.length
            alloc_start, new_free_block =
                _allocate_in_block!(model, block, length)

            if new_free_block === nothing || new_free_block.length == 0
                deleteat!(model.free_blocks, block_index)
            else
                model.free_blocks[block_index] = new_free_block
            end

            model.used_memory += length

            return alloc_start
        end
    end

    return -1
end

function _allocate_in_block!(model::TierraModel, block::FreeMemoryBlock, length)
    if block.length == length
        return block.start_address, nothing
    end

    block_start = block.start_address

    if rand(["beginning", "end"]) == "beginning"
        alloc_start = block_start
        alloc_end = block_start + length
        new_free_block = FreeMemoryBlock(alloc_end, block.length - length)
    else
        alloc_start = block_start + block.length - length
        new_free_block = FreeMemoryBlock(block_start, block.length - length)
    end

    return UInt16(alloc_start), new_free_block
end


using Random

abstract type AbstractEnv end
# もしくは `using ReinforcementLearningBase` するか (要検討)
abstract type Node end

mutable struct InputNode{T<:Number} <: Node
    id::Int
    state::T
    targets::Array{Int, 1}
    weights::Array{T, 1}
end

mutable struct MiddleNode{T<:Number} <: Node
    id::Int
    state::T
    targets::Array{Int, 1}
    weights::Array{T, 1}
    bias::T
    activation_function::Function
end

mutable struct OutputNode{T<:Number} <: Node
    id::Int
    state::T
    bias::T
    activation_function::Function
end

mutable struct Model{T<:Number}
    nodes::Array{Node, 1}
    input_size::Int
    output_size::Int
    nodes_num::Int
    weights_num::Int
    score::T
end

# モデルを作る
function Model(input_size::Int, output_size::Int; T=Float64, rate=0.5, rng=MersenneTwister())
    nodes = Array{Node, 1}()
    nodes_num = input_size + output_size
    weights_num = 0

    # InputNodeの設定
    for i in 1:input_size
        # 出力へつなげたり繋げなかったりする
        targets = Array{Int, 1}()
        for j in (input_size+1):nodes_num
            if rand(rng) < rate
                push!(targets, j)
                weights_num += 1
            end
        end
        node = InputNode(i, T(0), targets, 2rand(rng, T, length(targets)).-1)
        push!(nodes, node)
    end

    # OutputNodeの設定
    for i in (input_size+1):nodes_num
        node = OutputNode(i, T(0), 2rand(T)-1, x -> x)
        push!(nodes, node)
    end

    return Model{T}(nodes, input_size, output_size, nodes_num, weights_num, T(0))
end

Model{T}(input_size, output_size; kwargs...) where {T} = Model(input_size, output_size; T=T, kwargs...)

# InputNode以外の全ノードのstateをbiasにする
function reset!(model::Model)
    for i in (model.input_size+1):length(model.nodes)
        model.nodes[i].state = model.nodes[i].bias
    end
    nothing
end

# モデルに入力を入れて計算する
function (model::Model{T})(input::Array{T, 1}) where T <: Number
    reset!(model)
    middlenode_ids = Array{Int, 1}()

    # InputNodeからの出力を計算
    for i in 1:model.input_size
        model.nodes[i].state = input[i]
        for j in 1:length(model.nodes[i].targets)
            target = model.nodes[i].targets[j]
            model.nodes[target].state += model.nodes[i].weights[j] * model.nodes[i].state
            if typeof(model.nodes[j]) <: MiddleNode
                push!(target, middlenode_ids)
            end
        end
    end

    # MiddleNodeからの出力を計算
    while length(middlenode_ids) > 0
        id = popfirst!(middlenode_ids)
        for j in 1:length(model.nodes[id].targets)
            target = model.nodes[i].targets[j]
            model.nodes[target].state += model.nodes[id].activation_function(model.nodes[id].weights[j] * model.nodes[id].state)
        end
        if typeof(model.nodes[j]) == MiddleNode
            push!(j, middlenode_ids)
        end
    end

    return [model.nodes[i].activation_function(model.nodes[i].state) for i in (model.input_size+1):(model.input_size+model.output_size)]
end

mutable struct Species
    representative_id::Int
    nodes_num::Int
    weights_num::Int
    model_ids::Array{Int, 1}
end

mutable struct Population{T<:Number}
    models::Array{Model{T}, 1}
    species_set::Array{Species, 1}
    species_num::Int
    generation::Int
end

function node_distance(model1::Model, model2::Model; compatibility_weight_coefficient=0.5, compatibility_disjoint_coefficient=0.5)
    disjoint_nodes = abs(model1.nodes_num - model2.nodes_num) * compatibility_disjoint_coefficient

    bias_distance = 0.0
    for node1 in model1.nodes
        if typeof(node1) <: InputNode
            continue
        end
        for node2 in model2.nodes
            if typeof(node2) <: InputNode
                continue
            end
            if node1.id == node2.id
                bias_distance += abs(node1.bias - node2.bias)
                break
            end
        end
    end
    bias_distance *= compatibility_weight_coefficient

    return (disjoint_nodes+bias_distance)/max(model1.nodes_num, model2.nodes_num)
end

function connection_distance(model1::Model, model2::Model; compatibility_weight_coefficient=0.5, compatibility_disjoint_coefficient=0.5)
    disjoint_connections = abs(model1.weights_num - model2.weights_num) * compatibility_disjoint_coefficient

    # nodeの数の6乗ぐらいの計算量のクソアルゴリズムなので改善したい
    weight_distance = 0.0
    for node1 in model1.nodes
        if typeof(node1) <: OutputNode
            continue
        end
        for node2 in model2.nodes
            if typeof(node2) <: OutputNode
                continue
            end
            if node1.id == node2.id

                for i in 1:length(node1.targets)
                    target1 = node1.targets[i]
                    for j in 1:length(node2.targets)
                        target2 = node2.targets[j]
                        if target1 == target2
                            weight_distance += abs(node1.weights[i] - node2.weights[j])
                            break
                        end
                    end
                end
                break

            end
        end
    end
    weight_distance *= compatibility_weight_coefficient

    return (disjoint_connections + weight_distance) / max(model1.weights_num, model2.weights_num)
end

# 種を分ける (最初の世代の場合)
function Population(models::Array{Model{T}, 1}; compatibility_threshold=3.0, compatibility_disjoint_coefficient=0.5, compatibility_weight_coefficient=0.5) where T
    species_set_toReturn = [Species(1, models[1].nodes_num, models[1].weights_num, [1])]
    species_set_toExamine = copy(species_set_toReturn)
    unexamined = [i for i in 2:length(models)]

    while length(unexamined) > 0
        current_species = pop!(species_set_toExamine)
        for i in copy(unexamined)
            distance = node_distance(models[current_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)
                        + connection_distance(models[current_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)

            if (distance > compatibility_threshold && length(species_set_toExamine) == 0)
                new_species = Species(i, models[i].nodes_num, models[i].weights_num, [i])
                push!(species_set_toReturn, new_species)
                push!(species_set_toExamine, new_species)
                deleteat!(unexamined, findfirst(x->x==i, unexamined))
            elseif (distance <= compatibility_threshold)
                push!(species_set_toReturn[end].model_ids, i)
                deleteat!(unexamined, findfirst(x->x==i, unexamined))
            end
        end
    end

    return Population{T}(models, species_set_toReturn, length(species_set_toReturn), 1)
end

# 種を分ける（最初の世代じゃない場合）
function Population(models::Array{Model{T}, 1}, previous_population::Population;
    compatibility_threshold=3.0, compatibility_disjoint_coefficient=1.0, compatibility_weight_coefficient=0.5) where T

    species_set_toReturn = Array{Species, 1}()
    previous_species_set_toExamine = copy(previous_population.species_set)
    unexamined = [i for i in 1:length(models)]

    # 前の世代の種に含まれるかどうか調査する
    for current_species in previous_species_set_toExamine
        isnew = true

        for i in unexamined
            distance = node_distance(previous_population.models[current_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)
                        + connection_distance(previous_population.models[current_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)

            if (distance <= compatibility_threshold && isnew)
                new_species = Species(i, models[i].nodes_num, models[i].weights_num, [i])
                push!(species_set_toReturn, new_species)
                deleteat!(unexamined, findfirst(x->x==i, unexamined))
                isnew = false
            elseif (distance <= compatibility_threshold)
                push!(species_set_toReturn[end].model_ids, i)
                deleteat!(unexamined, findfirst(x->x==i, unexamined))
            end
        end
    end

    # 新種を分ける
    species_set_toExamine = Array{Species, 1}()
    while length(unexamined) > 0
        new_species = Species(unexamined[1], models[unexamined[1]].nodes_num, models[unexamined[1]].weights_num, [unexamined[1]])
        popfirst!(unexamined)

        for i in unexamined
            distance = node_distance(models[new_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)
                        + connection_distance(models[new_species.representative_id], models[i],
                            compatibility_disjoint_coefficient=compatibility_disjoint_coefficient,
                            compatibility_weight_coefficient=compatibility_weight_coefficient)

            if (distance <= compatibility_threshold)
                push!(new_species.model_ids, i)
                deleteat!(unexamined, findfirst(x->x==i, unexamined))
            end
        end
        push!(species_set_toReturn, new_species)
    end

    return Population{T}(models, species_set_toReturn, length(species_set_toReturn), previous_population.generation+1)
end

# 成績を評価
function evaluate!(model::Model, env::AbstractEnv)
    while !get_terminal(env)
        env(model(get_state(env)))
        model.score += get_reward(env)
    end
    nothing
end

function evaluate!(popu::Population, env::AbstractEnv)
    bestmodel_id = popu.model_ids[1]

    for model in popu.models
        evaluate!(model, env)
        if popu.models[bestmodel_id].score < model.score
            bestmodel_id = model.id
        end
    end

    return bestmodel_id
end

function crossover(parent1::Model, parent2::Model)
    child = parent1.nodes_num > parent2.nodes_num ? parent1 : parent2
    small_parent = parent1.nodes_num < parent2.nodes_num ? parent1 : parent2

    # 二つの親の InputNode に共通している weight をランダムに選択する
    for i in 1:child.input_size
        weight_ind = findall(x -> (small_parent.nodes[i].targets[x] ∈ child.nodes[i].targets), 1:length(small_parent.nodes[i].targets))
        for j in weight_ind
            if rand() < 0.5
                child.nodes[i].weights[j] = parent1.nodes[i].weights[j]
            else
                child.nodes[i].weights[j] = parent2.nodes[i].weights[j]
            end
        end
    end

    # 二つの親の MiddleNode に共通している weight をランダムに選択する
    for i in (child.input_size+child.output_size+1):child.nodes_num
        weight_ind = findall(x -> (small_parent.nodes[i].targets[x] ∈ child.nodes[i].targets), 1:length(small_parent.nodes[i].targets))
        for j in weight_ind
            if rand() < 0.5
                child.nodes[i].weights[j] = parent1.nodes[i].weights[j]
            else
                child.nodes[i].weights[j] = parent2.nodes[i].weights[j]
            end
        end
    end

    # 小さい parent の InputNode にしかない weight を追加する
    for i in 1:child.input_size
        adding_target = findall(x -> !(x ∈ child.nodes[i].targets), small_parent.nodes[i].targets)
        weight_ind = findall(x -> !(small_parent.nodes[i].targets[x] ∈ child.nodes[i].targets), 1:length(small_parent.nodes[i].targets))
        append!(child.nodes[i].targets, adding_target)
        append!(child.nodes[i].weight, small_parent.nodes[i].weight[weight_ind])
    end

    # 小さい parent の MiddleNode にしかない weight を追加する
    for i in (child.input_size+child.output_size+1):child.nodes_num
        adding_target = findall(x -> !(x ∈ child.nodes[i].targets), small_parent.nodes[i].targets)
        weight_ind = findall(x -> !(small_parent.nodes[i].targets[x] ∈ child.nodes[i].targets), 1:length(small_parent.nodes[i].targets))
        append!(child.nodes[i].targets, adding_target)
        append!(child.nodes[i].weight, small_parent.nodes[i].weight[weight_ind])
    end

    return child
end


# *********************************************************************************
# REopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# *********************************************************************************
# https://discourse.julialang.org/t/vector-of-matrices-vs-multidimensional-arrays/9602/5
# 5d2360465457a3f77ddc131e has TOU demand
# 59bc22705457a3372642da67 has monthly tiered demand (no TOU demand)

"""
    Base.@kwdef struct URDBrate

Contains some of the data for ElectricTariff
"""
struct URDBrate
    energy_rates::Array{Float64,2}  # time X tier
    energy_tier_limits::Array{Real,1}
    n_energy_tiers::Int

    n_monthly_demand_tiers::Int
    monthly_demand_tier_limits::Array{Real,1}
    monthly_demand_rates::Array{Float64,2}  # month X tier

    n_tou_demand_tiers::Int
    tou_demand_tier_limits::Array{Real,1}
    tou_demand_rates::Array{Float64,2}  # ratchet X tier
    tou_demand_ratchet_time_steps::Array{Array{Int64,1},1}  # length = n_tou_demand_ratchets

    demand_lookback_months::AbstractArray{Int,1}
    demand_lookback_percent::Float64
    demand_lookback_range::Int

    fixed_monthly_charge::Float64
    annual_min_charge::Float64
    min_monthly_charge::Float64

    sell_rates::Array{Float64,2}  # time X tier
end

"""
    URDBrate(urdb_label::String, year::Int=2019; time_steps_per_hour=1)

download URDB dict, parse into reopt inputs, return URDBrate struct.
    year is required to align weekday/weekend schedules.
"""
function URDBrate(urdb_label::String, year::Int=2019; time_steps_per_hour=1)
    urdb_response = download_urdb(urdb_label)
    URDBrate(urdb_response, year; time_steps_per_hour=time_steps_per_hour)
end

"""
    URDBrate(util_name::String, rate_name::String, year::Int=2019; time_steps_per_hour=1)

download URDB dict, parse into reopt inputs, return URDBrate struct.
    year is required to align weekday/weekend schedules.
"""
function URDBrate(util_name::String, rate_name::String, year::Int=2019; time_steps_per_hour=1)
    urdb_response = download_urdb(util_name, rate_name)
    URDBrate(urdb_response, year; time_steps_per_hour=time_steps_per_hour)
end

"""
    URDBrate(urdb_response::Dict, year::Int)

process URDB response dict, parse into reopt inputs, return URDBrate struct.
    year is required to align weekday/weekend schedules.
"""
function URDBrate(urdb_response::Dict, year::Int=2019; time_steps_per_hour=1)

    demand_min = get(urdb_response, "peakkwcapacitymin", 0.0)  # TODO add check for site min demand against tariff?

    n_monthly_demand_tiers, monthly_demand_tier_limits, monthly_demand_rates,
      n_tou_demand_tiers, tou_demand_tier_limits, tou_demand_rates, tou_demand_ratchet_time_steps =
      parse_demand_rates(urdb_response, year)

    energy_rates, energy_tier_limits, n_energy_tiers, sell_rates =
        parse_urdb_energy_costs(urdb_response, year; time_steps_per_hour=time_steps_per_hour)

    fixed_monthly_charge, annual_min_charge, min_monthly_charge = parse_urdb_fixed_charges(urdb_response)

    demand_lookback_months, demand_lookback_percent, demand_lookback_range = parse_urdb_lookback_charges(urdb_response)

    URDBrate(
        energy_rates,
        energy_tier_limits,
        n_energy_tiers,

        n_monthly_demand_tiers,
        monthly_demand_tier_limits,
        monthly_demand_rates,

        n_tou_demand_tiers,
        tou_demand_tier_limits,
        tou_demand_rates,
        tou_demand_ratchet_time_steps,

        demand_lookback_months,
        demand_lookback_percent,
        demand_lookback_range,

        fixed_monthly_charge,
        annual_min_charge,
        min_monthly_charge,

        sell_rates
    )
end

function download_urdb(urdb_label::String; version::Int=8)
    url = string("https://api.openei.org/utility_rates", "?api_key=", urdb_key,
                "&version=", version , "&format=json", "&detail=full",
                "&getpage=", urdb_label
    )
    response = nothing
    try
        @info "Checking URDB for " urdb_label
        r = HTTP.get(url, require_ssl_verification=false)  # cannot verify on NREL VPN
        response = JSON.parse(String(r.body))
        if r.status != 200
            error("Bad response from URDB: $(response["errors"])")  # TODO URDB has "errors"?
        end
    catch e
        error("Error occurred :$(e)")
    end

    rates = response["items"]  # response['items'] contains a vector of dicts
    if length(rates) == 0
        error("Could not find $(urdb_label) in URDB.")
    end
    if rates[1]["label"] == urdb_label
        return rates[1]
    else
        error("Could not find $(urdb_label) in URDB.")
    end
end

function download_urdb(util_name::String, rate_name::String; version::Int=8)
    url = string("https://api.openei.org/utility_rates", "?api_key=", urdb_key,
                "&version=", version , "&format=json", "&detail=full",
                "&ratesforutility=", replace(util_name, "&" => "%26")
    )
    response = nothing
    try
        @info "Checking URDB for " rate_name
        r = HTTP.get(url, require_ssl_verification=false)  # cannot verify on NREL VPN
        response = JSON.parse(String(r.body))
        if r.status != 200
            error("Bad response from URDB: $(response["errors"])")  # TODO URDB has "errors"?
        end
    catch e
        error("Error occurred :$(e)")
    end

    rates = response["items"]  # response['items'] contains a vector of dicts
    if length(rates) == 0
        error("Could not find $(rate_name) in URDB.")
    end

    matched_rates = []
    start_dates = []

    for rate in rates
        if contains(rate_name, rate["name"])
            push!(matched_rates, rate)  # urdb can contain multiple rates of same name
            if "startdate" in keys(rate)
                push!(start_dates, rate["startdate"])
            end
        end
    end

    # find the newest rate of those that match the rate_name
    newest_index = 1  # covers the case where one rate is returned without a "startdate"
    if length(start_dates) > 1 && length(start_dates) == length(matched_rates)
        _, newest_index = findmax(start_dates)
    end

    if length(matched_rates) == 0
        error("Could not find $(rate_name) in URDB.")
    end

    return matched_rates[newest_index]
end

"""
    parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e8)

use URDB dict to return rates, energy_cost_vector, energy_tier_limits_kwh where:
    - rates is vector summary of rates within URDB (used for average rates when necessary)
    - energy_cost_vector is a vector of vectors with inner vectors for each energy rate tier,
        inner vectors are costs in each time step
    - energy_tier_limits_kwh is a vector of upper kWh limits for each energy tier
"""
function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e8)
    if length(d["energyratestructure"]) == 0
        error("No energyratestructure in URDB response.")
    end
    # TODO check bigM (in multiple functions)
    energy_tiers = Float64[]
    for energy_rate in d["energyratestructure"]
        append!(energy_tiers, length(energy_rate))
    end
    energy_tier_set = Set(energy_tiers)
    if length(energy_tier_set) > 1
        @warn "energy periods contain different numbers of tiers, using limits of period with most tiers"
    end
    period_with_max_tiers = findall(energy_tiers .== maximum(energy_tiers))[1]
    n_energy_tiers = Int(maximum(energy_tier_set))

    rates = Float64[]
    energy_tier_limits_kwh = Float64[]
    non_kwh_units = false

    for energy_tier in d["energyratestructure"][period_with_max_tiers]
        # energy_tier is a dictionary, eg. {'max': 1000, 'rate': 0.07531, 'adj': 0.0119, 'unit': 'kWh'}
        energy_tier_max = get(energy_tier, "max", bigM)

        if "rate" in keys(energy_tier) || "adj" in keys(energy_tier)  || "sell" in keys(energy_tier)
            append!(energy_tier_limits_kwh, energy_tier_max)
        end

        if "unit" in keys(energy_tier) && string(energy_tier["unit"]) != "kWh"
            @warn "Using average rate in tier due to exotic units of " energy_tier["unit"]
            non_kwh_units = true
        end

        append!(rates, get(energy_tier, "rate", 0) + get(energy_tier, "adj", 0))
    end

    if non_kwh_units
        rate_average = sum(rates) / maximum([length(rates), 1])
        n_energy_tiers = 1
        energy_tier_limits_kwh = Float64[bigM]
    end

    energy_cost_vector = Float64[]
    sell_vector = Float64[]

    for tier in range(1, stop=n_energy_tiers)

        for month in range(1, stop=12)
            n_days = daysinmonth(Date(string(year) * "-" * string(month)))

            for day in range(1, stop=n_days)

                for hour in range(1, stop=24)

                    # NOTE: periods are zero indexed
                    if dayofweek(Date(year, month, day)) < 6  # Monday == 1
                        period = d["energyweekdayschedule"][month][hour] + 1
                    else
                        period = d["energyweekendschedule"][month][hour] + 1
                    end
                    # workaround for cases where there are different numbers of tiers in periods
                    n_tiers_in_period = length(d["energyratestructure"][period])
                    if n_tiers_in_period == 1
                        tier_use = 1
                    elseif tier > n_tiers_in_period
                        tier_use = n_tiers_in_period
                    else
                        tier_use = tier
                    end
                    if non_kwh_units
                        rate = rate_average
                    else
                        rate = get(d["energyratestructure"][period][tier_use], "rate", 0)
                    end
                    total_rate = rate + get(d["energyratestructure"][period][tier_use], "adj", 0)
                    sell = get(d["energyratestructure"][period][tier_use], "sell", 0)

                    for step in range(1, stop=time_steps_per_hour)  # repeat hourly rates intrahour
                        append!(energy_cost_vector, round(total_rate, digits=6))
                        append!(sell_vector, round(-sell, digits=6))
                    end
                end
            end
        end
    end
    energy_rates = reshape(energy_cost_vector, (:, n_energy_tiers))
    sell_rates = reshape(sell_vector, (:, n_energy_tiers))
    return energy_rates, energy_tier_limits_kwh, n_energy_tiers, sell_rates
end

"""
    parse_demand_rates(d::Dict, year::Int; bigM=1.0e8)

Parse monthly ("flat") and TOU demand rates
    can modify URDB dict when there is inconsistent numbers of tiers in rate structures
"""
function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8)

    if haskey(d, "flatdemandstructure")
        scrub_urdb_demand_tiers!(d["flatdemandstructure"])
        monthly_demand_tier_limits = parse_urdb_demand_tiers(d["flatdemandstructure"])
        n_monthly_demand_tiers = length(monthly_demand_tier_limits)
        monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers)
    else
        monthly_demand_tier_limits = []
        n_monthly_demand_tiers = 1
        monthly_demand_rates = Array{Float64,2}(undef, 0, 0)
    end

    if haskey(d, "demandratestructure")
        scrub_urdb_demand_tiers!(d["demandratestructure"])
        tou_demand_tier_limits = parse_urdb_demand_tiers(d["demandratestructure"])
        n_tou_demand_tiers = length(tou_demand_tier_limits)
        ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers)
    else
        tou_demand_tier_limits = []
        n_tou_demand_tiers = 0
        ratchet_time_steps = []
        tou_demand_rates = Array{Float64,2}(undef, 0, 0)
    end

    return n_monthly_demand_tiers, monthly_demand_tier_limits, monthly_demand_rates,
           n_tou_demand_tiers, tou_demand_tier_limits, tou_demand_rates, ratchet_time_steps

end

"""
    scrub_urdb_demand_tiers!(A::Array)

validate flatdemandstructure and demandratestructure have equal number of tiers across periods
"""
function scrub_urdb_demand_tiers!(A::Array)
    if length(A) == 0
        return
    end
    len_tiers = Int[length(r) for r in A]
    len_tiers_set = Set(len_tiers)
    n_tiers = maximum(len_tiers_set)

    if length(len_tiers_set) > 1
        @warn """Demand rate structure has varying number of tiers in periods.
                 Making the number of tiers the same across all periods by repeating the last tier."""
        for (i, rate) in enumerate(A)
            n_tiers_in_period = length(rate)
            if n_tiers_in_period != n_tiers
                rate_new = rate
                last_tier = rate[n_tiers_in_period]
                for j in range(1, stop=n_tiers - n_tiers_in_period)
                    append!(rate_new, last_tier)
                end
                A[i] = rate_new
            end
        end
    end
end

"""
    parse_urdb_demand_tiers(A::Array; bigM=1.0e8)

set up and validate demand tiers
    returns demand_tiers::Array{Float64, n_tiers}
"""
function parse_urdb_demand_tiers(A::Array; bigM=1.0e8)
    if length(A) == 0
        return []
    end
    len_tiers = Int[length(r) for r in A]
    n_tiers = maximum(len_tiers)
    period_with_max_tiers = findall(len_tiers .== maximum(len_tiers))[1]

    # set up tiers and validate that the highest tier has the same value across periods
    demand_tiers = Dict()
    demand_maxes = Float64[]
    for period in range(1, stop=length(A))
        demand_max = Float64[]
        for tier in A[period]
            append!(demand_max, get(tier, "max", bigM))
        end
        demand_tiers[period] = demand_max
        append!(demand_maxes, demand_max[end])  # TODO should this be maximum(demand_max)?
    end

    # test if the highest tier is the same across all periods
    if length(Set(demand_maxes)) > 1
        @warn "Highest demand tiers do not match across periods: using max tier from largest set of tiers."
    end
    return demand_tiers[period_with_max_tiers]
end

"""
    parse_urdb_monthly_demand(d::Dict)

return monthly demand rates as array{month, tier}
"""
function parse_urdb_monthly_demand(d::Dict, n_tiers)
    if !haskey(d, "flatdemandmonths")
        return []
    end
    if length(d["flatdemandmonths"]) == 0
        return []
    end

    demand_rates = zeros(12, n_tiers)  # array(month, tier)
    for month in range(1, stop=12)
        period = d["flatdemandmonths"][month] + 1  # URDB uses zero-indexing
        rates = d["flatdemandstructure"][period]  # vector of dicts

        for (t, tier) in enumerate(rates)
            demand_rates[month, t] = round(get(tier, "rate", 0.0) + get(tier, "adj", 0.0), digits=6)
        end
    end
    return demand_rates
end

"""
    parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int)

return array of arrary for ratchet time steps, tou demand rates array{ratchet, tier}
"""
function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int)
    if !haskey(d, "demandratestructure")
        return [], []
    end
    n_periods = length(d["demandratestructure"])
    ratchet_time_steps = Array[]
    rates_vec = Float64[]  # array(ratchet_num, tier), reshape later
    n_ratchets = 0  # counter

    for month in range(1, stop=12)
        for period in range(0, stop=n_periods)
            time_steps = get_tou_demand_steps(d, year=year, month=month, period=period-1)
            if length(time_steps) > 0  # can be zero! not every month contains same number of periods
                n_ratchets += 1
                append!(ratchet_time_steps, [time_steps])
                for (t, tier) in enumerate(d["demandratestructure"][period])
                    append!(rates_vec, round(get(tier, "rate", 0.0) + get(tier, "adj", 0.0), digits=6))
                end
            end
        end
    end
    rates = reshape(rates_vec, (:, n_tiers))  # Array{Float64,2}
    ratchet_time_steps = convert(Array{Array{Int64,1},1}, ratchet_time_steps)
    return ratchet_time_steps, rates
end

"""
    get_tou_demand_steps(d::Dict; year::Int, month::Int, period::Int, time_steps_per_hour=1)

return Array{Int, 1} for time_steps in ratchet (aka period)
"""
function get_tou_demand_steps(d::Dict; year::Int, month::Int, period::Int, time_steps_per_hour=1)
    step_array = Int[]
    start_step = 1
    start_hour = 1

    if month > 1
        plus_days = 0
        for m in range(1, stop=month-1)
            plus_days += daysinmonth(Date(string(year) * "-" * string(m)))
            if m == 2 && isleapyear(year)
                plus_days -= 1
            end
        end
        start_hour += plus_days * 24
        start_step = start_hour * time_steps_per_hour
    end

    hour_of_year = start_hour
    step_of_year = start_step

    for day in range(1, stop=daysinmonth(Date(string(year) * "-" * string(month))))
        for hour in range(1, stop=24)
            if dayofweek(Date(year, month, day)) < 6 &&
               d["demandweekdayschedule"][month][hour] == period
                append!(step_array, step_of_year)
            elseif dayofweek(Date(year, month, day)) > 5 &&
               d["demandweekendschedule"][month][hour] == period
                append!(step_array, step_of_year)
            end
            step_of_year += 1
        end
        hour_of_year += 1
    end
    return step_array
end

"""
    parse_urdb_fixed_charges(d::Dict)

return fixed_monthly, annual_min, min_monthly :: Float64
"""
function parse_urdb_fixed_charges(d::Dict)
    fixed_monthly = 0.0
    annual_min = 0.0
    min_monthly = 0.0

    # first try $/month, then check if $/day exists, as of 1/28/2020 there were only $/day and $month entries in the URDB
    fixed_monthly = Float64(get(d, "fixedmonthlycharge", 0.0))
    if fixed_monthly == 0.0
        if get(d, "fixedchargeunits", "") == "\$/month"
            fixed_monthly = Float64(get(d, "fixedchargefirstmeter", 0.0))
        elseif get(d, "fixedchargeunits", "") == "\$/day"
            fixed_monthly = Float64(get(d, "fixedchargefirstmeter", 0.0) * 30.4375)
            # scalar intended to approximate annual charges over 12 month period, derived from 365.25/12
        elseif get(d, "fixedchargeunits", "") == "\$/year"
            fixed_monthly = Float64(get(d, "fixedchargefirstmeter", 0.0) / 12)
        elseif !isnothing(get(d, "fixedchargefirstmeter",  nothing))
            @warn "A valid value for fixedchargeunits (\$/month, \$/day, or \$/year) was not provided in urdb_response so the value provided for fixedchargefirstmeter will be ignored."
        end
    end

    if get(d, "minchargeunits", "") == "\$/month"
        min_monthly = Float64(get(d, "mincharge", 0.0))
        # first try $/month, then check if $/day or $/year exists, as of 1/28/2020 these were the only unit types in the urdb
    elseif get(d, "minchargeunits", "") == "\$/day"
        min_monthly = Float64(get(d, "mincharge", 0.0) * 30.4375 )
        # scalar intended to approximate annual charges over 12 month period, derived from 365.25/12
    elseif get(d, "minchargeunits", "") == "\$/year"
        annual_min = Float64(get(d, "mincharge", 0.0))
    elseif !isnothing(get(d, "minchargeunits",  nothing))
        @warn "A valid value for minchargeunits (\$/month, \$/day, or \$/year) was not provided in urdb_response so the value provided for mincharge will be ignored."
    end

    return fixed_monthly, annual_min, min_monthly
end

"""
    parse_urdb_lookback_charges(d::Dict)

URDB lookback fields:
- lookbackMonths
    - Type: array
    - Array of 12 booleans, true or false, indicating months in which lookbackPercent applies.
        If any of these is true, lookbackRange should be zero.
- lookbackPercent
    - Type: decimal
    - Lookback percentage. Applies to either lookbackMonths with value=1, or a lookbackRange.
- lookbackRange
    - Type: integer
    - Number of months for which lookbackPercent applies. If not 0, lookbackMonths values should all be 0.
"""
function parse_urdb_lookback_charges(d::Dict)
    lookback_months = get(d, "lookbackMonths", Int[])
    lookback_percent = Float64(get(d, "lookbackPercent", 0.0))
    lookback_range = Int64(get(d, "lookbackRange", 0.0))

    reopt_lookback_months = Int[]
    if lookback_range != 0 && length(lookback_months) == 12
        for mth in range(1, stop=12)
            if lookback_months[mth] == 1
                push!(reopt_lookback_months, mth)
            end
        end
    end
    return reopt_lookback_months, lookback_percent, lookback_range
end


using Statistics
using CairoMakie, ColorSchemes

import Base: parse, +, /

struct Params
    N :: Int
    L :: Int
end

function parse(::Type{Params}, s::AbstractString)
    element = split(s,';')
    return Params(
        parse(Int,element[1]),
        parse(Int,element[2]),
    )
end

struct Duration
    hour   :: Int
    minute :: Int
    second :: Float64
end

function parse(::Type{Duration}, s::AbstractString)
    index = (
        o = 0,
        h = findfirst('h', s),
        m = findfirst('m', s),
        s = findfirst('s', s),
    )

    inc(::Nothing) = 1
    inc(x::Int64)  = x+1

    h = index.h === nothing ? 0 : parse(Int, s[inc(index.o):index.h-1])
    m = index.m === nothing ? 0 : parse(Int, s[inc(index.h):index.m-1])
    s = index.s === nothing ? 0 : parse(Float64, s[inc(index.m):index.s-1])

    return Duration(h,m,s)
end

seconds(t::Duration) = t.second + 60*t.minute + 3600*t.hour

function +(t1::Duration, t2::Duration)
    s = t1.second + t2.second
    m = t1.second + t2.second + (s÷60)
    h = t1.hour + t2.hour + (m÷60)

    return Duration(h, m%60, s%60)
end

function /(t::Duration, x::Int)
    h = t.hour ÷ x
    m = 60*(t.hour % x) + t.minute ÷ x
    s = 60*(t.minute % x) + t.second ÷ x

    m += s % 60
    s %= 60

    h += m % 60
    m %= 60

    return Duration(h, m, s)
end

function benchmarks(io)
    duration = Dict{Params,Array{Duration,1}}()
    for line in eachline(io)
        key, val = strip.(split(line, "=>"))
        param = parse(Params, key)
        dtime = parse(Duration, val)

        if param in keys(duration)
            push!(duration[param], dtime)
        else
            duration[param] = Duration[dtime]
        end
    end

    return duration
end

field(data, key) = Set(getfield.(keys(data), key)) |> collect |> sort
function plots(benchmark)
    L = field(benchmark, :L)
    N = field(benchmark, :N)

    colors = cgrad(:matter, length(L), categorical=true)

    fig  = Figure(font="Latin Modern Math", fontsize=26)
    axis = Axis(fig[1,1],
                xscale=CairoMakie.log10,
                yscale=CairoMakie.log10,
                xlabel="number of genomes",
                ylabel="time(sec)",
                xticks=([10, 100, 1000],    [L"10^1", L"10^2", L"10^3"]),
                yticks=([1, 10, 100, 1000], [L"10^0", L"10^1", L"10^2", L"10^3"]),
    )
    hidespines!(axis, :t, :r)
    for (i,l) in enumerate(L)
        data = [seconds.(benchmark[Params(n,l)]) for n in N]
        μ, σ = mean.(data), std.(data)
        lines!(axis, N, μ;
            color=colors[i],
            linewidth=2,
            label="$(l)"
        )

        band!(axis, N, μ-σ, μ+σ;
            color=(colors[i],0.25),
        )
    end
    lines!(N, .5*N, color=:black, linewidth=3, linestyle=:dash, label="linear trend")
    axislegend("Avg. genome length", position = :lt, nbanks=2)
    fig
end

function main(path, dest)
    plt = open(benchmarks, path) |> plots
    save(dest, plt, px_per_unit=2)
end

if abspath(PROGRAM_FILE) == @__FILE__
    main(ARGS[1], ARGS[2])
end


function set_wallpoint(nodes, bdcon, cellxmax, cellymax, cellzmax)
	# cal number of ponits
	nop = 0
	if Int(bdcon[1][1]) == 2 || Int(bdcon[1][1]) == 3
		nop = nop + (cellymax-1)*(cellzmax-1)
	end
	if Int(bdcon[2][1]) == 2 || Int(bdcon[2][1]) == 3
		nop = nop + (cellymax-1)*(cellzmax-1)
	end
	if Int(bdcon[3][1]) == 2 || Int(bdcon[3][1]) == 3
		nop = nop + (cellzmax-1)*(cellxmax-1)
	end
	if Int(bdcon[4][1]) == 2 || Int(bdcon[4][1]) == 3
		nop = nop + (cellzmax-1)*(cellxmax-1)
	end
	if Int(bdcon[5][1]) == 2 || Int(bdcon[5][1]) == 3
		nop = nop + (cellxmax-1)*(cellymax-1)
	end
	if Int(bdcon[6][1]) == 2 || Int(bdcon[6][1]) == 3
		nop = nop + (cellxmax-1)*(cellymax-1)
	end

	wallpoint = zeros(nop,3)
	ite = 1

	# x-
	if Int(bdcon[1][1]) == 2 || Int(bdcon[1][1]) == 3
		for j in 2:cellymax
			for k in 2:cellzmax
				wallpoint[ite,1] = nodes[2,j,k,1]
				wallpoint[ite,2] = nodes[2,j,k,2]
				wallpoint[ite,3] = nodes[2,j,k,3]
				ite = ite + 1
			end
		end
	end
	# x+
	if Int(bdcon[2][1]) == 2 || Int(bdcon[2][1]) == 3
		for j in 2:cellymax
			for k in 2:cellzmax
				wallpoint[ite,1] = nodes[cellzmax,j,k,1]
				wallpoint[ite,2] = nodes[cellzmax,j,k,2]
				wallpoint[ite,3] = nodes[cellzmax,j,k,3]
				ite = ite + 1
			end
		end
	end
	# y-
	if Int(bdcon[3][1]) == 2 || Int(bdcon[3][1]) == 3
		for k in 2:cellzmax
			for i in 2:cellxmax
				wallpoint[ite,1] = nodes[i,2,k,1]
				wallpoint[ite,2] = nodes[i,2,k,2]
				wallpoint[ite,3] = nodes[i,2,k,3]
				ite = ite + 1
			end
		end
	end
	# y+
	if Int(bdcon[4][1]) == 2 || Int(bdcon[4][1]) == 3
		for k in 2:cellzmax
			for i in 2:cellxmax
				wallpoint[ite,1] = nodes[i,cellymax,k,1]
				wallpoint[ite,2] = nodes[i,cellymax,k,2]
				wallpoint[ite,3] = nodes[i,cellymax,k,3]
				ite = ite + 1
			end
		end
	end
	# z-
	if Int(bdcon[5][1]) == 2 || Int(bdcon[5][1]) == 3
		for i in 2:cellxmax
			for j in 2:cellymax
				wallpoint[ite,1] = nodes[i,j,2,1]
				wallpoint[ite,2] = nodes[i,j,2,2]
				wallpoint[ite,3] = nodes[i,j,2,3]
				ite = ite + 1
			end
		end
	end
	# z+
	if Int(bdcon[6][1]) == 2 || Int(bdcon[6][1]) == 3
		for i in 2:cellxmax
			for j in 2:cellymax
				wallpoint[ite,1] = nodes[i,j,cellzmax,1]
				wallpoint[ite,2] = nodes[i,j,cellzmax,2]
				wallpoint[ite,3] = nodes[i,j,cellzmax,3]
				ite = ite + 1
			end
		end
	end
	return wallpoint, nop
end

function set_wally(cellcenter, wallpoint, nop, cellxmax, cellymax, cellzmax)
	wally = zeros(cellxmax, cellymax, cellzmax)

	distance_list = zeros(nop,2)
	distance = 0

	pickup_point = 5

	# 最大値を5つ抽出
	for i in 2:cellxmax-1
		for j in 2:cellymax-1
			for k in 2:cellzmax-1
				for n in 1:nop
						distance = ((cellcenter[i,j,k,1]-wallpoint[n,1])^2 +
									(cellcenter[i,j,k,2]-wallpoint[n,2])^2 +
									(cellcenter[i,j,k,3]-wallpoint[n,3])^2)^0.5
						distance_list[n,1] = n
						distance_list[n,2] = distance
				end

				qdistance_list = copy(distance_list)
				qdistance_list = quicksort(qdistance_list,1,nop,2,2)

				#println(distance_list[1,2])

				# 5wall_point
				wp = zeros(Int, pickup_point)
				for m in 1:pickup_point
					wp[m] = Int(qdistance_list[m,1])
				end

				# max(y)
				# 近傍5点を用いて一番遠いyが正解
				# 例1 1点はy+側、2点はy-側のとき、y-とy+で作れる三角形の高さが一番短いが間違い
				# 逆に一番遠いものが正解
				ytemp = zeros(10)
				ite1 = [1,1,1,1,1,1,2,2,2,3]
				ite2 = [2,2,2,3,3,4,3,3,4,4]
				ite3 = [3,4,5,4,5,5,4,5,5,5]

				# println(qdistance_list[5])
				for m in 1:10
					wp1 = wp[ite1[m]]
					wp2 = wp[ite2[m]]
					wp3 = wp[ite3[m]]

					dis1 = distance_list[wp1,2]
					dis2 = distance_list[wp2,2]
					dis3 = distance_list[wp3,2]

					wallpoint_dis1 = ((wallpoint[wp1,1]-wallpoint[wp2,1])^2 + (wallpoint[wp1,2]-wallpoint[wp2,2])^2 + (wallpoint[wp1,3]-wallpoint[wp2,3])^2)^0.5
					wallpoint_dis2 = ((wallpoint[wp2,1]-wallpoint[wp3,1])^2 + (wallpoint[wp2,2]-wallpoint[wp3,2])^2 + (wallpoint[wp2,3]-wallpoint[wp3,3])^2)^0.5
					wallpoint_dis3 = ((wallpoint[wp3,1]-wallpoint[wp1,1])^2 + (wallpoint[wp3,2]-wallpoint[wp1,2])^2 + (wallpoint[wp3,3]-wallpoint[wp1,3])^2)^0.5

					V = cal_tetrahedron_volume(dis1, dis2, dis3, wallpoint_dis1, wallpoint_dis2, wallpoint_dis3)

					S = cal_area(wallpoint_dis1, wallpoint_dis2, wallpoint_dis3)

					ytemp[m] = V/S
				end
				wally[i,j,k] = maximum(ytemp)
			end
		end
	end

	#=
	・壁面に下した垂線の計算について
	面積S = (s(s-a)(s-b)(s-c))^0.5 = hc/2  , h=垂線,c=底面(壁面の2点間距離)
	これで三辺の長さがわかれば垂線が算出できる
	=#
	return wally
end

function cal_tetrahedron_volume(a1,a2,a3,a4,a5,a6)
	temp1 = a1^2 * a5^2 * (a2^2 + a3^2 + a4^2 + a6^2 - a1^2 - a5^2)
	temp2 = a2^2 * a6^2 * (a1^2 + a3^2 + a4^2 + a5^2 - a2^2 - a6^2)
	temp3 = a3^2 * a4^2 * (a1^2 + a2^2 + a5^2 + a6^2 - a3^2 - a4^2)
	temp4 = -(a1*a2*a4)^2 - (a2*a3*a5)^2 - (a1*a3*a6)^2 - (a4*a5*a6)^2
	V2 = 1/144 * (temp1 + temp2 + temp3 + temp4)

	# ---------------------------------
	# 5つの近傍点で計算するため，構造格子だと，xが同じ値の場合がありエラーになるから
	# ---------------------------------
	volume = 0.0
	try volume = (V2)^0.5
	catch

	end

	return volume
end

function cal_area(a1,a2,a3)
	s = 0.5*(a1 + a2 + a3)

	# ---------------------------------
	# 5つの近傍点で計算するため，構造格子だと，xが同じ値の場合がありエラーになるから
	# ---------------------------------
	surface = s * (s-a1) * (s-a2) * (s-a3)

	if surface <= 0.0
		surface = 1.0e50
	end

	surface = surface^0.5

	return surface
end

function cal_yplus(Qbase, wally, mu, cellxmax, cellymax, cellzmax)
	yplus = zeros(cellxmax, cellymax, cellzmax)

	for i in 2:cellxmax-1
		for j in 2:cellymax-1
			for k in 2:cellzmax-1
				nu = mu[i,j,k] / Qbase[i,j,k,1]
				yplus[i,j,k], uplus = cal_wall_val_spalding(Qbase[i,j,k,2], wally[i,j,k], nu)

				if isequal(yplus[i,j,k], NaN) == true
					println("      ")
					println(Qbase[i,j,k,2])
					println(wally[i,j,k])
					println(nu)
					throw(UndefVarError(:x))
				end
			end
		end
	end

	return yplus
end

function cal_wall_val_spalding(u,y,nu)
	k = 0.4
	B = 5.5

	u_tau = 1.0
	for i in 1:20
		#=
		println("   zzz   ")
		println(u)
		println(y)
		println(nu)
		println(spalding(u_tau,u,y,nu,k,B))
		println(spalding_dash(u_tau,u,y,nu,k,B))
		println(u_tau)
		=#

		old = u_tau
		u_tau = u_tau-spalding(u_tau,u,y,nu,k,B)/spalding_dash(u_tau,u,y,nu,k,B) + 1.0e-50

		if abs(u_tau-old)/u_tau < 10^(-4)
			break
		end
	end

	yplus = u_tau*y/nu
	uplus = u/u_tau
	return yplus,uplus
end

function spalding(u_tau,u,y,nu,k,B)
	t = k*u/u_tau
	F = u/u_tau-u_tau*y/nu+exp(-k*B)*(exp(t)-1-t-t^2/2-t^3/6)
	return F
end

function spalding_dash(u_tau,u,y,nu,k,B)
	t = k*u/u_tau
	Fdash = -u/u_tau^2-y/nu+exp(-k*B)*(t/u_tau)*(-exp(t)+1+t+t^2/2)
	return Fdash
end

# test
#=
uu = 0.0
yy = 0.00014
nunu = 1.5e-5
println(cal_wall_val_spalding(uu,yy,nunu))
=#

function SGSstress()
	# 境界におけるtauが必要
end

function cal_Strain_rate_tensor()

end