## Copyright (c) 2013 Miguel Bazdresch
##
## This file is distributed under the 2-clause BSD License.

# append x,y,z coordinates and configuration to current figure
function addcoords(x::Coord,y::Coord,Z::Array,conf::CurveConf)
    global gnuplot_state

    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end

    # check coordinates: dimensions, sizes and types
    nex = !isempty(x); ney = !isempty(y); neZ = !isempty(Z)
    # check types
    if nex
        @assert(eltype(x)<:Real,"Invalid coordinates")
    end
    if ney
        @assert(eltype(y)<:Real,"Invalid coordinates")
    end
    if neZ
        @assert(eltype(Z)<:Real,"Invalid coordinates")
        @assert(1 < ndims(Z) < 4,"Invalid coordinates")
    end
    # fill missing x, y coordinates when Z is not empty
    if neZ && !nex
        x = 1:size(Z,2)
    end
    if neZ && !ney
        y = 1:size(Z,1)
    end
    # if x,y are matrices, convert to vectors
    if isa(x,Matrix)
        s = size(x)
        if s[1] == 1 || s[2] == 1
            x = squeeze(x)
        else
            error("Invalid abscissa coordinates")
        end
    elseif isa(x,UnitRange) || isa(x,Range)
        x = collect(x)
    end
    if isa(y,Matrix)
        s = size(y)
        if s[1] == 1 || s[2] == 1
            y = squeeze(y)
        else
            error("Invalid abscissa coordinates")
        end
    elseif isa(y,UnitRange) || isa(y,Range)
        y = collect(y)
    end
    # check number of elements
    if neZ
        if conf.plotstyle == "image" || conf.plotstyle == "rgbimage"
            @assert(size(Z,2) == length(x), "Wrong number of columns in Z")
            @assert(size(Z,1) == length(y), "Wrong number of rows in Z")
        else
            @assert(size(Z,1) == length(x), "Wrong number of columns in Z")
            @assert(size(Z,2) == length(y), "Wrong number of rows in Z")
        end
    else
        @assert(length(x) == length(y),
        "Abscissa and ordinate must have the same number of elements")
    end

    # check curve configuration: property names
    # TODO: figure out how to check valid color names -- gnuplot supports
    #  112 different color names.
    # check valid values of marker
    @assert(validate_marker(conf.marker), "Invalid mark name specified")
    if nex && !neZ ## 2-d plot
        # check valid values of plotstyle
        @assert(validate_2d_plotstyle(conf.plotstyle),
            "Invalid plotstyle specified")
    elseif nex && neZ ## 3-d plot or image
        @assert(validate_3d_plotstyle(conf.plotstyle),
            "Invalid plotstyle specified")
    end

    conf = copy(conf)       # we need to dereference conf
    # append data to figure
    c = findfigure(gnuplot_state.current)
    fig = gnuplot_state.figs[c]
    if fig.isempty == true
        fig.curves[1] = CurveData(x,y,Z,conf)
        fig.isempty = false
    else
        push!(fig.curves, CurveData(x,y,Z,conf))
    end
    gnuplot_state.figs[c] = fig
end
addcoords(y) = addcoords(1:length(y),y,Any[],CurveConf())
addcoords(y,c::CurveConf) = addcoords(1:length(y),y,Any[],c)
addcoords(x,y) = addcoords(x,y,Any[],CurveConf())
addcoords(x,y,c::CurveConf) = addcoords(x,y,Any[],c)
addcoords(x,y,Z) = addcoords(x,y,Z,CurveConf())
# X, Y data in matrix columns
function addcoords(X::Matrix,Y::Matrix,conf::CurveConf)
    for i = 1:size(X,2)
        addcoords(X[:,i],Y[:,i],Any[],conf)
    end
end
function addcoords(Y::Matrix,conf::CurveConf)
    x = 1:size(Y,1)
    X = zeros(size(Y))
    for i = 1:size(Y,2)
        X[:,i] = x
    end
    addcoords(X,Y,conf)
end
addcoords(X::Matrix, Y::Matrix) = addcoords(X,Y,CurveConf())
addcoords(Y::Matrix) = addcoords(Y,CurveConf())

# append error data to current set of coordinates
function adderror(yl::Coord,yh::Coord)
    global gnuplot_state
    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end
    c = findfigure(gnuplot_state.current)

    # check arguments and convert to vectors
    if isempty(yl)
        error("Invalid error data")
    else
        @assert(eltype(yl)<:Real,"Invalid error data")
        if isa(yl,Matrix)
            s = size(yl)
            if s[1] == 1 || s[2] == 1
                yl = squeeze(yl)
            else
                error("Invalid error data")
            end
        elseif isa(yl,UnitRange) || isa(yl,Range)
            yl = collect(yl)
        end
    end
    if !isempty(yh)
        @assert(eltype(yh)<:Real,"Invalid error data")
        if isa(yl,Matrix)
            s = size(yh)
            if s[1] == 1 || s[2] == 1
                yh = squeeze(yh)
            else
                error("Invalid error data")
            end
        elseif isa(yh,UnitRange) || isa(yh,Range)
            yh = collect(yh)
        end
    end
    # verify vector sizes -- this also implies that x,y coordinates must be
    # added to figure, before error data can be attached to it
    @assert(length(gnuplot_state.figs[c].curves[end].x) == length(yl),
        "Error data vector must be of same size as abscissa")
    if !isempty(yh)
        @assert(length(yh) == length(yl),
            "Error data vectors must be of same size")
    end

    # set fields in current curve
    gnuplot_state.figs[c].curves[end].ylow = yl
    gnuplot_state.figs[c].curves[end].yhigh = yh

end
adderror(ydelta) = adderror(ydelta,Any[])

# add financial data to current set of coordinates
# input order is: open, low, high, close
function addfinancial(args...)
	global gnuplot_state
	    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end
    c = findfigure(gnuplot_state.current)

    # check arguments and convert to vectors
    if length(args) != 4
        error("Invalid financial data")
	end
	for a in args
		@assert(!isempty(a),"Invalid financial data")
        @assert(eltype(a)<:Real,"Invalid financial data")
        @assert(typeof(a)<:Array,"Invalid financial data")
		@assert(length(gnuplot_state.figs[c].curves[end].x) == length(a),
			"Financial data vector must be of same size as abscissa")
	end

    # set fields in current curve
    gnuplot_state.figs[c].curves[end].finance.open = args[1]
    gnuplot_state.figs[c].curves[end].finance.low = args[2]
    gnuplot_state.figs[c].curves[end].finance.high = args[3]
    gnuplot_state.figs[c].curves[end].finance.close = args[4]

end

# add axes configuration to current figure
function addconf(conf::AxesConf)
    global gnuplot_state
    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end

    # Perform argument validation
    # TODO: find a way to validate the box argument
    # validate axis type
    @assert(validate_axis(conf.axis),"Invalid axis type specified")
    # validate ranges
    @assert(validate_range(conf.xrange),"Invalid xrange specified")
    @assert(validate_range(conf.yrange),"Invalid yrange specified")
    @assert(validate_range(conf.zrange),"Invalid zrange specified")

    conf = copy(conf)
    # select current plot
    c = findfigure(gnuplot_state.current)
    gnuplot_state.figs[c].conf = conf
end

# llplot() is our workhorse plotting function
function llplot()
    global gnuplot_state
    global gaston_config

    # select current plot
    c = findfigure(gnuplot_state.current)
    if c == 0
        println("No current figure")
        return
    end
    figs = gnuplot_state.figs
    config = figs[c].conf

    # Build terminal setup string and send it to gnuplot
    ts = termstring(gaston_config.terminal)
    gnuplot_send(ts)

    # if figure has no data, stop here
    if isempty(figs[c].curves[1].x)
        return
    end

    # datafile filename
    (filename,f) = mktemp(gnuplot_state.tmpdir)

    # Send appropriate coordinates and data to gnuplot, depending on
    # whether we are doing 2-d, 3-d or image plots.

    # 2-d plot: Z is empty or plostyle is {,rgb}image
    if isempty(figs[c].curves[1].Z) ||
        figs[c].curves[1].conf.plotstyle == "image" ||
        figs[c].curves[1].conf.plotstyle == "rgbimage"
        # create data file
        for i in figs[c].curves
            ps = i.conf.plotstyle
            if ps == "errorbars" || ps == "errorlines"
                if isempty(i.yhigh)
                    # ydelta (single error coordinate)
                    writedlm(f,[i.x i.y i.ylow],' ')
                else
                    # ylow, yhigh (double error coordinate)
                    writedlm(f,[i.x i.y i.ylow i.yhigh],' ')
                end
			elseif ps == "financebars"
            	# data is written to tmparr, which is then written to disk
            	tmparr = zeros(length(i.x),5)
                # output matrix
                for col = 1:length(i.x)
					tmparr[col,1] = i.x[col]
					tmparr[col,2] = i.finance.open[col]
					tmparr[col,3] = i.finance.low[col]
					tmparr[col,4] = i.finance.high[col]
					tmparr[col,5] = i.finance.close[col]
                end
				writedlm(f,tmparr,' ')
            elseif ps == "image"
            	# data is written to tmparr, which is then written to disk
            	tmparr = zeros(length(i.x)*length(i.y),3)
                tmparr_row_index = 1  # index into tmparr row
                # output matrix
                for row = 1:length(i.y)
                    x = length(i.x)
                    for col = 1:length(i.x)
                    	tmparr[tmparr_row_index,1] = i.x[col]
                    	tmparr[tmparr_row_index,2] = i.x[row]
                    	tmparr[tmparr_row_index,3] = i.Z[row,col]
                    	tmparr_row_index = tmparr_row_index+1
                        x = x-1
                    end
                end
				writedlm(f,tmparr,' ')
            elseif ps == "rgbimage"
            	# data is written to tmparr, which is then written to disk
            	tmparr = zeros(length(i.x)*length(i.y), 5)
            	tmparr_row_index = 1
                # output matrix
                for col = 1:length(i.x)
                    y = length(i.y)
                    for row = 1:length(i.y)
                    	tmparr[tmparr_row_index,1] = i.x[col]
                    	tmparr[tmparr_row_index,2] = i.y[row]
                    	tmparr[tmparr_row_index,3] = i.Z[y,col,1]
                    	tmparr[tmparr_row_index,4] = i.Z[y,col,2]
                    	tmparr[tmparr_row_index,5] = i.Z[y,col,3]
                    	tmparr_row_index = tmparr_row_index+1
                        y = y-1
                    end
                end
                writedlm(f,tmparr,' ')
            else
                writedlm(f,[i.x i.y],' ')
            end
            write(f,"\n\n")
        end
        close(f)
        # send figure configuration to gnuplot
        gnuplot_send_fig_config(config)
        # send plot command to gnuplot
        gnuplot_send(linestr(figs[c].curves, "plot", filename))

    # 3-d plot: Z is not empty and plotstyle is not {,rgb}image
    elseif !isempty(figs[c].curves[1].Z) &&
            figs[c].curves[1].conf.plotstyle != "image" &&
            figs[c].curves[1].conf.plotstyle != "rgbimage"
        # create data file
        for i in figs[c].curves
			# data is written to tmparr, which is then written to disk
			tmparr = zeros(1, 3)
			tmparr_row_index = 1
            for row in 1:length(i.x)
                for col in 1:length(i.y)
					tmparr[1,1] = i.x[row]
					tmparr[1,2] = i.y[col]
					tmparr[1,3] = i.Z[row,col]
					writedlm(f,tmparr,' ')
                end
                write(f,"\n")
            end
            write(f,"\n\n")
        end
        close(f)
        # send figure configuration to gnuplot
        gnuplot_send_fig_config(config)
        # send command to gnuplot
        gnuplot_send(linestr(figs[c].curves, "splot",filename))
    end
    # Wait until gnuplot is finished plotting before returning. To do this,
    # we make gnuplot output "X\n" in its stdout. Gnuplot will only get to
    # do this when the plot is finished. Otherwise, gnuplot will output
    # something in its stderr. In either case, we know that the plot is
    # finished and can carry on.
    gnuplot_send("set print \"-\"\n")
    gnuplot_send("print \"X\"\n")

	# Loop until we read data from either gnuplot's stdout or stdin
	while true
		yield()  # let async tasks run
		if !isempty(gnuplot_state.gp_stdout)
			# We got data from stdin, meaning the plot went well.
			gnuplot_state.gp_stdout = ""
			gnuplot_state.gp_error = false
			break
		end
		if !isempty(gnuplot_state.gp_stderr)
			# Gnuplot met trouble while plotting.
			gnuplot_state.gp_lasterror = gnuplot_state.gp_stderr
			gnuplot_state.gp_stderr = ""
			gnuplot_state.gp_error = true
			break
		end
	end

    # Reset gnuplot settable options.
    gnuplot_send("\nreset\n")

    # If the environment is IJulia, redisplay the figure.
    if displayable("image/png")
    	redisplay(figs[c])
	end
end


function Base.delete!(out, mask::BitArray)
    return out[1][:, mask], out[2][:, mask], out[3][:, mask], out[4][mask], out[5][mask]
end

"""
    Base.delete!(obj::T, f::Function) where T

Delete mesh particle for object using boolean array from function f.
"""
function Base.delete!(obj::T, f::Function) where T
    # x, v, y, vol, type
    function func(out)
        mask = vec(f(out)) .== false
        out = Base.delete!(out, mask)
        return out
    end
    if isa(obj, Shape)
        return PostOpObj(obj, func)
    elseif isa(obj, PostOpObj)
        push!(obj.operations, func)
        return obj
    else
        error("Not allowed.")
    end
end


## Copyright (c) 2013 Miguel Bazdresch
##
## This file is distributed under the 2-clause BSD License.

# append x,y,z coordinates and configuration to current figure
function addcoords(x::Coord,y::Coord,Z::Array,conf::CurveConf)
    global gnuplot_state

    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end

    # check coordinates: dimensions, sizes and types
    nex = !isempty(x); ney = !isempty(y); neZ = !isempty(Z)
    # check types
    if nex
        assert(eltype(x)<:Real,"Invalid coordinates")
    end
    if ney
        assert(eltype(y)<:Real,"Invalid coordinates")
    end
    if neZ
        assert(eltype(Z)<:Real,"Invalid coordinates")
        assert(1 < ndims(Z) < 4,"Invalid coordinates")
    end
    # fill missing x, y coordinates when Z is not empty
    if neZ && !nex
        x = 1:size(Z,2)
    end
    if neZ && !ney
        y = 1:size(Z,1)
    end
    # if x,y are matrices, convert to vectors
    if isa(x,Matrix)
        s = size(x)
        if s[1] == 1 || s[2] == 1
            x = squeeze(x)
        else
            error("Invalid abscissa coordinates")
        end
    elseif isa(x,Range1) || isa(x,Range)
        x = [x]
    end
    if isa(y,Matrix)
        s = size(y)
        if s[1] == 1 || s[2] == 1
            y = squeeze(y)
        else
            error("Invalid abscissa coordinates")
        end
    elseif isa(y,Range1) || isa(y,Range)
        y = [y]
    end
    # check number of elements
    if neZ
        if conf.plotstyle == "image" || conf.plotstyle == "rgbimage"
            assert(size(Z,2) == length(x), "Wrong number of columns in Z")
            assert(size(Z,1) == length(y), "Wrong number of rows in Z")
        else
            assert(size(Z,1) == length(x), "Wrong number of columns in Z")
            assert(size(Z,2) == length(y), "Wrong number of rows in Z")
        end
    else
        assert(length(x) == length(y),
        "Abscissa and ordinate must have the same number of elements")
    end

    # check curve configuration: property names
    # TODO: figure out how to check valid color names -- gnuplot supports
    #  112 different color names.
    # check valid values of marker
    assert(validate_marker(conf.marker), "Invalid mark name specified")
    if nex && !neZ ## 2-d plot
        # check valid values of plotstyle
        assert(validate_2d_plotstyle(conf.plotstyle),
            "Invalid plotstyle specified")
    elseif nex && neZ ## 3-d plot or image
        assert(validate_3d_plotstyle(conf.plotstyle),
            "Invalid plotstyle specified")
    end

    conf = copy(conf)       # we need to dereference conf
    # append data to figure
    c = findfigure(gnuplot_state.current)
    fig = gnuplot_state.figs[c]
    if fig.isempty == true
        fig.curves[1] = CurveData(x,y,Z,conf)
        fig.isempty = false
    else
        fig.curves = [fig.curves, CurveData(x,y,Z,conf)]
    end
    gnuplot_state.figs[c] = fig
end
addcoords(y) = addcoords(1:length(y),y,[],CurveConf())
addcoords(y,c::CurveConf) = addcoords(1:length(y),y,[],c)
addcoords(x,y) = addcoords(x,y,[],CurveConf())
addcoords(x,y,c::CurveConf) = addcoords(x,y,[],c)
addcoords(x,y,Z) = addcoords(x,y,Z,CurveConf())
# X, Y data in matrix columns
function addcoords(X::Matrix,Y::Matrix,conf::CurveConf)
    for i = 1:size(X,2)
        addcoords(X[:,i],Y[:,i],[],conf)
    end
end
function addcoords(Y::Matrix,conf::CurveConf)
    x = 1:size(Y,1)
    X = zeros(size(Y))
    for i = 1:size(Y,2)
        X[:,i] = x
    end
    addcoords(X,Y,conf)
end
addcoords(X::Matrix, Y::Matrix) = addcoords(X,Y,CurveConf())
addcoords(Y::Matrix) = addcoords(Y,CurveConf())

# append error data to current set of coordinates
function adderror(yl::Coord,yh::Coord)
    global gnuplot_state
    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end
    c = findfigure(gnuplot_state.current)

    # check arguments and convert to vectors
    if isempty(yl)
        error("Invalid error data")
    else
        assert(eltype(yl)<:Real,"Invalid error data")
        if isa(yl,Matrix)
            s = size(yl)
            if s[1] == 1 || s[2] == 1
                yl = squeeze(yl)
            else
                error("Invalid error data")
            end
        elseif isa(yl,Range1) || isa(yl,Range)
            yl = [yl]
        end
    end
    if !isempty(yh)
        assert(eltype(yh)<:Real,"Invalid error data")
        if isa(yl,Matrix)
            s = size(yh)
            if s[1] == 1 || s[2] == 1
                yh = squeeze(yh)
            else
                error("Invalid error data")
            end
        elseif isa(yh,Range1) || isa(yh,Range)
            yh = [yh]
        end
    end
    # verify vector sizes -- this also implies that x,y coordinates must be
    # added to figure, before error data can be attached to it
    assert(length(gnuplot_state.figs[c].curves[end].x) == length(yl),
        "Error data vector must be of same size as abscissa")
    if !isempty(yh)
        assert(length(yh) == length(yl),
            "Error data vectors must be of same size")
    end

    # set fields in current curve
    gnuplot_state.figs[c].curves[end].ylow = yl
    gnuplot_state.figs[c].curves[end].yhigh = yh

end
adderror(ydelta) = adderror(ydelta,[])

# add axes configuration to current figure
function addconf(conf::AxesConf)
    global gnuplot_state
    # check that at least one figure has been setup
    if gnuplot_state.current == 0
        figure(1)
    end

    # Perform argument validation
    # TODO: find a way to validate the box argument
    # validate axis type
    assert(validate_axis(conf.axis),"Invalid axis type specified")

    conf = copy(conf)
    # select current plot
    c = findfigure(gnuplot_state.current)
    gnuplot_state.figs[c].conf = conf
end

# llplot() is our workhorse plotting function
function llplot()
    global gnuplot_state
    global gaston_config

    # select current plot
    c = findfigure(gnuplot_state.current)
    if c == 0
        println("No current figure")
        return
    end
    figs = gnuplot_state.figs
    config = figs[c].conf

    # setup grid
    ts = gridstring(gaston_config.grid)
    gnuplot_send(ts)
    # Build terminal setup string and send it to gnuplot
    ts = termstring(gaston_config.terminal)
    gnuplot_send(ts)

    # if figure has no data, stop here
    if isempty(figs[c].curves[1].x)
        return
    end

    # datafile filename
    filename = "$(gnuplot_state.tmpdir)figure$(gnuplot_state.current).dat"

    # Send appropriate coordinates and data to gnuplot, depending on
    # whether we are doing 2-d, 3-d or image plots.

    # 2-d plot: Z is empty or plostyle is {,rgb}image
    if isempty(figs[c].curves[1].Z) ||
        figs[c].curves[1].conf.plotstyle == "image" ||
        figs[c].curves[1].conf.plotstyle == "rgbimage"
        # create data file
        f = open(filename,"w")
        for i in figs[c].curves
            ps = i.conf.plotstyle
            if ps == "errorbars" || ps == "errorlines"
                if isempty(i.yhigh)
                    # ydelta (single error coordinate)
                    writedlm(f,[i.x i.y i.ylow],' ')
                else
                    # ylow, yhigh (double error coordinate)
                    writedlm(f,[i.x i.y i.ylow i.yhigh],' ')
                end
            elseif ps == "image"
                # output matrix
                for col = 1:length(i.x)
                    y = length(i.y)
                    for row = 1:length(i.y)
                        writedlm(f,[i.x[col] i.y[row] i.Z[y,col]],' ')
                        y = y-1
                    end
                end
            elseif ps == "rgbimage"
                # output matrix
                for col = 1:length(i.x)
                    y = length(i.y)
                    for row = 1:length(i.y)
                        writedlm(f,
                        [i.x[col] i.y[row] i.Z[y,col,1] i.Z[y,col,2] i.Z[y,col,3]],
                        ' ')
                        y = y-1
                    end
                end
            else
                writedlm(f,[i.x i.y],' ')
            end
            write(f,"\n\n")
        end
        flush(f)
        close(f)
        # send figure configuration to gnuplot
        gnuplot_send_fig_config(config)
        # send plot command to gnuplot
        gnuplot_send(linestr(figs[c].curves, "plot", filename))

    # 3-d plot: Z is not empty and plotstyle is not {,rgb}image
    elseif !isempty(figs[c].curves[1].Z) &&
            figs[c].curves[1].conf.plotstyle != "image" &&
            figs[c].curves[1].conf.plotstyle != "rgbimage"
        # create data file
        f = open(filename,"w")
        for i in figs[c].curves
            for row in 1:length(i.x)
                for col in 1:length(i.y)
                    writedlm(f,[i.x[row] i.y[col] i.Z[row,col]],' ')
                end
                write(f,"\n")
            end
            write(f,"\n\n")
        end
        close(f)
        # send figure configuration to gnuplot
        gnuplot_send_fig_config(config)
        # send command to gnuplot
        gnuplot_send(linestr(figs[c].curves, "splot",filename))
    end
    gnuplot_send("reset")
end


export velocity

function velocity(v::Array{Float64,2}, mask, velocity::Array{Float64,1})
    v[:, mask] .*= 0.0
    v[:, mask] .+= vec(velocity)
    return v
end

function velocity(v::Array{Float64,2}, mask, velocity::Array{Float64,2})
    v[:, mask] .= velocity
    return v
end

function velocity(obj::T, f::Function, vel) where T <: Union{Shape, PostOpObj}
    # x, v, y, vol, type
    function func(out)
        v = out[:v]
        mask = vec(f(out))
        return repack!(out, [:v], [v])
    end
    if isa(obj, Shape)
        return PostOpObj(obj, func)
    elseif isa(obj, PostOpObj)
        push!(obj.operations, func)
        return obj
    else
        error("Not allowed.")
    end
end


# Example code to print list of built HVDC branches and converters
function display_results_tnep(result)
    built_cv = []
    built_br = []
    for (c, conv) in result["solution"]["convdc_ne"]
        if isapprox(conv["isbuilt"] , 1; atol = 0.01)
            print("Conv: $c \n")
            push!(built_cv,c)
        end
    end
    for (b, branch) in result["solution"]["branchdc_ne"]
        if isapprox(branch["isbuilt"] , 1; atol = 0.01)
            print("Branch: $b \n")
            push!(built_br,b)
        end
    end
    return built_cv, built_br
end


using LaserLab
using Test
# using Unitful
# import Unitful:
#     nm, μm, mm, cm, m, km,
#     mg, g, kg,
#     ps, ns, μs, ms, s, minute, hr, d, yr, Hz, kHz, MHz, GHz,
#     eV,
#     μJ, mJ, J,
# 	μW, mW, W

# function hnu_eV(lambda_nm)
#     return 1240.0 * (1.0/lambda_nm)
# end

# dspot(λ::Real, NA::Real) = 1.83*λ/(2*NA)

#include("dffunctions_test.jl")
#include("setup_test.jl")
#include("glaser_test.jl")
#include("pmt_test.jl")

function pmttest()
	#@testset "pmt_test.jl" begin
        fword  = "/Users/jj/JuliaProjects/LaserLab/test/C1--Trace--00010.trc"
        fbyte  = "/Users/jj/JuliaProjects/LaserLab/test/C1--Trace--00108.trc"

        iob = open(fbyte, "r")
        iow = open(fword, "r")

        @test typeof(iob) == IOStream
        @test typeof(iow) == IOStream

        WAVEDESCb = LaserLab.wavedesc(iob)
        WAVEDESCw = LaserLab.wavedesc(iow)
        @test WAVEDESCb == WAVEDESCw

        @test LaserLab.readword(iob, WAVEDESCb + 32) == 0  #Byte
        @test LaserLab.readword(iow, WAVEDESCw + 32) == 1  #Word

        @test LaserLab.readlong(iow, WAVEDESCw + 60) == 2 * LaserLab.readlong(iob, WAVEDESCb + 60)

        @test LaserLab.readstring(iow, WAVEDESCw + 76, 14) == "LECROYWS4104HD"
        @test LaserLab.readstring(iob, WAVEDESCb + 76, 14) == "LECROYWS4104HD"

        @test LaserLab.readfloat(iow, WAVEDESCw + 160) == LaserLab.readfloat(iob, WAVEDESCb + 160)

        @test LaserLab.readdouble(iow, WAVEDESCw+ 180) ≈ LaserLab.readdouble(iob, WAVEDESCb + 180)

        tsw = LaserLab.readtimestamp(iow, WAVEDESCw + 296)
        @test tsw.year == 2022

        xadd = LaserLab.scope_rawdata(iow, WAVEDESCw)
        @test xadd["NOMINAL_BITS"] == 12

        wvf   = LaserLab.xydata(iow, xadd)
        stats = LaserLab.wstats(wvf; nsigma=3.0)

        @test stats.mean + 3.0*stats.std ≈ stats.thrp
        @test stats.mean - 3.0*stats.std ≈ stats.thrn

    #end
end


module ObservationData

using ImageCore, ImageAxes, AxisIndices, NamedIndicesMeta

export
    # @defdim output
    obsdim,
    has_obsdim,
    obs_axis,
    obs_axis_type,
    obs_keys,
    obs_indices,
    to_obs_format,
    nobs,
    obs_format,
    is_observation

Base.@pure is_observation(x::Symbol) = (x === :obs) | (x === :observations) | (x === :samples)

@defdim obs is_observation

end


mutable struct Curriculum
    name::AbstractString        # Name of the curriculum (can be used as an identifier)
    terms::Array{Term}          # Array of terms in order
    courses::Array{Course}      # Array of courses in the curriculum
    numCourses::Int             # Number of courses in curriculum
    creditHours::Int            # Number of credit hours

    complexity::Int             # Sum of all course crucialities
    delay::Int                  # Sum of course delay factors
    blocking::Int               # Sum of course blocking factors
    centrality::Int             # Total Centrality
    reachability::Int           # Total Reachability

    passrate::Float64           # Average passrate of courses
    stopoutModel::Dict          # Dictionary that stores the the model for predicint stopouts

    graph::Any                  # LightGraph representation

    function Curriculum(curriculumName::AbstractString, terms::Array{Term})
        this = new()

        this.name = curriculumName
        this.terms = terms
        this.courses = Course[]

        for (i, term) in enumerate(terms)
            for course in term.courses
                # Add courses to array
                push!(this.courses, course)

                # Add postreq links
                for prereq in course.prereqs
                    push!(prereq.postreqs, course)
                end

                # Add co reqs
                for coreq in course.coreqs
                    push!(coreq.postreqs, course)
                end

                # Mark term the course belongs to
                course.term = i
            end
        end

        for (id, course) in enumerate(this.courses)
            # Compute complexity
            course.id = id
            cruciality(course)
        end

        # Create graph
        this.graph = curriculumGraph(this)

        # Compute centrality
        this.centrality = curriculumCentrality!(this)

        # Compute reachability
        this.reachability = curriculumReachability!(this)

        this.complexity = sum(map(x->x.cruciality, this.courses))
        this.delay = sum(map(x->x.delay, this.courses))
        this.blocking = sum(map(x->x.blocking, this.courses))
        this.numCourses = length(this.courses)
        this.passrate = sum(map(x->x.passrate, this.courses)) / this.numCourses
        this.stopoutModel = Dict()

        this.creditHours = sum(map(x->x.credits, this.courses))

        return this
    end

    function Curriculum(curriculumName::AbstractString, data::Dict)
        # Sort courses by term
        sort!(data["courses"], by = x -> x["term"])

        # Get number of terms
        terms = data["terms"]

        # Array of course arrays, one for each term
        courses = Array{Array{Course}}(undef, terms)
        for i=1:terms
            courses[i] = Course[]
        end

        allCourses = Course[]

        # Create all course objects
        for course in data["courses"]
            name = course["name"]
            credits = course["credits"]
            passrate = course["passrate"]
            haskey(data, "termReq") ? termReq = data["termReq"] : termReq = 0

            c = Course(name, credits, termReq, passrate, Course[], Course[])
            push!(courses[course["term"]], c)
            push!(allCourses, c)
        end

        # Assign pre and coreqs
        for (i, course) in enumerate(data["courses"])
            c = allCourses[i]

            # Prereqs
            prereqs = Course[]
            for prereq in course["prerequisites"]
                ind = findfirst(x -> x.name == prereq, allCourses)
                if ind != 0
                    push!(prereqs, allCourses[ind])
                end
            end

            # Coreqs
            coreqs = Course[]
            for coreq in course["corequisites"]
                ind = findfirst(x -> x.name == coreq, allCourses)
                if ind != 0
                    push!(coreqs, allCourses[ind])
                end
            end

            c.prereqs = prereqs
            c.coreqs = coreqs
        end

        terms = Term[]
        for courseArray in courses
            push!(terms, Term(courseArray))
        end

        this = Curriculum(curriculumName, terms)
        return this
    end

    function Curriculum(curriculumName::AbstractString, path::AbstractString)
        f = open(path)
        data = JSON.parse(read(f, String))
        close(f)

        this = Curriculum(curriculumName, data)
        return this
    end
end

function curriculumGraph(curriculum)
    numCourses = length(curriculum.courses)

    graph = DiGraph(numCourses)

    for course in curriculum.courses
        for prereq in course.prereqs
            add_edge!(graph, prereq.id, course.id)
        end
    end

    return graph
end

function curriculumCentrality!(curriculum)
    measures = zeros(length(curriculum.courses))

    adj = adjacency_matrix(curriculum.graph)

    for i=1:length(curriculum.courses)
        for j=1:length(curriculum.courses)
            paths = path_enumeration(i, j, adj)
            all_nodes = []
            for p in paths
                all_nodes = vcat(all_nodes, p.path)
            end

            for v in unique(all_nodes)
                measures[v] += 1
            end
        end
    end

    for (i, measure) in enumerate(measures)
        curriculum.courses[i].centrality = measure
    end

    return sum(measures)
end

function curriculumReachability!(curriculum)
    newC = deepcopy(curriculum)

    for course in newC.courses
        course.prereqs = Course[]
        course.postreqs = Course[]
    end

    for (ci, course) in enumerate(curriculum.courses)
        for prereq in course.prereqs
            push!(newC.courses[ci].postreqs, newC.courses[prereq.id])
        end
    end

    for (i, course) in enumerate(newC.courses)
        cruciality(course)
        curriculum.courses[i].reachability = course.blocking
    end

    return sum(map(x->x.blocking, newC.courses))
end


using ArgMacros

let
    @inlinearguments begin
        @argumentrequired Int a "-a" "--aa"
        @argtest a (<(10))
        @argumentdefault Float64 10 b "-b"
        @argumentoptional Symbol c "--cc"
        @argumentflag d "-d"
        @argumentcount e "-e"

        @positionalrequired String f
        @positionaldefault Int 20 g
        @argtest g (x -> x % 10 == 0)
        @positionaloptional Float64 h
    end
end


# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.7.0-DEV
#     language: julia
#     name: julia-1.7
# ---

# %%
using BenchmarkTools
using DistributedArrays
struct Foo{T, N} d::DArray{T, N} end
struct Bar{T, N, A} d::DArray{T, N, A} end

# %%
d = drand(4, 3, 2)
l = localpart(d)
foo = Foo(d)
bar = Bar(d);

# %%
f(foobar, i, j, k) = localpart(foobar.d)[i, j, k]
g(d, i, j, k) = localpart(d)[i, j, k]

# %%
@btime f($foo, 3, 2, 1)

# %%
@btime f($bar, 3, 2, 1)

# %%
@btime g($d, 3, 2, 1)

# %%
@btime getindex($l, 3, 2, 1)

# %%
@code_warntype f(foo, 3, 2, 1)

# %%
@code_warntype f(bar, 3, 2, 1)

# %%
isconcretetype(Array{Float64, 3})

# %%
isconcretetype(DArray{Float64, 3})

# %%
typeof(d)

# %%
isconcretetype(DArray{Float64, 3, Array{Float64, 3}})

# %%

0004/DArray{Float64, 3} is not a concrete type.jl


using Downloads

# WARNING: THIS FILE IS WORK-IN-PROGRESS

#--------------------------------------------------------------------
# Metadata info: dimensions
#--------------------------------------------------------------------
function listdimensions(apiurl::String, dataflow::String)

    io = IOBuffer()
    resp = Downloads.download(apiurl * "data/" * dataflow * "?detail=serieskeysonly&lastNObservations=1&format=jsondata", io) |> take!

    ds = JSON3.read(resp).structure.dimensions.series

    dim = OrderedDict()

    for dsᵢ in ds
        dim[dsᵢ.name] = NamedTuple(Symbol(j.id) => j.name for j in dsᵢ.values)
    end

    dim
end

#--------------------------------------------------------------------
# Basic Data download
#--------------------------------------------------------------------

"""Generates a SDMX API compliant url
#kwargs
    filter::Union{nothing,NamedTuple},
    updatedAfter::DateTime,
    firstNObservations::Int,
    lastNObservations::Int,
    dimensionAtObservation,
    attributes = "dsd",
    measures = "all",
    includeHistory = false
"""
function generateurl(;context = "*", agencyID = "*", resourceID = "*", version = "*", key::String="*", kwargs...) # TODO: substitute query args by kwargs...
    baseurl = join(["https://ws-entry-point/data/",context,agencyID,resourceID,version,key],"/")
    query = "/?"*join([String(k)*"="*kwargs[k] for k in keys(kwargs)],"&")
end

""" Generates key part of url from a dictionary"""
function generatekey(dims)
    key = ""
    for v in dims
        key = key*join[v,"+"]*"." # FIXME = sobraría un punto??
    end
end

""" Fetch data and creates a SDMX.Datatable"""
function getseries(url)
    io = IOBuffer()
    return Downloads.download(url, io) |> take! |> SDMX.read(alldims = false)
end


tests = ["arrviews",
         "contrank",
         "subviews",
         "linalg",
         "convenience"]

for t in tests
    fp = string(t, ".jl")
    println("* running $fp ...")
    include(fp)
end


## merge substitutions
#include("reso.jl")

# this will be used in proving with graph
# not yet implemented

"""
this merge may be a unfiy
"""
function merge(vars::Vlist, t1::Symbol, t2::Symbol)
 if t1==t2; return t1 end
 if isvar(t1,vars) && isvar(t2,vars)
   return t2
 elseif isvar(t1,vars)
   return t2
 elseif isvar(t2,vars)
   return t1
 else
   throw(ICMP(t1,t2,:merge))
 end
end

"""
merge unifys 2 to 1 for substituions
"""
function merge(vars::Vlist, sub1::Tlist, sub2::Tlist)
 if length(sub1) != length(sub2); throw(ICMP(sub1,sub2,:merge)) end
 s1=apply(vars,sub1,sub2) # is s1 == sub1??
 ss=apply(vars,sub2,s1)
 s2=apply(vars,sub1,ss)
 narg=[]
 for i in 1:length(sub1)
   mi = merge(vars, ss[i], s2[i])
   if ndims(sub1) == 1
     narg = vcat(narg,[mi])
   else
     narg = hcat(narg,[mi])
   end
 end
 narg
end


using LinearAlgebra
using Random
using Rimu
using Rimu.DictVectors
using Rimu.StochasticStyles: IsStochastic2Pop
using StaticArrays
using Suppressor
using Test
using ThreadsX

function test_dvec_interface(type, keys, vals, cap)
    K = eltype(keys)
    V = eltype(vals)
    pairs = [k => v for (k, v) in zip(keys, vals)]

    @testset "$type{$K,$V}" begin
        @testset "constructors" begin
            dvec1 = type(pairs...; capacity=cap)
            dvec2 = type(Dict(pairs...))
            for (k, v) in pairs
                @test dvec1[k] == dvec2[k] == v
            end
            @test dvec1 == dvec2

            dvec3 = type(Dict{K,V}(); capacity=cap)
            dvec4 = type{K,V}()

            @test isempty(dvec4)
            for (k, _) in pairs
                @test dvec3[k] == dvec4[k] == zero(V)
            end

            dvec5 = type(dvec2)
            @test dvec5 == dvec2

            dvec6 = type(IdDict(pairs))
            for (k, v) in pairs
                @test dvec6[k] == v
            end
        end
        @testset "empty, similar" begin
            dvec1 = type(pairs...; capacity=cap)
            dvec2 = empty(empty(empty(empty(dvec1))))
            dvec3 = similar(similar(similar(similar(dvec1))))
            @test typeof(dvec1) == typeof(dvec2) == typeof(dvec3)
            @test isempty(dvec2)
            @test isempty(dvec3)
            @test keytype(dvec1) == keytype(dvec2) == keytype(dvec3)
        end
        @testset "setindex, delete" begin
            dvec = type(pairs...; capacity=cap)
            @test length(dvec) == length(pairs)
            zero!(dvec)
            @test length(dvec) == 0
            for (k, v) in shuffle(pairs)
                dvec[k] = v
            end
            for (k, v) in shuffle(pairs)
                @test dvec[k] == v
                delete!(dvec, k)
                @test iszero(dvec[k])
            end
            @test isempty(dvec)
            for (k, v) in shuffle(pairs)
                dvec[k] += v
            end
            for (k, v) in shuffle(pairs)
                dvec[k] -= v
            end
            @test isempty(dvec)
        end
        @testset "types and traits" begin
            dvec = type(pairs...; capacity=cap)
            @test dvec isa AbstractDVec{K,V}

            @test eltype(dvec) ≡ Pair{K,V}
            @test eltype(typeof(dvec)) ≡ Pair{K,V}

            @test keytype(dvec) ≡ K
            @test keytype(typeof(dvec)) ≡ K

            @test valtype(dvec) ≡ V
            @test valtype(typeof(dvec)) ≡ V

            @test isreal(dvec) == (V <: Real)
            @test ndims(dvec) == 1
        end
        @testset "norm" begin
            dvec = type(Dict(pairs))
            for p in (1, 2, Inf)
                @test norm(dvec, p) == norm(vals, p)
            end
            @test norm(dvec) == norm(dvec, 2)
            @test_throws ErrorException norm(dvec, 3)

            @test norm(empty(dvec)) == 0
            @test norm(empty(dvec), 1) == 0
            @test norm(empty(dvec), 2) == 0
            @test norm(empty(dvec), Inf) == 0
        end
        @testset "copy" begin
            dvec1 = type(Dict(pairs))
            dvec2 = type{K,V}()

            copy!(dvec2, dvec1)
            for (k, v) in pairs
                @test dvec2[k] == v
            end

            dvec3 = copy(dvec1)
            empty!(dvec1)
            for (k, v) in pairs
                @test dvec3[k] == v
            end
        end
        @testset "mul!, *, rmul!" begin
            dvec = type(Dict(pairs))
            res1 = type{K,V}()
            mul!(res1, dvec, one(V))
            @test res1 == dvec
            mul!(res1, dvec, zero(V))
            @test isempty(res1)
            mul!(res1, dvec, V(2))
            res2 = V(2) * dvec
            res3 = dvec * V(2)
            @test res1 == res2 == res3
            for (k, v) in pairs
                @test res1[k] == 2v
            end

            rmul!(dvec, V(3))
            for (k, v) in pairs
                @test dvec[k] == 3v
            end
        end
        @testset "ThreadsX" begin
            dvec = type(Dict(pairs))
            @test ThreadsX.sum(values(dvec)) ≈ sum(values(dvec))
            @test ThreadsX.sum(last, Base.pairs(dvec)) ≈ sum(last, Base.pairs(dvec))
        end
        @testset "add!" begin
            dvec1 = type(Dict(pairs))
            dvec2 = type(Dict(pairs[1:2:end]))
            add!(dvec1, dvec2)
            for (i, (k, v)) in enumerate(pairs)
                if isodd(i)
                    @test dvec1[k] == 2v
                else
                    @test dvec1[k] == v
                end
            end

            copy!(dvec2, dvec1)
            add!(dvec1, type{K,V}())
            @test dvec1 == dvec2
        end
        @testset "axpy!" begin
            dvec1 = type(Dict(pairs))
            dvec2 = type(Dict(pairs[1:2:end]))
            axpy!(V(2), dvec1, dvec2)
            for (i, (k, v)) in enumerate(pairs)
                if isodd(i)
                    @test dvec2[k] == 3v
                else
                    @test dvec2[k] == 2v
                end
            end
        end
        @testset "axpby!" begin
            dvec1 = type(Dict(pairs))
            dvec2 = type(Dict(pairs[1:2:end]))
            axpby!(V(2), dvec1, V(3), dvec2)
            for (i, (k, v)) in enumerate(pairs)
                if isodd(i)
                    @test dvec2[k] == 5v
                else
                    @test dvec2[k] == 2v
                end
            end
        end
        @testset "dot" begin
            dvec1 = type(Dict(pairs))
            dvec2 = type(Dict(pairs[1:2:end]))
            dvec3 = type(Dict(pairs[2:2:end]))

            @test dvec1 ⋅ dvec1 ≈ norm(dvec1)^2
            @test dvec1 ⋅ dvec2 ≈ norm(dvec2)^2
            @test dot(dvec1, (dvec2, dvec3)) ≈ norm(dvec1)^2
        end
        @testset "iteration" begin
            dvec = type(Dict(pairs))

            dvec_pairs = [kv for kv in Base.pairs(dvec)]
            @test issetequal(pairs, dvec_pairs)

            dvec_keys = [k for k in Base.keys(dvec)]
            @test issetequal(dvec_keys, keys)

            dvec_vals = [k for k in Base.values(dvec)]
            @test issetequal(dvec_vals, vals)
        end
        @testset "projection" begin
            dvec = type(Dict(pairs))
            @test UniformProjector() ⋅ dvec == sum(dvec)
            @test UniformProjector()[2] == 1
            if valtype(dvec) isa AbstractFloat
                @test NormProjector() ⋅ dvec == norm(dvec, 1)
            end
            @test Norm2Projector() ⋅ dvec == norm(dvec, 2)
            @test Norm1ProjectorPPop() ⋅ dvec ==
                norm(real.(values(dvec)), 1) + im*norm(imag.(values(dvec)), 1)
            @test DictVectors.PopsProjector()⋅dvec ==
                real(collect(values(dvec))) ⋅ imag(collect(values(dvec)))
        end
        @testset "show" begin
            h, _ = displaysize()
            @test length(split(sprint(show, type(Dict(pairs))), '\n')) < h
        end
    end

    @testset "StochasticStyle" begin
        @test StochasticStyle(type(:a => 1)) isa IsStochasticInteger{Int}
        @test StochasticStyle(type(:a => 1.5; capacity=5)) isa IsDeterministic
        @test StochasticStyle(type(:a => 1 + 2im; capacity=5)) isa IsStochastic2Pop
        @test StochasticStyle(type(:a => SA[1 1; 1 1]; capacity=5)) isa StyleUnknown
    end
end

@testset "DVec" begin
    @testset "interface tests" begin
        keys1 = shuffle(1:20)
        vals1 = shuffle(1:20)
        test_dvec_interface(DVec, keys1, vals1, 100)

        keys2 = ['x', 'y', 'z', 'w', 'v']
        vals2 = [1.0 + 2.0im, 3.0 - 4.0im, 0.0 - 5.0im, -2.0 + 0.0im, 12.0 + im]
        test_dvec_interface(DVec, keys2, vals2, 200)
    end

    @testset "Stochastic styles convert eltype" begin
        dvec1 = DVec(:a => 0f0)
        @test valtype(dvec1) === Float32
        @test StochasticStyle(dvec1) == IsDeterministic{Float32}()

        dvec2 = DVec(:a => 1, style=IsDynamicSemistochastic())
        @test dvec2[:a] isa Float64

        dvec3 = DVec(:a => 1.0, style=IsStochasticInteger())
        @test dvec3 isa DVec{Symbol,Int}

        dvec4 = DVec(:a => 1.0, style=IsStochastic2Pop())
        @test !isreal(dvec4)
    end
end

@testset "InitiatorDVec" begin
    @testset "interface tests" begin
        keys1 = shuffle(1:20)
        vals1 = shuffle(1:20)
        test_dvec_interface(InitiatorDVec, keys1, vals1, 100)

        keys2 = ['x', 'y', 'z', 'w', 'v']
        vals2 = [1.0 + 2.0im, 3.0 - 4.0im, 0.0 - 5.0im, -2.0 + 0.0im, 12.0 + im]
        test_dvec_interface(InitiatorDVec, keys2, vals2, 200)
    end

    @testset "Stochastic styles convert eltype" begin
        dvec1 = InitiatorDVec(:a => 0f0)
        @test valtype(dvec1) === Float32
        @test StochasticStyle(dvec1) == IsDeterministic{Float32}()

        dvec2 = InitiatorDVec(:a => 1, style=IsDynamicSemistochastic())
        @test dvec2[:a] isa Float64

        dvec3 = InitiatorDVec(:a => 1.0, style=IsStochasticInteger())
        @test dvec3 isa InitiatorDVec{Symbol,Int}

        dvec4 = InitiatorDVec(:a => 1.0, style=IsStochastic2Pop())
        @test !isreal(dvec4)
    end
end


function get_eop_history(obj::TEMEProvider)
    return jcall(obj, "getEOPHistory", EOPHistory, ())
end

function get_non_interpolating_provider(obj::TEMEProvider)
    return jcall(obj, "getNonInterpolatingProvider", TEMEProvider, ())
end

function get_transform(obj::TEMEProvider, arg0::AbsoluteDate)
    return jcall(obj, "getTransform", Transform, (AbsoluteDate,), arg0)
end

function get_transform(obj::TEMEProvider, arg0::FieldAbsoluteDate)
    return jcall(obj, "getTransform", FieldTransform, (FieldAbsoluteDate,), arg0)
end


"""
	anyoverlapping(a::GenomicFeatures.Interval{T},
	b::GenomicFeatures.IntervalCollection{S})

Extend GenomicFeatures.isoverlapping to linearly check if interval a.
overlaps collection. Return true or false

```jldoctest
using GenomicFeatures
a = GenomicFeatures.Interval("chr1", 0, 10)
b = GenomicFeatures.IntervalCollection([
	GenomicFeatures.Interval("chr1", 5, 15),
	GenomicFeatures.Interval("chr1", 20, 25)
	])
anyoverlapping(a, b)

# output

true
```

See Also [`GenomePermutations.alloverlapping`](@ref), [`GenomePermutations.countoverlapping`](@ref).
"""
function anyoverlapping(a::GenomicFeatures.Interval{T}, b::GenomicFeatures.IntervalCollection{S}) where {T, S}
	for interval in b
		GenomicFeatures.isoverlapping(a, interval) && return(true::Bool)
	end
	return(false::Bool)
end

"""
	alloverlapping(a::GenomicFeatures.IntervalCollection{T}, b::GenomicFeatures.IntervalCollection{S})

Linearly check if all intervals in collection a overlap collection b.
Return true or false

```jldoctest
using GenomicFeatures
a = GenomicFeatures.IntervalCollection([
	GenomicFeatures.Interval("chr1", 14, 17),
	GenomicFeatures.Interval("chr1", 20, 25)
	])
b = GenomicFeatures.IntervalCollection([
	GenomicFeatures.Interval("chr1", 5, 10),
	GenomicFeatures.Interval("chr1", 20, 25)
	])
alloverlapping(a, b)

# output

false
```

See Also [`GenomePermutations.anyoverlapping`](@ref), [`GenomePermutations.countoverlapping`](@ref).
"""
function alloverlapping(a::GenomicFeatures.IntervalCollection{T}, b::GenomicFeatures.IntervalCollection{S}) where {T,S}
	for interval in a
		anyoverlapping(interval, b) || return(false::Bool)
	end
	return(true::Bool)
end

"""
	countoverlapping(a::GenomicFeatures.IntervalCollection{T} , b::GenomicFeatures.IntervalCollection{T})

Linearly count how many intervals in collection a overlap with collection b.

```jldoctest

using GenomicFeatures
a = GenomicFeatures.IntervalCollection([
	GenomicFeatures.Interval("chr1", 14, 17),
	GenomicFeatures.Interval("chr1", 20, 50)
	])
b = GenomicFeatures.IntervalCollection([
	GenomicFeatures.Interval("chr1", 5, 10),
	GenomicFeatures.Interval("chr1", 25, 35 ),
	GenomicFeatures.Interval("chr1", 40, 65)
	])

[countoverlapping(a, b), countoverlapping(b, a)]

# output

2-element Vector{Int64}:
 1
 2
```

#Note
As seen above, countoverlapping(a,b) != countoverlapping(b,a) as one interval in a
can overlap with >1 intervals in b.

See Also [`GenomePermutations.anyoverlapping`](@ref), [`GenomePermutations.alloverlapping`](@ref).
"""
function countoverlapping(
	a::GenomicFeatures.IntervalCollection{T},
	b::GenomicFeatures.IntervalCollection{S}
) where {T,S}

	overlaps::Int = 0
	for interval in a
		anyoverlapping(interval, b) && (overlaps += 1)
	end
	return(overlaps)
end

"""
	isin(a::GenomicFeatures.Interval{S}, GenomicFeatures.Interval{T})

Check if interval a is fully contained in interval b.

```jldoctest
using GenomicFeatures
a = GenomicFeatures.Interval("chr1", 5, 10)
b = GenomicFeatures.Interval("chr1", 1, 15)

isin(a, b)

# output

true
```

```jldoctest
using GenomicFeatures
a = GenomicFeatures.Interval("chr1", 20, 30)
b = GenomicFeatures.Interval("chr1", 1, 15)

isin(a, b)

# output

false
```

```jldoctest
using GenomicFeatures
a = GenomicFeatures.Interval("chr1", 10, 20)
b = GenomicFeatures.Interval("chr1", 1, 15)

isin(a, b)

# output

false
```
"""
function isin(a::GenomicFeatures.Interval{S},b::GenomicFeatures.Interval{T}) where {S,T}
	return (a.seqname == b.seqname && a.first >= b.first && a.last <= b.last)
end

"""
	isin(a::GenomicFeatures.Interval{T}, b::GenomicFeatures.IntervalCollection{S})

Linearly check if interval a is fully contained in collection b.
"""
function isin(a::GenomicFeatures.Interval{S},
				 b::GenomicFeatures.IntervalCollection{T}) where {S,T}

	for interval in b
		isin(a, interval) && return(true::Bool)
	end
	return(false::Bool)
end

"""
	isin(a:::GenomicFeatures.IntervalCollection{T}, b:::GenomicFeatures.IntervalCollection{S})

Linearly check if all intervals in collection a are contained in collection b.

# Note

At this point there are 2 layers of linear search so the time complexity is n^2.
"""
function isin(a::GenomicFeatures.IntervalCollection{T},
				 b::GenomicFeatures.IntervalCollection{S}) where {T,S}

	for interval in a
		isin(interval, b) || return(false::Bool)
	end
	return(true::Bool)
end


function test_getters()
    model = MetNets.toy_model()
    @test MetNets.mets(model) == model.mets
    @test MetNets.rxns(model) == model.rxns

    for (i, rxn) in enumerate(MetNets.rxns(model))
        @test MetNets.ub(model, rxn) == model.ub[i]
        @test MetNets.ub(model, i) == model.ub[i]
        @test MetNets.lb(model, rxn) == model.lb[i]
        @test MetNets.lb(model, i) == model.lb[i]
        @test MetNets.rxns(model, i) == model.rxns[i]
        @test MetNets.rxns(model, rxn) == rxn
    end

    for (i, met) in enumerate(MetNets.mets(model))
        @test MetNets.mets(model, i) == model.mets[i]
        @test MetNets.mets(model, met) == model.mets[i]
    end

end
test_getters()


abstract type MonteCarloSampler <: Sampler end
abstract type FullSpaceSampler <: Sampler end

parallel_type(s::Sampler) = true

abstract type SamplerCache{T}  end

"""
    caches(sampler, v, net, [parallel_type=NotParallel()])

Creates a `SamplerCache` for this sampler and state.
"""
cache(s::Sampler, hilb::AbstractHilbert, net, par=NotParallel()) =
    _sampler_cache(s, state(hilb, net), hilb, net, par)

cache(s::Sampler, σ, hilb::AbstractHilbert, net, par=NotParallel()) =
    _sampler_cache(s, σ, hilb, net, par)

"""
    init_sampler!(sampler, net, σ, [c=cache(sampler, σ, net)]) -> c

Initializes the sampler `sampler` and state `σ`. If no `SamplerCache` is
provided, one will be initialized and returned. The state σ is the first in the
list of sampled states.
"""
init_sampler!(s::Sampler, net, hilb::AbstractHilbert, σ, par_cache::ParallelType=NotParallel()) =
    init_sampler!(s, net, σ, cache(s, σ, hilb, net, par_cache))

"""
    chain_length(sampler, sampler_cache) -> Int

Returns the estimated length of the chain.
"""
function chain_length end

"""
    sampling_function(s::SamplerCache) -> function

Returns the sampling function. By default it's 2.0*real().
"""
function sampling_function end

samplenext!(σ, sampl::Sampler, net, sampler_cache) =
    samplenext!(σ, σ, sampl, net, sampler_cache)


module InvertedIndices

export InvertedIndex, Not

using Base: tail

"""
    InvertedIndex(idx)
    Not(idx)

Construct an inverted index, selecting all indices not in the passed `idx`.

Upon indexing into an array, the `InvertedIndex` behaves like a 1-dimensional
collection of the indices of the array that are not in `idx`. Bounds are
checked to ensure that all indices in `idx` are within the bounds of the array
— even though they are skipped. The `InvertedIndex` behaves like a
1-dimensional collection of its inverted indices. If `idx` spans multiple
dimensions (like a multidimensional logical mask or `CartesianIndex`), then the
inverted index will similarly span multiple dimensions.
"""
struct InvertedIndex{S}
    skip::S
end
const Not = InvertedIndex

# Like Base.LogicalIndex, the InvertedIndexIterator is a pseudo-vector that is
# just used as an iterator and does not support getindex.
struct InvertedIndexIterator{T,S,P} <: AbstractVector{T}
    skips::S
    picks::P
end
InvertedIndexIterator(skips, picks) = InvertedIndexIterator{eltype(picks), typeof(skips), typeof(picks)}(skips, picks)
Base.size(III::InvertedIndexIterator) = (length(III.picks) - length(III.skips),)

@inline Base.iterate(I::InvertedIndexIterator) = iterate(I, (iterate(I.skips), iterate(I.picks)))
Base.iterate(I::InvertedIndexIterator, ::Tuple{Any, Nothing}) = nothing
@inline function Base.iterate(I::InvertedIndexIterator, (skipitr, pickitr))
    while should_skip(skipitr, pickitr)
        skipitr = iterate(I.skips, tail(skipitr)...)
        pickitr = iterate(I.picks, tail(pickitr)...)
        pickitr === nothing && return nothing
    end
    return (pickitr[1], (skipitr, iterate(I.picks, tail(pickitr)...)))
end
Base.collect(III::InvertedIndexIterator) = [i for i in III]

should_skip(::Nothing, ::Any) = false
should_skip(s::Tuple, p::Tuple) = _should_skip(s[1], p[1])
_should_skip(s, p) = s == p
_should_skip(s::Integer, p::CartesianIndex{1}) = s == p.I[1]
_should_skip(s::CartesianIndex{1}, p::Integer) = s.I[1] == p

@inline Base.checkbounds(::Type{Bool}, A::AbstractArray, I::InvertedIndexIterator{<:Integer}) =
    checkbounds(Bool, A, I.skips) && eachindex(IndexLinear(), A) == eachindex(IndexLinear(), I.picks)
@inline Base.checkbounds(::Type{Bool}, A::AbstractArray, I::InvertedIndexIterator) = checkbounds(Bool, A, I.skips) && axes(A) == axes(I.picks)
@inline Base.checkindex(::Type{Bool}, indx::AbstractUnitRange, I::InvertedIndexIterator) = checkindex(Bool, indx, I.skips) && (indx,) == axes(I.picks)
@inline Base.checkindex(::Type{Bool}, inds::Tuple, I::InvertedIndexIterator) = checkindex(Bool, indx, I.skips) && inds == axes(I.picks)

@inline Base.ensure_indexable(I::Tuple{InvertedIndexIterator, Vararg{Any}}) = (collect(I[1]), Base.ensure_indexable(tail(I))...)

Base.show(io::IO, I::InvertedIndexIterator) = show(io, collect(I))

# We use a custom sort method that supports Base.LogicalIndex and CartesianIndex
sort(A::AbstractArray) = Base.sort(A)
sort(A::Union{Number,CartesianIndex,Base.LogicalIndex}) = A

@inline function Base.to_indices(A, inds, I::Tuple{InvertedIndex, Vararg{Any}})
    new_indices = to_indices(A, inds, (I[1].skip, tail(I)...))
    skips = sort(new_indices[1])
    picks = spanned_indices(inds, skips)[1]
    return (InvertedIndexIterator(skips, picks), tail(new_indices)...)
end

struct ZeroDArray{T} <: AbstractArray{T,0}
    x::T
end
Base.size(::ZeroDArray) = ()
Base.getindex(Z::ZeroDArray) = Z.x

# Be careful with CartesianIndex as they splat out to a variable number of new indices and do not iterate
function Base.to_indices(A, inds, I::Tuple{InvertedIndex{<:CartesianIndex}, Vararg{Any}})
    new_indices = to_indices(A, inds, (I[1].skip, tail(I)...))
    skips = ZeroDArray(I[1].skip)
    picks, tails = spanned_indices(inds, skips)
    return (InvertedIndexIterator(skips, picks), to_indices(A, tails, tail(I))...)
end

# Either return a CartesianRange or an axis vector
@inline spanned_indices(inds, ::Any) = inds[1], tail(inds)
const NIdx{N} = Union{CartesianIndex{N}, AbstractArray{CartesianIndex{N}}, AbstractArray{Bool,N}}
@inline spanned_indices(inds, ::NIdx{0}) = CartesianIndices(()), inds
@inline spanned_indices(inds, ::NIdx{1}) = inds[1], tail(inds)
@inline function spanned_indices(inds, ::NIdx{N}) where N
    heads, tails = Base.IteratorsMD.split(inds, Val(N))
    return CartesianIndices(heads), tails
end

# It's possible more indices were specified than there were axes
@inline spanned_indices(inds::Tuple{}, ::Any) = Base.OneTo(1), ()
@inline spanned_indices(inds::Tuple{}, ::NIdx{0}) = CartesianIndices(()), ()
@inline spanned_indices(inds::Tuple{}, ::NIdx{1}) = Base.OneTo(1), ()
@inline spanned_indices(inds::Tuple{}, ::NIdx{N}) where N = CartesianIndices(ntuple(i->Base.OneTo(1), N)), ()

# This is an interesting need — we need this because otherwise indexing with a
# single multidimensional boolean array ends up comparing a multidimensional cartesian
# index to a linear index. Does this need addressing in Base, too?
@inline Base.to_indices(A, I::Tuple{Not{<:NIdx{1}}}) = to_indices(A, (eachindex(IndexLinear(), A),), I)
@inline Base.to_indices(A, I::Tuple{Not{<:NIdx}}) = to_indices(A, axes(A), I)
# Arrays of Bool are even more confusing as they're sometimes linear and sometimes not
@inline Base.to_indices(A, I::Tuple{Not{<:AbstractArray{Bool, 1}}}) = to_indices(A, (eachindex(IndexLinear(), A),), I)
@inline Base.to_indices(A, I::Tuple{Not{<:Union{Array{Bool}, BitArray}}}) = to_indices(A, (eachindex(A),), I)

end # module


ENV["GKSwstype"]="100"

using DifferentiableNAS
using Flux
using Flux: throttle, logitcrossentropy, onecold, onehotbatch
using StatsBase
using Parameters
using CUDA
using Distributions
using BSON
using Plots
using Plots.PlotMeasures
include("CIFAR10.jl")

@with_kw struct trial_params
    epochs::Int = 50
    batchsize::Int = 32
    throttle_::Int = 20
    val_split::Float32 = 0.5
    trainval_fraction::Float32 = 1.0
    test_fraction::Float32 = 1.0
end

C(g::ColorGradient) = RGB[g[z] for z=LinRange(0,1,30)]
g = :inferno
color = cgrad(g) |> C

#acts dims: cellid x oplayer x image
folder_name = "test/models/osirim/darts_2021-01-04T22:44:32.328"
BSON.@load joinpath(folder_name,"histbatch.bson") histbatch
BSON.@load joinpath(folder_name,"histepoch.bson") histepoch

datadict = Dict{String, Array{Float32,4}}()
datadictepoch = Dict{String, Array{Float32,4}}()

epochs = length(histepoch.activations)
images = sum([size(d["3-6-sep_conv_3x3"],3) for d in histbatch.activations]) ÷ epochs

for (k,v) in histbatch.activations[1]
    datadict[k] = Array{Float32,4}(undef, length(histbatch.activations), size(v)...)
    datadictepoch[k] = Array{Float32,4}(undef, length(histepoch.activations), size(v,1), size(v,2), images)
end

e = 1
ep = 1
for (i, batch) in enumerate(histbatch.activations)
    for (k,v) in batch
        a = 1
        while a < 32
            datadict[k][i,:,:,a:size(v,3)+a-1] .= v #batch x cellid x oplayer x image
            a += size(v,3)
        end
        datadictepoch[k][ep,:,:,e:size(v,3)+e-1] .= v #epoch x cellid x oplayer x image
    end
    e += size(batch["3-6-sep_conv_3x3"],3)
    if e > images
        e = 1
        @show ep += 1
    end
end

layerdict = Dict(
    "max_pool_3x3" => [""],
    "avg_pool_3x3" => [""],
    "skip_connect" => [""],
    "sep_conv_3x3" => ["relu", "3x3conv", "1x1conv", "relu", "3x3conv", "1x1conv"],
    "sep_conv_5x5" => ["relu", "5x5conv", "1x1conv", "relu", "5x5conv", "1x1conv"],
    "dil_conv_3x3" => ["relu", "3x3dilconv", "3x3conv"],
    "dil_conv_5x5" => ["relu", "5x5dilconv", "5x5conv"]
)
ops = sort(collect(keys(datadict)), by = x -> (x[5:6], x[1], x[3]))
connects = vcat([[(j,i) for j = 1:i-1] for i = 3:6]...)
a_conn = [collect(zip(connects, a)) for a in histbatch.normal_αs]
sortin = sort(connects)
sorta = [[s[2] for s in sort(a)] for a in a_conn]

for (j, op) in enumerate(PRIMITIVES[2:length(PRIMITIVES)])
    layers = size(datadict[string("2-3-", op)],3)
    p = Vector(undef, layers+1)
    p[1] = plot(title = string(op, " alphas"), legend = :outertopright, left_margin = 10mm)
    for n = 1:14
        plot!([softmax(a[n])[j+1] for a in sorta], label=string(sortin[n][1],"->",sortin[n][2]))
    end
    for i = 1:layers
        p[i+1] = plot(title = string(op, " ",layerdict[op][i]))
        for (n1, n2) in sortin
            #cellop = string(n1,"-",n2,"-",op)
            #plot!(datadictepoch[cellop][1,:,i,:], label=string(n1,"->",n2), legend = :outerright, left_margin = 10mm)
            hdata = dropdims(mean(datadictepoch["4-6-dil_conv_3x3"][:,:,2,:], dims = 2), dims = 2)
            bins = minimum(hdata):0.01:maximum(hdata)
            h = hcat([fit(Histogram, hdata[i,:], bins).weights for i in 1:epochs]...)
            ticks = [(bins[i]+bins[i+1])/2 for i in 1:length(bins)-1]
            heatmap(1:epochs, ticks, h, xlabel="epoch", ylabel="spatial+channel+cell meaned activation", title="4-6-dil_conv_3x3, 3x3conv layer")
        end
    end
    plot(p..., layout = (layers+1,1), size = (1200, 400*(layers+1)))
    savefig(joinpath(folder_name,string("fig_", op, ".png")));
end

#heatmap(datadictepoch["3-6-dil_conv_3x3"][:,4,2,:]', xlabel="epoch", ylabel="image", title="3-6-dil_conv_3x3, cell 3, dilconv layer")
hdata = dropdims(mean(datadictepoch["4-6-sep_conv_3x3"][:,:,2,:], dims = 2), dims = 2)
bins = minimum(hdata):0.001:maximum(hdata)
h = hcat([fit(Histogram, hdata[i,:], bins).weights for i in 1:epochs]...)
ticks = [(bins[i]+bins[i+1])/2 for i in 1:length(bins)-1]
heatmap(1:epochs, ticks, h, xlabel="epoch", ylabel="spatial+channel+cell meaned activation", title="4-6-dil_conv_3x3, dilconv layer")

hdata = datadict["4-6-sep_conv_3x3"][:,4,5,:]
bins = minimum(hdata):0.01:maximum(hdata)
h = hcat([fit(Histogram, hdata[i,:], bins).weights for i in 1:size(datadict["4-6-sep_conv_3x3"][:,4,2,:],1)]...)
ticks = [(bins[i]+bins[i+1])/2 for i in 1:length(bins)-1]
heatmap(1:size(datadict["4-6-sep_conv_3x3"][:,4,2,:],1), ticks, h, xlabel="batch", ylabel="spatial+channel meaned activation", title="4-6-sep_conv_3x3, sepconv layer")

"""
n_y_min = minimum([softmax(a[i])[j] for a in histbatch.normal_αs for i in 1:14 for j in 1:8])
n_y_max = maximum([softmax(a[i])[j] for a in histbatch.normal_αs for i in 1:14 for j in 1:8])
p = Vector(undef, 14)
for i = 1:14
    p[i] = plot(title = string("Op ",connects[i][1],"->",connects[i][2]), ylim=(n_y_min,n_y_max), legend=false)
    for j = 1:8
        plot!([softmax(a[i])[j] for a in histbatch.normal_αs], label=PRIMITIVES[j])
    end
end
plot(p..., layout = (2,7), size = (2200,600));
savefig(joinpath(folder_name,"fig_n.png"))

r_y_min = minimum([softmax(a[i])[j] for a in histbatch.reduce_αs for i in 1:14 for j in 1:8])
r_y_max = maximum([softmax(a[i])[j] for a in histbatch.reduce_αs for i in 1:14 for j in 1:8])
p = Vector(undef, 14)
for i = 1:14
    p[i] = plot(title = string("Op ",connects[i][1],"->",connects[i][2]), ylim=(r_y_min,r_y_max), legend=false)
    for j = 1:8
        plot!([softmax(a[i])[j] for a in histbatch.reduce_αs], label=PRIMITIVES[j])
    end
end
plot(p..., layout = (2,7), size = (2200,600));
savefig(joinpath(folder_name,"fig_r.png"))

#normal_ = m_cpu.normal_αs
#reduce_ = m_cpu.reduce_αs
#BSON.@save "test/models/alphas09.58.bson" normal_ reduce_ histepoch histbatch argparams
"""


# *********************************************************************************
# 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.
# *********************************************************************************

function plot_electric_dispatch(d::Dict; title="Electric Systems Dispatch", save_html=false)

    traces = PlotlyJS.GenericTrace[]

    layout = PlotlyJS.Layout(
        title_text = title,
        xaxis_title_text = "time step",
        yaxis_title_text = "kW"
    )

    eload = d["ElectricLoad"]["load_series_kw"]
    T = length(eload)

    push!(traces, PlotlyJS.scatter(
        name = "total load",
        x = 1:T,
        y = d["ElectricLoad"]["load_series_kw"],
        fill = "none",
        line = PlotlyJS.attr(
            width = 3
        ),
    ))

    push!(traces, PlotlyJS.scatter(
        name = "grid supply",
        x = 1:T,
        y = d["ElectricUtility"]["year_one_to_load_series_kw"],
        fill = "tozeroy",
        marker = PlotlyJS.attr(
            color="rgb(12,12,12)",
        ),
        line = PlotlyJS.attr(
            width = 0
        ),
    ))
    # invisible line for stacking
    push!(traces, PlotlyJS.scatter(
        name = "invisible",
        x = 1:T,
        y = d["ElectricUtility"]["year_one_to_load_series_kw"],
        fill = Nothing,
        line = PlotlyJS.attr(
            width = 0
        ),
        showlegend = false,
        hoverinfo = "skip",
    ))

    pv_to_load = zeros(T)
    if "PV" in keys(d)  # TODO multiple PVs
        pv_to_load = d["PV"]["year_one_to_load_series_kw"]
        push!(traces, PlotlyJS.scatter(
            name = "PV+grid supply",
            x = 1:T,
            y = pv_to_load .+ d["ElectricUtility"]["year_one_to_load_series_kw"],
            fill = "tonexty",
            marker = PlotlyJS.attr(
                color="rgb(255, 127, 14)",
            ),
            line = PlotlyJS.attr(
                width = 0
            ),
        ))
    end

    layout = PlotlyJS.Layout()

    if "ElectricStorage" in keys(d)
        # invisible line for stacking
        push!(traces, PlotlyJS.scatter(
            name = "invisible",
            x = 1:T,
            y = d["ElectricUtility"]["year_one_to_load_series_kw"] .+ pv_to_load,
            fill = Nothing,
            line = PlotlyJS.attr(
                width = 0
            ),
            showlegend = false,
            hoverinfo = "skip",
        ))
        push!(traces, PlotlyJS.scatter(
            name = "battery+PV+grid supply",
            x = 1:T,
            y = d["ElectricUtility"]["year_one_to_load_series_kw"] .+ pv_to_load .+ d["ElectricStorage"]["year_one_to_load_series_kw"],
            fill = "tonexty",
            marker = PlotlyJS.attr(
                color="rgb(44, 160, 44)",
            ),
            line = PlotlyJS.attr(
                width = 0
            ),
        ))
        push!(traces, PlotlyJS.scatter(
            name = "ElectricStorage SOC",
            x = 1:T,
            y = d["ElectricStorage"]["year_one_soc_series_pct"],
            yaxis="y2",
            marker = PlotlyJS.attr(
                color="rgb(100,100,100)",
            ),
        ))

        layout = PlotlyJS.Layout(
            title_text = title,
            xaxis_title_text = "time step",
            yaxis_title_text = "kW",
            yaxis2 = PlotlyJS.attr(
                title = "SOC",
                overlaying = "y",
                side = "right"
            )
        )
    end
    p = PlotlyJS.plot(traces, layout)

    if save_html
        PlotlyJS.savefig(p, replace(title, " " => "_") * ".html")
    end

    PlotlyJS.plot(traces, layout)  # will not produce plot in a loop

end


function write_netcdf2d_reg(fname, var, lon, lat, varname, filenc)
    (nsx,) = size(lon)
    (nsy,) = size(lat)
    (ns, ns, nt) = size(var)

    mode = NC_NETCDF4

    ncin = NetCDF.open(filenc)
    lonatt = ncin.vars["lon"].atts
    latatt = ncin.vars["lat"].atts
    timeatt = ncin.vars["time"].atts
    varatt = ncin.vars[varname].atts
    tim = NetCDF.readvar(ncin,"time")

    #ncgetatt(filenc, "global", gatts)

    varattr = Dict()
    for k in collect(keys(varatt))
        if((k!="_FillValue")&&(k!="add_offset")&&(k!="scale_factor"))
            varattr[k] = varatt[k]
        end
    end

    isfile(fname) ? rm(fname) : nothing
    nccreate(fname, varname , "lon" , nsx, "lat", nsy, "time", tim[1:nt],
             timeatt, atts=varattr,mode=mode,t=NC_FLOAT)
    nccreate(fname, "lon" , "lon" , nsx,  atts=lonatt, mode=mode, t=NC_FLOAT)
    nccreate(fname, "lat" , "lat" , nsy,  atts=latatt, mode=mode, t=NC_FLOAT)

    ncwrite(var, fname, varname)
    ncwrite(lon, fname, "lon")
    ncwrite(lat, fname, "lat")
end