"""
This function does nothing if the product is not of Capacity type.
"""
function update_rec_revenues!(product::T,
                                price_years::NamedTuple{(:start_year, :end_year),
                                    Tuple{Int64, Int64}},
                                rec_revenues::Vector{Float64},
                                rec_prices::AxisArrays.AxisArray{Float64, 1},
                                energy_production::AxisArrays.AxisArray{Float64, 1}) where T <: Product
    return rec_revenues
end

"""
This function calculates expected rec revenues for each project.
Returns REC revenues
"""
function update_rec_revenues!(product::REC,
                                price_years::NamedTuple{(:start_year, :end_year),
                                    Tuple{Int64, Int64}},
                                rec_revenues::Vector{Float64},
                                rec_prices::AxisArrays.AxisArray{Float64, 1},
                                energy_production::AxisArrays.AxisArray{Float64, 1})
    rec_revenues += rec_prices[price_years[:start_year]:price_years[:end_year]] .*
    energy_production[1:end]
    return rec_revenues
end

"""
This function updates the expected profit array of the project
if the product is a REC product.
Returns nothing.
"""
function update_profit!(project::P,
                        scenario_name::String,
                        product::REC,
                        operating_profit::AxisArrays.AxisArray{Float64, 2},
                        capacity_revenues::Vector{Float64},
                        capacity_forward_years::Int64,
                        rec_revenues::Vector{Float64},
                        price_years::NamedTuple{(:start_year, :end_year),
                                     Tuple{Int64, Int64}},
                        update_years::NamedTuple{(:start_year, :end_year),
                                      Tuple{Int64, Int64}},
                        iteration_year::Int64) where P <: Project{<: BuildPhase}

    for i in 1:length(rec_revenues)
            set_scenario_profit!(get_finance_data(project),
                        scenario_name,
                        get_name(product),
                        iteration_year,
                        update_years[:start_year] + i - 1,
                        rec_revenues[i])
    end

    return
end


mutable struct DrakeVisualizerSink <: OdeIntegrators.OdeResultsSink
    vis::Visualizer
    min_wall_Δt::Float64
    last_update_wall_time::Float64

    function DrakeVisualizerSink(vis::Visualizer; max_fps::Float64 = 60.)
        new(vis, 1 / max_fps, -Inf)
    end
end

function OdeIntegrators.initialize(sink::DrakeVisualizerSink, t, state)
    sink.last_update_wall_time = -Inf
    OdeIntegrators.process(sink, t, state)
end

function OdeIntegrators.process(sink::DrakeVisualizerSink, t, state)
    wall_Δt = time() - sink.last_update_wall_time
    if wall_Δt > sink.min_wall_Δt
        settransform!(sink.vis, state)
        sink.last_update_wall_time = time()
    end
    nothing
end


using Test
using AtomicLevels
# ^^^ -- to make it possible to run the test file separately

module ATSPParser
    using Test
    using AtomicLevels
    import AtomicLevels: CSF, csfs
    include("atsp/csfparser.jl")

    compare_with_atsp(f, csfout) = @testset "ATSP CSFs: $(csfout)" begin
        atsp_csfs = parse_csf(joinpath(@__DIR__, "atsp", csfout))
        atlev_csfs = f()
        @test length(atlev_csfs) == length(atsp_csfs)
        for atlev_csf in atlev_csfs
            @test atlev_csf in atsp_csfs
        end
    end

    export compare_with_atsp
end

using .ATSPParser

@testset "CSFs" begin
    @testset "Construction" begin
        csf = CSF(rc"1s2 2p- 2p", [IntermediateTerm(0,Seniority(0)), IntermediateTerm(1//2,Seniority(1)), IntermediateTerm(3//2,Seniority(1))], [0, 1//2, 2])
        @test csf isa CSF{RelativisticOrbital{Int},HalfInt}
        @test csf isa RelativisticCSF
        @test csf == csf
        @test csf != CSF(rc"1s2 2p- 2p", [IntermediateTerm(0,Seniority(0)), IntermediateTerm(1//2,Seniority(1)), IntermediateTerm(3//2,Seniority(1))], [0, 1//2, 1])
        @test num_electrons(csf) == 4

        @test CSF(c"1s2 2p", [IntermediateTerm(T"1S",Seniority(0)), IntermediateTerm(T"2Po", Seniority(1))], [T"1S", T"2Po"]) isa NonRelativisticCSF
    end

    @testset "CSF list generation" begin
        @testset "ls coupling" begin
            csf_1s2 = CSF(c"1s2", [IntermediateTerm(T"1S",Seniority(0))],[T"1S"])
            @test csfs(c"1s2") == [csf_1s2]
            @test string(csf_1s2) == "1s²(₀¹S|¹S)+"

            csfs_1s_kp = [CSF(c"1s kp",
                              [IntermediateTerm(T"2S",Seniority(1)),IntermediateTerm(T"2Po",Seniority(1))],
                              [T"2S", T"1Po"]),
                          CSF(c"1s kp",
                              [IntermediateTerm(T"2S",Seniority(1)),IntermediateTerm(T"2Po",Seniority(1))],
                              [T"2S", T"3Po"])]
            @test csfs(c"1s kp") == csfs_1s_kp

            #= zgenconf input
              OI
                1s  2s
                 2   4  -1   2   0   2   n_orbitals,  n_electrons, parity, n_ref, k_min, k_max
                2p  3d
                 0   0
                 6  10
            =#
            compare_with_atsp("1s2c_2s2c_2p1_3_3d3_1.csfs") do
                csfs(c"1s2c 2s2c" ⊗ [c"2p1 3d3", c"2p3 3d1"])
            end
        end
        @testset "jj coupling" begin
            # These are not real tests, they only make sure that the
            # generation does not crash.
            sort(csfs([rc"3s 3p2",rc"3s 3p- 3p"]))
            csfs(rc"[Kr] 5s2 5p-2 5p3 6s")
            csfs(rc"1s kp")

            @test string(CSF(rc"[Kr] 5s2 5p- 5p4 kd",
                             [IntermediateTerm(0//1, Seniority(0)), IntermediateTerm(1//2, Seniority(1)), IntermediateTerm(0//1, Seniority(0)), IntermediateTerm(5//2, Seniority(1))],
                             [0//1, 1//2, 1//2, 3//1])) ==
                "[Kr]ᶜ 5s²(₀0|0) 5p-(₁1/2|1/2) 5p⁴(₀0|1/2) kd(₁5/2|3)-"
        end
    end
end


srand(1)

# TODO: Random links gets set up properly on instantiation.

context("landscape.jl") do
  context("NKLandscape") do
    l = NK.NKLandscape(4, 1)
    @fact length(l.links) --> l.n
    @fact length(l.contribs) --> l.n

    context("when near=true") do
      l = NK.NKLandscape(4, 2; near=true)
      @fact l.links[1] --> 0b1011
      @fact l.links[2] --> 0b0111
      @fact l.links[3] --> 0b1110
      @fact l.links[4] --> 0b1101
    end
  end

  context("NKqLandscape") do
    l = NK.NKqLandscape(4, 1, 2)
    @fact length(l.links) --> l.n
    @fact length(l.contribs) --> l.n

    context("when near=true") do
      l = NK.NKqLandscape(4, 2, 2; near=true)
      @fact l.links[1] --> 0b1011
      @fact l.links[2] --> 0b0111
      @fact l.links[3] --> 0b1110
      @fact l.links[4] --> 0b1101
    end
  end

  context("NKpLandscape") do
    l = NK.NKpLandscape(4, 1, 0.90)
    @fact length(l.links) --> l.n
    @fact length(l.contribs) --> l.n

    context("when near=true") do
      l = NK.NKpLandscape(4, 2, 0.90; near=true)
      @fact l.links[1] --> 0b1011
      @fact l.links[2] --> 0b0111
      @fact l.links[3] --> 0b1110
      @fact l.links[4] --> 0b1101
    end
  end
end


using AutomotiveDrivingModels
using AutoDrivers.GaussianMixtureRegressionDrivers

########################################
#            CollisionRate             #
########################################

type CollisionRate <: TraceMetricExtractor
    running_sum::Float64
    n_obs::Int

    CollisionRate(running_sum::Float64=0.0, n_obs::Int=0) = new(running_sum, n_obs)
end
Base.Symbol(::CollisionRate) = :collisionrate
get_score(m::CollisionRate) = m.running_sum / m.n_obs
function reset!(metric::CollisionRate)
    metric.running_sum = 0.0
    metric.n_obs = 0
    metric
end
function extract_collision_rate(rec::SceneRecord, roadway::Roadway, egoid::Int)
    collided = 0.0
    for pastframe in 1 - length(rec) : 0
        vehicle_index = get_index_of_first_vehicle_with_id(rec, egoid, pastframe)
        collide = convert(Float64, get(IS_COLLIDING, rec, roadway, vehicle_index, pastframe))
        collided = min(collide + collided, 1.0) # only count if first collision
    end
    collided
end
function extract!(
    metric::CollisionRate,
    rec_orig::SceneRecord, # the records are exactly as long as the simulation (ie, contain everything)
    rec_sim::SceneRecord,
    roadway::Roadway,
    egoid::Int;
    real::Bool=false,
    )
    if real
        metric.running_sum += extract_collision_rate(rec_orig, roadway, egoid)
    else
        metric.running_sum += extract_collision_rate(rec_sim, roadway, egoid)
    end
    metric.n_obs += 1

    metric
end

########################################
#            OffRoadRate               #
########################################

type OffRoadRate <: TraceMetricExtractor
    running_sum::Float64
    n_obs::Int

    OffRoadRate(running_sum::Float64=0.0, n_obs::Int=0) = new(running_sum, n_obs)
end
Base.Symbol(::OffRoadRate) = :offroadrate
get_score(m::OffRoadRate) = m.running_sum / m.n_obs
function reset!(metric::OffRoadRate)
    metric.running_sum = 0.0
    metric.n_obs = 0
    metric
end
function extract_off_road_rate(rec::SceneRecord, roadway::Roadway, egoid::Int)
    num_off_road = 0.0
    for pastframe in 1 - length(rec) : 0
        vehicle_index = get_index_of_first_vehicle_with_id(rec, egoid, pastframe)
        off_road = (convert(Float64, get(MARKERDIST_LEFT, rec, roadway, vehicle_index, pastframe)) < -1.0) +
                    (convert(Float64, get(MARKERDIST_RIGHT, rec, roadway, vehicle_index, pastframe)) < -1.0)
        num_off_road += off_road
    end
    num_off_road
end
function extract!(
    metric::OffRoadRate,
    rec_orig::SceneRecord, # the records are exactly as long as the simulation (ie, contain everything)
    rec_sim::SceneRecord,
    roadway::Roadway,
    egoid::Int;
    real::Bool=false,
    )
    if real
        metric.running_sum += extract_off_road_rate(rec_orig, roadway, egoid)
    else
        metric.running_sum += extract_off_road_rate(rec_sim, roadway, egoid)
    end
    metric.n_obs += 1

    metric
end

########################################
#            LaneChangeRate            #
########################################

type LaneChangeRate <: TraceMetricExtractor
    running_sum::Float64
    n_obs::Int

    LaneChangeRate(running_sum::Float64=0.0, n_obs::Int=0) = new(running_sum, n_obs)
end
Base.Symbol(::LaneChangeRate) = :lanechangerate
get_score(m::LaneChangeRate) = m.running_sum / m.n_obs
function reset!(metric::LaneChangeRate)
    metric.running_sum = 0.0
    metric.n_obs = 0
    metric
end
function extract_lane_change_rate(rec::SceneRecord, roadway::Roadway, egoid::Int)
    num_lane_change = 0.0
    vehicle_index = get_index_of_first_vehicle_with_id(rec, egoid, 1 - length(rec))
    veh_ego = rec[vehicle_index, 1 - length(rec)]
    prev_lane = roadway[veh_ego.state.posF.roadind.tag] # Find initial lane
    for pastframe in 2 - length(rec) : 0
        vehicle_index = get_index_of_first_vehicle_with_id(rec, egoid, pastframe)
        veh_ego = rec[vehicle_index, pastframe]
        lane = roadway[veh_ego.state.posF.roadind.tag]
        num_lane_change += (lane != prev_lane)
        prev_lane = lane
    end
    num_lane_change
end
function extract!(
    metric::LaneChangeRate,
    rec_orig::SceneRecord, # the records are exactly as long as the simulation (ie, contain everything)
    rec_sim::SceneRecord,
    roadway::Roadway,
    egoid::Int;
    real::Bool=false,
    )
    if real
        metric.running_sum += extract_lane_change_rate(rec_orig, roadway, egoid)
    else
        metric.running_sum += extract_lane_change_rate(rec_sim, roadway, egoid)
    end
    metric.n_obs += 1

    metric
end

########################################
#            HardBrakeRate             #
########################################

type HardBrakeRate <: TraceMetricExtractor
    running_sum::Float64
    n_obs::Int

    HardBrakeRate(running_sum::Float64=0.0, n_obs::Int=0) = new(running_sum, n_obs)
end
Base.Symbol(::HardBrakeRate) = :hardbrakerate
get_score(m::HardBrakeRate) = m.running_sum / m.n_obs
function reset!(metric::HardBrakeRate)
    metric.running_sum = 0.0
    metric.n_obs = 0
    metric
end
function extract_hard_brake_rate(rec::SceneRecord, roadway::Roadway, egoid::Int)
    num_hard_brake = 0.0
    for pastframe in 2 - length(rec) : 0
        vehicle_index = get_index_of_first_vehicle_with_id(rec, egoid, pastframe)
        acc_ego = convert(Float64, get(ACC, rec, roadway, vehicle_index, pastframe))
        num_hard_brake += (acc_ego < -3.0)
    end
    num_hard_brake
end
function extract!(
    metric::HardBrakeRate,
    rec_orig::SceneRecord, # the records are exactly as long as the simulation (ie, contain everything)
    rec_sim::SceneRecord,
    roadway::Roadway,
    egoid::Int;
    real::Bool=false,
    )
    if real
        metric.running_sum += extract_hard_brake_rate(rec_orig, roadway, egoid)
    else
        metric.running_sum += extract_hard_brake_rate(rec_sim, roadway, egoid)
    end
    metric.n_obs += 1

    metric
end

########################################
#        RWSE Global Position          #
########################################

type RWSEPosG <: TraceMetricExtractor
    horizon::Float64 # [s]
    running_sum::Float64
    n_obs::Int
end
RWSEPosG(horizon::Float64) = RWSEPosG(horizon, 0.0, 0)

Base.Symbol(m::RWSEPosG) = Symbol(@sprintf("RWSE_posG_%d_%02d", floor(Int, m.horizon), floor(Int, 100*rem(m.horizon, 1.0))))
get_score(m::RWSEPosG) = sqrt(m.running_sum / m.n_obs)
function reset!(metric::RWSEPosG)
    metric.running_sum = 0.0
    metric.n_obs = 0
    metric
end
function extract!(
    metric::RWSEPosG,
    rec_orig::SceneRecord, # the records are exactly as long as the simulation (ie, contain everything)
    rec_sim::SceneRecord,
    roadway::Roadway,
    egoid::Int,
    )

    # TODO: how to handle missing values???

    pastframe = 1-length(rec_orig) + clamp(round(Int, metric.horizon/rec_orig.timestep), 0, length(rec_orig)-1)

    # pull true value
    vehicle_index = get_index_of_first_vehicle_with_id(rec_orig, egoid, pastframe)
    veh_ego = rec_orig[vehicle_index, pastframe]
    x_true = veh_ego.state.posG.x
    y_true = veh_ego.state.posG.y

    # pull sim value
    vehicle_index = get_index_of_first_vehicle_with_id(rec_sim, egoid, pastframe)
    veh_ego = rec_sim[vehicle_index, pastframe]
    x_montecarlo = veh_ego.state.posG.x
    y_montecarlo = veh_ego.state.posG.y

    d = (x_true - x_montecarlo)^2 + (y_true - y_montecarlo)^2

    metric.running_sum += d
    metric.n_obs += 1

    metric
end

# Check for outliers, which seem to  occur with controller rollouts
function check_outliers(rec_orig::SceneRecord, rec_sim::SceneRecord, egoid::Int)
    pastframe = 1-length(rec_orig) + clamp(round(Int, 5.0/rec_orig.timestep), 0, length(rec_orig)-1)

    # pull true value
    vehicle_index = get_index_of_first_vehicle_with_id(rec_orig, egoid, pastframe)
    veh_ego = rec_orig[vehicle_index, pastframe]
    x_true = veh_ego.state.posG.x
    y_true = veh_ego.state.posG.y

    # pull sim value
    vehicle_index = get_index_of_first_vehicle_with_id(rec_sim, egoid, pastframe)
    veh_ego = rec_sim[vehicle_index, pastframe]
    x_montecarlo = veh_ego.state.posG.x
    y_montecarlo = veh_ego.state.posG.y

    d = sqrt((x_true - x_montecarlo)^2 + (y_true - y_montecarlo)^2)
    return (d >= 50.0)
end

function rollout!(
    rec::SceneRecord,
    model::DriverModel,
    egoid::Int,
    trajdata::Trajdata,
    time_start::Float64,
    time_end::Float64,
    simparams::Auto2D.SimParams;
    prime_history::Int = 0,
    gru_type::Bool=true,
    )

    # Initialize values
    Δt = rec.timestep
    simstate = simparams.simstates[1]

    # clear rec and make first observations
    #println("fieldnames(model.net)=", fieldnames(model.net))
    #if Symbol("gru") in fieldnames(model.net)
    if gru_type
        #println("rollout::gru!!")
        model.net[:gru].h_prev = zeros(length(model.net[:gru].h_prev))
    end
    empty!(simstate.rec)
    empty!(rec)
    reset_hidden_state!(model)
    for i in 1 : prime_history
        t = time_start - Δt * (prime_history - i + 1)
        update!(simstate.rec, get!(simstate.scene, trajdata, t))
        AutomotiveDrivingModels.observe!(model, simparams, simstate.scene, trajdata.roadway, simstate.egoid)
    end

    # empty playback reactive
    empty!(simstate.playback_reactive_active_vehicle_ids)

    # Run start time step
    t = time_start
    update!(simstate.rec, get!(simstate.scene, trajdata, t))
    update!(model.rec, simstate.scene)
    AutomotiveDrivingModels.observe!(model, simparams, simstate.scene, trajdata.roadway, simstate.egoid)

    # run simulation
    while t < time_end

        # Find action and step forward

        ego_action = rand(model)
        a = clamp(ego_action.a, -5.0, 3.0)
        ω = clamp(ego_action.ω, -0.1, 0.1)
        Auto2D.step(simparams, [a, ω])

        # update record
        AutomotiveDrivingModels.observe!(model, simparams, simstate.scene, trajdata.roadway, simstate.egoid)
        update!(rec, simstate.scene)

        # update time
        t += Δt
    end
    rec
end

function simulate!(
    rec::SceneRecord,
    model::DriverModel,
    egoid::Int,
    trajdata::Trajdata,
    time_start::Float64,
    time_end::Float64,
    simparams::Auto2D.SimParams;
    prime_history::Int = 0,
    )

    # Initialize values
    Δt = rec.timestep
    simstate = simparams.simstates[1]

    # clear rec and make first observations
    empty!(simstate.rec)
    empty!(rec)
    reset_hidden_state!(model)
    for i in 1 : prime_history
        t = time_start - Δt * (prime_history - i + 1)
        update!(simstate.rec, get!(simstate.scene, trajdata, t))
        AutomotiveDrivingModels.observe!(model, simstate.scene, trajdata.roadway, egoid)
    end

    # empty playback reactive
    empty!(simstate.playback_reactive_active_vehicle_ids)

    # Run start time step
    t = time_start
    update!(simstate.rec, get!(simstate.scene, trajdata, t))

    # If controller, set desired speed to be inital speed
    veh_index = get_index_of_first_vehicle_with_id(simstate.scene, egoid)
    if simparams.ego_action_type == LatLonAccel
        set_desired_speed!(model, simstate.scene[veh_index].state.v)
    end
    AutomotiveDrivingModels.observe!(model, simstate.scene, trajdata.roadway, egoid)

    # run simulation
    while t < time_end

        # Find action and step forward
        ego_action = rand(model)
        veh_index = get_index_of_first_vehicle_with_id(simstate.scene, egoid)
        if simstate.scene[veh_index].state.v < 0.0 && (simparams.ego_action_type == LatLonAccel)
            ego_action = LatLonAccel(ego_action.a_lat, max(ego_action.a_lon, 0.0))
        end
        if simparams.ego_action_type == AccelTurnrate
            Auto2D.step(simparams, [ego_action.a, ego_action.ω])
        else
            Auto2D.step(simparams, [ego_action.a_lat, a_lon])
        end
        AutomotiveDrivingModels.observe!(model, simstate.scene, trajdata.roadway, egoid)

        # update record
        update!(rec, simstate.scene)

        # update time
        t += Δt
    end
    rec
end

# Initialize values for simstate
function reset_simstate!(simstate::Auto2D.SimState, seg::TrajdataSegment)
    simstate.egoid = seg.egoid
    simstate.trajdata_index = seg.trajdata_index
    simstate.frame = seg.frame_lo
end

########################################
#        Calculate Metric Scores       #
########################################

function calc_metrics!(
    metrics_df::DataFrame,
    model::DriverModel,
    metrics::Vector{TraceMetricExtractor},
    simparams::Auto2D.SimParams,
    foldset_seg_test::FoldSet; # should match the test segments in evaldata
    n_simulations_per_trace::Int = 10,
    row::Int = foldset_seg_test.fold, # row in metrics_df to write to
    prime_history::Int = 0,
    calc_logl::Bool = true,
    gru_type::Bool = true,
    max_loop::Int = 1000,
    )
    # reset metrics
    for metric in metrics
        try
            reset!(metric)
            #println("reset!(metric)",metric)
        catch
            AutomotiveDrivingModels.reset!(metric)
            #println("AutomotiveDrivingModels.reset!(metric)",metric)
        end
    end

    logl = 0.0
    n_traces = 0

    # Set correct action type
    if Symbol("a") in fieldnames(rand(model))
        simparams.ego_action_type = AccelTurnrate
        #println(" ego_action_type=AccelTurnrate")
    else
        simparams.ego_action_type = LatLonAccel
        println(" ego_action_type=LatLonAccel")
    end

    # simulate traces and perform online metric extraction
    scene = Scene()
    for seg_index in foldset_seg_test
        if (n_traces >= max_loop)
            break
        end
        println(now(), " ", seg_index, "/", max_loop)
        seg = simparams.segments[seg_index]
        trajdata = simparams.trajdatas[seg.trajdata_index]

        rec_orig = pull_record(seg, trajdata, prime_history) # TODO - make efficient
        rec_sim = deepcopy(rec_orig)

        time_start = get_time(trajdata, seg.frame_lo)
        time_end = get_time(trajdata, seg.frame_hi)
        n_traces += 1

        for sim_index in 1 : n_simulations_per_trace
            #debug = open("debug.log", "a"); print(debug, " ", sim_index, "/", n_simulations_per_trace);  close(debug)
            print(" ", sim_index, "/", n_simulations_per_trace)
            reset_simstate!(simparams.simstates[1], seg)

            #println("fieldnames=",fieldnames(model))
            if Symbol("net") in fieldnames(model)
                #debug = open("debug.log", "a"); println(debug, "rollout!()");  close(debug)
                rollout!(rec_sim, model, seg.egoid, trajdata,
                          time_start, time_end, simparams, prime_history=prime_history, gru_type=gru_type)
                #println("rollout!()")
            else
                #debug = open("debug.log", "a"); println(debug, "simulate!()");  close(debug)
                simulate!(rec_sim, model, seg.egoid, trajdata,
                          time_start, time_end, simparams, prime_history=prime_history)
                println("simulate!()")
            end

            for metric in metrics
                if !check_outliers(rec_orig, rec_sim, seg.egoid)
                    try
                        extract!(metric, rec_orig, rec_sim, trajdata.roadway, seg.egoid)
                        #debug = open("debug.log", "a"); println(debug, "extract!()");  close(debug)
                    catch
                        AutomotiveDrivingModels.extract!(metric, rec_orig, rec_sim, trajdata.roadway, seg.egoid)
                        #debug = open("debug.log", "a"); println(debug, "AutomotiveDrivingModels.extract!()");  close(debug)
                    end
                end
            end
        end
        println(" ")
    end

    # compute metric scores
    for metric in metrics
        try
            metrics_df[row, Symbol(metric)] = get_score(metric)
        catch
            metrics_df[row, Symbol(metric)] = AutomotiveDrivingModels.get_score(metric)
        end
    end
    metrics_df[row, :time] = string(now())
    metrics_df[row, :logl] = logl/n_traces

    metrics_df
end


using Fjage

export UnetSource

const RxBasebandSignalNtf = MessageClass(@__MODULE__, "org.arl.unet.bb.RxBasebandSignalNtf")

struct UnetSource
  gw::Gateway
  bb::AgentID
  fs::Float32
  blksize::Int
  nchannels::Int
  streaming::Ref{Bool}
end

function UnetSource(host="127.0.0.1", port=1100; blksize=4096, bb=nothing)
  Fjage.registermessages()
  gw = Gateway(host, port)
  try
    if bb === nothing
      bb = agentforservice(gw, "org.arl.unet.Services.BASEBAND")
    else
      bb = agent(gw, bb)
    end
    subscribe(gw, topic(bb))
    blksize === nothing || (bb.pbsblk = blksize)
    blksize = bb.pbsblk
    bb.pbscnt = 1
    ntf = receive(gw, RxBasebandSignalNtf, 5000)
    if ntf === nothing
      bb.pbscnt = 0
      throw(ErrorException("No data from host"))
    end
    UnetSource(gw, bb, Float32(ntf.fs), blksize, ntf.channels, Ref(false))
  catch ex
    close(gw)
    rethrow(ex)
  end
end

connect(::UnetSource) = true

function Base.close(src::UnetSource)
  stopinputstream(src)
  close(src.gw)
end

inputframerate(src::UnetSource) = src.fs
inputblocksize(src::UnetSource) = src.blksize
inputchannels(src::UnetSource) = src.nchannels

function startinputstream(src::UnetSource, callback)
  src.streaming[] && return
  src.bb.pbscnt = -1
  src.streaming[] = true
  @async begin
    t0 = time()
    while src.streaming[]
      ntf = receive(src.gw, RxBasebandSignalNtf, 1000)
      if ntf !== nothing && iszero(ntf.fc)
        t = time() - t0
        try
          data = transpose(reshape(ntf.signal, src.nchannels, src.blksize))
          callback(round(Int, 1000000t), data)
        catch ex
          @error "$ex"
        end
      end
    end
  end
end

function stopinputstream(src::UnetSource)
  try
    req = ParameterReq(recipient=src.bb)
    set!(req, "pbscnt", 0)
    send(src.gw, req)
  catch ex
    @error "$ex"
  end
  src.streaming[] = false
end


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE

using Tensors, Materials, Test

# TODO: Civilized way to place this wall of numbers at the end of the source file?
# TODO: Or maybe read the expected results from a text file, like the other tests do?
let stresses_expected = [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [10.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [20.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [30.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [40.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [50.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [60.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [70.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [80.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [90.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [100.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [110.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [119.995, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [129.743, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [137.812, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [143.250, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [146.767, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [149.313, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [151.463, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [153.465, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [155.402, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [157.301, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [159.164, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [160.991, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [162.780, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [164.529, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [166.236, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [167.902, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [169.527, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [171.110, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [172.653, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [174.155, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [175.618, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [177.042, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [178.429, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [179.779, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [181.094, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [182.373, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [183.619, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [184.833, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [186.014, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [10.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [20.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [30.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [40.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [50.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [60.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [70.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [80.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [90.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [100.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [110.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [120.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [130.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [140.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [150.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [159.999, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [169.920, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [179.177, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [187.533, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [195.357, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [202.848, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [209.935, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [216.120, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [219.606, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [216.262, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [209.090, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [203.945, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [201.241, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [200.038, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [199.668, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [199.760, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [200.115, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [200.622, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [201.219, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [201.867, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [202.542, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [203.231, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [203.922, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [204.611, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [205.293, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [205.967, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [206.630, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [207.282, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [207.922, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [208.551, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [209.168, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [209.774, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [210.368, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [210.952, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [211.525, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [212.087, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [212.640, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [213.183, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [213.717, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [214.242, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [214.758, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [215.266, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [215.766, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [216.259, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [216.743, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [217.221, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [217.691, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [218.155, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [218.612, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [219.063, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [219.507, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [219.946, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [220.379, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [220.807, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [221.229, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [221.646, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [222.059, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [222.466, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [222.868, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [223.266, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [223.660, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [224.049, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [224.435, 0.0, 0.0, 0.0, 0.0, 0.0],
                         [224.816, 0.0, 0.0, 0.0, 0.0, 0.0]],
    strains_expected = [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
                        [0.00005, -0.00002, -0.00002, 0.0, 0.0, 0.0],
                        [0.00010, -0.00003, -0.00003, 0.0, 0.0, 0.0],
                        [0.00015, -0.00004, -0.00004, 0.0, 0.0, 0.0],
                        [0.00020, -0.00006, -0.00006, 0.0, 0.0, 0.0],
                        [0.00025, -0.00008, -0.00008, 0.0, 0.0, 0.0],
                        [0.00030, -0.00009, -0.00009, 0.0, 0.0, 0.0],
                        [0.00035, -0.00010, -0.00010, 0.0, 0.0, 0.0],
                        [0.00040, -0.00012, -0.00012, 0.0, 0.0, 0.0],
                        [0.00045, -0.00014, -0.00014, 0.0, 0.0, 0.0],
                        [0.00050, -0.00015, -0.00015, 0.0, 0.0, 0.0],
                        [0.00055, -0.00017, -0.00017, 0.0, 0.0, 0.0],
                        [0.00060, -0.00018, -0.00018, 0.0, 0.0, 0.0],
                        [0.00065, -0.00020, -0.00020, 0.0, 0.0, 0.0],
                        [0.00070, -0.00021, -0.00021, 0.0, 0.0, 0.0],
                        [0.00075, -0.00023, -0.00023, 0.0, 0.0, 0.0],
                        [0.00080, -0.00025, -0.00025, 0.0, 0.0, 0.0],
                        [0.00085, -0.00028, -0.00028, 0.0, 0.0, 0.0],
                        [0.00090, -0.00030, -0.00030, 0.0, 0.0, 0.0],
                        [0.00095, -0.00032, -0.00032, 0.0, 0.0, 0.0],
                        [0.00100, -0.00034, -0.00034, 0.0, 0.0, 0.0],
                        [0.00105, -0.00037, -0.00037, 0.0, 0.0, 0.0],
                        [0.00110, -0.00039, -0.00039, 0.0, 0.0, 0.0],
                        [0.00115, -0.00041, -0.00041, 0.0, 0.0, 0.0],
                        [0.00120, -0.00044, -0.00044, 0.0, 0.0, 0.0],
                        [0.00125, -0.00046, -0.00046, 0.0, 0.0, 0.0],
                        [0.00130, -0.00048, -0.00048, 0.0, 0.0, 0.0],
                        [0.00135, -0.00051, -0.00051, 0.0, 0.0, 0.0],
                        [0.00140, -0.00053, -0.00053, 0.0, 0.0, 0.0],
                        [0.00145, -0.00055, -0.00055, 0.0, 0.0, 0.0],
                        [0.00150, -0.00058, -0.00058, 0.0, 0.0, 0.0],
                        [0.00155, -0.00060, -0.00060, 0.0, 0.0, 0.0],
                        [0.00160, -0.00062, -0.00062, 0.0, 0.0, 0.0],
                        [0.00165, -0.00065, -0.00065, 0.0, 0.0, 0.0],
                        [0.00170, -0.00067, -0.00067, 0.0, 0.0, 0.0],
                        [0.00175, -0.00070, -0.00070, 0.0, 0.0, 0.0],
                        [0.00180, -0.00072, -0.00072, 0.0, 0.0, 0.0],
                        [0.00185, -0.00074, -0.00074, 0.0, 0.0, 0.0],
                        [0.00190, -0.00077, -0.00077, 0.0, 0.0, 0.0],
                        [0.00195, -0.00079, -0.00079, 0.0, 0.0, 0.0],
                        [0.00200, -0.00081, -0.00081, 0.0, 0.0, 0.0],
                        [0.00107, -0.00053, -0.00053, 0.0, 0.0, 0.0],
                        [0.00107, -0.00053, -0.00053, 0.0, 0.0, 0.0],
                        [0.00112, -0.00055, -0.00055, 0.0, 0.0, 0.0],
                        [0.00117, -0.00056, -0.00056, 0.0, 0.0, 0.0],
                        [0.00122, -0.00058, -0.00058, 0.0, 0.0, 0.0],
                        [0.00127, -0.00059, -0.00059, 0.0, 0.0, 0.0],
                        [0.00132, -0.00061, -0.00061, 0.0, 0.0, 0.0],
                        [0.00137, -0.00062, -0.00062, 0.0, 0.0, 0.0],
                        [0.00142, -0.00064, -0.00064, 0.0, 0.0, 0.0],
                        [0.00147, -0.00065, -0.00065, 0.0, 0.0, 0.0],
                        [0.00152, -0.00067, -0.00067, 0.0, 0.0, 0.0],
                        [0.00157, -0.00068, -0.00068, 0.0, 0.0, 0.0],
                        [0.00162, -0.00070, -0.00070, 0.0, 0.0, 0.0],
                        [0.00167, -0.00071, -0.00071, 0.0, 0.0, 0.0],
                        [0.00172, -0.00073, -0.00073, 0.0, 0.0, 0.0],
                        [0.00177, -0.00074, -0.00074, 0.0, 0.0, 0.0],
                        [0.00182, -0.00076, -0.00076, 0.0, 0.0, 0.0],
                        [0.00187, -0.00077, -0.00077, 0.0, 0.0, 0.0],
                        [0.00192, -0.00079, -0.00079, 0.0, 0.0, 0.0],
                        [0.00197, -0.00081, -0.00081, 0.0, 0.0, 0.0],
                        [0.00202, -0.00082, -0.00082, 0.0, 0.0, 0.0],
                        [0.00207, -0.00084, -0.00084, 0.0, 0.0, 0.0],
                        [0.00212, -0.00086, -0.00086, 0.0, 0.0, 0.0],
                        [0.00217, -0.00088, -0.00088, 0.0, 0.0, 0.0],
                        [0.00222, -0.00089, -0.00089, 0.0, 0.0, 0.0],
                        [0.00227, -0.00092, -0.00092, 0.0, 0.0, 0.0],
                        [0.00232, -0.00094, -0.00094, 0.0, 0.0, 0.0],
                        [0.00237, -0.00098, -0.00098, 0.0, 0.0, 0.0],
                        [0.00242, -0.00101, -0.00101, 0.0, 0.0, 0.0],
                        [0.00247, -0.00103, -0.00103, 0.0, 0.0, 0.0],
                        [0.00252, -0.00106, -0.00106, 0.0, 0.0, 0.0],
                        [0.00257, -0.00109, -0.00109, 0.0, 0.0, 0.0],
                        [0.00262, -0.00111, -0.00111, 0.0, 0.0, 0.0],
                        [0.00267, -0.00113, -0.00113, 0.0, 0.0, 0.0],
                        [0.00272, -0.00116, -0.00116, 0.0, 0.0, 0.0],
                        [0.00277, -0.00118, -0.00118, 0.0, 0.0, 0.0],
                        [0.00282, -0.00121, -0.00121, 0.0, 0.0, 0.0],
                        [0.00287, -0.00123, -0.00123, 0.0, 0.0, 0.0],
                        [0.00292, -0.00126, -0.00126, 0.0, 0.0, 0.0],
                        [0.00297, -0.00128, -0.00128, 0.0, 0.0, 0.0],
                        [0.00302, -0.00131, -0.00131, 0.0, 0.0, 0.0],
                        [0.00307, -0.00133, -0.00133, 0.0, 0.0, 0.0],
                        [0.00312, -0.00135, -0.00135, 0.0, 0.0, 0.0],
                        [0.00317, -0.00138, -0.00138, 0.0, 0.0, 0.0],
                        [0.00322, -0.00140, -0.00140, 0.0, 0.0, 0.0],
                        [0.00327, -0.00143, -0.00143, 0.0, 0.0, 0.0],
                        [0.00332, -0.00145, -0.00145, 0.0, 0.0, 0.0],
                        [0.00337, -0.00148, -0.00148, 0.0, 0.0, 0.0],
                        [0.00342, -0.00150, -0.00150, 0.0, 0.0, 0.0],
                        [0.00347, -0.00152, -0.00152, 0.0, 0.0, 0.0],
                        [0.00352, -0.00155, -0.00155, 0.0, 0.0, 0.0],
                        [0.00357, -0.00157, -0.00157, 0.0, 0.0, 0.0],
                        [0.00362, -0.00160, -0.00160, 0.0, 0.0, 0.0],
                        [0.00367, -0.00162, -0.00162, 0.0, 0.0, 0.0],
                        [0.00372, -0.00165, -0.00165, 0.0, 0.0, 0.0],
                        [0.00377, -0.00167, -0.00167, 0.0, 0.0, 0.0],
                        [0.00382, -0.00170, -0.00170, 0.0, 0.0, 0.0],
                        [0.00387, -0.00172, -0.00172, 0.0, 0.0, 0.0],
                        [0.00392, -0.00174, -0.00174, 0.0, 0.0, 0.0],
                        [0.00397, -0.00177, -0.00177, 0.0, 0.0, 0.0],
                        [0.00402, -0.00179, -0.00179, 0.0, 0.0, 0.0],
                        [0.00407, -0.00182, -0.00182, 0.0, 0.0, 0.0],
                        [0.00412, -0.00184, -0.00184, 0.0, 0.0, 0.0],
                        [0.00417, -0.00187, -0.00187, 0.0, 0.0, 0.0],
                        [0.00422, -0.00189, -0.00189, 0.0, 0.0, 0.0],
                        [0.00427, -0.00192, -0.00192, 0.0, 0.0, 0.0],
                        [0.00432, -0.00194, -0.00194, 0.0, 0.0, 0.0],
                        [0.00437, -0.00197, -0.00197, 0.0, 0.0, 0.0],
                        [0.00442, -0.00199, -0.00199, 0.0, 0.0, 0.0],
                        [0.00447, -0.00201, -0.00201, 0.0, 0.0, 0.0],
                        [0.00452, -0.00204, -0.00204, 0.0, 0.0, 0.0],
                        [0.00457, -0.00206, -0.00206, 0.0, 0.0, 0.0],
                        [0.00462, -0.00209, -0.00209, 0.0, 0.0, 0.0],
                        [0.00467, -0.00211, -0.00211, 0.0, 0.0, 0.0],
                        [0.00472, -0.00214, -0.00214, 0.0, 0.0, 0.0],
                        [0.00477, -0.00216, -0.00216, 0.0, 0.0, 0.0],
                        [0.00482, -0.00219, -0.00219, 0.0, 0.0, 0.0],
                        [0.00487, -0.00221, -0.00221, 0.0, 0.0, 0.0],
                        [0.00492, -0.00224, -0.00224, 0.0, 0.0, 0.0],
                        [0.00497, -0.00226, -0.00226, 0.0, 0.0, 0.0],
                        [0.00502, -0.00229, -0.00229, 0.0, 0.0, 0.0]],

    parameters = DSAParameterState(E =    200.0e3,
                                   nu =      0.3,
                                   R0 =   100.0,
                                   Kn =   100.0,
                                   nn =    10.0,
                                   C1 = 10000.0,
                                   D1 =   100.0,
                                   C2 = 50000.0,
                                   D2 =  1000.0,
                                   Q =     50.0,
                                   b =      0.1,
                                   w =      1e-5,
                                   P1 =   200.0,
                                   P2 =     1e-1,
                                   m =      0.66,
                                   m1 =     6.0,
                                   m2 =     6.0,
                                   M1 =  6000.0,
                                   M2 =  6000.0,
                                   ba =     1e4,
                                   xi =      1.0),
    material    = DSA(parameters=parameters),
    dtime       = 0.25,
    n_steps     = 100,
    n_interrupt = 40,  # for interrupt-and-hold test

    dstrain11   = 2e-4 * dtime, # Corresponds to 10 MPa elastic stress response

    tostrain(tens::Symm2) = copy(tovoigt(tens; offdiagscale=2.0)),
    tostress(tens::Symm2) = copy(tovoigt(tens)),

    times     = [material.drivers.time],
    stresses  = [tostress(material.variables.stress)],
    strains   = [tostrain(material.drivers.strain)],
    Ras       = [copy(material.variables.Ra)],
    tas       = [copy(material.variables.ta)],
    cumeqs    = [copy(material.variables.cumeq)]

    function snapshot!()
        push!(times, material.drivers.time)
        push!(stresses, tostress(material.variables.stress))
        push!(strains, tostrain(material.drivers.strain))
        push!(Ras, material.variables.Ra)
        push!(tas, material.variables.ta)
        push!(cumeqs, copy(material.variables.cumeq))
    end

    # TODO: This doesn't actually test anything.
    # # Uninterrupted test
    # material2 = DSA(parameters = parameters)
    # times2    = [material2.drivers.time]
    # stresses2 = [tostress(material2.variables.stress)]
    # strains2  = [tostrain(material2.drivers.strain)]
    # for i in 1:n_steps
    #     uniaxial_increment!(material2, dstrain11, dtime)
    #     update_material!(material2)
    #     push!(times2, material2.drivers.time)
    #     push!(stresses2, copy(tovoigt(material2.variables.stress)))
    #     push!(strains2, copy(tovoigt(material2.drivers.strain; offdiagscale = 2.0)))
    # end

    # Interrupted test
    for i in 1:n_interrupt
        uniaxial_increment!(material, dstrain11, dtime)
        update_material!(material)
        snapshot!()
    end

    # Interrupt and hold
    # Drive to zero stress
    strain_at_stop = material.drivers.strain[1,1]
    let dstress11 = -material.variables.stress[1,1]
        stress_driven_uniaxial_increment!(material, dstress11, dtime)
    end
    update_material!(material)
    snapshot!()
    # Hold for 3600 seconds
    stress_driven_uniaxial_increment!(material, 0.0, 3600)
    update_material!(material)
    snapshot!()

    # Continue test
    dstrain_extra = strain_at_stop - material.drivers.strain[1,1]
    n_extra_steps = Int(ceil(dstrain_extra / dstrain11))
    for i in (n_interrupt + 1):(n_steps + n_extra_steps)
        uniaxial_increment!(material, dstrain11, dtime)
        update_material!(material)
        snapshot!()
    end

    for i in 1:length(times)
        @test isapprox(stresses[i], stresses_expected[i]; atol = 1e-3)
        @test isapprox(strains[i],  strains_expected[i]; atol = 1e-5)
    end
    dcumeq = cumeqs[end] - cumeqs[end - 1]
    @test dcumeq > 0
end

# Plotting
# using PyPlot
# x11     = [a[1] for a in strains]
# y11     = [a[1] for a in stresses]
# x112    = [a[1] for a in strains2]
# y112    = [a[1] for a in stresses2]
# RasNorm = [Ra / parameters.P1 for Ra in Ras]
# tasNorm = [ta / maximum(tas) for ta in tas]
# fig = figure("test_DSA.jl", figsize = (5, 12)) # Create a new blank figure
# subplot(211)
# plot(x11,y11, label = "interrupted")
# plot(x112,y112,linestyle = "--", label = "uninterrupted")
# title("test_DSA.jl")
# xlabel("Strain, \$\\varepsilon_{11}\$")
# ylabel("Stress, \$\\sigma_{11}\$")
# legend()
# subplot(212)
# plot(times, RasNorm, label = "\$R_a\$")
# plot(times, tasNorm, linestyle = "--", label = "\$t_a\$")
# xlim([3600.0, maximum(times)])
# title("Normalized Evolution of \$R_a\$ & \$t_a\$")
# xlabel("Time")
# ylabel("Ra, ta")
# legend()
# fig.canvas.draw() # Update the figure
# show()
# gcf()


!isfile("NiTE.ini") && error("""
Cannot find NiTE.ini. Please change your directroy where NiTE.ini exists.
""")

using Cxx
using OpenNI2
using CVCore
using CVHighGUI

const ni2 = OpenNI2
const nite = NiTE

isesc(key) = key == 27
genfilename(ext=".png") =
    joinpath(dirname(@__FILE__), string(now(), "_", time_ns(), ext))

always_save = false
show_depth = true

ni2.initialize()
device = ni2.DevicePtr()
ni2.open(device)
ni2.setDepthColorSyncEnabled(device, false)

di = ni2.getDeviceInfo(device)
@show ni2.getName(di)
@show ni2.getVendor(di)

depth = ni2.VideoStreamPtr()
ni2.create(depth, device, ni2.SENSOR_DEPTH)

foreach(ni2.start, [depth])

frame = ni2.VideoFrameRef()

### NiTE ###
nite.initialize();
tracker = nite.UserTracker()
nite.create(tracker, device)
nite.setSkeletonSmoothingFactor(tracker, 0.8)

user_frame = nite.UserTrackerFrameRef()

while true
    readyIndex = ni2.waitForAnyStream([depth])
    @assert readyIndex == 0

    ni2.readFrame(depth, frame)
    arr = convert(Array{ni2.DepthPixel,2}, frame)
    scaledarr = arr * 1/maximum(arr)

    nite.readFrame(tracker, user_frame)
    users = nite.getUsers(user_frame)

    # track users
    for user in users
        id = nite.getId(user)
        if nite.isNew(user)
            nite.startSkeletonTracking(tracker,id)
        elseif nite.isLost(user)
            nite.stopSkeletonTracking(tracker,id)
        end
    end

    # For convenience
    m = Mat(scaledarr)

    for user in users
        nite.isLost(user) && continue
        id = nite.getId(user)
        s = nite.getSkeleton(user)
        for typ in [
            nite.JOINT_HEAD,
            nite.JOINT_NECK,
            nite.JOINT_LEFT_ELBOW,
            nite.JOINT_RIGHT_ELBOW,
            nite.JOINT_LEFT_HAND,
            nite.JOINT_RIGHT_HAND,
            nite.JOINT_TORSO,
            nite.JOINT_LEFT_HIP,
            nite.JOINT_RIGHT_HIP,
            nite.JOINT_LEFT_KNEE,
            nite.JOINT_RIGHT_KNEE,
            ]
            j = nite.getJoint(s, typ)
            nite.getPositionConfidence(j) < 0.3 && continue
            p = nite.getPosition(j)
            x,y,z = icxx"$p.x;",icxx"$p.y;",icxx"$p.z;"
            outX,outY = nite.convertJointCoordinatesToDepth(tracker,x,y,z)
            icxx"cv::circle($(m.handle), cv::Point($outX,$outY), 7, cv::Scalar(255,255,0));"
        end
    end
    show_depth && imshow("depth", m)

    key = waitKey(delay=1)
    isesc(key) && break

    if key == 's' || always_save
        scale!(scaledarr, 255.)
        ext = readyIndex == 0 ? "_depth.png" : "_ir.png"
        fname = genfilename(ext)
        @show fname
        imwrite(fname, Mat(scaledarr))
    end

    rand() > 0.97 && gc(false)
end

destroyAllWindows()

nite.release(user_frame)
nite.destroy(tracker)

foreach(ni2.stop, [depth])
foreach(ni2.destroy, [depth])

ni2.close(device)

nite.shutdown()
ni2.shutdown()

# Force deallocate
depth=0;user_frame=0;device=0;gc()


abstract type AbstractBox end

mutable struct CubicBox <: AbstractBox
    a::Float64
end

struct NonPeriodixBox <: AbstractBox
    nothing
end

CubicBox(a::Unitful.Length) = CubicBox(austrip(a))

mutable struct MoleculeSystem
    atoms::Vector{String}
    mass::Vector{Float64}
    function MoleculeSystem()
        new(Vector{String}(), Vector{Float64}())
    end
    function MoleculeSystem(atoms::AbstractVector{String})
        new(atoms, [MDTools.atom_masses[name] for name in atoms])
    end
end

mutable struct SimulationSystem
    mol::MoleculeSystem
    simbox::CubicBox
    integrator::LeapFrog
    coordinates::Matrix{Float64}
    velocities::Matrix{Float64}
    potential
    function SimulationSystem()
        new(MoleculeSystem(), CubicBox(Inf), LeapFrog(0.5u"fs"),
            Matrix{Float64}(undef,0,0), Matrix{Float64}(undef,0,0), Nothing
        )
    end
end

Base.length(m::MoleculeSystem) = length(m.atoms)

function add!(m::MoleculeSystem, name::AbstractString)
    push!(m.atoms, name)
    push!(m.mass, MDTools.atom_masses[name])
    return m
end

function (*)(n::Integer, m::MoleculeSystem)
    return MoleculeSystem(repeat(m.atoms, n))
end

(*)(m::MoleculeSystem, n::Integer) = n*m

function (+)(m1::MoleculeSystem, m2::MoleculeSystem)
    return MoleculeSystem(vcat(m1.atoms, m2.atoms))
end

function add!(ss::SimulationSystem, m::MoleculeSystem; mindis=3.0u"Å", maxtries=100)
    L = PeriodicEuclidean([ss.simbox.a, ss.simbox.a, ss.simbox.a])
    for i in 1:length(m)
        if size(ss.coordinates,1) == 3
            for j in 1:maxtries
                r = ss.simbox.a .* rand(3,1)
                d=pairwise(L, ss.coordinates, r; dims=2)
                if ! any(d .< austrip(mindis))
                    ss.coordinates = hcat(ss.coordinates, r)
                    ss.mol += MoleculeSystem([m.atoms[i]])
                    break
                end
                j == maxtries && error("Out of tries")
            end

        else
            ss.coordinates = ss.simbox.a .* rand(3,1)
            ss.mol += MoleculeSystem([m.atoms[i]])
        end
    end
    return ss
end


@testset "MPI" begin
    db = setup_db("test/data/TEST_PowerFlowNetworks_SQLite.sqlite"; delete_if_exists=true)
    load_instance_in_db!(db, "case6ww", 0, "test/data/case6ww.m", "MATPOWERM", Dates.now())
    load_instance_in_db!(db, "C2FEN02312", 3, "test/data/C2FEN02312/scenario_3/", "RAWGO", Dates.now())
    load_instance_in_db!(db, "C2S6N02045", 1, "test/data/C2S6N02045/scenario_1/", "RAWGO", Dates.now())

    execute_process_mpi(db, "save_basic_features_instances", "test/log/")
    execute_process_mpi(db, "save_features_instances", "test/log/")
    execute_process_mpi(db, "serialize_instances", "test/log/"; serialize_path="test/data/networks_serialized", graphs_path="test/data/graphs")
    execute_process_mpi(db, "generate_decompositions", "test/log";
                        cliques_path="test/data/cliques",
                        cliquetrees_path="test/data/cliquetrees",
                        graphs_path="test/data/graphs",
                        extension_alg="minimum_degree",
                        preprocess_path="test/data/configs/preprocess_add_random_50.json")
    execute_process_mpi(db, "merge_decompositions", "test/log";
                        heuristic=["molzahn"],
                        heuristic_switch=[0],
                        treshold_name="clique_nv_up",
                        merge_kwargs=Dict("treshold_percent" => 0.5))
    execute_process_mpi(db, "combine_decompositions", "test/log";
                        how="clique_intersect", extension_alg="cholesky")
end


# MIT License
#
# Copyright (c) 2018 Martin Biel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Affine decision function #
# ========================== #
struct AffineDecisionObjectiveBridge{T} <: MOIB.Objective.AbstractBridge
    decision_function::AffineDecisionFunction{T}
end

function MOIB.Objective.bridge_objective(::Type{AffineDecisionObjectiveBridge{T}}, model::MOI.ModelLike,
                                         f::AffineDecisionFunction{T}) where T
    # All decisions have been mapped to the variable part terms
    # at this point.
    F = MOI.ScalarAffineFunction{T}
    # Set the bridged objective
    MOI.set(model, MOI.ObjectiveFunction{F}(), MOI.ScalarAffineFunction(f.variable_part.terms, zero(T)))
    # Save decision function to allow modifications
    return AffineDecisionObjectiveBridge{T}(f)
end

function MOIB.Objective.supports_objective_function(
    ::Type{<:AffineDecisionObjectiveBridge}, ::Type{<:AffineDecisionFunction})
    return true
end
MOIB.added_constrained_variable_types(::Type{<:AffineDecisionObjectiveBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{<:AffineDecisionObjectiveBridge})
    return Tuple{DataType, DataType}[]
end
function MOIB.set_objective_function_type(::Type{AffineDecisionObjectiveBridge{T}}) where T
    return MOI.ScalarAffineFunction{T}
end

function MOI.get(::AffineDecisionObjectiveBridge, ::MOI.NumberOfVariables)
    return 0
end

function MOI.get(::AffineDecisionObjectiveBridge, ::MOI.ListOfVariableIndices)
    return MOI.VariableIndex[]
end

function MOI.delete(::MOI.ModelLike, ::AffineDecisionObjectiveBridge)
    # Nothing to delete
    return nothing
end

function MOI.set(::MOI.ModelLike, ::MOI.ObjectiveSense,
                 ::AffineDecisionObjectiveBridge, ::MOI.OptimizationSense)
    # Nothing to handle if sense changes
    return nothing
end

function MOI.get(model::MOI.ModelLike,
                 attr::MOIB.ObjectiveFunctionValue{F},
                 bridge::AffineDecisionObjectiveBridge{T}) where {T, F <: AffineDecisionFunction{T}}
    f = bridge.decision_function
    G = MOI.ScalarAffineFunction{T}
    obj_val = MOI.get(model, MOIB.ObjectiveFunctionValue{G}(attr.result_index))
    # Calculate and add constant
    return obj_val + f.variable_part.constant
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{F},
                 bridge::AffineDecisionObjectiveBridge{T}) where {T, F <: AffineDecisionFunction{T}}
    return bridge.decision_function
end

function MOI.modify(model::MOI.ModelLike, bridge::AffineDecisionObjectiveBridge{T}, change::MOI.ScalarConstantChange) where T
    f = bridge.decision_function
    # Modify constant of variable part
    f.variable_part.constant = change.new_constant
    return nothing
end

function MOI.modify(model::MOI.ModelLike, bridge::AffineDecisionObjectiveBridge{T}, change::MOI.ScalarCoefficientChange) where T
    f = bridge.decision_function
    # Modify variable part of decision function
    modify_coefficient!(f.variable_part.terms, change.variable, change.new_coefficient)
    # Modify the variable part of the mapped objective as well
    F = MOI.ScalarAffineFunction{T}
    MOI.modify(model, MOI.ObjectiveFunction{F}(), change)
    return nothing
end


using Juriba.Radiation
using Juriba.Constants
using StaticArrays, QuadGK, Interpolations

const RD = Juriba.Radiation

@Base.kwdef mutable struct Compton <: AbstractRadiation

    size::Int					        # size of arrays
    en_phot::Vector{Float64}			# array of photon energies
    num_phot::Vector{Float64} 			# array of number of photons in units of erg/s/Hz
    en_phot_obs::Vector{Float64}		# same as above but in observer frame
    num_phot_obs::Vector{Float64} 		# same as above but in observer frame

    seed_ph::Interpolations.Extrapolation
    iter_ph::InterruptException.Extrapolation
    esc_p_sph::Interpolations.Extrapolation
    esc_p_cyl::Interpolation.Extrapolation

    z::Float64 = 0.				            # Dimensions of emitting region
    r::Float64 = 0.
    vol::Float64 = 0.					    # Volume of emitting region
    beta::Float64 = 0.				        # speed of the emitting region
    angle::Float64 = 0.		        # Viewing angle/Doppler factor of emitting region
    dopfac::Float64 = 0.
    dopnum::Float64 = 0.				        # Doppler boosting exponent, depends on geometry
    counterjet::Bool = false			# boolean switch if user wants to include counterjet emission
    geometry::String = ""   	        # string to track geometry of emitting region

    seed_size::Int					# size of seed photon field
    Niter::Int						# number of IC iterations
    tau::Float64                    # optical depth/comtpon Y of emitting region
    ypar::Float64
    rphot::Float64 					# photospheric radius when tau > 1, used to renormalize volume
    escape_corr::Float64				# escape term, used to renormalize our spectra to CompPS

    seed_energ::Float64				# array of seed frequencies in Hz
    seed_urad::Float64 				# array of seed photon number density in log10(#/erg/cm^3)
    iter_urad::Float64 				# array of iterated photon number density in log10(#/erg/cm^3)

end

const Te_table = @SVector [19.999,30.,50.,70.,90.,120.,150.,200.,
                  280.,400.,600.,900.,1300.,1800.,2500.001]
const tau_table	= @SVector [0.04999,0.07,0.1,0.19,0.27,0.38,0.54,0.76,
                   0.94,1.08,1.34,1.75,2.1,2.6,3.0001]

esc_table_sph = SMatrix{15, 15}(
                    0.02,0.07,0.17,0.27,0.33,0.4,0.45,0.45,0.55,0.55,0.54,0.51,0.48,0.55,0.55,
                    0.02,0.07,0.18,0.275,0.33,0.4,0.45,0.47,0.55,0.53,0.56,0.51,0.5,0.55,0.55,
                    0.02,0.07,0.18,0.275,0.34,0.4,0.45,0.47,0.55,0.54,0.52,0.51,0.53,0.55,0.58,
                    0.025,0.085,0.18,0.275,0.35,0.4,0.45,0.47,0.55,0.54,0.59,0.57,0.5,0.55,0.6,
                    0.028,0.085,0.2,0.275,0.35,0.4,0.45,0.49,0.54,0.54,0.6,0.59,0.53,0.55,0.65,
                    0.035,0.09,0.21,0.29,0.35,0.4,0.45,0.49,0.55,0.57,0.58,0.6,0.53,0.55,0.7,
                    0.045,0.12,0.23,0.3,0.35,0.41,0.42,0.49,0.54,0.57,0.58,0.58,0.5,0.55,0.7,
                    0.055,0.135,0.23,0.3,0.35,0.4,0.42,0.49,0.52,0.57,0.59,0.61,0.58,0.57,0.85,
                    0.07,0.14,0.235,0.3,0.35,0.4,0.41,0.49,0.5,0.57,0.58,0.61,0.61,0.57,0.85,
                    0.08,0.145,0.235,0.3,0.35,0.39,0.4,0.49,0.5,0.57,0.58,0.63,0.7,0.6,0.85,
                    0.09,0.15,0.245,0.3,0.35,0.4,0.4,0.45,0.49,0.55,0.62,0.64,0.73,0.6,0.85,
                    0.105,0.17,0.26,0.31,0.35,0.4,0.4,0.45,0.47,0.56,0.62,0.65,0.78,0.65,0.85,
                    0.115,0.19,0.28,0.32,0.35,0.4,0.4,0.45,0.44,0.5,0.67,0.68,0.8,0.7,1.,
                    0.14,0.215,0.3,0.34,0.38,0.4,0.4,0.45,0.42,0.48,0.68,0.73,0.85,0.8,1.,
                    0.16,0.23,0.31,0.35,0.38,0.4,0.4,0.45,0.42,0.48,0.67,0.78,0.9,0.9,1.
                    )

esc_table_cyl = SMatrix{15, 15}(
                    0.02,0.06,0.22,0.3,0.5,0.5,0.57,0.7,0.75,0.75,0.75,0.77,0.75,0.76,0.7,
                    0.02,0.06,0.19,0.28,0.4,0.5,0.57,0.65,0.65,0.75,0.7,0.75,0.7,0.7,0.75,
                    0.017,0.04,0.17,0.27,0.32,0.5,0.57,0.6,0.6,0.7,0.65,0.65,0.73,0.67,0.7,
                    0.01,0.04,0.12,0.17,0.23,0.35,0.4,0.55,0.5,0.55,0.55,0.65,0.7,0.67,0.68,
                    0.01,0.04,0.1,0.17,0.19,0.28,0.32,0.4,0.45,0.5,0.5,0.5,0.68,0.66,0.68,
                    0.015,0.04,0.09,0.17,0.19,0.25,0.25,0.35,0.45,0.4,0.45,0.5,0.64,0.64,0.68,
                    0.02,0.05,0.09,0.15,0.17,0.22,0.24,0.3,0.35,0.35,0.45,0.4,0.62,0.61,0.64,
                    0.02,0.05,0.095,0.15,0.17,0.22,0.22,0.25,0.3,0.35,0.4,0.38,0.4,0.4,0.6,
                    0.024,0.05,0.095,0.14,0.16,0.21,0.21,0.25,0.27,0.32,0.4,0.42,0.4,0.4,0.5,
                    0.025,0.055,0.1,0.15,0.17,0.21,0.21,0.25,0.27,0.3,0.35,0.4,0.35,0.4,0.43,
                    0.03,0.065,0.11,0.15,0.18,0.21,0.21,0.25,0.27,0.3,0.35,0.4,0.4,0.45,0.47,
                    0.045,0.085,0.135,0.17,0.19,0.22,0.24,0.27,0.3,0.35,0.4,0.45,0.5,0.5,0.55,
                    0.06,0.1,0.16,0.2,0.22,0.25,0.26,0.29,0.33,0.35,0.45,0.5,0.5,0.55,0.65,
                    0.09,0.14,0.2,0.23,0.26,0.29,0.29,0.33,0.4,0.4,0.48,0.55,0.6,0.65,0.75,
                    0.11,0.165,0.23,0.26,0.29,0.31,0.33,0.36,0.4,0.45,0.52,0.6,0.65,0.75,0.8
                    )

function compton(s1::Int, s2::Int)

    size = s1
    seed_size = s2

    en_phot = zeros(Float64, size)
    num_phot = similar(en_phot)
    iter_urad = similar(en_phot)
    en_phot_obs = zeros(Float64, 2*size)
    num_phot_obs = similar(en_phot_obs)

    seed_energ = zeros(Float64, seed_size)
    seed_urad = zeros(Float64, seed_size)

    Niter = 20
    ypad = 0
    escape_corr = 1.0

    counterjet = false

    # xsize = 15
    # ysize = 15

    esc_p_sph = CubicSplineInterpolation((Te_table, tau_table),esc_table_sph)
    esc_p_cyl = CubicSplineInterpolation((Te_table, tau_table),esc_table_cyl)

    return Compton(size=size, seed_size=seed_size, en_phot=en_phot,
                   num_phot=num_phot, iter_urad=iter_urad, en_phot_obs=en_phot_obs,
                   num_phot_obs=num_phot_obs, seed_energ=seed_energ,
                   seed_urad=seed_urad, Niter=Niter, ypar=ypar,
                   escape_corr=escape_corr, esc_p_cyl=esc_p_cyl, esc_p_sph=esc_p_sph)
end

# This function is the kernel of eq 2.48 in Blumenthal & Gould(1970), represents the scattered photon spectrum
# for a given electron and includes the Klein-Nishina cross section.
function comfnc(ein, p::RD.comfnc_params)
    game = p.game
    e1 = p.e1

    phodis = p.phodis

    einit = exp(ein)
    btst = einit/(game*emerg)
    eg4 = 3.0*einit*game
    utst = eg4*(emerg + eg4)

    if ((e1 < btst && (btst-e1)/btst >= 4.0e-8) ||
        (e1 > utst && (e1-utst)/utst >= 4.0e-8))
        return 0.0
    else
        biggam = eg4/emerg
        q = e1/(biggam*(1.0 - e1))
        phonum = phodis(einit)
        tm1 = 2.0*q*log(q)
        tm2 = (1.0 + 2.0*q)*(1.0 - q)
        tm3 = 0.5*((biggam*q)^2)*(1.0 - q)/(1.0 + biggam*q)

        return (tm1 + tm2 + tm3)*10^phonum
    end

end

# This function is the integral of comfnc above over the total seed photon distribution
function comint(gam, p::comint_params)

    eph = p.eph
    ephmin = p.ephmin
    ephmax = p.ephmax

    eldis = p.eldis
    phodis = p.phodis

    game = exp(gam)
    e1 = eph/(game*emerg)
    econst = 2π*re0^2*cee
    blim = log(max(eph/(4.0*game*(game-eph/emerg)), ephmin))
    ulim = log(min(eph, ephmax))

    (ulim <= blim) && return 0.0

    F2params = RD.comfnc_params(game, e1, phodis)
    F2func(x) = comfnc(x, F2params)

    result, error = quadgk(F2func, blim, ulim, rtol=1.0, atol=1.0, maxevals=100)

    den = eldis(game)

    return econst*den*result/game
end

"""This integrates the individual electron spectrum from
comint over the total electron distribution"""
function comintegral(com::Compton, it, blim, ulim, enphot, enphmin, enphmax,
                     eldis)

    F1params = RD.comint_params(enphot, enphmin, enphmax,
                                eldis, com.seed_ph)
    F1params_it = RD.comint_params(enphot, enphmin, enphmax,
                                   eldis, com.iter_ph)

    if iszero(it)
        params = F1params
    else
        params = F1params_it
    end

    F1func(gam) = comint(gam, params)

    result, errror = quadgk(F1func, blim, ulim, rtol=1.0, atol=1.0, maxevals=100)
    return result
end

"""
This function calculates the final spectrum in all frequency bins,
including multiple scatters.
Note: the reason the Doppler boosting is a factor of 2 instead of 3 is because the calculations are done for
a conical jet in the Lind&BLanford 1985 prescription
"""
function compton_spectrum(com::Compton, gmin, gmax, eldis)

    ephmin = com.seed_energ[1]
    ephmax = com.seed_energ[end]

    dopfac_cj = com.dopfac*(1.0 - com.beta*cos(com.angle))/
                (1.0 +  com.beta*cos(com.angle))

    for it = 1:com.Niter
        for i = 1:com.size
            blim = log(max(gmin, com.en_phot[i]/emerg))
            ulim = log(gmax)

            if blim >= ulim
                comres = 1e-100
            else
                comres = comintegral(com, it, blim, ulim, com.en_phot[i],
                                  ephmin, ephmax, eldis)
            end

            com.num_phot[i] = com.num_phot[i]+comres*com.vol*com.en_phot[i]*herg
            com.en_phot_obs[i] = com.en_phot[i]*com.dopfac
            com.num_phot_obs[i] = com.num_phot[i]*dopfac_cj^com.dopnum

            if com.counterjet
                com.en_phot_obs[i+com.size] = com.en_phot[i]*dopfac_cj
                com.num_phot_obs[i+com.size] = com.num_phot[i]*dopfac_cj^com.dopnum
            else
                com.en_phot_obs[i+com.size] = 0.0
                com.num_phot_obs[i+com.size] = 0.0
            end

            if iszero(comres)
                com.iter_urad[i] = -50.0
            else
                com.iter_urad[i] = log10(com.escape_corr*comres*com.vol)/(π*com.r^2*cee)
            end

        end

        ephmin = com.en_phot[1]
        ephmax = com.en_phot[end]
        com.iter_ph = LinearInterpolation(com.en_phot, com.iter_urad)
    end
end

"""Method to include cyclosynchrotron array from `Cyclosyn.jl` to the seed field. The two input arrays should be
`get_energ()` and `get_nphot()` methods from `Cyclosyn.jl`. If the seed_urad array wasn't empty, the contribution
is automatically added to the existing one.
note: the second condition has a <= sign to dodge numerical errors when the seed field energy density is
extremely low, which can result in negative values/nan for the seed_urad """
function cyclosyn_seed(com::Compton, seed_arr, seed_lum)
    chr2 = cee*herg*π*com.r^2
    for i in eachindex(seed_arr)
        com.seed_energ[i] = seed_arr[i]

        if !iszero(com.seed_urad[i])
            com.seed_urad[i] = log10(10^com.seed_urad[i] + seed_lum[i]/(chr2*com.seed_energ[i]))
        elseif seed_lum[i]/(chr2*com.seed_energ[i]) <= 0
            com.seed_urad[i] = -100
        else
            com.seed_urad[i] = log10(seed_lum[i]/(chr2*com.seed_energ[i]))
        end

    end

    com.seed_ph = LinearInterpolation(seed_energ, seed_urad)

end

"""
Method to include black body to seed field for IC;
Note: Urad and Tbb need to be passed in the co-moving frame, the function does NOT account for beaming
"""
function bb_seed_k(com::Compton, seed_arr, Urad, Tbb)

    ulim = 100.0*Tbb*kboltz
    bfield_const = herg*cee^2*sbconst*Tbb^4

    seed_freq_array(com, seed_arr)
    for i = 1:com.seed_size
        if (com.seed_energ[i] < ulim)
            bbfield = (2.0*Urad*(seed_energ[i]/herg)^2.)/(bfield_const*(exp(seed_energ[i]/(kboltz*Tbb)) - 1.0))
        else
            bbfield = 1.0e-100
        end

        if !iszero(com.seed_urad[i])
            com.seed_urad[i] = log10(10^com.seed_urad[i] + bbfield)
        elseif bbfield > 0
            com.seed_urad[i] = log10(bbfield)
        else
            com.seed_urad[i] = -100.0
        end

    end

    com.seed_ph = LinearInterpolation(com.seed_energ, com.seed_urad)

end

function bb_seed_kev(com::Compton, seed_arr, Urad, Tbb)

    ulim = 100.0*Tbb*kboltz_kev2erg
    bfield_const = herg*cee^2*sbconst*(Tbb*kboltz_kev2erg/kboltz)^4

    seed_freq_array(com, seed_arr)
    for i = 1:com.seed_size
        if (com.seed_energ[i] < ulim)
            bbfield = (2.0*Urad*(seed_energ[i]/herg)^2.)/(bfield_const*(exp(seed_energ[i]/(kboltz*Tbb)) - 1.0))
        else
            bbfield = 1.0e-100
        end

        if !iszero(com.seed_urad[i])
            com.seed_urad[i] = log10(10^com.seed_urad[i] + bbfield)
        elseif bbfield > 0
            com.seed_urad[i] = log10(bbfield)
        else
            com.seed_urad[i] = -100.0
        end

    end

    com.seed_ph = LinearInterpolation(com.seed_energ, com.seed_urad)

end

"""
Calculates incident photon field for a Shakura-Sunyaev disk with aspect ratio H, inner temperature Tin,
inner radius Rin, at a distance z from the disk, taking beaming into account
"""
function disk_integral(alfa, p)

end

function seed_freq_array(com::Compton, seed_arr)
    com.seed_energ .= seed_arr
end


using Genie
using Genie.Router, Genie.Requests, Genie.Renderer.Json
using HTTP

include("./MiniMax.jl")
using .MiniMax: play_bot_turn!, State, MARK_P1, MARK_P2, MARK_TIE, MARK_EMPTY

Genie.config.cors_headers["Access-Control-Allow-Origin"] = "http://localhost:3000"
Genie.config.cors_headers["Access-Control-Allow-Headers"] = "Content-Type"
Genie.config.cors_headers["Access-Control-Allow-Methods"] = "GET, POST"

const TransformIndex = Int8[1 2 3; 4 5 6; 7 8 9];
const TransformWinner =
    Dict{Int8,String}(MARK_P1 => "player_1", MARK_P2 => "player_2", MARK_TIE => "tie")

route("/api/game/v1", method = POST) do
    game_type::String = getpayload(:type, "normal")
    payload::Dict{String,Any} = jsonpayload()

    if length(get(payload, "board", [])) == 9
        raw_array::Array{Int,1} = payload["board"]
        # need to take transpose of the board now and before returning the updated version
        game_board::Array{Int8,2} = reshape(convert(Array{Int8,1}, raw_array), (3, 3))'

        game_state::State = play_bot_turn!(game_board, game_type)

        winner::String = get(TransformWinner, game_state.winner, "open")
        last_entry::Int =
            (game_state.last_entry > 0) ? Int(TransformIndex[game_state.last_entry]) :
            Int(0)
        board::Array{Int,1} = convert(Array{Int,1}, reshape(game_state.board', (9,)))

        response::Dict{String,Union{String,Int,Array{Int,1}}} =
            Dict{String,Union{String,Int,Array{Int,1}}}(
                "winner" => winner,
                "bot_last_entry" => last_entry,
                "board" => board,
            )

        json(response)
    else
        "Payload must contain an array of length nine with key `board`."
    end
end

route("/api/game/v1", method = GET) do
    """Hello from game backend!

    Play bot's turn by making a POST request with status of the current game board as payload.
    This payload must be an one-dimensional array, length of nine, and provided by key `board`.
    Mark your moves in the board by -1, bot will use 1 to mark its latest move."""
end

function force_compile()
    sleep(5) # allow the server to start before running the following
    println("run test requests...")

    base_url::String = "http://localhost:8000"
    endpoint::String = "/api/game/v1"

    HTTP.request("GET", "$(base_url)$(endpoint)")

    HTTP.request(
        "POST",
        "$(base_url)$(endpoint)?type=normal",
        [("Content-Type", "application/json")],
        """{"board":[0,0,0,0,0,0,0,0,0]}""",
    )
    HTTP.request(
        "POST",
        "$(base_url)$(endpoint)?type=normal",
        [("Content-Type", "application/json")],
        """{"board":[0,0,0,0,-1,0,0,0,0]}""",
    )
    HTTP.request(
        "POST",
        "$(base_url)$(endpoint)?type=easy",
        [("Content-Type", "application/json")],
        """{"board":[0,0,0,0,-1,0,0,0,0]}""",
    )

    println("test requests completed")
end

function main()
    println("running the server...")

    @async force_compile()

    up(8000, "0.0.0.0", async = false)
end

if abspath(PROGRAM_FILE) == @__FILE__
    println("running main...")
    main()
else
    println("not running main...")
end


#-------------------------------------------------------------------
#* EMSO Model Library (EML) Copyright (C) 2004 - 2007 ALSOC.
#*
#* This LIBRARY is free software; you can distribute it and/or modify
#* it under the therms of the ALSOC FREE LICENSE as available at
#* http://www.enq.ufrgs.br/alsoc.
#*
#* EMSO Copyright (C) 2004 - 2007 ALSOC, original code
#* from http://www.rps.eng.br Copyright (C) 2002-2004.
#* All rights reserved.
#*
#* EMSO is distributed under the therms of the ALSOC LICENSE as
#* available at http://www.enq.ufrgs.br/alsoc.
#*--------------------------------------------------------------------
#* Author: Gerson Balbueno Bicca
#* $Id$
#*--------------------------------------------------------------------
type Physical_Properties_Heatex
	Physical_Properties_Heatex()=begin
		new(
			molweight(Dict{Symbol,Any}(
				:Brief=>"Average Mol Weight",
				:Default=>75,
				:Lower=>1,
				:Upper=>1e8
			)),
			cp_mol(Dict{Symbol,Any}(
				:Brief=>"Average Molar Heat Capacity",
				:Upper=>1e10
			)),
			Properties_In_Out(Dict{Symbol,Any}(
				:Brief=>"Properties at Inlet Stream",
				:Symbol=>"_{in}"
			)),
			Properties_In_Out(Dict{Symbol,Any}(
				:Brief=>"Properties at Outlet Stream",
				:Symbol=>"_{out}"
			)),
			[:Mw,:Cp,:Inlet,:Outlet,]
		)
	end
	Mw::molweight
	Cp::cp_mol
	Inlet::Properties_In_Out
	Outlet::Properties_In_Out
	variables::Array{Symbol,1}
	attributes::Dict{Symbol,Any}
end
export Physical_Properties_Heatex
function atributes(in::Physical_Properties_Heatex,_::Dict{Symbol,Any})
	fields::Dict{Symbol,Any}=Dict{Symbol,Any}()
	fields[:Pallete]=false
	fields[:Brief]="to be documented"
	fields[:Info]="to be documented"
	drive!(fields,_)
	return fields
end
Physical_Properties_Heatex(_::Dict{Symbol,Any})=begin
	newModel=Physical_Properties_Heatex()
	newModel.attributes=atributes(newModel,_)
	newModel
end


"""
`module  JAC.ImpactIonization`
    ... a submodel of JAC that contains all methods for computing electron impact ionization cross sections.
"""
module ImpactIonization

    using JAC, JAC.ManyElectron, JAC.ImpactExcitation, JAC.AutoIonization

    """
    `struct  ImpactIonization.Settings`  ... defines a type for the settings in estimating electron-impact ionization cross sections.

        + electronEnergies   ::Array{Float64,1}             ... List of impact-energies of the incoming elecgtrons.
        + calcShellDependent ::Bool                         ... True of shell-dependent cross sections are to be calculated, and false otherwise.
        + shells             ::Array{Shell,1}               ... List of shells for which shell-dependent cross sections are calculated.
    """
    struct Settings
        electronEnergies     ::Array{Float64,1}
        calcShellDependent   ::Bool
        shells               ::Array{Shell,1}
    end

    """
    `JAC.ImpactIonization.Settings()`  ... constructor for the default values of electron-impact ionization cross section estimates.
    """
    function Settings()
       Settings( Float64[], false, Shell[] )
    end

    ## `Base.show(io::IO, settings::ImpactIonization.Settings)`  ... prepares a proper printout of the variable settings::ImpactIonization.Settings.
    function Base.show(io::IO, settings::ImpactIonization.Settings)
        println(io, "electronEnergies:       $(settings.electronEnergies)  ")
        println(io, "calcShellDependent:     $(settings.calcShellDependent)  ")
        println(io, "shells:                 $(settings.shells)  ")
    end

    """
    `struct  ImpactIonization.Channel`
        ... defines a type for a electron-impact ionization channel to help characterize the incoming and two outgoing (continuum)
            states of many electron-states with (two) free electron

        + initialEpsilon   ::Float64            ... energy of the incoming free-electron
        + finalEpsilon     ::Float64            ... energy of the outgoing free-electron
        + releasedEpsilon  ::Float64            ... energy of the released free-electron
        + initialKappa     ::Int64              ... partial-wave of the incoming free electron
        + finalKappa       ::Int64              ... partial-wave of the outgoing free electron
        + releasedKappa    ::Int64              ... partial-wave of the released free electron
        + symmetry         ::LevelSymmetry      ... total angular momentum and parity of the scattering state
        + initialPhase     ::Float64            ... phase of the incoming partial wave
        + finalPhase       ::Float64            ... phase of the outgoing partial wave
        + releasedPhase    ::Float64            ... phase of the released partial wave
        + amplitude        ::Complex{Float64}   ... Collision amplitude associated with the given channel.
    """
    struct  Channel
        initialEpsilon     ::Float64
        finalEpsilon       ::Float64
        releasedEpsilon    ::Float64
        initialKappa       ::Int64
        finalKappa         ::Int64
        releasedKappa      ::Int64
        symmetry           ::LevelSymmetry
        initialPhase       ::Float64
        finalPhase         ::Float64
        releasedPhase      ::Float64
        amplitude          ::Complex{Float64}
    end

    # `Base.show(io::IO, channel::ImpactIonization.Channel)`  ... prepares a proper printout of the variable channel::ImpactIonization.Channel.
    function Base.show(io::IO, channel::ImpactIonization.Channel)
        println(io, "initialEpsilon:     $(channel.initialEpsilon)  ")
        println(io, "finalEpsilon:       $(channel.finalEpsilon)  ")
        println(io, "releasedEpsilon:    $(channel.releasedEpsilon)  ")
        println(io, "initialKappa:       $(channel.initialKappa)  ")
        println(io, "finalKappa:         $(channel.finalKappa)  ")
        println(io, "releasedKappa:      $(channel.releasedKappa)  ")
        println(io, "symmetry:           $(channel.symmetry)  ")
        println(io, "initialPhase:       $(channel.initialPhase)  ")
        println(io, "finalPhase:         $(channel.finalPhase)  ")
        println(io, "releasedPhase:      $(channel.releasedPhase)  ")
        println(io, "amplitude:          $(channel.amplitude)  ")
    end

    """
    `struct  ImpactIonization.Line`  ... defines a type for a electron-impact ionization line that may include the definition of channels and
                                         their corresponding amplitudes.

        + initialLevel   ::Level          ... initial-(state) level
        + finalLevel     ::Level          ... final-(state) level
        + crossSection   ::Float64        ... total cross section of this line
        + hasChannels    ::Bool           ... Determines whether the individual scattering (sub-) channels are defined in terms of their free-
                                              electron energies, kappa's, phases and the total angular momentum/parity as well as the amplitude,
                                              or not.
        + channels       ::Array{ImpactIonization.Line,1}  ... List of Eimex channels of this line.
    """
    struct  Line
        initialLevel     ::Level
        finalLevel       ::Level
        crossSection     ::Float64
        hasChannels      ::Bool
        channels         ::Array{ImpactIonization.Channel,1}
    end

    # `Base.show(io::IO, line::ImpactIonization.Line)`  ... prepares a proper printout of the variable line::ImpactIonization.Line.
    function Base.show(io::IO, line::ImpactIonization.Line)
        println(io, "initialLevel:     $(line.initialLevel)  ")
        println(io, "finalLevel:       $(line.finalLevel)  ")
        println(io, "crossSection:     $(line.crossSection)  ")
        println(io, "hasChannels:      $(line.hasChannels)  ")
        println(io, "channels:         $(line.channels)  ")
    end

end # module


using DSGE, ModelConstructors, Distributed
using Nullables, DataFrames, OrderedCollections, Dates

#############################
# To use:
# Just run in the Julia REPL
# include("run_default.jl")
# Note that the estimation
# step will take 2-3 hours.
#############################

##############
# Model Setup
##############
# Instantiate the FRBNY DSGE model object
m = Model1002()

# estimate as of 2015-Q3 using the default data vintage from 2015 Nov 27
m <= Setting(:data_vintage, "181115")
m <= Setting(:date_forecast_start, quartertodate("2018-Q4"))

# The following settings ensure that this script runs in
# a short amount of time. To properly estimate and
# forecast, we recommend either using the default settings
# (i.e. comment out the settings below) or
# changing the settings yourself.
m <= Setting(:n_mh_simulations, 100) # Do 100 MH steps during estimation
m <= Setting(:use_population_forecast, false) # Population forecast not available as data to turn off
m <= Setting(:forecast_block_size, 5) # adjust block size to run on small number of estimations

#############
# Estimation
#############
# Reoptimize parameter vector, compute Hessian at mode, and full posterior
# parameter sampling.
#
# Note some columns will have missing data because not all our data
# is publicly available. By default, `load_data` will error
# if any of the columns in the loaded DataFrame is empty,
# but we turn this feature off by setting the keyword `check_empty_columns = false`.
# Warnings will be still thrown after calling load_data indicating which columns
# are empty. However, estimate will still run when data is missing.
@time begin
    df = load_data(m, try_disk = false, check_empty_columns = false, summary_statistics = :none)
    data = df_to_matrix(m, df)
    estimate(m, data)
end

# produce LaTeX tables of parameter moments
moment_tables(m)

############
# Forecasts
############
# (1) forecast_one produces forecasts of the variables specified in output_vars
# and stores the forecasts in model units (log deviations from steady state).
# (2) compute_meansbands stores the forecast in a MeansBands type, defined in DSGE.jl,
# which transforms the forecast from model units to "human readable" units, i.e.
# the GDP forecast is transformed from annualized quarterly log per-capita growth rates
# to annualized quarterly aggregate percent change.

# The variables we want to forecast. In this case, all of the model observables
output_vars = [:histobs, :forecastobs]

# Modal forecast (point forecast)
forecast_one(m, :mode, :none, output_vars; check_empty_columns = false)
compute_meansbands(m, :mode, :none, output_vars; check_empty_columns = false)

# Full-distribution forecast (point forecast (mean) and uncertainty bands)
# Optionally add 10 processes to run the forecast in parallel (uncomment the 3 lines below).
# Alternatively, you can load the ClusterManagers package and add processes
# using one of the schedulers such as SGE or Slurm.
# addprocs(10)
# @everywhere using DSGE
# m <= Setting(:use_parallel_workers, true)

forecast_one(m, :full, :none, output_vars; check_empty_columns = false)
compute_meansbands(m, :full, :none, output_vars; check_empty_columns = false)

# Comment out the line below if you did not run the forecast in parallel.
# rmprocs(procs())


doc"""
    sum(x::Var, dim::Int)

Returns the sum over the given dimension.
"""
function Base.sum(x::Var, dim::Int)
    throw("Not implemented yet.")
end


@testset "Transient Responses" begin

    fname = joinpath(dirname(@__FILE__), "..", "..", "data", "test_Hz19.5-testing.bdf")
    fname2 = joinpath(dirname(@__FILE__), "..", "..", "data", "tmp", "test_Hz19.5-copy.bdf")
    fname_out = joinpath(dirname(@__FILE__), "..", "..", "data", "tmp", "testwrite.bdf")
    cp(fname, fname2, force = true)  # So doesnt use .mat file

    @testset "Read file" begin

        s = read_TR(fname)

        @test isa(s, NeuroimagingMeasurement)
        @test isa(s, EEG)
        @test isa(s, TR)

        s = read_TR(fname2)

        @test samplingrate(s) == 8192.0u"Hz"
        @test samplingrate(Int, s) == 8192
        @test isa(samplingrate(Float64, s), AbstractFloat) == true
        @test isa(samplingrate(Int, s), Int) == true

        @test isapprox(maximum(s.data[:, 2]), 54.5939; atol = 0.1)
        @test isapprox(minimum(s.data[:, 4]), -175.2503; atol = 0.1)

        s = read_TR(fname, valid_triggers = [8])
        s = read_TR(fname, min_epoch_length = 8388)
        s = read_TR(fname, max_epoch_length = 8389)

        s = read_TR(fname)
        original_length = length(s.triggers["Index"])
        s = read_TR(fname, remove_first = 10)
        @test original_length - 10 == length(s.triggers["Index"])

        s = read_TR(fname, max_epochs = 12)
        @test length(s.triggers["Index"]) == 12

        s = read_TR(fname)
        s.triggers = extra_triggers(s.triggers, 1, 7, 0.7, samplingrate(Float64, s))
        @test length(s.triggers["Index"]) == 56
    end

    @testset "Show" begin

        s = read_TR(fname)
        show(s)

    end

    @testset "Plot" begin
        s = read_TR(fname)
        Neuroimaging.plot(s)
        Neuroimaging.plot(s, "Cz")
        Neuroimaging.plot(s, ["Cz"])
        Neuroimaging.plot(s, ["Cz", "10Hz_SWN_70dB_R"])
    end

end


#coast.msh geometry, created by JIGSAW
#adapted by @miguelraz 28/03/2020 from
#https://github.com/dengwirda/jigsaw-matlab
#mshid=1
#ndims=2
#point=303
const lakes = [(-8.915414699999999,1.661592)
(-8.884706400000001,1.5538653)
(-8.7270571,1.4396306)
(-8.6780329,1.3310253)
(-8.2763869,1.3642085)
(-7.7589437,1.5521208)
(-7.3399811,1.5866412)
(-7.2064877,1.7400111)
(-6.987161,1.7838988)
(-6.9190874,1.6751018)
(-6.6594362,1.823788)
(-6.4705078,2.0285109)
(-6.3982459,2.0257761)
(-6.1776033,2.1236043)
(-5.9683636,1.9041636)
(-6.0500631,1.6420917)
(-6.2751037,1.4382319)
(-6.2083449,1.2768143)
(-6.3593873,1.1233594)
(-6.4379782,0.9672913)
(-6.4389687,0.940829)
(-6.3308569,0.9103191)
(-5.921668,1.1606956)
(-5.885353,1.1594465)
(-5.5306989,0.88275)
(-5.4571118,0.9068911)
(-5.2776995,0.8218321)
(-4.6882658,1.0696109)
(-4.4325551,1.115784)
(-3.9867787,1.5284986)
(-3.6232783,1.573356)
(-3.5156848,1.5181578)
(-3.1151141,1.6162773)
(-2.6031886,1.9253444)
(-2.4201615,2.0814581)
(-2.0576784,2.182551)
(-1.9469493,2.3929843)
(-1.6564457,2.6016249)
(-1.4385855,2.8113568)
(-1.185718,2.9681802)
(-0.896576,3.2839699)
(-0.4656316,3.4939341)
(-0.0715676,3.6520868)
(0.3221563,3.5994536)
(0.6089023,3.4943945)
(0.6092879,3.3885375)
(0.3942456,3.3878929)
(0.2866788,3.4141384)
(0.0717038,3.3345153)
(0.0896864,3.2286668)
(0.1076576,3.1757488)
(-0.1436793,3.01699)
(-0.3235837,2.8584538)
(-0.4676923,2.7529346)
(-0.9391942,2.0668111)
(-1.0495193,1.7498742)
(-1.0882363,1.3531376)
(-1.0158452,1.3262275)
(-0.9424102,1.4845979)
(-0.615238,1.7477533)
(-0.5061946000000001,1.9061816)
(-0.2890742,2.0115316)
(-0.506667,1.7473959)
(-0.6160022000000001,1.5360392)
(-0.397972,1.8000363)
(-0.1808403,1.8525961)
(0.2894343,1.7998173)
(0.5431103,1.6681146)
(0.9058832,1.5373304)
(0.9432855999999999,1.3258124)
(1.0534256,1.1147328)
(1.1641909,0.8507940000000001)
(1.3109203,0.6930545)
(1.3117251,0.5871977)
(1.421469,0.5351345)
(1.9317215,0.5400872)
(2.2226054,0.5965222)
(2.3340522,0.4391798)
(2.4073552,0.4137156)
(2.4449497,0.334835)
(2.5909063,0.3369578)
(2.6249835,0.4962917)
(2.7343488,0.4979874)
(2.8454551,0.3938967)
(2.936647,0.3954205)
(2.9201944,0.2892562)
(2.9931921,0.2905022)
(3.3135431,0.7199108)
(3.3863589,0.7213257)
(3.9960329,1.1315466)
(4.0705567,1.0538628)
(4.6490632,1.174476)
(4.903998,1.1550887)
(5.4495532,1.1450567)
(5.9850466,1.4278414)
(6.1653805,1.4607402)
(6.3476152,1.4409063)
(6.6355959,1.5048353)
(6.5309084,1.3948027)
(6.4623697,1.2862027)
(6.4803085,0.8098819)
(6.4094979,0.7542354)
(6.410009,0.7012564)
(6.4882671,0.5981826)
(6.7068647,0.6065329)
(6.8546905,0.559329)
(6.9296957,0.5093111)
(7.3625916,0.6332439)
(7.4195041,0.5826584)
(7.4810256,0.4262426)
(7.6622,0.4606821)
(7.7339418,0.4903887)
(7.6927729,0.5946095)
(7.6199031,0.5913803)
(7.5788199,0.6956243)
(7.4649955,0.7967085)
(7.4604098,0.9025555)
(7.6668095,1.176764)
(7.770946,1.2875089)
(7.7298813,1.3917132)
(7.6707366,1.4951115)
(7.6356734,1.4670289)
(7.6246141,1.3074531)
(7.5532118,1.2777792)
(7.5280861,1.4357493)
(7.5345391,1.7011603)
(7.7131429,1.762169)
(7.8603274,1.7158544)
(7.9228745,1.9308907)
(7.7348899,2.0813402)
(7.3713821,2.1182087)
(7.2698318,1.9547751)
(7.1885795,2.1633926)
(7.1152657,2.1868122)
(7.0441691,2.15734)
(6.9336956,2.2058041)
(6.9676141,2.2602054)
(7.0648074,2.529299)
(7.0897106,2.7954185)
(7.2917362,3.1221593)
(7.3230152,3.2295634)
(7.2074219,3.4101256)
(7.0987913,3.4319941)
(7.056259,3.5892427)
(6.8349672,3.7390777)
(6.5426013,3.8863685)
(6.3538119,4.1440578)
(6.6493349,4.7917061)
(6.5385166,4.8932842)
(6.7052993,5.1650313)
(6.7009764,5.2708721)
(5.7441174,5.2345817)
(5.5686945,5.1755508)
(4.788607,5.1509481)
(4.4636817,5.3536511)
(4.2075761,5.6646354)
(3.607578,7.1858876)
(3.3873644,7.6576363)
(3.2804384,7.7613007)
(3.1762847,7.7327191)
(3.0381962,7.6506117)
(2.8286881,7.6467634)
(2.8598071,7.8590921)
(2.6845531,7.9089847)
(2.5468001,7.8008409)
(2.4437671,7.6933492)
(2.3390419,7.6917801)
(2.1983122,7.7691861)
(2.1616242,7.9010276)
(2.0918985,7.9000964)
(2.0235167,7.7933405)
(1.9540682,7.7660064)
(1.570508,7.7353016)
(1.5002116,7.7875578)
(1.2547448,7.9442315)
(0.730965,8.1526824)
(0.4522013,8.2575018)
(0.1738657,8.3098811)
(-0.0694533,8.521515300000001)
(-0.1737495,8.4157382)
(-0.2084645,8.4422444)
(-0.347615,8.363095700000001)
(-0.4518238,8.389823099999999)
(-0.5558115,8.4695476)
(-0.5215963,8.310643900000001)
(-0.5221188,8.1518584)
(-0.4879606,7.9400337)
(-0.2092654,7.833566)
(-0.2097517,7.463066)
(-0.5257651,7.0403597)
(-0.5604465,7.1463352)
(-0.7716285000000001,6.9354925)
(-0.9473082,6.8835005)
(-0.9833619,6.7249256)
(-1.1593398,6.6731632)
(-1.2287946,6.7795396)
(-1.1574372,6.9378046)
(-0.6994015,7.4115277)
(-0.5933112,7.7286747)
(-0.6973173,7.8878837)
(-0.9759166,7.9422776)
(-1.1175557,7.6256239)
(-1.1194069,7.3609825)
(-1.2951661,7.2564438)
(-1.5483098,6.411761)
(-1.9366363,6.31007)
(-1.8637764,6.5209552)
(-1.7582839,6.5197737)
(-1.7202965,6.7840348)
(-1.5427314,6.9939696)
(-1.5417155,7.0998256)
(-1.5762336,7.1530937)
(-1.9985176,6.9989869)
(-2.3506579,6.8978706)
(-2.5645144,6.6893724)
(-2.5351899,6.3183276)
(-2.7562921,5.7395724)
(-3.7980607,5.0191439)
(-4.3373509,4.7676755)
(-4.9834948,4.5740477)
(-5.9256532,4.1282314)
(-7.0382285,3.1643631)
(-7.4517045,2.7580044)
(-8.203390300000001,2.1245258)
(-8.7235361,1.8639192)
(-6.0222359,1.402651)
(-5.9152018,1.5578835)
(-5.8726343,1.5299217)
(-5.7920161,1.5536639)
(-5.8345898,1.5816055)
(-5.8318773,1.6609928)
(-5.663557,1.7083083)
(-5.5966615,1.7590827)
(-5.6808641,1.8413432)
(-5.7929717,1.7391469)
(-6.0818599,1.5637339)
(-6.0865686,1.4314222)
(-5.7324455,1.9225485)
(-5.5683675,1.9594866)
(-5.5335991,1.9159672)
(-5.5318777,1.9688923)
(-5.449816,2.0457126)
(-5.4914569,2.1000333)
(-5.7297704,2.0019357)
(-5.7677013,1.9502324)
(-5.2706487,2.2254789)
(-5.2257103,2.277064)
(-5.2196558,2.3563407)
(-5.191165,2.4614151)
(-5.2924029,2.4539566)
(-5.3166868,2.3699543)
(-5.3193455,2.2852741)
(-5.344493,2.174812)
(-2.628509,4.8904087)
(-2.5231726,4.8093509)
(-2.4524965,4.7818143)
(-2.166425,4.9101487)
(-1.8814086,4.9860466)
(-2.0588911,4.9881727)
(-2.093713,5.0415487)
(-1.9150532,5.1452403)
(-1.7011793,5.2487523)
(-1.5239785,5.2470098)
(-1.1676824,5.5087407)
(-1.2022868,5.6148538)
(-0.9185046,5.7718016)
(-0.882891,5.8245348)
(-0.9532116,5.8778612)
(-1.3075201,5.7215257)
(-1.5563954,5.5649116)
(-1.6276655,5.5126727)
(-1.7340945,5.4872998)
(-1.840557,5.4619959)
(-2.0900008,5.3326514)
(-2.3756722,5.1777288)
(-2.590489,5.0486276)
(-0.1745905,7.648274)
(-0.1048759,7.4629629)
(-0.0174793,7.4629295)
(-0.0349355,7.5687895)
(0.0349007,7.7275752)
(0.2267794,7.7806613)
(0.3839078,7.7280333)
(0.558318,7.7550129)
(0.6632222,7.7024853)
(0.8201638,7.7296796)
(0.732548,7.8086476)
(0.6273773,7.9405222)
(0.4880418,7.9135694)
(0.1393711,7.9922753)
(0.0348428,7.9922181)
(-0.1743299,7.8864526)
(4.5964504,4.4039327)
(4.8814214,4.4120893)
(4.9866866,4.468199)
(5.1274771,4.5254771)
(5.1614199,4.5795088)
(5.0886198,4.6302255)
(4.9818982,4.6269721)
(4.8364965,4.7285905)
(4.5535316,4.6675729)
(4.3785074,4.5569131)
(4.3813116,4.4510625)]