# slt_read.jl
#
# Jeremy Rüffer
# Thünen Institut
# Institut für Agrarklimaschutz
# Junior Research Group NITROSPHERE
# Julia 0.7
# 09.12.2016
# Last Edit: 18.04.2019

"""# slt_read

Load a single SLT file

`slt_read(F,a_inputs,sample_rate)`\n
* **F**::String = File name, including path
* **a_inputs**::Int = Number of analog channels in the SLT file
* **sample_rate**::Int or String = Sampling rate of the sonic"""
function slt_read(F::String,AnalogIn::Int,freq::String)
	return slt_read(F,AnallogIn,parse(freq))
end # slt_read(F::String,AnalogIn::Int,freq::String)

function slt_read(F::String,a_inputs::Int,sample_rate::Int)
	# Arrays to output
	t = [] # Time
	d = [] # Data

	# Parsing information for SLT files can be found in the EddySoft PDF on page 15 (actual page 23).

	fid = open(F,"r")
	try
		# Header
		bpr = Int(read!(fid,Array{Int8}(undef,1))[1]) # Bytes Per Record
		eddymeasver = read!(fid,Array{Int8}(undef,1))
		dom = Int(read!(fid,Array{Int8}(undef,1))[1]) # Day of Month
		m = Int(read!(fid,Array{Int8}(undef,1))[1]) # Month number
		yr = 100*Int(read!(fid,Array{Int8}(undef,1))[1]) + Int(read!(fid,Array{Int8}(undef,1))[1]) # Year
		h = Int(read!(fid,Array{Int8}(undef,1))[1]) # Hour
		minut = Int(read!(fid,Array{Int8}(undef,1))[1]) # Minute
		t0 = DateTime(yr,m,dom,h,minut,0) # Initial file time

		fs = stat(F).size # File size in bytes
		l = (fs - 8 - 2*a_inputs)/(8 + 2*a_inputs) # Number of rows of data, trusting the user inputs
		if l - floor(l) != 0
			warn(F * " either has a different column count than " * string(a_input + 4) * " or was terminated early.")
		end
		l = Int(floor(l))

		# Bit Masks and Channels
		bm = read!(fid,Array{Int8}(undef,2*a_inputs))
		ch = bm[2:2:end] # Channels
		bm = bm[1:2:end] # Bit masks

		## Preallocate Output Array
		t_offset = fill!(Array{Dates.Millisecond}(undef,floor(l)),Dates.Millisecond(1000/sample_rate)) # dt between every sample
		t_offset[1] = Dates.Millisecond(0) # Correction, the first sample shouldn't have an offset
		t_offset = cumsum(t_offset) # Time offset from the start for every sample
		t = DateTime(yr,m,dom,h,minut,0) + t_offset # Time
		d = NaN.*Array{Float64}(undef,(Int(l),4 + a_inputs)) # Data Array

		for i=1:1:l
			d[i,1] = Float64(read!(fid,Array{Int16}(undef,1))[1])/100 # u
			d[i,2] = Float64(read!(fid,Array{Int16}(undef,1))[1])/100 # v
			d[i,3] = Float64(read!(fid,Array{Int16}(undef,1))[1])/100 # w
			d[i,4] = Float64(read!(fid,Array{Int16}(undef,1))[1])/50 # c, # Speed of Sound

			for j=1:1:a_inputs
				V = Float64(read!(fid,Array{Int16}(undef,1))[1]) # V1
				if bm[j] & 2^(1-1) > 0 # If the first bit is high
					# If the bit mask is high, use the following formula to convert the binary value to mV
					V = (V[1] + 25000)/10 # Binary to mV
				end
				d[i,j+4] = V
			end
		end
	catch
		println("Error loading file: " * F)
	end
	close(fid)

	return t,d
end # End of slt_read(F::String,a_inputs::Int,sample_rate::Int)


"""
    append_hs!(model::JuMP.Model, x, nh::Int)

Append equalty constraints (auto-generated)

# Arguments
    `model::JuMP.Model`: optimization model
    `x::JuMP.variables`: optimization model variables
    `nh::Int`: number of equality constraints
"""
function append_hs!(model::JuMP.Model, x, nh::Int)
    for ih = 1:nh
        if ih == 1
            @NLconstraint(model, h1(x...) == 0)
        elseif ih == 2
            @NLconstraint(model, h2(x...) == 0)
        elseif ih == 3
            @NLconstraint(model, h3(x...) == 0)
        elseif ih == 4
            @NLconstraint(model, h4(x...) == 0)
        elseif ih == 5
            @NLconstraint(model, h5(x...) == 0)
        elseif ih == 6
            @NLconstraint(model, h6(x...) == 0)
        elseif ih == 7
            @NLconstraint(model, h7(x...) == 0)
        elseif ih == 8
            @NLconstraint(model, h8(x...) == 0)
        elseif ih == 9
            @NLconstraint(model, h9(x...) == 0)
        elseif ih == 10
            @NLconstraint(model, h10(x...) == 0)
        elseif ih == 11
            @NLconstraint(model, h11(x...) == 0)
        elseif ih == 12
            @NLconstraint(model, h12(x...) == 0)
        elseif ih == 13
            @NLconstraint(model, h13(x...) == 0)
        elseif ih == 14
            @NLconstraint(model, h14(x...) == 0)
        elseif ih == 15
            @NLconstraint(model, h15(x...) == 0)
        elseif ih == 16
            @NLconstraint(model, h16(x...) == 0)
        elseif ih == 17
            @NLconstraint(model, h17(x...) == 0)
        elseif ih == 18
            @NLconstraint(model, h18(x...) == 0)
        elseif ih == 19
            @NLconstraint(model, h19(x...) == 0)
        elseif ih == 20
            @NLconstraint(model, h20(x...) == 0)
        elseif ih == 21
            @NLconstraint(model, h21(x...) == 0)
        elseif ih == 22
            @NLconstraint(model, h22(x...) == 0)
        elseif ih == 23
            @NLconstraint(model, h23(x...) == 0)
        elseif ih == 24
            @NLconstraint(model, h24(x...) == 0)
        elseif ih == 25
            @NLconstraint(model, h25(x...) == 0)
        elseif ih == 26
            @NLconstraint(model, h26(x...) == 0)
        elseif ih == 27
            @NLconstraint(model, h27(x...) == 0)
        elseif ih == 28
            @NLconstraint(model, h28(x...) == 0)
        elseif ih == 29
            @NLconstraint(model, h29(x...) == 0)
        elseif ih == 30
            @NLconstraint(model, h30(x...) == 0)
        elseif ih == 31
            @NLconstraint(model, h31(x...) == 0)
        elseif ih == 32
            @NLconstraint(model, h32(x...) == 0)
        elseif ih == 33
            @NLconstraint(model, h33(x...) == 0)
        elseif ih == 34
            @NLconstraint(model, h34(x...) == 0)
        elseif ih == 35
            @NLconstraint(model, h35(x...) == 0)
        elseif ih == 36
            @NLconstraint(model, h36(x...) == 0)
        elseif ih == 37
            @NLconstraint(model, h37(x...) == 0)
        elseif ih == 38
            @NLconstraint(model, h38(x...) == 0)
        elseif ih == 39
            @NLconstraint(model, h39(x...) == 0)
        elseif ih == 40
            @NLconstraint(model, h40(x...) == 0)
        elseif ih == 41
            @NLconstraint(model, h41(x...) == 0)
        elseif ih == 42
            @NLconstraint(model, h42(x...) == 0)
        elseif ih == 43
            @NLconstraint(model, h43(x...) == 0)
        elseif ih == 44
            @NLconstraint(model, h44(x...) == 0)
        elseif ih == 45
            @NLconstraint(model, h45(x...) == 0)
        elseif ih == 46
            @NLconstraint(model, h46(x...) == 0)
        elseif ih == 47
            @NLconstraint(model, h47(x...) == 0)
        elseif ih == 48
            @NLconstraint(model, h48(x...) == 0)
        elseif ih == 49
            @NLconstraint(model, h49(x...) == 0)
        elseif ih == 50
            @NLconstraint(model, h50(x...) == 0)
        elseif ih == 51
            @NLconstraint(model, h51(x...) == 0)
        elseif ih == 52
            @NLconstraint(model, h52(x...) == 0)
        elseif ih == 53
            @NLconstraint(model, h53(x...) == 0)
        elseif ih == 54
            @NLconstraint(model, h54(x...) == 0)
        elseif ih == 55
            @NLconstraint(model, h55(x...) == 0)
        elseif ih == 56
            @NLconstraint(model, h56(x...) == 0)
        elseif ih == 57
            @NLconstraint(model, h57(x...) == 0)
        elseif ih == 58
            @NLconstraint(model, h58(x...) == 0)
        elseif ih == 59
            @NLconstraint(model, h59(x...) == 0)
        elseif ih == 60
            @NLconstraint(model, h60(x...) == 0)
        elseif ih == 61
            @NLconstraint(model, h61(x...) == 0)
        elseif ih == 62
            @NLconstraint(model, h62(x...) == 0)
        elseif ih == 63
            @NLconstraint(model, h63(x...) == 0)
        elseif ih == 64
            @NLconstraint(model, h64(x...) == 0)
        elseif ih == 65
            @NLconstraint(model, h65(x...) == 0)
        elseif ih == 66
            @NLconstraint(model, h66(x...) == 0)
        elseif ih == 67
            @NLconstraint(model, h67(x...) == 0)
        elseif ih == 68
            @NLconstraint(model, h68(x...) == 0)
        elseif ih == 69
            @NLconstraint(model, h69(x...) == 0)
        elseif ih == 70
            @NLconstraint(model, h70(x...) == 0)
        elseif ih == 71
            @NLconstraint(model, h71(x...) == 0)
        elseif ih == 72
            @NLconstraint(model, h72(x...) == 0)
        elseif ih == 73
            @NLconstraint(model, h73(x...) == 0)
        elseif ih == 74
            @NLconstraint(model, h74(x...) == 0)
        elseif ih == 75
            @NLconstraint(model, h75(x...) == 0)
        elseif ih == 76
            @NLconstraint(model, h76(x...) == 0)
        elseif ih == 77
            @NLconstraint(model, h77(x...) == 0)
        elseif ih == 78
            @NLconstraint(model, h78(x...) == 0)
        elseif ih == 79
            @NLconstraint(model, h79(x...) == 0)
        elseif ih == 80
            @NLconstraint(model, h80(x...) == 0)
        elseif ih == 81
            @NLconstraint(model, h81(x...) == 0)
        elseif ih == 82
            @NLconstraint(model, h82(x...) == 0)
        elseif ih == 83
            @NLconstraint(model, h83(x...) == 0)
        elseif ih == 84
            @NLconstraint(model, h84(x...) == 0)
        elseif ih == 85
            @NLconstraint(model, h85(x...) == 0)
        elseif ih == 86
            @NLconstraint(model, h86(x...) == 0)
        elseif ih == 87
            @NLconstraint(model, h87(x...) == 0)
        elseif ih == 88
            @NLconstraint(model, h88(x...) == 0)
        elseif ih == 89
            @NLconstraint(model, h89(x...) == 0)
        elseif ih == 90
            @NLconstraint(model, h90(x...) == 0)
        elseif ih == 91
            @NLconstraint(model, h91(x...) == 0)
        elseif ih == 92
            @NLconstraint(model, h92(x...) == 0)
        elseif ih == 93
            @NLconstraint(model, h93(x...) == 0)
        elseif ih == 94
            @NLconstraint(model, h94(x...) == 0)
        elseif ih == 95
            @NLconstraint(model, h95(x...) == 0)
        elseif ih == 96
            @NLconstraint(model, h96(x...) == 0)
        elseif ih == 97
            @NLconstraint(model, h97(x...) == 0)
        elseif ih == 98
            @NLconstraint(model, h98(x...) == 0)
        elseif ih == 99
            @NLconstraint(model, h99(x...) == 0)
        elseif ih == 100
            @NLconstraint(model, h100(x...) == 0)
        end
    end
end


using ArgParse
import Reactive, GeometryTypes, Chipmunk, LinearAlgebra
const re = Reactive
const gt = GeometryTypes
const Cp = Chipmunk
const la = LinearAlgebra

"""
    Config

Saves all data, that is need through the whole programm. Will be initialized at
startup.
"""
mutable struct Config{WS<:WorldState, T<:Number}
    ws::WS
    """
    Field:

    Rectangular, (0,0) is lower left corner
    (9,6) is the right upper corner
    all in meters
    """
    field_size::Vector{T}
    field_corners::Vector{<:gt.Point2{T}}
    active_strategy::String
    strategies_table::Dict{String, Module}
    maximum_player_per_side::Int
    space::Cp.Space
    # set_progress_bar::Function
"""
    printables

For values that shall be visible in the gui. The name will be a first label and the value will be second label, as a signal. If it changes, the value in the second label will also be updated.
"""
    printables::Dict{String, re.Signal}

"""
    drawables

A dictionary for drawing something on tha canvas in the gui (where the field is displayed).
The name uses the folling conventions for the drawing:
- if the name beginns with '_' it will be drawn befor anything on the field is drawn and it will be assumed, that it is an image (matrix), the position is the top left corner and the botel right corner
- in all other cases it must be a element from Graphics2D, as function, that recives the first argument (the position). In the gui the posiiton will be transformed from world coordinates to screen coordinates
"""
    drawables::Dict{String, <:Tuple{<:AbstractVector{<:AbstractVector{<:Real}}, Function}} # the tuples saves the coordinate in the first place and a drawable function in the second

"""
    manipulators

Saves Gtk gui-elements. The name will be printed in a label above the elemente.
Name conventions:
- if the name starts with a D: instead of a widget as the element a tuple with a widget and a function is assumed. The function will be added as "button-pressed" event of the widget and after the execution of the function, the canvas will be redrawn.
- if the name starts with DN: the same as D, but the widget will not be shown in the widget area
- if the widget is a Gtk.GtkRadioButtonGroupLeaf and the are less than two RadioButtons in it, it will not be shown
- all others are names+widgets and the widget will be shown
"""
    manipulators::Dict{String, <:Tuple}

    global_communication::Dict{String, Any}
end
Config(ws::WorldState, field_size::Vector{<:AbstractFloat},
    field_corners::Vector{<:gt.Point2{<:Number}},
    active_strategy::String, space::Cp.Space) = Config(ws, field_size, field_corners,
    active_strategy, Dict{String, Module}(), 6, space,
    Dict{String, re.Signal}(),
    Dict{String, Tuple{Vector{Vector{Float64}}, Function}}(),
    Dict{String, Tuple}(),
    Dict{String, Any}())
function Config(config::Config)
    ws = WorldState(config.ws)
    space = createspace(ws)

    config = Config(ws, config.field_size, config.field_corners, config.active_strategy, space)

    setstrategies!(config)

    config
end

"""
    ArgParse.parse_item(::Type{Robot{Float64}}, x::AbstractString)

A Robot has its own Arguments. A string `x` containing the arguments are parsed to a `Robot` object.
"""
function ArgParse.parse_item(::Type{Robot{Float64}}, x::AbstractString)
    vec = [substring for substring in split(x[2:end-1], [' ']) if substring != ""]
    default = Nao([0.0, 0.0], 0.0)
    s = ArgParseSettings(commands_are_required = false)
    @add_arg_table s begin
        "--position"
            required = true
            help = "Position in world coordinate system (metric)."
            # range_tester = x -> x > 0
            nargs = 2
            arg_type = Float64
        "--body_orientation"
            help = "Robot orientation relative to the x-axis in rad."
            range_tester = x -> -π ≤ x ≤ π
            arg_type = Float64
            default = default.body_orientation
        "--head_orientation"
            help = "Head orientation relative to the `body_orientation` in rad."
            range_tester = x -> deg2rad(-119.5) ≤ x ≤ deg2rad(119.5)
            arg_type = Float64
            default = default.head_orientation
        "--velocity"
            help = "The current velocity of the robot in m/s."
            # range_tester = x -> -π ≤ x ≤ π
            nargs = 2
            arg_type = Float64
            default = get_velocity(default)
        "--angularvelocity"
            help = "The current angular velocity of the robot rad/s."
            # range_tester = x -> x ≥ 0
            arg_type = Float64
            default = get_angularvelocity(default)
        "--size"
            help = "Robot size in m."
            range_tester = x -> x > 0
            arg_type = Float64
            default = get_size(default)
        "--mass"
            help = "Robot mass in kg."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = get_mass(default)
        "--hfov"
            help = "Horizontal field of view in rad."
            range_tester = x -> x ≤ 2π
            arg_type = Float64
            default = default.hfov
        "--max_velocity"
            help = "The maximum velocity of the robot in m/s."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = default.max_velocity
        "--max_angularvelocity"
            help = "The maximum angular velocity of the robot in rad/s."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = default.max_angularvelocity
        # "--shape"
        #     help = "Shape of the robot."
        #     arg_type = Symbol
        #     default = default.shape
    end
    res = parse_args(vec, s, as_symbols=true)
    val = Robot(;res...)
    return val
end

"""
    ArgParse.parse_item(::Type{Obstacle{Float64}}, x::AbstractString)

A Robot has its own Arguments. A string `x` containing the arguments are parsed to a `Robot` object.
"""
function ArgParse.parse_item(::Type{Obstacle{Float64}}, x::AbstractString)
    vec = [substring for substring in split(x[2:end-1], [' ']) if substring != ""]
    default = Ball([0.0, 0.0])
    s = ArgParseSettings(commands_are_required = false)
    @add_arg_table s begin
        "--position"
            required = true
            help = "Position in world coordinate system (metric)."
            # range_tester = x -> x > 0
            nargs = 2
            arg_type = Float64
        "--velocity"
            help = "The current velocity of the robot in m/s."
            # range_tester = x -> -π ≤ x ≤ π
            nargs = 2
            arg_type = Float64
            default = get_velocity(default)
        "--size"
            help = "Obstacle size in m."
            range_tester = x -> x > 0
            arg_type = Float64
            default = get_size(default)
        "--mass"
            help = "Obstacle mass in kg."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = get_mass(default)
        "--max_velocity"
            help = "The maximum velocity of the robot in m/s."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = default.max_velocity
        "--friction"
            help = "Friction depending on the ground."
            range_tester = x -> x ≥ 0
            arg_type = Float64
            default = get_friction(default)
        "--fixed"
            help = "If the obstacle can't be moved."
            nargs = 0
            action = :store_true
        # "--shape"
        #     help = "Shape of the robot. Allowed is circle, poly, box and segment.0"
        #     arg_type = Symbol
        #     default = default.shape
        "--color"
            help = "Color of the object."
            range_tester = x -> 0 ≤ x ≤ 1
            nargs = 4
            arg_type = Float64
            default = default.color
    end
    res = parse_args(vec, s, as_symbols=true)
    val = Obstacle(;res...)
    return val
end

"""
    parse_commandline(args::AbstractVector{<:AbstractString})

Parses a string to a list with all command line arguments. If the command line
argument --file is given, the file at this location is parsed with this
function. All other command line arguments in the first place are rejected.
"""
function parse_commandline(args::AbstractVector{<:AbstractString})
    s = ArgParseSettings(commands_are_required = false)

    @add_arg_table s begin
        "--field_size"
            help = "Defines the width and height of the field in meters."
            nargs = 2
            arg_type = Float64
            default = [9.0, 6.0]
        "--add_obstacle"
            help = "Add as many obstacles as you want."
            action = :append_arg
            arg_type = Obstacle{Float64}
            default = Obstacle[]
        "--add_myPlayer", "-p"
            help = "Add as many myPlayers as you want."
            action = :append_arg
            arg_type = Robot{Float64}
            default = Robot[]
        "--add_opponent", "-o"
            help = "Add as many opponents as you want."
            action = :append_arg
            arg_type = Robot{Float64}
            default = Robot[]
        "--gravity"
            help = "Gravity for your location. Default should match for the most areas on earth."
            # range_tester = x -> x ≥ 0
            arg_type = Float64
            default = 9.81
        "--strategy", "-s"
            help = "The strategy, which shall be used."
            arg_type = String
            default = "ShowShadow"
        "--file", "-f"
            help = "Path to a file with all parameters."
            arg_type = String
    end
    arguments = parse_args(args, s)
    if haskey(arguments, "file") && arguments["file"] != nothing
        arguments = parse_args(collect(eachline(open(arguments["file"]))), s)
    end
    return arguments
end

function createspace(ws::WorldState)
    space = Cp.Space()

    for object in vcat(ws.myPlayers, ws.opponents, ws.obstacles)
        Cp.set_userdata(object.body, pointer_from_objref(object))
        Cp.add_body(space, object.body)
        Cp.add_shape(space, object.shape)
    end

    # Cp.set_damping(space, 0.1)

    for player in ws.myPlayers
        Cp.set_velocity_update_func(player.body, (b, g, d, t) -> nothing)
    end
    for obs in ws.obstacles

        # μr = get_friction(obs)
        # g = arguments["gravity"]

        function ball_linear_drag(body_ptr::Ptr{Nothing}, gravity::Cp.Vect, damping::Cdouble, dt::Cdouble) # TODO: needs too long
        # // Skip kinematic bodies.
        # if(cpBodyGetType(body) == CP_BODY_TYPE_KINEMATIC) return;
            # deceleration = 0.09
            body = Cp.Body(body_ptr) # TODO: tooks way too much memory
            # ud = unsafe_pointer_to_objref(Cp.get_userdata(body))
            # μr = get_friction(ud)
            # g = 9.81
            current_vel_vect = Cp.get_velocity(body)
            # current_vel = [current_vel_vect.x, current_vel_vect.y]
            cur_vel_len = sqrt(current_vel_vect.x^2 + current_vel_vect.y^2)
            mul(x::T, y::T) where T<:Number = x*y
            vel_len = cur_vel_len - mul(9.81, dt)
            new_vel_x = vel_len > 0 ? current_vel_vect.x/cur_vel_len*vel_len : 0.0
            new_vel_y = vel_len > 0 ? current_vel_vect.y/cur_vel_len*vel_len : 0.0
            Cp.set_velocity(body, Cp.Vect(new_vel_x, new_vel_y))
            return nothing
        end

        Cp.set_velocity_update_func(obs.body, ball_linear_drag)

    end

    return space
end

function setstrategies!(config::Config)
    config.strategies_table["CompareShadow"] = CompareShadow
    config.strategies_table["ShowShadow"] = ShowShadow
    config.strategies_table["TestStrategy"] = TestStrategy
    config.strategies_table["GlobalComShadow"] = GlobalComShadow
    config.strategies_table["GrowingRegions"] = GrowingRegions
    # config.strategies_table["BaMichelle"] = BaMichelle
    # config.strategies_table["BaJakob"] = BaJakob
    config.strategies_table["StartPositions01"] = StartPositions01
    config.strategies_table["PushballNN"] = PushballNN
end

"""
    get_config(args::AbstractVector{<:AbstractString})

Wraps the parse_commandline function and creates a `config` from the command
line list.
"""
function get_config(args::AbstractVector{<:AbstractString})
    arguments = parse_commandline(args) #String["--file", config_file_path]

    player = vcat(arguments["add_myPlayer"], arguments["add_opponent"])
    for i in 1:length(player), j in i+1:length(player)
        if distance(player[i], player[j]) < get_size(player[i]) + get_size(player[j]) # HACK: asumes circles
            a, b = player[i], player[j]
            error("Player on position $a and $b are too close.")
        end
    end

    field_size = arguments["field_size"]
    field_corners = [gt.Point2(-field_size[1]/2, -field_size[2]/2), gt.Point2(field_size[1]/2, -field_size[2]/2),
        gt.Point2(field_size[1]/2, field_size[2]/2), gt.Point2(-field_size[1]/2, field_size[2]/2)]
    ws = WorldState(arguments["add_myPlayer"], arguments["add_opponent"],
        arguments["add_obstacle"])
    active_strategy = arguments["strategy"]

    space = createspace(ws)

    config = Config(ws, field_size, field_corners, active_strategy, space)

    setstrategies!(config)

    config
end

function add_object!(config::Config, object::Object)
    Cp.set_userdata(object.body, pointer_from_objref(object))
    Cp.add_body(config.space, object.body)
    Cp.add_shape(config.space, object.shape)
end
function remove_object!(config::Config, object::Object)
    Cp.remove_body(config.space, object.body)
    Cp.remove_shape(config.space, object.shape)
end

"""
    get_strategy(config::Config)

Wrapper for easier access of the current strategy run function in the config.
"""
function get_strategy(config::Config)
    config.strategies_table[config.active_strategy]
end

function free!(config::Config)
    for obj in vcat(config.ws.myPlayers, config.ws.opponents, config.ws.obstacles)
        Cp.free(obj.shape)
        Cp.free(obj.body)
    end
    Cp.free(config.space)
    return
end


module DGMethods

using MPI
using ..MPIStateArrays
using ..Mesh.Grids
using ..Mesh.Topologies
using StaticArrays
using ..VariableTemplates
using DocStringExtensions
using KernelAbstractions
using KernelAbstractions.Extras: @unroll

export BalanceLaw,
    DGModel, init_ode_state, restart_ode_state, restart_auxiliary_state

include("balance_law.jl")
include("NumericalFluxes.jl")
include("DGModel.jl")
include("DGModel_kernels.jl")

end


# This list was obtained through AWK with Knitro 11.1 (and then pared down) with the following two commands:
# grep "#define" knitro.h | awk '{ printf("const %s = Cint(%s)\n",$2,$3) }'
# grep "#  define" knitro.h | awk '{ printf("const %s = Cint(%s)\n",$3,$4) }'
# For version up until 10.3, use following commands :
# grep "#define" knitro.h | awk '{ printf("const %s = Cint(%s)\n",$2,$3) }'
# grep "#  define" knitro.h | awk '{ printf("const %s = Cint(%s)\n",$3,$4) }'

const KNTRUE = Cint(1)
const KNFALSE = Cint(0)
const KN_INFINITY = Float64(0x1.fffffffffffffp+1023) # equal to DBL_MAX
const KN_PARAMTYPE_INTEGER = Cint(0)
const KN_PARAMTYPE_FLOAT = Cint(1)
const KN_PARAMTYPE_STRING = Cint(2)
const KN_OBJGOAL_MINIMIZE = Cint(0)
const KN_OBJGOAL_MAXIMIZE = Cint(1)
const KN_OBJTYPE_CONSTANT = Cint(-1)
const KN_OBJTYPE_GENERAL = Cint(0)
const KN_OBJTYPE_LINEAR = Cint(1)
const KN_OBJTYPE_QUADRATIC = Cint(2)
const KN_CONTYPE_CONSTANT = Cint(-1)
const KN_CONTYPE_GENERAL = Cint(0)
const KN_CONTYPE_LINEAR = Cint(1)
const KN_CONTYPE_QUADRATIC = Cint(2)
const KN_CONTYPE_CONIC = Cint(3)
const KN_RSDTYPE_CONSTANT = Cint(-1)
const KN_RSDTYPE_GENERAL = Cint(0)
const KN_RSDTYPE_LINEAR = Cint(1)
const KN_CCTYPE_VARVAR = Cint(0)
const KN_CCTYPE_VARCON = Cint(1)
const KN_CCTYPE_CONCON = Cint(2)
const KN_VARTYPE_CONTINUOUS = Cint(0)
const KN_VARTYPE_INTEGER = Cint(1)
const KN_VARTYPE_BINARY = Cint(2)
const KN_VAR_LINEAR = Cint(1)
const KN_OBJ_CONVEX = Cint(1)
const KN_OBJ_CONCAVE = Cint(2)
const KN_OBJ_CONTINUOUS = Cint(4)
const KN_OBJ_DIFFERENTIABLE = Cint(8)
const KN_OBJ_TWICE_DIFFERENTIABLE = Cint(16)
const KN_OBJ_NOISY = Cint(32)
const KN_OBJ_NONDETERMINISTIC = Cint(64)
const KN_CON_CONVEX = Cint(1)
const KN_CON_CONCAVE = Cint(2)
const KN_CON_CONTINUOUS = Cint(4)
const KN_CON_DIFFERENTIABLE = Cint(8)
const KN_CON_TWICE_DIFFERENTIABLE = Cint(16)
const KN_CON_NOISY = Cint(32)
const KN_CON_NONDETERMINISTIC = Cint(64)
const KN_DENSE = Cint(-1)
const KN_DENSE_ROWMAJOR = Cint(-2)
const KN_DENSE_COLMAJOR = Cint(-3)
const KN_RC_EVALFC = Cint(1)
const KN_RC_EVALGA = Cint(2)
const KN_RC_EVALH = Cint(3)
const KN_RC_EVALHV = Cint(7)
const KN_RC_EVALH_NO_F = Cint(8)
const KN_RC_EVALHV_NO_F = Cint(9)
const KN_RC_EVALR = Cint(10)
const KN_RC_EVALRJ = Cint(11)
const KN_RC_EVALFCGA = Cint(12)
const KN_RC_OPTIMAL_OR_SATISFACTORY = Cint(0)
const KN_RC_OPTIMAL = Cint(0)
const KN_RC_NEAR_OPT = Cint(-100)
const KN_RC_FEAS_XTOL = Cint(-101)
const KN_RC_FEAS_NO_IMPROVE = Cint(-102)
const KN_RC_FEAS_FTOL = Cint(-103)
const KN_RC_INFEASIBLE = Cint(-200)
const KN_RC_INFEAS_XTOL = Cint(-201)
const KN_RC_INFEAS_NO_IMPROVE = Cint(-202)
const KN_RC_INFEAS_MULTISTART = Cint(-203)
const KN_RC_INFEAS_CON_BOUNDS = Cint(-204)
const KN_RC_INFEAS_VAR_BOUNDS = Cint(-205)
const KN_RC_UNBOUNDED = Cint(-300)
if KNITRO_VERSION > v"12.0"
    const KN_RC_UNBOUNDED_OR_INFEAS = Cint(-301)
end
const KN_RC_ITER_LIMIT_FEAS = Cint(-400)
const KN_RC_TIME_LIMIT_FEAS = Cint(-401)
const KN_RC_FEVAL_LIMIT_FEAS = Cint(-402)
const KN_RC_MIP_EXH_FEAS = Cint(-403)
const KN_RC_MIP_TERM_FEAS = Cint(-404)
const KN_RC_MIP_SOLVE_LIMIT_FEAS = Cint(-405)
const KN_RC_MIP_NODE_LIMIT_FEAS = Cint(-406)
const KN_RC_ITER_LIMIT_INFEAS = Cint(-410)
const KN_RC_TIME_LIMIT_INFEAS = Cint(-411)
const KN_RC_FEVAL_LIMIT_INFEAS = Cint(-412)
const KN_RC_MIP_EXH_INFEAS = Cint(-413)
const KN_RC_MIP_SOLVE_LIMIT_INFEAS = Cint(-415)
const KN_RC_MIP_NODE_LIMIT_INFEAS = Cint(-416)
const KN_RC_CALLBACK_ERR = Cint(-500)
const KN_RC_LP_SOLVER_ERR = Cint(-501)
const KN_RC_EVAL_ERR = Cint(-502)
const KN_RC_OUT_OF_MEMORY = Cint(-503)
const KN_RC_USER_TERMINATION = Cint(-504)
const KN_RC_OPEN_FILE_ERR = Cint(-505)
const KN_RC_BAD_N_OR_F = Cint(-506)
const KN_RC_BAD_CONSTRAINT = Cint(-507)
const KN_RC_BAD_JACOBIAN = Cint(-508)
const KN_RC_BAD_HESSIAN = Cint(-509)
const KN_RC_BAD_CON_INDEX = Cint(-510)
const KN_RC_BAD_JAC_INDEX = Cint(-511)
const KN_RC_BAD_HESS_INDEX = Cint(-512)
const KN_RC_BAD_CON_BOUNDS = Cint(-513)
const KN_RC_BAD_VAR_BOUNDS = Cint(-514)
const KN_RC_ILLEGAL_CALL = Cint(-515)
const KN_RC_BAD_KCPTR = Cint(-516)
const KN_RC_NULL_POINTER = Cint(-517)
const KN_RC_BAD_INIT_VALUE = Cint(-518)
const KN_RC_LICENSE_ERROR = Cint(-520)
const KN_RC_BAD_PARAMINPUT = Cint(-521)
const KN_RC_LINEAR_SOLVER_ERR = Cint(-522)
const KN_RC_DERIV_CHECK_FAILED = Cint(-523)
const KN_RC_DERIV_CHECK_TERMINATE = Cint(-524)
const KN_RC_OVERFLOW_ERR = Cint(-525)
const KN_RC_BAD_SIZE = Cint(-526)
const KN_RC_BAD_VARIABLE = Cint(-527)
const KN_RC_BAD_VAR_INDEX = Cint(-528)
const KN_RC_BAD_OBJECTIVE = Cint(-529)
const KN_RC_BAD_OBJ_INDEX = Cint(-530)
const KN_RC_BAD_RESIDUAL = Cint(-531)
const KN_RC_BAD_RSD_INDEX = Cint(-532)
const KN_RC_INTERNAL_ERROR = Cint(-600)
const KN_PARAM_NEWPOINT = Cint(1001)
const KN_PARAM_HONORBNDS = Cint(1002)
const KN_PARAM_ALGORITHM = Cint(1003)
const KN_PARAM_ALG = Cint(1003)
const KN_PARAM_BAR_MURULE = Cint(1004)
const KN_PARAM_BAR_FEASIBLE = Cint(1006)
const KN_PARAM_GRADOPT = Cint(1007)
const KN_PARAM_HESSOPT = Cint(1008)
const KN_PARAM_BAR_INITPT = Cint(1009)
const KN_PARAM_ACT_LPSOLVER = Cint(1012)
const KN_PARAM_CG_MAXIT = Cint(1013)
const KN_PARAM_MAXIT = Cint(1014)
const KN_PARAM_OUTLEV = Cint(1015)
const KN_PARAM_OUTMODE = Cint(1016)
const KN_PARAM_SCALE = Cint(1017)
const KN_PARAM_SOC = Cint(1019)
const KN_PARAM_DELTA = Cint(1020)
const KN_PARAM_BAR_FEASMODETOL = Cint(1021)
const KN_PARAM_FEASTOL = Cint(1022)
const KN_PARAM_FEASTOLABS = Cint(1023)
const KN_PARAM_MAXTIMECPU = Cint(1024)
const KN_PARAM_BAR_INITMU = Cint(1025)
const KN_PARAM_OBJRANGE = Cint(1026)
const KN_PARAM_OPTTOL = Cint(1027)
const KN_PARAM_OPTTOLABS = Cint(1028)
const KN_PARAM_LINSOLVER_PIVOTTOL = Cint(1029)
const KN_PARAM_XTOL = Cint(1030)
const KN_PARAM_DEBUG = Cint(1031)
const KN_PARAM_MULTISTART = Cint(1033)
const KN_PARAM_MSENABLE = Cint(1033)
const KN_PARAM_MSMAXSOLVES = Cint(1034)
const KN_PARAM_MSMAXBNDRANGE = Cint(1035)
const KN_PARAM_MSMAXTIMECPU = Cint(1036)
const KN_PARAM_MSMAXTIMEREAL = Cint(1037)
const KN_PARAM_LMSIZE = Cint(1038)
const KN_PARAM_BAR_MAXCROSSIT = Cint(1039)
const KN_PARAM_MAXTIMEREAL = Cint(1040)
const KN_PARAM_CG_PRECOND = Cint(1041)
const KN_PARAM_BLASOPTION = Cint(1042)
const KN_PARAM_BAR_MAXREFACTOR = Cint(1043)
const KN_PARAM_LINESEARCH_MAXTRIALS = Cint(1044)
const KN_PARAM_BLASOPTIONLIB = Cint(1045)
const KN_PARAM_OUTAPPEND = Cint(1046)
const KN_PARAM_OUTDIR = Cint(1047)
const KN_PARAM_CPLEXLIB = Cint(1048)
const KN_PARAM_BAR_PENRULE = Cint(1049)
const KN_PARAM_BAR_PENCONS = Cint(1050)
const KN_PARAM_MSNUMTOSAVE = Cint(1051)
const KN_PARAM_MSSAVETOL = Cint(1052)
const KN_PARAM_PRESOLVEDEBUG = Cint(1053)
const KN_PARAM_MSTERMINATE = Cint(1054)
const KN_PARAM_MSSTARTPTRANGE = Cint(1055)
const KN_PARAM_INFEASTOL = Cint(1056)
const KN_PARAM_LINSOLVER = Cint(1057)
const KN_PARAM_BAR_DIRECTINTERVAL = Cint(1058)
const KN_PARAM_PRESOLVE = Cint(1059)
const KN_PARAM_PRESOLVE_TOL = Cint(1060)
const KN_PARAM_BAR_SWITCHRULE = Cint(1061)
const KN_PARAM_HESSIAN_NO_F = Cint(1062)
const KN_PARAM_MA_TERMINATE = Cint(1063)
const KN_PARAM_MA_MAXTIMECPU = Cint(1064)
const KN_PARAM_MA_MAXTIMEREAL = Cint(1065)
const KN_PARAM_MSSEED = Cint(1066)
const KN_PARAM_MA_OUTSUB = Cint(1067)
const KN_PARAM_MS_OUTSUB = Cint(1068)
const KN_PARAM_XPRESSLIB = Cint(1069)
const KN_PARAM_TUNER = Cint(1070)
const KN_PARAM_TUNER_OPTIONSFILE = Cint(1071)
const KN_PARAM_TUNER_MAXTIMECPU = Cint(1072)
const KN_PARAM_TUNER_MAXTIMEREAL = Cint(1073)
const KN_PARAM_TUNER_OUTSUB = Cint(1074)
const KN_PARAM_TUNER_TERMINATE = Cint(1075)
const KN_PARAM_LINSOLVER_OOC = Cint(1076)
const KN_PARAM_BAR_RELAXCONS = Cint(1077)
const KN_PARAM_MSDETERMINISTIC = Cint(1078)
const KN_PARAM_BAR_REFINEMENT = Cint(1079)
const KN_PARAM_DERIVCHECK = Cint(1080)
const KN_PARAM_DERIVCHECK_TYPE = Cint(1081)
const KN_PARAM_DERIVCHECK_TOL = Cint(1082)
const KN_PARAM_LINSOLVER_INEXACT = Cint(1083)
const KN_PARAM_LINSOLVER_INEXACTTOL = Cint(1084)
const KN_PARAM_MAXFEVALS = Cint(1085)
const KN_PARAM_FSTOPVAL = Cint(1086)
const KN_PARAM_DATACHECK = Cint(1087)
const KN_PARAM_DERIVCHECK_TERMINATE = Cint(1088)
const KN_PARAM_BAR_WATCHDOG = Cint(1089)
const KN_PARAM_FTOL = Cint(1090)
const KN_PARAM_FTOL_ITERS = Cint(1091)
const KN_PARAM_ACT_QPALG = Cint(1092)
const KN_PARAM_BAR_INITPI_MPEC = Cint(1093)
const KN_PARAM_XTOL_ITERS = Cint(1094)
const KN_PARAM_LINESEARCH = Cint(1095)
const KN_PARAM_OUT_CSVINFO = Cint(1096)
const KN_PARAM_INITPENALTY = Cint(1097)
const KN_PARAM_ACT_LPFEASTOL = Cint(1098)
const KN_PARAM_CG_STOPTOL = Cint(1099)
const KN_PARAM_RESTARTS = Cint(1100)
const KN_PARAM_RESTARTS_MAXIT = Cint(1101)
const KN_PARAM_BAR_SLACKBOUNDPUSH = Cint(1102)
const KN_PARAM_CG_PMEM = Cint(1103)
const KN_PARAM_BAR_SWITCHOBJ = Cint(1104)
const KN_PARAM_OUTNAME = Cint(1105)
const KN_PARAM_OUT_CSVNAME = Cint(1106)
const KN_PARAM_ACT_PARAMETRIC = Cint(1107)
const KN_PARAM_ACT_LPDUMPMPS = Cint(1108)
const KN_PARAM_ACT_LPALG = Cint(1109)
const KN_PARAM_ACT_LPPRESOLVE = Cint(1110)
const KN_PARAM_ACT_LPPENALTY = Cint(1111)
const KN_PARAM_BNDRANGE = Cint(1112)
const KN_PARAM_BAR_CONIC_ENABLE = Cint(1113)
const KN_PARAM_CONVEX = Cint(1114)
const KN_PARAM_OUT_HINTS = Cint(1115)
const KN_PARAM_EVAL_FCGA = Cint(1116)
const KN_PARAM_MIP_METHOD = Cint(2001)
const KN_PARAM_MIP_BRANCHRULE = Cint(2002)
const KN_PARAM_MIP_SELECTRULE = Cint(2003)
const KN_PARAM_MIP_INTGAPABS = Cint(2004)
const KN_PARAM_MIP_INTGAPREL = Cint(2005)
const KN_PARAM_MIP_MAXTIMECPU = Cint(2006)
const KN_PARAM_MIP_MAXTIMEREAL = Cint(2007)
const KN_PARAM_MIP_MAXSOLVES = Cint(2008)
const KN_PARAM_MIP_INTEGERTOL = Cint(2009)
const KN_PARAM_MIP_OUTLEVEL = Cint(2010)
const KN_PARAM_MIP_OUTINTERVAL = Cint(2011)
const KN_PARAM_MIP_OUTSUB = Cint(2012)
const KN_PARAM_MIP_DEBUG = Cint(2013)
const KN_PARAM_MIP_IMPLICATNS = Cint(2014)
const KN_PARAM_MIP_GUB_BRANCH = Cint(2015)
const KN_PARAM_MIP_KNAPSACK = Cint(2016)
const KN_PARAM_MIP_ROUNDING = Cint(2017)
const KN_PARAM_MIP_ROOTALG = Cint(2018)
const KN_PARAM_MIP_LPALG = Cint(2019)
const KN_PARAM_MIP_TERMINATE = Cint(2020)
const KN_PARAM_MIP_MAXNODES = Cint(2021)
const KN_PARAM_MIP_HEURISTIC = Cint(2022)
const KN_PARAM_MIP_HEUR_MAXIT = Cint(2023)
const KN_PARAM_MIP_HEUR_MAXTIMECPU = Cint(2024)
const KN_PARAM_MIP_HEUR_MAXTIMEREAL = Cint(2025)
const KN_PARAM_MIP_PSEUDOINIT = Cint(2026)
const KN_PARAM_MIP_STRONG_MAXIT = Cint(2027)
const KN_PARAM_MIP_STRONG_CANDLIM = Cint(2028)
const KN_PARAM_MIP_STRONG_LEVEL = Cint(2029)
const KN_PARAM_MIP_INTVAR_STRATEGY = Cint(2030)
const KN_PARAM_MIP_RELAXABLE = Cint(2031)
const KN_PARAM_MIP_NODEALG = Cint(2032)
const KN_PARAM_MIP_HEUR_TERMINATE = Cint(2033)
const KN_PARAM_MIP_SELECTDIR = Cint(2034)
const KN_PARAM_PAR_NUMTHREADS = Cint(3001)
const KN_PARAM_PAR_CONCURRENT_EVALS = Cint(3002)
const KN_PARAM_PAR_BLASNUMTHREADS = Cint(3003)
const KN_PARAM_PAR_LSNUMTHREADS = Cint(3004)
const KN_PARAM_PAR_MSNUMTHREADS = Cint(3005)
const KN_NEWPOINT_NONE = Cint(0)
const KN_NEWPOINT_SAVEONE = Cint(1)
const KN_NEWPOINT_SAVEALL = Cint(2)
const KN_HONORBNDS_NO = Cint(0)
const KN_HONORBNDS_ALWAYS = Cint(1)
const KN_HONORBNDS_INITPT = Cint(2)
const KN_ALG_AUTOMATIC = Cint(0)
const KN_ALG_AUTO = Cint(0)
const KN_ALG_BAR_DIRECT = Cint(1)
const KN_ALG_BAR_CG = Cint(2)
const KN_ALG_ACT_CG = Cint(3)
const KN_ALG_ACT_SQP = Cint(4)
const KN_ALG_MULTI = Cint(5)
const KN_BAR_MURULE_AUTOMATIC = Cint(0)
const KN_BAR_MURULE_AUTO = Cint(0)
const KN_BAR_MURULE_MONOTONE = Cint(1)
const KN_BAR_MURULE_ADAPTIVE = Cint(2)
const KN_BAR_MURULE_PROBING = Cint(3)
const KN_BAR_MURULE_DAMPMPC = Cint(4)
const KN_BAR_MURULE_FULLMPC = Cint(5)
const KN_BAR_MURULE_QUALITY = Cint(6)
const KN_BAR_FEASIBLE_NO = Cint(0)
const KN_BAR_FEASIBLE_STAY = Cint(1)
const KN_BAR_FEASIBLE_GET = Cint(2)
const KN_BAR_FEASIBLE_GET_STAY = Cint(3)
const KN_GRADOPT_EXACT = Cint(1)
const KN_GRADOPT_FORWARD = Cint(2)
const KN_GRADOPT_CENTRAL = Cint(3)
const KN_GRADOPT_USER_FORWARD = Cint(4)
const KN_GRADOPT_USER_CENTRAL = Cint(5)
const KN_HESSOPT_AUTO = Cint(0)
const KN_HESSOPT_EXACT = Cint(1)
const KN_HESSOPT_BFGS = Cint(2)
const KN_HESSOPT_SR1 = Cint(3)
const KN_HESSOPT_PRODUCT_FINDIFF = Cint(4)
const KN_HESSOPT_PRODUCT = Cint(5)
const KN_HESSOPT_LBFGS = Cint(6)
const KN_HESSOPT_GAUSS_NEWTON = Cint(7)
const KN_BAR_INITPT_AUTO = Cint(0)
const KN_BAR_INITPT_CONVEX = Cint(1)
const KN_BAR_INITPT_NEARBND = Cint(2)
const KN_BAR_INITPT_CENTRAL = Cint(3)
const KN_ACT_LPSOLVER_INTERNAL = Cint(1)
const KN_ACT_LPSOLVER_CPLEX = Cint(2)
const KN_ACT_LPSOLVER_XPRESS = Cint(3)
const KN_OUTLEV_NONE = Cint(0)
const KN_OUTLEV_SUMMARY = Cint(1)
const KN_OUTLEV_ITER_10 = Cint(2)
const KN_OUTLEV_ITER = Cint(3)
const KN_OUTLEV_ITER_VERBOSE = Cint(4)
const KN_OUTLEV_ITER_X = Cint(5)
const KN_OUTLEV_ALL = Cint(6)
const KN_OUTMODE_SCREEN = Cint(0)
const KN_OUTMODE_FILE = Cint(1)
const KN_OUTMODE_BOTH = Cint(2)
const KN_SCALE_NEVER = Cint(0)
const KN_SCALE_NO = Cint(0)
const KN_SCALE_USER_INTERNAL = Cint(1)
const KN_SCALE_USER_NONE = Cint(2)
const KN_SCALE_INTERNAL = Cint(3)
const KN_SOC_NO = Cint(0)
const KN_SOC_MAYBE = Cint(1)
const KN_SOC_YES = Cint(2)
const KN_DEBUG_NONE = Cint(0)
const KN_DEBUG_PROBLEM = Cint(1)
const KN_DEBUG_EXECUTION = Cint(2)
const KN_MULTISTART_NO = Cint(0)
const KN_MULTISTART_YES = Cint(1)
const KN_CG_PRECOND_NONE = Cint(0)
const KN_CG_PRECOND_CHOL = Cint(1)
const KN_BLASOPTION_KNITRO = Cint(0)
const KN_BLASOPTION_INTEL = Cint(1)
const KN_BLASOPTION_DYNAMIC = Cint(2)
const KN_OUTAPPEND_NO = Cint(0)
const KN_OUTAPPEND_YES = Cint(1)
const KN_BAR_PENRULE_AUTO = Cint(0)
const KN_BAR_PENRULE_SINGLE = Cint(1)
const KN_BAR_PENRULE_FLEX = Cint(2)
const KN_BAR_PENCONS_AUTO = Cint(0)
const KN_BAR_PENCONS_NONE = Cint(1)
const KN_BAR_PENCONS_ALL = Cint(2)
const KN_BAR_PENCONS_EQUALITIES = Cint(3)
const KN_BAR_PENCONS_INFEAS = Cint(4)
const KN_PRESOLVEDBG_NONE = Cint(0)
const KN_PRESOLVEDBG_BASIC = Cint(1)
const KN_PRESOLVEDBG_VERBOSE = Cint(2)
const KN_MSTERMINATE_MAXSOLVES = Cint(0)
const KN_MSTERMINATE_OPTIMAL = Cint(1)
const KN_MSTERMINATE_FEASIBLE = Cint(2)
const KN_MSTERMINATE_ANY = Cint(3)
const KN_LINSOLVER_AUTO = Cint(0)
const KN_LINSOLVER_INTERNAL = Cint(1)
const KN_LINSOLVER_HYBRID = Cint(2)
const KN_LINSOLVER_DENSEQR = Cint(3)
const KN_LINSOLVER_MA27 = Cint(4)
const KN_LINSOLVER_MA57 = Cint(5)
const KN_LINSOLVER_MKLPARDISO = Cint(6)
const KN_LINSOLVER_MA97 = Cint(7)
const KN_LINSOLVER_MA86 = Cint(8)
const KN_PRESOLVE_NONE = Cint(0)
const KN_PRESOLVE_BASIC = Cint(1)
const KN_PRESOLVE_ADVANCED = Cint(2)
if KNITRO_VERSION >= v"12.0"
    const KN_PRESOLVE_NO = Cint(0)
    const KN_PRESOLVE_YES = Cint(1)
    const KN_PRESOLVE_ADVANCED = Cint(2)
end
const KN_BAR_SWITCHRULE_AUTO = Cint(0)
const KN_BAR_SWITCHRULE_NEVER = Cint(1)
const KN_BAR_SWITCHRULE_MODERATE = Cint(2)
const KN_BAR_SWITCHRULE_AGGRESSIVE = Cint(3)
const KN_HESSIAN_NO_F_FORBID = Cint(0)
const KN_HESSIAN_NO_F_ALLOW = Cint(1)
const KN_MA_TERMINATE_ALL = Cint(0)
const KN_MA_TERMINATE_OPTIMAL = Cint(1)
const KN_MA_TERMINATE_FEASIBLE = Cint(2)
const KN_MA_TERMINATE_ANY = Cint(3)
const KN_MA_OUTSUB_NONE = Cint(0)
const KN_MA_OUTSUB_YES = Cint(1)
const KN_MS_OUTSUB_NONE = Cint(0)
const KN_MS_OUTSUB_YES = Cint(1)
const KN_TUNER_OFF = Cint(0)
const KN_TUNER_ON = Cint(1)
const KN_TUNER_OUTSUB_NONE = Cint(0)
const KN_TUNER_OUTSUB_SUMMARY = Cint(1)
const KN_TUNER_OUTSUB_ALL = Cint(2)
const KN_TUNER_TERMINATE_ALL = Cint(0)
const KN_TUNER_TERMINATE_OPTIMAL = Cint(1)
const KN_TUNER_TERMINATE_FEASIBLE = Cint(2)
const KN_TUNER_TERMINATE_ANY = Cint(3)
const KN_LINSOLVER_OOC_NO = Cint(0)
const KN_LINSOLVER_OOC_MAYBE = Cint(1)
const KN_LINSOLVER_OOC_YES = Cint(2)
const KN_BAR_RELAXCONS_NONE = Cint(0)
const KN_BAR_RELAXCONS_EQS = Cint(1)
const KN_BAR_RELAXCONS_INEQS = Cint(2)
const KN_BAR_RELAXCONS_ALL = Cint(3)
const KN_MSDETERMINISTIC_NO = Cint(0)
const KN_MSDETERMINISTIC_YES = Cint(1)
const KN_BAR_REFINEMENT_NO = Cint(0)
const KN_BAR_REFINEMENT_YES = Cint(1)
const KN_DERIVCHECK_NONE = Cint(0)
const KN_DERIVCHECK_FIRST = Cint(1)
const KN_DERIVCHECK_SECOND = Cint(2)
const KN_DERIVCHECK_ALL = Cint(3)
const KN_DERIVCHECK_FORWARD = Cint(1)
const KN_DERIVCHECK_CENTRAL = Cint(2)
const KN_LINSOLVER_INEXACT_NO = Cint(0)
const KN_LINSOLVER_INEXACT_YES = Cint(1)
const KN_DATACHECK_NO = Cint(0)
const KN_DATACHECK_YES = Cint(1)
const KN_DERIVCHECK_STOPERROR = Cint(1)
const KN_DERIVCHECK_STOPALWAYS = Cint(2)
const KN_BAR_WATCHDOG_NO = Cint(0)
const KN_BAR_WATCHDOG_YES = Cint(1)
const KN_ACT_QPALG_AUTO = Cint(0)
const KN_ACT_QPALG_BAR_DIRECT = Cint(1)
const KN_ACT_QPALG_BAR_CG = Cint(2)
const KN_ACT_QPALG_ACT_CG = Cint(3)
const KN_LINESEARCH_AUTO = Cint(0)
const KN_LINESEARCH_BACKTRACK = Cint(1)
const KN_LINESEARCH_INTERPOLATE = Cint(2)
const KN_OUT_CSVINFO_NO = Cint(0)
const KN_OUT_CSVINFO_YES = Cint(1)
const KN_BAR_SWITCHOBJ_NONE = Cint(0)
const KN_BAR_SWITCHOBJ_SCALARPROX = Cint(1)
const KN_BAR_SWITCHOBJ_DIAGPROX = Cint(2)
const KN_ACT_PARAMETRIC_NO = Cint(0)
const KN_ACT_PARAMETRIC_MAYBE = Cint(1)
const KN_ACT_PARAMETRIC_YES = Cint(2)
const KN_ACT_LPDUMPMPS_NO = Cint(0)
const KN_ACT_LPDUMPMPS_YES = Cint(1)
const KN_ACT_LPALG_DEFAULT = Cint(0)
const KN_ACT_LPALG_PRIMAL = Cint(1)
const KN_ACT_LPALG_DUAL = Cint(2)
const KN_ACT_LPALG_BARRIER = Cint(3)
const KN_ACT_LPPRESOLVE_OFF = Cint(0)
const KN_ACT_LPPRESOLVE_ON = Cint(1)
const KN_ACT_LPPENALTY_ALL = Cint(1)
const KN_ACT_LPPENALTY_NONLINEAR = Cint(2)
const KN_ACT_LPPENALTY_DYNAMIC = Cint(3)
const KN_BAR_CONIC_ENABLE_NONE = Cint(0)
const KN_BAR_CONIC_ENABLE_SOC = Cint(1)
const KN_CONVEX_NO = Cint(0)
const KN_CONVEX_YES = Cint(1)
const KN_OUT_HINTS_NO = Cint(0)
const KN_OUT_HINTS_YES = Cint(1)
const KN_EVAL_FCGA_NO = Cint(0)
const KN_EVAL_FCGA_YES = Cint(1)
const KN_MIP_METHOD_AUTO = Cint(0)
const KN_MIP_METHOD_BB = Cint(1)
const KN_MIP_METHOD_HQG = Cint(2)
const KN_MIP_METHOD_MISQP = Cint(3)
const KN_MIP_BRANCH_AUTO = Cint(0)
const KN_MIP_BRANCH_MOSTFRAC = Cint(1)
const KN_MIP_BRANCH_PSEUDOCOST = Cint(2)
const KN_MIP_BRANCH_STRONG = Cint(3)
const KN_MIP_SEL_AUTO = Cint(0)
const KN_MIP_SEL_DEPTHFIRST = Cint(1)
const KN_MIP_SEL_BESTBOUND = Cint(2)
const KN_MIP_SEL_COMBO_1 = Cint(3)
const KN_MIP_OUTLEVEL_NONE = Cint(0)
const KN_MIP_OUTLEVEL_ITERS = Cint(1)
const KN_MIP_OUTLEVEL_ITERSTIME = Cint(2)
const KN_MIP_OUTLEVEL_ROOT = Cint(3)
const KN_MIP_OUTSUB_NONE = Cint(0)
const KN_MIP_OUTSUB_YES = Cint(1)
const KN_MIP_OUTSUB_YESPROB = Cint(2)
const KN_MIP_DEBUG_NONE = Cint(0)
const KN_MIP_DEBUG_ALL = Cint(1)
const KN_MIP_IMPLICATNS_NO = Cint(0)
const KN_MIP_IMPLICATNS_YES = Cint(1)
const KN_MIP_GUB_BRANCH_NO = Cint(0)
const KN_MIP_GUB_BRANCH_YES = Cint(1)
const KN_MIP_KNAPSACK_NO = Cint(0)
const KN_MIP_KNAPSACK_NONE = Cint(0)
const KN_MIP_KNAPSACK_INEQ = Cint(1)
const KN_MIP_KNAPSACK_INEQ_EQ = Cint(2)
const KN_MIP_ROUND_AUTO = Cint(0)
const KN_MIP_ROUND_NONE = Cint(1)
const KN_MIP_ROUND_HEURISTIC = Cint(2)
const KN_MIP_ROUND_NLP_SOME = Cint(3)
const KN_MIP_ROUND_NLP_ALWAYS = Cint(4)
const KN_MIP_ROOTALG_AUTO = Cint(0)
const KN_MIP_ROOTALG_BAR_DIRECT = Cint(1)
const KN_MIP_ROOTALG_BAR_CG = Cint(2)
const KN_MIP_ROOTALG_ACT_CG = Cint(3)
const KN_MIP_ROOTALG_ACT_SQP = Cint(4)
const KN_MIP_ROOTALG_MULTI = Cint(5)
const KN_MIP_LPALG_AUTO = Cint(0)
const KN_MIP_LPALG_BAR_DIRECT = Cint(1)
const KN_MIP_LPALG_BAR_CG = Cint(2)
const KN_MIP_LPALG_ACT_CG = Cint(3)
const KN_MIP_TERMINATE_OPTIMAL = Cint(0)
const KN_MIP_TERMINATE_FEASIBLE = Cint(1)
const KN_MIP_HEURISTIC_AUTO = Cint(0)
const KN_MIP_HEURISTIC_NONE = Cint(1)
const KN_MIP_HEURISTIC_FEASPUMP = Cint(2)
const KN_MIP_HEURISTIC_MPEC = Cint(3)
const KN_MIP_PSEUDOINIT_AUTO = Cint(0)
const KN_MIP_PSEUDOINIT_AVE = Cint(1)
const KN_MIP_PSEUDOINIT_STRONG = Cint(2)
const KN_MIP_INTVAR_STRATEGY_NONE = Cint(0)
const KN_MIP_INTVAR_STRATEGY_RELAX = Cint(1)
const KN_MIP_INTVAR_STRATEGY_MPEC = Cint(2)
const KN_MIP_RELAXABLE_NONE = Cint(0)
const KN_MIP_RELAXABLE_ALL = Cint(1)
const KN_MIP_NODEALG_AUTO = Cint(0)
const KN_MIP_NODEALG_BAR_DIRECT = Cint(1)
const KN_MIP_NODEALG_BAR_CG = Cint(2)
const KN_MIP_NODEALG_ACT_CG = Cint(3)
const KN_MIP_NODEALG_ACT_SQP = Cint(4)
const KN_MIP_NODEALG_MULTI = Cint(5)
const KN_MIP_HEUR_TERMINATE_FEASIBLE = Cint(1)
const KN_MIP_HEUR_TERMINATE_LIMIT = Cint(2)
const KN_MIP_SELECTDIR_DOWN = Cint(0)
const KN_MIP_SELECTDIR_UP = Cint(1)
const KN_PAR_CONCURRENT_EVALS_NO = Cint(0)
const KN_PAR_CONCURRENT_EVALS_YES = Cint(1)
const KN_PAR_MSNUMTHREADS_AUTO = Cint(0)
if KNITRO_VERSION >= v"12.0"
    # Cuts parameters
    const KN_PARAM_MIP_ZEROHALF = Cint(2036)
    const KN_MIP_ZEROHALF_NONE = Cint(0)
    const KN_MIP_ZEROHALF_ROOT = Cint(1)
    const KN_MIP_ZEROHALF_TREE = Cint(2)
    const KN_MIP_ZEROHALF_ALL = Cint(3)
    const KN_PARAM_MIP_MIR = Cint(2037)
    const KN_MIP_MIR_NONE = Cint(0)
    const KN_MIP_MIR_TREE = Cint(1)
    const KN_PARAM_MIP_CLIQUE = Cint(2038)
    const KN_MIP_CLIQUE_NONE = Cint(0)
    const KN_MIP_CLIQUE_ROOT = Cint(1)
    const KN_MIP_CLIQUE_TREE = Cint(2)
    const KN_MIP_CLIQUE_ALL = Cint(3)
    # SOCP parameters
    const KN_PARAM_PAR_CONICNUMTHREADS = Cint(3006)
    # Presolve parameters
    const KN_PARAM_PRESOLVE_PASSES = Cint(1121)
    const KN_PARAM_PRESOLVE_LEVEL = Cint(1122)
    const KN_PRESOLVE_LEVEL_AUTO = Cint(-1)
    const KN_PRESOLVE_LEVEL_1 = Cint(1)
    const KN_PRESOLVE_LEVEL_2 = Cint(2)
end
if KNITRO_VERSION >= v"12.1"
    const KN_PARAM_FINDIFF_RELSTEPSIZE = Cint(1123)
    const KN_PARAM_INFEASTOL_ITERS = Cint(1124)
    # New options for MIP MIR cuts
    const KN_MIP_MIR_AUTO = Cint(-1)
    const KN_MIP_MIR_NLP = Cint(2)
    # New options for presolve
    const KN_PARAM_PRESOLVEOP_TIGHTEN = Cint(1125)
    const KN_PRESOLVEOP_TIGHTEN_AUTO = Cint(-1)
    const KN_PRESOLVEOP_TIGHTEN_NONE = Cint(0)
    const KN_PRESOLVEOP_TIGHTEN_VARBND = Cint(1)
end
if KNITRO_VERSION >= v"12.2"
    const KN_LINESEARCH_WEAKWOLFE = Cint(3)
    # Tune factorization of KKT system
    const KN_PARAM_BAR_LINSYS = Cint(1126)
    const KN_BAR_LINSYS_AUTO = Cint(-1)
    const KN_BAR_LINSYS_FULL = Cint(0)
    const KN_BAR_LINSYS_COMPACT1 = Cint(1)
    const KN_BAR_LINSYS_COMPACT2 = Cint(2)
    # Add new ways to handle init point during presolve
    const KN_PARAM_PRESOLVE_INITPT = Cint(1127)
    const KN_PRESOLVE_INITPT_AUTO = Cint(-1)
    const KN_PRESOLVE_INITPT_NOSHIFT = Cint(0)
    const KN_PRESOLVE_INITPT_LINSHIFT = Cint(1)
    const KN_PRESOLVE_INITPT_ANYSHIFT = Cint(2)
    # QP penalty in active set algorithm
    const KN_PARAM_ACT_QPPENALTY = Cint(1128)
    const KN_ACT_QPPENALTY_AUTO = Cint(-1)
    const KN_ACT_QPPENALTY_NONE = Cint(0)
    const KN_ACT_QPPENALTY_ALL = Cint(1)
end
if KNITRO_VERSION >= v"12.3"
    const KN_PARAM_BAR_LINSYS_STORAGE = Cint(1129)
    const KN_BAR_LINSYS_STORAGE_AUTO = Cint(-1)
    const KN_BAR_LINSYS_STORAGE_LOWMEM = Cint(1)
    const KN_BAR_LINSYS_STORAGE_NORMAL = Cint(2)
    const KN_PARAM_LINSOLVER_MAXITREF = Cint(1130)
    const KN_PARAM_BFGS_SCALING = Cint(1131)
    const KN_BFGS_SCALING_DYNAMIC = Cint(0)
    const KN_BFGS_SCALING_INVHESS = Cint(1)
    const KN_BFGS_SCALING_HESS = Cint(2)
end


using BVHFiles
using Test
using Flux

@testset "file" begin
    println("Starting test of 'file'")

    g = load("Example.bvh") |>
        global_positions! |>
        remove_joint!(7) |>
        remove_joint!(13) |>
        remove_joints!("J_L_Bale", "J_R_Bale", "J_L4", "J_L3", "J_L1", "J_T12", "J_T10", "J_T9", "J_T8", "J_T6", "J_T5", "J_T4", "J_T3", "J_T2") |>
        remove_joint!("J_T1", "J_C7") |>
        remove_joint!("J_C7", "J_C6") |>
        remove_joint!("J_C6", "J_C5") |>
        remove_joints!("J_C4", "J_C3", "J_C2", "J_C1", "J_Atlas") |>
        optimize_offsets! |>
        optimize_rotations!(ADAM, 0.005, 10, [1, 2, 3, 4, 6, 8, 9, 10, 12, 14])

    @test nv(g) == 25
    @test ne(g) == 24
    @test total_squared_errors(g) < 36.335

    dict = Dict(  "J_L_Hip" => "lThighBend",
                    "J_R_Hip" => "rThighBend",
                    "J_L_Knee" => "lShin",
                    "J_R_Knee" => "rShin",
                    "J_L_Ankle" => "lFoot",
                    "J_R_Ankle" => "rFoot",
                    "J_L5" => "abdomenLower",
                    "J_L2" => "abdomenUpper",
                    "J_T11" => "chestLower",
                    "J_T7" => "chestUpper",
                    "J_L_Clavicle" => "lCollar",
                    "J_R_Clavicle" => "rCollar",
                    "J_L_Shoulder" => "lShldrBend",
                    "J_R_Shoulder" => "rShldrBend",
                    "J_L_Elbow" => "lForearmBend",
                    "J_R_Elbow" => "rForearmBend",
                    "J_L_Hand" => "lHand",
                    "J_R_Hand" => "rHand",
                    "J_C5" => "neckLower")

    g |>
        add_joint!("J_L_Hip", "J_L_Knee", "lThighTwist") |>
        add_joint!("J_R_Hip", "J_R_Knee", "rThighTwist") |>
        add_joint!("J_L_Shoulder", "J_L_Elbow", "lShldrTwist") |>
        add_joint!("J_R_Shoulder", "J_R_Elbow", "rShldrTwist") |>
        add_joint!("J_L_Elbow", "J_L_Hand", "lForearmTwist") |>
        add_joint!("J_R_Elbow", "J_R_Hand", "rForearmTwist") |>
        rename!(dict)

    @test nv(g) == 31
    @test ne(g) == 30

    name!(g, 1, "ROOT hip")
    oabdomen = offset(g, 1, find(g, "abdomenLower"))
    add_joint!(g, "hip", "pelvis", [0.0, 0.0, oabdomen[3]], [find(g, "lThighBend"), find(g, "rThighBend")])
    ofoot = offset(g, find(g, "lShin"), find(g, "lFoot"))
    add_joint!(g, "lFoot", "lMetatarsals", [0.0, ofoot[3] / 70, ofoot[3] / 70])
    add_joint!(g, "rFoot", "rMetatarsals", [0.0, ofoot[3] / 70, ofoot[3] / 70])
    exclude = [find(g, "lCollar"), find(g, "rCollar"), find(g, "lThighBend"), find(g, "rThighBend")]
    T = [-1.0 0.0 0.0; 0.0 0.0 1.0; 0.0 1.0 0.0]

    @test nv(g) == 34
    @test ne(g) == 33

    replace_offsets!(g, load("DAZ3D.bvh"), exclude, T) |>
    change_sequences!(:ZXY) |>
    scale!(7.0)

    d = load("DAZ3D.bvh") |>
        zero! |>
        add_frames!(240) |>
        project!(g, T)

    @test frames(d) == frames(g)

    add_frames!(d, 10)

    @test frames(d) == 251

    remove_frames!(d, 20)

    @test frames(d) == 231

    remove_frames!(d, 20, false)

    @test frames(d) == 211
end


################################################################################

"""
    process(self::HMM, d::Array{T, 2}, splitSw::Bool;
    args::HMMParams) where T <: Number

# Description
Process hidden Markov model object

# Examples
```julia-repl
julia>

```

See also: [`setup!`](@ref), [`HMM`](@ref), [`HMMParams`](@ref)
"""
function process!(self::HMM, d::Array{T, 2}, splitSw::Bool; args::HMMParams) where T <: Number

  # reset
  reset!(self)

  # feed frame
  for ix in axes(d, 1)
    feed!(self, ix, d, args = args)
  end

  # backtrace
  tb = backTrace(self)

  divider = fill(1, size(self.dataM, 1))
  orig = deepcopy(self.dataM)
  pp = StructArrays.StructArray{ScorePair}(undef, 0)

  # update model
  mdist = zeros(Float64, size(self.dataM))
  mcount = zeros(Float64, size(self.dataM))

  for ix in axes(d, 1)
    self.dataM[tb[ix]] .+= d[ix, :]
    divider[tb[ix]] += 1
    pair = ScorePair(args.distance(orig[tb[ix]], d[ix, :]), ix)

    mdist[tb[ix]] += pair.score
    mcount[tb[ix]] += 1

    push!(pp, pair)
  end

  scores = sort(pp.score, rev = true)
  ixs = map(x -> findall(x .== pp.score), scores)
  pp = pp[vcat(ixs...)]

  for ix in eachindex(self.dataM)
    self.dataM[ix] /= divider[ix]
  end

  sortHMM!(self)

  if !splitSw
    return tb, self.dataM
  end

  max = 0.
  toSplit = 1
  minFreq = 20 # hard coded!

  for ix in eachindex(mdist)
    if mcount[ix] > minFreq
      avdist = mdist[ix] / mcount[ix]
      if avdist > max
        max = avdist
        toSplit = ix
      end
    end
  end

  half = 1 + mcount[toSplit] / 4

  extra = fill(0, size(self.dataM[1], 1))

  count = 1
  for ix in eachindex(pp)
    if tb[pp[ix].index] != toSplit
      continue
    end
    extra += d[pp[ix].index, :]
    count += 1
    if count >= half
      break
    end
  end

  extra ./= (count - 1)

  push!(self.dataM, extra)

  push!(self.tbM, fill(0, size(self.tbM[1], 1)))

  return tb, self.dataM

end

################################################################################


module MathOptInterface

"""
    ModelLike

Abstract supertype for objects that implement the "Model" interface for defining
an optimization problem.
"""
abstract type ModelLike end

# This allows to use `ModelLike`s in broadcast calls without the need to
# embed it in a `Ref`
Base.broadcastable(model::ModelLike) = Ref(model)

function Base.show(io::IO, model::ModelLike)
    return Utilities.print_with_acronym(io, summary(model))
end

"""
    AbstractOptimizer <: ModelLike

Abstract supertype for objects representing an instance of an optimization
problem tied to a particular solver. This is typically a solver's in-memory
representation. In addition to `ModelLike`, `AbstractOptimizer` objects let you
solve the model and query the solution.
"""
abstract type AbstractOptimizer <: ModelLike end

"""
    optimize!(optimizer::AbstractOptimizer)

Optimize the problem contained in `optimizer`.

Before calling `optimize!`, the problem should first be constructed using the
incremental interface (see [`supports_incremental_interface`](@ref)) or [`copy_to`](@ref).
"""
function optimize! end

"""
    optimize!(dest::AbstractOptimizer, src::ModelLike)::Tuple{IndexMap,Bool}

A "one-shot" call that copies the problem from `src` into `dest` and then uses
`dest` to optimize the problem.

Returns a tuple of an [`IndexMap`](@ref) and a `Bool` `copied`.

 * The [`IndexMap`](@ref) object translates variable and constraint indices from
   the `src` model to the corresponding indices in the `dest` optimizer. See
   [`copy_to`](@ref) for details.
 * If `copied == true`, `src` was copied to `dest` and then cached, allowing
   incremental modification if supported by the solver.
 * If `copied == false`, a cache of the model was _not_ kept in `dest`.
   Therefore, only the solution information (attributes for which
   [`is_set_by_optimize`](@ref) is true) is available to query.

!!! note
    The main purpose of `optimize!` method with two arguments is for use in
    [`Utilities.CachingOptimizer`](@ref).

!!! warning
    The new `optimize!` method with two arguments is an experimental new feature
    of MOI v0.10.2 that may break in MOI v1.0.

## Relationship to the single-argument `optimize!`

The default fallback of `optimize!(dest::AbstractOptimizer, src::ModelLike)` is
```julia
function optimize!(dest::AbstractOptimizer, src::ModelLike)
    index_map = copy_to(dest, src)
    optimize!(dest)
    return index_map, true
end
```
Therefore, subtypes of [`AbstractOptimizer`](@ref) should either implement this
two-argument method, or implement both [`copy_to(::Optimizer, ::ModelLike)`](@ref copy_to)
and `optimize!(::Optimizer)`.
"""
function optimize!(dest, src)
    # The arguments above are untyped to avoid ambiguities.
    index_map = copy_to(dest, src)
    optimize!(dest)
    return index_map, true
end

"""
    compute_conflict!(optimizer::AbstractOptimizer)

Computes a minimal subset of constraints such that the model with the other
constraint removed is still infeasible.

Some solvers call a set of conflicting constraints an Irreducible Inconsistent
Subsystem (IIS).

See also [`ConflictStatus`](@ref) and [`ConstraintConflictStatus`](@ref).

### Note

If the model is modified after a call to `compute_conflict!`, the implementor
is not obliged to purge the conflict. Any calls to the above attributes may
return values for the original conflict without a warning. Similarly, when
modifying the model, the conflict can be discarded.
"""
function compute_conflict!(optimizer::AbstractOptimizer)
    return throw(
        ArgumentError(
            "The optimizer $(typeof(optimizer)) does not support " *
            "`compute_conflict!`",
        ),
    )
end

"""
    write_to_file(model::ModelLike, filename::String)

Writes the current model data to the given file.
Supported file types depend on the model type.
"""
function write_to_file end

"""
    read_from_file(model::ModelLike, filename::String)

Read the file `filename` into the model `model`. If `model` is non-empty, this may
throw an error.

Supported file types depend on the model type.

### Note

Once the contents of the file are loaded into the model, users can query the variables via
`get(model, ListOfVariableIndices())`. However, some filetypes, such as LP files, do not
maintain an explicit ordering of the variables. Therefore, the returned list may be in an
arbitrary order. To avoid depending on the order of the indices, users should look up each
variable index by name: `get(model, VariableIndex, "name")`.
"""
function read_from_file end

"""
    is_empty(model::ModelLike)

Returns `false` if the `model` has any model attribute set or has any variables
or constraints.

Note that an empty model can have optimizer attributes set.
"""
function is_empty end

"""
    empty!(model::ModelLike)

Empty the model, that is, remove all variables, constraints and model attributes
but not optimizer attributes.
"""
function empty! end

"""
    supports_incremental_interface(model::ModelLike)

Return a `Bool` indicating whether `model` supports building incrementally via
[`add_variable`](@ref) and [`add_constraint`](@ref).

The main purpose of this function is to determine whether a model can be loaded
into `model` incrementally or whether it should be cached and copied at once
instead.
"""
supports_incremental_interface(::ModelLike) = false

"""
    copy_to(dest::ModelLike, src::ModelLike)::IndexMap

Copy the model from `src` into `dest`.

The target `dest` is emptied, and all previous indices to variables and
constraints in `dest` are invalidated.

Returns an [`IndexMap`](@ref) object that translates variable and constraint
indices from the `src` model to the corresponding indices in the `dest` model.

## Notes

 * If a constraint that in `src` is not supported by `dest`, then an
   [`UnsupportedConstraint`](@ref) error is thrown.
 * If an [`AbstractModelAttribute`](@ref), [`AbstractVariableAttribute`](@ref),
   or [`AbstractConstraintAttribute`](@ref) is set in `src` but not supported by
   `dest`, then an [`UnsupportedAttribute`](@ref) error is thrown.

[`AbstractOptimizerAttribute`](@ref)s are _not_ copied  to the `dest` model.

## IndexMap

Implementations of `copy_to` must return an [`IndexMap`](@ref). For technical
reasons, this type is defined in the Utilities submodule as
`MOI.Utilities.IndexMap`. However, since it is an integral part of the MOI API,
we provide `MOI.IndexMap` as an alias.

### Example

```julia
# Given empty `ModelLike` objects `src` and `dest`.

x = add_variable(src)

is_valid(src, x)   # true
is_valid(dest, x)  # false (`dest` has no variables)

index_map = copy_to(dest, src)
is_valid(dest, x) # false (unless index_map[x] == x)
is_valid(dest, index_map[x]) # true
```
"""
function copy_to(dest, src)
    return error(
        "`copy_to` is not supported by the solver `$(typeof(dest))`. Did " *
        "you mean to call " *
        "`optimize!(dest::AbstractOptimizer, src::ModelLike)` instead?",
    )
end

include("error.jl")
include("indextypes.jl")
include("functions.jl")
include("sets.jl")
include("attributes.jl")
include("constraints.jl")
include("modifications.jl")
include("variables.jl")
include("nlp.jl")

include("precompile.jl")

# submodules
include("Utilities/Utilities.jl") # MOI.Utilities
include("Test/Test.jl")
include("Bridges/Bridges.jl")     # MOI.Bridges
include("Benchmarks/Benchmarks.jl")
include("FileFormats/FileFormats.jl")

include("instantiate.jl")

_precompile_()

"""
    IndexMap()

The dictionary-like object returned by [`copy_to`](@ref).

## IndexMap

Implementations of [`copy_to`](@ref) must return an [`IndexMap`](@ref). For
technical reasons, the `IndexMap` type is defined in the Utilties submodule as
`MOI.Utilities.IndexMap`. However, since it is an integral part of the MOI API,
we provide this `MOI.IndexMap` as an alias.
"""
const IndexMap = Utilities.IndexMap

end


struct DeadVolume{T} <: AbstractVirtualVolume{T}
    name::String
    geometry::AbstractGeometry{T}
end

function modulate_driftvector(sv::CartesianVector{T}, cp::CartesianPoint{T}, tl::DeadVolume{T})::CartesianVector{T} where {T <: SSDFloat}
    return CartesianVector{T}(0,0,0)
end

function DeadVolume{T}(dict::Dict, inputunit_dict::Dict{String,Unitful.Units}) where T <: SSDFloat
    n = haskey(dict, "name") ? dict["name"] : "external part"
    g = Geometry(T, dict["geometry"], inputunit_dict)
    return DeadVolume{T}(n, g)
end

struct ArbitraryDriftModificationVolume{T} <: AbstractVirtualVolume{T}
    name::String
    id::Int
    geometry::AbstractGeometry{T}
end

function modulate_driftvector(sv::CartesianVector{T}, cp::CartesianPoint{T}, tl::ArbitraryDriftModificationVolume{T})::CartesianVector{T} where {T <: SSDFloat}
    modulate_driftvector(sv, cp, tl, Val{tl.id})
end
function modulate_driftvector(sv::CartesianVector{T}, cp::CartesianPoint{T}, tl::ArbitraryDriftModificationVolume{T}, ::Type{Val{id}})::CartesianVector{T} where {T <: SSDFloat, id}
    error("""
        This function needs to be overwritten by the user. Use `::Type{Val{<id>}}` for the last argument.
        <id> is the corresponding id specified in the json-config file of the detector.
    """)
end

function ArbitraryDriftModificationVolume{T}(dict::Dict, inputunit_dict::Dict{String,Unitful.Units}) where T <: SSDFloat
    n = haskey(dict, "name") ? dict["name"] : "external part"
    g = Geometry(T, dict["geometry"], inputunit_dict)
    id = Int(dict["id"])
    return ArbitraryDriftModificationVolume{T}(n, id, g)
end


mutable struct GridRoomsDirected{T, R} <: AbstractGridWorld
    world::GridWorldBase{Tuple{Agent, Wall, Goal}}
    agent_pos::CartesianIndex{2}
    agent_dir::AbstractDirection
    reward::T
    rng::R
    terminal_reward::T
    goal_pos::CartesianIndex{2}
    done::Bool
end

@generate_getters(GridRoomsDirected)
@generate_setters(GridRoomsDirected)

mutable struct GridRoomsUndirected{T, R} <: AbstractGridWorld
    world::GridWorldBase{Tuple{Agent, Wall, Goal}}
    agent_pos::CartesianIndex{2}
    reward::T
    rng::R
    terminal_reward::T
    goal_pos::CartesianIndex{2}
    done::Bool
end

@generate_getters(GridRoomsUndirected)
@generate_setters(GridRoomsUndirected)

#####
# Directed
#####

function GridRoomsDirected(; T = Float32, grid_size = (2, 2), room_size = (5, 5), rng = Random.GLOBAL_RNG)
    grid_height = grid_size[1]
    grid_width = grid_size[2]
    room_height = room_size[1]
    room_width = room_size[2]

    height = room_height * grid_height - grid_height + 1
    width = room_width * grid_width - grid_width + 1
    objects = (AGENT, WALL, GOAL)
    world = GridWorldBase(objects, height, width)

    origins = get_room_origins(grid_size, room_size)

    for origin in origins
        room = Room(origin, room_height, room_width)
        place_room!(world, room)

        world[WALL, room.region[(end + 1) ÷ 2, 1]] = false
        world[WALL, room.region[(end + 1) ÷ 2, end]] = false
        world[WALL, room.region[1, (end + 1) ÷ 2]] = false
        world[WALL, room.region[end, (end + 1) ÷ 2]] = false
    end

    world[WALL, [1, height], :] .= true
    world[WALL, :, [1, width]] .= true

    agent_pos = CartesianIndex(2, 2)
    agent_dir = RIGHT
    reward = zero(T)
    terminal_reward = one(T)
    goal_pos = CartesianIndex(height - 1, width - 1)
    done = false

    env = GridRoomsDirected(world, agent_pos, agent_dir, reward, rng, terminal_reward, goal_pos, done)

    RLBase.reset!(env)

    return env
end

RLBase.state_space(env::GridRoomsDirected, ::RLBase.Observation, ::RLBase.DefaultPlayer) = nothing
const GRID_ROOMS_DIRECTED_LAYERS = SA.SVector(2, 3)
RLBase.state(env::GridRoomsDirected, ::RLBase.Observation, ::RLBase.DefaultPlayer) = get_grid(get_world(env), get_agent_pos(env), get_agent_dir(env), get_half_size(env), GRID_ROOMS_DIRECTED_LAYERS)

RLBase.state_space(env::GridRoomsDirected, ::RLBase.InternalState, ::RLBase.DefaultPlayer) = nothing
RLBase.state(env::GridRoomsDirected, ::RLBase.InternalState, ::RLBase.DefaultPlayer) = (copy(get_grid(env)), get_agent_dir(env))

RLBase.action_space(env::GridRoomsDirected, ::RLBase.DefaultPlayer) = DIRECTED_NAVIGATION_ACTIONS
RLBase.reward(env::GridRoomsDirected, ::RLBase.DefaultPlayer) = get_reward(env)
RLBase.is_terminated(env::GridRoomsDirected) = get_done(env)

function RLBase.reset!(env::GridRoomsDirected{T}) where {T}
    world = get_world(env)
    rng = get_rng(env)

    world[AGENT, get_agent_pos(env)] = false
    world[GOAL, get_goal_pos(env)] = false

    new_goal_pos = rand(rng, pos -> !any(@view world[:, pos]), env)
    set_goal_pos!(env, new_goal_pos)
    world[GOAL, new_goal_pos] = true

    new_agent_pos = rand(rng, pos -> !any(@view world[:, pos]), env)
    set_agent_pos!(env, new_agent_pos)
    world[AGENT, new_agent_pos] = true

    set_agent_dir!(env, rand(rng, DIRECTIONS))

    set_reward!(env, zero(T))
    set_done!(env, false)

    return nothing
end

function (env::GridRoomsDirected{T})(action::AbstractTurnAction) where {T}
    new_dir = turn(action, get_agent_dir(env))
    set_agent_dir!(env, new_dir)
    world = get_world(env)

    if world[GOAL, get_agent_pos(env)]
        set_done!(env, true)
        set_reward!(env, get_terminal_reward(env))
    else
        set_done!(env, false)
        set_reward!(env, zero(T))
    end

    return nothing
end

function (env::GridRoomsDirected{T})(action::AbstractMoveAction) where {T}
    world = get_world(env)

    agent_pos = get_agent_pos(env)
    dest = move(action, get_agent_dir(env), agent_pos)

    if !world[WALL, dest]
        world[AGENT, agent_pos] = false
        set_agent_pos!(env, dest)
        world[AGENT, dest] = true
    end

    if world[GOAL, get_agent_pos(env)]
        set_done!(env, true)
        set_reward!(env, get_terminal_reward(env))
    else
        set_done!(env, false)
        set_reward!(env, zero(T))
    end

    return nothing
end

#####
# Undirected
#####

function GridRoomsUndirected(; T = Float32, grid_size = (2, 2), room_size = (5, 5), rng = Random.GLOBAL_RNG)
    grid_height = grid_size[1]
    grid_width = grid_size[2]
    room_height = room_size[1]
    room_width = room_size[2]

    height = room_height * grid_height - grid_height + 1
    width = room_width * grid_width - grid_width + 1
    objects = (AGENT, WALL, GOAL)
    world = GridWorldBase(objects, height, width)

    origins = get_room_origins(grid_size, room_size)

    for origin in origins
        room = Room(origin, room_height, room_width)
        place_room!(world, room)

        world[WALL, room.region[(end + 1) ÷ 2, 1]] = false
        world[WALL, room.region[(end + 1) ÷ 2, end]] = false
        world[WALL, room.region[1, (end + 1) ÷ 2]] = false
        world[WALL, room.region[end, (end + 1) ÷ 2]] = false
    end

    world[WALL, [1, height], :] .= true
    world[WALL, :, [1, width]] .= true

    agent_pos = CartesianIndex(2, 2)
    reward = zero(T)
    terminal_reward = one(T)
    goal_pos = CartesianIndex(height - 1, width - 1)
    done = false

    env = GridRoomsUndirected(world, agent_pos, reward, rng, terminal_reward, goal_pos, done)

    RLBase.reset!(env)

    return env
end

RLBase.state_space(env::GridRoomsUndirected, ::RLBase.Observation, ::RLBase.DefaultPlayer) = nothing
const GRID_ROOMS_UNDIRECTED_LAYERS = SA.SVector(2, 3)
RLBase.state(env::GridRoomsUndirected, ::RLBase.Observation, ::RLBase.DefaultPlayer) = get_grid(get_world(env), get_agent_pos(env), get_half_size(env), GRID_ROOMS_UNDIRECTED_LAYERS)

RLBase.state_space(env::GridRoomsUndirected, ::RLBase.InternalState, ::RLBase.DefaultPlayer) = nothing
RLBase.state(env::GridRoomsUndirected, ::RLBase.InternalState, ::RLBase.DefaultPlayer) = copy(get_grid(env))

RLBase.action_space(env::GridRoomsUndirected, player::RLBase.DefaultPlayer) = UNDIRECTED_NAVIGATION_ACTIONS
RLBase.reward(env::GridRoomsUndirected, ::RLBase.DefaultPlayer) = get_reward(env)
RLBase.is_terminated(env::GridRoomsUndirected) = get_done(env)

function RLBase.reset!(env::GridRoomsUndirected{T}) where {T}
    world = get_world(env)
    rng = get_rng(env)

    world[AGENT, get_agent_pos(env)] = false
    world[GOAL, get_goal_pos(env)] = false

    new_goal_pos = rand(rng, pos -> !any(@view world[:, pos]), env)
    set_goal_pos!(env, new_goal_pos)
    world[GOAL, new_goal_pos] = true

    new_agent_pos = rand(rng, pos -> !any(@view world[:, pos]), env)
    set_agent_pos!(env, new_agent_pos)
    world[AGENT, new_agent_pos] = true

    set_reward!(env, zero(T))
    set_done!(env, false)

    return nothing
end

function (env::GridRoomsUndirected{T})(action::AbstractMoveAction) where {T}
    world = get_world(env)

    agent_pos = get_agent_pos(env)
    dest = move(action, agent_pos)

    if !world[WALL, dest]
        world[AGENT, agent_pos] = false
        set_agent_pos!(env, dest)
        world[AGENT, dest] = true
    end

    if world[GOAL, get_agent_pos(env)]
        set_done!(env, true)
        set_reward!(env, get_terminal_reward(env))
    else
        set_done!(env, false)
        set_reward!(env, zero(T))
    end

    return nothing
end

#####
# Common
#####

function get_room_origins(grid_size, room_size)
    grid_height = grid_size[1]
    grid_width = grid_size[2]
    room_height = room_size[1]
    room_width = room_size[2]

    vertical_steps = 1 : room_height - 1 : 1 + (grid_height - 1) * (room_height - 1)
    horizontal_steps = 1 : room_width - 1 : 1 + (grid_width - 1) * (room_width - 1)

    return [CartesianIndex(i, j) for i in vertical_steps for j in horizontal_steps]
end


using Combinatorics
export importTechFile
export writedotfile
export generateCombinations

mutable struct MasterTech
    name::String
    inputs::Array
    outputs::Array
    functional_group::String
    appscore::Float64
    inrel::String
    outrel::String
    transC
    transC_reliability
    MasterTech() = new()
end

"""
This function reads a .csv file with technology and relationships
and returns a tuple of an array with sources and an array of all technologies
"""
function importTechFile(techFile::String; sourceGroup::String="U",
                        sourceAddGroup::String="Uadd", sinkGroup::String="D")

    techTable = readdlm(techFile, ';')

    ## Load the table into suitable Data Structure
    techList = MasterTech[]
    for i in 2:size(techTable,2)

        ## Copy some information
        currentTech = MasterTech()
        currentTech.name = techTable[1,i]
        currentTech.functional_group = techTable[2,i]
        currentTech.appscore = parse(Float64, string(techTable[6,i]))
        currentTech.inputs = Array{String, 1}(0)
        currentTech.outputs = Array{String, 1}(0)
        currentTech.inrel = techTable[4,i]
        currentTech.outrel = techTable[5,i]

        ## Separate Inputs and outputs.
        tmp = split(techTable[3,i], " ")
        filter!(x -> x != "", tmp)
        j = 1
        while (j <= length(tmp)) && (tmp[j] != "->")
            tmp[j] = replace(tmp[j], ",", "")
            push!(currentTech.inputs, tmp[j])
            j += 1
        end
        j += 1
        while j <= length(tmp)
            tmp[j] = replace(tmp[j], ",", "")
            push!(currentTech.outputs, tmp[j])
            j += 1
        end

        if length(intersect(currentTech.inputs, currentTech.outputs)) > 0
            error("Same input and output product(s) for Tech $(techTable[1,i]) (column $i)!
  Product(s): $(intersect(currentTech.inputs, currentTech.outputs))")
        end

        ## read transfer coefficients

        function make_transC_substance_dict(s::String)
            d = Dict{Product, Float64}()
            tmp = [split(replace(x, " ", ""), '=') for x in split(s, ',')]
            for t in tmp
                d[Product(t[1])] = parse(Float64, t[2])
            end
            return d
        end

        transC = Dict{String, Dict{Product, Float64}}()
        transC_rel = Dict{String, Float64}()
        for k in 1:NSUBSTANCE
            transC[SUBSTANCE_NAMES[k]] = make_transC_substance_dict(String(techTable[5+2*k,i]))
            transC_rel[SUBSTANCE_NAMES[k]] = convert(Float64, techTable[6+2*k,i])
        end
        currentTech.transC = transC
        currentTech.transC_reliability = transC_rel

        for sub in SUBSTANCE_NAMES
            if '>' in currentTech.outrel   # is transport technolgy
                needed_keys = Set([Product("x"), Product("airloss"), Product("soilloss"), Product("waterloss")])
            else
                if length(currentTech.outputs) > 0 # is not sink
                    out_tmp = [replace(p, "transported", "") for p in currentTech.outputs]
                    needed_keys = Set(vcat(Product.(out_tmp), [Product("airloss"), Product("soilloss"), Product("waterloss")]))
                else                # is sink
                    needed_keys = Set([Product("recovered"), Product("airloss"), Product("soilloss"), Product("waterloss")])
                end
            end
            if needed_keys != Set(keys(currentTech.transC[sub]))
                diff_element = setdiff(needed_keys, Set(keys(currentTech.transC[sub])))
                error("Transfere coefficients for Tech $(techTable[1,i]) (column $i) do not match for $sub.
  No match for: $([String(e.name) for e in diff_element])")
            end
        end

        ## Add to Array
        push!(techList, currentTech)

    end

    ## help function to generate two Techs with only "transported" or "not transported" output products
    function make2(t::Tech)

        if t.functional_group != Symbol(sinkGroup)      # of t is not a sink
            outs = t.outputs
            out_transported = filter(p -> contains(string(p.name), "transported"), outs)
            out_NOTtransported = filter(p -> !contains(string(p.name), "transported"), outs)

            ## produce array of two Techs
            techs = Tech[Tech(t.inputs,
                              out_NOTtransported,
                              t.name,
                              t.functional_group,
                              t.appscore,
                              t.n_inputs,
                              t.transC,
                              t.transC_reliability),
                         Tech(t.inputs,
                              out_transported,
                              t.name*"_trans",
                              t.functional_group,
                              t.appscore,
                              t.n_inputs,
                              t.transC,
                              t.transC_reliability)]

            return filter!(t -> length(t.outputs)>0, techs)
        else                    # if sink just return it as is
            [t]
        end
    end

    ## Now generate subtechs
    subTechList = AbstractTech[]
    for currentTech in techList
        ## Case 1: Copy | Copy
        if (currentTech.inrel == "NA" || currentTech.inrel == "AND") && (currentTech.outrel == "NA" || currentTech.outrel == "AND")

            newSubTech = Tech(currentTech.inputs,
                              currentTech.outputs,
                              currentTech.name,
                              currentTech.functional_group,
                              currentTech.appscore,
                              currentTech.transC,
                              currentTech.transC_reliability)

            append!(subTechList, make2(newSubTech))

            ## Case 2: Copy | GenVar
        elseif (currentTech.inrel == "NA" || currentTech.inrel == "AND") && (currentTech.outrel == "OR")
            error(" The relationship of output products cannot be defined as 'OR'!")

            ## Case 3: Copy | Gen1
        elseif (currentTech.inrel == "NA" || currentTech.inrel == "AND") && (currentTech.outrel == "XOR")
            error(" The relationship of output products cannot be defined as 'XOR'!")

            ## Case 4: GenVar | Hierarchy
        elseif currentTech.inrel == "OR" && (currentTech.outrel != "NA" && currentTech.outrel != "AND")

            ## Prepare (get the hierarchy)
            hira = split(currentTech.outrel, " > ")
            if length(hira) != length(currentTech.inputs)
                error("Case 4: Lenght of Hierarchy is expected to be the same as number of Input Products")
            end
            c = 1

            ## This bit gets the possible combinations of in-products
            for i in 1:length(currentTech.inputs)
                possibleCombis = collect(combinations(currentTech.inputs, i))
                for subTechIn in possibleCombis
                    ## This bit checks for the most dominant in-product to determine output
                    for candidateOut in hira
                        if in(candidateOut, subTechIn)

                            ## This bit generates the SubTech
                            subTechName = join([currentTech.name, c], "_")
                            subTechOut = join(["transported", candidateOut], "")
                            newSubTech = Tech(subTechIn,
                                              [subTechOut],
                                              subTechName,
                                              currentTech.functional_group,
                                              currentTech.appscore,
                                              currentTech.transC,
                                              currentTech.transC_reliability)
                            push!(subTechList, newSubTech)

                            c += 1
                            break
                        end
                    end
                end
            end

            ## Case 5: Gen1 | Select
        elseif currentTech.inrel == "XOR" && (currentTech.outrel != "NA" && currentTech.outrel != "AND")
            for i in 1:length(currentTech.inputs)
                subTechName = join([currentTech.name, i], "_")
                subTechIn = currentTech.inputs[i]
                subTechOut = join(["transported", subTechIn], "")
                newSubTech = Tech([subTechIn],
                                  [subTechOut],
                                  subTechName,
                                  currentTech.functional_group,
                                  currentTech.appscore,
                                  currentTech.transC,
                                  currentTech.transC_reliability)
                push!(subTechList, newSubTech)
            end

            ## Case 6: GenVar | Copy
        elseif currentTech.inrel == "OR" && (currentTech.outrel == "AND" || currentTech.outrel == "NA")
            c = 1
            for i in 1:length(currentTech.inputs)
                possibleCombis = collect(combinations(currentTech.inputs, i))
                for subTechIn in possibleCombis
                    subTechName = join([currentTech.name, c], "_")
                    newSubTech = Tech(subTechIn,
                                      currentTech.outputs,
                                      subTechName,
                                      currentTech.functional_group,
                                      currentTech.appscore,
                                      currentTech.transC,
                                      currentTech.transC_reliability)
                    append!(subTechList, make2(newSubTech))
                    c += 1
                end
            end

            ## Case 7: Gen1 | Copy
        elseif currentTech.inrel == "XOR" && (currentTech.outrel == "AND" || currentTech.outrel == "NA")
            for i in 1:length(currentTech.inputs)
                subTechIn = currentTech.inputs[i]
                subTechName = join([currentTech.name, i], "_")
                newSubTech = Tech([subTechIn],
                                  currentTech.outputs,
                                  subTechName,
                                  currentTech.functional_group,
                                  currentTech.appscore,
                                  currentTech.transC,
                                  currentTech.transC_reliability)
                append!(subTechList, make2(newSubTech))
            end

## Case 8: Other syntax
else
print(currentTech.inrel)
print(currentTech.outrel)
error("Case 8: Unrecognized Syntax Inrel.")
end
end

## Filter: for all Techs that are not in c_group:
##         either ALL inputs and outputs are "transported" or ALL inputs and outputs are "NOT transported".
function islegal(t::Tech)
    inp  = [string(s.name) for s in t.inputs]
    outp = [string(s.name) for s in t.outputs]

    inp_trans  = [contains(i, "transported") for i in inp]
    outp_trans = [contains(i, "transported") for i in outp]

    ##        is transport              |         everything is transported  | nothing is transported
    (t.functional_group == Symbol("C")) | (all(inp_trans) & all(outp_trans)) | (all(.!(inp_trans)) & all(.!(outp_trans)))
end

subTechFiltered = filter(islegal, subTechList)

# Test if all technologies in the input file exist at least in one form in the final tech lists
for t in techList
    if !any(contains(x.name, t.name) for x in subTechFiltered)
        error("Tech '$(t.name)' is not imported! Check csv file carefully!")
    end
end

## separate sources, sourcesAdd, and Technologies
sources = filter(t -> t.functional_group == Symbol(sourceGroup), subTechFiltered)
sourcesAdd = filter(t -> t.functional_group == Symbol(sourceAddGroup), subTechFiltered)
techs = filter(t -> (t.functional_group != Symbol(sourceGroup) && t.functional_group != Symbol(sourceAddGroup)), subTechFiltered)

return sources, sourcesAdd, techs

end

"""
     This function generates possible combinations of one source and all sourcesAdds
     returns all possible combinations.
    """
function generateCombinations{T <: AbstractTech}(source::T, sourcesAdd::Array{T})

    src_comb = Array{Tech}[]
    push!(src_comb, [source])

    for s_pos in Combinatorics.combinations(sourcesAdd)
        push!(s_pos, source)
        push!(src_comb, s_pos)
    end

    return src_comb
end

## ---------------------------------
## write dot file for visualisation with graphviz

"""
 Writes a DOT file of a `System`. The resulting file can be visualized with GraphViz, e,g.:
 ```
 dot -Tpng file.dot -o graph.png
 ```
 ## Arguments
 nogroup    Array of functional groups which should not be grouped in the plot

 """
function writedotfile(sys::System, file::String, no_group::Array{String}=["S", "C", "T"], options::String="")
    make_legal(name::String) = replace(name, " :: ", "")

    open(file, "w") do f
        println(f, "digraph system {")
        println(f, "rankdir=LR;")
        println(f, "node[style=filled colorscheme=pastel15];") # accent5
        if options!=""
            println(f, "$(options);")
        end

        if haskey(sys.properties, "ID")
            println(f, "label=\"ID: $(sys.properties["ID"])\";")
            println(f, "labelfontsize=22.0;")
            println(f, "labelloc=\"top\";")
            println(f, "labeljust=left")
        end

        ## define colors for function groups
        fgroups = sort(unique(t.functional_group for t in sys.techs))
        colors = Dict(fgroups[i] => mod(i,5)+1 for i in 1:length(fgroups))

        colors = Dict(:U => "# F15A31", :S => "# F99D34", :C => "# C1C430", :T => "# 70BF54", :D => "# 00B6CD")

        ## define nodes
        for t in vcat(sys.techs...)
            label = "$(t.name)\n"
            label = label * "($(t.functional_group))"
            println(f, replace("$(make_legal(t.name)) [shape=box, fillcolor=\"$(get(colors, t.functional_group, "# 999999"))\" label=\"$label\"];", ".", "_"))
        end
        ## edges
        for c in sys.connections
            println(f, replace("$(make_legal(c[2].name)) -> $(make_legal(c[3].name)) [label=\"$(c[1].name)\"];", ".", "_"))
        end

        no_group = [Symbol(x) for x in no_group]
        ## group according to functional groups
        for fg in filter(x -> !(x in no_group), fgroups)
            names = [t.name for t in sys.techs if t.functional_group==fg]
            names = map(n -> make_legal(replace(n, ".", "_")), names)
            println(f, "{ rank=same $(join(names, ' ')) }")
        end

        println(f, "}")
    end
end


# Automatically generated using Clang.jl

const CONEMODE = 0
const INSIDE_CONE = 0
const OUTSIDE_CONE = 1
const idxint = @static Sys.iswindows() ? Int64 : Clong
const pfloat = Cdouble

struct lpcone
    p::idxint
    w::Ptr{pfloat}
    v::Ptr{pfloat}
    kkt_idx::Ptr{idxint}
end

struct socone
    p::idxint
    skbar::Ptr{pfloat}
    zkbar::Ptr{pfloat}
    a::pfloat
    d1::pfloat
    w::pfloat
    eta::pfloat
    eta_square::pfloat
    q::Ptr{pfloat}
    Didx::Ptr{idxint}
    u0::pfloat
    u1::pfloat
    v1::pfloat
end

struct expcone
    colstart::NTuple{3,idxint}
    v::NTuple{6,pfloat}
    g::NTuple{3,pfloat}
end

struct cone
    lpc::Ptr{lpcone}
    soc::Ptr{socone}
    nsoc::idxint
    expc::Ptr{expcone}
    nexc::idxint
    fexv::idxint
end

# Skipping MacroDefinition: init_ctrlc ( )
# Skipping MacroDefinition: remove_ctrlc ( )
# Skipping MacroDefinition: check_ctrlc ( ) 0

const ECOS_VERSION = "2.0.8"
const MAXIT = 100
const FEASTOL = 1.0e-8
const ABSTOL = 1.0e-8
const RELTOL = 1.0e-8
const FTOL_INACC = 0.0001
const ATOL_INACC = 5.0e-5
const RTOL_INACC = 5.0e-5
const GAMMA = 0.99
const STATICREG = 1
const DELTASTAT = 7.0e-8
const DELTA = 2.0e-7
const EPS = 1.0e-13
const VERBOSE = 1
const NITREF = 9
const IRERRFACT = 6
const LINSYSACC = 1.0e-14
const SIGMAMIN = 0.0001
const SIGMAMAX = 1.0
const STEPMIN = 1.0e-6
const STEPMAX = 0.999
const SAFEGUARD = 500
const MAX_BK = 90
const BK_SCALE = 0.8
const MIN_DISTANCE = 0.1
const CENTRALITY = 1
const EQUILIBRATE = 1
const EQUIL_ITERS = 3
const ECOS_OPTIMAL = 0
const ECOS_PINF = 1
const ECOS_DINF = 2
const ECOS_INACC_OFFSET = 10
const ECOS_MAXIT = -1
const ECOS_NUMERICS = -2
const ECOS_OUTCONE = -3
const ECOS_SIGINT = -4
const ECOS_FATAL = -7

# Skipping MacroDefinition: MAX ( X , Y ) ( ( X ) < ( Y ) ? ( Y ) : ( X ) )
# Skipping MacroDefinition: SAFEDIV_POS ( X , Y ) ( ( Y ) < EPS ? ( ( X ) / EPS ) : ( X ) / ( Y ) )

struct settings
    gamma::pfloat
    delta::pfloat
    eps::pfloat
    feastol::pfloat
    abstol::pfloat
    reltol::pfloat
    feastol_inacc::pfloat
    abstol_inacc::pfloat
    reltol_inacc::pfloat
    nitref::idxint
    maxit::idxint
    verbose::idxint
    max_bk_iter::idxint
    bk_scale::pfloat
    centrality::pfloat
end

struct stats
    pcost::pfloat
    dcost::pfloat
    pres::pfloat
    dres::pfloat
    pinf::pfloat
    dinf::pfloat
    pinfres::pfloat
    dinfres::pfloat
    gap::pfloat
    relgap::pfloat
    sigma::pfloat
    mu::pfloat
    step::pfloat
    step_aff::pfloat
    kapovert::pfloat
    iter::idxint
    nitref1::idxint
    nitref2::idxint
    nitref3::idxint
    tsetup::pfloat
    tsolve::pfloat
    pob::idxint
    cb::idxint
    cob::idxint
    pb::idxint
    db::idxint
    affBack::idxint
    cmbBack::idxint
    centrality::pfloat
end

struct spmat
    jc::Ptr{idxint}
    ir::Ptr{idxint}
    pr::Ptr{pfloat}
    n::idxint
    m::idxint
    nnz::idxint
end

struct kkt
    PKPt::Ptr{spmat}
    L::Ptr{spmat}
    D::Ptr{pfloat}
    work1::Ptr{pfloat}
    work2::Ptr{pfloat}
    work3::Ptr{pfloat}
    work4::Ptr{pfloat}
    work5::Ptr{pfloat}
    work6::Ptr{pfloat}
    RHS1::Ptr{pfloat}
    RHS2::Ptr{pfloat}
    dx1::Ptr{pfloat}
    dx2::Ptr{pfloat}
    dy1::Ptr{pfloat}
    dy2::Ptr{pfloat}
    dz1::Ptr{pfloat}
    dz2::Ptr{pfloat}
    P::Ptr{idxint}
    Pinv::Ptr{idxint}
    PK::Ptr{idxint}
    Parent::Ptr{idxint}
    Sign::Ptr{idxint}
    Pattern::Ptr{idxint}
    Flag::Ptr{idxint}
    Lnz::Ptr{idxint}
    delta::pfloat
end

struct pwork
    n::idxint
    m::idxint
    p::idxint
    D::idxint
    x::Ptr{pfloat}
    y::Ptr{pfloat}
    z::Ptr{pfloat}
    s::Ptr{pfloat}
    lambda::Ptr{pfloat}
    kap::pfloat
    tau::pfloat
    best_x::Ptr{pfloat}
    best_y::Ptr{pfloat}
    best_z::Ptr{pfloat}
    best_s::Ptr{pfloat}
    best_kap::pfloat
    best_tau::pfloat
    best_cx::pfloat
    best_by::pfloat
    best_hz::pfloat
    best_info::Ptr{stats}
    dsaff::Ptr{pfloat}
    dzaff::Ptr{pfloat}
    W_times_dzaff::Ptr{pfloat}
    dsaff_by_W::Ptr{pfloat}
    saff::Ptr{pfloat}
    zaff::Ptr{pfloat}
    C::Ptr{cone}
    A::Ptr{spmat}
    G::Ptr{spmat}
    c::Ptr{pfloat}
    b::Ptr{pfloat}
    h::Ptr{pfloat}
    AtoK::Ptr{idxint}
    GtoK::Ptr{idxint}
    xequil::Ptr{pfloat}
    Aequil::Ptr{pfloat}
    Gequil::Ptr{pfloat}
    resx0::pfloat
    resy0::pfloat
    resz0::pfloat
    rx::Ptr{pfloat}
    ry::Ptr{pfloat}
    rz::Ptr{pfloat}
    rt::pfloat
    hresx::pfloat
    hresy::pfloat
    hresz::pfloat
    nx::pfloat
    ny::pfloat
    nz::pfloat
    ns::pfloat
    cx::pfloat
    by::pfloat
    hz::pfloat
    sz::pfloat
    KKT::Ptr{kkt}
    info::Ptr{stats}
    stgs::Ptr{settings}
end

const MI_PRINTLEVEL = 1
const MI_ABS_EPS = 1.0e-6
const MI_REL_EPS = 0.001
const MI_MAXITER = 1000
const MI_INT_TOL = FTOL_INACC
const MI_SOLVED_NON_BRANCHABLE = 3
const MI_SOLVED_BRANCHABLE = 2
const MI_NOT_SOLVED = 1
const MI_FREE = 0
const MI_ONE = 1
const MI_ZERO = 0
const MI_STAR = -1
const MI_OPTIMAL_SOLN = ECOS_OPTIMAL
const MI_INFEASIBLE = ECOS_PINF
const MI_UNBOUNDED = ECOS_DINF
const MI_MAXITER_FEASIBLE_SOLN = ECOS_OPTIMAL + ECOS_INACC_OFFSET
const MI_MAXITER_NO_SOLN = ECOS_PINF + ECOS_INACC_OFFSET
const MI_MAXITER_UNBOUNDED = ECOS_DINF + ECOS_INACC_OFFSET
const MAX_FLOAT_INT = 8388608

@cenum BRANCHING_STRATEGY::UInt32 begin
    BRANCHING_STRATEGY_MOST_INFEASIBLE = 0
    BRANCHING_STRATEGY_STRONG_BRANCHING = 1
    BRANCHING_STRATEGY_PSEUDOCOST_BRANCHING = 2
    BRANCHING_STRATEGY_RELIABILITY = 3
    BRANCHING_STRATEGY_RANDOM = 4
end

@cenum NODE_SELECTION_METHOD::UInt32 begin
    BREADTH_FIRST = 0
    DIVE_LOWER_NODE = 1
    DIVE_UPPER_NODE = 2
end

struct settings_bb
    maxit::idxint
    verbose::idxint
    abs_tol_gap::pfloat
    rel_tol_gap::pfloat
    integer_tol::pfloat
    branching_strategy::BRANCHING_STRATEGY
    reliable_eta::idxint
    node_selection_method::NODE_SELECTION_METHOD
end

struct node
    status::UInt8
    L::pfloat
    U::pfloat
    relaxation::pfloat
    split_idx::idxint
    split_val::pfloat
    prev_split_idx::idxint
    prev_split_val::pfloat
    prev_relaxation::pfloat
    up_branch_node::Cint
end

struct ecos_bb_pwork
    num_bool_vars::idxint
    num_int_vars::idxint
    nodes::Ptr{node}
    bool_node_ids::Cstring
    int_node_ids::Ptr{pfloat}
    bool_vars_idx::Ptr{idxint}
    int_vars_idx::Ptr{idxint}
    ecos_prob::Ptr{pwork}
    A::Ptr{spmat}
    G::Ptr{spmat}
    c::Ptr{pfloat}
    b::Ptr{pfloat}
    h::Ptr{pfloat}
    x::Ptr{pfloat}
    y::Ptr{pfloat}
    z::Ptr{pfloat}
    s::Ptr{pfloat}
    kap::pfloat
    tau::pfloat
    info::Ptr{stats}
    global_U::pfloat
    global_L::pfloat
    tmp_bool_node_id::Cstring
    tmp_int_node_id::Ptr{pfloat}
    iter::idxint
    dive_node_id::idxint
    tmp_branching_bool_node_id::Cstring
    tmp_branching_int_node_id::Ptr{pfloat}
    pseudocost_bin_sum::Ptr{pfloat}
    pseudocost_int_sum::Ptr{pfloat}
    pseudocost_bin_cnt::Ptr{idxint}
    pseudocost_int_cnt::Ptr{idxint}
    Gpr_new::Ptr{pfloat}
    Gjc_new::Ptr{idxint}
    Gir_new::Ptr{idxint}
    h_new::Ptr{pfloat}
    ecos_stgs::Ptr{settings}
    stgs::Ptr{settings_bb}
    default_settings::idxint
end

const PRINTLEVEL = 2
const PROFILING = 1
const DEBUG = 0
const ECOS_INFINITY = Inf
const ECOS_NAN = NaN
# const PRINTTEXT = printf
# const MALLOC = malloc
# const FREE = free
const KKT_PROBLEM = 0
const KKT_OK = 1


module io

using HDF5

"""
Load segment size list
"""
function load_seg_list(fn)
    return readdlm(fn, ';', Int64)
end

function load_semmap(fname)
    table = readdlm(fname, ';', Int)
    Dict( table[i,1] => table[i,2] for i in 1:size(table,1) )
end

"""
Load Synaptor edge csv & parse columns appropriately
"""
function load_edges(fn)
    tbl = readdlm(fn, ';')[:,1:4]
    tbl[:,2] = [map(parse, split(tbl[i,2][2:end-1],",")) for i in 1:size(tbl,1)]
    tbl[:,3] = [map(parse, split(tbl[i,3][2:end-1],",")) for i in 1:size(tbl,1)]
    return tbl
end

"""
Load python parsed edge csv & parse columns appropriately
"""
function load_edges_from_python(fn)
    tbl = readdlm(fn, ',', Int64)
    new_tbl = Array(Any, size(tbl,1), 4)
    new_tbl[:,1] = tbl[:,1]
    new_tbl[:,2] = [[tbl[i,2:3]...] for i in 1:size(tbl,1)]
    new_tbl[:,3] = [[tbl[i,4:6]...] for i in 1:size(tbl,1)]
    new_tbl[:,4] = tbl[:,7]
    return new_tbl
end

function read_h5( fname, read_whole_dset=true, h5_dset_name="/main" )

  if read_whole_dset
    d = h5read( fname, h5_dset_name );
  else
    f = h5open( fname );
    d = f[h5_dset_name];
  end

  d
end

function write_map_file( output_fname, dicts... )

  open(output_fname, "w+") do f
    if length(dicts) > 0
    for k in keys(dicts[1])

      vals = ["$(d[k]);" for d in dicts ];
      write(f, "$k;$(vals...)\n")

    end #for
    end #if
  end#open

end

"""
Filter edges & save in easy python format with flat arrays

Inputs:
  fn: filename to write to
  edges: mixed array (list of lists)
  filter_ids: list of segment ids to include

Output:
  file with all lists expanded

"""
function python_write_edges(fn, edges, filter_ids)
  e = []
  for i in 1:size(edges,1)
    if edges[i,2][1] in filter_ids && edges[i,2][2] in filter_ids
      push!(e, [edges[i,1], edges[i,2]..., edges[i,3]..., edges[i,4]])
    end
  end
  writedlm(fn, hcat(e...)', ",")
end

end #module end


wider_js_type(::Type{<:Integer}) = Float64  # Javascript thinks everything is a `double`
wider_js_type(::Type{Float64}) = Float64
wider_js_type(::Type{Bool}) = Bool
wider_js_type(x) = x

const DEFAULT_PROPERTY_TYPES = Dict{String, String}(
    "scale" => "vector3",
    "position" => "vector3",
    "quaternion" => "quaternion",
    "visible" => "bool",
)

function get_property_type(property_name::AbstractString)
    get(DEFAULT_PROPERTY_TYPES, property_name, "number")
end

function _setprop!(clip::AnimationClip, frame::Integer, prop::AbstractString, jstype::AbstractString, value)
    T = wider_js_type(typeof(value))
    track = get!(clip.tracks, prop) do
        AnimationTrack{T}(prop, jstype)
    end
    insert!(track, frame, value)
    return nothing
end

function getclip!(animation::Animation, path::Path)
    get!(animation.clips, path) do
        AnimationClip(fps=animation.default_framerate)
    end
end

js_quaternion(m::AbstractMatrix) = js_quaternion(RotMatrix(SMatrix{3, 3, eltype(m)}(m)))
js_quaternion(q::UnitQuaternion) = [q.x, q.y, q.z, q.w]
js_quaternion(::UniformScaling) = js_quaternion(UnitQuaternion(1., 0., 0., 0.))
js_quaternion(r::Rotation) = js_quaternion(UnitQuaternion(r))
js_quaternion(tform::Transformation) = js_quaternion(transform_deriv(tform, SVector(0., 0, 0)))

function js_scaling(tform::AbstractAffineMap)
    m = transform_deriv(tform, SVector(0., 0, 0))
    SVector(norm(SVector(m[1, 1], m[2, 1], m[3, 1])),
            norm(SVector(m[1, 2], m[2, 2], m[3, 2])),
            norm(SVector(m[1, 3], m[2, 3], m[3, 3])))
end

js_position(t::Transformation) = convert(Vector, t(SVector(0., 0, 0)))

Cassette.@context AnimationCtx

function Cassette.overdub(ctx::AnimationCtx, ::typeof(settransform!), vis::Visualizer, tform::Transformation)
    animation, frame = ctx.metadata
    clip = getclip!(animation, vis.path)
    _setprop!(clip, frame, "scale", "vector3", js_scaling(tform))
    _setprop!(clip, frame, "position", "vector3", js_position(tform))
    _setprop!(clip, frame, "quaternion", "quaternion", js_quaternion(tform))
end

function Cassette.overdub(ctx::AnimationCtx, ::typeof(setprop!), vis::Visualizer, prop::AbstractString, value)
    animation, frame = ctx.metadata
    clip = getclip!(animation, vis.path)
    _setprop!(clip, frame, prop, get_property_type(prop), value)
end

function Cassette.overdub(ctx::AnimationCtx, ::typeof(setprop!), vis::Visualizer, prop::AbstractString, jstype::AbstractString, value)
    animation, frame = ctx.metadata
    clip = getclip!(animation, vis.path)
    _setprop!(clip, frame, prop, jstype, value)
end

"""
Call the given function `f`, but intercept any `settransform!` or `setprop!` calls
and apply them to the given animation at the given frame instead.

$(TYPEDSIGNATURES)

Usage:

```
vis = Visualizer()
setobject!(vis[:cube], Rect(Vec(0.0, 0.0, 0.0), Vec(0.5, 0.5, 0.5)))

anim = Animation()

# At frame 0, set the cube's position to be the origin
atframe(anim, 0) do
    settransform!(vis[:cube], Translation(0.0, 0.0, 0.0))
end

# At frame 30, set the cube's position to be [1, 0, 0]
atframe(anim, 30) do
    settransform!(vis[:cube], Translation(1.0, 0.0, 0.0))
end

setanimation!(vis, anim)
```
"""
function atframe(f, animation::Animation, frame::Integer)
    Cassette.overdub(AnimationCtx(metadata=(animation, frame)), f)
    return animation
end