using Test
using Pkg.Artifacts
using NCDatasets

using RRTMGP
using RRTMGP.Device: array_type, array_device
using RRTMGP.Vmrs
using RRTMGP.LookUpTables
using RRTMGP.AtmosphericStates
using RRTMGP.Optics
using RRTMGP.Sources
using RRTMGP.BCs
using RRTMGP.Fluxes
using RRTMGP.AngularDiscretizations
using RRTMGP.RTE
using RRTMGP.RTESolver

using CLIMAParameters
struct EarthParameterSet <: AbstractEarthParameterSet end
const param_set = EarthParameterSet()
# overriding some parameters to match with RRTMGP FORTRAN code
CLIMAParameters.Planet.grav(::EarthParameterSet) = 9.80665
CLIMAParameters.Planet.molmass_dryair(::EarthParameterSet) = 0.028964
CLIMAParameters.Planet.molmass_water(::EarthParameterSet) = 0.018016

include("reference_files.jl")
include("read_all_sky.jl")

DA = array_type()

function all_sky(
    ::Type{OPC},
    ::Type{FT},
    ::Type{I},
    ::Type{DA};
    use_lut::Bool = true,
) where {FT<:AbstractFloat,I<:Int,DA,OPC}
    opc = Symbol(OPC)
    FTA1D = DA{FT,1}
    FTA2D = DA{FT,2}
    max_threads = Int(256)
    n_gauss_angles = 1
    ncol = 128 # repeats col#1 128 time per RRTMGP example

    lw_file = get_ref_filename(:lookup_tables, :clearsky, λ = :lw)             # lw lookup tables for gas optics
    lw_cld_file = get_ref_filename(:lookup_tables, :cloudysky, λ = :lw)        # lw cloud lookup tables
    sw_file = get_ref_filename(:lookup_tables, :clearsky, λ = :sw)             # sw lookup tables for gas optics
    sw_cld_file = get_ref_filename(:lookup_tables, :cloudysky, λ = :sw)        # lw cloud lookup tables

    input_file = get_ref_filename(:atmos_state, :cloudysky)                    # all-sky atmos state
    if use_lut
        flux_file = get_ref_filename(:comparison, :cloudysky, lut_pade = :lut) # flux files for comparison (LUT)
    else
        flux_file = get_ref_filename(:comparison, :cloudysky, lut_pade = :pade) # flux files for comparison (pade)
    end

    #reading longwave gas optics lookup data
    ds_lw = Dataset(lw_file, "r")
    lookup_lw, idx_gases = LookUpLW(ds_lw, I, FT, DA)
    close(ds_lw)
    # reading longwave cloud lookup data
    ds_lw_cld = Dataset(lw_cld_file, "r")
    lookup_lw_cld = LookUpCld(ds_lw_cld, I, FT, DA, use_lut)
    close(ds_lw_cld)
    #reading shortwave gas optics lookup data
    ds_sw = Dataset(sw_file, "r")
    lookup_sw, idx_gases = LookUpSW(ds_sw, I, FT, DA)
    close(ds_sw)
    # reading longwave cloud lookup data
    ds_sw_cld = Dataset(sw_cld_file, "r")
    lookup_sw_cld = LookUpCld(ds_sw_cld, I, FT, DA, use_lut)
    close(ds_sw_cld)
    # reading input file
    ds_in = Dataset(input_file, "r")

    as, sfc_emis, sfc_alb_direct, sfc_alb_diffuse, zenith, toa_flux, bot_at_1 =
        setup_allsky_as(
            ds_in,
            idx_gases,
            lookup_lw,
            lookup_sw,
            lookup_lw_cld,
            lookup_sw_cld,
            use_lut,
            ncol,
            FT,
            DA,
            max_threads,
        )
    close(ds_in)
    ncol, nlay = as.ncol, as.nlay
    nlev = nlay + 1
    #---reading comparison files -----------------------------------
    if bot_at_1
        flip_ind = 1:nlev
    else
        flip_ind = nlev:-1:1
    end

    # Setting up longwave problem---------------------------------------
    ngpt_lw = lookup_lw.n_gpt
    op = OPC(FT, ncol, nlay, DA) # allocating optical properties object
    src_lw = source_func_longwave(FT, ncol, nlay, opc, DA)   # allocating longwave source function object
    bcs_lw = LwBCs{FT,typeof(sfc_emis),Nothing}(sfc_emis, nothing)    # setting up boundary conditions
    fluxb_lw = FluxLW(ncol, nlay, FT, DA)                             # flux storage for bandwise calculations
    flux_lw = FluxLW(ncol, nlay, FT, DA)                              # longwave fluxes

    # Setting up shortwave problem---------------------------------------
    ngpt_sw = lookup_sw.n_gpt
    src_sw = source_func_shortwave(FT, ncol, nlay, opc, DA)        # allocating longwave source function object

    # setting up boundary conditions
    inc_flux_diffuse = nothing

    bcs_sw = SwBCs(
        zenith,
        toa_flux,
        sfc_alb_direct,
        inc_flux_diffuse,
        sfc_alb_diffuse,
    )

    fluxb_sw = FluxSW(ncol, nlay, FT, DA) # flux storage for bandwise calculations
    flux_sw = FluxSW(ncol, nlay, FT, DA)  # shortwave fluxes for band calculations
    #-------------------------------------------------------------------
    # initializing RTE solver
    slv = Solver(
        as,
        op,
        src_lw,
        src_sw,
        bcs_lw,
        bcs_sw,
        fluxb_lw,
        fluxb_sw,
        flux_lw,
        flux_sw,
    )
    #------solvers
    solve_lw!(slv, max_threads, lookup_lw, lookup_lw_cld)
    solve_sw!(slv, max_threads, lookup_sw, lookup_sw_cld)

    #for i = 1:10
    #    @time solve_lw!(slv, max_threads, lookup_lw, lookup_lw_cld)
    #    @time solve_sw!(slv, max_threads, lookup_sw, lookup_sw_cld)
    #    println("------------------------------------------------")
    #end
    #-------------
    # comparison
    method = use_lut ? "Lookup Table Interpolation method" : "PADE method"
    ds_comp = Dataset(flux_file, "r")
    comp_flux_up_lw =
        Array(transpose(ds_comp["lw_flux_up"][:][1:ncol, flip_ind]))
    comp_flux_dn_lw =
        Array(transpose(ds_comp["lw_flux_dn"][:][1:ncol, flip_ind]))
    comp_flux_up_sw =
        Array(transpose(ds_comp["sw_flux_up"][:][1:ncol, flip_ind]))
    comp_flux_dn_sw =
        Array(transpose(ds_comp["sw_flux_dn"][:][1:ncol, flip_ind]))
    close(ds_comp)

    flux_up_lw = Array(slv.flux_lw.flux_up)
    flux_dn_lw = Array(slv.flux_lw.flux_dn)

    max_err_flux_up_lw = maximum(abs.(flux_up_lw .- comp_flux_up_lw))
    max_err_flux_dn_lw = maximum(abs.(flux_dn_lw .- comp_flux_dn_lw))

    println("=======================================")
    println("Cloudy-sky longwave test - $opc")
    println(method)
    println("max_err_flux_up_lw = $max_err_flux_up_lw")
    println("max_err_flux_dn_lw = $max_err_flux_dn_lw")

    flux_up_sw = Array(slv.flux_sw.flux_up)
    flux_dn_sw = Array(slv.flux_sw.flux_dn)
    flux_dn_dir_sw = Array(slv.flux_sw.flux_dn_dir)

    max_err_flux_up_sw = maximum(abs.(flux_up_sw .- comp_flux_up_sw))
    max_err_flux_dn_sw = maximum(abs.(flux_dn_sw .- comp_flux_dn_sw))

    println("Cloudy-sky shortwave test - $opc")
    println(method)
    println("max_err_flux_up_sw = $max_err_flux_up_sw")
    println("max_err_flux_dn_sw = $max_err_flux_dn_sw")
    println("=======================================")

    toler = FT(1e-5)

    @test max_err_flux_up_lw < toler
    @test max_err_flux_dn_lw < toler

    @test max_err_flux_up_sw < toler
    @test max_err_flux_dn_sw < toler
end
println("running cloudy lookup table version")
all_sky(TwoStream, Float64, Int, DA, use_lut = true)
println("running cloudy pade version")
all_sky(TwoStream, Float64, Int, DA, use_lut = false)


include(Knet.dir("data", "mnist.jl"))
using ArgParse
using Dates

atype = gpu() >= 0 ? KnetArray{Float64} : Array{Float64}

function train(args="")
    s = ArgParseSettings()
    s.autofix_names = true
    s.description = "RAM implementation in Knet."

    @add_arg_table s begin
        ("--name"; help="experiment name"; default=nothing; required=true)
        ("--savedir"; help="checkpoint save directory"; default=SAVEDIR)
        ("--epochs"; help="# of epochs"; arg_type=Int; default=2000)
        ("--batchsize"; help="batch size"; arg_type=Int; default=256)
        ("--seed"; help="random seed"; arg_type=Int; default=-1)
        ("--atype"; help="array type"; default=string(atype))
        ("--lr"; help="learning rate"; arg_type=Float64; default=1e-4)
        ("--gclip"; help="gradient clip"; arg_type=Float64; default=0.1)
        ("--optim"; help="optimizer"; default="rmsprop")
        ("--loadfile"; help="load file"; default=nothing)
        ("--init"; help="init function"; default="randinit")
        ("--sigma"; help="std dev"; arg_type=Float64; default=.22)
        ("--patchsize"; help="patch size"; arg_type=Int; default=8)
        ("--num-patches"; help="# of patches"; arg_type=Int; default=1)
        ("--glimpse-scale"; help="glimpse scale"; arg_type=Float64; default=1.)
        ("--num-channels"; help="# of channels"; arg_type=Int; default=1)
        ("--num-glimpses"; help="# of glimpses"; arg_type=Int; default=6)
        ("--loc-hidden"; help="# of units in location net"; arg_type=Int;
         default=128)
        ("--glimpse-hidden"; help="# of units in glimpse net"; arg_type=Int;
         default=128)
        ("--hiddensize"; help="# of units in contoller rnn"; arg_type=Int;
         default=256)
        ("--rnntype"; help="type of controller (relu|lstm)"; default=":relu")
        ("--num-classes"; help="# of classes"; arg_type=Int; default=10)
    end

    isa(args, AbstractString) && (args=split(args))
    o = parse_args(args, s; as_symbols=true)
    @info "Options parsed [$(now())]"
    println(o); flush(stdout)
    o[:atype] = eval(Meta.parse(o[:atype]))
    o[:seed] > 0 && Knet.seed!(o[:seed])
    optim = eval(Meta.parse(o[:optim]))
    o[:init] = eval(Meta.parse(o[:init]))
    o[:rnntype] = eval(Meta.parse(o[:rnntype]))
    o[:epochs] == -1 && return o

    xtrn, ytrn, xtst, ytst = mnist()

    ram = Net(
        o[:patchsize],
        o[:num_patches],
        o[:glimpse_scale],
        o[:num_channels],
        o[:loc_hidden],
        o[:glimpse_hidden],
        o[:sigma],
        o[:hiddensize],
        o[:num_classes],
        o[:num_glimpses]; rnnType=o[:rnntype], atype=o[:atype], init=o[:init])

    dtrn = minibatch(xtrn, ytrn, o[:batchsize]; xtype=o[:atype])
    dtst = minibatch(xtst, ytst, o[:batchsize]; xtype=o[:atype])

    bestmodel_path = abspath(joinpath(o[:savedir], o[:name]*"-best.jld2"))
    lastmodel_path = abspath(joinpath(o[:savedir], o[:name]*"-last.jld2"))
    history = []
    bestacc = 0.0
    o[:loadfile] != nothing && Knet.@load o[:loadfile] ram history
    loss(x, ygold) = sum(ram(x, ygold)[1:3])
    for epoch = 1:o[:epochs]
        progress!(optim(loss, dtrn; lr=o[:lr], gclip=o[:gclip]))
        trn_losses, trn_acc = validate(ram, dtrn)
        tst_losses, tst_acc = validate(ram, dtst)
        println(
            "epoch=$(1+length(history)) ",
            "trnloss=$(trn_losses), trnacc=$trn_acc, ",
            "tstloss=$(tst_losses), tstacc=$tst_acc")
        push!(history, ([trn_losses..., trn_acc, tst_losses..., tst_acc]))
        Knet.@save lastmodel_path ram history o

        if tst_acc > bestacc
            bestacc = tst_acc
            Knet.@save bestmodel_path ram history o
        end
    end
end


"""
    FastMove(moveType, stab, power, energy, cooldown)

Struct for holding fast moves that holds all information that determines
mechanics: type, STAB (same type attack bonus), power, energy, and cooldown.
Note that this is agnostic to the identity of the actual move itself.
"""
struct FastMove
    moveType::Int8
    stab::Int8
    power::UInt8
    energy::Int8
    cooldown::Int8
end

"""
    FastMove(gm_move, types)

Generate a fast move from the gamemaster entry of the move, and the types of
the mon using it (to determing STAB, same type attack bonus). This should only
be used internally, as generating the move from the name is a lot cleaner.
"""
function FastMove(gm_move::Dict{String,Any}, types)
    return FastMove(
            typings[gm_move["type"]],
            (typings[gm_move["type"]] in types) ? Int8(12) : Int8(1),
            UInt8(gm_move["power"]),
            Int8(gm_move["energyGain"]),
            Int8(gm_move["cooldown"] ÷ 500)
        )
end

"""
    FastMove(move_name, types)

Generate a fast move from the name of the move, and the types of the mon using
it (to determing STAB, same type attack bonus)
"""
function FastMove(move_name::String, types)
    move_index = findfirst(isequal(move_name), map(x ->
        gamemaster["moves"][x]["moveId"], 1:length(gamemaster["moves"])))
    gm_move = gamemaster["moves"][move_index]
    return FastMove(gm_move, types)
end

"""
    ChargedMove(moveType, stab, power, energy, buffChance, opp_buffs, self_buffs)

Struct for holding charged moves that holds all information that determines
mechanics: type, STAB (same type attack bonus), power, energy, and buff
information (chance, which buffs are applied and to whom). Note that this is
agnostic to the identity of the actual move itself.
"""
struct ChargedMove
    moveType::Int8
    stab::Int8
    power::UInt8
    energy::Int8
    buffChance::Int8
    opp_buffs::StatBuffs
    self_buffs::StatBuffs
end

"""
    ChargedMove(gm_move, types)

Generate a charged move from the gamemaster entry of the move, and the types of
the mon using it (to determing STAB, same type attack bonus). This should only
be used internally, as generating the move from the name is a lot cleaner.
"""
function ChargedMove(gm_move::Dict{String,Any}, types)
    return ChargedMove(
        typings[gm_move["type"]],
        (typings[gm_move["type"]] in types) ? Int8(12) : Int8(10),
        UInt8(gm_move["power"]),
        Int8(gm_move["energy"]),
        haskey(gm_move, "buffs") ? round(Int8, parse(Float64,
            gm_move["buffApplyChance"]) * 100) : Int8(0),
        StatBuffs(haskey(gm_move, "buffs") &&
            gm_move["buffTarget"] == "opponent" ?
                Int8(gm_move["buffs"][1]) : Int8(0), haskey(gm_move, "buffs") &&
            gm_move["buffTarget"] == "opponent" ?
                Int8(gm_move["buffs"][2]) : Int8(0)),
        StatBuffs(haskey(gm_move, "buffs") &&
            gm_move["buffTarget"] == "self" ?
                Int8(gm_move["buffs"][1]) : Int8(0), haskey(gm_move, "buffs") &&
            gm_move["buffTarget"] == "self" ?
                Int8(gm_move["buffs"][2]) : Int8(0))
    )
end

"""
    ChargedMove(move_name, types)

Generate a charged move from the name of the move, and the types of the mon
using it (to determing STAB, same type attack bonus)
"""
function ChargedMove(move_name::String, types)
    if move_name == "NONE"
        return ChargedMove(Int8(0), Int8(0), UInt8(0), Int8(0), Int8(0),
            defaultBuff, defaultBuff)
    end
    move_index = findfirst(isequal(move_name), map(x ->
        gamemaster["moves"][x]["moveId"], 1:length(gamemaster["moves"])))
    gm_move = gamemaster["moves"][move_index]
    return ChargedMove(gm_move, types)
end


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

Adds the `CHP` results to the dictionary passed back from `run_reopt` using the solved model `m` and the `REoptInputs` for node `_n`.
Note: the node number is an empty string if evaluating a single `Site`.

CHP results:
- `size_kw` Power capacity size of the CHP system [kW]
- `size_supplemental_firing_kw` Power capacity of CHP supplementary firing system [kW]
- `year_one_fuel_used_mmbtu` Fuel consumed in year one [MMBtu]
- `year_one_electric_energy_produced_kwh` Electric energy produced in year one [kWh]
- `year_one_thermal_energy_produced_mmbtu` Thermal energy produced in year one [MMBtu]
- `year_one_electric_production_series_kw` Electric power production time-series array [kW]
- `year_one_to_grid_series_kw` Electric power exported time-series array [kW]
- `year_one_to_battery_series_kw` Electric power to charge the battery storage time-series array [kW]
- `year_one_to_load_series_kw` Electric power to serve the electric load time-series array [kW]
- `year_one_thermal_to_waste_series_mmbtu_per_hour` Thermal power wasted/unused/vented time-series array [MMBtu/hr]
- `year_one_thermal_to_load_series_mmbtu_per_hour` Thermal power to serve the heating load time-series array [MMBtu/hr]
- `year_one_chp_fuel_cost` Fuel cost from fuel consumed by the CHP system [\$]
- `lifecycle_chp_fuel_cost` Fuel cost from fuel consumed by the CHP system [\$]
"""
function add_chp_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="")
    r = Dict{String, Any}()
	r["size_kw"] = value(sum(m[Symbol("dvSize"*_n)][t] for t in p.techs.chp))
    r["size_supplemental_firing_kw"] = value(sum(m[Symbol("dvSupplementaryFiringSize"*_n)][t] for t in p.techs.chp))
	@expression(m, CHPFuelUsedKWH, sum(m[Symbol("dvFuelUsage"*_n)][t, ts] for t in p.techs.chp, ts in p.time_steps))
	r["year_one_fuel_used_mmbtu"] = round(value(CHPFuelUsedKWH) / MMBTU_TO_KWH, digits=3)
	@expression(m, Year1CHPElecProd,
		p.hours_per_timestep * sum(m[Symbol("dvRatedProduction"*_n)][t,ts] * p.production_factor[t, ts]
			for t in p.techs.chp, ts in p.time_steps))
	r["year_one_electric_energy_produced_kwh"] = round(value(Year1CHPElecProd), digits=3)
	@expression(m, Year1CHPThermalProdKWH,
		p.hours_per_timestep * sum(m[Symbol("dvThermalProduction"*_n)][t,ts] +
        m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] -
        m[Symbol("dvProductionToWaste"*_n)][t,ts]
            for t in p.techs.chp, ts in p.time_steps))
	r["year_one_thermal_energy_produced_mmbtu"] = round(value(Year1CHPThermalProdKWH) / MMBTU_TO_KWH, digits=3)
	@expression(m, CHPElecProdTotal[ts in p.time_steps],
		sum(m[Symbol("dvRatedProduction"*_n)][t,ts] * p.production_factor[t, ts] for t in p.techs.chp))
	r["year_one_electric_production_series_kw"] = round.(value.(CHPElecProdTotal), digits=3)
	@expression(m, CHPtoGrid[ts in p.time_steps], sum(m[Symbol("dvProductionToGrid"*_n)][t,u,ts]
			for t in p.techs.chp, u in p.export_bins_by_tech[t]))
	r["year_one_to_grid_series_kw"] = round.(value.(CHPtoGrid), digits=3)
	@expression(m, CHPtoBatt[ts in p.time_steps],
		sum(m[Symbol("dvProductionToStorage"*_n)][:elec,t,ts] for t in p.techs.chp))
	r["year_one_to_battery_series_kw"] = round.(value.(CHPtoBatt), digits=3)
	@expression(m, CHPtoLoad[ts in p.time_steps],
		sum(m[Symbol("dvRatedProduction"*_n)][t, ts] * p.production_factor[t, ts] * p.levelization_factor[t]
			for t in p.techs.chp) - CHPtoBatt[ts] - CHPtoGrid[ts])
	r["year_one_to_load_series_kw"] = round.(value.(CHPtoLoad), digits=3)
	# @expression(m, CHPtoHotTES[ts in p.time_steps],
	# 	sum(m[Symbol("dvProductionToStorage"*_n)]["HotTES",t,ts] for t in p.techs.chp))
	# r["year_one_thermal_to_tes_series_mmbtu_per_hour"] = round.(value.(CHPtoHotTES), digits=5)
    # if !isempty(p.SteamTurbineTechs)
    #     @expression(m, CHPToSteamTurbine[ts in p.time_steps], sum(m[Symbol("dvThermalToSteamTurbine"*_n)][t,ts] for t in p.techs.chp))
    #     r["year_one_thermal_to_steamturbine_series_mmbtu_per_hour"] = round.(value.(CHPToSteamTurbine), digits=3)
    # else
    #     CHPToSteamTurbine = zeros(p.TimeStepCount)
    #     r["year_one_thermal_to_steamturbine_series_mmbtu_per_hour"] = round.(CHPToSteamTurbine, digits=3)
    # end
	@expression(m, CHPThermalToWasteKWH[ts in p.time_steps],
		sum(m[Symbol("dvProductionToWaste"*_n)][t,ts] for t in p.techs.chp))
	r["year_one_thermal_to_waste_series_mmbtu_per_hour"] = round.(value.(CHPThermalToWasteKWH) / MMBTU_TO_KWH, digits=5)
	# @expression(m, CHPThermalToLoad[ts in p.time_steps],
	# 	sum(m[Symbol("dvThermalProduction"*_n)][t,ts] + m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts]
	# 		for t in p.techs.chp) - CHPtoHotTES[ts] - CHPToSteamTurbine[ts] - CHPThermalToWaste[ts])
    @expression(m, CHPThermalToLoadKWH[ts in p.time_steps],
        sum(m[Symbol("dvThermalProduction"*_n)][t,ts] for t in p.techs.chp) -
        CHPThermalToWasteKWH[ts])
	r["year_one_thermal_to_load_series_mmbtu_per_hour"] = round.(value.(CHPThermalToLoadKWH) / MMBTU_TO_KWH, digits=5)
    r["year_one_chp_fuel_cost"] = round(value(m[:TotalCHPFuelCosts] / p.pwf_fuel["CHP"]), digits=3)
	r["lifecycle_chp_fuel_cost"] = round(value(m[:TotalCHPFuelCosts]) * p.s.financial.offtaker_tax_pct, digits=3)
	#Standby charges and hourly O&M
	r["lifecycle_chp_standby_cost_us_dollars"] = round(value(m[Symbol("TotalCHPStandbyCharges")]) * p.s.financial.offtaker_tax_pct, digits=0)
	r["year_one_chp_standby_cost_us_dollars"] = round(value(m[Symbol("TotalCHPStandbyCharges")]) / p.pwf_e, digits=0)

    d["CHP"] = r
    nothing
end


@testset "MOI_wrapper.jl" begin
    for opt in dual_linear_optimizer
        linear_config = MOIT.TestConfig(atol = 1e-6, rtol = 1e-6)
        linear_cache =
            MOIU.UniversalFallback(Dualization.DualizableModel{Float64}())
        MOI.empty!(opt)
        linear_cached = MOIU.CachingOptimizer(linear_cache, opt)
        linear_bridged = MOIB.full_bridge_optimizer(linear_cached, Float64)

        @testset "linear test" begin
            MOIT.contlineartest(
                linear_bridged,
                linear_config,
                [
                    "linear8b", # Asks for infeasibility ray
                    "linear8c", # Asks for infeasibility ray
                    "linear12", # Asks for infeasibility ray
                    "linear13", # Feasibility problem
                    "linear15",  # Feasibility when written in the canonical form
                ],
            )
        end
    end

    for opt in dual_conic_optimizer
        conic_config = MOIT.TestConfig(atol = 1e-4, rtol = 1e-4)
        conic_cache =
            MOIU.UniversalFallback(Dualization.DualizableModel{Float64}())
        conic_cached = MOIU.CachingOptimizer(conic_cache, opt)
        conic_bridged = MOIB.full_bridge_optimizer(conic_cached, Float64)

        @testset "conic linear, soc, rsoc and sdp test" begin
            MOIT.contconictest(
                conic_bridged,
                conic_config,
                [
                    "lin3", # Feasibility problem
                    "lin4", # Feasibility problem
                    "geomean3f",
                    "geomean3v", # CSDP does not converge after https://github.com/jump-dev/Dualization.jl/pull/86
                    "normone2", # Feasibility problem
                    "norminf2", # Feasibility problem
                    "soc3", # Feasibility problem
                    "rotatedsoc2", # Feasibility problem
                    "rotatedsoc4", # Termination status is `ALMOST_OPTIMAL`
                    "exp", # Tested in exp and power cone test
                    "dualexp", # Tested in exp and power cone test
                    "pow", # Tested in exp and power cone test
                    "dualpow", # Tested in exp and power cone test
                    "rootdet", # Dual not defined in MOI
                    "logdet", # Dual not defined in MOI
                    "relentr", # Dual not defined in MOI
                    "psdt3", # atol should be 1e-2
                    "psds3", # atol should be 1e-2
                ],
            )
        end

        @testset "quadratically constrained" begin
            MOIT.contquadratictest(
                conic_bridged,
                conic_config,
                ["qp", "ncqcp", "socp"],
            )
        end
    end

    for opt in dual_power_cone_optimizer
        power_cone_config = MOIT.TestConfig(atol = 1e-3, rtol = 1e-3)
        power_cone_cache =
            MOIU.UniversalFallback(Dualization.DualizableModel{Float64}())
        power_cone_cached = MOIU.CachingOptimizer(power_cone_cache, opt)
        power_cone_bridged =
            MOIB.full_bridge_optimizer(power_cone_cached, Float64)

        @testset "power cone test" begin
            MOIT.contconictest(
                power_cone_bridged,
                power_cone_config,
                [
                    "lin", # Tested in coninc linear, soc, rsoc and sdp test
                    "normone", # Tested in coninc linear, soc, rsoc and sdp test
                    "norminf", # Tested in coninc linear, soc, rsoc and sdp test
                    "soc", # Tested in coninc linear, soc, rsoc and sdp test
                    "rsoc", # Tested in coninc linear, soc, rsoc and sdp test
                    "geomean", # Tested in coninc linear, soc, rsoc and sdp test
                    "sdp", # Tested in coninc linear, soc, rsoc and sdp test
                    "rootdet", # Dual not defined in MOI
                    "logdet", # Dual not defined in MOI
                    "relentr", # Dual not defined in MOI
                ],
            )
        end
    end

    @testset "attributes" begin
        for optimizer in [dual_conic_optimizer; dual_linear_optimizer]
            before = MOI.get(optimizer, MOI.Silent())
            MOI.set(optimizer, MOI.Silent(), !before)
            @test MOI.get(optimizer, MOI.Silent()) == !before
            MOI.set(optimizer, MOI.Silent(), before)
            @test MOI.get(optimizer, MOI.Silent()) == before
        end
        for i in eachindex(dual_conic_optimizer)
            @test MOI.get(dual_conic_optimizer[i], MOI.SolverName()) ==
                  "Dual model with $(MOI.get(primal_conic_optimizer[i], MOI.SolverName())) attached"
        end
        for i in eachindex(dual_linear_optimizer)
            @test MOI.get(dual_linear_optimizer[i], MOI.SolverName()) ==
                  "Dual model with $(MOI.get(primal_linear_optimizer[i], MOI.SolverName())) attached"
        end
    end

    @testset "support" begin
        for opt in dual_linear_optimizer
            @test !MOI.supports_constraint(opt, SVF, MOI.Integer)
            @test MOI.supports(opt, MOI.ObjectiveSense())
        end
        for opt in dual_conic_optimizer
            @test MOI.supports_constraint(
                opt,
                VVF,
                MOI.PositiveSemidefiniteConeTriangle,
            )
        end
    end

    @testset "dual_status" begin
        @test Dualization.dual_status(MOI.INFEASIBLE) == MOI.DUAL_INFEASIBLE
        @test Dualization.dual_status(MOI.DUAL_INFEASIBLE) == MOI.INFEASIBLE
        @test Dualization.dual_status(MOI.ALMOST_INFEASIBLE) ==
              MOI.ALMOST_DUAL_INFEASIBLE
        @test Dualization.dual_status(MOI.ALMOST_DUAL_INFEASIBLE) ==
              MOI.ALMOST_INFEASIBLE
    end

    @testset "DualOptimizer" begin
        for opt in primal_linear_optimizer
            err = ErrorException("DualOptimizer must have a solver attached")
            @test_throws err DualOptimizer()
            dual_opt_f32 = Dualization.DualOptimizer{Float32}(opt)
            Caching_OptimizerType = typeof(dual_opt_f32.dual_problem.dual_model)
            @test typeof(dual_opt_f32) ==
                  DualOptimizer{Float32,Caching_OptimizerType}
        end
    end
end


module NeuralStyles

using Metalhead
using Images
using Optim
using Flux
#using CuArrays
import Flux: @epochs
#CuArrays.allowscalar(false)

include("pre.jl")
include("styles.jl")

export train

end # module


@testset "nonparametric" begin
    @test_throws ArgumentError Periodogram(ones(1000), -1)
    @test_throws ArgumentError Periodogram(ones(1000,2), -1)
    @test Periodogram(rand(1000),1/2) isa Periodogram
    @test BartlettPeriodogram(rand(1000),1/2) isa BartlettPeriodogram
    @test Periodogram(rand(1000,2),1/2) isa Periodogram
    @test BartlettPeriodogram(rand(1000,2),1/2) isa BartlettPeriodogram
    @test CoherancyEstimate(rand(1000,2),1/2) isa CoherancyEstimate
    @test Periodogram(rand(1000),1/2) + Periodogram(rand(1000),1/2) isa Periodogram
    @test BartlettPeriodogram(rand(1000),1/2) + BartlettPeriodogram(rand(1000),1/2) isa BartlettPeriodogram
    @test Periodogram(rand(1000,2),1/2) + Periodogram(rand(1000,2),1/2) isa Periodogram
    @test BartlettPeriodogram(rand(1000,2),1/2) + BartlettPeriodogram(rand(1000,2),1/2) isa BartlettPeriodogram
    @test CoherancyEstimate(rand(1000,2),1/2) + CoherancyEstimate(rand(1000,2),1/2) isa CoherancyEstimate
end


################################################################################
#
#	ABSTRACT TYPE
#
#   ReciprocalUnitcell{P,B} <: Unitcell{P,B}
#   --> P is the reciprocal site type (<: AbstractReciprocalPoint{D})
#       --> D is the dimension of embedding space
#   --> B is the bond type (<: AbstractBond{L,N})
#       --> N is the dimension of the Bravais lattice that the bond is located in
#       --> L is the label type of bonds
#
#   FILE CONTAINS
#       - abstract type definition
#       - interface definition
#       - TODO interface testing
#
################################################################################

################################################################################
#
#   ABSTRACT TYPE DEFINITION
#
################################################################################
abstract type AbstractReciprocalUnitcell{
        P <: AbstractReciprocalPoint{D} where {D},
        B <: AbstractBond{L,N} where {L,N}
    } <: AbstractUnitcell{P,B} end

# export the type
export AbstractReciprocalUnitcell

################################################################################
#
#	INTERFACING / ACCESSING UNITCELLS
#	(functions that are overwritten by abstract reciprocal type, NOT concrete)
#
################################################################################

# default constructor interface
# used for creation of new unitcells
function newUnitcell(
            ::Type{U},
            lattice_vectors :: Vector{<:Vector{<:Real}},
            sites           :: Vector{S},
            bonds           :: Vector{B}
        ) :: U where {D,N,L,S<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{S,B}}

    # check if only a single site is given
    @assert length(sites) == 1
    # check if the site is located at 0
    @assert sum(abs.(point(sites[1]))) < 1e-8

    # pass to the reciprocal unitcell constructor interface
    return newReciprocalUnitcell(U, lattice_vectors, bonds)
end

# export the newUnitcell interface
export newUnitcell

# accessing a list of sites
function sites(
            unitcell :: U
        ) :: Vector{S} where {D,N,L,S<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{S,B}}

    # return the Gamma point inside an array
    return S[gammaPoint(unitcell),]
end
# setting a list of sites
function sites!(
            unitcell :: U,
            sites    :: Vector{S}
        ) where {D,N,L,S<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{S,B}}

    # check if only a single site is given
    @assert length(sites) == 1
    # check if the site is located at 0
    @assert sum(abs.(point(sites[1]))) < 1e-8

    # set the Gamma point
    gammaPoint!(unitcell, sites[1])
end

# export the sites interface
export sites, sites!

################################################################################
#
#	INTERFACING / ACCESSING RECIPROCAL UNITCELLS
#	(functions that have to be overwritten by concrete types)
#
################################################################################

# constructor
function newReciprocalUnitcell(
            ::Type{R},
            lattice_vectors :: Vector{<:Vector{<:Real}},
            bonds           :: Vector{B}
        ) :: R where {D,N,L,P<:AbstractReciprocalPoint{D}, B<:AbstractBond{N,L}, R<:AbstractReciprocalUnitcell{P,B}}

    # throw an error because implementation for concrete type is missing
    error(  "not implemented function 'newReciprocalUnitcell' for concrete reciprocal unitcell type " *
            string(R) * " with bond type " * string(B) )
end

# export the constructor
export newReciprocalUnitcell

# accessing a list of lattice vectors
function latticeVectors(
            unitcell :: U
        ) :: Vector{Vector{Float64}} where {P,B,U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'latticeVectors' for concrete reciprocal unitcell type " * string(U) )
end
# setting a list of lattice vectors
function latticeVectors!(
            unitcell        :: U,
            lattice_vectors :: Vector{<:Vector{<:Real}}
        ) where {P,B,U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'latticeVectors!' for concrete reciprocal unitcell type " * string(U) )
end

# export the latticeVectors interface
export latticeVectors, latticeVectors!

# accessing the gamma point
function gammaPoint(
            unitcell :: U
        ) :: P where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'gammaPoint' for concrete reciprocal unitcell type " * string(U) )
end

# setting the gamma point
function gammaPoint!(
            unitcell :: U,
            site     :: P
        ) :: P where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'gammaPoint!' for concrete reciprocal unitcell type " * string(U) )
end

# export gamma point interface
export gammaPoint, gammaPoint!

# accessing a list of bonds
function bonds(
            unitcell :: U
        ) :: Vector{B} where {N,L,P,B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'bonds' for concrete reciprocal unitcell type " *
            string(U) * " with bond type " * string(B)   )
end
# setting a list of bonds
function bonds!(
            unitcell :: U,
            bonds    :: Vector{B}
        ) where {N,L,P,B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'bonds!' for concrete reciprocal unitcell type " *
            string(U) * " with bond type " * string(B)   )
end

# export the bonds interface
export bonds, bonds!

################################################################################
#
#   ADDITIONAL SYNTAX FOR RECIPROCAL UNITCELLS
#
################################################################################

# specific to reciprocal Bravais lattices (just rename a1 etc to b1 etc)

# b1
function b1(
            unitcell :: U
        ) :: Vector{Float64} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # return the first entry in the lattice vectors list
    return latticeVectors(unitcell)[1]
end

# b2
function b2(
            unitcell :: U
        ) :: Vector{Float64} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # return the second entry in the lattice vectors list
    return latticeVectors(unitcell)[2]
end

# b3
function b3(
            unitcell :: U
        ) :: Vector{Float64} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N},U<:AbstractReciprocalUnitcell{P,B}}

    # return the third entry in the lattice vectors list
    return latticeVectors(unitcell)[3]
end

# export the Bravais lattice interfaces
export b1, b2, b3


"""
    create_internal_transitions!(action::AbstractAction)

For every translation an internal translation is added to `action.internal_transitions`.
Same is true for all other transitions.
"""
function create_internal_transitions!(action::AbstractAction)
    for trans in action.transitions
        if trans isa Translation
            push!(action.internal_transitions, InternalTranslation(O))
        elseif trans isa Rotation
            push!(action.internal_transitions, InternalRotation(0.0, O))
        elseif trans isa Scaling
            push!(action.internal_transitions, InternalScaling((1.0, 1.0)))
        end
    end
end

"""
    compute_transition!(action::AbstractAction, video::Video, frame::Int)

Update action.internal_transitions for the current frame number
"""
function compute_transition!(action::AbstractAction, video::Video, frame::Int)
    for (trans, internal_trans) in zip(action.transitions, action.internal_transitions)
        compute_transition!(internal_trans, trans, video, action, frame)
    end
end

"""
    compute_transition!(internal_rotation::InternalRotation, rotation::Rotation, video,
                        action::AbstractAction, frame)

Computes the rotation transformation for the `action`.
If the `Rotation` is given directly it uses the frame number for interpolation.
If `rotation` includes symbols the current definition of that look up is used for computation.
"""
function compute_transition!(
    internal_rotation::InternalRotation,
    rotation::Rotation,
    video,
    action::AbstractAction,
    frame,
)
    t = get_interpolation(action, frame)
    from, to, center = rotation.from, rotation.to, rotation.center

    center isa Symbol && (center = pos(center))
    from isa Symbol && (from = angle(from))
    to isa Symbol && (to = angle(to))

    internal_rotation.angle = from + t * (to - from)
    internal_rotation.center = center
end

"""
    compute_transition!(internal_translation::InternalTranslation, translation::Translation,
                        video, action::AbstractAction, frame)

Computes the translation transformation for the `action`.
If the `translation` is given directly it uses the frame number for interpolation.
If `translation` includes symbols the current definition of that symbol is looked up
and used for computation.
"""
function compute_transition!(
    internal_translation::InternalTranslation,
    translation::Translation,
    video,
    action::AbstractAction,
    frame,
)
    t = get_interpolation(action, frame)
    from, to = translation.from, translation.to

    from isa Symbol && (from = pos(from))
    to isa Symbol && (to = pos(to))

    internal_translation.by = from + t * (to - from)
end

"""
    compute_transition!(internal_translation::InternalScaling, translation::Scaling,
                        video, action::AbstractAction, frame)

Computes the scaling transformation for the `action`.
If the `scaling` is given directly it uses the frame number for interpolation.
If `scaling` includes symbols, the current definition of that symbol is looked up
and used for computation.
"""
function compute_transition!(
    internal_scale::InternalScaling,
    scale::Scaling,
    video,
    action::AbstractAction,
    frame,
)
    t = get_interpolation(action, frame)
    from, to = scale.from, scale.to

    if !scale.compute_from_once || frame == first(get_frames(action))
        from isa Symbol && (from = get_scale(from))
        if scale.compute_from_once
            scale.from = from
        end
    end
    to isa Symbol && (to = get_scale(to))
    internal_scale.scale = from .+ t .* (to .- from)
end

"""
    perform_transformation(action::AbstractAction)

Perform the transformations as described in action.internal_transitions
"""
function perform_transformation(action::AbstractAction)
    for trans in action.internal_transitions
        perform_transformation(trans)
    end
end

"""
    perform_transformation(trans::InternalTranslation)

Translate as described in `trans`.
"""
function perform_transformation(trans::InternalTranslation)
    translate(trans.by)
end

"""
    perform_transformation(trans::InternalRotation)

Translate and rotate as described in `trans`.
"""
function perform_transformation(trans::InternalRotation)
    translate(trans.center)
    rotate(trans.angle)
end

"""
    perform_transformation(trans::InternalScaling)

Scale as described in `trans`.
"""
function perform_transformation(trans::InternalScaling)
    scaleto(trans.scale...)
end


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

module Types

export VersionInterval, VersionSet, Requires, Available, Fixed, merge_requires!, satisfies
import Base: show, isempty, in, intersect, ==, hash, copy, deepcopy_internal

immutable VersionInterval
    lower::VersionNumber
    upper::VersionNumber
end
VersionInterval(lower::VersionNumber) = VersionInterval(lower,typemax(VersionNumber))
VersionInterval() = VersionInterval(typemin(VersionNumber))

show(io::IO, i::VersionInterval) = print(io, "[$(i.lower),$(i.upper))")
isempty(i::VersionInterval) = i.upper <= i.lower
in(v::VersionNumber, i::VersionInterval) = i.lower <= v < i.upper
intersect(a::VersionInterval, b::VersionInterval) = VersionInterval(max(a.lower,b.lower), min(a.upper,b.upper))
==(a::VersionInterval, b::VersionInterval) = a.lower == b.lower && a.upper == b.upper
hash(i::VersionInterval, h::UInt) = hash((i.lower, i.upper), h + (0x0f870a92db508386 % UInt))

immutable VersionSet
    intervals::Vector{VersionInterval}
end
function VersionSet(versions::Vector{VersionNumber})
    intervals = VersionInterval[]
    if isempty(versions)
        push!(intervals, VersionInterval())
    else
        isodd(length(versions)) && push!(versions, typemax(VersionNumber))
        while !isempty(versions)
            push!(intervals, VersionInterval(shift!(versions), shift!(versions)))
        end
    end
    VersionSet(intervals)
end
VersionSet(versions::VersionNumber...) = VersionSet(VersionNumber[versions...])

show(io::IO, s::VersionSet) = join(io, s.intervals, " ∪ ")
isempty(s::VersionSet) = all(isempty, s.intervals)
in(v::VersionNumber, s::VersionSet) = any(i->in(v,i), s.intervals)
function intersect(A::VersionSet, B::VersionSet)
    ivals = vec([ intersect(a,b) for a in A.intervals, b in B.intervals ])
    ivals = copy(ivals) # temporary bandaid for issue #4592
    filter!(i->!isempty(i), ivals)
    sort!(ivals, by=i->i.lower)
    VersionSet(ivals)
end
==(A::VersionSet, B::VersionSet) = A.intervals == B.intervals
hash(s::VersionSet, h::UInt) = hash(s.intervals, h + (0x2fd2ca6efa023f44 % UInt))
deepcopy_internal(vs::VersionSet, ::ObjectIdDict) = VersionSet(copy(vs.intervals))

typealias Requires Dict{String,VersionSet}

function merge_requires!(A::Requires, B::Requires)
    for (pkg,vers) in B
        A[pkg] = haskey(A,pkg) ? intersect(A[pkg],vers) : vers
    end
    return A
end

satisfies(pkg::AbstractString, ver::VersionNumber, reqs::Requires) =
    !haskey(reqs, pkg) || in(ver, reqs[pkg])

immutable Available
    sha1::String
    requires::Requires
end

==(a::Available, b::Available) = a.sha1 == b.sha1 && a.requires == b.requires
hash(a::Available, h::UInt) = hash((a.sha1, a.requires), h + (0xbc8ae0de9d11d972 % UInt))
copy(a::Available) = Available(a.sha1, copy(a.requires))

show(io::IO, a::Available) = isempty(a.requires) ?
    print(io, "Available(", repr(a.sha1), ")") :
    print(io, "Available(", repr(a.sha1), ",", a.requires, ")")

immutable Fixed
    version::VersionNumber
    requires::Requires
end
Fixed(v::VersionNumber) = Fixed(v,Requires())

==(a::Fixed, b::Fixed) = a.version == b.version && a.requires == b.requires
hash(f::Fixed, h::UInt) = hash((f.version, f.requires), h + (0x68628b809fd417ca % UInt))

show(io::IO, f::Fixed) = isempty(f.requires) ?
    print(io, "Fixed(", repr(f.version), ")") :
    print(io, "Fixed(", repr(f.version), ",", f.requires, ")")

# TODO: Available & Fixed are almost the same – merge them?
# Free could include the same information too, it just isn't
# required by anything that processes these things.

end # module


using Knet
libknet8handle = Libdl.dlopen(Knet.libknet8)

SIZE = 100000
ITER = 100000
x32 = KnetArray(rand(Float32,SIZE))
y32 = similar(x32)
s32 = rand(Float32)
x64 = KnetArray(rand(Float64,SIZE))
y64 = similar(x64)
s64 = rand(Float64)

function cuda10test(fname, jname=fname, o...)
    println(fname)
    fcpu = eval(parse(jname))
    f32 = Libdl.dlsym(libknet8handle, fname*"_32_10")
    @time cuda10rep(f32,x32,s32,y32)
    isapprox(Array(y32),fcpu(Array(x32),s32)) || warn("$fname 32")
    f64 = Libdl.dlsym(libknet8handle, fname*"_64_10")
    @time cuda10rep(f64,x64,s64,y64)
    isapprox(Array(y64),fcpu(Array(x64),s64)) || warn("$fname 64")
end

function cuda10rep{T}(f,x::KnetArray{T},s::T,y::KnetArray{T})
    n = Cint(length(y))
    for i=1:ITER
        ccall(f,Void,(Cint,Ptr{T},T,Ptr{T}),n,x,s,y)
    end
    Knet.@cuda(cudart,cudaDeviceSynchronize,())
    Knet.@cuda(cudart,cudaGetLastError,())
end

for f in Knet.cuda10
    isa(f,Tuple) || (f=(f,))
    cuda10test(f...)
    cuda10test(f...)
    cuda10test(f...)
end


# Fast version with BasicCounting algorithm

include("./LinkedList.jl")
#using ListItem

mutable struct AdwinObject
    DELTA                   :: Float64
    mintMinimLongitudWindow :: Int
    mdbldelta               :: Float64
    mintTime                :: Int
    mintClock               :: Int
    mdblWidth               :: Float64  # Mean of Width = mdblWidth/Number of items
    #BUCKET
    MAXBUCKETS              :: Int
    lastBucketRow           :: Int
    TOTAL                   :: Float64
    VARIANCE                :: Float64
    WIDTH                   :: Int
    BucketNumber            :: Int
    #
    Detect                  :: Int
    numberDetections        :: Int
    DetectTwice             :: Int
    blnBucketDeleted        :: Bool
    BucketNumberMAX         :: Int
    mintMinWinLength        :: Int
    listRowBuckets          :: List
    AdwinObject() = new()
end

function initAdwinObject!(AO)
    AO.DELTA = 0.002
    AO.mintMinimLongitudWindow = 10
    AO.mdbldelta = 0.002
    AO.mintTime = 0
    AO.mintClock = 32
    AO.mdblWidth = 0 # Mean of Width = mdblWidth/Number of items
    #BUCKET
    AO.MAXBUCKETS = 5
    AO.lastBucketRow = 0
    AO.TOTAL = 0
    AO.VARIANCE = 0
    AO.WIDTH = 0
    AO.BucketNumber = 0
    AO.Detect = 0
    AO.numberDetections = 0
    AO.DetectTwice  = 0
    AO.blnBucketDeleted = false
    AO.BucketNumberMAX = 0
    AO.mintMinWinLength = 5
    #AO.listRowBuckets = List()
end

function initBuckets!(ao::AdwinObject)
    ao.listRowBuckets = List()
    ao.lastBucketRow = 0
    ao.TOTAL = 0
    ao.VARIANCE = 0
    ao.WIDTH = 0
    ao.BucketNumber = 0
end

function setInput!(ao::AdwinObject, intEntrada::Float64, delta::Float64)
    blnChange = false
    blnExit = false
    cursor :: ListIt = ListIt()
    ao.mintTime += 1
    # 		//1,2)Increment window in one element
    insertElement!(ao, intEntrada) # -- 1
    bitBucketDeleted = false
    #		//3)Reduce  window
    if ao.mintTime % ao.mintClock == 0 && ao.WIDTH > ao.mintMinimLongitudWindow #line 401
      # println("... setInput!: Enter first If")
      blnReduceWidth= true
      while blnReduceWidth
        # println("... setInput!: Enter While")
        blnReduceWidth = false
        blnExit = false # line 406
        n0=0
        n1=ao.WIDTH
        u0=0
        u1= ao.TOTAL # getTotal();
        v0=0
        v1=ao.VARIANCE
        n2=0
        u2=0

        cursor = ao.listRowBuckets.tail
        i = ao.lastBucketRow
        while !blnExit && !(cursor == cursor.next) # line 418
          println("... setInput!: Enter second While")
          for k = 0 : cursor.bucketSizeRow  # line 419
            println(k)
          end
        end
        # println(myCounter(ao.listRowBuckets.head))
      end
    end
end

function setInput!(ao::AdwinObject, intEntrada::Float64)
    setInput!(ao, intEntrada, ao.mdbldelta)
end

function insertElementBucket!(ao::AdwinObject, value::Float64, variance::Float64, node::Node)
    insertBucket!(node, value, variance) # todo -- 3
    ao.BucketNumber += 1
    if ao.BucketNumber > ao.BucketNumberMAX
      ao.BucketNumberMAX = ao.BucketNumber
    end
end

# function compressBuckets!(ao::AdwinObject)
#     # Traverse the list of buckets in increasing order
#     n1::Int
#     n2::Int
#     u1::Float64
#     u2::Float64
#     incVariance::Float64
#     cursor::ListIt
#     nextNode::ListIt
#     cursor = ao.listRowBuckets.head
#     i::Int = 0
#     while !isnull(cursor)
#         # Find the number of buckets in a row
#         k = cursor.bucketSizeRow
#         # If the row is full, merge buckets
#         if k == ao.MAXBUCKETS+1
#             nextNode = cursor.next
#             if isnull(nextNode)
#                 ao.listRowBuckets # line 365
#             end
#         end
#     end
# end

function compressBuckets!(ao::AdwinObject) # --2
  # 		//Traverse the list of buckets in increasing order
  n1::Int=0
  n2::Int=0
  u1::Float64=0
  u2::Float64=0
  cursor :: Node = ao.listRowBuckets.head
  nextNode:: Node = EmptyNode()
  i=0
  while ! typeof(cursor) <: EmptyNode # --3
    # //Find the number of buckets in a row
    #k = cursor.bucketSizeRow
    # 			//If the row is full, merge buckets
    #if k == ao.MAXBUCKETS
    #  nextNode = cursor.next
    #else
    #  break
    #end
    cursor = cursor.next
  end
end

function insertElement!(ao::AdwinObject, value::Float64)
    ao.WIDTH += 1
    insertElementBucket!(ao, 0.0, value, ao.listRowBuckets.head)
    incVariance::Float64 = 0.0
    if ao.WIDTH > 1
        incVariance = (ao.WIDTH-1)*(value-ao.TOTAL/(ao.WIDTH-1))*(value-ao.TOTAL/(ao.WIDTH-1))/ao.WIDTH
    end
    ao.VARIANCE += incVariance
    ao.TOTAL += value
    # println("w: $value")
    # println(myCounter(ao.listRowBuckets.head))
    #compressBuckets!(ao)
end

function run_adwin()
    AO = AdwinObject()
    initAdwinObject!(AO)
    initBuckets!(AO)
    for p in 0:999
        x = p < 500 ? 1000.0 : 500.0
        setInput!(AO, x)
    end
    println("... run_adwin(): OK")
end


#!/usr/bin/env julia --project=@.

using Bukdu # pipeline Conn routes resources get plug Router
using Sockets
using FileIO

using JWebImageDemo

function get_server()
    if haskey(ENV, "ON_HEROKU")
        (port=parse(Int, ENV["PORT"]), host=Sockets.IPAddr(0,0,0,0))
    else
        (port=8080, host=ip"127.0.0.1")
    end
end

server = get_server()

struct WelcomeController <: ApplicationController
    conn::Conn
end

import HTTP, HTTP.Parsers

"""
https://www.ietf.org/rfc/rfc2616.txt
"""
function parse_multipart(data::Vector{UInt8})
    chunk_end = HTTP.Parsers.find_end_of_chunk_size(data)
    if chunk_end == 0
        return nothing
    end

    boundary = data[1:chunk_end - 2] # without \r\n
    header_end = HTTP.Parsers.find_end_of_header(data)

    data_start = header_end + 1
    data_end = length(data) - chunk_end - 2
    # check that ending is with same boundary + two additional hyphens
    if (data[data_end + 1: end] == [boundary..., 0x2d, 0x2d, 0x0d, 0x0a])
       return data[data_start : data_end]
    end
    return nothing
end

function process_image(c::WelcomeController)
    data = parse_multipart(c.conn.request.body)
    dump(c.conn.request)
    if data != nothing
        buf = IOBuffer(data)
        img = FileIO.load(Stream(format"PNG", buf))
        out_img = JWebImageDemo.permute_image(img, 5)
        out_buf = IOBuffer()
        FileIO.save(Stream(format"PNG", out_buf), out_img)
        return Render("image/png", take!(out_buf))
    end
    render(Text, "Please upload some image file")
end

routes() do
    plug(Plug.Static, at="/", from=normpath(@__DIR__, "../public"))

    post("/api/process_image", WelcomeController, process_image)
end

Bukdu.start(server.port; host=server.host)

Router.call(get, "/") #
# CLI.routes()

Base.JLOptions().isinteractive==0 && wait()

# Bukdu.stop()


"""
    showpower(p::Point, power::P;
        maxpower = 10.0kW,
        fs = EM,
        symbsize = 1.5fs,
        backgroundcolor = colorant"white",
        labelpower::Bool = true) where {P <: Power}

p                   bottom left corner of indicator.
power               to be shown.
maxpower = 10.0kW   the symbol will be coloured to indicate power / maxpower
fs = EM             font size
symbsize = 1.5EM    lightning bolt size.
backgroundcolor     is used to pick a color for an outline.
labelpower = true   text or no text
"""
function showpower(p::Point, power::Power;
                   maxpower = 15.0kW,
                   fs = EM,
                   symbsize = 1.5fs,
                   backgroundcolor = colorant"white",
                   labelpower::Bool = true)

    fractionpower = power / ( maxpower|> typeof(power))
    rounded = round(unit(power), power, digits = 1)
    symbsize = 1.5fs
    # Prepare color selection
    luminback = lumin(backgroundcolor)
    luminfront = get_current_lumin()
    deltalumin = luminback - luminfront
    avglumin = luminfront + 0.5deltalumin
    curcol = get_current_RGB()
    avgcol = color_with_lumin(curcol, luminfront + 0.5 * deltalumin)
    contrastcol = color_with_lumin(curcol, luminfront + 2 * deltalumin)

    gsave()
    fontsize(symbsize)
    fontface("DejaVu Sans")
    str = "⚡"
    wi, he = textextents(str)[3:4]
    # Outline symbol
    sethue(avgcol)
    fontface("DejaVu Sans Bold")
    fontsize(1.0989010989010988 * symbsize)
    text(str, p - fs.*(0.03, -0.04))
    # Shadow text
    fontface("DejaVu Sans")
    fontsize(fs)
    labelpower && text(string(rounded), p + (1.5wi, 0) - fs.*(0.03, -0.04))

    # Inside symbol
    sethue(color_with_lumin(color_from_palette("red"), luminback))
    fontsize(symbsize)
    text(str, p)
    # Front text
    fontsize(fs)
    labelpower && text(string(rounded), p + (1.5wi, 0))

    # Make a mask of the symbol
    clipreset()
    newpath()
    translate(p)
    fontsize(symbsize)
    textpath(str)
    fillpreserve()
    clip()
    translate(-p)

    # fill the symbol from bottom according to relative power
    sethue(curcol)
    rect(p, wi , -fractionpower * he, :fill)
    sethue(contrastcol)
    setline(PT)
    rect(p, wi , -fractionpower * he, :stroke)

    # Clean up
    clipreset()
    grestore()
    nothing
end


module photovoltaic
struct pv_module
    module_type # 模块类型
    stc_watts
    stc_eff
    ff
    aspect_ratio
    width
    length
    area
    vmp
    ndiode
    gamma
    ar_glass
    bifaciality
    function pv_module(;module_type::Int=0,bifaciality::Float64=0.001
    )
        new(
        tilt,
        array_type,
        module_type,
        losses,
        azimuth,
        gcr,
        radius,
        name,
        location,
        existing_kw,
        min_kw,
        max_kw,
        installed_cost_per_kw,
        om_cost_per_kw,
        degradation_pct,
        macrs_option_years,
        macrs_bonus_pct,
        macrs_itc_reduction,
        kw_per_square_foot,
        acres_per_kw,
        inv_eff,
        dc_ac_ratio,
        prod_factor_series_kw,
        federal_itc_pct,
        federal_rebate_per_kw,
        state_ibi_pct,
        state_ibi_max,
        state_rebate_per_kw,
        state_rebate_max,
        utility_ibi_pct,
        utility_ibi_max,
        utility_rebate_per_kw,
        utility_rebate_max,
        production_incentive_per_kwh,
        production_incentive_max_benefit,
        production_incentive_years,
        production_incentive_max_kw
    )
    end
end

struct pv_array
    array_type

    dc_nameplate
    dc_ac_ratio
    ac_nameplate
    xfmr_rating
    ac_plant_max
    inv_eff_percent
    dc_loss_percent
    tilt
    azimuth
    rotlim
    function pv_array(;)
        new(

        )
    end
end

function prodfactor(pv::PV,latitude::Real, longitude::Real; timeframe="hourly")

end

end


module DCAbuild
using DCAlign
using PlmDCA, FastaIO, GaussDCA
using Statistics, Printf, DelimitedFiles, Logging, StatsBase

include("insertions.jl")      # all insertions related functions
include("utils.jl")	          # i/o functions,
include("build_model.jl")     # main script for learning a Potts model, insertion penalties and gap penalties
include("fasta_utils.jl")	  # fasta utilities
include("seed_align.jl")	  # pipeline to an aligned seed from an unaligned one

function __init__()
	if Sys.which("mafft") === nothing
		 error("Please install mafft binaries at https://mafft.cbrc.jp/alignment/software/mafft-7.453-with-extensions-src.tgz and make the executables available to the system")
	end
	if Sys.which("hmmbuild") === nothing
		error("Please install hmmer suite at http://eddylab.org/software/hmmer/hmmer.tar.gz and make the executables available to the system")
	end
end

end #end module


function model(model_name, model_expr, mod = Base)
    model_object = read_model(model_expr, mod)
    model_object_name = Symbol(model_name, "#OBJECT#DEFINITION#")
    quote
        struct $model_name{T <: NamedTuple}
            data::T
        end
        const $model_object_name = $model_object
        @generated function ProbabilityModels.PaddedMatrices.logdensity(
            var"###DATA###"::$model_name{var"##PROB#MODEL#NAMED#TUPLE#TYPE##"}, var"#θ#"
        ) where {var"##PROB#MODEL#NAMED#TUPLE#TYPE##"}
            var"#__m__#" = deepcopy($model_object_name)
            ProbabilityModels.preprocess!(var"#__m__#", var"##PROB#MODEL#NAMED#TUPLE#TYPE##")
            q = ProbabilityModels.ReverseDiffExpressionsBase.lower(var"#__m__#")
            pushfirst!(q.args, Expr(:(=), var"#DATA#", Expr(:(.), var"###DATA###", QuoteNode(:data))))
            q
        end
        @generated function ProbabilityModels.PaddedMatrices.∂logdensity!(
            var"#∇#", var"###DATA###"::$model_name{var"##PROB#MODEL#NAMED#TUPLE#TYPE##"}, var"#θ#"
        ) where {var"##PROB#MODEL#NAMED#TUPLE#TYPE##"}
            var"#__m__#" = deepcopy($model_object_name)
            ProbabilityModels.preprocess!(var"#__m__#", var"##PROB#MODEL#NAMED#TUPLE#TYPE##")
            var"#__∂m__#" = ProbabilityModels.ReverseDiffExpressionsBase.differentiate(var"#__m__#")
            q = ProbabilityModels.ReverseDiffExpressionsBase.lower(var"#__∂m__#")
            pushfirst!(q.args, Expr(:(=), var"#DATA#", Expr(:(.), var"###DATA###", QuoteNode(:data))))
            q
        end
    end
end

macro model(model_name, model_expr)
    model(model_name, model_expr, __module__) |> esc
end


using Flux.Tracker, NNlib
using Flux.Tracker: TrackedReal, gradcheck
using FluxExtensions

include("utils.jl")
include("layers/resdense.jl")
include("pdfs.jl")
include("triangularloss.jl")

@testset "sumnondiagonal" begin
	x = randn(4,4)
	@test abs(FluxExtensions.sumnondiagonal(x) .- (sum(x) - sum(diag(x)))) < 1e-14
	@test gradcheck(FluxExtensions.sumnondiagonal,x)
end

@testset "scatter" begin
	x = randn(4,4)
	@test gradcheck((x) -> sum(FluxExtensions.scatter(x,4)),x)
	@test gradcheck((x) -> sum(FluxExtensions.scatter(x,[1,2,3,4])),x)
end

@testset "conversion to Float32" begin
	m = Dense(3, 1)
	x = randn(Float32, 3,5)
	mm = to32(m)
	@test eltype(Flux.data(m(x))) == Float64
	@test eltype(Flux.data(mm(x))) == Float32
end


module ExampleTables

using DataFrames

struct ExampleTable
    colrange :: Vector
    rownames :: Vector
    data :: DataFrame
    examples :: DataFrame
end

function ExampleTable( d :: DataFrame )
    d2 = deepcopy( d )
    ExampleTable( d, d2 )
end

function ExampleTable( colrange :: Vector{T<:Real}, rownames :: Vector )
    Exan

end

end


module ICATT

include("elements.jl")
include("kepler.jl")
include("lambert.jl")
include("dopri.jl")

end


draw(PNG("portrait.png", 100, 140),
     Paper("lightgrey")) do
    move(0, 0)
    line(1, 0)
    move(0, 0)
    line(0, 1)
end


q1 = nominal_configuration(biped)
v1 = zeros(biped.nq)

h = 0.01
T = 100

s = Simulator(biped, T, h=h)
status = simulate!(s, q1, v1)

vis = Visualizer()
open(vis)
visualize!(vis, s)


a  = read_SSR(joinpath(dirname(@__FILE__), "..", "data", "test_Hz19.5-testing.bdf"))

@fact a.processing["Side"] --> "Bilateral"
@fact a.processing["Name"] --> "P2"
@fact a.processing["Amplitude"] --> 70.0
@fact a.processing["Carrier_Frequency"] --> 1000.0
@fact modulationrate(a) --> 40.0390625


# by Lazaro Alonso
using GLMakie, GeoMakie, CairoMakie, Downloads
using GeoJSON, GeoInterface
CairoMakie.activate!()
let
    # data from
    # https://github.com/telegeography/www.submarinecablemap.com
    urlPoints = "https://raw.githubusercontent.com/telegeography/www.submarinecablemap.com/master/web/public/api/v3/landing-point/landing-point-geo.json"
    urlCables = "https://raw.githubusercontent.com/telegeography/www.submarinecablemap.com/master/web/public/api/v3/cable/cable-geo.json"

    landPoints = Downloads.download(urlPoints, IOBuffer())
    landCables = Downloads.download(urlCables, IOBuffer())

    land_geoPoints = GeoJSON.read(seekstart(landPoints))
    land_geoCables = GeoJSON.read(seekstart(landCables))

    toPoints = GeoMakie.geo2basic(land_geoPoints)
    #toLines = GeoMakie.geo2basic(land_geoCables) # this should probably be supported.
    feat = GeoInterface.features(land_geoCables)
    toLines = GeoInterface.coordinates.(GeoInterface.geometry.(feat))
    splitLines = []
    for i in 1:length(toLines)
        for j in 1:length(toLines[i])
            push!(splitLines, Point2f0.(toLines[i][j]))
        end
    end
    # broken lines at -180 and 180... they should
    # be the same line and be in the same array.

    function plotCables()
        fig, ax, = scatter(toPoints, markersize= 5, color = 1:length(toPoints),
            colormap = :plasma, figure = (;resolution = (1200,700), fontsize = 24))
        [lines!(ax,splitLines[i], linewidth=0.85) for i in 1:length(splitLines)]
        lines!(GeoMakie.coastlines(), color = :white, linewidth = 0.35)
        limits!(ax,-185,185,-95,95)
        ax.xticks = -180:60:180
        ax.yticks = -90:30:90
        ax.xtickformat = "{:d}ᵒ"
        ax.ytickformat = "{:d}ᵒ"
        fig
    end
    fig = with_theme(plotCables, theme_dark())
    save(joinpath(@__DIR__, "output", "submarineCables.png"), fig) # HIDE
end
using Pkg # HIDE
Pkg.status(["GLMakie", "GeoMakie", "CairoMakie", "Downloads", "GeoJSON", "GeoInterface"]) # HIDE


# This file was generated, do not modify it. # hide
tree = machine(DecisionTreeRegressor(), X, y)
e = evaluate!(tree, resampling=Holdout(fraction_train=0.8),
              measure=[rms, rmslp1])
e |> pprint # use PrettyPrinting


include("../core/eq-core.jl")
include("../core/score-engine.jl")

function test_royal_flush()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.CLUBS),
        Card(Ranks.ACE, Suits.DIAMONDS),
        Card(Ranks.ACE, Suits.HEARTS),
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.ACE, Suits.CLUBS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
    ]
    royal_flush = _royal_flush_draw(cards)
    if ! expect(royal_flush, best_cards, desc="ERROR: match royal flush")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.ACE, Suits.CLUBS), Card(Ranks.TEN, Suits.HEARTS)))
    board = Board(Card(Ranks.KING, Suits.CLUBS), Card(Ranks.ACE, Suits.HEARTS), Card(Ranks.JACK, Suits.CLUBS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.TEN, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.ROYAL, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score royal flush")
        failing += 1
    end
    return failing
end

function test_straight_flush()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.KING, Suits.DIAMONDS),
        Card(Ranks.KING, Suits.HEARTS),
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.NINE, Suits.CLUBS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.NINE, Suits.CLUBS)
    ]
    straight_flush = _straight_flush_draw(cards)
    if ! expect(straight_flush, best_cards, desc="ERROR: match straight flush")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.ACE, Suits.SPADES), Card(Ranks.TEN, Suits.CLUBS)))
    board = Board(Card(Ranks.KING, Suits.CLUBS), Card(Ranks.NINE, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.TEN, Suits.HEARTS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.SFLUSH, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score straight flush")
        failing += 1
    end
    return failing
end

function test_four_of_a_kind()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.HEARTS),
        Card(Ranks.KING, Suits.DIAMONDS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.NINE, Suits.SPADES),
        Card(Ranks.TWO, Suits.CLUBS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.HEARTS),
        Card(Ranks.KING, Suits.DIAMONDS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.ACE, Suits.SPADES)
    ]
    quad = _quad_draw(cards)
    if ! expect(quad, best_cards, desc="ERROR: match four-of-a-kind")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.KING, Suits.SPADES), Card(Ranks.KING, Suits.HEARTS)))
    board = Board(Card(Ranks.KING, Suits.CLUBS), Card(Ranks.ACE, Suits.SPADES), Card(Ranks.JACK, Suits.CLUBS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.KING, Suits.DIAMONDS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.QUAD, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score four-of-a-kind")
        failing += 1
    end

    return failing
end

function test_full_house()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.DIAMONDS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TWO, Suits.CLUBS),
        Card(Ranks.NINE, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.DIAMONDS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS)
    ]
    full_house = _full_draw(cards)
    if ! expect(full_house, best_cards, desc="ERROR: match full house")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.KING, Suits.SPADES), Card(Ranks.KING, Suits.CLUBS)))
    board = Board(Card(Ranks.KING, Suits.DIAMONDS), Card(Ranks.NINE, Suits.CLUBS), Card(Ranks.TEN, Suits.CLUBS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.TEN, Suits.HEARTS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.FULL, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score full house")
        failing += 1
    end
    return failing
end

function test_flush()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TWO, Suits.CLUBS),
        Card(Ranks.NINE, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TWO, Suits.CLUBS)
    ]
    flush = _flush_draw(cards)
    if ! expect(flush, best_cards, desc="ERROR: match flush")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.TWO, Suits.CLUBS), Card(Ranks.KING, Suits.CLUBS)))
    board = Board(Card(Ranks.KING, Suits.DIAMONDS), Card(Ranks.NINE, Suits.HEARTS), Card(Ranks.TEN, Suits.CLUBS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.FLUSH, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score flush")
        failing += 1
    end
    return failing
end

function test_straight()
    failing = 0
    cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.NINE, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.ACE, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
    ]
    straight = _straight_draw(cards)
    if ! expect(straight, best_cards, desc="ERROR: match straight")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.ACE, Suits.SPADES), Card(Ranks.KING, Suits.CLUBS)))
    board = Board(Card(Ranks.TEN, Suits.SPADES), Card(Ranks.NINE, Suits.HEARTS), Card(Ranks.TEN, Suits.SPADES), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.STRAIGHT, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score straight")
        failing += 1
    end
    return failing
end

function test_three_of_a_kind()
    failing = 0
    cards = [
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.TWO, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TEN, Suits.CLUBS),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
    ]
    trip = _trip_draw(cards)
    if ! expect(trip, best_cards, desc="ERROR: match three-of-a-kind")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.TEN, Suits.CLUBS), Card(Ranks.KING, Suits.CLUBS)))
    board = Board(Card(Ranks.TEN, Suits.SPADES), Card(Ranks.TWO, Suits.HEARTS), Card(Ranks.TEN, Suits.HEARTS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.TRIP, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score three-of-a-kind")
        failing += 1
    end
    return failing
end

function test_two_pair()
    failing = 0
    cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.TWO, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.KING, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.QUEEN, Suits.CLUBS),
    ]
    dubs = _dubs_draw(cards)
    if ! expect(dubs, best_cards, desc="ERROR: match two-pair")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.KING, Suits.SPADES), Card(Ranks.KING, Suits.CLUBS)))
    board = Board(Card(Ranks.TEN, Suits.SPADES), Card(Ranks.TWO, Suits.HEARTS), Card(Ranks.TEN, Suits.HEARTS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.DUBS, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score two-pair")
        failing += 1
    end
    return failing
end

function test_pair()
    failing = 0
    cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.FOUR, Suits.DIAMONDS),
        Card(Ranks.TWO, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.TEN, Suits.SPADES),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS)
    ]
    pair = _pair_draw(cards)
    if ! expect(pair, best_cards, desc="ERROR: match pair")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.KING, Suits.SPADES), Card(Ranks.FOUR, Suits.DIAMONDS)))
    board = Board(Card(Ranks.TEN, Suits.SPADES), Card(Ranks.TWO, Suits.HEARTS), Card(Ranks.TEN, Suits.HEARTS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.PAIR, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score pair")
        failing += 1
    end
    return failing
end

function test_high_card()
    failing = 0
    cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.SEVEN, Suits.HEARTS),
        Card(Ranks.FOUR, Suits.DIAMONDS),
        Card(Ranks.TWO, Suits.HEARTS)
    ]
    best_cards = [
        Card(Ranks.KING, Suits.SPADES),
        Card(Ranks.QUEEN, Suits.CLUBS),
        Card(Ranks.JACK, Suits.CLUBS),
        Card(Ranks.TEN, Suits.HEARTS),
        Card(Ranks.SEVEN, Suits.HEARTS)
    ]
    high = _high_draw(cards)
    if ! expect(high, best_cards, desc="ERROR: match high card")
        failing += 1
    end
    player = Player(0, Hole(0, Card(Ranks.KING, Suits.SPADES), Card(Ranks.FOUR, Suits.DIAMONDS)))
    board = Board(Card(Ranks.SEVEN, Suits.HEARTS), Card(Ranks.TWO, Suits.HEARTS), Card(Ranks.TEN, Suits.HEARTS), Card(Ranks.QUEEN, Suits.CLUBS), Card(Ranks.JACK, Suits.CLUBS))
    hand = score(player, board)
    best_hand = Hand(0, Rankings.HIGH, best_cards...)
    if ! expect(hand, best_hand, desc="ERROR: score high card")
        failing += 1
    end

    return failing
end

function test_compare_hands()
    failing = 0
    board = Board(Card(Ranks.KING, Suits.HEARTS), Card(Ranks.QUEEN, Suits.HEARTS), Card(Ranks.JACK, Suits.HEARTS), Card(Ranks.TEN, Suits.HEARTS), Card(Ranks.TEN, Suits.CLUBS))
    p1 = Player(1, Hole(1, Card(Ranks.ACE, Suits.HEARTS), Card(Ranks.TWO, Suits.CLUBS)))
    p2 = Player(2, Hole(2, Card(Ranks.NINE, Suits.HEARTS), Card(Ranks.TWO, Suits.DIAMONDS)))
    p3 = Player(3, Hole(3, Card(Ranks.TEN, Suits.DIAMONDS), Card(Ranks.TEN, Suits.SPADES)))
    p4 = Player(4, Hole(4, Card(Ranks.KING, Suits.CLUBS), Card(Ranks.KING, Suits.SPADES)))
    p5 = Player(5, Hole(5, Card(Ranks.TWO, Suits.HEARTS), Card(Ranks.THREE, Suits.CLUBS)))
    p6 = Player(6, Hole(6, Card(Ranks.ACE, Suits.SPADES), Card(Ranks.THREE, Suits.SPADES)))
    p7 = Player(7, Hole(7, Card(Ranks.TEN, Suits.SPADES), Card(Ranks.FOUR, Suits.SPADES)))
    p8 = Player(8, Hole(8, Card(Ranks.JACK, Suits.SPADES), Card(Ranks.FOUR, Suits.CLUBS)))
    p9 = Player(9, Hole(9, Card(Ranks.EIGHT, Suits.CLUBS), Card(Ranks.SEVEN, Suits.CLUBS)))
    p10 = Player(10, Hole(10, Card(Ranks.FIVE, Suits.CLUBS), Card(Ranks.SIX, Suits.SPADES)))
    p1_hand = score(p1, board)
    p2_hand = score(p2, board)
    p3_hand = score(p3, board)
    p4_hand = score(p4, board)
    p5_hand = score(p5, board)
    p6_hand = score(p6, board)
    p7_hand = score(p7, board)
    p8_hand = score(p8, board)
    p9_hand = score(p9, board)
    p10_hand = score(p10, board)
    begin
        best_hands = compare_hands(p1_hand, p2_hand, p3_hand, p4_hand, p5_hand, p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p1_hand], desc="ERROR: royal flush is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p2_hand, p3_hand, p4_hand, p5_hand, p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p2_hand], desc="ERROR: straight flush is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p3_hand, p4_hand, p5_hand, p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p3_hand], desc="ERROR: four-of-a-kind is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p4_hand, p5_hand, p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p4_hand], desc="ERROR: full-house is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p5_hand, p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p5_hand], desc="ERROR: flush is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p6_hand, p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p6_hand], desc="ERROR: straight is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p7_hand, p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p7_hand], desc="ERROR: three-of-a-kind is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p8_hand, p9_hand, p10_hand)
        if ! expect(best_hands, [p8_hand], desc="ERROR: two-pair is best hand")
            failing += 1
        end
    end
    begin
        best_hands = compare_hands(p9_hand, p10_hand)
        if ! expect(best_hands, [p9_hand, p10_hand], desc="ERROR: tie hands")
            failing += 1
        end
    end
    return failing
end

function expect(cards::Array{Card}, exp::Array{Card}; desc="")
    if length(cards) != length(exp)
        println(desc)
        return false
    end
    for i in 1:length(cards)
        if ! match(cards[i], exp[i])
            println(desc)
            return false
        end
    end
    return true
end

function expect(cards::Nothing, exp::Array{Card}; desc="")
    println(desc)
    return false
end

function expect(hand::Hand, exp::Hand; desc="")
    if hand.playerID != exp.playerID
        println(desc)
        return false
    end
    if hand.ranking != exp.ranking
        println(desc)
        return false
    end
    if ! match(hand.card1, exp.card1)
        println(desc)
        return false
    end
    if ! match(hand.card2, exp.card2)
        println(desc)
        return false
    end
    if ! match(hand.card3, exp.card3)
        println(desc)
        return false
    end
    if ! match(hand.card4, exp.card4)
        println(desc)
        return false
    end
    if ! match(hand.card5, exp.card5)
        println(desc)
        return false
    end
    return true
end

function expect(hand::Nothing, exp::Hand; desc="")
    println(desc)
    return false
end

function expect(best_hands::Array{Hand}, exp::Array{Hand}; desc="")
    if length(best_hands) != length(exp)
        println(desc)
        return false
    end
    for i in 1:length(best_hands)
        if best_hands[i].playerID != exp[i].playerID
            println(desc)
            return false
        end
    end
    return true
end

function match(c1::Card, c2::Card)
    return c1.rank == c2.rank && c1.suit == c2.suit
end

function run_tests()
    failing = 0
    failing += test_royal_flush()
    failing += test_straight_flush()
    failing += test_four_of_a_kind()
    failing += test_full_house()
    failing += test_flush()
    failing += test_straight()
    failing += test_three_of_a_kind()
    failing += test_two_pair()
    failing += test_pair()
    failing += test_high_card()
    failing += test_compare_hands()
    println("Done!")
    if failing == 0
        println("All tests passing!")
    else
        println("$(failing) test faililng!")
    end
end

run_tests()


abstract type ConstantApproach end;
struct FixConstant      <: ConstantApproach end;
struct DoNotFixConstant <: ConstantApproach end;

#"""
#    struct FESpaceWithConstantFixed{CS,<:ConstantApproach} <: SingleFieldFESpace
#      space::SingleFieldFESpace
#      constraint_style::Val{CS}
#      dof_to_fix::Int
#    end
#"""
struct FESpaceWithConstantFixed{CS,CA<:ConstantApproach} <: SingleFieldFESpace
  space::SingleFieldFESpace
  constraint_style::Val{CS}
  dof_to_fix::Int
  @doc """
      FESpaceWithConstantFixed(space::SingleFieldFESpace, fix_constant::Bool,
      dof_to_fix::Int=num_free_dofs(space))
  """
  function FESpaceWithConstantFixed(space::SingleFieldFESpace,
     fix_constant::Bool,
     dof_to_fix::Int=num_free_dofs(space))
    cs = constraint_style(space)
    CS = get_val_parameter(cs)
    if (fix_constant && num_dirichlet_dofs(space)==0)
      new{CS,FixConstant}(space,cs,dof_to_fix)
    else
      new{CS,DoNotFixConstant}(space,cs,dof_to_fix)
    end
  end
end

const FESpaceWithLastDofRemoved{CS} = FESpaceWithConstantFixed{CS,FixConstant}

@deprecate(FESpaceWithLastDofRemoved(space::SingleFieldFESpace),
           FESpaceWithConstantFixed(space,true))

get_constant_approach(::FESpaceWithConstantFixed{CS,CA}) where {CS,CA}=CA()

# Genuine functions
function num_free_dofs(f::FESpaceWithConstantFixed{CS,FixConstant}) where {CS}
  num_free_dofs(f.space)-1
end

function num_free_dofs(f::FESpaceWithConstantFixed{CS,DoNotFixConstant}) where {CS}
  num_free_dofs(f.space)
end

function zero_free_values(f::FESpaceWithConstantFixed)
  zeros(num_free_dofs(f))
end

function get_cell_dofs(f::FESpaceWithConstantFixed{CS,FixConstant}) where {CS}
  cell_dofs = get_cell_dofs(f.space)
  CellDofsWithDofFixed(cell_dofs,f.dof_to_fix)
end

function get_cell_dofs(f::FESpaceWithConstantFixed{CS,DoNotFixConstant}) where {CS}
  get_cell_dofs(f.space)
end

num_dirichlet_dofs(f::FESpaceWithConstantFixed{CS,FixConstant}) where {CS} = 1

num_dirichlet_dofs(f::FESpaceWithConstantFixed{CS,DoNotFixConstant}) where {CS} = 0

function zero_dirichlet_values(f::FESpaceWithConstantFixed)
  T = Float64 # TODO
  zeros(T,num_dirichlet_dofs(f))
end

num_dirichlet_tags(f::FESpaceWithConstantFixed{CS,FixConstant}) where {CS} = 1

num_dirichlet_tags(f::FESpaceWithConstantFixed{CS,DoNotFixConstant}) where {CS} = 0

get_dirichlet_dof_tag(f::FESpaceWithConstantFixed{CS,FixConstant}) where {CS} = Int8[1,]

get_dirichlet_dof_tag(f::FESpaceWithConstantFixed{CS,DoNotFixConstant}) where {CS} = Int8[]

function scatter_free_and_dirichlet_values(
  f::FESpaceWithConstantFixed{CS,FixConstant},fv,dv) where {CS}
  @assert length(dv) == 1
  _dv = similar(dv,eltype(dv),0)
  _fv = VectorWithEntryInserted(fv,f.dof_to_fix,dv[1])
  scatter_free_and_dirichlet_values(f.space,_fv,_dv)
end

function scatter_free_and_dirichlet_values(
  f::FESpaceWithConstantFixed{CS,DoNotFixConstant},fv,dv) where {CS}
  @assert length(dv) == 0
 scatter_free_and_dirichlet_values(f.space,fv,dv)
end

function gather_free_and_dirichlet_values(
  f::FESpaceWithConstantFixed{CS,FixConstant},cv) where {CS}
  _fv, _dv = gather_free_and_dirichlet_values(f.space,cv)
  @assert length(_dv) == 0
  fv = VectorWithEntryRemoved(_fv,f.dof_to_fix)
  dv = _fv[f.dof_to_fix:f.dof_to_fix]
  (fv, dv)
end

function gather_free_and_dirichlet_values(
  f::FESpaceWithConstantFixed{CS,DoNotFixConstant},cv) where {CS}
  gather_free_and_dirichlet_values(f.space,cv)
end

function gather_free_and_dirichlet_values!(
  fv,dv,f::FESpaceWithConstantFixed{CS,FixConstant},cv) where {CS}
  _fv, _dv = gather_free_and_dirichlet_values(f.space,cv)
  @assert length(_dv) == 0
  fv    .= VectorWithEntryRemoved(_fv,f.dof_to_fix)
  dv[1]  = _fv[f.dof_to_fix]
  (fv, dv)
end

function gather_free_and_dirichlet_values!(
  fv,dv,f::FESpaceWithConstantFixed{CS,DoNotFixConstant},cv) where {CS}
  gather_free_and_dirichlet_values(f.space,cv)
end

function TrialFESpace(f::FESpaceWithConstantFixed{CS,CA}) where {CS,CA}
  U = TrialFESpace(f.space)
  FESpaceWithConstantFixed(U,CA==FixConstant,f.dof_to_fix)
end

# Delegated functions

function get_cell_basis(f::FESpaceWithConstantFixed)
  get_cell_basis(f.space)
end

function get_cell_dof_basis(f::FESpaceWithConstantFixed)
  get_cell_dof_basis(f.space)
end

get_cell_axes(t::FESpaceWithConstantFixed)= get_cell_axes(t.space)

get_cell_axes_with_constraints(t::FESpaceWithConstantFixed)= get_cell_axes_with_constraints(t.space)

CellData.CellField(t::FESpaceWithConstantFixed,cell_vals) = CellField(t.space,cell_vals)

constraint_style(::Type{<:FESpaceWithConstantFixed{CS}}) where CS = Val{CS}()

# Helpers

struct CellDofsWithDofFixed{A<:AbstractArray} <: AbstractVector{Vector{Int}}
  cell_dofs::A
  dof_to_fix::Int
end

Base.size(a::CellDofsWithDofFixed) = (length(a.cell_dofs),)

Base.IndexStyle(::Type{<:CellDofsWithDofFixed}) = IndexLinear()

function Base.getindex(a::CellDofsWithDofFixed,i::Integer)
  cache = array_cache(a)
  getindex!(cache,a,i)
end

function array_cache(a::CellDofsWithDofFixed)
  @assert eltype(a.cell_dofs) == Vector{Int}
  b = testitem(a.cell_dofs)
  c = CachedArray(b)
  cache = array_cache(a.cell_dofs)
  (c, cache)
end

@inline function getindex!(d,a::CellDofsWithDofFixed,i::Integer)
  c, cache = d
  b = getindex!(cache,a.cell_dofs,i)
  setsize!(c,size(b))
  r = c.array
  for j in 1:length(b)
    bj = b[j]
    if bj == a.dof_to_fix
      rj = -1
    elseif bj < a.dof_to_fix
      rj = bj
    else
      rj = bj-1
    end
    r[j] = rj
  end
  r
end


# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

module FileFormats

import MathOptInterface
const MOI = MathOptInterface

import CodecBzip2
import CodecZlib

include("utils.jl")

include("CBF/CBF.jl")
include("LP/LP.jl")
include("MOF/MOF.jl")
include("MPS/MPS.jl")
include("NL/NL.jl")
include("SDPA/SDPA.jl")

"""
    FileFormat

List of accepted export formats.

- `FORMAT_AUTOMATIC`: try to detect the file format based on the file name
- `FORMAT_CBF`: the Conic Benchmark format
- `FORMAT_LP`: the LP file format
- `FORMAT_MOF`: the MathOptFormat file format
- `FORMAT_MPS`: the MPS file format
- `FORMAT_NL`: the AMPL .nl file format
- `FORMAT_REW`: the .rew file format, which is MPS with generic names
- `FORMAT_SDPA`: the SemiDefinite Programming Algorithm format
"""
@enum(
    FileFormat,
    FORMAT_AUTOMATIC,
    FORMAT_CBF,
    FORMAT_LP,
    FORMAT_MOF,
    FORMAT_MPS,
    FORMAT_NL,
    FORMAT_REW,
    FORMAT_SDPA,
)

"""
    Model(
        ;
        format::FileFormat = FORMAT_AUTOMATIC,
        filename::Union{Nothing, String} = nothing,
        kwargs...
    )

Return model corresponding to the `FileFormat` `format`, or, if
`format == FORMAT_AUTOMATIC`, guess the format from `filename`.

The `filename` argument is only needed if `format == FORMAT_AUTOMATIC`.

`kwargs` are passed to the underlying model constructor.
"""
function Model(;
    format::FileFormat = FORMAT_AUTOMATIC,
    filename::Union{Nothing,String} = nothing,
    kwargs...,
)
    if format == FORMAT_CBF
        return CBF.Model(; kwargs...)
    elseif format == FORMAT_LP
        return LP.Model(; kwargs...)
    elseif format == FORMAT_MOF
        return MOF.Model(; kwargs...)
    elseif format == FORMAT_MPS
        return MPS.Model(; kwargs...)
    elseif format == FORMAT_NL
        return NL.Model(; kwargs...)
    elseif format == FORMAT_REW
        return MPS.Model(; generic_names = true, kwargs...)
    elseif format == FORMAT_SDPA
        return SDPA.Model(; kwargs...)
    else
        @assert format == FORMAT_AUTOMATIC
        if filename === nothing
            error("When `format==FORMAT_AUTOMATIC` you must pass a `filename`.")
        end
        for (ext, model) in [
            (".cbf", CBF.Model),
            (".lp", LP.Model),
            (".mof.json", MOF.Model),
            (".mps", MPS.Model),
            (
                ".rew",
                (; kwargs...) -> MPS.Model(; generic_names = true, kwargs...),
            ),
            (".nl", NL.Model),
            (".dat-s", SDPA.Model),
            (".sdpa", SDPA.Model),
        ]
            if endswith(filename, ext) || occursin("$(ext).", filename)
                return model(; kwargs...)
            end
        end
        error("Unable to automatically detect format of $(filename).")
    end
end

const MATH_OPT_FORMATS =
    Union{CBF.Model,LP.Model,MOF.Model,MPS.Model,NL.Model,SDPA.Model}

function MOI.write_to_file(model::MATH_OPT_FORMATS, filename::String)
    compressed_open(filename, "w", AutomaticCompression()) do io
        return write(io, model)
    end
end

function MOI.read_from_file(model::MATH_OPT_FORMATS, filename::String)
    compressed_open(filename, "r", AutomaticCompression()) do io
        return read!(io, model)
    end
end

end


export NEATIndiv, NEATIndividual, distance

abstract type NEATIndiv <: Cambrian.Individual end

mutable struct NEATIndividual <: NEATIndiv
    genes::Dict
    fitness::Array{Float64}
    neuron_pos::Array{Float64}
    network::Network
    activ_functions::Dict
end

function NEATIndiv(cfg::Dict)
    NEATIndividual(cfg)
end

function NEATIndividual(cfg::Dict)
    n_in = cfg["n_in"]
    n_out = cfg["n_out"]
    neuron_pos::Array{Float64}=[]

    # Neuron positions: Input and output
    neuron_pos = -n_in:n_out

    # Neurons activation functions
    activ_functions = Dict()
    for i in neuron_pos
        activ_functions[i]=rand(cfg["activation_functions"])
    end

    # Add genes
    genes=Dict()
    if cfg["start_fully_connected"]
        for i in 1:n_in
            for j in 1:n_out
                inno = i * n_out + j
                genes[inno] = Gene(-1.0 * i, 1.0 * j, inno)
            end
        end
        cfg["innovation_max"] = maximum([cfg["innovation_max"], n_in * n_out])
    end

    sort!(neuron_pos)

    fitness = -Inf .* ones(cfg["d_fitness"])

    network = Network(n_in, n_out, Dict())

    NEATIndividual(genes, fitness, neuron_pos, network, activ_functions)
end

function NEATIndiv(ind::NEATIndiv)
    deepcopy(ind)
end

function distance(i1::NEATIndiv, i2::NEATIndiv, cfg::Dict)
    # N = maximum([length(g1), length(g2)])   # Paper version
    N = 1 # Implementation version

    # Check for empty genomes
    if length(i1.genes)==0
        return cfg["excess_coef"] * length(i2.genes) / N
    end
    if length(i2.genes)==0
        return cfg["excess_coef"] * length(i1.genes) / N
    end

    # Gene innovation numbers in each individual
    g1 = keys(i1.genes)
    g2 = keys(i2.genes)

    excess = 0.
    disjoint = 0.
    weight_diffs = []
    for i in 1:cfg["innovation_max"]
        if i in g1
            if i in g2
                # In both
                d = abs(i1.genes[i].weight - i2.genes[i].weight)
                push!(weight_diffs, d)
            else # Only in g1
                if i > maximum(g2)
                    excess += 1
                else
                    disjoint += 1
                end
            end
        else
            if i in g2 # Only in g2
                if i > maximum(g1)
                    excess += 1
                else
                    disjoint += 1
                end
            end
        end
    end

    dist = cfg["excess_coef"] * excess / N
    dist += cfg["disjoint_coef"] * disjoint / N

    if length(weight_diffs) > 0
        dist += cfg["weight_diff_coef"] * sum(weight_diffs) / length(weight_diffs)
    end

    dist
end

function parse_fitness(s::String)
	if s == "-Inf"
		-Inf
	else
		parse(Float64, s)
	end
end

function NEATIndiv(cfg::Dict, s::String)
	n_in = cfg["n_in"]
	n_out = cfg["n_out"]

	dict = JSON.parse(s)

	fitness=parse_fitness.(dict["Fitness"])

	# Genes
	genes = Dict()
	neuron_pos::Array{Float64}= 1.0 .* (-n_in:n_out)
	for (k, v) in pairs(dict["Genes"])
		g = Gene(v)
		genes[parse(Float64, k)] = g
		if !(g.origin in neuron_pos)
			push!(neuron_pos, g.origin)
		end
		# Add out_neuron if not already in
		if !(g.destination in neuron_pos)
			push!(neuron_pos, g.destination)
		end
	end
	sort!(neuron_pos)

	# Activation functions
	activ_functions = Dict()
	for (k, v) in pairs(dict["Activ_func"])
		activ_functions[parse(Float64, k)] = activ_dict[v]
	end

	network = Network(n_in, n_out, Dict())

	if dict["Type"]=="NEAT"
		ind = NEATIndividual(genes, fitness, neuron_pos, network, activ_functions)
	elseif dict["Type"]=="HyperNEAT"
		grid_net = GridNetwork(cfg)
		ind = HyperNEATIndividual(genes, fitness, neuron_pos, network, activ_functions, grid_net)
	end
end

function NEATIndividual(cfg::Dict, s::String)
	NEATIndiv(cfg, s)
end


# SPDX-FileCopyrightText: 2021 Johannes Hampp <johannes.hampp@zeu.jlug.de>
#
# SPDX-License-Identifier: MIT

# Activate julia environment
using Pkg
Pkg.activate("./workflow/envs")
using GlobalEnergyGIS

region = snakemake.wildcards["region"]
ssp = snakemake.wildcards["ssp"]
rcp = snakemake.wildcards["rcp"]
ssp_year = parse(Int64, snakemake.wildcards["ssp_year"])
era_year = parse(Int64, snakemake.wildcards["era_year"])

ssp_rcp_scenario = replace("$ssp-$rcp", "." => "") # GEGIS expects RCP without "."

# Use GEGIS ML approach to generated synthetic demand
predictdemand(gisregion=region,
        sspscenario=ssp_rcp_scenario,
        sspyear=ssp_year,
        era_year=era_year)

# Make sure output directory exists
mkpath(dirname(snakemake.output[1]))

# Move the output file from the default output location to a better location
mv("./resources/gegis/output/SyntheticDemand_" * region
        * "_" * ssp_rcp_scenario
        * "-" * string(ssp_year)
        * "_" * string(era_year)
        * ".jld",
   snakemake.output[1])