#! format: off
# this file is automatically generated by SnoopCompile

function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    let fbody = try Base.bodyfunction(which(fit!, (LinearMixedModel,))) catch missing end
    if !ismissing(fbody)
        precompile(fbody, (Bool,Bool,Nothing,Int64,typeof(fit!),LinearMixedModel,))
    end
end   # time: 6.582904
    Base.precompile(Tuple{typeof(fit!),GeneralizedLinearMixedModel})   # time: 6.0995073
    Base.precompile(Tuple{Type{LinearMixedModel},AbstractArray,Tuple,FormulaTerm,Vector{Any},Nothing})   # time: 2.2513552
    let fbody = try Base.bodyfunction(which(fit!, (LinearMixedModel{Float64},))) catch missing end
    if !ismissing(fbody)
        precompile(fbody, (Bool,Bool,Nothing,Int64,typeof(fit!),LinearMixedModel{Float64},))
    end
end   # time: 2.1690955
    isdefined(MixedModels, Symbol("#22#23")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#22#23"))})   # time: 0.70335937
    Base.precompile(Tuple{Type{LinearMixedModel},Vector{Float64},Tuple{Matrix{Float64}, ReMat{Float64, 2}},FormulaTerm{ContinuousTerm{Float64}, Tuple{MatrixTerm{Tuple{InterceptTerm{true}, ContinuousTerm{Float64}}}, RandomEffectsTerm}},Vector{Any},Nothing})   # time: 0.6737979
    Base.precompile(Tuple{typeof(deviance!),GeneralizedLinearMixedModel,Int64})   # time: 0.6433022
    Base.precompile(Tuple{Type{LinearMixedModel},Vector{Float64},FeTerm{Float64, Matrix{Float64}},Vector{AbstractReMat{Float64}},FormulaTerm{ContinuousTerm{Float64}, Tuple{MatrixTerm{Tuple{InterceptTerm{true}, ContinuousTerm{Float64}}}, RandomEffectsTerm}},Vector{Any},Nothing})   # time: 0.42538697
    Base.precompile(Tuple{typeof(deviance!),GeneralizedLinearMixedModel{Float64, Bernoulli{Float64}},Int64})   # time: 0.3716886
    Base.precompile(Tuple{typeof(fit!),GeneralizedLinearMixedModel{Float64, Bernoulli{Float64}}})   # time: 0.1559177
    Base.precompile(Tuple{typeof(adjA),Vector{Int32},Matrix{Float64}})   # time: 0.11642844
    Base.precompile(Tuple{typeof(apply_schema),Term,MultiSchema{FullRank},UnionAll})   # time: 0.0992202
    Base.precompile(Tuple{typeof(fit),Type{GeneralizedLinearMixedModel},FormulaTerm,NamedTuple,Bernoulli,LogitLink})   # time: 0.09839547
    Base.precompile(Tuple{typeof(scaleinflate!),UniformBlockDiagonal{Float64},ReMat{Float64, 2}})   # time: 0.07856259
    Base.precompile(Tuple{typeof(*),Adjoint{Float64, ReMat{Float64, 2}},ReMat{Float64, 2}})   # time: 0.064247385
    let fbody = try Base.bodyfunction(which(fit, (Type{LinearMixedModel},FormulaTerm,NamedTuple,))) catch missing end
    if !ismissing(fbody)
        precompile(fbody, (Any,Any,Bool,Bool,Any,Any,typeof(fit),Type{LinearMixedModel},FormulaTerm,NamedTuple,))
    end
end   # time: 0.0641711
    Base.precompile(Tuple{typeof(apply_schema),Tuple{RandomEffectsTerm, RandomEffectsTerm},MultiSchema{FullRank},Type{LinearMixedModel}})   # time: 0.062291622
    Base.precompile(Tuple{typeof(apply_schema),RandomEffectsTerm,MultiSchema{FullRank},Type{<:MixedModel}})   # time: 0.05415768
    Base.precompile(Tuple{typeof(rmulΛ!),Matrix{Float64},ReMat{Float64, 2}})   # time: 0.03744466
    Base.precompile(Tuple{typeof(rdiv!),Matrix{Float64},UpperTriangular{Float64, Adjoint{Float64, UniformBlockDiagonal{Float64}}}})   # time: 0.037409946
    Base.precompile(Tuple{typeof(LD),UniformBlockDiagonal{Float64}})   # time: 0.029826526
    Base.precompile(Tuple{typeof(*),Adjoint{Float64, ReMat{Float64, 1}},ReMat{Float64, 1}})   # time: 0.02977296
    Base.precompile(Tuple{typeof(GLM.wrkresp!),SubArray{Float64, 1, Matrix{Float64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true},GLM.GlmResp{Vector{Float64}, Bernoulli{Float64}, LogitLink}})   # time: 0.022571307
    Base.precompile(Tuple{typeof(scaleinflate!),Diagonal{Float64, Vector{Float64}},ReMat{Float64, 1}})   # time: 0.014116756
    Base.precompile(Tuple{typeof(cholUnblocked!),UniformBlockDiagonal{Float64},Type{Val{:L}}})   # time: 0.011265205
    Base.precompile(Tuple{Core.Type{MixedModels.GeneralizedLinearMixedModel{Float64, Distributions.Bernoulli}},LinearMixedModel,Any,Any,Vector,Any,Any,Any,Any,Any,Any,Any,Any,Any,Any})   # time: 0.010291444
    Base.precompile(Tuple{typeof(apply_schema),RandomEffectsTerm,MultiSchema{FullRank},Type{LinearMixedModel}})   # time: 0.009160354
    Base.precompile(Tuple{Type{FeTerm},Matrix{Float64},Any})   # time: 0.008526229
    Base.precompile(Tuple{typeof(apply_schema),Term,MultiSchema{FullRank},Type})   # time: 0.008067499
    Base.precompile(Tuple{typeof(apply_schema),Tuple{RandomEffectsTerm, RandomEffectsTerm},MultiSchema,Type})   # time: 0.006452216
    Base.precompile(Tuple{typeof(*),Adjoint{Float64, FeMat{Float64, Matrix{Float64}}},ReMat{Float64, 2}})   # time: 0.005299216
    Base.precompile(Tuple{typeof(reweight!),ReMat{Float64, 1},Vector{Float64}})   # time: 0.00500359
    Base.precompile(Tuple{typeof(reweight!),ReMat{Float64, 2},Vector{Float64}})   # time: 0.004945764
    Base.precompile(Tuple{typeof(_pushALblock!),Vector{AbstractMatrix{Float64}},Vector{AbstractMatrix{Float64}},Diagonal{Float64, Vector{Float64}}})   # time: 0.004846841
    Base.precompile(Tuple{typeof(apply_schema),Tuple{RandomEffectsTerm, RandomEffectsTerm},MultiSchema{FullRank},Type{<:MixedModel}})   # time: 0.004639013
    Base.precompile(Tuple{typeof(_pushALblock!),Vector{AbstractMatrix{Float64}},Vector{AbstractMatrix{Float64}},UniformBlockDiagonal{Float64}})   # time: 0.004212355
    isdefined(MixedModels, Symbol("#obj#92")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#obj#92")),Vector{Float64},Vector{Float64}})   # time: 0.00415707
    Base.precompile(Tuple{typeof(reweight!),LinearMixedModel{Float64},Vector{Float64}})   # time: 0.003799728
    Base.precompile(Tuple{typeof(copyto!),UniformBlockDiagonal{Float64},UniformBlockDiagonal{Float64}})   # time: 0.003437127
    Base.precompile(Tuple{typeof(cholUnblocked!),Matrix{Float64},Type{Val{:L}}})   # time: 0.002697967
    isdefined(MixedModels, Symbol("#97#99")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#97#99")),ReMat{Float64, 1}})   # time: 0.002630397
    Base.precompile(Tuple{typeof(apply_schema),ConstantTerm{Int64},MultiSchema{FullRank},UnionAll})   # time: 0.002451962
    Base.precompile(Tuple{typeof(apply_schema),ConstantTerm{Int64},MultiSchema{FullRank},Type})   # time: 0.002029733
    Base.precompile(Tuple{typeof(rmulΛ!),Matrix{Float64},ReMat{Float64, 1}})   # time: 0.00186467
    Base.precompile(Tuple{typeof(getproperty),LinearMixedModel,Symbol})   # time: 0.001859925
    isdefined(MixedModels, Symbol("#59#61")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#59#61")),ReMat{Float64, 1}})   # time: 0.00181014
    isdefined(MixedModels, Symbol("#58#60")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#58#60")),ReMat{Float64, 2}})   # time: 0.001746027
    Base.precompile(Tuple{typeof(mul!),Vector{Float64},ReMat{Float64, 1},Vector{Float64},Float64,Float64})   # time: 0.001642233
    Base.precompile(Tuple{typeof(cholUnblocked!),Diagonal{Float64, Vector{Float64}},Type{Val{:L}}})   # time: 0.001591365
    isdefined(MixedModels, Symbol("#59#61")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#59#61")),ReMat{Float64, 2}})   # time: 0.001566984
    Base.precompile(Tuple{Type{RandomEffectsTerm},Any,Any})   # time: 0.001504504
    isdefined(MixedModels, Symbol("#58#60")) && Base.precompile(Tuple{getfield(MixedModels, Symbol("#58#60")),ReMat{Float64, 1}})   # time: 0.001407624
    Base.precompile(Tuple{typeof(*),Adjoint{Float64, FeMat{Float64, Matrix{Float64}}},ReMat{Float64, 1}})   # time: 0.001186352
    Base.precompile(Tuple{Type{MultiSchema},FullRank})   # time: 0.001066192
    Base.precompile(Tuple{Type{ReMat{Float64, 2}},CategoricalTerm{DummyCoding, String, 17},Vector{Int32},Vector{String},Vector{String},Matrix{Float64},Matrix{Float64},LowerTriangular{Float64, Matrix{Float64}},Vector{Int64},SparseMatrixCSC{Float64, Int32},Matrix{Float64}})   # time: 0.001038076
end


using WatchJuliaBurn
using Test
using LinearAlgebra

@testset "WatchJuliaBurn.jl" begin
    ## Base
    @test_throws ArgumentError c╯°□°ↄ╯(ArgumentError("Great Success"))
    @test 🗺(sin, 1:5) == sin.(1:5)
    @test 📖(:a => 2.0) == Dict(:a => 2.0)
    @test ✅
    @test 👍
    @test 👌
    @test !👎
    @test 🥩"Amazing" == raw"Amazing"
    @test 🔥(📖(:a => 2.0, :b => 3.0), :a) == 📖(:b => 3.0)
    @test_nowarn 🖨("Yes!")
    @test 📡(sin, 1:5) == sin.(1:5)
    @test ⬛ === nothing
    @test 🕵(x->x==1, [1, 2, 1]) == [1, 3]
    @test_nowarn ☝(👍)
    if VERSION >= v"1.5"
        @test ⛰(IOBuffer("Brilliant"), Char) == 'B'
    end

    ## Arrays
    @test 😻([1], [2]; dims=1) == [1, 2]
    @test ⬇😻([1], [2]) == [1, 2]
    @test ➡😻([1], [2]) == [1 2]
    @test 😹([1], [2]; dims=1) == [1, 2]
    @test ⬇😹([1], [2]) == [1, 2]
    @test ➡😹([1], [2]) == [1 2]
    @test 🐈([1], [2]; dims=1) == [1, 2]
    @test ⬇🐈([1], [2]) == [1, 2]
    @test ➡🐈([1], [2]) == [1 2]
    @test 🔢([1 0; 0 1]) == [1 0; 0 1]
    @test 🧺(1:3) == [1, 2, 3]
    if VERSION >= v"1.2"
        @eval @test_nowarn $(Symbol(Char(0x0001fa93) * Char(0x0001fa93)))(rand(3, 3)) # 🪓🪓
    end
    # @test_nowarn 🪟(rand(3, 3), 1:2, 1:2)

    ## Math
    @test 🥧 ≈ 3.1415 atol=1e-4
    @test 🍰 ≈ 3.1415 atol=1e-4
    @test 🧑🏻➡🧑🏽(2.0) == tan(2.0)
    if VERSION >= v"1.5"
        @eval @test $(Symbol(Char(0x0001fab5)))(1.0) == log(1.0) # 🪵
    end
    @test 🗜(5.0, 1.0, 2.0) == 2.0
    @test 👔💪(-2) == 2
    @test 🎽💪(-2) == 2
    @test 🛸❓(1im) == 👎
    @test 🔮(1 + 2im) == 2

    ## Monkey try/catch/finally
    include("monkeytests.jl")

end


function RangeRateIonosphericDelayModifier(arg0::IonosphericModel, arg1::jdouble, arg2::jboolean)
    return RangeRateIonosphericDelayModifier((IonosphericModel, jdouble, jboolean), arg0, arg1, arg2)
end

function get_parameters_drivers(obj::RangeRateIonosphericDelayModifier)
    return jcall(obj, "getParametersDrivers", List, ())
end

function modify(obj::RangeRateIonosphericDelayModifier, arg0::EstimatedMeasurement)
    return jcall(obj, "modify", void, (EstimatedMeasurement,), arg0)
end


using Printf

function main()
    output_dir_name="post_result"
    input_result_dir="result"

    #vtk_頭の文字
    front="# vtk DataFile Version 2.0"*"\n"
    back="\n"*"ASCII"*"\n"*"DATASET UNSTRUCTURED_GRID"*"\n"
    write_file(output_dir_name,input_result_dir,front,back)
end

function write_file(outdir,inresult_dir,front,back)

    nodes_xmax, nodes_ymax = read_nodenum(1)
    nodes = read_nodes(1)

    elements = read_elements(1)

    inf = readdir(inresult_dir)
    cellnum = (nodes_xmax-3)*(nodes_ymax-3)
    rho = zeros(cellnum)
    u = zeros(cellnum)
    v = zeros(cellnum)
    p = zeros(cellnum)
    T = zeros(cellnum)

    for i in 1:length(inf)
        if occursin(".dat", inf[i]) == true
            dname = replace(inf[i],".dat" => "")
            out_file = replace(inf[i],".dat" => ".vtk")
            print("start writing "*out_file*"\n")

            fff=[]
            open(inresult_dir*"/"*inf[i], "r") do f
                fff=read(f,String)
            end
            fff=split(fff,"\n",keepempty=false)

            for j in 2:length(fff)
                fff[j]=replace(fff[j]," \r" => "")
                temp = split(fff[j]," ")
                rho[j-1] = parse(Float64,temp[1])
                u[j-1] = parse(Float64,temp[2])
                v[j-1] = parse(Float64,temp[3])
                p[j-1] = parse(Float64,temp[4])
                T[j-1] = parse(Float64,temp[5])
            end

            write_points(nodes,out_file,outdir,dname,front,back)
            write_cells(elements,out_file,outdir)
            write_result(rho,out_file,outdir,1)
            write_result(u,out_file,outdir,2)
            write_result(v,out_file,outdir,3)
            write_result(p,out_file,outdir,4)
            write_result(T,out_file,outdir,5)
            write_result_vec(u,v,out_file,outdir)
            print("fin writing "*out_file*"\n")
        end
    end
end

function read_nodenum(skipnum)
    # xmax : 仮想セルも含めたnodeのxの数
    # ymax : 仮想セルも含めたnodeのyの数
    fff=[]
    open("grid/nodesnum", "r") do f
        fff=read(f,String)
    end
    fff=split(fff,"\n",keepempty=false)
    num_nodes=length(fff)-skipnum

    for i in 1+skipnum:length(fff)
        fff[i]=replace(fff[i]," \r" => "")
    end

    temp = split(fff[2]," ")
    xmax = parse(Int64,temp[1])
    ymax = parse(Int64,temp[2])

    return xmax, ymax
end

function read_nodes(skipnum)

    fff=[]
    open("grid/nodes_forvtk", "r") do f
        fff=read(f,String)
    end
    fff=split(fff,"\n",keepempty=false)
    num_nodes=length(fff)-skipnum

    for i in 1+skipnum:length(fff)
        fff[i]=replace(fff[i]," \r" => "")
    end

    nodes=zeros(num_nodes,3)
    for i in 1:num_nodes
        temp=split(fff[i+skipnum]," ")

        # x = parse(Float64,temp[1])
        # y = parse(Float64,temp[2])
        # z = parse(Float64,temp[3])
        x = parse(Float64,temp[2])
        y = parse(Float64,temp[3])
        z = 0.0
        nodes[i,1] = x
        nodes[i,2] = y
        nodes[i,3] = z
    end
    return nodes
end

function read_elements(skipnum)
    fff=[]
    open("grid/element_forvtk", "r") do f
        fff=read(f,String)
    end
    fff=split(fff,"\n",keepempty=false)
    num_elements=length(fff)-skipnum

    for i in 1+skipnum:length(fff)
        fff[i]=replace(fff[i]," \r" => "")
    end

    elements = zeros(num_elements,4)
    for i in 1:num_elements
        temp=split(fff[i+skipnum]," ")

        elements[i,1] = parse(Int64,temp[2])-1
        elements[i,2] = parse(Int64,temp[3])-1
        elements[i,3] = parse(Int64,temp[4])-1
        elements[i,4] = parse(Int64,temp[5])-1
    end
    return elements
end

function  write_points(nodes,out_file,outdir,dname,front,back)
    a = size(nodes)[1]
    a_st = @sprintf("%1.0f", a)

    fff = outdir*"/"*out_file
    open(fff,"w") do f
        write(f,front)
        write(f,dname)
        write(f,back)
        write(f,"POINTS "*a_st*" float\n")
        for i in 1:a
            x = @sprintf("%7.7f", nodes[i,1])
            y = @sprintf("%7.7f", nodes[i,2])
            z = @sprintf("%7.7f", nodes[i,3])
            write(f, x*" "*y*" "*z*"\n")
        end
    end
    println("fin writing points")
end

function write_cells(elements,out_file,outdir)
    a = size(elements)[1]
    b = a*5
    a_st = @sprintf("%1.0f", a)
    b_st = @sprintf("%1.0f", b)

    fff = outdir*"/"*out_file
    open(fff,"a") do f
        write(f, "CELLS "*a_st*" "*b_st*"\n")
        for i in 1:a
            d1 = @sprintf("%1.0f", elements[i,1])
            d2 = @sprintf("%1.0f", elements[i,2])
            d3 = @sprintf("%1.0f", elements[i,3])
            d4 = @sprintf("%1.0f", elements[i,4])
            write(f, "4 "*d1*" "*d2*" "*d3*" "*d4*"\n")
        end
        write(f, "CELL_TYPES "*a_st*"\n")
        for i in 1:a
            write(f, "9\n")          #四角のみ
        end
    end
    println("fin writing cells")
end

function write_result(val,out_file,outdir,k)
    dtype = ["rho","u","v","p","T"]

    a = length(val)
    a_st = @sprintf("%1.0f", a)

    temp1 = "CELL_DATA "*a_st*"\n"
    temp2 = "SCALARS "*dtype[k]*" float\nLOOKUP_TABLE default\n"

    fff = outdir*"/"*out_file
    open(fff,"a") do f
        if k == 1
            write(f, temp1)
        end
        write(f, temp2)
        for i in 1:a
            data = @sprintf("%7.7f", val[i])
            write(f, data)
            write(f, "\n")
        end
    end
end

function write_result_vec(u,v,out_file,outdir)
    a = length(u)
    a_st = @sprintf("%1.0f", a)

    temp1 = "CELL_DATA "*a_st*"\n"
    temp2 = "VECTORS velocity_vec float\n"

    fff = outdir*"/"*out_file
    open(fff,"a") do f
        #write(f, temp1)
        write(f, temp2)
        for i in 1:a
            data = @sprintf("%7.7f", u[i])*" "*@sprintf("%7.7f", v[i])*" 0.0"
            write(f, data)
            write(f, "\n")
        end
    end
end

# main
main()


__precompile__()

module RigidBodySim

include("util.jl")
include("core.jl")
include("control.jl")
include("visualization.jl")

@reexport using .Core
@reexport using .Control
@reexport using .Visualization

# Select DifferentialEquations exports
using DiffEqBase: ODEProblem, init, solve!, solve, CallbackSet
export ODEProblem, init, solve!, solve, CallbackSet

using DiffEqCallbacks: PeriodicCallback
export PeriodicCallback

using OrdinaryDiffEq: Tsit5, Vern7, RK4
export Tsit5, Vern7, RK4

end # module


abstract type AbstractVTKData end

abstract type AbstractStaticVTKData <: AbstractVTKData end
abstract type AbstractTimeSeriesVTKData{TTime, TData} <: AbstractVTKData end

abstract type AbstractVTKSimpleData <: AbstractStaticVTKData end
#abstract AbstractVTKAMRData <: AbstractStaticVTKData
abstract type AbstractVTKMultiblockData <: AbstractStaticVTKData end

abstract type AbstractVTKUnstructuredData <: AbstractVTKSimpleData end
abstract type AbstractVTKStructuredData <: AbstractVTKSimpleData end
abstract type AbstractVTKRectilinearData <: AbstractVTKStructuredData end

mutable struct VTKUnstructuredData{TCoord, TConnect, TPointData, TCellData} <: AbstractVTKUnstructuredData
    point_coords::Matrix{TCoord}
    cell_types::Vector{Int}
    cell_connectivity::Vector{TConnect}
    point_data::Dict{String, TPointData}
    cell_data::Dict{String, TCellData}
end
function VTKUnstructuredData(   point_coords,
                                cell_types,
                                cell_connectivity,
                                point_data,
                                cell_data,
                                validate
                            ) where T
    dataset = VTKUnstructuredData(  point_coords,
                                    cell_types,
                                    cell_connectivity,
                                    point_data,
                                    cell_data
                                )
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end

function VTKUnstructuredData(   point_coords,
                                cell_types = Int[],
                                cell_connectivity = Vector{Int}[],
                                validate = false
                            )

    point_data = Dict{String, Array}()
    cell_data = Dict{String, Array}()
    return VTKUnstructuredData( point_coords,
                                cell_types,
                                cell_connectivity,
                                point_data,
                                cell_data,
                                validate
                              )
end

mutable struct VTKPolyData{TCoord, TConnect, TPointData, TCellData} <: AbstractVTKUnstructuredData
    point_coords::Matrix{TCoord}
    cell_types::Vector{Int}
    cell_connectivity::Vector{TConnect}
    point_data::Dict{String, TPointData}
    cell_data::Dict{String, TCellData}
end
function VTKPolyData(   point_coords,
                        cell_types,
                        cell_connectivity,
                        point_data,
                        cell_data,
                        validate
                    )
    dataset = VTKPolyData(  point_coords,
                            cell_types,
                            cell_connectivity,
                            point_data,
                            cell_data
                        )
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end

function VTKPolyData(   point_coords,
                        cell_types = Int[],
                        cell_connectivity = Vector{Int}[],
                        validate::Bool = false
                    )

    point_data = Dict{String, Array}()
    cell_data = Dict{String, Array}()
    return VTKPolyData( point_coords,
                        cell_types,
                        cell_connectivity,
                        point_data,
                        cell_data,
                        validate
                      )
end

mutable struct VTKStructuredData{TCoord, TArray <: Array{TCoord}, TPointData, TCellData} <: AbstractVTKStructuredData
    point_coords::TArray
    point_data::Dict{String, TPointData}
    cell_data::Dict{String, TCellData}
end
function VTKStructuredData( point_coords,
                            point_data,
                            cell_data,
                            validate::Bool
                          )
    dataset = VTKStructuredData(point_coords, point_data, cell_data)
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end
function VTKStructuredData(point_coords, validate::Bool)
    point_data = Dict{String, Array}()
    cell_data = Dict{String, Array}()
    return VTKStructuredData(point_coords, point_data, cell_data, validate)
end

mutable struct VTKRectilinearData{TCoord, N, TPointData, TCellData} <: AbstractVTKRectilinearData
    point_coords::NTuple{N, Vector{TCoord}}
    point_data::Dict{String, TPointData}
    cell_data::Dict{String, TCellData}
end
function VTKRectilinearData(point_coords, point_data, cell_data, validate)
    dataset = VTKRectilinearData(point_coords, point_data, cell_data)
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end
function VTKRectilinearData(point_coords, validate::Bool = false)
    point_data = Dict{String, Array}()
    cell_data = Dict{String, Array}()
    VTKRectilinearData(point_coords, point_data, cell_data, validate)
end

mutable struct VTKUniformRectilinearData{TCoord, N, TPointData, TCellData} <: AbstractVTKRectilinearData
    origin::NTuple{N, TCoord}
    spacing::NTuple{N, TCoord}
    extents::NTuple{N, Int}
    point_data::Dict{String, TPointData}
    cell_data::Dict{String, TCellData}
end
function VTKUniformRectilinearData( origin,
                                    spacing,
                                    extents,
                                    point_data,
                                    cell_data,
                                    validate::Bool
                                  )
    dataset = VTKUniformRectilinearData(origin,
                                        spacing,
                                        extents,
                                        point_data,
                                        cell_data
                                       )
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end
function VTKUniformRectilinearData( origin,
                                    spacing,
                                    extents,
                                    validate::Bool = false,
                                  )
    point_data = Dict{String, Array}()
    cell_data = Dict{String, Array}()
    return VTKUniformRectilinearData(   origin,
                                        spacing,
                                        extents,
                                        point_data,
                                        cell_data,
                                        validate
                                    )
end
const VTKImageData = VTKUniformRectilinearData

#=
type VTKBergerOligerAMRData <: AbstractVTKAMRData
    data::VTKUniformRectilinearData
    children::Vector{VTKBergerOligerAMRData}
    children_boundary_cells::Matrix{Int}
end
=#

mutable struct VTKMultiblockData{N, TBlocks <: Tuple{Vararg{AbstractStaticVTKData, N}}} <: AbstractVTKMultiblockData
    blocks::TBlocks
end

mutable struct VTKTimeSeriesData{TTime, TData <: AbstractStaticVTKData} <: AbstractTimeSeriesVTKData{TTime, TData}
    timemarkers::Vector{TTime}
    data::Vector{TData}
end
function VTKTimeSeriesData(timemarkers, data, validate)
    dataset = VTKTimeSeriesData(timemarkers, data)
    if validate
        valid, _error = is_valid(dataset)
        valid ? (return dataset) : throw(_error)
    end
    return dataset
end


export Layout
module Layout

using CUDA
using GPUifyLoops: @unroll
using GemmKernels.Tiling
using StaticArrays

# ---------------------
# Customise computation
# ---------------------

@inline threadblock_condition(layout_a, layout_b, block_i, block_j, block_k, block_tile) = true

# ----------------------
# Explicit vectorisation
# ----------------------

struct Vec{N, T} end

@inline @generated function vloada(::Type{Vec{N, T}}, ptr::Core.LLVMPtr{T, AS}, i::Integer = 1) where {N, T, AS}
    alignment = sizeof(T) * N
    vec_len = (sizeof(T) * N) ÷ sizeof(Float32)

    return quote
        vec_ptr = Base.bitcast(Core.LLVMPtr{NTuple{$vec_len, VecElement{Float32}}, AS}, ptr)
        return unsafe_load(vec_ptr, (i-1) ÷ N + 1, Val($alignment))
    end
end

@inline @generated function vstorea!(::Type{Vec{N, T}}, ptr::Core.LLVMPtr{T, AS}, x, i::Integer = 1) where {N, T, AS}
    alignment = sizeof(T) * N
    vec_len = (sizeof(T) * N) ÷ sizeof(Float32)

    return quote
        vec_ptr = Base.bitcast(Core.LLVMPtr{NTuple{$vec_len, VecElement{Float32}}, AS}, ptr)
        return unsafe_store!(vec_ptr, x, (i-1) ÷ N + 1, Val($alignment))
    end
end

# -----------
# Layout base
# -----------

abstract type LayoutBase{T} end

@inline eltype(::Type{<:LayoutBase{T}}) where {T} = T
@inline physical_size(::Type{<:LayoutBase{T}}, logical_size::NamedTuple) where {T} = Tuple(logical_size)

# --------------
# Padded layouts
# --------------

struct Padded{L, P} end

@inline function pad_logical_coord(::Type{Padded{L, P}}, crd::NamedTuple) where {L, P}
    t = Tuple(crd)
    return typeof(crd)((Base.first(t) + P, Base.tail(t)...))
end

@inline eltype(::Type{Padded{L, P}}) where {L, P} = eltype(L)
@inline physical_size(::Type{Padded{L, P}}, logical_size::NamedTuple) where {L, P} = physical_size(L, pad_logical_coord(Padded{L, P}, logical_size))
@inline load(::Type{Padded{L, P}}, workspace, tile::Tile, logical_size::NamedTuple) where {L, P} = load(L, workspace, tile)
@inline store!(::Type{Padded{L, P}}, workspace, value, tile::Tile) where {L, P} = store!(L, workspace, value, tile::Tile)

# ---------------
# AlignedColMajor
# ---------------

struct AlignedColMajor{T} <: LayoutBase{T} end

@inline physical_size(::Type{Padded{AlignedColMajor{T}, P}}, logical_size::NamedTuple) where {T, P} = (logical_size[1] + P, logical_size[2])

@inline fragtype(::Type{AlignedColMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{4, VecElement{Float32}}

@inline function load(::Type{AlignedColMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    N = 16 ÷ sizeof(T)

    linear_base = linearise(tile.base, Base.size(workspace))
    linear_offset = linearise(tile.offset, Base.size(workspace))

    return vloada(Vec{N, T}, pointer(workspace), linear_base + linear_offset - 1)
end

@inline function store!(::Type{AlignedColMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    N = 16 ÷ sizeof(T)

    linear_base = linearise(tile.base, Base.size(workspace))
    linear_offset = linearise(tile.offset, Base.size(workspace))

    vstorea!(Vec{N, T}, pointer(workspace), value, linear_base + linear_offset - 1)
end

# --------
# Diagonal
# --------

struct Diagonal{T} <: LayoutBase{T} end

@inline bitcast_helper(x::NTuple{8, VecElement{Float16}}) = Base.llvmcall(
    "
    %ret = bitcast <8 x i16> %0 to <4 x float>
    ret <4 x float> %ret
    ", NTuple{4, VecElement{Float32}}, Tuple{NTuple{8, VecElement{Float16}}}, x)

@inline function load(::Type{Diagonal{T}}, workspace, tile::Tile{size}) where {T, size}
    N = 16 ÷ sizeof(T)

    # The row index is given by t.index[1] + (k - 1), the column index is given by t.index[2] (0-based).
    # Only load on the diagonal, i.e. if row and column are equal.
    # Note that t.index[2] is 0-based, so we need to add 1 before loading from workspace.
    # TODO: Remove the <4 x float> everywhere, so we don't have to do this ugly casting all over the place.
    return bitcast_helper(ntuple(k -> VecElement{Float16}(tile.index[1] + k - 1 == tile.index[2] ? @inbounds(workspace[tile.index[2] + 1]) : 0), Val(8)))
end

@inline threadblock_condition(layout_a::Type{Diagonal{T}}, layout_b, block_i, block_j, block_k, block_tile) where {T} = abs(block_i - block_k) <= block_tile.size.K

# ---------------
# AlignedRowMajor
# ---------------

struct AlignedRowMajor{T} <: LayoutBase{T} end

@inline physical_size(::Type{Padded{AlignedRowMajor{T}, P}}, logical_size::NamedTuple) where {T, P} = (logical_size[2] + P, logical_size[1])

@inline fragtype(::Type{AlignedRowMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{4, VecElement{Float32}}

@inline function load(::Type{AlignedRowMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    N = 16 ÷ sizeof(T)

    linear_base = linearise(reverse(Tuple(tile.base)), Base.size(workspace))
    linear_offset = linearise(reverse(Tuple(tile.offset)), Base.size(workspace))

    return vloada(Vec{N, T}, pointer(workspace), linear_base + linear_offset - 1)
end

@inline function store!(::Type{AlignedRowMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    N = 16 ÷ sizeof(T)

    linear_base = linearise(reverse(Tuple(tile.base)), Base.size(workspace))
    linear_offset = linearise(reverse(Tuple(tile.offset)), Base.size(workspace))

    vstorea!(Vec{N, T}, pointer(workspace), value, linear_base + linear_offset - 1)
end

# -------------------
# InterleavedColMajor
# -------------------

struct InterleavedColMajor{T} <: LayoutBase{T} end

@inline fragtype(::Type{InterleavedColMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{tile_size[1] * tile_size[2], Complex{T}}

@inline function load(::Type{InterleavedColMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    x = ntuple(i -> zero(Complex{T}), tile.size[1] * tile.size[2])

    @unroll for j = 1 : tile.size[2]
        @unroll for i = 1 : tile.size[1]
            t = translate_offset(tile, (i - 1, j - 1))
            @inbounds val = workspace[t.index[1] + 1, t.index[2] + 1]
            x = Base.setindex(x, val, (i - 1) * tile.size[2] + j)
        end
    end

    return x
end

@inline function store!(::Type{InterleavedColMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    @unroll for j = 1 : tile.size[2]
        @unroll for i = 1 : tile.size[1]
            t = translate_offset(tile, (i - 1, j - 1))
            val = value[(i - 1) * tile.size[2] + j]
            @inbounds workspace[t.index[1] + 1, t.index[2] + 1] = val
        end
    end
end

# -------------------
# InterleavedRowMajor
# -------------------

struct InterleavedRowMajor{T} <: LayoutBase{T} end

@inline fragtype(::Type{InterleavedRowMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{tile_size[1] * tile_size[2], Complex{T}}

@inline function load(::Type{InterleavedRowMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    x = ntuple(i -> zero(Complex{T}), tile.size[1] * tile.size[2])

    @unroll for i = 1 : tile.size[1]
        @unroll for j = 1 : tile.size[2]
            t = translate_offset(tile, (i - 1, j - 1))
            @inbounds val = workspace[t.index[2] + 1, t.index[1] + 1]
            x = Base.setindex(x, val, (i - 1) * tile.size[2] + j)
        end
    end

    return x
end

@inline function store!(::Type{InterleavedRowMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    @unroll for i = 1 : tile.size[1]
        @unroll for j = 1 : tile.size[2]
            t = translate_offset(tile, (i - 1, j - 1))
            val = value[(i - 1) * tile.size[2] + j]
            @inbounds workspace[t.index[2] + 1, t.index[1] + 1] = val
        end
    end
end

# -------------
# SplitColMajor
# -------------

struct SplitColMajor{T} <: LayoutBase{T} end

@inline function physical_size(::Type{SplitColMajor{T}}, logical_size::NamedTuple) where {T}
    t = Tuple(logical_size)
    return (t..., 2)
end

@inline fragtype(::Type{SplitColMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{tile_size[1] * tile_size[2], Complex{T}}

@inline function load(::Type{SplitColMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    x = ntuple(i -> zero(Complex{T}), tile.size[1] * tile.size[2])

    @unroll for j = 1 : tile.size[2]
        @unroll for i = 1 : tile.size[1]
            t = translate_offset(tile, (i - 1, j - 1))
            @inbounds val = workspace[t.index[1] + 1, t.index[2] + 1, 1] + im *
                            workspace[t.index[1] + 1, t.index[2] + 1, 2]
            x = Base.setindex(x, val, (i - 1) * tile.size[2] + j)
        end
    end

    return x
end

@inline function store!(::Type{SplitColMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    @unroll for j = 1 : tile.size[2]
        @unroll for i = 1 : tile.size[1]
            t = translate_offset(tile, (i - 1, j - 1))
            val = value[(i - 1) * tile.size[2] + j]
            @inbounds workspace[t.index[1] + 1, t.index[2] + 1, 1] = val.re
            @inbounds workspace[t.index[1] + 1, t.index[2] + 1, 2] = val.im
        end
    end
end

# -------------
# SplitRowMajor
# -------------

struct SplitRowMajor{T} <: LayoutBase{T} end

@inline function physical_size(::Type{Padded{SplitRowMajor{T}, P}}, logical_size::NamedTuple) where {T, P}
    return (logical_size[2] + P, logical_size[1], 2)
end

@inline fragtype(::Type{SplitRowMajor{T}}, tile_size::NamedTuple) where {T} = NTuple{tile_size[1] * tile_size[2], Complex{T}}

@inline function load(::Type{SplitRowMajor{T}}, workspace, tile::Tile{size}) where {T, size}
    x = ntuple(i -> zero(Complex{T}), tile.size[1] * tile.size[2])

    @unroll for i = 1 : tile.size[1]
        @unroll for j = 1 : tile.size[2]
            t = translate_offset(tile, (i - 1, j - 1))
            @inbounds val = workspace[t.index[2] + 1, t.index[1] + 1, 1] + im *
                            workspace[t.index[2] + 1, t.index[1] + 1, 2]
            x = Base.setindex(x, val, (i - 1) * tile.size[2] + j)
        end
    end

    return x
end

@inline function store!(::Type{SplitRowMajor{T}}, workspace, value, tile::Tile{size}) where {T, size}
    @unroll for i = 1 : tile.size[1]
        @unroll for j = 1 : tile.size[2]
            t = translate_offset(tile, (i - 1, j - 1))
            val = value[(i - 1) * tile.size[2] + j]
            @inbounds workspace[t.index[2] + 1, t.index[1] + 1, 1] = val.re
            @inbounds workspace[t.index[2] + 1, t.index[1] + 1, 2] = val.im
        end
    end
end

end


mutable struct joint_state_t <: LCMType
    utime::Int64
    num_joints::Int16
    joint_name::Vector{String}
    joint_position::Vector{Float32}
    joint_velocity::Vector{Float32}
    joint_effort::Vector{Float32}
end

@lcmtypesetup(joint_state_t,
    joint_name => (num_joints,),
    joint_position => (num_joints,),
    joint_velocity => (num_joints,),
    joint_effort => (num_joints,),
)


"""
This struct contains all data for running a CEM for price forecasts or for endoogeneous Economic Dispatch.
    zones: Name of zones included in the market clearing problem.
    lines: Vector of inter-zonal transmission lines included in the market clearing problem.
    capital_cost_multiplier: Average capital cost multiplier for new builds.
    inv_periods: Vector of all markets modeled for the entire CEM planning horizon.
    projects: Vector of all projects included in the market clearing problem.
    rep_hour_weight: Weight of representative hours in the market clearing problem.
"""

struct MarketClearingProblem{Z, T}

    zones::Vector{String}
    lines::Vector{ZonalLine}
    capital_cost_multiplier::Float64
    inv_periods::Vector{MarketCollection{Z, T}}
    projects::Vector{MarketProject}
    rep_hour_weight::Vector{Float64}

    function MarketClearingProblem(
        zones::Vector{String},
        lines::Vector{ZonalLine},
        capital_cost_multiplier::Float64,
        inv_periods::Vector{MarketCollection{Z, T}},
        projects::Vector{MarketProject},
        rep_hour_weight::Vector{Float64}
    ) where {Z, T}
        @assert T == length(rep_hour_weight)
        @assert length(zones) ==  Z
        @assert allunique(getproperty.(projects, :name))
        new{Z, T}(zones, lines, capital_cost_multiplier, inv_periods, projects, rep_hour_weight)
    end

end


# Loads SMB results from Matthias' GloGEM model
#
# Format is Elevation bands vs year total-balance & winter-balance
# Elev  1980  1981  1982  1983  1984  1985  1986  1987  1988  1989  1990  1991  1992  1993  1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  1980  1981  1982  1983  1984  1985  1986  1987  1988  1989  1990  1991  1992  1993  1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012
# 2495  -0.23  -0.55  -0.79  -0.61  -0.21  -0.90  -1.19  -0.82  -1.17  -0.79  -1.20  -0.87  -1.45  -0.91  -1.30  -0.35  -1.18  -0.97  -1.44  -1.30  -1.41  -0.28  -1.72  -2.36  -0.59  -1.93  -1.43  -2.05  -1.33  -1.73  -0.98  -2.06  -1.68   0.72   0.71   0.76   0.72   0.55   0.53   0.68   0.50   0.64   0.44   0.53   0.52   0.70   0.70   0.71   0.65   0.06   0.62   0.47   0.78   0.65   0.93   0.32   0.62   0.71   0.23   0.47   0.11   0.56   0.59   0.53   0.48   0.54
# 2505  -0.21  -0.52  -0.76  -0.59  -0.18  -0.87  -1.16  -0.80  -1.13  -0.76  -1.17  -0.85  -1.42  -0.88  -1.27  -0.32  -1.14  -0.94  -1.41  -0.91  -1.38  -0.26  -1.68  -2.33  -0.57  -1.52  -1.40  -2.01  -1.30  -1.70  -0.95  -1.73  -1.65   0.72   0.71   0.76   0.73   0.56   0.54   0.69   0.51   0.65   0.45   0.53   0.53   0.70   0.71   0.72   0.66   0.07   0.63   0.48   0.78   0.65   0.94   0.32   0.62   0.72   0.24   0.47   0.12   0.56   0.59   0.53   0.48   0.54
# ...

dir2reg  = Dict(
    :Alaska=>1,
    :WesternCanada=>2,
    :ArcticCanadaNorth=>3,
    :ArcticCanadaSouth=>4,
    :Greenland=>5,
    :Iceland=>6,
    :Svalbard=>7, # and Jan Mayen
    :Scandinavia=>8,
    :RussianArctic=>9,
    :NorthAsia=>10,
    :CentralEurope=>11,
    :CaucasusMiddleEast=>12,
    :CentralAsia=>13,
    :SouthAsiaWest=>14,
    :SouthAsiaEast=>15,
    :LowLatitudes=>16,
    :SouthernAndes=>17,
    :NewZealand=>18,
    :SubAntarctic=>19
)

reg2dir = Dict(r=>n for (n,r) in dir2reg)

function load!(ds::DataSet{BdotData, GloGEMLoader}, dem, glaciermask, target_proj,
               outline, cropbox, pp::Phys)
    @unpack files, opts, daterange = ds
    @unpack sigma = opts
    make2D = get(opts, :make2D, true)

    rgi = getrgi(outline.gid)
    vers,reg,nr = parse_rgi(rgi)
    if vers!=v"6"
        error("GloGEM runs on RGI version 6 but here it is version $vers")
    end
    # make filename
    #base = "/home/mauro/netfs/vaw/root/scratch_net/sermeq/mhuss/r6_global_results"
    #ff = joinpath(base, string(reg2dir[reg]), "PAST/mb_elevation", "belev_$(VAWTools.int2str5(nr)).dat")
    ff = ds.files[1]
    #
    out = get_cache_ram!(ds, :bdot, ()->readdlm(ff, F, header=true))
    # only keep first half as second half is winter-balance:
    years = map(x->parse(Int,x),out[2][2:end÷2])
    bdots = out[1][:,2:end÷2] # values at elevation bands and years
    for i in eachindex(bdots)
        if bdots[i]==-99
            bdots[i]=FILL
        end
    end

    ele_ = out[1][:,1] # middle of elevation band
    # convert to range
    st = diff(ele_)[1]
    ele = ele_[1]:st:ele_[end]
    @assert collect(ele)==ele_

    # average over some years
    if daterange==(Date(),Date()) # use all
        bdot = squeeze(mean(bdots,2),2)
    else
        inds = Dates.year(daterange[1]).<=years.<=Dates.year(daterange[2])
        bdot = squeeze(mean(bdots[:,inds],2),2)
    end

    # scale to m ice / a
    scale_m_ice!(bdot, pp)
    vals = Gridded1d(ele,bdot)

    # fill gaps
    fillgaps!(vals, 3)

    if make2D
        # make a 2D bdot field:
        bands, bandi = VAWTools.bin_grid(dem, vals.x, glaciermask) #, min_bands=0)
        bdot_2D = VAWTools.map_back_to_2D(size(dem.v), bandi, vals.v)
        return SomeData(vals=Gridded(dem.x, dem.y, bdot_2D), sigma=sigma)
    else
        return SomeData(vals=vals, sigma=sigma)
    end
end


# ------------------------------------------------------------------------------------------
# # IMDB Movie Review Sentiment Analysis Demo
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Setup
# 1-Adds required packages to Julia.
# 2-Loads the data and a pretrained model.
# ------------------------------------------------------------------------------------------

include("imdb.jl")

# ------------------------------------------------------------------------------------------
# ## Sample Data
# The model was trained using 25000 movie reviews such as the following (shift-ENTER to see
# random example)
# Each review was tokenized, lowercased, truncated to max 150 words and a 30,000 word
# vocabulary.
# ------------------------------------------------------------------------------------------

r = rand(1:length(xtrn))
println(reviewstring(xtrn[r],ytrn[r]))
flush(STDOUT)

# ------------------------------------------------------------------------------------------
# ## Test
# We test the model on 25000 never before seen reviews on the test set (shift-ENTER to see
# random example)
# The test accuracy is around 86%
# ------------------------------------------------------------------------------------------

r = rand(1:length(xtst))
println(reviewstring(xtst[r],ytst[r]))
println("\nModel prediction: "*predictstring(xtst[r]))
flush(STDOUT)

# ------------------------------------------------------------------------------------------
# ## User Input
# In this cell you can enter your own review and let the model guess the sentiment
# ------------------------------------------------------------------------------------------

userinput = readline(STDIN)
words = split(lowercase(userinput))
ex = [get!(imdbdict,wr,UNK) for wr in words]
ex[ex.>MAXFEATURES]=UNK
println("\nModel prediction: "*predictstring(ex))


"""
    SplitLayer(xs...)

A layer that calls a number of sublayers/mappings with the same input and
returns a tuple of their outputs. Can be used in a regular Flux model:

```julia-repl
julia> m = Chain(Dense(2,3), SplitLayer(Dense(3,2), x->x .* 2))
julia> length(params(m)) == 4
julia> (x,y) = m(rand(2))
(Float32[-1.0541434, 1.1694773], Float32[-3.1472511, -0.86115724, -0.39665926])
```
Comes with a convenient constructor for a SplitLayer built from Dense layers
with given activation(s):
```julia-repl
julia> m = Chain(Dense(2,3), SplitLayer(3, [2,5], σ))
julia> (x,y) = m(rand(2))
(Float32[0.3069554, 0.3362006], Float32[0.437131, 0.4982477, 0.6465078, 0.4523438, 0.5068563])
```

You can also provide just a vector / scalar that should be trained but have the
same value for all inputs (like a lonely bias vector). This functionality is
provided by the `TrainableVector`/`TrainableScalar` types. For vector inputs
they simply return the array they are wrapping. For matrix (i.e. batch) inputs
they return appropriately repeated arrays:
```julia-repl
julia> m = SplitLayer(Dense(2,3), ones(Float32,3))
julia> length(params(m)) == 3
julia> (x,y) = m(rand(2,5))
julia> size(y) == (3,5)
julia> y
3×3 Array{Float32,2}:
 1.0  1.0  1.0
 1.0  1.0  1.0
 1.0  1.0  1.0
```
"""
struct SplitLayer{T<:Tuple}
    layers::T
end

SplitLayer(layers...) = SplitLayer(map(maybe_trainable, layers))

function (m::SplitLayer)(x)
    Tuple(layer(x) for layer in m.layers)
end

@functor SplitLayer

# for use as e.g. shared variance
struct TrainableVector{T<:AbstractArray}
    v::T
end
(v::TrainableVector)(x::AbstractVector) = v.v
(v::TrainableVector)(x::AbstractMatrix) = v.v .* reshape(fillsimilar(v.v,size(x,ndims(x)),1),1,:)
(v::TrainableVector)() = v.v
@functor TrainableVector

# for use as e.g. shared variance
struct TrainableScalar{T<:Real}
    s::AbstractVector{T}
    TrainableScalar{T}(x::T) where T<:Real = new{T}([x])
end
TrainableScalar(x::T) where T<:Real = TrainableScalar{T}(x)
(s::TrainableScalar)(x::AbstractVector) = s.s[1]
(s::TrainableScalar)(x::AbstractMatrix) = fillsimilar(x,size(x,ndims(x)),1) .* s.s
@functor TrainableScalar

maybe_trainable(x) = x
maybe_trainable(x::AbstractArray) = TrainableVector(x)
maybe_trainable(x::Real) = TrainableScalar(x)

fillsimilar(x::AbstractArray, s::Tuple, value::Real) = fill!(similar(x, s...), value)
fillsimilar(x::AbstractArray, s, value::Real) = fill!(similar(x, s), value)
@non_differentiable fillsimilar(::Any, ::Any, ::Any)


#!/usr/bin/env julia

"""
feature_counts.jl - Houses functions that deal with GFF feature-BAM alignment overlaps and counts

By: Shaun Adkins (sadkins@som.umaryland.edu)
"""

### Feature Overlap - Miscellaneous information about a feature with respect to its overlaps to alignments

mutable struct FeatureOverlap
    num_alignments::Float32
    feat_counts::Float32
    coords_set::BitSet
end

coordinate_set(f::FeatureOverlap) = f.coords_set
featurecounts(f::FeatureOverlap) = f.feat_counts
totalalignments(f::FeatureOverlap) = f.num_alignments

inc_featurecounts!(f::FeatureOverlap, counts) = f.feat_counts += counts
inc_alignments!(f::FeatureOverlap, num_alignments) = f.num_alignments += num_alignments

### MultimappedAlignment - Alignments that overlap features but are multimapped

struct MultimappedAlignment
    featurename::String
    align_feat_ratio::Float32
    alignmenttype::T where {T<:AbstractAlignment}
end

featurename(m::MultimappedAlignment) = m.featurename
alignment_feature_ratio(m::MultimappedAlignment) = m.align_feat_ratio
alignment_type(m::MultimappedAlignment) = m.alignmenttype

function calc_relative_abundance(feature_counts::Float32, template_totalcounts::Float32)::Float32
    """Calculate relative abundance fraction for the given feature with respect to all features overlapping the template."""
    @assert template_totalcounts > 0 "The template_totalcounts value should be greater than 0"
    # Total relative abundances for all features overlapping this template should = 1.0
    return @fastmath feature_counts / template_totalcounts
end

function calc_subset_totalcounts(feat_overlaps::Dict{String, FeatureOverlap}, feature_counter::Dict{String, UInt})
    """For all features overlapping the alignment template, return the sum of all those feature counts."""
    totalcounts = zero(Float32)
    # Worth noting if a feature overlaps multiple times, it will be reflected in totalcounts
    # This will be appropriately handled when all alignments are iterated through to increment the FeatureOverlap feat_counts for the feature.
    for (featurename, count) in feature_counter
        totalcounts += featurecounts(feat_overlaps[featurename]) * count
    end
    return totalcounts
end

function calc_totalcounts(feat_overlaps::Dict{String, FeatureOverlap})
    """Calculate the sum of all the feature counts."""
    return sum(featurecounts(feat_overlaps[featurename]) for featurename in keys(feat_overlaps))
end

function calc_tpm(len::UInt, totalcounts::Float32, feat_counts::Float32)
    """Calculate TPM score for current feature."""
    return @fastmath(feat_counts * 1000 / len) * 1000000 / totalcounts
end

function compute_alignment_feature_ratio(uniq_feat_coords::BitSet, alignment::Interval)::Float32
    """Calculate the ratio of fragament coordinates that intersect with non-overlapping feature coordinates."""
    # Pertinent alignment info
    alignment_coords =  get_alignment_coords_set(alignment)
    alignment_intersect = intersect(uniq_feat_coords, alignment_coords)
    # Percentage of alignment that aligned with this annotation feature
    # Return as Float32 for performance
    return @fastmath length(alignment_intersect) / length(alignment_coords)
end

function compute_mm_adjusted_counts(total_feat_counts::Float32, template_feat_counts::Float32, template_totalcounts::Float32)::Float32
    """Calculate the adjustment feature counts for a multimapped template to a given feature."""
    relative_abundance = calc_relative_abundance(total_feat_counts, template_totalcounts)
    return template_feat_counts * relative_abundance
end

function compute_mm_counts_by_em(feat_overlaps::Dict{String, FeatureOverlap}, multimapped_dict::Dict{String,StructArray}, args::Dict)
    """Adjust the feature counts of the multimapped overlaps via the Expectation-Maximization algorithm."""
    # Initialize multimapped overlap feature dictionary
    featurenames = collect(keys(feat_overlaps))
    adjusted_mm_overlaps = Dict{String, FeatureOverlap}(featurename => initialize_overlap_info(coordinate_set(feat_overlaps[featurename])) for featurename in featurenames)

    # Iterate through the multimaped alignments for each template
    @inbounds for (tn, templatealignments) in multimapped_dict
        # All multimapped alignments for a record should have the same alignment type
        alignmenttype = alignment_type(first(templatealignments))

        # I do not use "unique" for this because there may be a chance that the template aligns to the same feature multiple times
        overlappingfeatures = [featurename(a) for a in templatealignments]

        template_feature_counter = Dict{String, UInt}(fn => zero(UInt) for fn in unique(overlappingfeatures))
        template_feat_counts = Dict{String, Float32}(fn => zero(Float32) for fn in unique(overlappingfeatures))
        Threads.@threads for fn in unique(overlappingfeatures)
            # Filter all alignments that have this particular template-feature overlap
            templatefeaturealignments = filter(x -> x.featurename == fn, templatealignments)
            template_feature_counter[fn] += length(templatefeaturealignments)
            template_feat_counts[fn] += sum(alignment_feature_ratio, templatefeaturealignments)
        end

        template_totalcounts = calc_subset_totalcounts(feat_overlaps, template_feature_counter)
        # In cases where every feature for this template had no overlaps with any singly-mapped alignments, do not consider them.
        template_totalcounts > zero(Float32) || continue

        # Adjust contribution proportion of multimapped reads, based on estimated relative abundance
        for (featurename, counts) in template_feat_counts
            counts > zero(Float32) || continue
            adjusted_feat_counts = compute_mm_adjusted_counts(featurecounts(feat_overlaps[featurename]), counts, template_totalcounts)
            # If the feature had no contribution to any singly-mapped alignments, ignore it.
            adjusted_feat_counts > zero(Float32) && increment_feature_overlap_information!(adjusted_mm_overlaps[featurename], adjusted_feat_counts, alignmenttype)
        end
    end
    return adjusted_mm_overlaps
end

function create_feat_overlaps_dict(features::Array{GFF3.Record,1}, uniq_coords::Dict{String,Dict}, attr_type::String, stranded_type::String)
    feat_overlaps = Dict{String, FeatureOverlap}()
    for feature in features
        featurename = get_featurename_from_attrs(feature, attr_type)
        uniq_feat_coords = get_feature_nonoverlapping_coords(feature, uniq_coords, isstranded(stranded_type))
        feat_overlaps[featurename] = initialize_overlap_info(uniq_feat_coords)
    end
    return feat_overlaps
end

function filter_features_overlapping_alignments(features::Array{GFF3.Record,1}, alignment::Interval, isstranded::Bool)
    """Filter features to those just that align with the current alignment on the same strand."""
    alignmentstrand = getstrand(alignment, isstranded)
    overlapping_features = filter(x -> isoverlapping(convert(Interval, x), alignment), features)
    filter!(x -> alignmentstrand == getstrand(x, isstranded), overlapping_features)
    return overlapping_features   # Returns an array of GFF3 Records
end

function increment_feature_overlap_information!(feat_overlap::FeatureOverlap, align_feat_ratio::Float32, alignmenttype::T) where {T<:AbstractAlignment}
    """Increment number of alignments and feature count information for feature if alignment overlapped with uniq coords."""
    inc_alignments!(feat_overlap, multiplier(alignmenttype))
    inc_featurecounts!(feat_overlap, align_feat_ratio * multiplier(alignmenttype))
end

function initialize_overlap_info(uniq_feat_coords::BitSet)
    """Initialize overlap diction information."""
    num_alignments = zero(Float32); feat_counts = zero(Float32)
    return FeatureOverlap(num_alignments, feat_counts, uniq_feat_coords)
end

function merge_mm_counts(feat_overlaps::Dict{String, FeatureOverlap}, mm_feat_overlaps::Dict{String, FeatureOverlap}, adjust_num_alignments::Bool=true)
    """Merge EM-computed counts for multimapped reads and singly-mapped feature counts.  Return a new Dict of FeatureOverlaps"""
    adjusted_overlaps = deepcopy(feat_overlaps)
    featurenames = collect(keys(feat_overlaps))
    [inc_featurecounts!(adjusted_overlaps[fn], featurecounts(mm_feat_overlaps[fn])) for fn in featurenames]
    adjust_num_alignments || return adjusted_overlaps

    [inc_alignments!(adjusted_overlaps[fn], totalalignments(mm_feat_overlaps[fn])) for fn in featurenames]
    return adjusted_overlaps
end

function merge_mm_counts!(feat_overlaps::Dict{String, FeatureOverlap}, mm_feat_overlaps::Dict{String, FeatureOverlap}, adjust_num_alignments::Bool=true)
    """Merge EM-computed counts for multimapped reads back into the general feature overlap counts."""
    featurenames = collect(keys(feat_overlaps))
    [inc_featurecounts!(feat_overlaps[fn], featurecounts(mm_feat_overlaps[fn])) for fn in featurenames]
    adjust_num_alignments || return

    [inc_alignments!(feat_overlaps[fn], totalalignments(mm_feat_overlaps[fn])) for fn in featurenames]
end

function process_feature_overlaps!(feat_overlaps::Dict{String, FeatureOverlap}, multimapped_dict::Dict{String,StructArray}, reader::BAM.Reader, feature::GFF3.Record, args)
    """Process all alignment intervals that overlap with the given feature interval."""
    featurename = get_featurename_from_attrs(feature, args["attribute_type"])
    feat_overlap = feat_overlaps[featurename]
    max_frag_size = convert(UInt, args["max_fragment_size"])
    featurestrand = getstrand(feature, isstranded(args["stranded"]))

    # Returning SuperBAMRecord objects instead of BAM.Record objects
    for rec in eachoverlap(reader, feature, args["stranded"], max_frag_size)
        alignmentinterval = interval(rec)
        @assert alignmentinterval !== nothing "All iterated records should be a validated read or fragment"
        strand(rec) == featurestrand || continue

        alignmenttype = alignment_type(rec)
        args["pp_only"] && isa(alignmenttype, ReadAlignment) && continue

        align_feat_ratio =  compute_alignment_feature_ratio(coordinate_set(feat_overlap), alignmentinterval)::Float32
        # Save multimapped records as Interval objects for later, if needed, otherwise increment information for this feature now
        if ismultimapped(record(rec))
            if !args["rm_multimap"]
                templatename = BAM.tempname(record(rec))
                get!(multimapped_dict, templatename, StructArray{MultimappedAlignment}(undef,0))
                push!(multimapped_dict[templatename], MultimappedAlignment(featurename, align_feat_ratio, alignmenttype))
            end
            continue
        end
        align_feat_ratio > 0.0 && increment_feature_overlap_information!(feat_overlap, align_feat_ratio, alignmenttype)
    end

    # NOTE: This function modifies feat_overlaps and multimapped_dict
end

function process_all_feature_overlaps(feat_overlaps::Dict{String,FeatureOverlap}, features::Vector{GFF3.Record}, reader::BAM.Reader, args)
    """Process all features from the annotation file for overlaps with BAM alignments."""
    multimapped_dict = Dict{String,StructArray}()
    [process_feature_overlaps!(feat_overlaps, multimapped_dict, reader, feature, args) for feature in features]
    return multimapped_dict
end


function _gradient_type(
  ::Type{A},::Type{<:SVector{D}}) where A<:StaticArray{S,T,N} where {S,T,N,D}
  SG = _gradient_size(Size(A),Val(D))
  TG = T
  NG = N+1
  LG = _gradient_length(Size(A),Val(D))
  SArray{SG,TG,NG,LG}
end

function _gradient_type(
  ::Type{A},::Type{<:VectorValue{D}}) where A<:MultiValue{S,T,N,L} where {S,T,N,L,D}
  G = _gradient_type(SArray{S,T,N,L},SVector{D,T})
  _to_multivalue_type(G)
end

function _gradient_type(::Type{T},::Type{<:VectorValue{D}}) where {T<:Real,D}
  VectorValue{D,T}
end

function _gradient_type(::Type{T},::Type{<:SVector{D}}) where {T<:Real,D}
  SVector{D,T}
end

function _to_multivalue_type(::Type{SArray{S,T,N,L}}) where {S,T,N,L}
  MultiValue{S,T,N,L}
end

@generated function _gradient_size(::Size{B},::Val{D}) where {B,D}
  str = join(["$b," for b in B])
  Meta.parse("Tuple{$D,$str}")
end

function _gradient_length(::Size{B},::Val{D}) where {B,D}
  prod((D,B...))
end

_mutable(::Type{SArray{S,T,N,L}}) where {S,T,N,L} = MArray{S,T,N,L}

_mutable(::Type{MultiValue{S,T,N,L}}) where {S,T,N,L} = MArray{S,T,N,L}

_length(x) = length(x)

_length(::Type{<:Real}) = 1

macro abstractmethod()
  quote
    error("This function belongs to an interface definition and cannot be used.")
  end
end

macro notimplemented()
  quote
    error("This function in not yet implemented")
  end
end

macro notimplementedif(condition)
  quote
    if $(esc(condition))
      @notimplemented
    end
  end
end

macro unreachable()
  quote
    error("This line of code cannot be reached")
  end
end


module DiffEqJump

using Reexport
@reexport using DiffEqBase

using Compat, RandomNumbers, TreeViews, LinearAlgebra
using DataStructures, PoissonRandom, Random, ArrayInterface
using FunctionWrappers, UnPack
using LightGraphs

import DiffEqBase: DiscreteCallback, init, solve, solve!, plot_indices, initialize!
import Base: size, getindex, setindex!, length, similar, show, merge!, merge
import DataStructures: update!
import LightGraphs: neighbors, outdegree

import RecursiveArrayTools: recursivecopy!
using StaticArrays, Base.Threads

abstract type AbstractJump end
abstract type AbstractAggregatorAlgorithm end
abstract type AbstractJumpAggregator end

import Base.Threads
@static if VERSION < v"1.3"
  seed_multiplier() = Threads.threadid()
else
  seed_multiplier() = 1
end

include("jumps.jl")
include("massaction_rates.jl")
include("aggregators/aggregators.jl")
include("aggregators/ssajump.jl")
include("aggregators/direct.jl")
include("aggregators/frm.jl")
include("aggregators/sortingdirect.jl")
include("aggregators/nrm.jl")
include("aggregators/bracketing.jl")
include("aggregators/rssa.jl")
include("aggregators/prioritytable.jl")
include("aggregators/directcr.jl")
include("aggregators/rssacr.jl")
include("aggregators/rdirect.jl")

# spatial:
include("spatial/topology.jl")
include("spatial/hop_rates.jl")
include("spatial/reaction_rates.jl")
include("spatial/flatten.jl")
include("spatial/utils.jl")

include("spatial/nsm.jl")
include("spatial/directcrdirect.jl")

include("aggregators/aggregated_api.jl")

include("extended_jump_array.jl")
include("problem.jl")
include("solve.jl")
include("coupled_array.jl")
include("coupling.jl")
include("SSA_stepper.jl")
include("simple_regular_solve.jl")

export ConstantRateJump, VariableRateJump, RegularJump, MassActionJump,
       JumpSet

export JumpProblem

export SplitCoupledJumpProblem

export Direct, DirectFW, SortingDirect, DirectCR
export BracketData, RSSA
export FRM, FRMFW, NRM
export RSSACR, RDirect

export get_num_majumps, needs_depgraph, needs_vartojumps_map

export init, solve, solve!

export reset_aggregated_jumps!

export ExtendedJumpArray

# spatial structs and functions
export CartesianGrid, CartesianGridRej, CartesianGridIter
export outdegree, num_sites, neighbors
export NSM, DirectCRDirect

end # module


mutable struct TileStates
    quads::Array{Tuple{Integer, Integer}, 2}
    chars::Array{Char, 2}
    rands::Array{UInt8, 2}

    TileStates() = new(Matrix{Tuple{Integer, Integer}}(undef, 0, 0), Matrix{Char}(undef, 0, 0), Matrix{Integer}(undef, 0, 0))
end

mutable struct DrawableRoom
    map::Map
    room::Room

    fgTileStates::TileStates
    bgTileStates::TileStates

    rendering::Union{Layer, Nothing}
    layers::Array{Layer, 1}

    fillColor::Union{Nothing, colorTupleType}
end

Base.size(state::TileStates) = size(state.rands)

getTileStateSeed(name::String, package::String="", fg::Bool=false) = foldl((a, b) -> a + Int(b) * 128, collect(package * (fg ? "fg" : "bg") * name), init=0)
getTileStateSeed(room::Room, package::String="", fg::Bool=false) = getTileStateSeed(room.name, package, fg)

function updateTileStates!(room::String, package::String, states::TileStates, width::Integer, height::Integer, fg::Bool=false)
    seed = getTileStateSeed(room, package, fg)
    rng = MersenneTwister(seed)

    states.quads = fill((-1, -1), (height, width))
    states.chars = fill('0', (height, width))
    states.rands = rand(rng, 0:255, (height, width))
end

updateTileStates!(room::Room, package::String, states::TileStates, width::Integer, height::Integer, fg::Bool=false) = updateTileStates!(room.name, package, states, width, height, fg)

function getDrawingLayers()
    return Layer[
        Layer("bgParallax"),
        Layer("bgTiles", clearOnReset=false),
        Layer("bgDecals"),
        Layer("entities"),
        Layer("fgTiles", clearOnReset=false),
        Layer("fgParallax"),
        Layer("fgDecals"),
        Layer("triggers"),

        Layer("tools", selectable=false),
        Layer("all", dummy=true)
    ]
end

function roomVisible(camera::Camera, width::Integer, height::Integer, room::Room)
    actuallX = camera.x / camera.scale
    actuallY = camera.y / camera.scale

    actuallWidth = width / camera.scale
    actuallHeight = height / camera.scale

    cameraRect = Rectangle(actuallX, actuallY, actuallWidth, actuallHeight)
    roomRect = Rectangle(Int.(room.position)..., Int.(room.size)...)

    return checkCollision(cameraRect, roomRect)
end

redrawRenderingLayer(renderingLayer::Layer, layers::Array{Layer, 1}) = renderingLayer.redraw || any(map(layer -> layer.redraw, layers))

function getRoomBackgroundColor(room::Room)
    if 0 <= room.color < length(colors.background_room_color_coded_fill)
        return colors.background_room_color_coded_fill[room.color + 1]

    else
        return colors.background_room_fill
    end
end

function DrawableRoom(map::Map, room::Room)
    return DrawableRoom(
        map,
        room,

        TileStates(),
        TileStates(),

        Layer("rendering"),
        getDrawingLayers(),

        nothing
    )
end

function destroy(dr::DrawableRoom)
    ctx = getSurfaceContext(dr.rendering.surface)
    Cairo.destroy(ctx)

    for layer in dr.layers
        ctx = getSurfaceContext(layer.surface)
        Cairo.destroy(ctx)
    end
end


@testset "OpenStreetMap space" begin
    space = OpenStreetMapSpace(TEST_MAP)
    @test length(space.s) == 1799
    @test sprint(show, space) ==
          "OpenStreetMapSpace with 1456 roadways and 1799 intersections"

    Random.seed!(689)
    model = ABM(Agent10, space)

    @test osm_random_road_position(model) != osm_random_road_position(model)
    intersection = random_position(model)
    @test intersection[1] == intersection[2]
    @test intersection[3] == 0.0
    ll = osm_latlon(intersection, model)
    @test intersection == osm_intersection(ll, model)

    start_latlon = (39.534773980413505, -119.78937575923226)
    start_i = osm_intersection(start_latlon, model)
    i_diff = sum(abs.(osm_latlon(start_i, model) .- start_latlon))
    start_r = osm_road(start_latlon, model)
    r_diff = sum(abs.(osm_latlon(start_r, model) .- start_latlon))
    @test i_diff > r_diff

    finish_latlon = (39.52530416953533, -119.76949287425508)
    finish_i = osm_intersection(finish_latlon, model)
    finish_r = osm_road(finish_latlon, model)

    route = osm_plan_route(start_r, finish_r, model)
    @test length(route) == 20
    add_agent!(start_r, model, route, finish_r)
    add_agent!(finish_i, model, [], finish_i)

    @test osm_latlon(model[2], model) == osm_latlon(finish_i[1], model)
    np = nearby_positions(model[2], model)
    @test length(np) == 3
    @test osm_latlon(np[1], model) == (39.5259267, -119.76681090000002)

    rand_road = osm_random_road_position(model)
    rand_intersection = random_position(model)
    rand_route = osm_plan_route(rand_road, rand_intersection, model; return_trip = true)
    @test length(rand_route) == 18

    @test osm_road_length(model[1].pos, model) ≈ 106.49016546202557
    @test osm_road_length(finish_r[1], finish_r[2], model) ≈ 395.8120895006937

    @test length(osm_plan_route(start_r[2], finish_r[1], model)) == 22
    @test length(osm_plan_route(start_r, finish_r[1], model)) == 21
    @test length(osm_plan_route(start_r[2], finish_r, model)) == 21
    @test osm_plan_route(start_r, finish_r[1], model) !=
          osm_plan_route(start_r[2], finish_r, model)
    @test length(osm_plan_route(start_r, finish_r, model)) == 20

    @test osm_map_coordinates(model[1], model) == (-2904.337825035879, 1444.5549163962387)
    @test osm_map_coordinates(model[2], model) == (-1475.8022717621393, 461.34058791100955)

    @test !osm_is_stationary(model[1])
    move_agent!(model[1], model, 403.2)
    @test length(model[1].route) == 16
    move_agent!(model[1], model, 1500)
    @test isempty(model[1].route)
    @test !osm_is_stationary(model[1])
    move_agent!(model[1], model, 200)
    @test !osm_is_stationary(model[1])
    move_agent!(model[1], model, 200)
    @test !osm_is_stationary(model[1])
    move_agent!(model[1], model, 200)
    @test osm_is_stationary(model[1])

    @test osm_is_stationary(model[2])
    @test move_agent!(model[2], model, 50) == nothing

    add_agent!(start_r, model, route, finish_r)
    osm_random_route!(model[3], model)
    @test model[3].destination != finish_r
    move_agent!(model[3], model, 100.0)

    for i in 1:5
        s = (start_r[1:2]..., start_r[3] - i)
        route = osm_plan_route(s, finish_r, model)
        add_agent!(s, model, route, finish_r)
    end

    @test sort!(nearby_ids(model[6], model, 2)) == [4, 5, 7, 8]
    @test sort!(nearby_ids(model[6], model, 800.0)) == [3, 4, 5, 7, 8]
end


module Cobweb

using ..Ahorn, Maple

const placements = Ahorn.PlacementDict(
    "Cobweb" => Ahorn.EntityPlacement(
        Maple.Cobweb,
        "line"
    )
)

cobwebColorHex = "696A6A"
cobwebColor = (41, 42, 42, 1) ./ (255, 255, 255, 1)
cobwebColorSelected = (Ahorn.colors.selection_selected_fc)

function renderCobweb(ctx::Ahorn.Cairo.CairoContext, entity::Maple.Cobweb, color::Ahorn.colorTupleType=cobwebColor)
    x, y = Ahorn.position(entity)

    start = (x, y)
    nodes = get(entity.data, "nodes", [start])
    stop = nodes[1]

    baseCurve = Ahorn.SimpleCurve(start, stop, (start .+ stop) ./ 2 .+ (0, 4))
    middle = Ahorn.getPoint(baseCurve, 0.5)

    for node in nodes
        curve = Ahorn.SimpleCurve(middle, node, (middle .+ node) ./ 2 .+ (0, 4))
        Ahorn.drawSimpleCurve(ctx, curve, color, thickness=1)
    end

    curve = Ahorn.SimpleCurve(middle, start, (middle .+ start) ./ 2 .+ (0, 4))
    Ahorn.drawSimpleCurve(ctx, curve, color, thickness=1)
end

Ahorn.nodeLimits(entity::Maple.Cobweb) = 1, -1

function Ahorn.selection(entity::Maple.Cobweb)
    nodes = get(entity.data, "nodes", ())
    x, y = Ahorn.position(entity)

    res = Ahorn.Rectangle[Ahorn.Rectangle(x - 4, y - 4, 8, 8)]

    for node in nodes
        nx, ny = node

        push!(res, Ahorn.Rectangle(nx - 4, ny - 4, 8, 8))
    end

    return res
end

Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::Maple.Cobweb) = renderCobweb(ctx, entity, cobwebColorSelected)

function Ahorn.renderAbs(ctx::Ahorn.Cairo.CairoContext, entity::Maple.Cobweb, room::Maple.Room)
    # Make sure Alpha is 1
    rawColor = Ahorn.argb32ToRGBATuple(parse(Int, get(entity.data, "color", cobwebColorHex), base=16))[1:3] ./ 255
    color = (rawColor..., 1.0)

    renderCobweb(ctx, entity, color)
end

end


export AbstractVariable, degree
export is_clamped, is_marginalisation, is_moment_matching
export FoldLeftProdStrategy, FoldRightProdStrategy, CustomProdStrategy
export getmarginal, getmarginals, setmarginal!, setmarginals!, name, as_variable

using Rocket

abstract type AbstractVariable end

## Messages to Marginal product strategies

struct FoldLeftProdStrategy end
struct FoldRightProdStrategy end

struct CustomProdStrategy{F}
    prod_callback_generator :: F
end

"""
    messages_prod_fn(strategy, prod_constraint, form_constraint, form_check_strategy)

Returns a suitable prod computation function for a given strategy and constraints

See also: [`FoldLeftProdStrategy`](@ref), [`FoldRightProdStrategy`](@ref), [`CustomProdStrategy`](@ref)
"""
function messages_prod_fn end

messages_prod_fn(::FoldLeftProdStrategy, prod_constraint, form_constraint, form_check_strategy)       = prod_foldl_reduce(prod_constraint, form_constraint, form_check_strategy)
messages_prod_fn(::FoldRightProdStrategy, prod_constraint, form_constraint, form_check_strategy)      = prod_foldr_reduce(prod_constraint, form_constraint, form_check_strategy)
messages_prod_fn(strategy::CustomProdStrategy, prod_constraint, form_constraint, form_check_strategy) = strategy.prod_callback_generator(prod_constraint, form_constraint, form_check_strategy)

function marginal_prod_fn(strategy, prod_constraint, form_constraint, form_check_strategy)
    return let prod_fn = messages_prod_fn(strategy, prod_constraint, form_constraint, form_check_strategy)
        return (messages) -> as_marginal(prod_fn(messages))
    end
end

## Common functions

function degree end

add_pipeline_stage!(variable::AbstractVariable, stage) = error("Its not possible to add a new pipeline stage for $(variable)")

# Helper functions
# Getters

getmarginal(variable::AbstractVariable) = getmarginal(variable, SkipInitial())

function getmarginal(variable::AbstractVariable, skip_strategy::MarginalSkipStrategy)
    vmarginal = _getmarginal(variable)
    if vmarginal === nothing
        vmarginal = as_marginal_observable(_makemarginal(variable))
        _setmarginal!(variable, vmarginal)
    end
    return as_marginal_observable(vmarginal, skip_strategy)
end

getmarginals(variables::AbstractArray{ <: AbstractVariable })                                      = getmarginals(variables, SkipInitial())
getmarginals(variables::AbstractArray{ <: AbstractVariable }, skip_strategy::MarginalSkipStrategy) = collectLatest(map(v -> getmarginal(v, skip_strategy), variables))

# Setters

function setmarginal!(variable::AbstractVariable, marginal)
    setmarginal!(getmarginal(variable, IncludeAll()), marginal)
end

setmarginals!(variables::AbstractArray{ <: AbstractVariable }, marginal::Distribution)    = _setmarginals!(Base.HasLength(), variables, Iterators.repeated(marginal, length(variables)))
setmarginals!(variables::AbstractArray{ <: AbstractVariable }, marginals)                 = _setmarginals!(Base.IteratorSize(marginals), variables, marginals)

function _setmarginals!(::Base.IteratorSize, variables::AbstractArray{ <: AbstractVariable }, marginals)
    foreach(zip(variables, marginals)) do (variable, marginal)
        setmarginal!(getmarginal(variable, IncludeAll()), marginal)
    end
end

function _setmarginals!(::Any, variables::AbstractArray{ <: AbstractVariable }, marginals)
    error("setmarginals!() failed. Default value is neither an iterable object nor a distribution.")
end

function name(variable::AbstractVariable)
    return variable.name
end

## Helper functions

as_variable(x)                   = constvar(gensym(:as_var), x)
as_variable(v::AbstractVariable) = v
as_variable(t::Tuple)            = map(as_variable, t)


module Opcode

export AABBTree, intersect, intersect!, deform!, raycast, raycast!

using CxxWrap

const depsfile = joinpath(dirname(dirname(@__FILE__)), "deps", "deps.jl")
if !isfile(depsfile)
    error("$depsfile not found, Opcode did not build properly")
end
include(depsfile)
const jlcxx_path = _l_jlcxx

# Wrap the functions defined in C++
wrap_module(Opcode._l_opcode, Opcode)

const AABBTree = Opcode.ModelRef

function AABBTree(verts::Array{<:Real,2}, faces::Array{<:Integer,2})
    size(verts,1)==3 && size(faces,1)==3 || error("require verts : 3 x n and faces : 3 x m")
    AABBTree(Opcode.aabb_tree_create(verts,size(verts,2),faces,size(faces,2)))
end

# cast first n rays in (origins,dirs)
function raycast(tree::AABBTree, origins::Array{<:Real,2}, dirs::Array{<:Real,2}, n::Integer = size(origins,2);
                 mode::Int=0)
    size(origins,1)==size(dirs,1)==3 &&
    n <= min(size(origins,2), size(dirs,2)) || error("require origins : 3 x n and dirs : 3 x n")
    if mode==0
        hits = Vector{Int}()
    	dists = Vector{Float64}();
    	faceinds = Vector{Int}();
    	barys = Vector{Float64}();
    	Opcode.aabb_tree_intersect(tree, origins, dirs, hits, dists, faceinds, barys, n)
    	(hits,dists,faceinds,reshape(barys,2,length(hits)))
    else
        hits = Vector{Int}(n)
        dists = Vector{Float64}(n);
        faceinds = Vector{Int}(n);
        barys = Matrix{Float64}(2,n);
        Opcode.aabb_tree_intersect_mut(tree, origins, dirs, hits, dists, faceinds, barys, n)
        (hits,dists,faceinds,reshape(barys,2,length(hits)))
    end
end

function raycast!(tree::AABBTree, origins::Array{<:Real,2}, dirs::Array{<:Real,2},
                    hits::Vector{<:Integer}, dists::Vector{<:Real}, faceinds::Vector{<:Integer},
		    barys::Array{<:Real,2}, n :: Integer = size(origins,2))
    size(origins,1)==size(dirs,1)==3 && size(barys,1)==2 &&
    n <= min(size(origins,2),size(dirs,2),length(hits),length(dists),length(faceinds),size(barys,2)) ||
    error("input dimensions")
    Opcode.aabb_tree_intersect_mut(tree, origins, dirs, hits, dists, faceinds, barys, n)
    tree
end

function deform!(tree::AABBTree, verts::Array{<:Real,2})
    size(verts,1)==3 || error("require verts : 3 x n")
    Opcode.aabb_tree_deform(tree, verts, size(verts,2))
    tree
end

function bary2eucl!(x::Array{<:Real,2}, bary::Array{<:Real,2}, faceinds::Vector{<:Integer},
                   verts::Array{<:Real,2}, faces::Array{<:Integer})
    n = size(bary,2)
    size(bary,1)==2 || error("bary : 2 x n")
    @simd for i = 1:n
    	b2 = bary[1,i]
	b3 = bary[2,i]
        b1 = 1 - b2 - b3
        v1,v2,v3 = (faces[1,faceinds[i]], faces[2,faceinds[i]], faces[3,faceinds[i]])
        v1x,v1y,v1z = (verts[1,v1], verts[2,v1], verts[3,v1])
        v2x,v2y,v2z = (verts[1,v2], verts[2,v2], verts[3,v2])
        v3x,v3y,v3z = (verts[1,v3], verts[2,v3], verts[3,v3])
        x[1,i] = b1*v1x + b2*v2x + b3*v3x
        x[2,i] = b1*v1y + b2*v2y + b3*v3y
        x[3,i] = b1*v1z + b2*v2z + b3*v3z
    end
    x
end
function bary2eucl!(bary::Array{T,2}, faceinds::Vector{<:Integer},
                    verts::Array{<:Real,2}, faces::Array{<:Integer}) where {T<:Real}
    x = Array{T,2}(3,n)
    bary2eucl(x,bary,faceinds,verts,faces)
end

end # module


@testset "Analytic" begin
    seed!(42)
    D = 10
    N = 20
    b = 5
    x = [rand(D) for i in 1:D]
    y = rand(N)
    i = Analytic()
    @test Analytic(ϵ = 0.0001f0) isa Analytic{Float32}
    @test repr(i) == "Analytic Inference"
    xview = view(x, :)
    yview = view(y, :)
    i = AGP.init_inference(i, N, xview, yview)

    @test AGP.xview(i) == view(x, :)
    @test AGP.yview(i) == view(y, :)

    @test AGP.nMinibatch(i) == N
    @test AGP.getρ(i) == 1.0
    @test AGP.isStochastic(i) == false
    @test AGP.MBIndices(i) == 1:N
end


struct AggressivenessPOWNodeBelief{P<:MLPOMDP}
    model::P
    b::AggressivenessBelief
    r::Float64
end

rand(rng::AbstractRNG, b::AggressivenessPOWNodeBelief) = (rand(rng, b.b), b.r)

struct AggressivenessPOWFilter
    params::WeightUpdateParams
end

POMCPOW.belief_type(::Type{AggressivenessPOWFilter}, ::Type{P}) where P<:MLPOMDP = AggressivenessPOWNodeBelief{P}

function POMCPOW.init_node_sr_belief(f::AggressivenessPOWFilter, p::MLPOMDP, s, a, sp, o, r)
    particles = [Vector{Float64}() for c in o.cars]
    cweights = [Vector{Float64}() for c in o.cars]
    gen = CorrelatedIDMMOBIL(p.dmodel.behaviors)
    maybe_push_one!(particles, cweights, f.params, p.dmodel.phys_param, gen, sp, o)
    ab = AggressivenessBelief(gen, o, particles, cweights)
    return AggressivenessPOWNodeBelief(p, ab, r)
end

function POMCPOW.push_weighted!(b::AggressivenessPOWNodeBelief, f::AggressivenessPOWFilter, s, sp, r)
    maybe_push_one!(b.b.particles, b.b.cweights, f.params, b.model.dmodel.phys_param, b.b.gen, sp, b.b.physical)
end

actions(p::MLPOMDP, b::AggressivenessPOWNodeBelief) = actions(p, b.b.physical)

function actions(p::MLPOMDP, h::POWTreeObsNode)
    if isroot(h)
        return actions(p, belief(h))
    else
        return actions(p, sr_belief(h))
    end
end

struct BehaviorPOWNodeBelief{P<:MLPOMDP,G}
    model::P
    b::BehaviorParticleBelief{G}
    r::Float64
end

rand(rng::AbstractRNG, b::BehaviorPOWNodeBelief) = (rand(rng, b.b), b.r)

struct BehaviorPOWFilter
    params::WeightUpdateParams
end

POMCPOW.belief_type(::Type{BehaviorPOWFilter}, ::Type{P}) where P<:MLPOMDP = BehaviorPOWNodeBelief{P, gen_type(P)}

function POMCPOW.init_node_sr_belief(f::BehaviorPOWFilter, p::MLPOMDP, s, a, sp, o, r)
    particles = [Vector{IDMMOBILBehavior}() for c in o.cars]
    cweights = [Vector{Float64}() for c in o.cars]
    gen = p.dmodel.behaviors
    maybe_push_one!(particles, cweights, f.params, p.dmodel.phys_param, gen, sp, o)
    ab = BehaviorParticleBelief(gen, o, particles, cweights)
    return BehaviorPOWNodeBelief(p, ab, r)
end

function POMCPOW.push_weighted!(b::BehaviorPOWNodeBelief, f::BehaviorPOWFilter, s, sp, r)
    maybe_push_one!(b.b.particles, b.b.cweights, f.params, b.model.dmodel.phys_param, b.b.gen, sp, b.b.physical)
end

actions(p::MLPOMDP, b::BehaviorPOWNodeBelief) = actions(p, b.b.physical)


"""
`module  JAC.InternalConversion`
    ... a submodel of JAC that contains all methods for computing photo-excitation-autoionization cross sections and
        rates.
"""
module InternalConversion

    using Printf, ..AutoIonization, ..Basics, ..ManyElectron, ..Radial, ..PhotoEmission, ..TableStrings

    """
    `struct  InternalConversion.Settings`
        ... defines a type for the details and parameters of computing photon-impact excitation-autoionization
            pathways |i(N)>  --> |m(N)>  --> |f(N-1)>.

        + multipoles              ::Array{EmMultipole,1}               ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge,1}                  ... Specifies the gauges to be included into the computations.
        + printBefore  ::Bool                               ... True, if all energies and lines are printed before their evaluation.
        + selectPathways          ::Bool                               ... True if particular pathways are selected for the computations.
        + selectedPathways        ::Array{Tuple{Int64,Int64,Int64},1}  ... List of list of pathways, given by tupels (inital, inmediate, final).
        + maxKappa                ::Int64                              ... Maximum kappa value of partial waves to be included.
    """
    struct Settings
        multipoles                ::Array{EmMultipole,1}
        gauges                    ::Array{UseGauge,1}
        printBefore    ::Bool
        selectPathways            ::Bool
        selectedPathways          ::Array{Tuple{Int64,Int64,Int64},1}
        maxKappa                  ::Int64
    end

    """
    `InternalConversion.Settings()`  ... constructor for the default values of photon-impact excitation-autoionizaton settings.
    """
    function Settings()
        Settings( Basics.EmMultipole[], Basics.UseGauge[], false,  false, Tuple{Int64,Int64,Int64}[], 0)
    end

    # `Base.show(io::IO, settings::InternalConversion.Settings)`
    #   ... prepares a proper printout of the variable settings::InternalConversion.Settings.
    function Base.show(io::IO, settings::InternalConversion.Settings)
        println(io, "multipoles:              $(settings.multipoles)  ")
        println(io, "gauges:                  $(settings.gauges)  ")
        println(io, "printBefore:  $(settings.printBefore)  ")
        println(io, "selectPathways:          $(settings.selectPathways)  ")
        println(io, "selectedPathways:        $(settings.selectedPathways)  ")
        println(io, "maxKappa:                $(settings.maxKappa)  ")
    end

    """
    `struct  InternalConversion.Channel`
        ... defines a type for a photon-impact excitaton & autoionization channel that specifies all quantum numbers,
            phases and amplitudes.

        + excitationChannel  ::PhotoEmission.Channel     ... Channel that describes the photon-impact excitation process.
        + augerChannel       ::AutoIonization.Channel    ... Channel that describes the subsequent Auger/autoionization process.
    """
    struct  Channel
        excitationChannel    ::PhotoEmission.Channel
        augerChannel         ::AutoIonization.Channel
    end

    """
    `struct  InternalConversion.Pathway`
        ... defines a type for a photon-impact excitation pathway that may include the definition of different excitation and
            autoionization channels and their corresponding amplitudes.

        + initialLevel        ::Level                  ... initial-(state) level
        + intermediateLevel   ::Level                  ... intermediate-(state) level
        + finalLevel          ::Level                  ... final-(state) level
        + photonEnergy        ::Float64                 ... energy of the (incoming) electron
        + electronEnergy      ::Float64                 ... energy of the (finally outgoing, scattered) electron
        + crossSection        ::EmProperty              ... total cross section of this pathway
        + hasChannels         ::Bool                    ... Determines whether the individual excitation and autoionization channels are defined
                                                            in terms of their multipole, gauge, free-electron kappa, phases and the total
                                                            angular momentum/parity as well as the amplitude, or not.
        + channels            ::Array{InternalConversion.Channel,1}  ... List of channels of this pathway.
    """
    struct  Pathway
        initialLevel          ::Level
        intermediateLevel     ::Level
        finalLevel            ::Level
        photonEnergy          ::Float64
        electronEnergy        ::Float64
        crossSection          ::EmProperty
        hasChannels           ::Bool
        channels              ::Array{InternalConversion.Channel,1}
    end

    """
    `InternalConversion.Pathway()`
        ... 'empty' constructor for an photon-impact excitation-autoionization pathway between a specified initial, intermediate and
            final level.
    """
    function Pathway()
        Pathway(Level(), Level(), Level(), 0., 0., 0., false, InternalConversion.Channel[] )
    end

    # `Base.show(io::IO, pathway::InternalConversion.Pathway)`
    # ... prepares a proper printout of the variable pathway::InternalConversion.Pathway.
    function Base.show(io::IO, pathway::InternalConversion.Pathway)
        println(io, "initialLevel:               $(pathway.initialLevel)  ")
        println(io, "intermediateLevel:          $(pathway.intermediateLevel)  ")
        println(io, "finalLevel:                 $(pathway.finalLevel)  ")
        println(io, "photonEnergy                $(pathway.photonEnergy)  ")
        println(io, "electronEnergy              $(pathway.electronEnergy)  ")
        println(io, "crossSection:               $(pathway.crossSection)  ")
        println(io, "hasChannels:                $(pathway.hasChannels)  ")
        println(io, "channels:                   $(pathway.channels)  ")
    end

    """
    `InternalConversion.computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                                     settings::InternalConversion.Settings; output=true)`
        ... to compute the multiphoton transition amplitudes and all properties as requested by the given settings. A list of
            lines::Array{InternalConversion.Lines} is returned.
    """
    function  computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                           settings::InternalConversion.Settings; output=true)
        println("")
        printstyled("InternalConversion.computeLines(): The computation of internal conversion amplitudes starts now ... \n", color=:light_green)
        printstyled("---------------------------------------------------- ---------------------------------------------- \n", color=:light_green)
        println("")
        #
        pathways = "Not yet implemented !"
        #
        if    output    return( pathways )
        else            return( nothing )
        end
    end

end # module