include("src/init.jl")# Include all relevant functions

"""
This file contains the script to execute learning containing
the Value and Critic function in seperate Networks
    => Extra for the NIPS challenge
    https://www.crowdai.org/challenges/nips-2018-ai-for-prosthetics-challenge
"""

θa = init_θ_single_norm(params.structure_net_pl)
θv = init_θ_single_norm(params.structure_net_vf)
best_net = init_θ_single_norm(params.structure_net_pl)
res = zeros(params.n_iter)
prec = zeros(params.n_iter)
all_traj  = init_all_traj(params)

#Global Variable of Environment!
@everywhere @pyimport osim.env as osim
@everywhere env = osim.ProstheticsEnv(visualize=true)
#@everywhere env = osim.L2RunEnv(visualize=true)

sleep(1)

println("-----------------------")
println("         start         ")
println("-----------------------")

function actor_critic_single(params::Params, θa::Any, θv::Any, res::Array{Float64,1}, prec::Array{Float64,1}, all_traj::Traj_Data, best_net::Any)
    #Init Part:
    best_net = copy(θa)
    min_cost = Inf
    #------------------------
    #Initialize the Policy
    ĝ_a = copy(θa)
    opt_para_a = optimizers(θa,   Adam; lr = params.lr_pl)

    #Initialize the Value Function
    ĝ_v = copy(θv)
    opt_para_v = optimizers(θv,   Adam; lr = params.lr_vf)

    #Init Progress bar
    progress = Progress(params.n_iter,1)

    # Init rms
    rms_all = init_rms(params.s_init)
    rms_loc = init_rms(params.s_init)

    #saving old data to see differences
    a_t_old = all_traj.traj[1].a_t
    weights_old = zeros(length(net_to_vec(θa)),2)

    for i_iter = 1:params.n_iter
        ĝ_a *= 0
        ĝ_v *= 0

        #execute the parallel_episodes
        (ĝ_a, ĝ_v, res[i_iter], prec[i_iter]) = parallel_episodes_skeleton(params,θa,θv,all_traj,rms_all)

        #update RMS:
        #=
        if i_iter == 1
            two_pass_variance!(all_traj, rms_all, params)
        else
            two_pass_variance!(all_traj, rms_loc, params)
            update_variance!(rms_all,rms_loc)
        end
        =#

        #Reorganice Data in minibatches
        mini_batch = build_minibatches(all_traj,params)

        #First refit the value function for new data batch:
        θv = refit_VF(params, θv, mini_batch, opt_para_v)

        #Now apply the gae for the new data based on updated value function
        gae_all!(all_traj,θv,params)

        #Reorganice Data in minibatches as advantages have changed...
        mini_batch = build_minibatches(all_traj,params)

        #Do the Policy / and Value updates
        (dump, θv) = parallel_PPO(params, θa, θv, mini_batch, opt_para_a, opt_para_v)

        #ĝ_a = parallel_gradient(params, θa, mini_batch)
        #θa  = do_TRPO_update(ĝ_a, mini_batch, θa, params)

        if min_cost > res[i_iter]
            min_cost = res[i_iter]
            best_net = copy(θa)
            println("new_best: ",min_cost)
        end

        #Visualization_stuff
        #-----------------------------------------------------------------------
        ProgressMeter.update!(progress,i_iter) #update progress bar
        print(" ",res[i_iter])
        a_t_old ,weights_old = plot_stuff(params, all_traj, a_t_old,
        weights_old, i_iter,θa, θv, res, prec/params.n_traj, ĝ_a, ĝ_v)
        #-----------------------------------------------------------------------

        @everywhere GC.gc(true)
    end
end

actor_critic_single(params, θa, θv, res, prec, all_traj, best_net)
save_data(res, prec, θa, θv, all_traj)


"""
Reference Sequence

Reference sequence is a sequence of A/C/G/T/N. In the internals, it compresses
`N` positions and consumes less than three bits per base. Unlike `BioSequence`
in Bio.jl, reference sequences are immutable and hence no modifyting
operators are provided.
"""
immutable ReferenceSequence <: Sequence
    data::Vector{UInt64}
    nmask::NMask
    part::UnitRange{Int}
end

function ReferenceSequence{T<:Integer}(seq::ReferenceSequence, part::UnitRange{T})
    ReferenceSequence(seq.data, seq.nmask, part)
end

function Base.convert{A<:DNAAlphabet}(::Type{ReferenceSequence}, seq::BioSequence{A})
    data = Vector{UInt64}(cld(length(seq), 32))
    nmask = falses(length(seq))
    i = 1
    for j in 1:endof(data)
        x = UInt64(0)
        r = 0
        while r < 64 && i ≤ endof(seq)
            nt = seq[i]
            if nt ≤ DNA_T
                x |= convert(UInt64, nt) << r
            elseif nt == DNA_N
                nmask[i] = true
            else
                throw(ArgumentError("found a invalid symbol $(seq[i]) ∉ {A,C,G,T,N} at $i"))
            end
            i += 1
            r += 2
        end
        data[j] = x
    end
    return ReferenceSequence(data, NMask(nmask), 1:length(seq))
end

function Base.convert(::Type{ReferenceSequence}, seq::AbstractString)
    return ReferenceSequence(DNASequence(seq))
end

function Base.convert(::Type{DNASequence}, seq::ReferenceSequence)
    bioseq = DNASequence(length(seq))
    for i in 1:endof(seq)
        bioseq[i] = seq[i]
    end
    return bioseq
end

function Base.convert{S<:AbstractString}(::Type{S}, seq::ReferenceSequence)
    return S([Char(nt) for nt in seq])
end

Base.endof(seq::ReferenceSequence) = length(seq.part)
Base.length(seq::ReferenceSequence) = length(seq.part)
Base.isempty(seq::ReferenceSequence) = length(seq) == 0

function Base.checkbounds(seq::ReferenceSequence, i::Integer)
    if 1 ≤ i ≤ endof(seq)
        return true
    end
    throw(BoundsError(i))
end

function Base.checkbounds(seq::ReferenceSequence, part::UnitRange)
    if 1 ≤ part.start && part.stop ≤ endof(seq)
        return true
    end
    throw(BoundsError(part))
end

@inline function Base.getindex(seq::ReferenceSequence, i::Integer)
    checkbounds(seq, i)
    return unsafe_getindex(seq, i)
end

@inline function unsafe_getindex(seq::ReferenceSequence, i::Integer)
    j = Int(i) + seq.part.start - 2
    d, r = divrem32(j)
    @inbounds begin
        if seq.nmask[j+1]
            return DNA_N
        else
            return DNANucleotide((seq.data[d+1] >> 2r) & 0b11)
        end
    end
end

function Base.getindex{T<:Integer}(seq::ReferenceSequence, part::UnitRange{T})
    checkbounds(seq, part)
    return ReferenceSequence(seq, part)
end

divrem32(i) = i >> 5, i & 0b11111

Base.eltype(::Type{ReferenceSequence}) = DNANucleotide

# iterator
Base.start(seq::ReferenceSequence) = 1
Base.done(seq::ReferenceSequence, i) = i > endof(seq)
Base.next(seq::ReferenceSequence, i) = unsafe_getindex(seq, i), i + 1

# Comparison
# ----------

function Base.(:(==))(seq1::ReferenceSequence, seq2::ReferenceSequence)
    if length(seq1) != length(seq2)
        return false
    end
    for (x, y) in zip(seq1, seq2)
        if x != y
            return false
        end
    end
    return true
end

function Base.(:(==)){A<:DNAAlphabet}(seq1::ReferenceSequence, seq2::BioSequence{A})
    if length(seq1) != length(seq2)
        return false
    end
    for (x, y) in zip(seq1, seq2)
        if x != y
            return false
        end
    end
    return true
end

Base.(:(==)){A<:DNAAlphabet}(seq1::BioSequence{A}, seq2::ReferenceSequence) =
    seq2 == seq1

function Base.cmp(seq1::ReferenceSequence, seq2::ReferenceSequence)
    for (x, y) in zip(seq1, seq2)
        c = cmp(x, y)
        if c != 0
            return c
        end
    end
    return cmp(length(seq1), length(seq2))
end

function Base.cmp{A<:DNAAlphabet}(seq1::ReferenceSequence, seq2::BioSequence{A})
    for (x, y) in zip(seq1, seq2)
        c = cmp(x, y)
        if c != 0
            return c
        end
    end
    return cmp(length(seq1), length(seq2))
end

Base.cmp{A<:DNAAlphabet}(seq1::BioSequence{A}, seq2::ReferenceSequence) =
    -cmp(seq2, seq1)

Base.isless(seq1::ReferenceSequence, seq2::ReferenceSequence) =
    cmp(seq1, seq2) == -1
Base.isless{A<:DNAAlphabet}(seq1::ReferenceSequence, seq2::BioSequence{A}) =
    cmp(seq1, seq2) == -1
Base.isless{A<:DNAAlphabet}(seq1::BioSequence{A}, seq2::ReferenceSequence) =
    cmp(seq1, seq2) == -1

# Finders
# -------

function Base.findnext(seq::ReferenceSequence, val, start::Integer)
    checkbounds(seq, start)
    v = convert(DNANucleotide, val)
    if v == DNA_N
        return findnextn(seq.nmask, start)
    else
        for i in Int(start):endof(seq)
            x = unsafe_getindex(seq, i)
            if x == val
                return i
            end
        end
    end
    return 0
end

function Base.findprev(seq::ReferenceSequence, val, start::Integer)
    checkbounds(seq, start)
    v = convert(DNANucleotide, val)
    if v == DNA_N
        return findprevn(seq.nmask, start)
    else
        for i in Int(start):-1:1
            x = unsafe_getindex(seq, i)
            if x == val
                return i
            end
        end
    end
    return 0
end

Base.findfirst(seq::ReferenceSequence, val) = findnext(seq, val, 1)
Base.findlast(seq::ReferenceSequence, val)  = findprev(seq, val, endof(seq))

# Printers
# --------

Base.summary(seq::ReferenceSequence) = string(length(seq), "nt Reference Sequence")

function Base.show(io::IO, seq::ReferenceSequence)
    println(io, summary(seq), ':')
    showcompact(io, seq)
end

function Base.showcompact(io::IO, seq::ReferenceSequence)
    width = Base.tty_size()[2]
    if length(seq) > width
        h = div(width, 2)
        for i in 1:h-2
            print(io, seq[i])
        end
        print(io, '…')
        for i in endof(seq)-h+1:endof(seq)
            print(io, seq[i])
        end
    else
        for i in 1:endof(seq)
            print(io, seq[i])
        end
    end
end

function Base.print(io::IO, seq::ReferenceSequence)
    for nt in seq
        print(io, nt)
    end
end

# TODO: implement following functions for incremantal construction
# Base.push!(seq::ReferenceSequence, x)
# Base.append!(seq::ReferenceSequence, other)


@testset "Test deprecated accessors for BatteryEMS" begin
    #TODO: use PSCB test system after PR#12 is merge and delete battery device
    # c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat_ems");
    # batt = get_component(BatteryEMS, c_sys5_bat, "Bat2");
    batt = BatteryEMS(;
        name = "Bat2",
        prime_mover = PrimeMovers.BA,
        available = true,
        bus = Bus(nothing),
        initial_energy = 5.0,
        state_of_charge_limits = (min = 0.10, max = 7.0),
        rating = 7.0,
        active_power = 2.0,
        input_active_power_limits = (min = 0.0, max = 2.0),
        output_active_power_limits = (min = 0.0, max = 2.0),
        efficiency = (in = 0.80, out = 0.90),
        reactive_power = 0.0,
        reactive_power_limits = (min = -2.0, max = 2.0),
        base_power = 100.0,
        storage_target = 0.2,
        operation_cost = StorageManagementCost(
            variable = VariableCost(0.0),
            fixed = 0.0,
            start_up = 0.0,
            shut_down = 0.0,
            energy_shortage_cost = 50.0,
            energy_surplus_cost = 40.0,
        ),
    )
    dummy_value = 99.9
    @test @test_deprecated(set_energy_value!(batt, dummy_value)) == dummy_value
    @test @test_deprecated(set_penalty_cost!(batt, dummy_value)) == dummy_value

    @test @test_deprecated(get_energy_value(batt)) ==
          get_energy_surplus_cost(get_operation_cost(batt))
    @test @test_deprecated(get_penalty_cost(batt)) ==
          get_energy_shortage_cost(get_operation_cost(batt))
end

@testset "Test deprecated CurrentControl" begin
    @test_deprecated(CurrentControl(
        0.59, #kpv:: Voltage controller proportional gain
        736.0, #kiv:: Voltage controller integral gain
        0.0, #kffv:: Binary variable enabling the voltage feed-forward in output of current controllers
        0.0, #rv:: Virtual resistance in pu
        0.2, #lv: Virtual inductance in pu
        1.27, #kpc:: Current controller proportional gain
        14.3, #kiv:: Current controller integral gain
        0.0, #kffi:: Binary variable enabling the current feed-forward in output of current controllers
        50.0, #ωad:: Active damping low pass filter cut-off frequency
        0.2, #kad:: Active damping gain
    ))
end


# auxiliary functions:
# calculate plotting coordinates of nodes & edges for PhyloNetworks objects

"""
    getEdgeNodeCoordinates(net, useEdgeLength::Bool, useSimpleHybridLines::Bool)

Calculate coordinates for plotting later with Gadfly or RCall.

Actually modifies some (minor) attributes of the network,
as it calls `directEdges!` and `preorder!`.

output: tuple with the following elements, in which the order of
nodes corresponds to the order in `net.node`, and the order of
edges corresponds to that in `net.edge` (filtered to minor edges as needed).

1. `edge_xB`: x coordinate for the Beginning and ...
2. `edge_xE`: ...  End of each edge, in the same order as in `net.edge`
3. `edge_yB`: y coordinate for edges, Begin ...
4. `edge_yE`: ... and End.
   Each major edge is drawn as a single horizontal line. Minor hybrid edges are
   drawn as: a single diagonal segment if `useSimpleHybridLines` is true,
   or as 2 connected segments otherwise: one horizontal (whose length on the
   x axis can be used to represent the edge length), and the other diagonal to
   connect the horizontal segment to the child node.
   `edge_*` contains the coordinates for the horizontal segment only, which is
   reduced to a single point (Begin = End) when using "SimpleHybridLines".
   `minoredge_*` (see below) contains information for the diagonal segment.
   Agreed, `edge_yB` = `edge_yE` always (relic from before v0.3:
   no minoredge output back then, and simple diagonal lines only)
5. `node_x`: x and ...
6. `node_y`: ... y coordinate at the middle of the vertical bar that represents a node.
   The (or each) parent edge of the node connects to this middle point,
   but the node itself is drawn as a vertical bar connected to all it children edges.
   order: same as in `net.node`
7. `node_yB`: y coordinates of the Beginning and the ...
8. `node_yE`: ... End of the vertical bar representing each node.
   The x coordinate (Begin & End) of the end points of the vertical bar is
   the same as that of the mid-point, given by `node_x`.
9. `minoredge_xB`: x coordinate for the Beginning and ...
10. `minoredge_xE`: ... End of the diagonal segment of each minor hybrid edge,
   in the same order as in `filter(e -> !e.isMajor, net.edge)`.
11. `minoredge_yB`: y coordinate for the beginning and ...
12. `minoredge_yE`: ... end of the diagonal segment of each minor hybrid edge.
13-16. `xmin`, `xmax`, `ymin`, `ymax`: ranges for the x and y axes.
"""
function getEdgeNodeCoordinates(net::HybridNetwork, useEdgeLength::Bool, useSimpleHybridLines::Bool)
    try
        directEdges!(net)   # to update isChild1
    catch e
        if isa(e, PhyloNetworks.RootMismatch)
            e = PhyloNetworks.RootMismatch( e.msg * "\nPlease change the root, perhaps using rootatnode! or rootatedge!")
        end
        rethrow(e)
    end
    preorder!(net)       # to update net.nodes_changed: true pre-ordering

    # determine y for each node = y of its parent edge: post-order traversal
    # also [yB,yE] for each internal node: range of y's of all children nodes
    # y max is the numTaxa + number of minor edges
    ymin = 1.0;
    ymax = net.numTaxa
    if !useSimpleHybridLines
        ymax += sum(!e.isMajor for e in net.edge)
    end

    node_y  = zeros(Float64, net.numNodes) # order: in net.nodes, *!not in nodes_changed!*
    node_yB = zeros(Float64,net.numNodes) # min (B=begin) and max (E=end)
    node_yE = zeros(Float64,net.numNodes) #   of at children's nodes
    edge_yB = zeros(Float64,net.numEdges) # yE of edge = y of child node
    # set node_y of leaves: follow cladewise order
    # also sets edge_yB of minor hybrid edges
    nexty = ymax # first tips at the top, last at bottom
    cladewise_queue = copy(net.node[net.root].edge) # the child edges of root
    # print("queued the root's children's indices: "); @show queue
    while !isempty(cladewise_queue)
        cur_edge = pop!(cladewise_queue); # deliberate choice over shift! for cladewise order
        # increment spacing and add to node_y if leaf
        if getChild(cur_edge).leaf
            ni = findfirst(x->x===getChild(cur_edge), net.node)
            node_y[ni]  = nexty
            node_yB[ni] = nexty
            node_yE[ni] = nexty
            nexty -= 1
        end

        # only for new hybrid lines:
        # increment spacing and add to edge_yB if parent edge is minor
        if !cur_edge.isMajor && !useSimpleHybridLines
            edge_yB[findfirst(x->x===cur_edge, net.edge)] = nexty
            nexty -= 1
        end

        # push children edges if this is a major edge:
        if cur_edge.isMajor
            for e in getChild(cur_edge).edge
                if getParent(e) === getChild(cur_edge)# don't go backwards
                    push!(cladewise_queue, e)
                end
            end
        end
    end

    # set node_y of internal nodes: follow post-order
    for i=length(net.node):-1:1
        nn = net.nodes_changed[i]
        !nn.leaf || continue # previous loop took care of leaves
        ni = findfirst(x -> x===nn, net.node)
        node_yB[ni]=ymax; node_yE[ni]=ymin;
        minor_yB  = ymax; minor_yE  = ymin;
        nomajorchild=useSimpleHybridLines # only use this var if using simple hybrid lines
        for e in nn.edge
            if nn == PhyloNetworks.getParent(e) # if e = child of node
                if useSimpleHybridLines
                    # old simple hybrid lines
                    if e.isMajor || nomajorchild
                        cc = PhyloNetworks.getChild(e)
                        yy = node_y[findfirst(x -> x===cc, net.node)]
                        yy!==nothing || error("oops, child $(cc.number) has not been visited before node $(nn.number).")
                    end
                    if e.isMajor
                        nomajorchild = false # we found a child edge that is a major edge
                        node_yB[ni] = min(node_yB[ni], yy)
                        node_yE[ni] = max(node_yE[ni], yy)
                    elseif nomajorchild # e is minor edge, and no major found so far
                        minor_yB = min(minor_yB, yy)
                        minor_yE = max(minor_yE, yy)
                    end
                else
                    # new pretty hybrid lines
                    if e.isMajor
                        cc = PhyloNetworks.getChild(e)
                        child_y = node_y[findfirst(x -> x===cc, net.node)]
                        child_y!==nothing || error("oops, child $(cc.number) has not been visited before node $(nn.number).")
                    else
                        child_y = edge_yB[findfirst(x->x===e, net.edge)]
                    end
                    node_yB[ni] = min(node_yB[ni], child_y)
                    node_yE[ni] = max(node_yE[ni], child_y)
                end
            end
        end
        if nomajorchild # if all children edges are minor hybrid edges. If so: not level 1, not tree-child
            if minor_yB == minor_yE # one single child. jitter by 0.1 to make the plot readable
                minor_yB += (minor_yB < (ymax+ymin)/2 ? 0.1 : -0.1)
                minor_yE = minor_yB
            end
            node_yB[ni] = minor_yB
            node_yE[ni] = minor_yE
        end
        node_y[ni] = (node_yB[ni]+node_yE[ni])/2
        if nomajorchild #since the minor edges are leaving from the center of the node's y pos.
            node_yB[ni] = node_y[ni]
            node_yE[ni] = node_y[ni]
        end
    end

    # setting branch lengths for plotting
    elenCalculate = !useEdgeLength
    if (useEdgeLength)
        allBLmissing = true; nonBLmissing = true;
        for e in net.edge
            if (nonBLmissing && e.length==-1.0) nonBLmissing=false; end
            if (allBLmissing && e.length!=-1.0) allBLmissing=false; end
        end
        if (allBLmissing)
            println("All edge lengths are missing, won't be used for plotting.")
            elenCalculate = true
        end
        if (!nonBLmissing && !allBLmissing) # not all, but some are missing
            @warn "At least one non-missing edge length: plotting any missing length as 1.0"
        end
    end
    elen = Float64[] # edge lengths to be used for plotting. same order as net.edge.
    if (elenCalculate)
        # setting elen such that the age of each node = 1 + age of oldest child
        # (including minor hybrid edges): need true post-ordering.
        # calculating node ages first, elen will be calculated later.
        elen     = zeros(Float64,net.numEdges)
        node_age = zeros(Float64,net.numNodes)
        for i=length(net.node):-1:1 # post-order traversal
            if (net.nodes_changed[i].leaf) continue; end
            ni = findfirst(x -> x===net.nodes_changed[i], net.node)
            for e in net.nodes_changed[i].edge # loop over children only
                if net.nodes_changed[i] == (e.isChild1 ? e.node[2] : e.node[1])
                    node_age[ni] = max(node_age[ni], 1 +
                     node_age[findfirst(x -> x=== PhyloNetworks.getChild(e), net.node)])
                end
            end
        end
    else
        for e in net.edge
            push!(elen, (e.length==-1.0 ? 1.0 : e.length))
        end
    end

    # determine xB,xE for each edge: pre-order traversal, uses branch lengths
    # then x and yB,yE for each node: x=xE of parent edge
    xmin = 1.0; xmax=xmin
    node_x  = zeros(Float64,net.numNodes) # order: in net.nodes, *!not in nodes_changed!*
    edge_xB = zeros(Float64,net.numEdges) # min (B=begin) and max (E=end)
    edge_xE = zeros(Float64,net.numEdges) # xE-xB = edge length
    node_x[net.root] = xmin # root node: x=xmin=0
    for i=2:length(net.node)              # true pre-order, skipping the root (i=1)
        ni = findfirst(x -> x===net.nodes_changed[i], net.node)
        ei = nothing # index of major parent edge of current node
        for e in net.nodes_changed[i].edge
            if (e.isMajor && net.nodes_changed[i] == e.node[e.isChild1 ? 1 : 2]) # major parent edge
                ei = findfirst(x -> x===e, net.edge)
                break
            end
        end
        ei !== nothing || error("oops, could not find major parent edge of node number $ni.")
        edge_yB[ei] = node_y[ni]
        pni = findfirst(x -> x===PhyloNetworks.getParent(net.edge[ei]), net.node) # parent node index
        edge_xB[ei] = node_x[pni]
        if elenCalculate
            elen[ei] = node_age[pni] - node_age[ni]
        end
        edge_xE[ei] = edge_xB[ei] + elen[ei]
        node_x[ni] = edge_xE[ei]
    end
    edge_yE = copy(edge_yB) # true for tree and major edges

    # coordinates of the diagonal lines that connect hybrid edges with their targets
    minoredge_xB = Float64[]
    minoredge_xE = Float64[]
    minoredge_yB = Float64[]
    minoredge_yE = Float64[]

    for i=1:net.numEdges
        if (!net.edge[i].isMajor) # minor hybrid edges
            # indices of child and parent nodes
            cni = findfirst(x -> x===PhyloNetworks.getChild( net.edge[i]), net.node)
            pni = findfirst(x -> x===PhyloNetworks.getParent(net.edge[i]), net.node)

            edge_xB[i] = node_x[pni]
            edge_xE[i] = useSimpleHybridLines ? edge_xB[i] : (useEdgeLength ? edge_xB[i] + elen[i] : node_x[cni])

            if useSimpleHybridLines
                edge_yB[i] = node_y[pni]
            end
            edge_yE[i] = edge_yB[i]

            push!(minoredge_xB, edge_xE[i])
            push!(minoredge_yB, edge_yE[i])
            push!(minoredge_xE, node_x[cni])
            push!(minoredge_yE, node_y[cni])
            #@show i; @show net.edge[i]; @show pni; @show net.node[pni]; @show cni; @show net.node[cni]
        end
    end

    xmax = max(xmax, edge_xE...)

    #@show node_x;  @show node_yB; @show node_y;  @show node_yE
    #@show edge_xB; @show edge_xE; @show edge_yB; @show edge_yE
    return edge_xB, edge_xE, edge_yB, edge_yE,
           node_x, node_y, node_yB, node_yE,
           minoredge_xB, minoredge_xE, minoredge_yB, minoredge_yE,
           xmin, xmax, ymin, ymax
end

"""
    checkNodeDataFrame(net, nodeLabel)

Check data frame for node annotations:
- check that the data has at least 2 columns (if it has any)
- check that the first column has integers (to serve as node numbers)
- remove rows with no node numbers
- warning if some node numbers in the data are not in the network.
"""
function checkNodeDataFrame(net::HybridNetwork, nodeLabel::DataFrame)
    labelnodes = size(nodeLabel,1)>0
    if (labelnodes && (size(nodeLabel,2)<2 ||
            !(nonmissingtype(eltype(nodeLabel[!,1])) <: Integer)))
        @warn "nodeLabel should have 2+ columns, the first one giving the node numbers (Integer)"
        labelnodes = false
    end
    if labelnodes # remove rows with no node number, check if at least one row remains
        filter!(row->!ismissing(row[1]), nodeLabel)
        labelnodes = size(nodeLabel,1)>0
    end
    if labelnodes
      tmp = setdiff(nodeLabel[!,1], [n.number for n in net.node])
      if length(tmp)>0
        msg = "Some node numbers in the nodeLabel data frame are not found in the network:\n"
        for a in tmp msg *= string(" ",a); end
        @warn msg
      end
    end
    return(labelnodes, nodeLabel)
end

"""
    prepareNodeDataFrame(net, nodeLabel::DataFrame,
        showNodeNumber::Bool, showIntNodeLabel::Bool, labelnodes::Bool,
        node_x, node_y)

Make data frame for node annotation. `node_*` should be Float64 vectors.
`nodeLabel` should have columns as required by [`checkNodeDataFrame`](@ref)

Columns of output data frame:
- x, y: coordinates on the plots (from `node_*`)
- name: node name
- num: node number
- lab: node label
- lea: is leaf?
"""
function prepareNodeDataFrame(net::HybridNetwork, nodeLabel::DataFrame,
        showNodeNumber::Bool, showIntNodeLabel::Bool, labelnodes::Bool,
        node_x::Array{Float64,1}, node_y::Array{Float64,1})
    nrows = (showNodeNumber || showIntNodeLabel || labelnodes ? net.numNodes : net.numTaxa)
    ndf = DataFrame(:name => Vector{String}(undef,nrows),
        :num => Vector{String}(undef,nrows), :lab => Vector{String}(undef,nrows),
        :lea => Vector{Bool}(  undef,nrows), :x => Vector{Float64}( undef,nrows),
        :y => Vector{Float64}( undef,nrows), copycols=false)
    j=1
    for i=1:net.numNodes
    if net.node[i].leaf  || showNodeNumber || showIntNodeLabel || labelnodes
        ndf[j,:name] = net.node[i].name
        ndf[j,:num] = string(net.node[i].number)
        if labelnodes
          jn = findfirst(isequal(net.node[i].number), nodeLabel[!,1])
          ndf[j,:lab] = (jn===nothing || ismissing(nodeLabel[jn,2]) ? "" :  # node label not in table or missing
            (nonmissingtype(eltype(nodeLabel[!,2])) <: AbstractFloat ?
              @sprintf("%0.3g",nodeLabel[jn,2]) : string(nodeLabel[jn,2])))
        end
        ndf[j,:lea] = net.node[i].leaf # use this later to remove H? labels
        ndf[j,:y] = node_y[i]
        ndf[j,:x] = node_x[i]
        j += 1
    end
    end
    # @show ndf
    return(ndf)
end

"""
    prepareEdgeDataFrame(net, edgeLabel::DataFrame, mainTree::Bool,
        edge_xB, edge_xE, edge_yB, edge_yE,
        minoredge_xB, minoredge_xE, minoredge_yB, minoredge_yE)

Check data frame for edge annotation.
`edge_*`: Float64 vectors giving the coordinates for the beginning and end of edges.
Return data frame with columns
- x, y: coordinates on the plots
- len: node name
- gam: gamma (inheritance value)
- num: node number
- lab: node label
- hyb: is hybrid?
- min: is minor?
"""
function prepareEdgeDataFrame(net::HybridNetwork, edgeLabel::DataFrame, mainTree::Bool,
        edge_xB::Array{Float64,1}, edge_xE::Array{Float64,1},
        edge_yB::Array{Float64,1}, edge_yE::Array{Float64,1},
        minoredge_xB::Array{Float64,1}, minoredge_xE::Array{Float64,1},
        minoredge_yB::Array{Float64,1}, minoredge_yE::Array{Float64,1})
    nrows = net.numEdges - (mainTree ? length(minoredge_xB) : 0)
    edf = DataFrame(:len => Vector{String}(undef,nrows),
        :gam => Vector{String}(undef,nrows), :num => Vector{String}(undef,nrows),
        :lab => Vector{String}(undef,nrows), :hyb => Vector{Bool}(undef,nrows),
        :min => Vector{Bool}(  undef,nrows), :x => Vector{Float64}(undef,nrows),
        :y  => Vector{Float64}(undef,nrows), copycols=false)
    labeledges = size(edgeLabel,1)>0
    if (labeledges && (size(edgeLabel,2)<2 ||
            !(nonmissingtype(eltype(edgeLabel[!,1])) <: Integer)))
        @warn "edgeLabel should have 2+ columns, the first one giving the edge numbers (Integer)"
        labeledges = false
    end
    if labeledges # remove rows with no edge number and check if at least one remains
        filter!(row->!ismissing(row[1]), edgeLabel)
        labeledges = size(edgeLabel,1)>0
    end
    if labeledges
      tmp = setdiff(edgeLabel[!,1], [e.number for e in net.edge])
      if length(tmp)>0
        msg = "Some edge numbers in the edgeLabel data frame are not found in the network:\n"
        for a in tmp msg *= string(" ",a); end
        @warn msg
      end
    end
    j=1   # index of row in edf
    imh=1 # index of minor hybrid edge in filter(ee -> !ee.isMajor, net.edge)
    for i = 1:length(net.edge)
        ee = net.edge[i]
        # skip the edge if it's minor and we only want the main tree:
        mainTree && !ee.isMajor && continue
        edf[j,:len] = (ee.length==-1.0 ? "" : @sprintf("%0.3g",ee.length))
        # @sprintf("%c=%0.3g",'γ',ee.length)
        edf[j,:gam] = (ee.gamma==-1.0  ? "" : @sprintf("%0.3g",ee.gamma))
        edf[j,:num] = string(ee.number)
        if labeledges
            je = findfirst(isequal(ee.number), edgeLabel[!,1])
            edf[j,:lab] = (je===nothing || ismissing(edgeLabel[je,2]) ? "" :  # edge label not found in table
            (nonmissingtype(eltype(edgeLabel[!,2])) <: AbstractFloat ?
                @sprintf("%0.3g",edgeLabel[je,2]) : string(edgeLabel[je,2])))
        end
        edf[j,:hyb] = ee.hybrid
        edf[j,:min] = !ee.isMajor
        if ee.isMajor
            edf[j,:y] = (edge_yB[i] + edge_yE[i])/2
            edf[j,:x] = (edge_xB[i] + edge_xE[i])/2
        else
            edf[j,:y] = (minoredge_yB[imh] + minoredge_yE[imh])/2
            edf[j,:x] = (minoredge_xB[imh] + minoredge_xE[imh])/2
            imh += 1
        end
        j += 1
    end
    # @show edf
    return labeledges, edf
end


#Define Mercury type and object
struct MercuryType <: abstractPlanet end
Mercury = Planet(MercuryType)

#Data, as previously used in AstroDynBase
mu(::Type{MercuryType}) = 2.2032e4 #km^3/s^2
j2(::Type{MercuryType}) = 2.027e-4
mean_radius(::Type{MercuryType}) = 2439.7
equatorial_radius(::Type{MercuryType}) = 2439.7
polar_radius(::Type{MercuryType}) = 2439.7

naif_id(::Type{MercuryType}) = 199
parent(::Type{MercuryType}) = MercuryBarycenter
frame(::Type{MercuryType}) = "IAU_MERCURY"


module MdSlicing
export ProblemSpec, ConvexMesh, fillfps, slice1d, slice2d, sliceplorer, hypersliceplorer, plot

include("types.jl")
include("meshes.jl")
include("hyperslicesets.jl")
include("intersecttest.jl")
include("hyperslice.jl")
include("sliceplorer.jl")
include("plots.jl")

# FIXME: put types back in
# function fillfps(ps::ProblemSpec, fp::Array{Union{Missing,Float64},1}, n::Int = 50)
function fillfps(ps, fp, n=50)
    if !any(map(ismissing, fp)) # special case
        return [fp]
    end
    fpvals =
    [ismissing(x[1]) ? (x[2][1] .+ (x[2][2] - x[2][1]) .* rand(n)) : fill(x[1], n) for x in zip(fp, ps.vals)]
  # transpose everything
    [[fpvals[c][r] for c = 1:length(fp)] for r = 1:n]
end

end # module


using JSON, Random, ArgParse
import EvaluationOfRLAlgs

include("algconstructors.jl")
include("envconstructors.jl")

function runsweep(id, seed, envconfig, algname, save_dir, trials, paramdir=nothing)
    num_episodes = envconfig["num_episodes"]
    num_endepisodes = envconfig["num_endepisodes"]

    rng = Random.MersenneTwister(seed)

    save_name = "$(algname)_$(lpad(seed, 5, '0')).csv"

    save_path = joinpath(save_dir, save_name)

    open(save_path, "w") do f
        for trial in 1:trials
            env = create_env(envconfig, rng)
            agent, hyps, logp = sample_alg(algname, env, rng, num_episodes)
            meanret = 0.
            meaneret = 0.

            returns = EvaluationOfRLAlgs.run_agent!(env, agent, num_episodes, rng)
            meanret += mean(returns)
            ereturns = EvaluationOfRLAlgs.eval_agent(env, agent, num_endepisodes, rng)
            meaneret += mean(ereturns)

            result = join([hyps..., logp, meanret, meaneret], ',')
            write(f, "$(result)\n")
            flush(f)
            println("$trial \t $(result)")
            flush(stdout)
        end
    end

end

function main()
    s = ArgParseSettings()
    @add_arg_table s begin
        "--alg-name", "-a"
            help = "name of the algorithm to run"
            arg_type = String
            required = true
        "--env-file", "-e"
            help = "file path to JSON file containing environment to run"
            arg_type = String
            required = true
        "--log-dir", "-l"
            help = "folder directory to store results"
            arg_type = String
            required = true
        "--id"
            help = "identifier for this experiment. Used in defining a random seed"
            arg_type = Int
            required = true
        "--seed", "-s"
            help = "random seed is seed + id"
            arg_type = Int
            required = true
        "--trials", "-t"
            help = "number of random trials to run"
            arg_type = Int
            default = 1
    end

    parsed_args = parse_args(ARGS, s)
    aname = parsed_args["alg-name"]
    efile = parsed_args["env-file"]
    println("Excuting algorithm: $aname")
    println("Loading environment config: $efile")
    println("Logging Directory: $(parsed_args["log-dir"])")
    flush(stdout)
    envconfig = JSON.parsefile(efile, dicttype=Dict, inttype=Int64)["env"]
    println("Environment config:")
    println(envconfig)
    flush(stdout)
    save_dir = parsed_args["log-dir"]
    save_dir = joinpath(save_dir, envconfig["logname"])
    mkpath(save_dir)
    println("Saving results in: $save_dir")
    trials = parsed_args["trials"]
    id = parsed_args["id"]
    seed = parsed_args["seed"]

    println("id $id seed=(id+seed) $(id + seed)")
    flush(stdout)
    seed = id + seed

    runsweep(id, seed, envconfig, aname, save_dir, trials)

end

main()


## core stream types ##

# the first argument to any IO MUST be a POINTER (to a JL_STREAM) or using show on it will cause memory corruption

# Generic IO functions

## byte-order mark, ntoh & hton ##

const ENDIAN_BOM = reinterpret(Uint32,uint8([1:4]))[1]

if ENDIAN_BOM == 0x01020304
    ntoh(x) = x
    hton(x) = x
    ltoh(x) = bswap(x)
    htol(x) = bswap(x)
elseif ENDIAN_BOM == 0x04030201
    ntoh(x) = bswap(x)
    hton(x) = bswap(x)
    ltoh(x) = x
    htol(x) = x
else
    error("seriously? what is this machine?")
end

isreadonly(s) = isreadable(s) && !iswritable(s)

## binary I/O ##

# all subtypes should implement this
write(s::IO, x::Uint8) = error(typeof(s)," does not support byte I/O")

write(io::IO, x) = throw(MethodError(write, (io, x)))
write(io::IO, xs...) = for x in xs write(io, x) end

if ENDIAN_BOM == 0x01020304
    function write(s::IO, x::Integer)
        sz = sizeof(x)
        for n = sz:-1:1
            write(s, uint8((x>>>((n-1)<<3))))
        end
        sz
    end
else
    function write(s::IO, x::Integer)
        sz = sizeof(x)
        for n = 1:sz
            write(s, uint8((x>>>((n-1)<<3))))
        end
        sz
    end
end

write(s::IO, x::Bool)    = write(s, uint8(x))
write(s::IO, x::Float16) = write(s, reinterpret(Int16,x))
write(s::IO, x::Float32) = write(s, reinterpret(Int32,x))
write(s::IO, x::Float64) = write(s, reinterpret(Int64,x))

function write(s::IO, a::AbstractArray)
    nb = 0
    for i = 1:length(a)
        nb += write(s, a[i])
    end
    nb
end

function write(s::IO, c::Char)
    if c < 0x80
        write(s, uint8(c))
        return 1
    elseif c < 0x800
        write(s, uint8(( c >> 6          ) | 0xC0))
        write(s, uint8(( c        & 0x3F ) | 0x80))
        return 2
    elseif c < 0x10000
        write(s, uint8(( c >> 12         ) | 0xE0))
        write(s, uint8(((c >> 6)  & 0x3F ) | 0x80))
        write(s, uint8(( c        & 0x3F ) | 0x80))
        return 3
    elseif c < 0x110000
        write(s, uint8(( c >> 18         ) | 0xF0))
        write(s, uint8(((c >> 12) & 0x3F ) | 0x80))
        write(s, uint8(((c >> 6)  & 0x3F ) | 0x80))
        write(s, uint8(( c        & 0x3F ) | 0x80))
        return 4
    else
        return write(s, '\ufffd')
    end
end

function write(s::IO, p::Ptr, n::Integer)
    for i=1:n
        write(s, unsafe_load(p, i))
    end
    n
end

function write(io::IO, s::Symbol)
    pname = convert(Ptr{Uint8}, s)
    write(io, pname, int(ccall(:strlen, Csize_t, (Ptr{Uint8},), pname)))
end

# all subtypes should implement this
read(s::IO, x::Type{Uint8}) = error(typeof(s)," does not support byte I/O")

function read{T <: Integer}(s::IO, ::Type{T})
    x = zero(T)
    for n = 1:sizeof(x)
        x |= (convert(T,read(s,Uint8))<<((n-1)<<3))
    end
    return x
end

read(s::IO, ::Type{Bool})    = (read(s,Uint8)!=0)
read(s::IO, ::Type{Float16}) = box(Float16,unbox(Int16,read(s,Int16)))
read(s::IO, ::Type{Float32}) = box(Float32,unbox(Int32,read(s,Int32)))
read(s::IO, ::Type{Float64}) = box(Float64,unbox(Int64,read(s,Int64)))

read{T}(s::IO, t::Type{T}, d1::Int, dims::Int...) =
    read(s, t, tuple(d1,dims...))
read{T}(s::IO, t::Type{T}, d1::Integer, dims::Integer...) =
    read(s, t, map(int,tuple(d1,dims...)))

read{T}(s::IO, ::Type{T}, dims::Dims) = read!(s, Array(T, dims))

function read!{T}(s::IO, a::Array{T})
    for i = 1:length(a)
        a[i] = read(s, T)
    end
    return a
end

function read(s::IO, ::Type{Char})
    ch = read(s, Uint8)
    if ch < 0x80
        return char(ch)
    end

    # mimic utf8.next function
    trailing = Base.utf8_trailing[ch+1]
    c::Uint32 = 0
    for j = 1:trailing
        c += ch
        c <<= 6
        ch = read(s, Uint8)
    end
    c += ch
    c -= Base.utf8_offset[trailing+1]
    char(c)
end

function readuntil(s::IO, delim::Char)
    if delim < 0x80
        data = readuntil(s, uint8(delim))
        enc = byte_string_classify(data)
        return (enc==1) ? ASCIIString(data) : UTF8String(data)
    end
    out = IOBuffer()
    while !eof(s)
        c = read(s, Char)
        write(out, c)
        if c == delim
            break
        end
    end
    takebuf_string(out)
end

function readuntil{T}(s::IO, delim::T)
    out = T[]
    while !eof(s)
        c = read(s, T)
        push!(out, c)
        if c == delim
            break
        end
    end
    out
end

# based on code by Glen Hertz
function readuntil(s::IO, t::String)
    l = length(t)
    if l == 0
        return ""
    end
    if l > 40
        warn("readuntil(IO,String) will perform poorly with a long string")
    end
    out = IOBuffer()
    m = Array(Char, l)  # last part of stream to match
    t = collect(t)
    i = 0
    while !eof(s)
        i += 1
        c = read(s, Char)
        write(out, c)
        if i <= l
            m[i] = c
        else
            # shift to last part of s
            for j = 2:l
                m[j-1] = m[j]
            end
            m[l] = c
        end
        if i >= l && m == t
            break
        end
    end
    return takebuf_string(out)
end

readline(s::IO) = readuntil(s, '\n')
readchomp(x) = chomp!(readall(x))

# read up to nb bytes into nb, returning # bytes read
function readbytes!(s::IO, b::AbstractArray{Uint8}, nb=length(b))
    olb = lb = length(b)
    nr = 0
    while nr < nb && !eof(s)
        a = read(s, Uint8)
        nr += 1
        if nr > lb
            lb = nr * 2
            resize!(b, lb)
        end
        b[nr] = a
    end
    if lb > olb
        resize!(b, nr) # shrink to just contain input data if was resized
    end
    return nr
end

# read up to nb bytes from s, returning a Vector{Uint8} of bytes read.
function readbytes(s::IO, nb=typemax(Int))
    b = Array(Uint8, nb == typemax(Int) ? 1024 : nb)
    nr = readbytes!(s, b, nb)
    resize!(b, nr)
end

function readall(s::IO)
    b = readbytes(s)
    return is_valid_ascii(b) ? ASCIIString(b) : UTF8String(b)
end
readall(filename::String) = open(readall, filename)

## high-level iterator interfaces ##

type EachLine
    stream::IO
    ondone::Function
    EachLine(stream) = EachLine(stream, ()->nothing)
    EachLine(stream, ondone) = new(stream, ondone)
end
eachline(stream::IO) = EachLine(stream)

start(itr::EachLine) = nothing
function done(itr::EachLine, nada)
    if !eof(itr.stream)
        return false
    end
    itr.ondone()
    true
end
next(itr::EachLine, nada) = (readline(itr.stream), nothing)
eltype(itr::EachLine) = ByteString

readlines(s=STDIN) = collect(eachline(s))

# IOStream Marking

# Note that these functions expect that io.mark exists for
# the concrete IO type.  This may not be true for IO types
# not in base.

function mark(io::IO)
    io.mark = position(io)
end

function unmark(io::IO)
    !ismarked(io) && return false
    io.mark = -1
    return true
end

function reset(io::IO)
    !ismarked(io) && error(io, " not marked")
    m = io.mark
    seek(io, m)
    io.mark = -1 # must be after seek, or seek may fail
    return m
end

ismarked(io::IO) = io.mark >= 0


using GeometryBasics, RPRMakie
using Colors, FileIO
using Colors: N0f8

image = begin
    radiance = 500
    lights = [EnvironmentLight(1.0, load(RPR.assetpath("studio026.exr"))),
              PointLight(Vec3f(10), RGBf(radiance, radiance, radiance * 1.1))]
    fig = Figure(; resolution=(1500, 700))
    ax = LScene(fig[1, 1]; show_axis=false, scenekw=(lights=lights,))
    screen = RPRScreen(ax.scene; plugin=RPR.Northstar, iterations=400)

    matsys = screen.matsys
    emissive = RPR.EmissiveMaterial(matsys)
    diffuse = RPR.DiffuseMaterial(matsys)
    glass = RPR.Glass(matsys)
    plastic = RPR.Plastic(matsys)
    chrome = RPR.Chrome(matsys)
    dielectric = RPR.DielectricBrdfX(matsys)
    gold = RPR.SurfaceGoldX(matsys)

    materials = [glass chrome;
                 gold dielectric;
                 emissive plastic]

    mesh!(ax, load(Makie.assetpath("matball_floor.obj")); color=:white)
    palette = reshape(Makie.default_palettes.color[][1:6], size(materials))

    for i in CartesianIndices(materials)
        x, y = Tuple(i)
        mat = materials[i]
        mplot = if mat === emissive
            matball!(ax, diffuse; inner=emissive, color=nothing)
        else
            matball!(ax, mat; color=nothing)
        end
        v = Vec3f(((x, y) .- (0.5 .* size(materials)) .- 0.5)..., 0)
        translate!(mplot, 0.9 .* (v .- Vec3f(0, 3, 0)))
    end
    cam = cameracontrols(ax.scene)
    cam.eyeposition[] = Vec3f(-0.3, -5.5, 0.9)
    cam.lookat[] = Vec3f(0.5, 0, -0.5)
    cam.upvector[] = Vec3f(0, 0, 1)
    cam.fov[] = 35
    emissive.color = Vec3f(4, 2, 2)
    colorbuffer(screen)
end

save("materials.png", image)


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct MarketBidCost <: OperationalCost
        variable::Union{Nothing, IS.TimeSeriesKey}
        no_load::Float64
        start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}
        shut_down::Float64
        ancillary_services::Vector{Service}
    end

Data Structure Operational Cost to reflect market bids of energy and ancilliary services.
Compatible with most US Market bidding mechanisms

# Arguments
- `variable::Union{Nothing, IS.TimeSeriesKey}`: Variable Cost TimeSeriesKey
- `no_load::Float64`: no load cost
- `start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}`: start-up cost at different stages of the thermal cycle. Warm is also refered as intermediate in some markets
- `shut_down::Float64`: shut-down cost, validation range: `(0, nothing)`, action if invalid: `warn`
- `ancillary_services::Vector{Service}`: Bids for the ancillary services
"""
mutable struct MarketBidCost <: OperationalCost
    "Variable Cost TimeSeriesKey"
    variable::Union{Nothing, IS.TimeSeriesKey}
    "no load cost"
    no_load::Float64
    "start-up cost at different stages of the thermal cycle. Warm is also refered as intermediate in some markets"
    start_up::NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}
    "shut-down cost"
    shut_down::Float64
    "Bids for the ancillary services"
    ancillary_services::Vector{Service}
end

function MarketBidCost(; variable, no_load, start_up, shut_down, ancillary_services=Vector{Service}(), )
    MarketBidCost(variable, no_load, start_up, shut_down, ancillary_services, )
end

# Constructor for demo purposes; non-functional.
function MarketBidCost(::Nothing)
    MarketBidCost(;
        variable=nothing,
        no_load=0.0,
        start_up=(hot = START_COST, warm = START_COST, cold = START_COST),
        shut_down=0.0,
        ancillary_services=Vector{Service}(),
    )
end

"""Get [`MarketBidCost`](@ref) `variable`."""
get_variable(value::MarketBidCost) = value.variable
"""Get [`MarketBidCost`](@ref) `no_load`."""
get_no_load(value::MarketBidCost) = value.no_load
"""Get [`MarketBidCost`](@ref) `start_up`."""
get_start_up(value::MarketBidCost) = value.start_up
"""Get [`MarketBidCost`](@ref) `shut_down`."""
get_shut_down(value::MarketBidCost) = value.shut_down
"""Get [`MarketBidCost`](@ref) `ancillary_services`."""
get_ancillary_services(value::MarketBidCost) = value.ancillary_services

"""Set [`MarketBidCost`](@ref) `variable`."""
set_variable!(value::MarketBidCost, val) = value.variable = val
"""Set [`MarketBidCost`](@ref) `no_load`."""
set_no_load!(value::MarketBidCost, val) = value.no_load = val
"""Set [`MarketBidCost`](@ref) `start_up`."""
set_start_up!(value::MarketBidCost, val) = value.start_up = val
"""Set [`MarketBidCost`](@ref) `shut_down`."""
set_shut_down!(value::MarketBidCost, val) = value.shut_down = val
"""Set [`MarketBidCost`](@ref) `ancillary_services`."""
set_ancillary_services!(value::MarketBidCost, val) = value.ancillary_services = val


using Raylib_jll

struct RayColor
    r::Cuchar
    g::Cuchar
    b::Cuchar
    a::Cuchar
end

const RAYWHITE = RayColor(245, 245, 245, 255)
const LIGHTGRAY = RayColor(200, 200, 200, 255)
const DARKGRAY = RayColor(80, 80, 80, 255)
const MAROON = RayColor(190, 33, 55, 255)
const RED = RayColor(230, 41, 55, 255)

struct RayVector3
    x::Cfloat
    y::Cfloat
    z::Cfloat
end

struct RayCamera3D
    position::RayVector3
    target::RayVector3
    up::RayVector3
    fovy::Cfloat
    projection::Cint
end

function main()
    screenWidth = 800
    screenHeight = 450

    @ccall libraylib.InitWindow(screenWidth::Cint, screenHeight::Cint,
                             "raylib [core] example - 3d camera mode"::Cstring)::Cvoid

    camera = RayCamera3D(
        RayVector3(0, 10, 10),
        RayVector3(0, 0, 0),
        RayVector3(0, 1, 0),
        45,
        0,
    )

    cubePosition = RayVector3(0, 0, 0)

    @ccall libraylib.SetTargetFPS(60::Cint)::Cvoid

    while iszero(@ccall libraylib.WindowShouldClose()::Cint)
        @ccall libraylib.BeginDrawing()::Cvoid
        @ccall libraylib.ClearBackground(RAYWHITE::RayColor)::Cvoid

        @ccall libraylib.BeginMode3D(camera::RayCamera3D)::Cvoid
        @ccall libraylib.DrawCube(cubePosition::RayVector3, 2f0::Cfloat, 2f0::Cfloat, 2f0::Cfloat,
                               RED::RayColor)::RayColor

        @ccall libraylib.DrawCubeWires(cubePosition::RayVector3, 2f0::Cfloat, 2f0::Cfloat, 2f0::Cfloat,
                                    MAROON::RayColor)::Cvoid

        @ccall libraylib.DrawGrid(10::Cint, 1f0::Cfloat)::Cvoid

        @ccall libraylib.EndMode3D()::Cvoid
        @ccall libraylib.DrawText("Welcome to the third dimension!"::Cstring,
                               10::Cint, 40::Cint, 20::Cint, DARKGRAY::RayColor)::Cvoid
        @ccall libraylib.EndDrawing()::Cvoid
    end

    @ccall libraylib.CloseWindow()::Cvoid

end


module ReferenceModels

using JLD2
using ..HelperFuncs
using Random
using DocStringExtensions

import JSON
import TurbulenceConvection
tc = pkgdir(TurbulenceConvection)
include(joinpath(tc, "driver", "main.jl"))
include(joinpath(tc, "driver", "generate_namelist.jl"))
export main1d

import NCDatasets
NC = NCDatasets

export ReferenceModel, ReferenceModelBatch
export get_t_start, get_t_end, get_t_start_Σ, get_t_end_Σ, get_z_obs
export y_dir, Σ_dir, scm_dir, num_vars, uuid
export y_nc_file, Σ_nc_file, get_scm_namelist
export data_directory, namelist_directory
export construct_reference_models
export get_minibatch!, reshuffle_on_epoch_end, write_ref_model_batch
export time_shift_reference_model, write_val_ref_model_batch

"""
    ReferenceModel{FT <: Real}

A structure containing information about the 'true' reference model
and the observation map used to compare the parameterized and
reference models.

# Fields

$(TYPEDFIELDS)

# Constructors

    ReferenceModel(
        y_names::Vector{String},
        y_dir::String,
        scm_dir::String,
        case_name::String,
        t_start::Real,
        t_end::Real;
        Σ_dir::Union{String, Nothing} = nothing,
        Σ_t_start::Union{Real, Nothing} = nothing,
        Σ_t_end::Union{Real, Nothing} = nothing,
        n_obs::Union{Integer, Nothing} = nothing,
        namelist_args = nothing,
    )

`ReferenceModel` constructor allowing for any or all of `Σ_dir`, `Σ_t_start`, `Σ_t_end` to be
unspecified, in which case they take their values from `y_dir`, `t_start` and `t_end`, respectively.

    ReferenceModel(
        y_names::Vector{String},
        y_dir::String,
        scm_parent_dir::String,
        scm_suffix::String,
        case_name::String,
        t_start::Real,
        t_end::Real;
        Σ_dir::Union{String, Nothing} = nothing,
        Σ_t_start::Union{Real, Nothing} = nothing,
        Σ_t_end::Union{Real, Nothing} = nothing,
        n_obs::Union{Integer, Nothing} = nothing,
        namelist_args = nothing,
    )

`ReferenceModel` constructor using `scm_parent_dir`, `case_name`, `scm_suffix`, to define `scm_dir`.
"""
Base.@kwdef struct ReferenceModel{FT <: Real}
    "Vector of reference variable names"
    y_names::Vector{String}
    "Directory for reference data to compute `y` mean vector"
    y_dir::String
    "Directory for reference data to compute `Σ` covariance matrix"
    Σ_dir::String
    "Directory for static data related to forward scm model (parameter file & vertical levels)"
    scm_dir::String
    "Name of case"
    case_name::String
    # TODO: Make t_start and t_end vectors for multiple time intervals per reference model.
    "Start time for computing mean statistics over"
    y_t_start::FT
    "End time for computing mean statistics over"
    y_t_end::FT
    "Start time for computing covariance statistics over"
    Σ_t_start::FT
    "End time for computing covariance statistics over"
    Σ_t_end::FT
    "Vector of observed vertical locations"
    z_obs::Vector{FT}
    "TurbulenceConvection namelist"
    namelist::Dict
end  # ReferenceModel struct

function ReferenceModel(
    y_names::Vector{String},
    y_dir::String,
    scm_dir::String,
    case_name::String,
    t_start::Real,
    t_end::Real;
    Σ_dir::Union{String, Nothing} = nothing,
    Σ_t_start::Union{Real, Nothing} = nothing,
    Σ_t_end::Union{Real, Nothing} = nothing,
    n_obs::Union{Integer, Nothing} = nothing,
    namelist_args = nothing,
)
    # Always create new namelist
    namelist = get_scm_namelist(scm_dir, case_name, y_dir = y_dir, namelist_args = namelist_args, overwrite = true)
    z_obs = construct_z_obs(namelist)
    z_obs = !isnothing(n_obs) ? Array(range(z_obs[1], z_obs[end], n_obs)) : z_obs
    FT = eltype(z_obs)

    Σ_dir = isnothing(Σ_dir) ? y_dir : Σ_dir
    Σ_t_start = isnothing(Σ_t_start) ? t_start : Σ_t_start
    Σ_t_end = isnothing(Σ_t_end) ? t_end : Σ_t_end

    ReferenceModel{FT}(
        y_names,
        y_dir,
        Σ_dir,
        scm_dir,
        case_name,
        FT(t_start),
        FT(t_end),
        FT(Σ_t_start),
        FT(Σ_t_end),
        z_obs,
        namelist,
    )
end

function ReferenceModel(
    y_names::Vector{String},
    y_dir::String,
    scm_parent_dir::String,
    scm_suffix::String,
    case_name::String,
    t_start::Real,
    t_end::Real;
    Σ_dir::Union{String, Nothing} = nothing,
    Σ_t_start::Union{Real, Nothing} = nothing,
    Σ_t_end::Union{Real, Nothing} = nothing,
    n_obs::Union{Integer, Nothing} = nothing,
    namelist_args = nothing,
)
    scm_dir = data_directory(scm_parent_dir, case_name, scm_suffix)
    args = (y_names, y_dir, scm_dir, case_name, t_start, t_end)
    return ReferenceModel(
        args...,
        Σ_dir = Σ_dir,
        Σ_t_start = Σ_t_start,
        Σ_t_end = Σ_t_end,
        n_obs = n_obs,
        namelist_args = namelist_args,
    )
end

get_t_start(m::ReferenceModel) = m.y_t_start
get_t_end(m::ReferenceModel) = m.y_t_end
get_t_start_Σ(m::ReferenceModel) = m.Σ_t_start
get_t_end_Σ(m::ReferenceModel) = m.Σ_t_end

"Returns the observed vertical locations for a reference model"
get_z_obs(m::ReferenceModel) = m.z_obs

y_dir(m::ReferenceModel) = m.y_dir
Σ_dir(m::ReferenceModel) = m.Σ_dir
scm_dir(m::ReferenceModel) = m.scm_dir
get_scm_namelist(m::ReferenceModel) = deepcopy(m.namelist)

# TODO: cache filename and move `get_stats_path` call to constructor.
y_nc_file(m::ReferenceModel) = get_stats_path(y_dir(m))
Σ_nc_file(m::ReferenceModel) = get_stats_path(Σ_dir(m))

data_directory(root::S, name::S, suffix::S) where {S <: AbstractString} = joinpath(root, "Output.$name.$suffix")
uuid(m::ReferenceModel) = String(split(scm_dir(m), ".")[end])

namelist_directory(root::String, m::ReferenceModel) = namelist_directory(root, m.case_name)
namelist_directory(root::S, casename::S) where {S <: AbstractString} = joinpath(root, "namelist_$casename.in")

num_vars(m::ReferenceModel) = length(m.y_names)

"""
    get_scm_namelist(
        output_dir::String,
        case_name::String;
        y_dir::Union{String, Nothing} = nothing,
        overwrite::Bool = false,
        namelist_args = nothing,
    )::Dict

Returns a TurbulenceConvection.jl namelist, given the case and a list of namelist arguments.

Inputs:

 - `output_dir`     :: Directory where the namelist will be stored or should be located.
 - `case_name`      :: Name of the TurbulenceConvection.jl case considered.
 - `y_dir`          :: Directory with LES data to drive the SCM with, if `case_name` is `LES_driven_SCM`.
 - `overwrite`      :: Whether to overwrite the namelist file, if it exists.
 - `namelist_args`  :: Vector of non-default arguments to be used in the namelist, defined as a vector of tuples.
Outputs:
 - `namelist`   :: The TurbulenceConvection.jl namelist.
"""
function get_scm_namelist(
    output_dir::String,
    case_name::String;
    y_dir::Union{String, Nothing} = nothing,
    overwrite::Bool = true,
    namelist_args = nothing,
)::Dict
    namelist_path = namelist_directory(output_dir, case_name)
    namelist = if ~isfile(namelist_path) | overwrite
        NameList.default_namelist(case_name, root = output_dir)
    else
        JSON.parsefile(namelist_path)
    end
    namelist["stats_io"]["calibrate_io"] = true
    if !isnothing(namelist_args)
        for namelist_arg in namelist_args
            change_entry!(namelist, namelist_arg)
        end
    end

    namelist["meta"]["uuid"] = String(split(output_dir, ".")[end])
    namelist["output"]["output_root"] = dirname(output_dir)
    # if `LES_driven_SCM` case, provide input LES stats file
    if case_name == "LES_driven_SCM"
        @assert !isnothing(y_dir) "lesfile must be specified in the construction of LES_driven_SCM namelist."
        namelist["meta"]["lesfile"] = get_stats_path(y_dir)
    end

    return namelist
end

"""
    construct_z_obs(namelist::Dict)

Constructs the vector of observed locations given a TurbulenceConvection.jl namelist.
"""
function construct_z_obs(namelist::Dict)
    grid = construct_grid(namelist)
    return vec(grid.zc)
end

"""
    construct_reference_models(kwarg_ld::Dict{Symbol, Vector{T} where T})::Vector{ReferenceModel}

Returns a vector of `ReferenceModel`s given a dictionary of keyword argument lists.

Inputs:

 - `kwarg_ld`     :: Dictionary of keyword argument lists

Outputs:

 - `ref_models`   :: Vector where the i-th ReferenceModel is constructed from the i-th element of every keyword argument list of the dictionary.
"""
function construct_reference_models(kwarg_ld::Dict{Symbol, Vector{T} where T})::Vector{ReferenceModel}
    n_RM = length(kwarg_ld[:case_name])
    ref_models = Vector{ReferenceModel}()
    for RM_i in 1:n_RM
        kw = Dict(k => v[RM_i] for (k, v) in pairs(kwarg_ld))  # unpack dict
        args = if haskey(kw, :scm_dir)
            # construct ref_models using `scm_dir` directly
            (kw[j] for j in (:y_names, :y_dir, :scm_dir, :case_name, :t_start, :t_end))
        elseif all(haskey.(Ref(kw), [:scm_parent_dir, :scm_suffix]))
            # construct ref_models using `scm_parent_dir` and `scm_suffix`
            (kw[j] for j in (:y_names, :y_dir, :scm_parent_dir, :scm_suffix, :case_name, :t_start, :t_end))
        else
            throw(
                ArgumentError(
                    "You need to specify either `scm_dir` or all of " *
                    "(`scm_parent_dir`, `case_name`, `scm_suffix`) to construct a `ReferenceModel`",
                ),
            )
        end
        push!(
            ref_models,
            ReferenceModel(
                args...,
                Σ_dir = get(kw, :Σ_dir, nothing),
                Σ_t_start = get(kw, :Σ_t_start, nothing),
                Σ_t_end = get(kw, :Σ_t_end, nothing),
                n_obs = get(kw, :n_obs, nothing),
                namelist_args = get(kw, :namelist_args, nothing),
            ),
        )
    end
    return ref_models
end

"""
    time_shift_reference_model(m::ReferenceModel, Δt::FT) where {FT <: Real}

Returns a time-shifted ReferenceModel, considering an interval relative to the last
available time step of the original model.

Inputs:

 - `m`     :: A ReferenceModel.
 - `Δt`  :: [LES last time - SCM start time (LES timeframe)]

Outputs:

 - The time-shifted ReferenceModel.
"""
function time_shift_reference_model(m::ReferenceModel, Δt::FT) where {FT <: Real}
    filename = y_nc_file(m)
    t = nc_fetch(filename, "t")
    t_start = t[end] - Δt + get_t_start(m)
    t_end = t[end] - Δt + get_t_end(m)
    Σ_t_start = t[end] - Δt + get_t_start_Σ(m)
    Σ_t_end = t[end] - Δt + get_t_end_Σ(m)

    @assert t_start >= 0 "t_start must be positive after time shift, but $t_start was given."
    @assert Σ_t_start >= 0 "Σ_t_start must be positive after time shift, but $Σ_t_start was given."
    @info string(
        "Shifting time windows for ReferenceModel $(m.case_name)",
        "to ty=($t_start, $t_end), tΣ=($Σ_t_start, $Σ_t_end).",
    )
    return ReferenceModel(
        m.y_names,
        m.y_dir,
        m.Σ_dir,
        m.scm_dir,
        m.case_name,
        t_start,
        t_end,
        Σ_t_start,
        Σ_t_end,
        m.z_obs,
        m.namelist,
    )
end

"""
    struct ReferenceModelBatch

A structure containing a batch of ReferenceModels and an evaluation
order for ReferenceModels within the current epoch.

# Fields

$(TYPEDFIELDS)

# Constructors

    ReferenceModelBatch(ref_models::Vector{ReferenceModel}, shuffling::Bool = true)

`ReferenceModelBatch` constructor given a vector of `ReferenceModel`s.

    ReferenceModelBatch(kwarg_ld::Dict{Symbol, Vector{T} where T}, shuffling::Bool = true)

`ReferenceModelBatch` constructor given a dictionary of keyword argument lists.

Inputs:

 - `kwarg_ld`     :: Dictionary of keyword argument lists
 - `shuffling`    :: Whether to shuffle the order of ReferenceModels.
"""
Base.@kwdef struct ReferenceModelBatch
    "Vector containing all reference models"
    ref_models::Vector{ReferenceModel}
    "Vector of indices defining the `ReferenceModel` evaluation order when batching"
    eval_order::Vector{Int}
end

function ReferenceModelBatch(kwarg_ld::Dict{Symbol, Vector{T} where T}, shuffling::Bool = true)
    ref_models = construct_reference_models(kwarg_ld)
    eval_order = shuffling ? shuffle(1:length(ref_models)) : 1:length(ref_models)
    return ReferenceModelBatch(ref_models, eval_order)
end

function ReferenceModelBatch(ref_models::Vector{ReferenceModel}, shuffling::Bool = true)
    eval_order = shuffling ? shuffle(1:length(ref_models)) : 1:length(ref_models)
    return ReferenceModelBatch(ref_models, eval_order)
end

"""
    get_minibatch!(ref_models::ReferenceModelBatch, batch_size::Int)

Returns a minibatch of `ReferenceModel`s from a ReferenceModelBatch and updates
the eval order.

The size of the minibatch is either the requested size, or the remainder of the
elements in the eval_order for this epoch.

Inputs:

 - `ref_model_batch` :: A ReferenceModelBatch.
 - `batch_size`      :: The number of `ReferenceModel`s to retrieve.

Outputs:

 - A vector of `ReferenceModel`s.
 - The indices of the returned `ReferenceModel`s.
"""
function get_minibatch!(ref_model_batch::ReferenceModelBatch, batch_size::Int)
    batch = min(batch_size, length(ref_model_batch.eval_order))
    indices = [pop!(ref_model_batch.eval_order) for i in 1:batch]
    return ref_model_batch.ref_models[indices], indices
end

"Restarts a shuffled evaluation order if the current epoch has finished."
function reshuffle_on_epoch_end(ref_model_batch::ReferenceModelBatch, shuffling::Bool = true)
    if isempty(ref_model_batch.eval_order)
        @info "Current epoch finished. Reshuffling dataset."
        return ReferenceModelBatch(ref_model_batch.ref_models, shuffling)
    else
        return ref_model_batch
    end
end

function write_ref_model_batch(ref_model_batch::ReferenceModelBatch; outdir_path::String = pwd())
    jldsave(joinpath(outdir_path, "ref_model_batch.jld2"); ref_model_batch)
end
function write_val_ref_model_batch(ref_model_batch::ReferenceModelBatch; outdir_path::String = pwd())
    jldsave(joinpath(outdir_path, "val_ref_model_batch.jld2"); ref_model_batch)
end

end # module


using Graphs
using AtomicLevels

type LevelVertex
    config::Config
    term::Term
    calc_props::Dict
    exp_eng
    wfn
end
import Graphs.attributes
function attributes(l::LevelVertex, g::AbstractGraph)
    Dict{UTF8String,Any}("label" => "$(l.config) $(l.term)")
end

LevelVertex(config::Config, term::Term,
      exp_eng = Inf;
      kwargs...) = LevelVertex(config, term, Dict(kwargs), exp_eng, nothing)

level_graph_init = () -> graph(Vector{LevelVertex}(),Vector{Edge{LevelVertex}}())
level_graph = level_graph_init()

import Base.print, Base.show
function print(io::IO, lg::typeof(level_graph_init()))
    map(reverse(topological_sort_by_dfs(level_graph))) do l
        println(l.config, " ", l.term)
    end
end

function add_level!(dep::Union{LevelVertex,Void}, args...; kwargs...)
    s = LevelVertex(args...; kwargs...)
    global level_graph
    add_vertex!(level_graph, s)
    dep != nothing && add_edge!(level_graph, s, dep)
    s
end
add_level!(args...; kwargs...) = add_level!(nothing, args...; kwargs...)

function add_levels!(dep::Union{LevelVertex,Void}, config::Config, args...; kwargs...)
    ts = terms(config)
    for i in eachindex(ts)
        a = copy(args)
        if length(a) > 0 && length(a[1]) > 1
            a = (a[1][i],a[2:end]...)
        end
        dep = add_level!(dep, config, ts[i], a...; kwargs...)
    end
    dep
end
add_levels!(args...; kwargs...) = add_levels!(nothing, args...; kwargs...)

function clear_levels!()
    global level_graph
    level_graph = level_graph_init()
end

# Used to perform calculation using MPI codes if that is
# requested. However, certain level calculations may be explicitly
# marked as unsuitable for MPI codes (e.g. gst of He II), so these are
# masked.
function maybe_mpi_run(g::Function, d::Dict)
    if get(d, :nompi, false)
        delete!(d, :nompi)
        delete!(d, :mpi_np)
        g(d)
    elseif (np = get(d, :mpi_np, 1)) > 1
        delete!(d, :mpi_np)
        init_mpi(np, joinpath(homedir(), "tmp_mpi")) do
            g(d)
        end
    else
        g(d)
    end
end

function get_and_delete!(d::Dict, sym)
    v = d[sym]
    delete!(d, sym)
    v
end

function get_and_delete!(d::Dict, args...)
    map(args) do a
        get_and_delete!(d, a)
    end
end

get_wfn(wfn::Union{AbstractString,Void}) = wfn
get_wfn(wfn::Vector) = first(wfn)

function calc_level(f::Function, level::LevelVertex, wfn; kwargs...)
    wfn = maybe_mpi_run((d) -> f(level, d, get_wfn(wfn)),
                        merge(Dict(kwargs), level.calc_props))
    println(wfn)
    assert(isfile(wfn))
    wfn
end

# Loop over all levels in topological order such that the output of
# one calculation can be used as input for the next, that has
# requested that.
function calc_levels(f::Function; kwargs...)
    global level_graph

    map(reverse(topological_sort_by_dfs(level_graph))) do l
        e = out_edges(l, level_graph)
        if length(e) > 0
            wfn = first(e).target.wfn
        else
            wfn = nothing
        end

        l.wfn = calc_level(f, l, wfn; kwargs...)
    end
end

function calc_levels_atsp(Z; kwargs...)
    calc_levels(;kwargs...) do l,d,wfn0
        ncorr = get_and_delete!(d, :ncorr)
        hf_mchf_bp(l.config, l.term, Z, ncorr, wfn0; overwrite = false, d...)[2]
    end
end

function plot_level_graph(filename)
    stdin, proc = open(`neato -T$(splitext(filename)[2][2:end]) -o "$filename"`, "w")
    to_dot(AST.level_graph, stdin)
    close(stdin)
    filename
end

export add_level!, add_levels!, clear_levels!, calc_levels, calc_levels_atsp, plot_level_graph


function fight(attackmultiplier::Int64, attacklower::Int64, hpforplayer::Int64, monsterlvl::Int64)
    global attackpoints = 1.05*attackmultiplier
    global playerhp = 15+hpforplayer
    yesorno = rand(1:100)
    if yesorno < 33
        whatalien = rand(1:3)
        if whatalien == 1
            println("A [insert name here] attacked you!")
            global alive = "True"
            global hp = 2*monsterlvl
            while alive == "True"
                global attacklow = "False"
                global poisoning = "False"
                println("Use attack [insert name here], [insert name here], or [insert name here]:")
                global answer = "placeholder"
                global answer = readline()
                if answer == "[insert name here]"
                    global poisoning = "True"
                    if attacklow == "True"
                        global hp = hp-attackpoints
                    else
                        global hp = hp-attackpoints/attacklower
                    end
                    println("You Hit! [insert name here] HP: $(hp)")
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:5)
                    if hitormiss > 2
                        global hp = hp-(attackpoints*1.5)
                        global poisoning = "False"
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        println("You missed ([insert name here] attack lowered)")
                        global attacklow = "True"
                    end
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:3)
                    if hitormiss == 1
                        global hp = hp/attackmultiplier
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        global playerhp = playerhp-5
                        println("You missed and hurt yourself!")
                    end
                end
                if alive == "True"
                    println("[insert name here] is attacking! Your HP: $(playerhp)")
                    println("[insert name here] used [insert name here]!")
                    global playerhp = playerhp-(attackpoints/4)
                    println("Your HP is now $(playerhp)")
                end
                if poisoning == "True"
                    global hp = hp-2
                    println("Poisoning...")
                    println("ENEMY HP: $(hp)")
                end
                if hp < 1
                    global alive = "False"
                    println("You won the battle")
                end
                if playerhp < 1
                    global alive = "False"
                    print("You lost the battle")
                end
            end
        elseif whatalien == 2
            println("[insert name here] attacked you!")
            global alive = "True"
            global hp = 5*monsterlvl
            while alive == "True"
                global attacklow = "False"
                global poisoning = "False"
                println("Use attack [insert name here], [insert name here], or [insert name here]:")
                global answer = "placeholder"
                global answer = readline()

                if answer == "[insert name here]"
                    global poisoning = "True"
                    if attacklow == "True"
                        global hp = hp-attackpoints
                    else
                        global hp = hp-attackpoints/2
                    end
                    println("You Hit! [insert name here] HP: $(hp)")
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:5)
                    if hitormiss > 2
                        global hp = hp-(attackpoints*1.5)
                        global poisoning = "False"
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        println("You missed ([attack 1 name here] attack lowered)")
                        global attacklow = "True"
                    end
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:3)
                    if hitormiss == 1
                        global hp = hp/attackmultiplier
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        global playerhp = playerhp-5
                        println("You missed and hurt yourself!")
                    end
                end
                if alive == "True"
                    println("[insert name here] is attacking! Your HP: $(playerhp)")
                    println("[insert name here] used [insert name here]!")
                    global playerhp = playerhp-4
                    println("Your HP is now $(playerhp)")
                end
                if poisoning == "True"
                    global hp = hp-2
                    println("Poisoning...")
                    println("ENEMY HP: $(hp)")
                end
                if hp < 1
                    global alive = "False"
                end
                if playerhp < 1
                    global alive = "False"
                    print("You lost the battle")
                end
            end
        elseif whatalien == 3
            println("A [insert name here] attacked you!")
            global alive = "True"
            global hp = 8*monsterlvl
            while alive == "True"
                global attacklow = "False"
                global poisoning = "False"
                println("Use attack [insert name here], [insert name here], or [insert name here]:")
                global answer = "placeholder"
                global answer = readline()

                if answer == "[insert name here]"
                    global poisoning = "True"
                    if attacklow == "True"
                        global hp = hp-attackpoints
                    else
                        global hp = hp-attackpoints/2
                    end
                    println("You Hit! Bratlle HP: $(hp)")
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:5)
                    if hitormiss > 2
                        global hp = hp-(attackpoints*1.5)
                        global poisoning = "False"
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        println("You missed ([attack 1 name] attack lowered)")
                        global attacklow = "True"
                    end
                elseif answer == "[insert name here]"
                    global hitormiss = rand(1:3)
                    if hitormiss == 1
                        global hp = hp/attackmultiplier
                        println("You Hit! [insert name here] HP: $(hp)")
                    else
                        global playerhp = playerhp-5
                        println("You missed and hurt yourself!")
                    end
                end
                if alive == "True"
                    println("[insert name here] is attacking! Your HP: $(playerhp)")
                    println("[insert name here] used [insert name here]!")
                    global playerhp = playerhp-5
                    println("Your HP is now $(playerhp)")
                end
                if poisoning == "True"
                    global hp = hp-2
                    println("Poisoning...")
                    println("ENEMY HP: $(hp)")
                end
                if hp < 1
                    global alive = "False"
                    println("You won the battle")
                end
                if playerhp < 1
                    global alive = "False"
                    print("You lost the battle")
                end
            end
        end
    else
        println("You were not attacked")
    end
end