#!/usr/bin/env julia

import Celeste.ParallelRun: one_node_joint_infer, infer_init, BoundingBox, find_neighbors
import Celeste.SDSSIO: RunCamcolField, load_field_images, PlainFITSStrategy

const datadir = joinpath(Pkg.dir("Celeste"), "test", "data")
if ccall(:jl_generating_output, Cint, ()) == 0
    cd(datadir) do
        run(`make RUN=7713 CAMCOL=3 FIELD=152`)
    end
end

"""
This benchmark operates on a box of the sky that contains just
one light source. It visits 1048 pixels per evaluation of the elbo,
and the optimizer runs for 37 iterations, for 38,776 pixel-visits
in total.
"""
function benchmark_one_light_source()
    box = BoundingBox(347.7444, 347.7446, 16.6202, 16.6204)
    rcfs = [RunCamcolField(7713, 3, 152)]

    ctni = infer_init(rcfs, PlainFITSStrategy(datadir); box=box)[1:4]

    # Warm up---this compiles the code
    one_node_joint_infer(ctni...)

    # clear allocations in case julia is running with --track-allocations=user
    Profile.clear_malloc_data()

    if isempty(ARGS)
        @time one_node_joint_infer(ctni...)
    elseif ARGS[1] == "--profile"
        Profile.init(delay=0.01)
        @profile one_node_joint_infer(ctni...)
        Profile.print(format=:flat, sortedby=:count)
    end
end

benchmark_one_light_source()


using CUDA
using Adapt
#using Random
include("../util/Matrix_utils.jl")
include("../util/Enum_Collection.jl")
include("Button.jl")
include("Drawables.jl")

function get_window(name::Enum,windows::All_Windows)#::Window
    if name == main_window
        return windows.main_window
    else
        return windows.main_window
    end
end

function draw_self(window::Window,windows::All_Windows,buttons::All_Buttons)
    #kernel_blit_image_inplace(threadIdx().x,window.current_matrix,window.matrix_self,window.x_Coord,window.y_Coord)
    kernel_blit_image_inplace(threadIdx().x,blockIdx().x,window.current_matrix,window.matrix_self,window.x_Coord,window.y_Coord)

    for button in window.buttons
        current_button = get_button(button,buttons)
        draw_self(current_button,window.current_matrix)
    end
end

function click(x,y,window::Window,all_buttons::All_Buttons)
    click_on_window = false

    if includes_point(x,y,window.x_Coord,window.y_Coord,window.width,window.height)
        click_on_window = true
        #for button in window.buttons
        for button in window.buttons

            current_button = get_button(button,all_buttons)
            reward,click_on_child,coords_x,coords_y = click(x,y,current_button,window.x_Coord,window.y_Coord)
            sync_threads()
            if click_on_child
                if current_button.status[1,blockIdx().x]
                    #kernel_blit_image_inplace(threadIdx().x,blockIdx().x,window.current_matrix,current_button.matrix_clicked,coords_x,coords_y)
                else
                    #kernel_blit_image_inplace(threadIdx().x,blockIdx().x,window.current_matrix,current_button.matrix_unclicked,coords_x,coords_y)
                end
                return reward,click_on_window,window.x_Coord,window.y_Coord
            end
        end
    end
    return 0,false,0,0
end


include("units.jl")
import Base.show

struct BuildOp
    unitBuildFunc
    unitType # needed for rendering
    buildLength::Number
    timer::GameTimer
    BuildOp(unitBuildFunc, unitType, buildLength) = new(unitBuildFunc, unitType, buildLength, GameTimer())
end
time_remaining(bop::BuildOp) = bop.buildLength - elapsed(bop.timer)
Base.show(io::IO, bop::BuildOp) = print(io, "BuildOp($(bop.unitBuildFunc)() in $(round(time_remaining(bop), 2)) secs)")

mutable struct Player
    units::PlayerUnits
    money
    build_ops::Array{BuildOp}
    Player() = new(PlayerUnits(), 0, BuildOp[])
    Player(m) = new(PlayerUnits(), m, BuildOp[])

    #Player() = (self = new(0, BuildOp[]); self.units = PlayerUnits(self))
    #Player(m) = (self = new(m, BuildOp[]); self.units = PlayerUnits(self))
end

function update!(p::Player, dt)
    p.money += length(p.units.collectors) * kMoneyPersecPercollector * dt

    for bop in p.build_ops
        update!(bop.timer, dt)
        if (time_remaining(bop) < 0)
            bop.unitBuildFunc()
        end
    end
    # Remove the completed build ops.
    filter!(bop->time_remaining(bop)>0, p.build_ops)
end

function purchase_unit!(p::Player, unitType::Type, addUnitFunc)
    if p.money < build_cost(unitType)
        return
    end
    p.money -= build_cost(unitType)
    bop = BuildOp(addUnitFunc, unitType, build_time(unitType))
    push!(p.build_ops, bop)
    start!(bop.timer)
    sort!(p.build_ops; by=time_remaining)
    return (bop, p.money)
end
function purchase_collector!(p::Player, pos = WorldPos(0,0))
     purchase_unit!(p, Collector, ()->add_unit!(p.units, Collector(p.units, pos)))
 end
function purchase_fighter!(p::Player, pos = WorldPos(0,0))
     purchase_unit!(p, Fighter, ()->add_unit!(p.units, Fighter(p.units, pos)))
 end

# Tests
p = Player(20)
purchase_collector!(p)
@test 17 == p.money
purchase_fighter!(p)
purchase_collector!(p)
@test 3 == length(p.build_ops)
update!(p, 0.05)
@test 3 == length(p.build_ops)
update!(p, 100)
@test 0 == length(p.build_ops)
@test 3 == length(p.units.units)
@test 2 == length(p.units.collectors)
# kill unit
try
    while true
        attack!(p.units.units[1], p.units.fighters[1])
    end
catch end
@test 0 == length(p.units.units)


mutable struct GFUpdateState
    prev_trace::GFTrace
    trace::GFTrace
    constraints::Any
    score::Float64
    visitor::AddressVisitor
    params::Dict{Symbol,Any}
    discard::DynamicAssignment
    args_change::Any
    retchange::Union{Some{Any},Nothing}
    callee_output_changes::HomogenousTrie{Any,Any}
end

function GFUpdateState(args_change, prev_trace, constraints, params)
    visitor = AddressVisitor()
    discard = DynamicAssignment()
    GFUpdateState(prev_trace, GFTrace(), constraints, 0., visitor,
                  params, discard, args_change, nothing, HomogenousTrie{Any,Any}())
end

get_args_change(state::GFUpdateState) = state.args_change

function set_ret_change!(state::GFUpdateState, value)
    if state.retchange === nothing
        state.retchange = value
    else
        lightweight_retchange_already_set_err()
    end
end

function get_addr_change(state::GFUpdateState, addr)
    get_leaf_node(state.callee_output_changes, addr)
end

function addr(state::GFUpdateState, dist::Distribution{T}, args, addr) where {T}
    visit!(state.visitor, addr)
    constrained = has_leaf_node(state.constraints, addr)
    has_previous = has_primitive_call(state.prev_trace, addr)
    if has_internal_node(state.constraints, addr)
        lightweight_got_internal_node_err(addr)
    end
    local retval::T
    if has_previous
        prev_call::CallRecord = get_primitive_call(state.prev_trace, addr)
    end
    if constrained
        retval = get_leaf_node(state.constraints, addr)
    elseif has_previous
        retval = prev_call.retval
    else
        error("Constraint not given for new address: $addr")
    end
    if constrained && has_previous
        # there was a change and this was the previous value
        retchange = (true, prev_call.retval)
    elseif has_previous
        retchange = NoChange()
    else
        # retchange is null, because the address is new
        retchange = nothing
    end
    score = logpdf(dist, retval, args...)
    call = CallRecord(score, retval, args)
    state.trace = assoc_primitive_call(state.trace, addr, call)
    state.score += score
    if constrained && has_previous
        set_leaf_node!(state.discard, addr, prev_call.retval)
    end
    set_leaf_node!(state.callee_output_changes, addr, retchange)
    retval
end

function addr(state::GFUpdateState, gen::Generator{T}, args, addr, args_change) where {T}
    visit!(state.visitor, addr)
    if has_internal_node(state.constraints, addr)
        constraints = get_internal_node(state.constraints, addr)
    elseif has_leaf_node(state.constraints, addr)
        lightweight_got_leaf_node_err(addr)
    else
        constraints = EmptyAssignment()
    end
    if has_subtrace(state.prev_trace, addr)
        prev_trace = get_subtrace(state.prev_trace, addr)
        (trace, _, discard, retchange) = update(gen, args, args_change,
            prev_trace, constraints)
        set_internal_node!(state.discard, addr, discard)
        set_leaf_node!(state.callee_output_changes, addr, retchange)
    else
        trace = assess(gen, args, constraints)
        set_leaf_node!(state.callee_output_changes, addr, nothing)
    end
    call::CallRecord = get_call_record(trace)
    retval::T = call.retval
    state.trace = assoc_subtrace(state.trace, addr, trace)
    state.score += call.score
    retval
end

splice(state::GFUpdateState, gen::GenFunction, args::Tuple) = exec(gf, state, args)

function update(gen::GenFunction, new_args, args_change, trace::GFTrace, constraints)
    state = Gen.GFUpdateState(args_change, trace, constraints, gen.params)
    retval = Gen.exec(gen, state, new_args)
    new_call = Gen.CallRecord{Any}(state.score, retval, new_args)
    state.trace.call = new_call
    # discard addresses that were deleted
    unvisited = Gen.get_unvisited(state.visitor, get_assignment(state.prev_trace))
    merge!(state.discard, unvisited) # TODO use merge()?
    if !isempty(Gen.get_unvisited(state.visitor, constraints))
        error("Update did not consume all constraints")
    end

    # compute the weight
    prev_score = get_call_record(trace).score
    weight = state.score - prev_score
    (state.trace, weight, state.discard, state.retchange)
end


@testset "Layers" begin
  @test Jtac.atype(false) == Array{Float32}
  @test Jtac.atype(true)  == Knet.KnetArray{Float32}
  p = Knet.Param([1,2])
  p.opt = Knet.Adam(lr = 1e-3)
  p2 = Jtac.copy_param(p)
  @test Jtac.is_param(p) == Jtac.is_param(p2) == true
  @test !Jtac.is_param([1,2])
  @test p2.opt == p.opt
  Conv(50, 10)
  chain = @chain (11, 10, 5) Conv(10) Pool() Dropout(0.5) Dense(15) Batchnorm()
  stack1 = @stack (11, 10, 5) Conv(10) Pool() Dropout(0.5) Dense(15) Batchnorm()
  stack2 = @stack (11, 10, 5) stack_input=true Conv(10) Pool() Dropout(0.5) Dense(15) Batchnorm()
  resnet = @residual (9, 9, 5) Conv(5, relu, padding = 1) Conv(5, relu, padding = 1)
  @test size(chain(ones(Float32, 11, 10, 5, 2))) == (15, 2)
  @test size(stack1(ones(Float32, 11, 10, 5, 2))) == (1070, 2)
  @test size(stack2(ones(Float32, 11, 10, 5, 2))) == (1620, 2)
  @test size(resnet(ones(Float32, 9, 9, 5, 3))) == (9, 9, 5, 3)
  @test_throws ErrorException @residual (9, 9, 5) Dense(10, relu)
end

@testset "Models" begin
  for G in [TicTacToe, MetaTac]
    game = G()
    for model in [Shallow(G), ShallowConv(G, 20), MLP(G, [200, 50])]
      value, policy = model(game)
      @test length(value) == 1
      @test length(policy) == policy_length(G)
    end
  end
  for G in [MetaTac]
    chain = @chain G Conv(10) Pool() Dropout(0.5) Dense(15) Batchnorm()
    stack = @stack G Conv(10) Pool() Dropout(0.5) Dense(15) Batchnorm()
    for model in [NeuralModel(G, chain), NeuralModel(G, stack)]
      value, policy = model(G())
      @test length(value) == 1
      @test length(policy) == policy_length(G)
    end
  end
end


module EventsWrapper

using JavaCall

const AbsoluteDate = @jimport org.orekit.time.AbsoluteDate
const AbstractDetector = @jimport org.orekit.propagation.events.AbstractDetector
const Action = @jimport org.hipparchus.ode.events.Action
const AdapterDetector = @jimport org.orekit.propagation.events.AdapterDetector
const AlignmentDetector = @jimport org.orekit.propagation.events.AlignmentDetector
const AltitudeDetector = @jimport org.orekit.propagation.events.AltitudeDetector
const AngularSeparationDetector = @jimport org.orekit.propagation.events.AngularSeparationDetector
const ApsideDetector = @jimport org.orekit.propagation.events.ApsideDetector
const AtmosphericRefractionModel = @jimport org.orekit.models.AtmosphericRefractionModel
const BodyShape = @jimport org.orekit.bodies.BodyShape
const BooleanDetector = @jimport org.orekit.propagation.events.BooleanDetector
const Class = @jimport java.lang.Class
const Collection = @jimport java.util.Collection
const DataContext = @jimport org.orekit.data.DataContext
const DateDetector = @jimport org.orekit.propagation.events.DateDetector
const EclipseDetector = @jimport org.orekit.propagation.events.EclipseDetector
const ElevationDetector = @jimport org.orekit.propagation.events.ElevationDetector
const ElevationExtremumDetector = @jimport org.orekit.propagation.events.ElevationExtremumDetector
const ElevationMask = @jimport org.orekit.utils.ElevationMask
const EnablingPredicate = @jimport org.orekit.propagation.events.EnablingPredicate
const Enum = @jimport java.lang.Enum
const EventDetector = @jimport org.orekit.propagation.events.EventDetector
const EventEnablingPredicateFilter = @jimport org.orekit.propagation.events.EventEnablingPredicateFilter
const EventHandler = @jimport org.orekit.propagation.events.handlers.EventHandler
const EventShifter = @jimport org.orekit.propagation.events.EventShifter
const EventSlopeFilter = @jimport org.orekit.propagation.events.EventSlopeFilter
const EventState = @jimport org.orekit.propagation.events.EventState
const EventState_EventOccurrence = @jimport org.orekit.propagation.events.EventState$EventOccurrence
const EventsLogger = @jimport org.orekit.propagation.events.EventsLogger
const Field = @jimport org.hipparchus.Field
const FieldAbsoluteDate = @jimport org.orekit.time.FieldAbsoluteDate
const FieldAbstractDetector = @jimport org.orekit.propagation.events.FieldAbstractDetector
const FieldAltitudeDetector = @jimport org.orekit.propagation.events.FieldAltitudeDetector
const FieldApsideDetector = @jimport org.orekit.propagation.events.FieldApsideDetector
const FieldDateDetector = @jimport org.orekit.propagation.events.FieldDateDetector
const FieldEclipseDetector = @jimport org.orekit.propagation.events.FieldEclipseDetector
const FieldElevationDetector = @jimport org.orekit.propagation.events.FieldElevationDetector
const FieldEventDetector = @jimport org.orekit.propagation.events.FieldEventDetector
const FieldEventHandler = @jimport org.orekit.propagation.events.handlers.FieldEventHandler
const FieldEventState = @jimport org.orekit.propagation.events.FieldEventState
const FieldEventState_EventOccurrence = @jimport org.orekit.propagation.events.FieldEventState$EventOccurrence
const FieldEventsLogger = @jimport org.orekit.propagation.events.FieldEventsLogger
const FieldFunctionalDetector = @jimport org.orekit.propagation.events.FieldFunctionalDetector
const FieldLatitudeCrossingDetector = @jimport org.orekit.propagation.events.FieldLatitudeCrossingDetector
const FieldNodeDetector = @jimport org.orekit.propagation.events.FieldNodeDetector
const FieldOfView = @jimport org.orekit.geometry.fov.FieldOfView
const FieldOfViewDetector = @jimport org.orekit.propagation.events.FieldOfViewDetector
const FieldOrbit = @jimport org.orekit.orbits.FieldOrbit
const FieldOrekitStepInterpolator = @jimport org.orekit.propagation.sampling.FieldOrekitStepInterpolator
const FieldSpacecraftState = @jimport org.orekit.propagation.FieldSpacecraftState
const FieldTimeStamped = @jimport org.orekit.time.FieldTimeStamped
const FilterType = @jimport org.orekit.propagation.events.FilterType
const FootprintOverlapDetector = @jimport org.orekit.propagation.events.FootprintOverlapDetector
const Frame = @jimport org.orekit.frames.Frame
const Function = @jimport java.util.function.Function
const FunctionalDetector = @jimport org.orekit.propagation.events.FunctionalDetector
const GeoMagneticFieldFactory_FieldModel = @jimport org.orekit.models.earth.GeoMagneticFieldFactory$FieldModel
const GeographicZoneDetector = @jimport org.orekit.propagation.events.GeographicZoneDetector
const GroundAtNightDetector = @jimport org.orekit.propagation.events.GroundAtNightDetector
const GroundFieldOfViewDetector = @jimport org.orekit.propagation.events.GroundFieldOfViewDetector
const HaloXZPlaneCrossingDetector = @jimport org.orekit.propagation.events.HaloXZPlaneCrossingDetector
const InterSatDirectViewDetector = @jimport org.orekit.propagation.events.InterSatDirectViewDetector
const JString = @jimport java.lang.String
const LatitudeCrossingDetector = @jimport org.orekit.propagation.events.LatitudeCrossingDetector
const LatitudeExtremumDetector = @jimport org.orekit.propagation.events.LatitudeExtremumDetector
const List = @jimport java.util.List
const LongitudeCrossingDetector = @jimport org.orekit.propagation.events.LongitudeCrossingDetector
const LongitudeExtremumDetector = @jimport org.orekit.propagation.events.LongitudeExtremumDetector
const MagneticFieldDetector = @jimport org.orekit.propagation.events.MagneticFieldDetector
const NegateDetector = @jimport org.orekit.propagation.events.NegateDetector
const NodeDetector = @jimport org.orekit.propagation.events.NodeDetector
const Object = @jimport java.lang.Object
const OneAxisEllipsoid = @jimport org.orekit.bodies.OneAxisEllipsoid
const Optional = @jimport java.util.Optional
const Orbit = @jimport org.orekit.orbits.Orbit
const OrbitType = @jimport org.orekit.orbits.OrbitType
const OrekitStepInterpolator = @jimport org.orekit.propagation.sampling.OrekitStepInterpolator
const PVCoordinatesProvider = @jimport org.orekit.utils.PVCoordinatesProvider
const PositionAngle = @jimport org.orekit.orbits.PositionAngle
const PositionAngleDetector = @jimport org.orekit.propagation.events.PositionAngleDetector
const RealFieldElement = @jimport org.hipparchus.RealFieldElement
const SpacecraftState = @jimport org.orekit.propagation.SpacecraftState
const SphericalPolygonsSet = @jimport org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet
const TimeStamped = @jimport org.orekit.time.TimeStamped
const ToDoubleFunction = @jimport java.util.function.ToDoubleFunction
const TopocentricFrame = @jimport org.orekit.frames.TopocentricFrame
const Transformer = @jimport org.orekit.propagation.events.Transformer
const VisibilityTrigger = @jimport org.orekit.propagation.events.VisibilityTrigger

export AbstractDetector
export AdapterDetector
export AlignmentDetector
export AltitudeDetector
export AngularSeparationDetector
export ApsideDetector
export BooleanDetector
export DateDetector
export EclipseDetector
export ElevationDetector
export ElevationExtremumDetector
export EnablingPredicate
export EventDetector
export EventEnablingPredicateFilter
export EventShifter
export EventSlopeFilter
export EventState
export EventsLogger
export FieldAbstractDetector
export FieldAltitudeDetector
export FieldApsideDetector
export FieldDateDetector
export FieldEclipseDetector
export FieldElevationDetector
export FieldEventDetector
export FieldEventState
export FieldEventsLogger
export FieldFunctionalDetector
export FieldLatitudeCrossingDetector
export FieldNodeDetector
export FieldOfViewDetector
export FilterType
export FootprintOverlapDetector
export FunctionalDetector
export GeographicZoneDetector
export GroundAtNightDetector
export GroundFieldOfViewDetector
export HaloXZPlaneCrossingDetector
export InterSatDirectViewDetector
export LatitudeCrossingDetector
export LatitudeExtremumDetector
export LongitudeCrossingDetector
export LongitudeExtremumDetector
export MagneticFieldDetector
export NegateDetector
export NodeDetector
export PositionAngleDetector
export Transformer
export VisibilityTrigger
export add_event_date
export and_combine
export clear_logged_events
export compare_to
export describe_constable
export do_event
export equals
export evaluate_step
export event_is_enabled
export event_occurred
export g
export get_align_angle
export get_altitude
export get_angle
export get_beacon
export get_body
export get_body_shape
export get_central_body
export get_class
export get_date
export get_declaring_class
export get_decreasing_time_shift
export get_detector
export get_detectors
export get_elevation
export get_elevation_mask
export get_event_date
export get_event_detector
export get_fov
export get_frame
export get_function
export get_handler
export get_increasing_time_shift
export get_latitude
export get_logged_events
export get_longitude
export get_margin
export get_max_check_interval
export get_max_iteration_count
export get_min_elevation
export get_observer
export get_occulted
export get_occulted_radius
export get_occulting
export get_occulting_radius
export get_orbit_type
export get_original
export get_pending_event
export get_position_angle
export get_proximity_angle
export get_pv_coordinates_provider
export get_pv_target
export get_refraction_model
export get_slave
export get_threshold
export get_topocentric_frame
export get_total_eclipse
export get_zone
export hash_code
export init
export is_forward
export monitor_detector
export name
export not_combine
export notify
export notify_all
export or_combine
export ordinal
export radius_correction
export reinitialize_begin
export reset_state
export to_string
export try_advance
export value_of
export values
export wait
export with_constant_elevation
export with_elevation_mask
export with_function
export with_handler
export with_margin
export with_max_check
export with_max_iter
export with_penumbra
export with_refraction
export with_threshold
export with_umbra

include("HandlersWrapper/HandlersWrapper.jl")
include("abstract_detector.jl")
include("adapter_detector.jl")
include("alignment_detector.jl")
include("altitude_detector.jl")
include("angular_separation_detector.jl")
include("apside_detector.jl")
include("boolean_detector.jl")
include("date_detector.jl")
include("eclipse_detector.jl")
include("elevation_detector.jl")
include("elevation_extremum_detector.jl")
include("enabling_predicate.jl")
include("event_detector.jl")
include("event_enabling_predicate_filter.jl")
include("event_shifter.jl")
include("event_slope_filter.jl")
include("event_state.jl")
include("events_logger.jl")
include("field_abstract_detector.jl")
include("field_altitude_detector.jl")
include("field_apside_detector.jl")
include("field_date_detector.jl")
include("field_eclipse_detector.jl")
include("field_elevation_detector.jl")
include("field_event_detector.jl")
include("field_event_state.jl")
include("field_events_logger.jl")
include("field_functional_detector.jl")
include("field_latitude_crossing_detector.jl")
include("field_node_detector.jl")
include("field_of_view_detector.jl")
include("filter_type.jl")
include("footprint_overlap_detector.jl")
include("functional_detector.jl")
include("geographic_zone_detector.jl")
include("ground_at_night_detector.jl")
include("ground_field_of_view_detector.jl")
include("halo_xz_plane_crossing_detector.jl")
include("inter_sat_direct_view_detector.jl")
include("latitude_crossing_detector.jl")
include("latitude_extremum_detector.jl")
include("longitude_crossing_detector.jl")
include("longitude_extremum_detector.jl")
include("magnetic_field_detector.jl")
include("negate_detector.jl")
include("node_detector.jl")
include("position_angle_detector.jl")
include("transformer.jl")
include("visibility_trigger.jl")

end


import Pkg
Pkg.rm(["Grids", "GridGames"])
Pkg.develop(["Grids", "GridGames"])
using MuZero
using Base.Threads: @spawn
include("game.jl")
include("muzero_config.jl")
config = getconfig(Tac)
actor_pool_size = Threads.nthreads() - 1
config.num_actors = actor_pool_size
@show Threads.nthreads(), config.num_actors
learner = Learner(
    config;
    uuid="b234fc40-a07b-4418-9fbc-adb0278dd4e1",
    dir="~/models/MuZeroModels.jl/tictactoe/",
    play_only=true,
)
play!(learner)


cd(ENV["PWD"])

import SeaMass
using Base.Test
using Plots
pyplot()

function test(filename, id, inputSpectrumID, outputSpectrumID)

  # load SMB input spectrum
  smbSpectrumIn = SeaMass.SmbSpectrum(
    "data/out/" * filename * "/1.seamass/" * filename * "." * id * ".smb",
    outputSpectrumID
  )

  # load SMB blurred input spectrum
  smbSpectrumInBlur = false
  try
    smbSpectrumInBlur = SeaMass.SmbSpectrum(
      "data/out/" * filename * "/1.seamass/" * filename * "." * id * ".input.smb",
      outputSpectrumID
    )
  end

  # load SMB reconstructed output spectrum
  smbSpectrumOutReconstruct = SeaMass.SmbSpectrum(
    "data/out/" * filename * "/4.seamass-restore_--reconstruct/" * filename * "." * id * ".smb",
    outputSpectrumID
  )

  # Compare smb
  plot(
    smbSpectrumIn.locations,
    vcat(
      smbSpectrumIn.counts ./ (smbSpectrumIn.locations[2:end] - smbSpectrumIn.locations[1:end-1]),
      0.0,
    ),
    line = :steppost,
    label = "SMB input",
    title = "SMB binned comparison - " * filename,
    xlabel = "m/z (Th)",
    ylabel = "ion count density",
    reuse = false,
  )
  if smbSpectrumInBlur != false
    plot!(
      smbSpectrumInBlur.locations,
      vcat(
        smbSpectrumInBlur.counts ./  (smbSpectrumInBlur.locations[2:end] - smbSpectrumInBlur.locations[1:end-1]),
        0.0,
      ),
      line = :steppost,
      label = "SMB blurred input",
    )
  end
  plot!(
    smbSpectrumOutReconstruct.locations,
    vcat(
      smbSpectrumOutReconstruct.counts ./  (smbSpectrumOutReconstruct.locations[2:end] - smbSpectrumOutReconstruct.locations[1:end-1]),
      0.0,
    ),
    line = :steppost,
    label = "SMB output (seaMass-restore --reconstruct)",
  )
  gui()

  # load SMB output spectrum
  smbSpectrumOut = SeaMass.SmbSpectrum(
    "data/out/" * filename * "/2.seamass-restore/" * filename * "." * id * ".smb",
    outputSpectrumID
  )

  # load SMB deconvolved output spectrum
  smbSpectrumOutDeconvolve = SeaMass.SmbSpectrum(
    "data/out/" * filename * "/3.seamass-restore_--deconvolve/" * filename * "." * id * ".smb",
    outputSpectrumID
  )

  # load SMB centroided output spectrum
  smbSpectrumOutCentroid = SeaMass.SmbSpectrum(
    "data/out/" * filename * "/5.seamass-restore_--centroid/" * filename * "." * id * ".smb",
    outputSpectrumID
  )

  plot(
    smbSpectrumOutReconstruct.locations,
    vcat(
      smbSpectrumOutReconstruct.counts ./  (smbSpectrumOutReconstruct.locations[2:end] - smbSpectrumOutReconstruct.locations[1:end-1]),
      0.0,
    ),
    line = :steppost,
    label = "SMB output (seaMass-restore --reconstruct)",
    title = "SMB proccessed comparison - " * filename,
    xlabel = "m/z (Th)",
    ylabel = "ion count density",
    reuse = false,
  )
  plot!(
    smbSpectrumOutDeconvolve.locations,
    smbSpectrumOutDeconvolve.counts,
    label = "SMB output (seaMass-restore --deconvolve)",
  )
  plot!(
    smbSpectrumOut.locations,
    smbSpectrumOut.counts,
    label = "SMB output (seaMass-restore)",
  )
  sticks!(
    smbSpectrumOutCentroid.locations,
    smbSpectrumOutCentroid.counts,
    label = "SMB output (seaMass-restore --centroid)",
    m = 4,
  )
  gui()

  # load mzMLb reconstructed output spectrum
  mzmlbSpectrumOutReconstruct = SeaMass.MzmlbSpectrum(
    "data/out/" * filename * "/4.seamass-restore_--reconstruct/" * filename * ".mzMLb",
    outputSpectrumID
  )

  # load mzMLb output spectrum
  mzmlbSpectrumOut = SeaMass.MzmlbSpectrum(
    "data/out/" * filename * "/2.seamass-restore/" * filename * ".mzMLb",
    outputSpectrumID
  )

  # load mzMLb centroided output spectrum
  mzmlbSpectrumOutCentroid = SeaMass.MzmlbSpectrum(
    "data/out/" * filename * "/5.seamass-restore_--centroid/" * filename * ".mzMLb",
    outputSpectrumID
  )

  # Compare mzMLb
  plot(
    smbSpectrumIn.locations,
    vcat(
      smbSpectrumIn.counts ./ (smbSpectrumIn.exposure .* (smbSpectrumIn.locations[2:end] - smbSpectrumIn.locations[1:end-1])),
      0.0,
    ),
    line = :steppost,
    label = "SMB input",
    title = "mzMLb comparison - " * filename,
    xlabel = "m/z (Th)",
    ylabel = "intensity density",
    reuse = false,
  )
  plot!(
    mzmlbSpectrumOutReconstruct.mzs,
    mzmlbSpectrumOutReconstruct.intensities,
    label = "mzMLb output (seaMass-restore --reconstruct)",
  )
  plot!(
    mzmlbSpectrumOut.mzs,
    mzmlbSpectrumOut.intensities,
    label = "mzMLb output (seaMass-restore)",
  )
  sticks!(
    mzmlbSpectrumOutCentroid.mzs,
    mzmlbSpectrumOutCentroid.intensities,
    label = "mzMLb output (seaMass-restore --centroid)",
    m = 4,
  )
  gui()

end

filename = "HYE124_TTOF6600_64var_lgillet_I150211_008__index_59994"
id = "p-55-227433333333"
inputSpectrumID = 1
outputSpectrumID = 1
test(filename, id, inputSpectrumID, outputSpectrumID)

filename = "P02U_Swath_1__mzWindow_602_605__scanTime_2300_3500"
id = "p"
inputSpectrumID = (67 - 1) * 51 + 1
outputSpectrumID = 67
test(filename, id, inputSpectrumID, outputSpectrumID)

println("Press <Enter> to finish")
readline()


@testset "DIIS" begin
    dm = Fermi.DIIS.DIISManager{Float32, Float32}()
    @test length(dm) == 0
    x = rand(10)
    y = rand(10)
    push!(dm, x, y)
    @test length(dm) == 1
    @test_throws BoundsError Fermi.DIIS.extrapolate(dm)
end


# Add fallbacks for predict_* which require mean, mode, median.

const BadModeTypes   = Union{AbstractArray{Continuous},Table(Continuous)}
const BadMeanTypes   = Union{AbstractArray{<:Finite},Table(Finite)}
const BadMedianTypes = Union{AbstractArray{<:Finite},Table(Finite)}

# mode:
predict_mode(m::Probabilistic, fitresult, Xnew) =
    predict_mode(m, fitresult, Xnew, Val(target_scitype(m)))
predict_mode(m, fitresult, Xnew, ::Any) =
    mode.(predict(m, fitresult, Xnew))
predict_mode(m, fitresult, Xnew, ::Val{<:BadModeTypes}) =
    throw(ArgumentError("Attempting to compute mode of predictions made "*
                        "by a model expecting `Continuous` targets. "))

# mean:
predict_mean(m::Probabilistic, fitresult, Xnew) =
    predict_mean(m, fitresult, Xnew, Val(target_scitype(m)))
predict_mean(m, fitresult, Xnew, ::Any) =
    mean.(predict(m, fitresult, Xnew))
predict_mean(m, fitresult, Xnew, ::Val{<:BadMeanTypes}) =
    throw(ArgumentError("Attempting to compute mean of predictions made "*
                        "by a model expecting `Finite` targets. "))

# median:
predict_median(m::Probabilistic, fitresult, Xnew) =
    predict_median(m, fitresult, Xnew, Val(target_scitype(m)))
predict_median(m, fitresult, Xnew, ::Any) =
    median.(predict(m, fitresult, Xnew))
predict_median(m, fitresult, Xnew, ::Val{<:BadMedianTypes}) =
    throw(ArgumentError("Attempting to compute median of predictions made "*
                        "by a model expecting `Finite` targets. "))

# not in MLJModelInterface as methodswith requires InteractiveUtils
MLJModelInterface.implemented_methods(::FI, M::Type{<:MLJType}) =
    getfield.(methodswith(M), :name) |> unique

# serialization fallbacks:
MLJModelInterface.save(filename, model, fitresult; kwargs...) = fitresult
MLJModelInterface.restore(filename, model, serializable_fitresult) =
                          serializable_fitresult

# to suppress inclusion of abstract types in the model registry.
for T in (:Supervised, :Unsupervised,
          :Interval, :Static, :Deterministic, :Probabilistic)
    ex = quote
        MLJModelInterface.is_wrapper(::Type{$T}) = true
    end
    eval(ex)
end


# This file is a part of RadiationDetectorDSP.jl, licensed under the MIT License (MIT).

__precompile__(true)

module RadiationDetectorDSP

using ArgCheck
using ArraysOfArrays
using Distributions
using DSP
using ElasticArrays
using LinearAlgebra
using ParallelProcessingTools
using Parameters
using RadiationDetectorSignals
using Random
using RecipesBase
using StaticArrays
using Statistics
using StatsBase
using TypedTables

# package code goes here

end # module


using Arrow, DataFrames
using PyPlot

include("plot_styles.jl")

basedir = "/work/sschaub/JuliaForHEP/final_plotting/"
output_dir = joinpath(basedir, "lola+all_1")
recorded_measures = DataFrame(Arrow.Table(joinpath(output_dir, "recorded_measures.arrow")))

fig, ax = subplots(figsize=(12, 9))
ax.plot([recorded_measures.train_loss recorded_measures.test_loss recorded_measures.validation_loss])
ax.set_title("Loss Values")
ax.set_xlabel("Epoch")
ax.set_ylabel("Loss")
ax.legend(["Training Loss", "Testing Loss", "Validation Loss"])
annotate_cms(ax)

fig.savefig(joinpath(output_dir, "losses.pdf"))


using .Networks

using Dates
using JuMP
using Printf

@with_kw mutable struct BalanceMarket <: AbstractMarket
    configs = EnergyMarketConfigs(problem_name = "BalanceMarket",
                                CONSIDER_TSOACTIONS_LIMITATIONS=true,
                                CONSIDER_TSOACTIONS_IMPOSITIONS=true,
                                REF_SCHEDULE_TYPE=PSCOPF.TSO()  #This can be the
                                                                # TSO if TSOBilevel updates the tso_schedule according to the preceding market decided values
                                                                # or the market too cause DECIDED values refer to the preceding ECH and it is the market who launched last at the preceding ech
                                )
end


# This script is intended to be a dependency for the hydropower example.
using PowerSystems
using Statistics
using Dates
using TimeSeries

# We can use some of the simple data that's been assembled for testing PowerSimulations.
PowerSystems.download(PowerSystems.TestData; branch = "master") # *note* add `force=true` to get a fresh copy
data_dir = joinpath(dirname(dirname(pathof(PowerSystems))), "data");

rawsys = PowerSystems.PowerSystemTableData(
    joinpath(data_dir, "5-bus-hydro"),
    100.0,
    joinpath(data_dir, "5-bus-hydro", "user_descriptors.yaml");
    generator_mapping_file = joinpath(data_dir, "5-bus-hydro", "generator_mapping.yaml"),
)
c_sys5_hy_uc = System(
    rawsys,
    timeseries_metadata_file = joinpath(
        data_dir,
        "forecasts",
        "5bus_ts",
        "7day",
        "timeseries_pointers_da_7day.json",
    ),
    time_series_in_memory = true,
)
transform_single_time_series!(c_sys5_hy_uc, 24, Hour(24))

c_sys5_hy_ed = System(
    rawsys,
    timeseries_metadata_file = joinpath(
        data_dir,
        "forecasts",
        "5bus_ts",
        "7day",
        "timeseries_pointers_rt_7day.json",
    ),
    time_series_in_memory = true,
)
transform_single_time_series!(c_sys5_hy_ed, 12, Hour(1))

c_sys5_hy_wk = System(rawsys, time_series_in_memory = true)

n = 3# number of days

# And an hourly system with longer time scales
MultiDay = collect(
    get_forecast_initial_times(c_sys5_hy_uc)[1]:Hour(24):(get_forecast_initial_times(
        c_sys5_hy_uc,
    )[1] + Day(n)),
);

for load in get_components(PowerLoad, c_sys5_hy_uc)
    fc_values = Vector{Float64}()
    for t in MultiDay
        fc = get_time_series_array(Deterministic, load, "max_active_power"; start_time = t)
        push!(fc_values, mean(values(fc)))
    end
    wk_fc = TimeArray(MultiDay, fc_values)
    add_time_series!(
        c_sys5_hy_wk,
        get_component(PowerLoad, c_sys5_hy_wk, get_name(load)),
        SingleTimeSeries("max_active_power", wk_fc),
    )
end

for gen in get_components(HydroGen, c_sys5_hy_uc)
    fc_values = Vector{Float64}()
    for t in MultiDay
        fc = get_time_series_array(Deterministic, gen, "max_active_power"; start_time = t)
        push!(fc_values, mean(values(fc)))
    end

    wk_fc = TimeArray(MultiDay, fc_values)

    add_time_series!(
        c_sys5_hy_wk,
        get_component(HydroGen, c_sys5_hy_wk, get_name(gen)),
        SingleTimeSeries("max_active_power", wk_fc),
    )
end

for gen in get_components(HydroEnergyReservoir, c_sys5_hy_uc)
    for label in ["storage_capacity", "inflow", "storage_target"]
        fc_values = Vector{Float64}()
        for t in MultiDay
            fc = get_time_series_array(
                Deterministic,
                gen,
                label;
                start_time = get_forecast_initial_times(c_sys5_hy_uc)[1],
            )
            push!(fc_values, mean(values(fc)))
        end

        wk_fc = TimeArray(MultiDay, fc_values)

        add_time_series!(
            c_sys5_hy_wk,
            get_component(HydroGen, c_sys5_hy_wk, get_name(gen)),
            SingleTimeSeries(label, wk_fc),
        )
    end
end
transform_single_time_series!(c_sys5_hy_wk, 2, Hour(48))


#!NOTE: Only methods for FlattenContinuous given here, so if all flattened, defaults to Real construct_flatten/unflatten
#!NOTE2: need to use unflattentype::UnflattenStrict/UnflattenFlexible instead of ::U so specializes correctly
############################################################################################
function construct_flatten(
    output::Type{T},
    flattentype::FlattenContinuous,
    unflattentype::UnflattenStrict,
    x::Union{I,Array{I}}
) where {T<:AbstractFloat, I<:Integer}
    v = I[]
    flatten_to_Int(x::Union{R,AbstractArray{R}}) where {R<:Real} = v
    unflatten_to_Int(v::Union{R,AbstractVector{R}}) where {R<:Real} = x
    return flatten_to_Int, unflatten_to_Int
end
function construct_flatten(
    output::Type{T},
    flattentype::FlattenContinuous,
    unflattentype::UnflattenFlexible,
    x::Union{I,Array{I}}
) where {T<:AbstractFloat, I<:Integer}
    v = I[]
    flatten_to_Int(x::Union{R,AbstractArray{R}}) where {R<:Real} = v
    unflatten_to_Int(v::Union{R,AbstractVector{R}}) where {R<:Real} = x
    return flatten_to_Int, unflatten_to_Int
end

############################################################################################
#Export
export
    construct_flatten


__precompile__()
module Utils

export get_memoryuse, initlogo, printparams

include("map_removeEQ.jl")
using .Map_removeEQ

using SeisIO, Printf, Dates, JLD2, FileIO, Distributed

const KB = 1024.0 #[bytes]
const MB = 1024.0 * KB
const GB = 1024.0 * MB

"""
get_memoryuse(InputDict::Dict)

evaluate memory use and estimate computational time
"""
function get_memoryuse(InputDict::Dict)

    trial_id        = 1

    while true
        global t1 = @elapsed EE = map_removeEQ(trial_id, InputDict) #[s]

		global Stest = []

        for i = 1:size(EE[1])[1]
			push!(Stest, EE[1][i])
		end

        dl = [Stest[i].misc["dlerror"] for i in 1:size(Stest)[1]]
        if issubset(0, dl)
            break;
        else
            trial_id += 1
        end
    end

    numofitr = InputDict["NumofTimestamp"] # number of iteration by parallel processing
    if trial_id == numofitr - 1
        error("all requests you submitted with input dictionary was failed. Please check the station availability in your request.")
    end

    mem_per_requestid = 1.2 * sizeof(Stest) / GB #[GB] *for the safty, required memory is multiplied by 1.2

    max_num_of_processes_per_parallelcycle = floor(Int64, InputDict["MAX_MEM_PER_CPU"]/mem_per_requestid)
    estimated_downloadtime = now() + Second(round(3 * t1 * numofitr / nprocs()))

	printstyled("---EQ REMOVAL STATS SUMMARY---\n"; color=:cyan, bold=true)

    println(@sprintf("Number of processes is %d.", nprocs()))

    totaldownloadsize = mem_per_requestid * numofitr
    if totaldownloadsize < MB
        totaldownloadsize = totaldownloadsize * GB / MB #[MB]
        sizeunit = "MB"
    else
        sizeunit = "GB"
    end

	println(@sprintf("One process returns %4.2e [%s] of dataset so maximum num of parallel processes is %d.",
	 			totaldownloadsize, sizeunit, max_num_of_processes_per_parallelcycle))
	println(@sprintf("Download will finish at %s.", round(estimated_downloadtime, Dates.Second(1))))
    println("*We have a time lag with processing time above, like in 10 minutes or so.*")
    println("*This estimation also changes if some processes fail and are skipped.*")

    return max_num_of_processes_per_parallelcycle

end

"""
printparams(param::Dict)

print parameters
"""
function printparams(param::Dict)
    printstyled("---Input Parameters---\n"; color=:cyan, bold=true)
    for key in keys(param)
        println(@sprintf("%-24s = %-10s", key, string(param["$key"])))
    end
end

"""
initlogo()

print initial logo
"""
function initlogo()

    print("
    _____      _      ______
   /  ___|    (_)     | ___ \\
   \\ `--.  ___ _ ___  | |_/ /___ _ __ ___   _____   _____
    `--. \\/ _ \\ / __| |    // _ \\ '_ ` _ \\ / _ \\ \\ / / _ \\
   /\\__/ /  __/ \\__ \\ | |\\ \\  __/ | | | | | (_) \\ V /  __/
   \\____/ \\___|_|___/ \\_| \\_\\___|_| |_| |_|\\___/ \\_/ \\___|
    ______ ____
   |  ____/ __ \\       |
   | |__ | |  | |      |
   |  __|| |  | |      |  v1.0 (Last update 07/07/2019)
   | |___| |__| |      |  © Kurama Okubo
   |______\\___\\_\\      |

")

    println("Job start running at "*string(now())*"\n")

end

end


using DrWatson
@quickactivate
using Test
using GenerativeAD

@testset "Models" begin
    include("knn.jl")
    include("vae.jl")
    include("aae.jl")
    include("gan.jl")
end


# Piracy that used to be requires, allowing Tracker.jl to be specialized for SciML

function RecursiveArrayTools.recursivecopy!(b::AbstractArray{T,N}, a::AbstractArray{T2,N}) where {T<:Tracker.TrackedArray,T2<:Tracker.TrackedArray,N}
    @inbounds for i in eachindex(a)
        b[i] = copy(a[i])
    end
end

DiffEqBase.value(x::Type{Tracker.TrackedReal{T}}) where {T} = T
DiffEqBase.value(x::Type{Tracker.TrackedArray{T,N,A}}) where {T,N,A} = Array{T,N}
DiffEqBase.value(x::Tracker.TrackedReal) = x.data
DiffEqBase.value(x::Tracker.TrackedArray) = x.data

DiffEqBase.promote_u0(u0::Tracker.TrackedArray, p::Tracker.TrackedArray, t0) = u0
DiffEqBase.promote_u0(u0::AbstractArray{<:Tracker.TrackedReal}, p::Tracker.TrackedArray, t0) = u0
DiffEqBase.promote_u0(u0::Tracker.TrackedArray, p::AbstractArray{<:Tracker.TrackedReal}, t0) = u0
DiffEqBase.promote_u0(u0::AbstractArray{<:Tracker.TrackedReal}, p::AbstractArray{<:Tracker.TrackedReal}, t0) = u0
DiffEqBase.promote_u0(u0, p::Tracker.TrackedArray, t0) = Tracker.track(u0)
DiffEqBase.promote_u0(u0, p::AbstractArray{<:Tracker.TrackedReal}, t0) = eltype(p).(u0)

@inline DiffEqBase.fastpow(x::Tracker.TrackedReal, y::Tracker.TrackedReal) = x^y
@inline Base.any(f::Function, x::Tracker.TrackedArray) = any(f, Tracker.data(x))

# Support adaptive with non-tracked time
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::Tracker.TrackedArray, t) where {N}
    sqrt(sum(abs2, DiffEqBase.value(u)) / length(u))
end
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::AbstractArray{<:Tracker.TrackedReal,N}, t) where {N}
    sqrt(sum(x -> DiffEqBase.ODE_DEFAULT_NORM(x[1], x[2]), zip((DiffEqBase.value(x) for x in u), Iterators.repeated(t))) / length(u))
end
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::Array{<:Tracker.TrackedReal,N}, t) where {N}
    sqrt(sum(x -> DiffEqBase.ODE_DEFAULT_NORM(x[1], x[2]), zip((DiffEqBase.value(x) for x in u), Iterators.repeated(t))) / length(u))
end
@inline DiffEqBase.ODE_DEFAULT_NORM(u::Tracker.TrackedReal, t) = abs(DiffEqBase.value(u))

# Support TrackedReal time, don't drop tracking on the adaptivity there
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::Tracker.TrackedArray, t::Tracker.TrackedReal) where {N}
    sqrt(sum(abs2, u) / length(u))
end
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::AbstractArray{<:Tracker.TrackedReal,N}, t::Tracker.TrackedReal) where {N}
    sqrt(sum(x -> DiffEqBase.ODE_DEFAULT_NORM(x[1], x[2]), zip(u, Iterators.repeated(t))) / length(u))
end
@inline function DiffEqBase.ODE_DEFAULT_NORM(u::Array{<:Tracker.TrackedReal,N}, t::Tracker.TrackedReal) where {N}
    sqrt(sum(x -> DiffEqBase.ODE_DEFAULT_NORM(x[1], x[2]), zip(u, Iterators.repeated(t))) / length(u))
end
@inline DiffEqBase.ODE_DEFAULT_NORM(u::Tracker.TrackedReal, t::Tracker.TrackedReal) = abs(u)

function DiffEqBase.solve_up(prob::DiffEqBase.DEProblem, sensealg::Union{DiffEqBase.AbstractSensitivityAlgorithm,Nothing}, u0::Tracker.TrackedArray, p::Tracker.TrackedArray, args...; kwargs...)
    Tracker.track(solve_up, prob, sensealg, u0, p, args...; kwargs...)
end

function DiffEqBase.solve_up(prob::DiffEqBase.DEProblem, sensealg::Union{DiffEqBase.AbstractSensitivityAlgorithm,Nothing}, u0::Tracker.TrackedArray, p, args...; kwargs...)
    Tracker.track(solve_up, prob, sensealg, u0, p, args...; kwargs...)
end

function DiffEqBase.solve_up(prob::DiffEqBase.DEProblem, sensealg::Union{DiffEqBase.AbstractSensitivityAlgorithm,Nothing}, u0, p::Tracker.TrackedArray, args...; kwargs...)
    Tracker.track(solve_up, prob, sensealg, u0, p, args...; kwargs...)
end

Tracker.@grad function DiffEqBase.solve_up(prob, sensealg::Union{Nothing,DiffEqBase.AbstractSensitivityAlgorithm},
    u0, p, args...;
    kwargs...)
    _solve_adjoint(prob, sensealg, Tracker.data(u0), Tracker.data(p), args...; kwargs...)
end


"""
	match_patches(img::AbstractArray{Float64},
			refIndex::Array{CartesianIndex{2},2},
			patchSize::CartesianIndex{2},
			searchWindow::CartesianIndex{2},
			nMatch::Int64,
			distF::Function)

Full-search block matching algorithm for BM3DDenoise
"""
function match_patches(
	img::AbstractArray{Float64,2},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	searchWindow::CartesianIndex{2},
	nMatch::Int64,
	distF::Function,
)

	refIndexH, refIndexW = size(refIndex)

	matches = Array{CartesianIndex{2}}(undef, refIndexH, refIndexW, nMatch)

	dist = Array{Float64}(undef, refIndexH, refIndexW, nMatch + 1)# dist[i, j, end] is the largest item in current dist[i, j, 1:end-1]
	dist .= typemax(Float64)

	maxDistMatch = Array{UInt}(undef, refIndexH, refIndexW)# maxDistMatch[i, j] is the position in matches[i, j, :] corresponding to the largest distance block
	maxDistMatch .= 1

	lockPool = Array{ReentrantLock}(undef, refIndexH, refIndexW)
	for i in 1:length(lockPool)
		lockPool[i] = ReentrantLock()
	end

	@inbounds Threads.@threads for j in 1:refIndexW
		for i in 1:refIndexH
			p = refIndex[i, j]
			iE = min(refIndexH, i + searchWindow[1])
			jE = min(refIndexW, j + searchWindow[2])
			imgRef = @view img[p:(p + patchSize - CartesianIndex(1, 1))]

			# In order to skip the current block and save unnecessary judgment, it is split into three parts.
			for sj in (j + 1):jE, si in (i + 1):iE
				@views weight = distF(
					imgRef,
					img[refIndex[si, sj]:(refIndex[
						si,
						sj,
					] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(si, sj)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[si, sj]) do
					if weight < dist[si, sj, nMatch + 1]
						maxDistImg = maxDistMatch[si, sj]
						matches[si, sj, maxDistImg] = CartesianIndex(i, j)
						dist[si, sj, maxDistImg] = weight
						dist[si, sj, nMatch + 1], maxDistMatch[si, sj] =
							findmax(@views dist[si, sj, 1:nMatch])
					end
				end
			end
			for si in (i + 1):iE
				@views weight = distF(
					imgRef,
					img[refIndex[si, j]:(refIndex[si, j] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(si, j)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[si, j]) do
					if weight < dist[si, j, nMatch + 1]
						maxDistImg = maxDistMatch[si, j]
						matches[si, j, maxDistImg] = CartesianIndex(i, j)
						dist[si, j, maxDistImg] = weight
						dist[si, j, nMatch + 1], maxDistMatch[si, j] =
							findmax(@views dist[si, j, 1:nMatch])
					end
				end
			end
			for sj in (j + 1):jE
				@views weight = distF(
					imgRef,
					img[refIndex[i, sj]:(refIndex[i, sj] + patchSize - CartesianIndex(1, 1))],
				)
				lock(lockPool[i, j]) do
					if weight < dist[i, j, nMatch + 1]
						maxDistImg = maxDistMatch[i, j]
						matches[i, j, maxDistImg] = CartesianIndex(i, sj)
						dist[i, j, maxDistImg] = weight
						dist[i, j, nMatch + 1], maxDistMatch[i, j] =
							findmax(@views dist[i, j, 1:nMatch])
					end
				end

				lock(lockPool[i, sj]) do
					if weight < dist[i, sj, nMatch + 1]
						maxDistImg = maxDistMatch[i, sj]
						matches[i, sj, maxDistImg] = CartesianIndex(i, j)
						dist[i, sj, maxDistImg] = weight
						dist[i, sj, nMatch + 1], maxDistMatch[i, sj] =
							findmax(@views dist[i, sj, 1:nMatch])
					end
				end
			end
		end
	end
	matches
end

# For color images
function match_patches(
	img::Array{Float64,3},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	searchWindow::CartesianIndex{2},
	nMatch::Int64,
	distF::Function,
)
	match_patches(view(img, :, :, 1), refIndex, patchSize, searchWindow, nMatch, distF)
end


"""
    $(TYPEDEF)

Make a two-dimensional, one-legged robot walk forward as fast as possible. The robot model is
based on the work T. Erez, Y. Tassa, and E. Todorov: ["Infinite Horizon Model Predictive
Control for Nonlinear Periodic Tasks"](https://homes.cs.washington.edu/~todorov/papers/ErezRSS11.pdf), 2011.

# Spaces

* **State: (20, )**
* **Action: (3, )**
* **Observation: (11, )**
"""
mutable struct HopperV2{Sim, SSpace, OSpace} <: AbstractMuJoCoEnvironment
    sim::Sim
    statespace::SSpace
    observationspace::OSpace
    last_torso_x::Float64
    randreset_distribution::Uniform{Float64}
    function HopperV2(sim::MJSim)
        sspace = MultiShape(
            simstate=statespace(sim),
            last_torso_x=ScalarShape(Float64)
        )
        ospace = MultiShape(
            cropped_qpos = VectorShape(Float64, sim.m.nq - 1),
            qvel = VectorShape(Float64, sim.m.nv)
        )
        env = new{typeof(sim), typeof(sspace), typeof(ospace)}(
            sim, sspace, ospace, 0, Uniform(-0.005, 0.005)
        )
        reset!(env)
    end
end

HopperV2() = first(tconstruct(HopperV2, 1))

function LyceumBase.tconstruct(::Type{HopperV2}, n::Integer)
    modelpath = joinpath(@__DIR__, "hopper.xml")
    [HopperV2(s) for s in tconstruct(MJSim, n, modelpath, skip=4)]
end

@inline getsim(env::HopperV2) = env.sim

@inline LyceumBase.statespace(env::HopperV2) = env.statespace

function LyceumBase.getstate!(state, env::HopperV2)
    checkaxes(statespace(env), state)
    @uviews state begin
        shaped = statespace(env)(state)
        getstate!(shaped.simstate, env.sim)
        shaped.last_torso_x = env.last_torso_x
    end
    state
end

function LyceumBase.setstate!(env::HopperV2, state)
    checkaxes(statespace(env), state)
    @uviews state begin
        shaped = statespace(env)(state)
        setstate!(env.sim, shaped.simstate)
        env.last_torso_x = shaped.last_torso_x
    end
    env
end

@inline LyceumBase.observationspace(env::HopperV2) = env.observationspace

function LyceumBase.getobservation!(obs, env::HopperV2)
    checkaxes(observationspace(env), obs)
    qpos = env.sim.d.qpos
    @views @uviews qpos obs begin
        shaped = observationspace(env)(obs)
        copyto!(shaped.cropped_qpos, qpos[2:end])
        copyto!(shaped.qvel, env.sim.d.qvel)
        clamp!(shaped.qvel, -10, 10)
    end
    obs
end

function LyceumBase.getreward(state, action, ::Any, env::HopperV2)
    checkaxes(statespace(env), state)
    checkaxes(actionspace(env), action)
    @uviews state begin
        shapedstate = statespace(env)(state)
        alive_bonus = 1.0
        reward = (_torso_x(shapedstate, env) - shapedstate.last_torso_x) / timestep(env)
        reward += alive_bonus
        reward -= 1e-3 * sum(x->x^2, action)
        reward
    end
end

function LyceumBase.reset!(env::HopperV2)
    reset!(env.sim)
    env.last_torso_x = _torso_x(env)
    env
end

function LyceumBase.randreset!(rng::AbstractRNG, env::HopperV2)
    reset_nofwd!(env.sim)
    perturb!(rng, env.randreset_distribution, env.sim.d.qpos)
    perturb!(rng, env.randreset_distribution, env.sim.d.qvel)
    forward!(env.sim)
    env.last_torso_x = _torso_x(env)
    env
end

function LyceumBase.step!(env::HopperV2)
    env.last_torso_x = _torso_x(env)
    step!(env.sim)
    env
end

function LyceumBase.isdone(state, ::Any, env::HopperV2)
    checkaxes(statespace(env), state)
    @uviews state begin
        shapedstate = statespace(env)(state)
        torso_x = _torso_x(shapedstate, env)
        height = _torso_height(shapedstate, env)
        torso_ang = _torso_ang(shapedstate, env)
        qpos = shapedstate.simstate.qpos
        qvel = shapedstate.simstate.qvel

        done = !(
            all(isfinite, state)
            && all(x->abs(x) < 100, uview(qpos, 3:length(qpos)))
            && all(x->abs(x) < 100, uview(qvel))
            && height > 0.7
            && abs(torso_ang) < 0.2
        )
        done
    end
end

@inline _torso_x(shapedstate::ShapedView, ::HopperV2) = shapedstate.simstate.qpos[1]
@inline _torso_x(env::HopperV2) = env.sim.d.qpos[1]
@inline _torso_height(shapedstate::ShapedView, ::HopperV2) = shapedstate.simstate.qpos[2]
@inline _torso_ang(shapedstate::ShapedView, ::HopperV2) = shapedstate.simstate.qpos[3]


function mesh_material(context, matsys, plot, color_obs = plot.color)
    specular = plot.specular[]
    shininess = plot.shininess[]
    color = to_value(color_obs)
    color_signal = if color isa AbstractMatrix{<:Number}
        tex = RPR.MaterialNode(matsys, RPR.RPR_MATERIAL_NODE_IMAGE_TEXTURE)
        map(color_obs, plot.colormap, plot.colorrange) do color, cmap, crange
            color_interp = Makie.interpolated_getindex.((to_colormap(cmap),), color, (crange,))
            img = RPR.Image(context, collect(color_interp'))
            set!(tex, RPR.RPR_MATERIAL_INPUT_DATA, img)
            return tex
        end
    elseif color isa AbstractMatrix{<:Colorant}
        tex = RPR.MaterialNode(matsys, RPR.RPR_MATERIAL_NODE_IMAGE_TEXTURE)
        map(color_obs) do color
            img = RPR.Image(context, Makie.el32convert(color'))
            set!(tex, RPR.RPR_MATERIAL_INPUT_DATA, img)
            return tex
        end
    elseif color isa Colorant || color isa Union{String,Symbol}
        map(to_color, color_obs)
    elseif color isa Nothing
        # ignore!
        color_obs
    else
        error("Unsupported color type for RadeonProRender backend: $(typeof(color))")
    end

    material = to_value(get(plot, :material, RPR.DiffuseMaterial(matsys)))

    map(color_signal) do color
        if !isnothing(color) && hasproperty(material, :color)
            material.color = color
        end
    end

    return material.node
end

function to_rpr_object(context, matsys, scene, plot::Makie.Mesh)
    # Potentially per instance attributes
    rpr_mesh = RPR.Shape(context, to_value(plot[1]))
    material = mesh_material(context, matsys, plot)
    map(plot.model) do m
        RPR.transform!(rpr_mesh, m)
    end
    set!(rpr_mesh, material)
    return rpr_mesh
end

function to_rpr_object(context, matsys, scene, plot::Makie.MeshScatter)
    # Potentially per instance attributes
    positions = to_value(plot[1])
    m_mesh = convert_attribute(plot.marker[], key"marker"(), key"meshscatter"())
    marker = RPR.Shape(context, m_mesh)
    instances = [marker]
    n_instances = length(positions)
    RPR.rprShapeSetObjectID(marker, 0)
    material = if haskey(plot, :material)
        plot.material[]
    else
        RPR.DiffuseMaterial(matsys)
    end
    set!(marker, material)
    for i in 1:(n_instances-1)
        inst = RPR.Shape(context, marker)
        RPR.rprShapeSetObjectID(inst, i)
        push!(instances, inst)
    end

    color = to_color(plot.color[])
    if color isa AbstractVector{<:Number}
        cmap = to_colormap(plot.colormap[])
        crange = plot.colorrange[]
        color_from_num = Makie.interpolated_getindex.((cmap,), color, (crange,))

        object_id = RPR.InputLookupMaterial(matsys)
        object_id.value = RPR.RPR_MATERIAL_NODE_LOOKUP_OBJECT_ID

        uv = object_id * Vec3f(0, 1/n_instances, 0)

        tex = RPR.Texture(matsys, collect(color_from_num'); uv = uv)

        material.color = tex
    elseif color isa Colorant
        material.color = color
    else
        error("Unsupported color type for RadeonProRender backend: $(typeof(color))")
    end

    markersize = Makie.to_3d_scale(plot.markersize[])

    scales = if markersize isa Vec
        Iterators.repeated(markersize, n_instances)
    else
        markersize
    end

    rotations = Makie.to_rotation(plot.rotations[])

    rotations = if rotations isa Makie.Quaternion
        Iterators.repeated(rotations, n_instances)
    else
        rotations
    end

    for (instance, position, scale, rotation) in zip(instances, positions, scales, rotations)
        mat = Makie.transformationmatrix(position, scale, rotation)
        transform!(instance, mat)
    end

    return instances
end

function to_rpr_object(context, matsys, scene, plot::Makie.Surface)
    x = plot[1]
    y = plot[2]
    z = plot[3]

    function grid(x, y, z, trans)
        g = map(CartesianIndices(z)) do i
            p = Point3f(Makie.get_dim(x, i, 1, size(z)), Makie.get_dim(y, i, 2, size(z)), z[i])
            return Makie.apply_transform(trans, p)
        end
        return vec(g)
    end

    positions = lift(grid, x, y, z, Makie.transform_func_obs(plot))
    r = Tesselation(Rect2f((0, 0), (1, 1)), size(z[]))
    # decomposing a rectangle into uv and triangles is what we need to map the z coordinates on
    # since the xyz data assumes the coordinates to have the same neighouring relations
    # like a grid
    faces = decompose(GLTriangleFace, r)
    uv = decompose_uv(r)
    # with this we can beuild a mesh
    mesh = GeometryBasics.Mesh(meta(vec(positions[]), uv=uv), faces)

    rpr_mesh = RPR.Shape(context, mesh)
    color = plot.color[]
    material = mesh_material(context, matsys, plot, color isa AbstractMatrix ? plot.color : z)
    set!(rpr_mesh, material)
    return rpr_mesh
end

using FileIO

@recipe(Matball, material) do scene
    return Attributes(
        base = Makie.automatic,
        inner = Makie.automatic,
        outer = Makie.automatic,
        color = :blue
    )
end

function Makie.plot!(plot::Matball)
    base = plot.material[]
    for name in [:base, :inner, :outer]
        mat = getproperty(plot, name)[]
        mat = mat isa Makie.Automatic ? base : mat
        mesh = load(assetpath("matball_$(name).obj"))
        mesh!(plot, mesh, material=mat, color=plot.color)
    end
    return plot
end


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

export LAPACKException,
       ARPACKException,
       SingularException,
       PosDefException,
       RankDeficientException

struct LAPACKException <: Exception
    info::BlasInt
end

struct ARPACKException <: Exception
    info::String
end

function ARPACKException(i::Integer)
    if i == -8
        return ARPACKException("error return from calculation of a real Schur form.")
    elseif i == -9
        return ARPACKException("error return from calculation of eigenvectors.")
    elseif i == -14
        return ARPACKException("did not find any eigenvalues to sufficient accuracy. Try with a different starting vector or more Lanczos vectors by increasing the value of ncv.")
    end
    return ARPACKException("unspecified ARPACK error: $i")
end

struct SingularException <: Exception
    info::BlasInt
end

struct PosDefException <: Exception
    info::BlasInt
end
function Base.showerror(io::IO, ex::PosDefException)
    print(io, "PosDefException: matrix is not ")
    if ex.info == -1
        print(io, "Hermitian")
    else
        print(io, "positive definite")
    end
    print(io, "; Cholesky factorization failed.")
end

struct RankDeficientException <: Exception
    info::BlasInt
end


# this script does plotting for our paper
# ------------------------
# Including (in order):
# 1. MODEL EVALUATION: fertility experiments
#    1. UE-BMI under baseline scenario
#    2. Baseline compared with accounting results
#    3. Fertility experiments and UE-BMI
#    4. Summary table for benchmark
#    5. correlations (during plotting years) between econ vars & aging population ratio
# 2. MODEL EVALUATION: contribution rate increases
#    1. Rising of firm’s contribution rate to UE-BMI
#    2. ζ elasticity of four economic variables
# 3. MODEL EVALUATION: contract reforms on individual accounts
#    1. Reforms of individual accounts of UE-BMI
#  -----------------------
# NOTE: all figures are output to "pwd()/output/"

include("$(pwd())/main.jl") # if not run main.jl yet, run it first

# -----------------------------------
# SECTION introduction
# -----------------------------------
# NOTE: set pwd() as the root directory of the project to correctly cite paths!
   # --------- for plotting
   import EasyPlot
   using PyPlot
   # --------- for analysis & I/O
   import Statistics
   import DataFrames, CSV

# -----------------------------------
# SECTION: baseline & fertility experiments plotting
# -----------------------------------
# 0. define expr, collection; then run Fertiltiy Experiments
include("$(pwd())/scripts/subsections_PlotForPaper/sub_01_Def2RunFertilityExperiments.jl")

# 1. baseline plotting (no channel analysis)
   # Figure: Baseline compared with accounting results (BenchmarkCpAccount.pdf)
   # Figure: UE-BMI under baseline scenario (BenchProfile.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_02_Plot4Baseline.jl")

# 2. fertility experiments plotting (no channel analysis)
   # NOTE: including def, expr, eval
   # Figure: Fertility shocks and UE-BMI (FertilityShockProfile.pdf)
   # Table: Summary of baseline simulation (SummaryBench.csv)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_03_Plot4Baseline&FertilityExperiments.jl")

# 3. compute cor( econ var, aging population ratio )
   # No output file
include("$(pwd())/scripts/subsections_PlotForPaper/sub_04_Plot4BaselineCor.jl")

# 4. channel analysis in steady states
   # NOTE: based on the decomposition of gap
   # NOTE: including def, expr, eval
   # Figure: Effect decomposition in steady state 2010 parameters (Channel_DecomposeInSState.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_05_Plot4ChannelAnalysis.jl")

# -----------------------------------
# SECTION: contribution rates increase
# -----------------------------------
# 0. define expr, collection; then get the econ vars under lattice of zeta values
include("$(pwd())/scripts/subsections_PlotForPaper/sub_06_Def&RunContributionRateRise.jl")

# 1. the four economic vars under different zeta values, plotting
   # Figure: Rising of firm’s contribution rate to UE-BMI (ContributionRateRise.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_07_Plot4ContributionRateRise.jl")

# 2. define expr, collection; then compute the zeta elasticities of diff econ vars
   # NOTE: requires "sub_06_Def&RunContributionRateRise.jl" run
include("$(pwd())/scripts/subsections_PlotForPaper/sub_08_Def&RunElasticityOfZeta.jl")

# 3. elasticity analysis on zeta, plotting
   # Figure: ζ elasticity of four economic variables (ContributionRateRise_Elasticity.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_09_Plot4ZetaElasticity.jl")

# -----------------------------------
# SECTION: UE-BMI contract reforms on individual accounts
# -----------------------------------
# 0. define expr, collection; then get the econ vars under diff scenarios
include("$(pwd())/scripts/subsections_PlotForPaper/sub_10_Def&RunIndiAcctReform.jl")

# 1. the four economic vars under different scenarios, plotting
   # Figure: Reforms of individual accounts of UE-BMI (IndiAcctReform.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_11_Plot4IndiAcctReform.jl")

# 2. crowding out caused by cancelling individual accounts, plotting
   # Figure: Crowding out effects (CrowdingOutEffect.pdf)
include("$(pwd())/scripts/subsections_PlotForPaper/sub_12_Plot4CrowdingOutEffect.jl")

#


"""
    Proof of concept for Caesar-ROS integration
    (check Caesar wiki for details/instructions)

    To do:
    - re-enable JSON replies
    - switch to a pattern that does not require use of global variables
    - periodic export of factor graph object
"""

using RobotOS

# standard types
@rosimport geometry_msgs.msg: Point, Pose2D
# bespoke types
@rosimport caesar_ros.srv: AddVariable, AddPrior3D, AddFactorZPR, AddFactorXYH

rostypegen()
using .geometry_msgs.msg
using .caesar_ros.srv

### Load Caesar mmisam stuff
using RoME
using DistributedFactorGraphs

using JSON2
using DocStringExtensions

global fg

"""
    $SIGNATURES

Service callback for adding a variable to the factor graph.

Notes
- See https://github.com/JuliaRobotics/DistributedFactorGraphs.jl/issues/134
"""
function add_variable(req::AddVariableRequest)
    @info("received add_variable request", req)
    global fg
    reply = AddVariableResponse("failed to add variable")
    type_symbol = Symbol(req.type)
    if in(type_symbol, [:Pose2, :Pose3, :Point3])
        global fg
        vars = ls(fg)
        id = Symbol(req.id)
        if(!in(id, vars))
            retv = addVariable!(fg, id, getfield(Main, type_symbol))
            reply.status = JSON2.write(packVariable(fg, retv))
        else
            @warn("Failed to add variable - repeat variable")
        end
    else
        @warn("Failed to add variable - unsupported type", type_symbol)
    end
    return(reply)
end

"""
    $SIGNATURES

Service callback for adding a binary XYH partial factor.
"""
function add_factor_xyh(req::AddFactorXYHRequest )
    @info("received add_factor_xyh request", req)
    id0=Symbol(req.id0)
    id1=Symbol(req.id1)
    global fg
    vars = ls(fg)
    reply = AddFactorXYHResponse()
    if (in(id0, vars) & in(id1, vars))
        mu_xy = [req.x, req.y]
        Sigma = reshape(req.covariance,3,3)
        Sigma_xy = Sigma[1:2,1:2]
        z_xyh = Pose3Pose3XYYaw(MvNormal(mu_xy, Sigma[1:2,1:2]), Normal(req.yaw, Sigma[3,3]))
        retv = addFactor!(fg, [id0, id1], z_xyh)
        # @show reply.status = JSON2.write(packVariable(fg, retv))
    else
        @warn("Failed to add FactorXYH - missing pose")
        reply.status="failed"
    end

    return(reply)
end

"""
    $SIGNATURES

Service callback for adding a unary  partial factor.
"""
function add_factor_zpr(req::AddFactorZPRRequest )
    @info("received add_factor_zpr request", req)
    id = Symbol(req.id)
    global fg
    vars = ls(fg)
    reply = AddFactorZPRResponse()
    if (in(id,vars) )
        mu_z = req.z
        mu_rp = [req.roll, req.pitch]
        Sigma_zpr = reshape(req.covariance, 3, 3)
        sigma_z = Sigma_zpr[1,1]
        Sigma_rp = Sigma_zpr[3:-1:2,3:-1:2]
        z_zpr = PriorPose3ZRP(Normal(mu_z, sigma_z), MvNormal(mu_rp, Sigma_rp))
        retv = addFactor!(fg, [id], z_zpr)
        # reply.status = JSON2.write(packVariable(fg, retv)) # breaks
    else
        @warn("Failed to add FactorZPR - missing pose")
        reply.status="failed"
    end

    return(reply)
end

function main()
    init_node("caesar_srv_endpoint")

    global fg
    fg = initfg()

    # services - variable
    add_variable_srv = Service("AddVariable", caesar_ros.srv.AddVariable, add_variable)
    # services - factors
    add_factor_zpr_srv = Service("AddFactorZPR",caesar_ros.srv.AddFactorZPR, add_factor_zpr)
    add_factor_xyh_srv = Service("AddFactorXYH",caesar_ros.srv.AddFactorXYH, add_factor_xyh )

    # main loop
    @info "services have been set up; entering main loop"
    loop_rate = Rate(5.0)
    while ! is_shutdown()
        rossleep(loop_rate)
    end
end

main()


push!(LOAD_PATH, "/home/ian/Grad/Research/Disks/DiskJockey/movie/")
# push!(LOAD_PATH, "/n/home07/iczekala/DiskJockey/")
# push!(LOAD_PATH, "/pool/scout0/DiskJockey/")

using consonance

using ArgParse

s = ArgParseSettings()
@add_arg_table s begin
    # "--opt1"
    # help = "an option with an argument"
    "--run_index", "-r"
    help = "Output run index"
    default = 0
    arg_type = Int
end

parsed_args = parse_args(ARGS, s)
run_index = parsed_args["run_index"]

imgdir = moviedir * @sprintf("g%02d/", run_index)

using DiskJockey.constants
using DiskJockey.image

import YAML
config = YAML.load(open("config.yaml"))
dpc = config["parameters"]["dpc"][1]

import PyPlot.plt
using LaTeXStrings

global norm = plt[:Normalize](vmin=log10(vmax_g) - 4, vmax=log10(vmax_g), clip=false)

# Plot the channel maps using sky convention
function plot_chmaps(img::image.SkyImage, id::Int)

    fig, ax = plt[:subplots](ncols=nchan, figsize=(6.4, 1.6))
    # Image needs to be flipped along RA dimension
    ext = (img.ra[end], img.ra[1], img.dec[1], img.dec[end])

    for col=1:nchan

        ax[col][:xaxis][:set_ticklabels]([])
        ax[col][:yaxis][:set_ticklabels]([])

        # Set all labels but the leftmost to blank
        # if col != 1
        #     ax[col][:xaxis][:set_ticklabels]([])
        #     ax[col][:yaxis][:set_ticklabels]([])
        # else
        #     ax[col][:set_xlabel](L"$\Delta \alpha$ ('')", size=12)
        #     ax[col][:set_ylabel](L"$\Delta \delta$ ('')", size=12)
        #     ax[col][:tick_params](axis="both", which="major", labelsize=10)
        # end

        #Flip the frame for Sky convention
        frame = flipdim(img.data[:,:,col], 2)
        frame += 1e-10 #Add a tiny bit so that we don't have log10(0)
        lframe = log10(frame)
        max = maximum(lframe)
        ax[col][:imshow](lframe, extent=ext, norm=norm, interpolation="none", origin="lower", cmap=plt[:get_cmap]("PuBu"))
        if col==1
            ax[col][:annotate](@sprintf("%.1f km/s", vels[col]), (0.1, 0.78), xycoords="axes fraction", size=12)
        else
            ax[col][:annotate](@sprintf("%.1f", vels[col]), (0.1, 0.78), xycoords="axes fraction", size=12)
        end

    end

    # Annotate the parameters
    p = pars[id]
    mass = p.M_star
    r_c = p.r_c
    incl = p.incl
    label_incl = L"$i$: " * @sprintf("%.0f", incl) * L"${}^\circ$"
    label_mass = L"$M_\ast$: " * @sprintf("%.2f", mass) * L" $M_\odot$"
    fig[:text](0.01, 0.87, label_mass, size=16)
    fig[:text](0.25, 0.87, label_incl, size=16)

    fig[:subplots_adjust](wspace=0.08, top=0.9, bottom=0.01, left=0.02, right=0.98)

    #plt.savefig(outdir * @sprintf("g%04d.png", id), dpi=150)
    plt[:savefig](imgdir * @sprintf("g%04d.svg", id), transparent=true)
    plt[:close]("all")

end

# How many frames per process?
start = run_index * nframes_per_proc + 1

ids = Int[i for i=start:(start + nframes_per_proc)]

for id in ids
    if id <= nframes
        fname = imgdir * @sprintf("gimage%04d.out", id)
        im = imread(fname)
        skim = imToSky(im, dpc)
        plot_chmaps(skim, id)
    else
        break
    end
end


using ALifeBenchmark

"""
rm = RandomModel(1000, 10)

results = run_simulation(rm, 100)

history = History(results)

println(get_total_fitness_variance(history))
println(get_biotic_variance(history))
println(get_abiotic_variance(history))
println(get_mutational_variance(history))
println(get_inherent_variance(history))
"""

using Profile
using Printf

function f()
    model = TierraModel(LARGE_ANCESTOR)
    for _ in 1:1_000_000
        execute_slice!(model)
    end
end

f()

@time f()

@profile f()

"""
while true

    break
    #print(model)
    @printf "Memory in use: %2i %% \t Num of organisms: %i \t Num of free blocks: %i \n" round(Int64, 100*model.used_memory / model.memory_size) length(model.organism_keys) length(model.free_blocks)
    # readline()
end
"""


__precompile__()
module MathOptInterfaceXpress

export XpressOptimizer

using Xpress
const XPR = Xpress
using MathOptInterface
const MOI = MathOptInterface
using LinQuadOptInterface
const LQOI = LinQuadOptInterface

const SUPPORTED_OBJECTIVES = [
    LQOI.Linear,
    LQOI.Quad
]

const SUPPORTED_CONSTRAINTS = [
    (LQOI.Linear, LQOI.EQ),
    (LQOI.Linear, LQOI.LE),
    (LQOI.Linear, LQOI.GE),
    (LQOI.Linear, LQOI.IV),
    (LQOI.Quad, LQOI.EQ),
    (LQOI.Quad, LQOI.LE),
    (LQOI.Quad, LQOI.GE),
    (LQOI.SinVar, LQOI.EQ),
    (LQOI.SinVar, LQOI.LE),
    (LQOI.SinVar, LQOI.GE),
    (LQOI.SinVar, LQOI.IV),
    (LQOI.SinVar, MOI.ZeroOne),
    (LQOI.SinVar, MOI.Integer),
    (LQOI.VecVar, LQOI.SOS1),
    (LQOI.VecVar, LQOI.SOS2),
    (LQOI.VecVar, MOI.Nonnegatives),
    (LQOI.VecVar, MOI.Nonpositives),
    (LQOI.VecVar, MOI.Zeros),
    (LQOI.VecLin, MOI.Nonnegatives),
    (LQOI.VecLin, MOI.Nonpositives),
    (LQOI.VecLin, MOI.Zeros)
]

mutable struct XpressOptimizer <: LQOI.LinQuadOptimizer
    LQOI.@LinQuadOptimizerBase
    params::Dict{Any,Any}
    XpressOptimizer(::Void) = new()
end

LQOI.LinearQuadraticModel(::Type{XpressOptimizer}, env) = XPR.Model()

function XpressOptimizer(; kwargs...)

    env = nothing
    m = XpressOptimizer(nothing)
    m.params = Dict{Any,Any}()
    MOI.empty!(m)
    for (name,value) in kwargs
        m.params[name] = value
        XPR.setparam!(m.inner, XPR.XPRS_CONTROLS_DICT[name], value)
    end
    return m
end

function MOI.empty!(m::XpressOptimizer)
    MOI.empty!(m,nothing)
    for (name,value) in m.params
        XPR.setparam!(m.inner, XPR.XPRS_CONTROLS_DICT[name], value)
    end
end

LQOI.supported_constraints(s::XpressOptimizer) = SUPPORTED_CONSTRAINTS
LQOI.supported_objectives(s::XpressOptimizer) = SUPPORTED_OBJECTIVES

backend_type(m::XpressOptimizer, ::MOI.GreaterThan{T}) where T = Cchar('G')
backend_type(m::XpressOptimizer, ::MOI.LessThan{T}) where T    = Cchar('L')
backend_type(m::XpressOptimizer, ::MOI.EqualTo{T}) where T     = Cchar('E')
# Implemented separately
# backend_type(m::XpressOptimizer, ::MOI.Interval{T}) where T    = Cchar('R')

backend_type(m::XpressOptimizer, ::MOI.Zeros)        = Cchar('E')
backend_type(m::XpressOptimizer, ::MOI.Nonpositives) = Cchar('L')
backend_type(m::XpressOptimizer, ::MOI.Nonnegatives) = Cchar('G')

#=
not in LinQuad
=#

setparam!(instance::XpressOptimizer, name, val) = XPR.setparam!(instance.inner, XPR.XPRS_CONTROLS_DICT[name], val)

setlogfile!(instance::XpressOptimizer, path) = XPR.setlogfile(instance.inner, path::String)

cintvec(v::Vector) = convert(Vector{Int32}, v)

#=
    inner wrapper
=#

#=
Constraints
=#

MOI.canset(::XpressOptimizer, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}) = true
MOI.canset(::XpressOptimizer, ::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}) = true
MOI.canget(::XpressOptimizer, ::MOI.ConstraintSet, ::Type{LQOI.LCI{LQOI.IV}}) = true

LQOI.change_variable_bounds!(instance::XpressOptimizer, colvec, valvec, sensevec) = XPR.chgbounds!(instance.inner, cintvec(colvec), sensevec, valvec)

LQOI.get_variable_lowerbound(instance::XpressOptimizer, col) = XPR.get_lb(instance.inner, col, col)[1]
LQOI.get_variable_upperbound(instance::XpressOptimizer, col) = XPR.get_ub(instance.inner, col, col)[1]

LQOI.get_number_linear_constraints(instance::XpressOptimizer) = XPR.num_linconstrs(instance.inner)

LQOI.add_linear_constraints!(instance::XpressOptimizer, A::LQOI.CSRMatrix{Float64}, sensevec, rhsvec) = XPR.add_constrs!(instance.inner, A.row_pointers, A.columns, A.coefficients, sensevec, rhsvec)

function LQOI.add_ranged_constraints!(instance::XpressOptimizer, A::LQOI.CSRMatrix{Float64}, lowerbound, upperbound)
    newrows = length(lowerbound)
    rows = XPR.num_linconstrs(instance.inner)
    addedrows = collect((rows+1):(rows+newrows))

    sensevec = fill(Cchar('E'),newrows)
    XPR.add_constrs!(instance.inner, A.row_pointers, A.columns, A.coefficients, sensevec, upperbound)

    XPR.chg_rhsrange!(instance.inner, cintvec(addedrows), +upperbound-lowerbound)
end

function LQOI.modify_ranged_constraints!(instance::XpressOptimizer, rows::Vector{Int}, lowerbound::Vector{Float64}, upperbound::Vector{Float64})
    XPR.set_rhs!(instance.inner, rows, upperbound)
    XPR.chg_rhsrange!(instance.inner, cintvec(rows), +upperbound-lowerbound)
end

LQOI.get_rhs(instance::XpressOptimizer, row) = XPR.get_rhs(instance.inner, row, row)[1]

function LQOI.get_range(instance::XpressOptimizer, row)
    ub = XPR.get_rhs(instance.inner, row, row)[1]
    r = XPR.get_rhsrange(instance.inner, row, row)[1]
    return ub-r, ub
end

# TODO improve
function LQOI.get_linear_constraint(instance::XpressOptimizer, idx)
    A = XPR.get_rows(instance.inner, idx, idx)'
    return A.rowval-1, A.nzval
end

function LQOI.get_quadratic_constraint(instance::XpressOptimizer, idx)
    A = XPR.get_rows(instance.inner, idx, idx)'

    Q = XPR.get_qrowmatrix_triplets(instance.inner, idx)

    return A.rowval-1, A.nzval, Q
end

# notin LQOI
# TODO improve
function getcoef(instance::XpressOptimizer, row, col)
    A = XPR.get_rows(instance.inner, row, row)'
    cols = A.rowval
    vals = A.nzval

    pos = findfirst(cols, col)
    if pos > 0
        return vals[pos]
    else
        return 0.0
    end
end

LQOI.change_matrix_coefficient!(instance::XpressOptimizer, row, col, coef) = XPR.chg_coeffs!(instance.inner, row, col, coef)

LQOI.change_objective_coefficient!(instance::XpressOptimizer, col, coef) = XPR.set_objcoeffs!(instance.inner, Int32[col], Float64[coef])

LQOI.change_rhs_coefficient!(instance::XpressOptimizer, row, coef) = XPR.set_rhs!(instance.inner, Int32[row], Float64[coef])

LQOI.delete_linear_constraints!(instance::XpressOptimizer, rowbeg, rowend) = XPR.del_constrs!(instance.inner, cintvec(collect(rowbeg:rowend)))

LQOI.delete_quadratic_constraints!(instance::XpressOptimizer, rowbeg, rowend) = XPR.del_constrs!(instance.inner, cintvec(collect(rowbeg:rowend)))

LQOI.change_variable_types!(instance::XpressOptimizer, colvec, typevec) = XPR.chgcoltypes!(instance.inner, colvec, typevec)

LQOI.change_linear_constraint_sense!(instance::XpressOptimizer, rowvec, sensevec) = XPR.set_rowtype!(instance.inner, rowvec, sensevec)

LQOI.add_sos_constraint!(instance::XpressOptimizer, colvec, valvec, typ) = XPR.add_sos!(instance.inner, typ, colvec, valvec)

LQOI.delete_sos!(instance::XpressOptimizer, idx1, idx2) = XPR.del_sos!(instance.inner, cintvec(collect(idx1:idx2)))

# TODO improve getting processes
function LQOI.get_sos_constraint(instance::XpressOptimizer, idx)
    A, types = XPR.get_sos_matrix(instance.inner)
    line = A[idx,:] #sparse vec
    cols = line.nzind
    vals = line.nzval
    typ = types[idx] == Cchar('1') ? :SOS1 : :SOS2
    return cols, vals, typ
end

LQOI.get_number_quadratic_constraints(instance::XpressOptimizer) = XPR.num_qconstrs(instance.inner)

function scalediagonal!(V, I, J, scale)
    #  LQOI assumes 0.5 x' Q x, but Gurobi requires the list of terms, e.g.,
    #  2x^2 + xy + y^2, so we multiply the diagonal of V by 0.5. We don't
    #  multiply the off-diagonal terms since we assume they are symmetric and we
    #  only need to give one.
    #
    #  We also need to make sure that after adding the constraint we un-scale
    #  the vector because we can't modify user-data.
    for i in 1:length(I)
        if I[i] == J[i]
            V[i] *= scale
        end
    end
end

function LQOI.add_quadratic_constraint!(instance::XpressOptimizer, cols, coefs, rhs, sense, I, J, V)
    @assert length(I) == length(J) == length(V)
    scalediagonal!(V, I, J, 0.5)
    XPR.add_qconstr!(instance.inner, cols, coefs, I, J, V, sense, rhs)
    scalediagonal!(V, I, J, 2.0)
end
#=
    Objective
=#

function LQOI.set_quadratic_objective!(instance::XpressOptimizer, I, J, V)
    @assert length(I) == length(J) == length(V)
    XPR.delq!(instance.inner)
    # scalediagonal!(V, I, J, 0.5)
    XPR.add_qpterms!(instance.inner, I, J, V)
    # scalediagonal!(V, I, J, 2.0)
    return nothing
end

function LQOI.set_linear_objective!(instance::XpressOptimizer, colvec, coefvec)
    nvars = XPR.num_vars(instance.inner)
    obj = zeros(Float64, nvars)

    for i in eachindex(colvec)
        obj[colvec[i]] = coefvec[i]
    end

    XPR.set_obj!(instance.inner, obj)
    nothing
end

function LQOI.change_objective_sense!(instance::XpressOptimizer, symbol)
    if symbol == :min
        XPR.set_sense!(instance.inner, :minimize)
    else
        XPR.set_sense!(instance.inner, :maximize)
    end
end

function LQOI.get_linear_objective!(instance::XpressOptimizer, x::Vector{Float64})
    obj = XPR.get_obj(instance.inner)
    @assert length(x) == length(obj)
    for i in 1:length(obj)
        x[i] = obj[i]
    end
end

function LQOI.get_quadratic_terms_objective(instance::XpressOptimizer)
    I, J, V = XPR.getq(instance.inner)
    return I, J, V
end

function LQOI.get_objectivesense(instance::XpressOptimizer)
    s = XPR.model_sense(instance.inner)
    if s == :maximize
        return MOI.MaxSense
    else
        return MOI.MinSense
    end
end

#=
    Variables
=#

LQOI.get_number_variables(instance::XpressOptimizer) = XPR.num_vars(instance.inner)

LQOI.add_variables!(instance::XpressOptimizer, int) = XPR.add_cvars!(instance.inner, zeros(int))

LQOI.delete_variables!(instance::XpressOptimizer, col, col2) = XPR.del_vars!(instance.inner, col)

# LQOI.lqs_addmipstarts(m, colvec, valvec)
function LQOI.add_mip_starts!(instance::XpressOptimizer, colvec, valvec)
    x = zeros(XPR.num_vars(instance.inner))
    for i in eachindex(colvec)
        x[colvec[i]] = valvec[i]
    end
    XPR.loadbasis(instance.inner, x)
end

#=
    Solve
=#

LQOI.solve_mip_problem!(instance::XpressOptimizer) = XPR.mipoptimize(instance.inner)

LQOI.solve_quadratic_problem!(instance::XpressOptimizer) = ( writeproblem(instance, "db", "l");LQOI.solve_linear_problem!(instance) )

LQOI.solve_linear_problem!(instance::XpressOptimizer) = XPR.lpoptimize(instance.inner)

function LQOI.get_termination_status(instance::XpressOptimizer)
    stat_lp = XPR.get_lp_status2(instance.inner)
    if XPR.is_mip(instance.inner)
        stat_mip = XPR.get_mip_status2(instance.inner)
        if stat_mip == XPR.MIP_NotLoaded
            return MOI.OtherError
        elseif stat_mip == XPR.MIP_LPNotOptimal
            # MIP search incomplete but there is no linear sol
            # return MOI.OtherError
            return MOI.InfeasibleOrUnbounded
        elseif stat_mip == XPR.MIP_NoSolFound
            # MIP search incomplete but there is no integer sol
            other = xprsmoi_stopstatus(instance.inner)
            if other == MOI.OtherError
                return MOI.SlowProgress#OtherLimit
            else
                return other
            end

        elseif stat_mip == XPR.MIP_Solution
            # MIP search incomplete but there is a solution
            other = xprsmoi_stopstatus(instance.inner)
            if other == MOI.OtherError
                return MOI.OtherLimit
            else
                return other
            end

        elseif stat_mip == XPR.MIP_Infeasible
            if XPR.hasdualray(instance.inner)
                return MOI.Success
            else
                return MOI.InfeasibleNoResult
            end
        elseif stat_mip == XPR.MIP_Optimal
            return MOI.Success
        elseif stat_mip == XPR.MIP_Unbounded
            if XPR.hasprimalray(instance.inner)
                return MOI.Success
            else
                return MOI.UnboundedNoResult
            end
        end
        return MOI.OtherError
    else
        if stat_lp == XPR.LP_Unstarted
            return MOI.OtherError
        elseif stat_lp == XPR.LP_Optimal
            return MOI.Success
        elseif stat_lp == XPR.LP_Infeasible
            if XPR.hasdualray(instance.inner)
                return MOI.Success
            else
                return MOI.InfeasibleNoResult
            end
        elseif stat_lp == XPR.LP_CutOff
            return MOI.ObjectiveLimit
        elseif stat_lp == XPR.LP_Unfinished
            return xprsmoi_stopstatus(instance.inner)
        elseif stat_lp == XPR.LP_Unbounded
            if XPR.hasprimalray(instance.inner)
                return MOI.Success
            else
                return MOI.UnboundedNoResult
            end
        elseif stat_lp == XPR.LP_CutOffInDual
            return MOI.ObjectiveLimit
        elseif stat_lp == XPR.LP_Unsolved
            return MOI.OtherError
        elseif stat_lp == XPR.LP_NonConvex
            return MOI.InvalidModel
        end
        return MOI.OtherError
    end
end

function xprsmoi_stopstatus(instance::XpressOptimizer)
    ss = XPR.get_stopstatus(instance.inner)
    if ss == XPR.StopTimeLimit
        return MOI.TimeLimit
    elseif ss == XPR.StopControlC
        return MOI.Interrupted
    elseif ss == XPR.StopNodeLimit
        # should not be here
        warn("should not be here")
        return MOI.NodeLimit
    elseif ss == XPR.StopIterLimit
        return MOI.IterationLimit
    elseif ss == XPR.StopMIPGap
        return MOI.ObjectiveLimit
    elseif ss == XPR.StopSolLimit
        return MOI.SolutionLimit
    elseif ss == XPR.StopUser
        return MOI.Interrupted
    end
    return MOI.OtherError
end

function LQOI.get_primal_status(instance::XpressOptimizer)
    if XPR.is_mip(instance.inner)
        stat_mip = XPR.get_mip_status2(instance.inner)
        if stat_mip in [XPR.MIP_Solution, XPR.MIP_Optimal]
            return MOI.FeasiblePoint
        elseif XPR.MIP_Infeasible && XPR.hasdualray(instance.inner)
            return MOI.InfeasibilityCertificate
        elseif XPR.MIP_Unbounded && XPR.hasprimalray(instance.inner)
            return MOI.InfeasibilityCertificate
        elseif stat_mip in [XPR.MIP_LPOptimal, XPR.MIP_NoSolFound]
            return MOI.InfeasiblePoint
        end
        return MOI.UnknownResultStatus
    else
        stat_lp = XPR.get_lp_status2(instance.inner)
        if stat_lp == XPR.LP_Optimal
            return MOI.FeasiblePoint
        elseif stat_lp == XPR.LP_Unbounded && XPR.hasprimalray(instance.inner)
            return MOI.InfeasibilityCertificate
        # elseif stat_lp == LP_Infeasible
        #     return MOI.InfeasiblePoint - xpress wont return
        # elseif cutoff//cutoffindual ???
        else
            return MOI.UnknownResultStatus
        end
    end
end

function LQOI.get_dual_status(instance::XpressOptimizer)
    if XPR.is_mip(instance.inner)
        return MOI.UnknownResultStatus
    else
        stat_lp = XPR.get_lp_status2(instance.inner)
        if stat_lp == XPR.LP_Optimal
            return MOI.FeasiblePoint
        elseif stat_lp == XPR.LP_Infeasible && XPR.hasdualray(instance.inner)
            return MOI.InfeasibilityCertificate
        # elseif stat_lp == LP_Unbounded
        #     return MOI.InfeasiblePoint - xpress wont return
        # elseif cutoff//cutoffindual ???
        else
            return MOI.UnknownResultStatus
        end
    end
end

LQOI.get_variable_primal_solution!(instance::XpressOptimizer, place) = XPR.get_solution!(instance.inner, place)

function LQOI.get_linear_primal_solution!(instance::XpressOptimizer, place)
    if num_qconstrs(instance.inner) == 0
        XPR.get_slack_lin!(instance.inner, place)
        rhs = XPR.get_rhs(instance.inner)
        for i in eachindex(place)
            place[i] = -place[i]+rhs[i]
        end
    else
        XPR.get_slack_lin!(instance.inner, place)
        rhs = XPR.get_rhs(instance.inner)
        lrows = XPR.get_lrows(instance.inner)
        for (i,v) in enumerate(lrows)
            place[i] = -place[i]+rhs[v]
        end
    end
    nothing
end

function moi_lrows(m::Xpress.Model)
    tt_rows = collect(1:num_constrs(m))
    if num_qconstrs(m) == 0
        return tt_rows
    else
        return setdiff(tt_rows, XPR.get_qrows(m))
    end
end

function LQOI.get_quadratic_primal_solution!(instance::XpressOptimizer, place)
    if num_qconstrs(instance.inner) == 0
        return nothing
    else
        qrows = XPR.get_qrows(instance.inner)
        newplace = zeros(num_constrs(instance.inner))
        XPR.get_slack!(instance.inner, newplace)
        rhs = XPR.get_rhs(instance.inner)
        for (i,v) in enumerate(qrows)
            place[i] = -newplace[v]+rhs[v]
        end
    end
    nothing
end

LQOI.get_variable_dual_solution!(instance::XpressOptimizer, place) = XPR.get_reducedcost!(instance.inner, place)

LQOI.get_linear_dual_solution!(instance::XpressOptimizer, place) = XPR.get_dual_lin!(instance.inner, place)

function LQOI.get_quadratic_dual_solution!(instance::XpressOptimizer, place)
    if num_qconstrs(instance.inner) == 0
        return nothing
    else
        qrows = XPR.get_qrows(instance.inner)
        newplace = zeros(num_constrs(instance.inner))
        XPR.get_dual!(instance.inner, newplace)
        for (i,v) in enumerate(qrows)
            place[i] = newplace[v]
        end
    end
end

LQOI.get_objective_value(instance::XpressOptimizer) = XPR.get_objval(instance.inner)

LQOI.get_objective_bound(instance::XpressOptimizer) = XPR.get_mip_objval(instance.inner)

function LQOI.get_relative_mip_gap(instance::XpressOptimizer)
    L = XPR.get_mip_objval(instance.inner)
    U = XPR.get_bestbound(instance.inner)
    return abs(U-L)/U
end

LQOI.get_iteration_count(instance::XpressOptimizer)  = XPR.get_simplex_iter_count(instance.inner)

LQOI.get_barrier_iterations(instance::XpressOptimizer) = XPR.get_barrier_iter_count(instance.inner)

LQOI.get_node_count(instance::XpressOptimizer) = XPR.get_node_count(instance.inner)

LQOI.get_farkas_dual!(instance::XpressOptimizer, place) = XPR.getdualray!(instance.inner, place)

LQOI.get_unbounded_ray!(instance::XpressOptimizer, place) = XPR.getprimalray!(instance.inner, place)

MOI.free!(m::XpressOptimizer) = XPR.free_model(m.onner)

"""
    writeproblem(m: :MOI.AbstractOptimizer, filename::String)
Writes the current problem data to the given file.
Supported file types are solver-dependent.
"""
writeproblem(instance::XpressOptimizer, filename::String, flags::String="") = XPR.write_model(instance.inner, filename, flags)

end # module