export generate, generate_accompaniment

using NoteSequences
using NoteSequences: DEFAULT_STEPS_PER_SECOND

using StatsBase: wsample

const MIN_PITCH = 21
const MAX_PITCH = 108
const VELOCITY_BINS = 32

"""
    default_melody_encoder()

Return a TextMelodyEncoder with the following parameters.
`minpitch::Int=21`
`maxpitch::Int=109`
`steps_per_second::Int=100`
"""
default_melody_encoder() = TextMelodyEncoder(MIN_PITCH, MAX_PITCH + 1, DEFAULT_STEPS_PER_SECOND)

"""
    default_performance_encoder(add_eos::Bool=true)

Return a MidiPerformanceEncoder with the following parameters.
`steps_per_second::Int`=100
`velocity_bins::Int`=32
`minpitch::Int`=21
`maxpitch::Int`=109

If `add_eos` is true, `EOS_TOKEN` is appended at the end of the sequence.
"""
function default_performance_encoder(add_eos::Bool=true)
    MidiPerformanceEncoder(DEFAULT_STEPS_PER_SECOND, VELOCITY_BINS,
                           MIN_PITCH, MAX_PITCH, add_eos)
end

"""
    generate(model::UnconditionalMusicTransformer;
             primer::Performance=Performance(DEFAULT_STEPS_PER_SECOND, velocity_bins=32),
             numsteps::Int=3000,
             as_notesequence::Bool=false,
             encoder::MidiPerformanceEncoder=default_performance_encoder())

Generate a musical performance from the `UnconditionalMusicTransformer` with the given
`primer`. If the `primer` is empty, a default event of `PerformanceEvent(TIME_SHIFT, 100)` will be
appended. The performance will be converted to and from one-hot indices using the `encoder`.

`numsteps` determines the amount of steps to generate for.
A step is 10 milliseconds or 0.01 seconds at the default steps per second rate of 100.
If `as_notesequence` is true, a `NoteSequence` is returned.
Otherwise, a midifile is returned.
"""
function generate(model::UnconditionalMusicTransformer;
                  primer::Performance=Performance(DEFAULT_STEPS_PER_SECOND, velocity_bins=VELOCITY_BINS),
                  numsteps::Int=3000,
                  as_notesequence::Bool=false,
                  encoder::MidiPerformanceEncoder=default_performance_encoder())

    performance = deepcopy(primer)

    targets = map(event -> encode_event(event, encoder), performance)
    pred = 0
    @info "Generating..."

    # Sample till EOS is encountered or numsteps is reached
    while pred != EOS_TOKEN && performance.numsteps <= numsteps
        t = targets |> device
        logits = model(t)
        probabilities = softmax(logits[:, end]) |> cpu
        pred = wsample(1:encoder.vocab_size, probabilities)

        # Add prediction to the model targets
        push!(targets, pred)

        # Add predicted event to performance
        push!(performance, decode_event(pred, encoder))
    end

    ns = getnotesequence(performance)
    as_notesequence == true && return ns
    midifile(ns)
end

"""
Generate a musical performance from the `UnconditionalMusicTransformer` with the given
`target_indices`. `target_indices` is the one-hot indices of performance events. If it is empty,
a zero matrix will be passed to the model (default token).

`decode_len` determines the length of the sequence to decode.
The generated performance will be decoded from the predicted indices using the `encoder`.
If `as_notesequence` is true, a `NoteSequence` is returned.
Otherwise, a midifile is returned.
"""
function generate(model::UnconditionalMusicTransformer,
                  target_indices::Vector{Int};
                  decode_len::Int=1024,
                  as_notesequence::Bool=false,
                  encoder::MidiPerformanceEncoder=default_performance_encoder())

    targets = deepcopy(target_indices)
    pred = 0
    @info "Generating..."

    # Sample till EOS is encountered or decoding length is reached
    while pred != EOS_TOKEN && length(targets) <= decode_len
        t = targets |> device
        logits = model(t)
        probabilities = softmax(logits[:, end]) |> cpu
        pred = wsample(1:encoder.vocab_size, probabilities)
        push!(targets, pred)
    end

    ns = decode_to_notesequence(targets, encoder)
    as_notesequence == true && return ns
    midifile(ns)
end

"""
    generate_accompaniment(model::MelodyConditionedMusicTransformer,
                           melody::Vector{Int};
                           decode_len::Int=2048,
                           as_notesequence::Bool=false,
                           target_encoder::MidiPerformanceEncoder=default_performance_encoder())

Generate an accompaniment for the given melody using the `MelodyConditionedMusicTransformer`.

`decode_len` determines the length of the sequence to decode.
The generated performance will be decoded from the predicted indices using `target_encoder`.
If `as_notesequence` is true, a `NoteSequence` is returned.
Otherwise, a midifile is returned.
"""
function generate_accompaniment(model::MelodyConditionedMusicTransformer,
                                melody::Vector{Int};
                                decode_len::Int=(4096 - length(inputs)),
                                as_notesequence::Bool=false,
                                target_encoder::MidiPerformanceEncoder=default_performance_encoder())

    if isempty(melody)
        throw(error("Melody must not be empty"))
    end

    targets = Vector{Int}()
    @info "Generating..."

    model = model |> device

    melody, targets = device.([melody, targets])
    logits, encoder_context = model(melody, targets)
    pred = wsample(1:target_encoder.vocab_size, softmax(logits[:, end]) |> cpu)
    push!(targets, pred)

    while pred != EOS_TOKEN && length(targets) <= decode_len
        t = targets |> device
        logits = model(t, encoder_context)
        probabilities = softmax(logits[:, end]) |> cpu
        pred = wsample(1:target_encoder.vocab_size, probabilities)
        push!(targets, pred)
    end

    ns = decode_to_notesequence(targets, target_encoder)
    as_notesequence == true && return ns
    midifile(ns)
end


# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control
#
# This file is part of module
#   ModiaMath.SimulationEngine(ModiaMath/SimulationEngine/_module.jl)
#

# Methods that involve IDA UserData

#=
"KLU sparse matrix type"
type SlsMat
   M::Cint                        # number of rows
   N::Cint                        # number of columns
   NNZ::Cint                      # maximum number of nonzero entries in the matrix
   data::Ptr{Sundials.realtype}   # data[NNZ]    - value of nonzero entries
   rowvals::Ptr{Cint}             # rowvals[NNZ] - row indices of data (index starts at zero)
   colptrs::Ptr{Cint}             # colptrs[N+1] - index of the first column entry into the
                                  #                data and rowvals arrays (index starts at zero)
                                  #                The last entry contains the total number of nonzero values
                                  #                in the matrix and hence points one past the end
                                  #                of the active data in the data and rowvals arrays.
end

const SlsMat_Ptr = Ptr{SlsMat}

"""
    CSCtoSlsMat!(mat, matKLU)

Copy SparseMatrixCSC mat to SlsMat data structure matKLU
(storage for both objects are provided in the calling program)
"""
function CSCtoSlsMat!(mat::SparseMatrixCSC{Float64,Cint}, matKLU::SlsMat)
   for i = 1:length(mat.nzval)
      unsafe_store!(matKLU.data   , mat.nzval[i], i)
      unsafe_store!(matKLU.rowvals, mat.rowval[i]-1, i)
   end

   for i = 1:length(mat.colptr)
      unsafe_store!(matKLU.colptrs, mat.colptr[i]-1, i)
   end
end
=#

##################################################################
#
# Wrappers to IDA code
#
##################################################################

function idasol_f(t::Sundials.realtype, _y::Sundials.N_Vector, _yp::Sundials.N_Vector, _r::Sundials.N_Vector, simModel::IntegratorData)
    Sundials.IDAGetCurrentStep(simModel.ida_mem, simModel.hcur)
    Sundials.IDAGetCurrentOrder(simModel.ida_mem, simModel.order)
    stat           = simModel.statistics
    stat.hMin      = min(stat.hMin, simModel.hcur[1])
    stat.hMax      = max(stat.hMax, simModel.hcur[1])
    stat.orderMax  = max(stat.orderMax, simModel.order[1])
    stat.nResidues += 1

    sim                  = simModel.simulationState
    sim.time             = t
    simModel.last_t      = t
    simModel.last_norm_y = norm(simModel.y, Inf)

    # Check that simModel.y is still identical to _y
    if pointer(simModel.y) === Sundials.__N_VGetArrayPointer_Serial(_y)
       # If this assumption is true, no unnecessary memory is allocated via Sundials.asarray(..)
        y  = simModel.y
    else
        y  = Sundials.asarray(_y)
        println("\n!!! Info message from ModiaMath.simulate:\n",
                 "    idasol_f assumption SimModel.y === _y is not valid; using asarray(_y).")
    end

    if pointer(simModel.yp) === Sundials.__N_VGetArrayPointer_Serial(_yp)
       # If this assumption is true, no unnecessary memory is allocated via Sundials.asarray(..)
        yp = simModel.yp
    else
        yp = Sundials.asarray(_yp)
        println("\n!!! Info message from ModiaMath.simulate:\n",
                 "    idasol_f assumption SimModel.yp === _yp is not valid; using asarray(_yp).")
    end

    ModiaMath.DAE.getResidues!(simModel.model, sim, t, y, yp, simModel.r, simModel.hcur[1])

    # Copy simModel.r to _r
    @static if VERSION >= v"0.7.0-DEV.2005"
        unsafe_copyto!(Sundials.__N_VGetArrayPointer_Serial(_r), pointer(simModel.r), simModel.simulationState.nx)
    else
        unsafe_copy!(Sundials.__N_VGetArrayPointer_Serial(_r), pointer(simModel.r), simModel.simulationState.nx)
    end
    return Cint(0)   # indicates normal return
end

@static if VERSION >= v"0.7.0-DEV.2005"
    @noinline function old_cfunction(f, r, a)
        ccall(:jl_function_ptr, Ptr{Cvoid}, (Any, Any, Any), f, r, a)
    end
else
    const idasol_fc = cfunction(idasol_f, Cint, (Sundials.realtype, Sundials.N_Vector, Sundials.N_Vector, Sundials.N_Vector, Ref{IntegratorData}))
end

#------- root finding
function idasol_g(t::Sundials.realtype, y::Sundials.N_Vector, yp::Sundials.N_Vector, gout::Ptr{Sundials.realtype}, simModel::IntegratorData)
    simModel.statistics.nZeroCrossings += 1
    sim      = simModel.simulationState
    sim.time = t
    ModiaMath.DAE.getEventIndicators!(simModel.model, sim, t, Sundials.asarray(y), Sundials.asarray(yp), simModel.z)

    for i = 1:simModel.simulationState.nz
        unsafe_store!(gout, simModel.z[i], i)
    end
    return Cint(0)   # indicates normal return
end

@static if VERSION < v"0.7.0-DEV.2005"
    const idasol_gc = cfunction(idasol_g, Cint, (Sundials.realtype, Sundials.N_Vector, Sundials.N_Vector, Ptr{Sundials.realtype}, Ref{IntegratorData}))
end

#=
#-------- Jacobian
function idasol_sjac(t::Sundials.realtype, c_j::Sundials.realtype, y::Sundials.N_Vector, yp::Sundials.N_Vector, r::Sundials.N_Vector,
                     jacKLU::SlsMat, simModel::IntegratorData,
                     tmp1::Sundials.N_Vector, tmp2::Sundials.N_Vector, tmp3::Sundials.N_Vector)
    # Compute non-zero values of jac
    sim      = simModel.simulationState
    sim.time = t
    h        = IDAGetCurrentStep(simModel.ida_mem)
    IDAGetErrWeights(simModel.ida_mem, simModel.eweight)

    ModiaMath.SparseJacobian.computeJacobian!(ModiaMath.DAE.getResidues!, simModel.model, simModel.simulationState,
                                              t, Sundials.asarray(y), Sundials.asarray(yp), Sundials.asarray(r),
                                              sim.cg, c_j, h, Sundials.asarray(simModel.eweight), Sundials.asarray(tmp1), sim.jac)
    simModel.statistics.nResidues += sim.cg.ngroups

    # Copy userData.jac to jacKLU
    #   function call gives a Julia crash: CSCtoSlsMat!(simModel.cm.jac, jacKLU)
    #   Manually inlining the function works
    jac = sim.jac
    for i = 1:length(jac.nzval)
      unsafe_store!(jacKLU.data   , jac.nzval[i], i)
      unsafe_store!(jacKLU.rowvals, jac.rowval[i]-1, i)
    end

    for i = 1:length(jac.colptr)
      unsafe_store!(jacKLU.colptrs, jac.colptr[i]-1, i)
    end

    return Cint(0)   # indicates normal return
end

@static if VERSION < v"0.7.0-DEV.2005"
    const idasol_sjacc = @cfunction(idasol_sjac, Int32, (Sundials.realtype, Sundials.realtype, Sundials.N_Vector, Sundials.N_Vector, Sundials.N_Vector, Ref{SlsMat},
                                                         Ref{IntegratorData}, Sundials.N_Vector, Sundials.N_Vector, Sundials.N_Vector))
else
    const idasol_sjacc = cfunction(idasol_sjac, Int32, (Sundials.realtype, Sundials.realtype, Sundials.N_Vector, Sundials.N_Vector, Sundials.N_Vector, Ref{SlsMat},
                                                        Ref{IntegratorData}, Sundials.N_Vector, Sundials.N_Vector, Sundials.N_Vector))
end
=#

function idasol_ErrHandlerFn(error_code::Cint, IDAmodule::Cstring, IDAfunction::Cstring,
                             message::Cstring, simModel::IntegratorData)
    modelName = simModel.model.modelName
    if error_code > 0
        # Print information text
        println("\n\n!!! Warning from ModiaMath.simulate(", modelName, ", ...): ", unsafe_string(IDAfunction),
               "(...) returned with [", unsafe_string(IDAmodule), "] error_code  = ",
               error_code, ":\n", unsafe_string(message), "\n")
    elseif error_code < 0
        # Raise error
        time   = simModel.last_t
        norm_y = simModel.last_norm_y

        if time > typemin(Float64)
            error("\n\n!!! Error from ModiaMath.simulate(", modelName, ", ...): ", unsafe_string(IDAfunction),
                "(...) returned with an [", unsafe_string(IDAmodule), "] error:\n",
                unsafe_string(message), "\n( norm( x(t=", @sprintf("%0.3g",time), " s) ) = ", @sprintf("%0.3g",norm_y),
                "; if this value is large, the model is unstable )\n")
        else
            error("\n\n!!! Error from ModiaMath.simulate(", modelName, ", ...): ", unsafe_string(IDAfunction),
                "(...) returned with an [", unsafe_string(IDAmodule), "] error:\n",
                unsafe_string(message), "\n")
        end
    end
    nothing
end

@static if VERSION < v"0.7.0-DEV.2005"
    const idasol_ErrHandlerFnc = cfunction(idasol_ErrHandlerFn, Void, (Cint, Cstring, Cstring, Cstring, Ref{IntegratorData}))
end


__precompile__()

module BioAlignments

export
    # alignment operations
    Operation,
    OP_MATCH,
    OP_INSERT,
    OP_DELETE,
    OP_SKIP,
    OP_SOFT_CLIP,
    OP_HARD_CLIP,
    OP_PAD,
    OP_SEQ_MATCH,
    OP_SEQ_MISMATCH,
    OP_BACK,
    OP_START,

    # alignment type
    AlignmentAnchor,
    Alignment,
    AlignedSequence,
    seq2ref,
    ref2seq,
    ismatchop,
    isinsertop,
    isdeleteop,
    cigar,

    # substitution matrices
    AbstractSubstitutionMatrix,
    SubstitutionMatrix,
    DichotomousSubstitutionMatrix,
    EDNAFULL,
    PAM30,
    PAM70,
    PAM250,
    BLOSUM45,
    BLOSUM50,
    BLOSUM62,
    BLOSUM80,
    BLOSUM90,

    # alignment types
    GlobalAlignment,
    SemiGlobalAlignment,
    OverlapAlignment,
    LocalAlignment,
    EditDistance,
    HammingDistance,
    LevenshteinDistance,

    # alignment models
    AbstractScoreModel,
    AffineGapScoreModel,
    AbstractCostModel,
    CostModel,

    # pairwise alignment
    PairwiseAlignment,
    count_matches,
    count_mismatches,
    count_insertions,
    count_deletions,
    count_aligned,
    PairwiseAlignmentResult,
    pairalign,
    score,
    distance,
    alignment,
    hasalignment,

    SAM,
    BAM,
    header,
    eachoverlap,
    isfilled,
    seqname,
    hasseqname,
    leftposition,
    hasleftposition,
    rightposition,
    hasrightposition,
    sequence,
    hassequence

import BioCore: BioCore, distance, header, isfilled, seqname, hasseqname, sequence, hassequence, leftposition, rightposition, hasleftposition, hasrightposition
import BioSequences
import BioSymbols
import GenomicFeatures: eachoverlap
import IntervalTrees
using LinearAlgebra: diagind

include("operations.jl")
include("anchors.jl")
include("alignment.jl")
include("alignedseq.jl")

include("types.jl")
include("submat.jl")
include("models.jl")
include("pairwise/pairalign.jl")

include("sam/sam.jl")
include("bam/bam.jl")

end # module


using YaoExtensions, Yao
using Random, Test

@testset "EchoBlock" begin
    block = EchoBlock(4, :Test)
    reg = rand_state(4)
    @test reg |> block == reg
    @test mat(block) != nothing
    @test ishermitian(block)
    @test isreflexive(block)
    @test isunitary(block)
    @test getiparams(block) == ()

    @test EchoBlock()(5) isa EchoBlock
    @test EchoBlock()(5).sym == :ECHO
end


using DFTShims
using AxisArrays
using Unitful

macro lintpragma(s) end
@lintpragma("Ignore use of undeclared D")
@lintpragma("Ignore use of undeclared U")

const DD = Dispatch.Dimensions
const DH = Dispatch.Hartree

spin = AxisArray(zeros((10, 2)), Axis{:radius}(0u"m":1u"m":9u"m"), Axis{:spin}((:+, :-)))
nospin = AxisArray(zeros((10, 2)), Axis{:radius}(0u"m":1u"m":9u"m"), Axis{:n}((1, 2)))
const polarized = ColinearSpin()
const unpolarized = SpinDegenerate()

@testset "has_axis" begin
    @test has_axis(spin, :spin)
    @test has_axis(spin, Axis{:spin})
    @test has_axis(typeof(spin), :spin)
    @test has_axis(typeof(spin), Axis{:spin})
    @test !has_axis(nospin, :spin)
    @test !has_axis(nospin, Axis{:spin})
    @test !has_axis(typeof(nospin), :spin)
    @test !has_axis(typeof(nospin), Axis{:spin})
end

@testset "spin polarization" begin
    @test @inferred is_spin_polarized(spin)
    @test ! @inferred is_spin_polarized(nospin)

    @test @inferred is_spin_polarized(typeof(spin))
    @test ! @inferred is_spin_polarized(typeof(nospin))

    @test @inferred(SpinCategory(spin)) === polarized
    @test @inferred(SpinCategory(nospin)) === unpolarized
end

@testset "components" begin
    @test @inferred(components(UnitfulHartree.ρ, unpolarized)) == (:ρ,)
    @test components(UnitfulHartree.ρ, polarized) == (:α, :β)
    @test components(UnitfulHartree.ϵ, polarized) == (:α, :β)
    @test length(components(DD.Scalars.∂ϵ_∂σ, unpolarized)) == 1
    @test length(@inferred(components(UnitfulHartree.∂ϵ_∂σ, polarized))) == 3
    @test length(@inferred(components(UnitfulHartree.∂²ϵ_∂ρ², polarized))) == 3
    @test length(@inferred(components(UnitfulHartree.∂²ϵ_∂ρ∂σ, unpolarized))) == 1
    @test length(@inferred(components(UnitfulHartree.∂²ϵ_∂ρ∂σ, polarized))) == 6
    @test length(@inferred(components(UnitfulHartree.∂²ϵ_∂σ², polarized))) == 6
    @test length(@inferred(components(UnitfulHartree.∂³ϵ_∂ρ³, polarized))) == 4
    @test length(@inferred(components(UnitfulHartree.∂³ϵ_∂ρ²∂σ, polarized))) == 9
    @test length(@inferred(components(UnitfulHartree.∂³ϵ_∂ρ∂σ², polarized))) == 12
    @test length(@inferred(components(UnitfulHartree.∂³ϵ_∂σ³, polarized))) == 10
end

@testset "functional category" begin
    @test @inferred(FunctionalCategory(DH.Scalars.ρ{Int64})) === LDA()
    @test @inferred(FunctionalCategory(DH.Scalars.∂ϵ_∂σ)) === GGA()
    @test @inferred(FunctionalCategory(DD.Scalars.∂²ϵ_∂ρ²)) === LDA()
    @test @inferred(FunctionalCategory(DD.Scalars.∂²ϵ_∂ρ∂σ)) === GGA()
    @test @inferred(FunctionalCategory(DD.Scalars.∂²ϵ_∂σ²)) === GGA()
    @test @inferred(FunctionalCategory(UnitfulHartree.∂³ϵ_∂ρ³)) === LDA()
    @test @inferred(FunctionalCategory(UnitfulHartree.∂³ϵ_∂ρ²∂σ)) === GGA()
    @test @inferred(FunctionalCategory(UnitfulHartree.∂³ϵ_∂ρ∂σ²)) === GGA()
    @test @inferred(FunctionalCategory(UnitfulHartree.∂³ϵ_∂σ³)) === GGA()
end

@testset "Type concretization" begin
    @test @inferred(concretize_type(DD.Scalars.ρ, Int16)) === DH.Scalars.ρ{Int16}
    @test @inferred(concretize_type(DH.Scalars.ρ, Int16)) === DH.Scalars.ρ{Int16}
    @test @inferred(concretize_type(typeof(1.0u"m"), Int16)) === typeof(1.0u"m")
    @test @inferred(concretize_type(DH.Scalars.ρ, Quantity{Int16, D, U} where {D, U})) ===
            DH.Scalars.ρ{Int16}
    @test @inferred(concretize_type(DD.Scalars.ρ, Quantity{Int16, D, U} where {D, U})) ===
            DH.Scalars.ρ{Int16}
    const Q = Quantity{Int16, typeof(dimension(1u"C")), U} where U
    @test @inferred(concretize_type(DD.Scalars.ρ, Q)) === DH.Scalars.ρ{Int16}
end


julia> [RpcApi.SimpleOrganism(0x0000000000022086,:(function (o::Creature.Organism,)
        function func_273(var_272::Int8=-40)
            Creature.eatLeft(o,Int(var_272))
            var_272 = var_272 + var_272
            var_272 = var_272 * var_272
            var_272 = var_272 + var_272
            Creature.eatRight(o,Int(var_272))
            return var_272
        end
        function func_264(var_263::Int8=-125)
            for i::Int8 = 1:div(var_263,_LOOP_STEPS_DIVIDER)
            end
            return var_263
        end
        local var_243::Bool = false
        function func_232(var_230::Bool=false,var_231::Int16=-29748)
            Creature.stepLeft(o)
            return var_230
        end
        function func_189(var_188::Int16=2883)
            Creature.stepDown(o)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            Creature.stepDown(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_117 = var_117 + var_117
        o.mem[var_156] = var_156
        var_124 = var_124 + var_124
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        var_124 = var_124 / var_124
        var_124 = var_124 * var_124
        var_243 = var_123 | var_123
        if var_123 != var_123
            Creature.stepLeft(o)
        end
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepRight(o)
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        if var_123 !== var_123
            Creature.stepDown(o)
        end
        Creature.eatRight(o,Int(var_117))
        var_120 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_120
            end
        if var_124 <= var_124
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepRight(o)
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
        end
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        if var_123 <= var_123
            Creature.stepUp(o)
        end
        Creature.stepDown(o)
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],4,9571,49,118365,0x00000228,Dict(0=>0,10070=>10070,-4015=>19308,23323=>23323,19308=>19308,-1631=>-1631,1=>1),[1894,4],72),RpcApi.SimpleOrganism(0x0000000000021f07,:(function (o::Creature.Organism,)
        local var_243::Int8 = 83
        local var_242::Int16 = -28511
        function func_189(var_188::Int16=2883)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.stepDown(o)
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        o.mem[var_120] = var_120
        var_156 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_156
            end
        Creature.eatUp(o,Int(var_117))
        Creature.stepRight(o)
        Creature.eatUp(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        if var_120 > var_156
            local var_251::Int8 = 87
            Creature.stepRight(o)
            Creature.stepRight(o)
        end
        var_117 = var_117 + var_117
        Creature.eatRight(o,Int(var_117))
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = var_124 - var_124
        Creature.stepRight(o)
        var_124 = func_142(var_124)
        Creature.eatRight(o,Int(var_117))
        if var_124 <= var_124
            local var_230::Int16 = 27608
            var_230 = var_230 * var_230
            Creature.stepDown(o)
            var_230 = if haskey(o.mem,var_230)
                    o.mem[var_230]
                else
                    var_230
                end
        end
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepDown(o)
        var_123 = var_123 | var_123
        Creature.eatUp(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_156)))
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepDown(o)
        var_120 = func_189(var_156)
        Creature.eatDown(o,Int(var_243))
        Creature.eatDown(o,Int(var_117))
        Creature.stepLeft(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_120 = var_120 + var_120
        o.mem[var_120] = var_156
        Creature.eatLeft(o,Int(var_117))
        var_120 = var_120 - var_120
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 | var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.eatDown(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        if var_120 >= var_120
            local var_238::Int64 = -5358840696455759399
        end
        Creature.stepUp(o)
        if var_123 <= var_123
            local var_244::ASCIIString = "G36GCJ4z"
            Creature.stepLeft(o)
            Creature.stepUp(o)
            var_244 = var_244 * var_244
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
        end
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],3,9344,0,108844,0x00000089,Dict(-8030=>-4015,0=>-4015,-4015=>-4015,-3262=>-1631,23323=>23323,19308=>23323,-1631=>-4015,27608=>27608,1=>-4015),[1898,17],90),RpcApi.SimpleOrganism(0x0000000000021a28,:(function
 (o::Creature.Organism,)
        local var_243::Int8 = 83
        local var_242::Int16 = -28511
        function func_189(var_188::Int16=2883)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        Creature.stepDown(o)
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepDown(o)
        Creature.eatLeft(o,Int(var_117))
        o.mem[var_120] = var_120
        var_156 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_156
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatDown(o,Int(var_243))
        Creature.stepRight(o)
        Creature.eatUp(o,Int(var_117))
        var_120 = var_120 - var_120
        Creature.stepLeft(o)
        if var_120 > var_156
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepUp(o)
        end
        var_117 = var_117 + var_117
        Creature.eatRight(o,Int(var_117))
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatDown(o,Int(var_117))
        var_242 = var_120 / var_242
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = var_124 - var_124
        Creature.stepRight(o)
        var_242 = var_120 - var_120
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_123 = var_123 & var_123
        var_123 = var_123 | var_123
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_156)))
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepDown(o)
        var_120 = func_189(var_156)
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepLeft(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_120 = var_120 + var_120
        Creature.stepLeft(o)
        Creature.stepUp(o)
        var_120 = var_120 - var_120
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 | var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        if var_120 >= var_120
            local var_238::Int64 = -5358840696455759399
            var_238 = var_238 * var_238
        end
        var_120 = func_189(var_120)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        if var_123 <= var_123
            Creature.stepLeft(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepLeft(o)
        end
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],3,2407,10,101121,0x000000e4,Dict(-8030=>-4015,0=>0,-4015=>-4015,-3262=>-1631,23323=>23323,19308=>23323,-1631=>-4015,27608=>27608,1=>1),[1,1],88),RpcApi.SimpleOrganism(0x00000000000220ac,:(function (o::Creatu
re.Organism,)
        function func_279(var_278::ASCIIString="Hjxigv88")
            Creature.stepUp(o)
            Creature.stepDown(o)
            return var_278
        end
        function func_273(var_272::Int8=-40)
            for i::Int8 = 1:div(var_272,_LOOP_STEPS_DIVIDER)
            end
            return var_272
        end
        function func_264(var_263::Int8=-125)
            Creature.stepUp(o)
            Creature.stepRight(o)
            Creature.eatLeft(o,Int(var_263))
            Creature.stepRight(o)
            Creature.eatUp(o,Int(var_263))
            var_263 = var_263 / var_263
            return var_263
        end
        local var_243::Bool = false
        function func_232(var_230::Bool=false,var_231::Int16=-29748)
            local var_282::Bool = true
            return var_230
        end
        function func_189(var_188::Int16=2883)
            if var_188 === var_188
            end
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            Creature.stepUp(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_117 = var_117 + var_117
        o.mem[var_156] = var_156
        var_124 = var_124 + var_124
        Creature.stepUp(o)
        var_243 = Bool(abs(var_123 - var_123))
        Creature.eatUp(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        var_124 = var_124 * var_124
        var_243 = var_123 | var_123
        if var_123 != var_123
            Creature.stepUp(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
        end
        Creature.stepUp(o)
        Creature.stepRight(o)
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.stepUp(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_120 = var_156 / var_120
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        if var_123 !== var_123
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepDown(o)
        end
        Creature.eatRight(o,Int(var_117))
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        if var_124 <= var_124
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepRight(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
        end
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 * var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_243 = var_243 | var_243
        Creature.eatUp(o,Int(var_117))
        if var_123 <= var_123
            Creature.stepUp(o)
        end
        Creature.stepDown(o)
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],6,4602,53,100021,0x0000022b,Dict(0=>0,10070=>10070,-4015=>19308,23323=>23323,19308=>19308,-1631=>-1631,1=>1),[1816,9],80),RpcApi.SimpleOrganism(0x0000000000021e69,:(function (o::Creature.Organism,)
        function func_257(var_255::Int8=113,var_256::Int8=7)
            Creature.eatDown(o,Int(var_256))
            return var_255
        end
        local var_243::Int8 = 83
        local var_242::Int16 = -28511
        function func_189(var_188::Int16=2883)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            local var_250::Bool = true
            Creature.stepRight(o)
            Creature.stepUp(o)
            if var_250 === var_250
                Creature.stepUp(o)
            end
            Creature.stepDown(o)
            Creature.stepDown(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        Creature.stepDown(o)
        Creature.stepDown(o)
        var_120 = var_120 / var_120
        Creature.stepDown(o)
        Creature.eatLeft(o,Int(var_117))
        o.mem[var_120] = var_120
        var_156 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_156
            end
        Creature.eatUp(o,Int(var_117))
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_243))
        if var_120 > var_156
            Creature.stepRight(o)
        end
        var_117 = var_117 + var_117
        Creature.eatRight(o,Int(var_117))
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepRight(o)
        Creature.stepRight(o)
        var_124 = func_142(var_124)
        Creature.eatRight(o,Int(var_117))
        if var_124 <= var_124
            local var_230::Int16 = 27608
            var_230 = func_189(var_230)
            var_230 = var_230 / var_230
            Creature.stepDown(o)
            Creature.stepDown(o)
            var_230 = if haskey(o.mem,var_230)
                    o.mem[var_230]
                else
                    var_230
                end
            var_230 = func_189(var_230)
        end
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        o.mem[var_242] = var_120
        Creature.stepDown(o)
        var_123 = var_123 | var_123
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_156)))
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepDown(o)
        var_120 = func_189(var_156)
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepLeft(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_120 = var_120 + var_120
        o.mem[var_120] = var_156
        Creature.stepUp(o)
        var_120 = var_120 - var_120
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 | var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.eatDown(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        if var_120 >= var_120
            local var_238::Int64 = -5358840696455759399
        end
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        if var_123 <= var_123
            local var_244::ASCIIString = "G36GCJ4z"
            local var_258::ASCIIString = "zRLZiPFN"
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
        end
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],5,9344,51,99438,0x000000c2,Dict(2=>1,-3262=>-1631,-28511=>1,-1631=>-4015,0=>-4015,-8030=>-4015,-4015=>-4015,23323=>23323,19308=>23323,27608=>27608,1=>1),[1913,929],99),RpcApi.SimpleOrganism(0x00000000000220a
8,:(function (o::Creature.Organism,)
        function func_273(var_272::Int8=-40)
            Creature.stepRight(o)
            Creature.eatLeft(o,Int(var_272))
            var_272 = var_272 + var_272
            Creature.eatLeft(o,Int(var_272))
            Creature.eatDown(o,Int(var_272))
            return var_272
        end
        function func_264(var_263::Int8=-125)
            Creature.stepUp(o)
            var_263 = var_263 * var_263
            return var_263
        end
        local var_243::Bool = false
        function func_232(var_230::Bool=false,var_231::Int16=-29748)
            return var_230
        end
        function func_189(var_188::Int16=2883)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_117 = var_117 + var_117
        o.mem[var_156] = var_156
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        var_124 = var_124 / var_124
        var_124 = var_124 * var_124
        var_243 = var_123 | var_123
        if var_123 != var_123
            Creature.stepLeft(o)
            Creature.stepRight(o)
        end
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.eatLeft(o,Int(var_117))
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        Creature.stepUp(o)
        var_123 = var_123 | var_243
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        if var_123 !== var_123
            Creature.stepRight(o)
            Creature.stepDown(o)
        end
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        var_120 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_120
            end
        if var_124 <= var_124
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepRight(o)
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
        end
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        var_243 = var_243 | var_243
        Creature.eatUp(o,Int(var_117))
        if var_123 <= var_123
            Creature.stepUp(o)
        end
        Creature.stepDown(o)
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],4,9571,49,97013,0x00000226,Dict(0=>0,10070=>10070,-4015=>19308,23323=>23323,19308=>19308,-1631=>-1631,1=>1),[1907,10],74),RpcApi.SimpleOrganism(0x0000000000021f62,:(function (o::Creature.Organism,)
        local var_243::Int8 = 83
        local var_242::Int16 = -28511
        function func_189(var_188::Int16=2883)
            local var_256::Bool = false
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            Creature.stepDown(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        Creature.stepDown(o)
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepDown(o)
        Creature.eatLeft(o,Int(var_117))
        o.mem[var_120] = var_120
        var_156 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_156
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatDown(o,Int(var_243))
        Creature.stepRight(o)
        Creature.eatUp(o,Int(var_117))
        var_120 = var_120 - var_120
        Creature.stepLeft(o)
        if var_120 > var_156
            Creature.stepUp(o)
            Creature.stepUp(o)
        end
        var_117 = var_117 + var_117
        Creature.eatRight(o,Int(var_117))
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatDown(o,Int(var_117))
        var_242 = var_120 / var_242
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = var_124 - var_124
        Creature.stepRight(o)
        var_242 = var_120 - var_120
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_123 = var_123 & var_123
        var_123 = var_123 | var_123
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        var_120 = func_189(var_156)
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepLeft(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_120 = var_120 + var_120
        Creature.stepLeft(o)
        Creature.stepUp(o)
        var_120 = var_120 - var_120
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 | var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        if var_120 >= var_120
            local var_238::Int64 = -5358840696455759399
            var_238 = var_238 * var_238
        end
        var_120 = func_189(var_120)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        if var_123 <= var_123
            Creature.stepLeft(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepLeft(o)
        end
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],3,2407,10,96050,0x000000e7,Dict(-8030=>-4015,0=>0,-4015=>-4015,-3262=>-1631,23323=>23323,19308=>23323,-1631=>-4015,27608=>27608,1=>1),[1874,1],88),RpcApi.SimpleOrganism(0x0000000000021fc4,:(function (o::Crea
ture.Organism,)
        function func_273(var_272::Int8=-40)
            Creature.stepRight(o)
            Creature.eatUp(o,Int(var_272))
            return var_272
        end
        function func_264(var_263::Int8=-125)
            Creature.stepUp(o)
            return var_263
        end
        local var_243::Bool = false
        function func_232(var_230::Bool=false,var_231::Int16=-29748)
            return var_230
        end
        function func_189(var_188::Int16=2883)
            local var_278::Int16 = 12060
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_117 = var_117 + var_117
        o.mem[var_156] = var_156
        var_124 = var_124 + var_124
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        var_124 = var_124 / var_124
        var_124 = var_124 * var_124
        var_243 = var_123 | var_123
        if var_123 != var_123
            Creature.stepLeft(o)
        end
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepRight(o)
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        if var_123 !== var_123
            Creature.stepRight(o)
            Creature.stepDown(o)
        end
        Creature.eatRight(o,Int(var_117))
        var_120 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_120
            end
        if var_124 <= var_124
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepRight(o)
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
        end
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        var_243 = var_243 | var_243
        Creature.eatUp(o,Int(var_117))
        if var_123 <= var_123
            Creature.stepUp(o)
        end
        Creature.stepDown(o)
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],4,9571,27,94525,0x0000021e,Dict(0=>0,10070=>10070,-4015=>19308,23323=>23323,19308=>19308,-1631=>-1631,1=>1),[1907,2],68),RpcApi.SimpleOrganism(0x0000000000021f74,:(function (o::Creature.Organism,)
        function func_273(var_272::Int8=-40)
            Creature.stepRight(o)
            Creature.eatLeft(o,Int(var_272))
            var_272 = var_272 + var_272
            var_272 = var_272 * var_272
            var_272 = var_272 + var_272
            return var_272
        end
        function func_264(var_263::Int8=-125)
            return var_263
        end
        local var_243::Bool = false
        function func_232(var_230::Bool=false,var_231::Int16=-29748)
            Creature.stepLeft(o)
            return var_230
        end
        function func_189(var_188::Int16=2883)
            local var_278::Int16 = 18957
            Creature.stepDown(o)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            Creature.stepDown(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        local var_156::Int16 = 19308
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_117 = var_117 + var_117
        o.mem[var_156] = var_156
        var_124 = var_124 + var_124
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        Creature.eatUp(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        var_124 = var_124 / var_124
        var_124 = var_124 * var_124
        var_243 = var_123 | var_123
        if var_123 != var_123
            Creature.stepLeft(o)
        end
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepRight(o)
        var_156 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_156
            end
        Creature.stepUp(o)
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        if var_123 !== var_123
            Creature.stepDown(o)
        end
        Creature.eatRight(o,Int(var_117))
        var_120 = if haskey(o.mem,var_156)
                o.mem[var_156]
            else
                var_120
            end
        if var_124 <= var_124
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepRight(o)
            Creature.stepDown(o)
            Creature.stepDown(o)
            Creature.stepLeft(o)
        end
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatLeft(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatUp(o,Int(var_117))
        if var_123 <= var_123
            Creature.stepUp(o)
        end
        Creature.stepDown(o)
        Creature.stepLeft(o)
        return true
    end),[110,300,95,0,1,1,1],4,9571,49,81028,0x00000228,Dict(0=>0,10070=>10070,-4015=>19308,23323=>23323,19308=>19308,-1631=>-1631,1=>1),[1905,6],72),RpcApi.SimpleOrganism(0x0000000000021d87,:(function (o::Creature.Organism,)
        function func_257(var_255::Int8=113,var_256::Int8=7)
            local var_259::Int64 = 8472002642027034382
            local var_260::Int16 = -23495
            return var_255
        end
        local var_243::Int8 = 83
        local var_242::Int16 = -28511
        function func_189(var_188::Int16=2883)
            return var_188
        end
        function func_142(var_141::Int64=7558744495470982559)
            local var_250::Bool = true
            Creature.stepRight(o)
            Creature.stepUp(o)
            if var_250 === var_250
                Creature.stepRight(o)
                Creature.stepLeft(o)
            end
            var_141 = var_141 + var_141
            Creature.stepDown(o)
            return var_141
        end
        local var_124::Int64 = -1961703428302216313
        local var_123::Bool = true
        local var_120::Int16 = -4015
        local var_117::Int8 = 61
        Creature.stepDown(o)
        Creature.stepDown(o)
        var_120 = var_120 / var_120
        Creature.stepDown(o)
        Creature.eatLeft(o,Int(var_117))
        o.mem[var_120] = var_120
        var_156 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_156
            end
        Creature.eatUp(o,Int(var_117))
        Creature.stepRight(o)
        var_120 = var_120 - var_120
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_243))
        if var_120 > var_156
            Creature.stepRight(o)
        end
        var_117 = var_117 + var_117
        Creature.eatRight(o,Int(var_117))
        Creature.stepDown(o)
        Creature.stepRight(o)
        Creature.stepRight(o)
        Creature.eatDown(o,Int(var_117))
        Creature.eatRight(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepRight(o)
        Creature.stepRight(o)
        var_124 = func_142(var_124)
        Creature.eatRight(o,Int(var_117))
        if var_124 <= var_124
            local var_230::Int16 = 27608
            var_230 = func_189(var_230)
            var_230 = var_230 / var_230
            Creature.stepDown(o)
            Creature.stepDown(o)
            var_230 = if haskey(o.mem,var_230)
                    o.mem[var_230]
                else
                    var_230
                end
            var_230 = func_189(var_230)
        end
        Creature.stepRight(o)
        Creature.eatRight(o,Int(var_117))
        o.mem[var_242] = var_120
        Creature.stepDown(o)
        var_123 = var_123 | var_123
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_156)))
        Creature.stepRight(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepDown(o)
        var_120 = func_189(var_156)
        Creature.eatDown(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        Creature.stepLeft(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        var_120 = var_120 + var_120
        o.mem[var_120] = var_156
        Creature.stepUp(o)
        var_120 = var_120 - var_120
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_123 = var_123 | var_123
        Creature.eatLeft(o,Int(var_117))
        Creature.stepUp(o)
        Creature.stepDown(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.eatDown(o,Int(var_117))
        if var_120 >= var_120
            local var_238::Int64 = -5358840696455759399
        end
        Creature.stepUp(o)
        var_120 = if haskey(o.mem,var_120)
                o.mem[var_120]
            else
                var_120
            end
        if var_123 <= var_123
            local var_244::ASCIIString = "G36GCJ4z"
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
            Creature.stepLeft(o)
            Creature.stepUp(o)
        end
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        Creature.eatLeft(o,Int(var_117))
        Creature.stepLeft(o)
        var_124 = Int(Creature.getEnergy(o,Int(var_120),Int(var_120)))
        return true
    end),[110,300,95,0,1,1,1],5,9344,51,75588,0x000000c5,Dict(2=>1,-3262=>-1631,-28511=>0,-1631=>-4015,0=>-4015,-8030=>-4015,-4015=>-4015,23323=>23323,19308=>23323,27608=>27608,1=>1),[1913,891],102)]


module types

using DataFrames

# Abstract class for all resource modules
abstract type ResourceVariable end

abstract type ProgramVariable end

abstract type ProjectVariable <: ProgramVariable end

abstract type LaborVariable <: ResourceVariable end

abstract type FinanceVariable end

abstract type BudgetVariable <: FinanceVariable end

abstract type CostVariable <: FinanceVariable end

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

# Concrete Type

"""
    BudgetVariable

# Fields
`Hours::Real` : Stores Budget Hours for the Object
`QuotedDollars::Real` : Stores Budget Dollars for the Object
`Rate::Real` : Stores Budget Rate for the Object (dollars per hour)
`TravelDollars::Real` : Stores Budget Travel Dollars for the Object
"""
mutable struct Budget <: BudgetVariable
    Dept ::String
    Hours::Real                 # Budget in hours
    Rate::Real                  # Rate of dollars per hour
    TravelDollars::Real         # Budget in dollars for travel

    QuotedDollars_ENG::Real         # Budget in dollars used for quoted costs
    Var_ENG :: Real                  # Difference between quoted budget and Projected costs

    QuotedDollars_HDWR::Real         # Budget in dollars used for quoted costs
    Var_HDWR :: Real                  # Difference between quoted budget and Projected costs

    QuotedDollars_RESALE::Real         # Budget in dollars used for quoted costs
    Var_RESALE :: Real                  # Difference between quoted budget and Projected costs

    function Budget()           # Standard Constructor Function

        new("", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

    end

end

mutable struct Cost <: CostVariable

    Dept::String

    ActualHours::Real           # Cost in hours
    ActualTravel::Real          # Cost in dollars for travel

    Actual_ENG::Real                # Cost in dollars used for quoted costs
    Anticipated_ENG:: Real          # Cost in dollars for anticipated costs
    Projected_ENG:: Real            # Cost in dollars for projected costs

    Actual_HDWR::Real                # Cost in dollars used for quoted costs
    Anticipated_HDWR:: Real          # Cost in dollars for anticipated costs
    Projected_HDWR:: Real            # Cost in dollars for projected costs

    Actual_RESALE::Real                # Cost in dollars used for quoted costs
    Anticipated_RESALE:: Real          # Cost in dollars for anticipated costs
    Projected_RESALE:: Real            # Cost in dollars for projected costs

    function Cost() # Standard Constructor Function

        new("", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

    end

end

mutable struct Project <: ProjectVariable
    Number :: String              # Project Number
    Customer:: String           # Customer Name

    Budget::Array{Budget, 1}              # Budget for the Project
    Cost:: Array{Cost, 1}                  # Cost for the Project

    FwdHoursForecast::DataFrame         # Array holding Hours Forecasted Fwd monthly(future). Element [1] is the first month forecasted.
    RevHoursForecast::DataFrame          # Array holding Hours Forecasted Rev monthly (in past). Element [1] is the first month forecasted.

    function Project(project::String, customer::String) # Standard Constructor Function

        new(project, customer, Array{Budget, 1}(), Array{Cost, 1}(), DataFrame(Employee=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
        Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
        Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
        Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
        Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}()), DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
        Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
        Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
        Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
        Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}()));

    end

end

"""
    Program <: ProgramVariable

    # Fields
    `Projects::Array{Project, 1}` : Stores the Projects for the Program
    `Name::String` : Stores the Name of the Program

"""
mutable struct Program <: ProgramVariable
    Name :: String              # Program Name
    Projects :: Array{Project, 1} # Projects in the Program
    function Program() # Standard Constructor Function

        new("", Array{Project, 1}());

    end

end

"""
    DisciplineLabor

# Fields
- `Hours::Real <:Budget` : Stores Budget Hours for the Object
- `Dollars::Real <: Budget` : Stores Budget Dollars for the Object
- `Rate::Real <: Budget` : Stores Budget Rate for the Object (dollars per hour)
- `TravelDollars::Real <: Budget` : Stores Budget Travel Dollars for the Object
- `ActualHours::Float64 `: stores actual incurred hours for resource
- `ActualHours::Float64 `: stores actual incurred hours for resource
- `ActualIncurredCost::Float64`: stores actual incurred Costs
- `FwdHoursAvailable::Array`: stores monthly Hours Available from Present going forward
- `FwdHoursForecast::Vector{Vector{Int64}} `: stores Forecast monthly Hours from Present month going forward
- `RevHoursForecast::Array`: stores Forecast monthly Hours from Present month going backwards (reverse)
- `RevHoursAvailable::Array`: stores Forecast monthly Hours from Present month going backwards (reverse)
- `FwdCostsForecast::Array`: stores forecasted monthly Labor cost  from Present going forward
- `RevCostsForecast::Array`: stores forecasted monthly Labor cost  from Present going backwards (reverse)
- `RevActualCostHours::Array `: stores monthly actual labor costs  from Present going backwards (reverse)
- `Dept::String`: stores Dept name
- `Name::String`: stores name of the resource
- `Projects::Array`: stores projects for the resource
"""
mutable struct  DisciplineLabor <:LaborVariable

    FwdHoursAvailable::Array        # Fwd Hours Available on a given month.
    FwdHoursForecast::DataFrame         # Array holding Hours Forecasted Fwd monthly(future). Element [1] is the first month forecasted.
    RevHoursForecast::DataFrame           # Array holding Hours Forecasted Rev monthly (in past). Element [1] is the first month forecasted.
    RevHoursAvailable::Array        # Array holding Hours Available Rev monthly (in past). Element [1] is the first month forecasted.

    FwdCostsForecast::Array          # Array holding costs Forecasted Fwd monthly (future)
    RevCostsForecast::Array         # Array holding costs Forecasted Rev  monthly (past)

    RevActualHours::DataFrame        # Array holding Hours incurred Rev monthly (in past). Element [1] is the first month Actual Hours.
    RevActualCostHours::Array     # Array holding Hours in Dollar Amounts incurred Rev monthly (in past)

    Dept::String                        # Department for Discipline

    Name::String                    # name of resource

    Projects::Array                 # Project for Discipline

    """
        DisciplineLabor()

    Standard Constructor for Discipline Labor.
    ```
    new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Array{Float64, 1}(undef, 12), Vector{Vector{Int64}}(), Array{Float64, 1}(undef, 12), Array{Float64, 1}(undef, 12) ,
            Array{Float64, 1}(undef, 12), Array{Float64, 1}(undef, 12), "430300")
    ```

        DisciplineLabor(Dept, N)
    Detailed Constructor for Discipline Labor. Specifies Dept and number of months to hold forecast and actuals (N)

    # Examples
    ```julia
    CE = DisciplineLabor()

    ```
    """
    function DisciplineLabor()      # standard constructor supplied, all zerors, with 24 months array as default

        new(Array{Float64, 1}(), DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
        Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
        Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
        Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
        Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
        ,
         DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
         Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
            Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
            Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
            Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
            Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
         , Array{Float64, 2}(undef, 24, 24),  Array{Float64, 2}(undef, 24, 24), Array{Float64, 2}(undef, 24,24) ,
        DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
           Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
           Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
           Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
           Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
           , Array{Float64, 2}(undef, 24, 24), "430300","", Array{String, 1}())

    end

    """
        DisciplineLabor(Dept,Name)

    Standard Constructor for Discipline Labor'

        # Parameters
        Dept::String
        Name::String
        N::Int

    ```
    new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Array{Float64, 1}(undef, 12), Vector{Vector{Int64}}(), Array{Float64, 1}(undef, 12), Array{Float64, 1}(undef, 12) ,
            Array{Float64, 1}(undef, 12), Array{Float64, 1}(undef, 12), "430300")
    ```

        DisciplineLabor(Dept, N)
    Detailed Constructor for Discipline Labor. Specifies Dept and number of months to hold forecast and actuals (N)

    # Examples
    ```julia
    CE = DisciplineLabor()

    ```
    """
    function DisciplineLabor(Dept::String, Name::String)      # standard constructor supplied, all zerors, with 24 months array as default

        new(Array{Float64, 1}(), DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
        Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
        Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
        Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
        Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
        ,
         DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
         Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
            Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
            Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
            Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
            Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
         , Array{Float64, 2}(undef, 24, 24),  Array{Float64, 2}(undef, 24, 24), Array{Float64, 2}(undef, 24,24) ,
        DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
           Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
           Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
           Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
           Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
           , Array{Float64, 2}(undef, 24, 24), Dept,Name, Array{String, 1}())

    end

end

"""
    TeamLabor()

Generates a TeamLabor object.

# Arguments
- `Team::Array`: Array of DisciplineLabor objects
- `Name::String`: Name of the Team
- `Dept::String`: Department of the Team
- `Hours::Real <:Budget` : Stores Budget Hours for the Object
- `Dollars::Real <: Budget` : Stores Budget Dollars for the Object
- `Rate::Real <: Budget` : Stores Budget Rate for the Object (dollars per hour)
- `TravelDollars::Real <: Budget` : Stores Budget Travel Dollars for the Object
- `ActualHours::Float64`: Total Incurred Hours
- `ActualIncurredCost::Float64`: Total Incurred Cost
- `Projects::Array` : Array of Projects for the Team
- `FwdHoursAvailable::Array`: stores Fwd Hours Available on a given month.
- `FwdHoursForecast::Vector{Vector{Int64}} `: stores Forecast monthly Hours from Present month going forward
- `RevHoursForecast::Array`: stores Forecast monthly Hours from Present month going backwards (reverse)
- `RevHoursAvailable::Array`: stores Available monthly Hours from Present month going backwards (reverse)
- `FwdCostsForecast::Array`: stores Forecasted monthly Labor cost  from Present going forward
- `RevCostsForecast::Array`: stores Forecasted monthly Labor cost  from Present going backwards (reverse)
- `RevActualHours::Array`: stores Actual monthly Hours from Present month going backwards (reverse)
- `RevActualCostHours::Array`: stores Actual monthly Labor cost  from Present going backwards (reverse)

# Returns
- `TeamLabor`: TeamLabor object
"""
mutable struct  TeamLabor <:LaborVariable

    Team::Array{DisciplineLabor, 1}
    Name::String # name of team
    Dept::String                        # Department for Discipline

    Projects::Array # project name
    FwdHoursAvailable::Array        # Fwd Hours Available on a given month.
    FwdHoursForecast::DataFrame        # DataFrame holding Hours Forecasted Fwd monthly(future). Element [1] is the first month forecasted.
    RevHoursForecast::DataFrame        # Array holding Hours Forecasted Rev monthly (in past). Element [1] is the first month forecasted.
    RevHoursAvailable::Array        # Array holding Hours Available Rev monthly (in past). Element [1] is the first month forecasted.
    FwdCostsForecast::Array          # Array holding costs Forecasted Fwd monthly (future)
    RevCostsForecast::Array         # Array holding costs Forecasted Rev  monthly (past)
    RevActualHours::DataFrame       # Array holding Hours incurred Rev monthly (in past). Element [1] is the first month Actual Hours.
    RevActualCostHours::Array     # Array holding Hours in Dollar Amounts incurred Rev monthly (in past)

    function TeamLabor()
        new(Array{DisciplineLabor, 1}(),  "", "", Array{String, 1}(),
        Array{Float64, 1}(), DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
        Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
        Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
        Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
        Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
        ,
         DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
         Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
            Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
            Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
            Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
            Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
         , Array{Float64, 2}(undef, 24, 24),  Array{Float64, 2}(undef, 24, 24), Array{Float64, 2}(undef, 24,24) ,
        DataFrame(Project=  Array{String,1}(), Month1 = Array{Int64, 1}(), Month2 = Array{Int64, 1}(), Month3 = Array{Int64, 1}(),
        Month4 = Array{Int64, 1}(), Month5 = Array{Int64, 1}(), Month6 = Array{Int64, 1}(), Month7 = Array{Int64, 1}(), Month8 = Array{Int64, 1}(),
           Month9 = Array{Int64, 1}(), Month10 = Array{Int64, 1}(), Month11 = Array{Int64, 1}(), Month12 = Array{Int64, 1}(),
           Month13 = Array{Int64, 1}(), Month14 = Array{Int64, 1}(), Month15 = Array{Int64, 1}(), Month16 = Array{Int64, 1}(), Month17 = Array{Int64, 1}(),
           Month18 = Array{Int64, 1}(), Month19 = Array{Int64, 1}(), Month20 = Array{Int64, 1}(), Month21 = Array{Int64, 1}(), Month22 = Array{Int64, 1}(),
           Month23 = Array{Int64, 1}(), Month24 = Array{Int64, 1}())
           , Array{Float64, 2}(undef, 24, 24))
    end

end

# Defining how the types show up on Standard IO:
Base.show(io::IO, D::DisciplineLabor) = print(io, "(👨, " , D.Name, ", ", D.Dept, ", Hello: Ready to help!")
Base.show(io::IO, D::TeamLabor) = print(io, "(👨 , 👩  " , D.Name, ", ", D.Dept, ", Alpha Team ready to help!")
Base.show(io::IO, p::Project) = print(io, "(💼, " , p.Number, ", ", p.Customer, ", Make every Project a Success 💪🏻 before it even begins!")
Base.show(io::IO, p::Program) = print(io, "(💼, 💼, " , p.Name,  ", Program launched! 🚀🚀🚀")

end


module SMRTypes

import Base: show

export cSMRWMrkChannel, SMRWMrkChannel, cSMRContChannel, SMRContChannel,
       cSMREventChannel, SMREventChannel, cSMRMarkerChannel, SMRMarkerChannel,
       cSMRChannelInfo, cSMRChannelInfoArray, SMRChannelInfo, show,
       channel_string

const MARKER_SIZE = UInt8(4)

abstract type SMRCType end
abstract type SMRType end

# =========================================================================== #
struct cSMRWMrkChannel <: SMRCType
    length::UInt64
    npt::UInt64
    timestamps::Ptr{Float64}
    markers::Ptr{UInt8}
    wavemarks::Ptr{Int16}
end

mutable struct SMRWMrkChannel <: SMRType
    timestamps::Vector{Float64}
    markers::Matrix{UInt8}
    wavemarks::Matrix{Int16}

    function SMRWMrkChannel(x::cSMRWMrkChannel)
        self = new()

        self.timestamps = Vector{Float64}(undef, x.length)
        copyto!(self.timestamps, unsafe_wrap(Vector{Float64}, x.timestamps, x.length, own=false))

        self.markers = Matrix{UInt8}(undef, MARKER_SIZE, x.length)
        copyto!(self.markers, unsafe_wrap(Vector{UInt8}, x.markers, x.length * MARKER_SIZE, own=false))

        self.wavemarks = Matrix{Int16}(undef, x.npt, x.length)
        copyto!(self.wavemarks, unsafe_wrap(Vector{Int16}, x.wavemarks, x.length * x.npt, own=false))

        return self
    end
end
# =========================================================================== #
struct cSMRContChannel <: SMRCType
    length::UInt64
    sampling_rate::Float64
    data::Ptr{Int16}
end

mutable struct SMRContChannel <: SMRType
    data::Vector{Int16}
    sampling_rate::Float64

    function SMRContChannel(x::cSMRContChannel)
        self = new()

        self.data = Vector{Int16}(undef, x.length)
        copyto!(self.data, unsafe_wrap(Vector{Int16}, x.data, x.length, own=false))

        self.sampling_rate = x.sampling_rate

        return self
    end
end
# =========================================================================== #
struct cSMREventChannel <: SMRCType
    length::UInt64
    data::Ptr{Float64}
end

mutable struct SMREventChannel <: SMRType
    data::Vector{Float64}

    function SMREventChannel(x::cSMREventChannel)
        self = new()

        self.data = Vector{Float64}(undef, x.length)
        copyto!(self.data, unsafe_wrap(Vector{Float64}, x.data, x.length, own=false))

        return self
    end
end
# =========================================================================== #
struct cSMRMarkerChannel <: SMRCType
    length::UInt64
    npt::UInt64
    timestamps::Ptr{Float64}
    markers::Ptr{UInt8}
    text::Ptr{UInt8}
end

mutable struct SMRMarkerChannel <: SMRType
    timestamps::Vector{Float64}
    markers::Matrix{UInt8}
    text::Vector{String}

    function SMRMarkerChannel(mrk::cSMRMarkerChannel)
        self = new()

        self.timestamps = Vector{Float64}(undef, mrk.length)
        copyto!(self.timestamps, unsafe_wrap(Vector{Float64}, mrk.timestamps, mrk.length, own=false))

        self.markers = Matrix{UInt8}(undef, MARKER_SIZE, mrk.length)
        copyto!(self.markers, unsafe_wrap(Vector{UInt8}, mrk.markers, mrk.length * MARKER_SIZE, own=false))

        self.text = Vector{String}(undef, mrk.length)

        ary = unsafe_wrap(Vector{UInt8}, mrk.text, mrk.length * mrk.npt, own=false)
        for k = 1:mrk.length
            isrt = ((k-1)*mrk.npt)+1
            iend = k*mrk.npt
            if all(x->x=='\0', ary[isrt:iend])
                self.text[k] = ""
            else
                self.text[k] = strip(join(map(Char, ary[isrt:iend])), '\0')
            end
        end

        return self
    end
end
# =========================================================================== #
struct cSMRChannelInfo <: SMRCType
    title::Cstring
    index::Int32
    kind::UInt8
    phy_chan::Int16
end

struct cSMRChannelInfoArray <: SMRCType
    length::UInt32
    ifo::Ptr{Ptr{cSMRChannelInfo}}
end

mutable struct SMRChannelInfo <: SMRType
    title::String
    index::Int
    kind::Int
    phy_chan::Int

    function SMRChannelInfo(x::cSMRChannelInfo)
        self = new()
        self.title = unsafe_string(x.title)
        self.index = Int(x.index)
        self.kind = Int(x.kind)
        self.phy_chan = Int(x.phy_chan)
        return self
    end
end
# =========================================================================== #
function show(io::IO, ifo::SMRChannelInfo)
    str = "\"" * ifo.title *
          "\": " * channel_string(ifo.kind) *
          ", " * string(ifo.index)
    print(io, str)
end
# =========================================================================== #
function channel_string(x::T) where T<:Integer
    sx = string(x)
    if x == 1
        str = "continuous"
    elseif x == 2
        str = "event"
    elseif x == 3
        str = "event"
    elseif x == 4
        str = "event"
    elseif x == 5
        str = "marker"
    elseif x == 6
        str = "adc_marker"
    elseif x == 7
        str = "real_marker"
    elseif x == 8
        str = "text_marker"
    elseif x == 9
        str = "real_wave"
    else
        @warn(string(x) * " is not a valid channel type")
        str = "INVALID"
    end
    return str
end
# =========================================================================== #
end #END MODULE


@testset "Test Simulation Models" begin
    models = SimulationModels(
        [
            DecisionModel(
                MockOperationProblem;
                horizon=48,
                interval=Hour(24),
                steps=2,
                name="DAUC",
            ),
            DecisionModel(
                MockOperationProblem;
                horizon=24,
                interval=Hour(1),
                steps=2 * 24,
                name="HAUC",
            ),
            DecisionModel(
                MockOperationProblem;
                horizon=12,
                interval=Minute(5),
                steps=2 * 24 * 12,
                name="ED",
            ),
        ],
        EmulationModel(MockEmulationProblem; resolution=Minute(1), name="AGC"),
    )

    @test length(PSI.get_decision_models(models)) == 3
    @test PSI.get_emulation_model(models) !== nothing

    @test_throws ErrorException SimulationModels(
        [
            DecisionModel(
                MockOperationProblem;
                horizon=48,
                interval=Hour(24),
                steps=2,
                name="DAUC",
            ),
            DecisionModel(
                MockOperationProblem;
                horizon=24,
                interval=Hour(1),
                steps=2 * 24,
                name="DAUC",
            ),
            DecisionModel(
                MockOperationProblem;
                horizon=12,
                interval=Minute(5),
                steps=2 * 24 * 12,
                name="ED",
            ),
        ],
        EmulationModel(MockEmulationProblem; resolution=Minute(1), name="AGC"),
    )
end


"""
Analytical reports generated during training, for debugging and
hyperparameters tuning.
"""
module Report

# Some imports for docstrings cross-references
import ..MCTS, ..Network, ..MemAnalysisParams

"""
    Report.Loss

Decomposition of the loss in a sum of terms (all have type `Float32`).

- `L` is the total loss: `L == Lp + Lv + Lreg + Linv`
- `Lp` is the policy cross-entropy loss term
- `Lv` is the average value mean square error
- `Lreg` is the L2 regularization loss term
- `Linv` is the loss term penalizing the average weight put by the
  network on invalid actions
"""
struct Loss
  L :: Float32
  Lp :: Float32
  Lv :: Float32
  Lreg :: Float32
  Linv :: Float32
end

"""
    Report.LearningStatus

Statistics about the performance of the neural network on a subset of the
memory buffer.

- `loss`: detailed loss on the samples, as an object of type
    [`Report.Loss`](@ref)
- `Hp`: average entropy of the ``π`` component of samples (MCTS policy);
    this quantity is independent of the network and therefore constant
    during a learning iteration
- `Hpnet`: average entropy of the network's prescribed policy on the samples
"""
struct LearningStatus
  loss :: Loss
  Hp :: Float32 # property of the memory, constant during a learning iteration
  Hpnet :: Float32
end

"""
    Report.Checkpoint

Report generated after a checkpoint evaluation.

- `batch_id`: number of batches after which the checkpoint was computed
- `status_after`: learning status at the checkpoint, as an object of type
   [`Report.LearningStatus`](@ref)
- `reward`: average reward collected by the contender network
- `redundancy`: ratio of duplicate positions encountered during the evaluation,
   not counting the initial position. If this number is too high, you may
   want to increase the move selection temperature.
- `nn_replaced`: true if the current best neural network was updated after
   the checkpoint
"""
struct Checkpoint
  batch_id :: Int
  status_after :: LearningStatus
  reward :: Float64
  redundancy :: Float64
  nn_replaced :: Bool
end

"""
    Report.Learning

Report generated at the end of the learning phase of an iteration.

- `time_convert`, `time_loss`, `time_train` and `time_eval` are the
    amounts of time (in seconds) spent at converting the samples,
    computing losses, performing gradient updates and evaluating checkpoints
    respectively
- `initial_status`: status before the learning phase, as an object of type
    [`Report.LearningStatus`](@ref)
- `losses`: loss value on each minibatch
- `checkpoints`: vector of [`Report.Checkpoint`](@ref) reports
- `nn_replaced`: true if the best neural network was replaced
"""
struct Learning
  time_convert :: Float64
  time_loss :: Float64
  time_train :: Float64
  time_eval :: Float64
  initial_status :: LearningStatus
  losses :: Vector{Float32}
  checkpoints :: Vector{Checkpoint}
  nn_replaced :: Bool
end

"""
    Report.Samples

Statistics about a set of samples, as collected during memory analysis.

- `num_samples`: total number of samples
- `num_boards`: number of distinct board positions
- `Wtot`: total weight of the samples
- `status`: [`Report.LearningStatus`](@ref) statistics of the current network
    on the samples
"""
struct Samples
  num_samples :: Int
  num_boards :: Int
  Wtot :: Float32
  status :: LearningStatus
end

"""
    Report.StageSamples

Statistics for the samples corresponding to a particular game stage,
as collected during memory analysis.

The samples whose statistics are collected in the
[`samples_stats`](@ref Report.Samples) field correspond to historical positions
where the number of remaining moves until the end of the game was in the range
defined by the `min_remaining_length` and `max_remaining_length` fields.
"""
struct StageSamples
  min_remaining_length :: Int
  max_remaining_length :: Int
  samples_stats :: Samples
end

"""
    Report.Memory

Report generated by the memory analysis phase of an iteration. It features
samples statistics for
  - the whole memory buffer (`all_samples :: Report.Samples`)
  - the samples collected during the last self-play iteration
     (`latest_batch :: Report.Samples`)
  - the subsets of the memory buffer corresponding to different game stages:
     (`per_game_stage :: Vector{Report.StageSamples}`)

See [`MemAnalysisParams`](@ref).
"""
struct Memory
  latest_batch :: Samples
  all_samples :: Samples
  per_game_stage :: Vector{StageSamples}
end

"""
    Report.SelfPlay

Report generated after the self-play phase of an iteration.

- `inference_time_ratio`: see [`MCTS.inference_time_ratio`](@ref)
- `samples_gen_speed`: average number of samples generated per second
- `average_exploration_depth`: see [`MCTS.average_exploration_depth`](@ref)
- `mcts_memory_footprint`: estimation of the maximal memory footprint of the
    MCTS tree during self-play, as computed by
    [`MCTS.approximate_memory_footprint`](@ref)
- `memory_size`: number of samples in the memory buffer at the end of the
    self-play phase
- `memory_num_distinct_boards`: number of distinct board positions in the
    memory buffer at the end of the self-play phase
"""
struct SelfPlay
  inference_time_ratio :: Float64
  samples_gen_speed :: Float64
  average_exploration_depth :: Float64
  mcts_memory_footprint :: Int
  memory_size :: Int
  memory_num_distinct_boards :: Int
end

"""
    Report.Perfs

Performances report for a subroutine.
- `time`: total time spent, in seconds
- `allocated`: amount of memory allocated, in bytes
- `gc_time`: total amount of time spent in the garbage collector
"""
struct Perfs
  time :: Float64
  allocated :: Int64
  gc_time :: Float64
end

"""
    Report.Iteration

Report generated after each training iteration.
- `perfs_self_play`, `perfs_memory_analysis` and `perfs_learning` are
    performance reports for the different phases of the iteration, as
    objects of type [`Report.Perfs`](@ref)
- `self_play`, `memory`, `learning` have types [`Report.SelfPlay`](@ref),
    [`Report.SelfPlay`](@ref) and [`Report.Learning`](@ref) respectively
"""
struct Iteration
  perfs_self_play :: Perfs
  perfs_memory_analysis :: Perfs
  perfs_learning :: Perfs
  self_play :: SelfPlay
  memory :: Union{Memory, Nothing}
  learning :: Learning
end

"""
    Report.Initial

Report summarizing the configuration of an agent before training starts.
- `num_network_parameters`: see [`Network.num_parameters`](@ref)
- `num_network_regularized_parameters`:
    see [`Network.num_regularized_parameters`](@ref)
- `mcts_footprint_per_node`: see [`MCTS.memory_footprint_per_node`](@ref)
"""
struct Initial
  num_network_parameters :: Int
  num_network_regularized_parameters :: Int
  mcts_footprint_per_node :: Int
end

#####
##### Profiling utilities
#####

macro timed(e)
  quote
    local v, t, mem, gct = Base.@timed $(esc(e))
    v, Perfs(t, mem, gct)
  end
end

#####
##### Printing and plotting
#####

using Formatting
using ..Log

const NUM_COL = Log.ColType(7, x -> fmt(".4f", x))
const BIGINT_COL = Log.ColType(10, n -> format(ceil(Int, n), commas=true))

const LEARNING_STATUS_TABLE = Log.Table([
  ("Loss",   NUM_COL,     s -> s.loss.L),
  ("Lv",     NUM_COL,     s -> s.loss.Lv),
  ("Lp",     NUM_COL,     s -> s.loss.Lp),
  ("Lreg",   NUM_COL,     s -> s.loss.Lreg),
  ("Linv",   NUM_COL,     s -> s.loss.Linv),
  ("Hp",     NUM_COL,     s -> s.Hp),
  ("Hpnet",  NUM_COL,     s -> s.Hpnet)])

const SAMPLES_STATS_TABLE = Log.Table([
  ("Loss",   NUM_COL,     s -> s.status.loss.L),
  ("Lv",     NUM_COL,     s -> s.status.loss.Lv),
  ("Lp",     NUM_COL,     s -> s.status.loss.Lp),
  ("Lreg",   NUM_COL,     s -> s.status.loss.Lreg),
  ("Linv",   NUM_COL,     s -> s.status.loss.Linv),
  ("Hpnet",  NUM_COL,     s -> s.status.Hpnet),
  ("Hp",     NUM_COL,     s -> s.status.Hp),
  ("Wtot",   BIGINT_COL,  s -> s.Wtot),
  ("Nb",     BIGINT_COL,  s -> s.num_boards),
  ("Ns",     BIGINT_COL,  s -> s.num_samples)])

function print(logger::Logger, status::Report.LearningStatus; kw...)
  Log.table_row(logger, LEARNING_STATUS_TABLE, status; kw...)
end

function print(logger::Logger, stats::Report.Memory)
  content, styles, comments = [], [], []
  # All samples
  push!(content, stats.all_samples)
  push!(styles, Log.BOLD)
  push!(comments, ["all samples"])
  # Latest batch
  push!(content, stats.latest_batch)
  push!(styles, Log.BOLD)
  push!(comments, ["latest batch"])
  # Per game stage
  for stage in stats.per_game_stage
    minrem = stage.min_remaining_length
    maxrem = stage.max_remaining_length
    push!(content, stage.samples_stats)
    push!(styles, Log.NO_STYLE)
    push!(comments, ["$minrem to $maxrem turns left"])
  end
  Log.table(
    logger, SAMPLES_STATS_TABLE, content, styles=styles, comments=comments)
end

function print(logger::Logger, report::SelfPlay)
  t = round(Int, 100 * report.inference_time_ratio)
  Log.print(logger, "Time spent on inference: $(t)%")
  sspeed = format(round(Int, report.samples_gen_speed), commas=true)
  Log.print(logger, "Generating $(sspeed) samples per second on average")
  avgdepth = fmt(".1f", report.average_exploration_depth)
  Log.print(logger, "Average exploration depth: $avgdepth")
  memf = format(report.mcts_memory_footprint, autoscale=:metric, precision=2)
  Log.print(logger, "MCTS memory footprint: $(memf)B")
  mems = format(report.memory_size, commas=true)
  memd = format(report.memory_num_distinct_boards, commas=true)
  Log.print(logger, "Experience buffer size: $(mems) ($(memd) distinct boards)")
end

function print(logger::Logger, report::Initial)
  nnparams = format(report.num_network_parameters, commas=true)
  Log.print(logger, "Number of network parameters: $nnparams")
  nnregparams = format(report.num_network_regularized_parameters, commas=true)
  Log.print(logger, "Number of regularized network parameters: $nnregparams")
  mfpn = report.mcts_footprint_per_node
  Log.print(logger, "Memory footprint per MCTS node: $(mfpn) bytes")
end

end