using Makie
using StaticArrays
using Printf

room_count = [0]  # global
v = Vector{Int64}()  # global

struct NODE
    dir_pair::String
    x::Union{Nothing,Float64}
    y::Union{Nothing,Float64}
    function NODE(; x=nothing, y=nothing, dir_pair::String)
        return new(dir_pair, x, y)
    end
end
is_x(n::NODE) = !isnothing(n.x)
is_y(n::NODE) = !isnothing(n.y)

###

mutable struct Attached_Type
    room
    dir::String
    Δ_pos::Float64
    door_pos_in_child::Float64
    function Attached_Type(room, dir::String, Δ_pos::Float64, door_pos_in_child::Float64)
        return new(room, dir, Δ_pos, door_pos_in_child)
    end
end

###

mutable struct Room
    id::Int64
    x::Float64
    y::Float64
    refine_method
    name::String
    attached::Vector{Attached_Type}
    nodes::Vector{NODE}
    scene_element
    is_active::Bool
    global_bounds::NTuple{4,Float64}
    is_pos_x::Bool
    is_pos_y::Bool
    function Room(x, y, refine_method, name::String)
        attached = Vector{Attached_Type}()
        nodes = Vector{NODE}()
        room_count[1] += 1
        return new(room_count[1], x, y, refine_method, name, attached, nodes, nothing, false)
    end
end

get_Δx(r::Room) = r.global_bounds[2] -  r.global_bounds[1]
get_Δy(r::Room) = r.global_bounds[4] -  r.global_bounds[3]
get_x0(r::Room) = r.global_bounds[1]
get_x1(r::Room) = r.global_bounds[2]
get_y0(r::Room) = r.global_bounds[3]
get_y1(r::Room) = r.global_bounds[4]

get_nx(r::Room) = get_nx(r.refine_method)
get_ny(r::Room) = get_ny(r.refine_method)

function search_for_x_constraint(r::Room)
    nodes = r.nodes
    for node_k = nodes
        is_x(node_k) && (return node_k.x)
    end
    return nothing
end
function search_for_y_constraint(r::Room)
    nodes = r.nodes
    for node_k = nodes
        is_y(node_k) && (return node_k.y)
    end
    return nothing
end

is_con_y(r::Room) = !isnothing(search_for_y_constraint(r))
is_con_x(r::Room) = !isnothing(search_for_x_constraint(r))

###

abstract type RefineType end

mutable struct RefineHoriz <: RefineType
    n::Int64
    function RefineHoriz()
        return new(1)
    end
end
mutable struct RefineVert <: RefineType
    n::Int64
    function RefineVert()
        return new(1)
    end
end
mutable struct RefineRectUR <: RefineType
    nx::Int64
    ny::Int64
    function RefineRectUR()
        return new(1, 1)
    end
end
mutable struct RefineRectDR <: RefineType
    nx::Int64
    ny::Int64
    function RefineRectDR()
        return new(1, 1)
    end
end
mutable struct RefineTri <: RefineType end

refine_x!(r::Union{RefineRectUR,RefineRectDR}) = (r.nx += 1; return nothing)
refine_y!(r::Union{RefineRectUR,RefineRectDR}) = (r.ny += 1; return nothing)
refine_x!(r::Union{RefineHoriz,RefineVert})    = (r.n += 1;  return nothing)
refine_y!(r::Union{RefineHoriz,RefineVert})    = (r.n += 1;  return nothing)

get_nx(r::Union{RefineRectUR,RefineRectDR}) = r.nx
get_ny(r::Union{RefineRectUR,RefineRectDR}) = r.ny

get_nx(r::RefineHoriz) = r.n
get_ny(r::RefineHoriz) = 1

get_nx(r::RefineVert) = 1
get_ny(r::RefineVert) = r.n

###

function add_attached!(r::Room, r_new::Room, dir::String, offset::Float64, opening::Float64)
    a = Attached_Type(r_new, dir, offset, opening)
    push!(r.attached, a)
end

plot_room!(r::Room) = plot_room!(r, 0.0, 0.0)

function plot_room!(r::Room, x0::Float64, y0::Float64)
    x1 = r.x + x0
    y1 = r.y + y0
    x = [x0, x1, x1, x0, x0]
    y = [y0, y0, y1, y1, y0]
    plot!(x, y, color="black")
    r.global_bounds = (x0, x1, y0, y1)
    for attached_k = r.attached
        x0_k, y0_k = calc_offset(r, attached_k, x0, y0)
        plot_room!(attached_k.room, x0_k, y0_k)
    end
end

function calc_offset(r::Room, a::Attached_Type, x0::Float64, y0::Float64)
    if a.dir == "r"
        x0 += r.x
        y0 += a.Δ_pos
        door_pos = y0 + a.door_pos_in_child
        push!(r.nodes,      NODE(y=door_pos, dir_pair="r"))
        push!(a.room.nodes, NODE(y=door_pos, dir_pair="l"))
    elseif a.dir == "u"
        y0 += r.y
        x0 += a.Δ_pos
        door_pos = x0 + a.door_pos_in_child
        push!(r.nodes,      NODE(x=door_pos, dir_pair="u"))
        push!(a.room.nodes, NODE(x=door_pos, dir_pair="d"))
    elseif a.dir == "d"
        y0 -= a.room.y
        x0 += a.Δ_pos
        door_pos = x0 + a.door_pos_in_child
        push!(r.nodes,      NODE(x=door_pos, dir_pair="d"))
        push!(a.room.nodes, NODE(x=door_pos, dir_pair="u"))
    elseif a.dir == "l"
        x0 -= a.room.x
        y0 += a.Δ_pos
        door_pos = y0 + a.door_pos_in_child
        push!(r.nodes,      NODE(y=door_pos, dir_pair="l"))
        push!(a.room.nodes, NODE(y=door_pos, dir_pair="r"))
    else
        error("not implemented error")
    end

    return x0, y0
end

function do_start_smart(v_con::Nothing, r::Room, n::Int64, is_x::Bool)
    V0 = ifelse(is_x, get_x0, get_y0)(r)
    V1 = ifelse(is_x, get_x1, get_y1)(r)
    v_space = (V1 - V0) / n
    v_start = V0 + v_space / 2
    return v_start, v_space, true
end

function find_con_side(r::Room, v_con::Float64, is_x::Bool)
    v_con = ifelse(is_x, search_for_x_constraint, search_for_y_constraint)(r)
    V0 = ifelse(is_x, get_x0, get_y0)(r)
    V1 = ifelse(is_x, get_x1, get_y1)(r)
    return abs(v_con - V0) > abs(v_con - V1)
end

function do_start_smart(v_con::Float64, r::Room, n::Int64, is_x::Bool)
    (n == 1) && (return v_con, 0.0, true)
    V0 = ifelse(is_x, get_x0, get_y0)(r)
    V1 = ifelse(is_x, get_x1, get_y1)(r)
    is_pos = abs(v_con - V0) < abs(v_con - V1)
    v0 = v_con
    v1 = V1 + (V0 - v0)
    v_space = (v1 - v0) / (n - 1)
    if !is_pos
        v_space *= -1.0
        v0 = v1
    end
    return v0, v_space, is_pos
end

function do_start_smart(r::Room, is_x::Bool)
    n = ifelse(is_x, get_nx, get_ny)(r)
    v_con = ifelse(is_x, search_for_x_constraint, search_for_y_constraint)(r)
    v_start, v_space, is_pos = do_start_smart(v_con, r, n, is_x)
    return v_start, v_space, n, is_pos
end

function plot_node(r::Room, ::RefineTri)
    n1 = r.nodes[1]
    n2 = r.nodes[2]

    is_y(n1) || error("not implemented")
    is_y(n2) || error("not implemented")
    (length(r.nodes) == 2) || error("not implemented")

    X0 = r.global_bounds[1]
    X1 = r.global_bounds[2]
    ΔX = X1 - X0

    x0 = X0 + ΔX * 0.25
    x1 = X0 + ΔX * 0.75

    y0 = n1.y
    y1 = n2.y
    y1_below = y1 - 2*(y1 - y0)

    x = [x0, x1, x1]
    y = [y0, y1, y1_below]

    return x, y, true, true
end

function plot_node(r::Room, ::Union{RefineRectDR,RefineVert,RefineHoriz,RefineRectUR})
    start_x, space_x, nx, is_pos_x = do_start_smart(r, true)
    start_y, space_y, ny, is_pos_y = do_start_smart(r, false)
    x = Vector{Float64}()
    y = Vector{Float64}()
    for ix = 0:(nx-1)
        for iy = 0:(ny-1)
            push!(x, start_x + space_x * ix)
            push!(y, start_y + space_y * iy)
        end
    end
    return x, y, is_pos_x, is_pos_y
end

function plot_node!(r::Room)
    x, y, is_pos_x, is_pos_y = plot_node(r, r.refine_method)
    r.is_pos_x = is_pos_x
    r.is_pos_y = is_pos_y

    # prevent remove points from previous iterations if necessary
    index_in_v = findall(v .== r.id)
    if length(index_in_v) != 0
        index = only(index_in_v)
        delete!(scene, scene[end - index + 1])
        deleteat!(v, index)
    end
    scatter!(x, y)
    pushfirst!(v, r.id)

    for attached_k = r.attached # recurse
        is_pos_x, is_pos_y = plot_node!(attached_k.room)
    end

    return is_pos_x, is_pos_y
end

###

struct Graph
    points::Vector{SVector{2,Float64}}
    edges::Vector{SVector{2,Int64}}
    function Graph()
        points = Vector{SVector{2,Float64}}()
        edges = Vector{SVector{2,Int64}}()
        return new(points, edges)
    end
end

function add_graph_room!(g::Graph, r::Room, ::RefineTri, ee_parent)
    x, y = plot_node(r, RefineTri())

    len_orig = length(g.points)

    for k = 1:3
        push!(g.points, SVector{2,Float64}(x[k], y[k]))
    end

    for k = 1:3
        push!(g.edges, SVector{2,Int64}(k, rem(k, 3) + 1) .+ len_orig)
    end

    return ExtremeInd(1, 1, 3, 2, len_orig)
end

function add_graph_room!(g::Graph, r::Room, ::Union{RefineRectDR,RefineVert,RefineHoriz,RefineRectUR}, ee_parent)
    ind_2_node(ind_y::Int64, ind_x::Int64) = ind_y + (ind_x - 1) * ny

    start_x, space_x, nx, is_reg_x = do_start_smart(r, true)
    start_y, space_y, ny, is_reg_y = do_start_smart(r, false)
    point_matrix = Matrix{SVector{2,Float64}}(undef, ny, nx)
    edge_vec = Vector{SVector{2,Int64}}()
    for ix = 1:nx
        for iy = 1:ny
            x = start_x + space_x * (ix - 1)
            y = start_y + space_y * (iy - 1)
            point_matrix[iy, ix] = SVector{2,Float64}(x, y)
            index = ind_2_node(iy, ix)
            if ix != nx
                index_right = ind_2_node(iy, ix + 1)
                push!(edge_vec, SVector{2,Int64}(index, index_right))
            end
            if iy != ny
                index_down = ind_2_node(iy + 1, ix)
                push!(edge_vec, SVector{2,Int64}(index, index_down))
            end
        end
    end

    len_orig = length(g.points)

    for point_k = point_matrix
        # point_k = SVector{2,Float64}(point_k[1] + randn() * 0.15, point_k[2] + randn() * 0.15)
        push!(g.points, point_k)
    end

    for edge_k = edge_vec
        push!(g.edges, edge_k .+ len_orig)
    end

    return ExtremeInd(ind_2_node(ny, 1), ind_2_node(1, 1), ind_2_node(ny, nx), ind_2_node(1, nx), len_orig)
end

struct ExtremeInd
    i_ul::Int64
    i_dl::Int64
    i_ur::Int64
    i_dr::Int64
    function ExtremeInd(i_ul, i_dl, i_ur, i_dr, len_orig)
        return new(i_ul + len_orig, i_dl + len_orig, i_ur + len_orig, i_dr + len_orig)
    end
end

function add_graph!(g::Graph, r::Room, a=nothing)
    ee_parent = add_graph_room!(g, r, r.refine_method, a)

    for attached_k = r.attached
        ee_child = add_graph!(g, attached_k.room, attached_k)

        if attached_k.dir == "r"
            if r.is_pos_y
                i_parent = ee_parent.i_dr
            else
                i_parent = ee_parent.i_ur
            end
            i_child = ee_child.i_ul
            push!(g.edges, SVector{2,Int64}(i_parent, i_child))
        elseif attached_k.dir == "u"
            v_con = search_for_x_constraint(attached_k.room)
            i_child = ifelse(find_con_side(attached_k.room, v_con, true), ee_child.i_dr, ee_child.i_dl)
            i_parent = ifelse(find_con_side(r, v_con, true), ee_parent.i_ur, ee_parent.i_ul)
            push!(g.edges, SVector{2,Int64}(i_parent, i_child))
        elseif attached_k.dir == "d"
            v_con = search_for_x_constraint(attached_k.room)
            i_child = ifelse(find_con_side(attached_k.room, v_con, true), ee_child.i_ur, ee_child.i_ul)
            i_parent = ifelse(find_con_side(r, v_con, true), ee_parent.i_dr, ee_parent.i_dl)
            push!(g.edges, SVector{2,Int64}(i_parent, i_child))
        elseif attached_k.dir == "l"
            v_con = search_for_y_constraint(attached_k.room)
            i_child = ifelse(find_con_side(attached_k.room, v_con, false), ee_child.i_ur, ee_child.i_dr)
            i_parent = ifelse(find_con_side(r, v_con, false), ee_parent.i_ul, ee_parent.i_dl)
            push!(g.edges, SVector{2,Int64}(i_parent, i_child))
        end

    end

    return ee_parent
end

function print_graph(g::Graph)
    println("===================")
    println()

    dx = -6.95
    dy = 31.8

    # translate graph to my coordinates
    i = 1
    for point_k = g.points
        s0 = @sprintf("%d", i)
        s1 = @sprintf("%0.5f", point_k[1] + dx)
        s2 = @sprintf("%0.5f", point_k[2] + dy)
        println(s0," ", s1, " ", s2)
        i += 1
    end
    println()
    j = 1
    for edge_k = g.edges
        println(j," ",edge_k[1], " ", edge_k[2])
        j += 1
    end
    println()
end

function plot_graph(g::Graph)

    # # prevent remove points from previous iterations if necessary
    # index_in_v = findall(v .== -9999)
    # if length(index_in_v) != 0
    #     index = only(index_in_v)
    #     delete!(scene, scene[end - index + 1])
    #     deleteat!(v, index)
    # end

    for edge_k = g.edges
        p1 = g.points[edge_k[1]]
        p2 = g.points[edge_k[2]]
        plot!([p1[1], p2[1]], [p1[2], p2[2]])
    end

    # pushfirst!(v, -9999)
end

###

function create_layout_gazebo(is_debug::Bool=false)
    width = 3.9
    wid2 = 1.95

    # cafe
    r1 = Room(10.0, 17.0, RefineRectUR(), "room_1")

    is_debug && (r1.refine_method.nx = 3)
    is_debug && (r1.refine_method.ny = 5)

    # H3
    r2 = Room(10.0, 8.3, RefineTri(), "room_2")
    add_attached!(r1, r2, "r", 10.5, 3.3)

    # H2 - Stairs
    r3 = Room(12.0, width, RefineHoriz(), "room_3")
    add_attached!(r2, r3, "r", 0.0, wid2)

    is_debug && (r3.refine_method.n = 4)

    r_last_hall = r3

    for k = 1:4

        hx = ifelse(k == 4, 9.4, 9.3)
        r4 = Room(hx, width, RefineHoriz(), "room_4")
        add_attached!(r_last_hall, r4, "r", 0.0, wid2)

        r5 = Room(7.9, 5.5, RefineRectDR(), "room_5")
        add_attached!(r4, r5, "u", 0.0, 7.45)

        r_last_hall = r4

        is_debug && (r_last_hall.refine_method.n = 2)
        is_debug && (r5.refine_method.nx = 2)
        is_debug && (r5.refine_method.ny = 2)
    end

    width = 3.4
    for k = 1:3

        h = [4.1, 3.6, 4.8]
        wd = 1.5

        # H1
        r6 = Room(width, h[k], RefineVert(), "room_6")

        Δx = ifelse(k == 1, 6.0, 0.0)
        add_attached!(r_last_hall, r6, "d", Δx, wd)

        is_debug && (r6.refine_method.n = 2)

        # rooms A-B-C
        r7 = Room(5.83, 3.0, RefineHoriz(), "room_7")

        dh = ifelse(k==3, 1.2, 0.0)
        add_attached!(r6, r7, "l", dh, 1.5)

        is_debug && (r7.refine_method.n = 3)

        r_last_hall = r6
    end

    w8 = 17.0
    r8 = Room(w8, 31.0, RefineRectUR(), "room_8")
    add_attached!(r_last_hall, r8, "d", - 13.6, 15.8)

    is_debug && (r8.refine_method.nx = 3)
    is_debug && (r8.refine_method.ny = 5)

    return r1
end

function create_layout_bloat(is_debug::Bool=false)

    width = 4.29
    wid2 = width/2

    # cafe dimensions: (12.65, 23.2)
    r1 = Room(12.65, 23.2, RefineRectUR(), "room_1")

    is_debug && (r1.refine_method.nx = 3)
    is_debug && (r1.refine_method.ny = 5)

    # Hall 3 dimensions (11.0, 9.13)
    r2 = Room(11.0, 9.13, RefineTri(), "room_2")
    # Attach cafe-h3:  16.05, 2.4
    add_attached!(r1, r2, "r", 16.05, wid2)

    # H2.S dimensions (8.58, 4.29)
    r3 = Room(8.58, width, RefineHoriz(), "room_3")
    # H2.S attach  (0.0, 2.145)
    add_attached!(r2, r3, "r", 0.0, wid2)

    is_debug && (r3.refine_method.n = 4)

    r_last_hall = r3

    # rooms G-F-E-D (11.99, 4.29) (10.45, 4.29)
    wh2 = 4.29
    wh2h = wh2/2
    for k = 1:4

        # H2.D dimensions <> H2.G, H2.F, H2.E dimensions
        hx = ifelse(k == 4, 10.45, 11.99)
        r4 = Room(hx, wh2, RefineHoriz(), "room_4")
        # Attach classrooms: (0, 2.145)
        add_attached!(r_last_hall, r4, "r", 0.0, wh2h)

        # G-D rooms dimensions (10.45, 6.05)
        r5 = Room(10.45, 6.05, RefineRectDR(), "room_5")
        # G-D attach doors (0.0, 9.2)
        add_attached!(r4, r5, "u", 0.0, 9.2)

        r_last_hall = r4

        is_debug && (r_last_hall.refine_method.n = 2)
        is_debug && (r5.refine_method.nx = 2)
        is_debug && (r5.refine_method.ny = 2)
    end

    wh1 = 3.74
    wh1h = wh1/2

    for k = 1:3

        # H1C (3.74, 4.51) , H1B (3.74, 3.96), H1A (3.74, 5.28)
        h = [4.51, 3.96, 5.28]
        r6 = Room(width, h[k], RefineVert(), "room_6")

        # Attach H1-H2 (6.71, 1.87)
        Δx = ifelse(k == 1, 6.71, 0.0)
        add_attached!(r_last_hall, r6, "d", Δx, wh1h)

        is_debug && (r6.refine_method.n = 2)

        # Rooms A-B-C dimensions (6.41, 3.3)
        r7 = Room(6.41, 3.3, RefineHoriz(), "room_7")

        # H1B-C attach (0.0, 1.8), H1-A attach (1.32, 1.8)
        dh = ifelse(k==3, 1.32, 0.0)
        add_attached!(r6, r7, "l", dh, 1.8)

        is_debug && (r7.refine_method.n = 3)

        r_last_hall = r6
    end

    w8 = 18.7
    # Gym dimensions (18.7, 34.1)
    r8 = Room(w8, 34.1, RefineRectUR(), "room_8")
    # Attach Gym (-14.96, 16.83)
    add_attached!(r_last_hall, r8, "d", - 14.96, 16.83)

    is_debug && (r8.refine_method.nx = 3)
    is_debug && (r8.refine_method.ny = 5)

    return r1
end

scene = Scene()
r1 = create_layout_bloat()
plot_room!(r1)
plot_node!(r1)

### Event-based stuff

function print_active_room(r::Room)
    if r.is_active
        println(r.name, " is active!")
    end

    for attached_k = r.attached
        print_active_room(attached_k.room)
    end
end

function set_all_room_inactive(r::Room)
    r.is_active = false
    for attached_k = r.attached
        set_all_room_inactive(attached_k.room)
    end
end

function set_active_room(r::Room, x::Float32, y::Float32)
    if (r.global_bounds[1] < x < r.global_bounds[2])
        if (r.global_bounds[3] < y < r.global_bounds[4])
            r.is_active = true
        end
    end
    for attached_k = r.attached
        set_active_room(attached_k.room, x, y)
    end
end

function refine_active!(r::Room, fun_name::Function)
    r.is_active && fun_name(r.refine_method)
    for attached_k = r.attached
        refine_active!(attached_k.room, fun_name)
    end
end

display(scene)

xlims!(-40, 75.0)
ylims!(-40, 40.0)

if true
    dir = lift(scene.events.keyboardbuttons) do but
        is_lr = ispressed(but, Keyboard.left) || ispressed(but, Keyboard.right)
        is_ud = ispressed(but, Keyboard.up) || ispressed(but, Keyboard.down)

        if is_lr
            println("in keyboard event loop x")
            refine_active!(r1, refine_x!)
            plot_node!(r1)
        end

        if is_ud
            println("in keyboard event loop y")
            refine_active!(r1, refine_y!)
            plot_node!(r1)
        end

        if ispressed(but, Keyboard.space)
            g = Graph()
            add_graph!(g, r1)
            plot_graph(g)
            print_graph(g)
        end
        return 0
    end

    pos = lift(scene.events.mouseposition) do mpos
        # println("HIT")

        x0, x1 = mouseposition(scene)

        set_all_room_inactive(r1)
        set_active_room(r1, x0, x1)
        return 0
    end
end

println()
println()
println("DONE!!!")


using DataDeps

register(DataDep(
  "climate-model-simulation-crashes",
  "http://archive.ics.uci.edu/ml/datasets/Climate+Model+Simulation+Crashes",
  "http://archive.ics.uci.edu/ml/machine-learning-databases/00252/pop_failures.dat",
  "8a892cbb056072e84749aed7409d6f17c7b4a1c899fc98ce52dc2af8b727ff9c",
  post_fetch_method=(path -> begin
    # The whitespace in this file is weird. Read/write to normalize whitespace
    writedlm(path, readdlm(path))

    UCIData.process_dataset(path,
        target_index=21,
        feature_indices=3:20,
        id_indices=1:2,
        delim='\t',
        skipto=2,
    )
  end),
))


# This file was generated, do not modify it. # hide
p1 = :(model.evo_tree_classifier.η)
p2 = :(model.evo_tree_classifier.max_depth)

r1 = range(iterated_pipe, p1, lower=-2, upper=-0.5, scale=x->10^x)
r2 = range(iterated_pipe, p2, lower=2, upper=6)


"""
A basic implementation of the order-based algorithm for queues with unobservable service.
"""
module UnobservableQueue

using SimJulia, ResumableFunctions
using Distributions, DataFrames, StatsBase

export
    Paraminf,
    infer,
    inferLCFS

include("queue.jl")
include("estimators.jl")
include("inference.jl")
include("lcfs.jl")

end


Base.string(x::XFloat16) = string(Float16(reinterpret(Float32, x)))
Base.string(x::XFloat32) = string(Float32(reinterpret(Float64, x)))

Base.show(io::IO, x::XFloat16) = print(io, Float16(reinterpret(Float32, x)))
Base.show(io::IO, x::XFloat32) = print(io, Float32(reinterpret(Float64, x)))

Base.tryparse(::Type{XFloat16}, x::String) = reinterpret(XFloat16, tryparse(Float32, x))
Base.tryparse(::Type{XFloat32}, x::String) = reinterpret(XFloat32, tryparse(Float64, x))

Printf.ini_dec(d::XFloat16, ndigits, digits) =
    Printf.ini_dec(reinterpret(Float32, d), ndigits, digits)

Printf.ini_dec(d::XFloat32, ndigits, digits) =
    Printf.ini_dec(reinterpret(Float64, d), ndigits, digits)

Base.hash(x::XFloat16) = hash(reinterpret(Float32, x))
Base.hash(x::XFloat32) = hash(reinterpret(Float64, x))
Base.hash(x::XFloat16, h::UInt) = hash(Float64(reinterpret(Float32,x)), h)
Base.hash(x::XFloat32, h::UInt) = hash(reinterpret(Float64,x), h)


using Revise
using SwitchingAnalysis
using HypothesisTests
using BrowseTables
## loading
ongoing_dir = linux_gdrive
#ongoing_dir = mac_gdrive
files_loc = joinpath(ongoing_dir,files_dir)
figs_loc = joinpath(ongoing_dir,figs_dir)
streaks =  (files_loc,"streaks.csv"); types = columns_types))
pokes =  (files_loc,"pokes.csv")))
##
union(streaks[:,:ExpDay])
gd = combine(groupby(streaks,:ExpDay)) do dd
    (day = dd[1,:Day], phase = dd[1,:Phase],
    treatment = dd[1,:Treatment], inj = dd[1,:Injection],
    PhaseDay = dd[1,:ProtocolDay])
end
open_html_table(gd)

## Adjustments

pokes[!,:Protocol] = [ismissing(x) ? missing : string(x) for x in pokes[:,:Protocol]]
filter!(r-> !ismissing(r.Protocol) &&
    r.ExpDay > 5 &&
    r.Protocol in ["0.5","0.75","1.0"] &&
    r.Trial < 60,
    pokes)

filter!(r-> !ismissing(r.Protocol) &&
    r.ExpDay >5 &&
    r.Protocol in ["0.5","0.75","1.0"] &&
    r.Trial < 60 &&
    r.Num_pokes > 1,
    streaks)
gr(size=(600,600), tick_orientation = :out, grid = false)

## Cumulative per protocol

Df = combine(groupby(streaks,:Protocol)) do dd
    ecdf(dd,:Num_pokes)
end
Protocol_colors!(Df)
@df Df plot(:Xaxis, :Mean;
    group = :Protocol,
    ribbon = :SEM,
    legend= :bottomright,
    color = :color)
savefig(joinpath(figs_loc,"CumulativePokesPerProtocol.pdf"))

## Pokes per protocol by phase

Df = combine(groupby(streaks,:Phase)) do dd
    summarize(dd,:Protocol,:Num_pokes)
end
Drug_colors!(Df)
@df Df groupedbar(:Xaxis, :Mean,
    group = :Phase,
    yerror = :SEM,
    color = :color,
    legend = false)
savefig(joinpath(figs_loc,"BarPokesPerPhase.pdf"))

## Pokes in training

control = filter(r-> r.Phase == "training", streaks)
Df = summarize(control,:Protocol,:Num_pokes)
rename!(Df,:Xaxis => :Protocol)
Protocol_colors!(Df)
sort!(Df,:Protocol)
@df Df bar(:Protocol, :Mean;
    yerror = :SEM,
    color = :color,
    yticks = 0:15,
    legend = false)
savefig(joinpath(figs_loc,"TrainingPokesPerProtocol.pdf"))

## Pokes per protocol by Treatment

Df = combine(groupby(streaks,:Treatment)) do dd
    summarize(dd,:Protocol,:Num_pokes)
end
filter!(r->!in(r.Treatment,["None","PostVehicle","Saline"]), Df)
Drug_colors!(Df)
@df Df groupedbar(:Xaxis, :Mean,
    group = :Treatment,
    yerror = :SEM,
    color = :color,
    legend = :topleft,
    ylims = (0,21))
savefig(joinpath(figs_loc,"BarPokesPerProtocolandInj.pdf"))

## PreVehicle data
control = filter(r-> r.Treatment == "PreVehicle", streaks)
Df = combine(groupby(control,:Protocol)) do dd
    ecdf(dd,:Num_pokes)
end
Protocol_colors!(Df)
@df Df plot(:Xaxis, :Mean;
    group = :Protocol,
    ribbon = :SEM,
    legend= :bottomright,
    color = :color)
savefig(joinpath(figs_loc,"CumulativePreVeh.pdf"))
Df = summarize(control,:Protocol,:Num_pokes)
rename!(Df,:Xaxis => :Protocol)
Protocol_colors!(Df)
sort!(Df,:Protocol)
@df Df bar(:Protocol, :Mean;
    yerror = :SEM,
    color = :color,
    yticks = 0:15,
    legend = false)
savefig(joinpath(figs_loc,"PreVehPokesPerProtocol.pdf"))

## Preward at leaving

streaks


using HDF5, JLD
using PyPlot
using FITSIO

###############################
########## Rec Image ##########
file = string("/Users/deguignet/Documents/Julia/imagerec_400ite_woshar.jld")
x = load(file, "admmst.x");
x = x[1024-512:1024+512-1,1024-512:1024+512-1,:];

file2 = string("/Users/deguignet/Documents/Julia/imagerec_700ite.jld")
x2 = load(file2, "admmst.x");
x2 = x2[1024-512:1024+512-1,1024-512:1024+512-1,:];

###############################
####### Original object #######
skyfile = string("/Users/deguignet/Documents/Julia/I_HALO_CL_RS_SKY.FITS")
f = FITS(skyfile)
sky = read(f[1]);
close(f)

###############################
############ Dirty ############
dirtyfile = string("/Users/deguignet/Documents/Julia/example_sim_dirty.fits")
f = FITS(dirtyfile)
mydata = read(f[1]);
close(f)

###############################
############# PSF #############
psffile = string("/Users/deguignet/Documents/Julia/example_sim_psf.fits")
f = FITS(psffile)
psf = read(f[1]);
close(f)

###############################
nxy  = size(x)[1]
lastiter = load(file, "algost.lastiter")
nfreq = load(file, "algost.nfreq")
tol1 = load(file, "toolst.tol1")
tol2 = load(file, "toolst.tol2")
tol3 = load(file, "toolst.tol3")
tol4 = load(file, "toolst.tol4")
tol5 = load(file, "toolst.tol5")
nu = load(file, "psfst.nu")
nu0 = load(file, "psfst.nu0")

sumsky2 = Array(Float64,nfreq)
for z in 1:nfreq
    sumsky2[z] = sum(sky[:,:,z].^2)
end
###############################

###############################
############ RMSE #############
# rmse = Array(Float64,nfreq)
# for z in 1:nfreq
#     rmse[z] = sqrt(sum( (x[:,:,z] - sky[:,:,z]).^2)/sumsky2[z])
# end
#
#
# figure(1)
# clf()
# plot(nu,rmse)
##########################
#
# err = Array(Float64,nxy,nxy,nfreq)
# for z in 1:nfreq
#     err[:,:,z] = (sky[:,:,z] - x[:,:,z]).^2./(sky[:,:,z].^2)
# end

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

##########################
#
# figure(2)
# clf()
# for z in 1:nfreq
#     subplot(6,6,z)
#     a = nu[z]/1e9
#     imshow(x[:,:,z],cmap=ColorMap("ocean"))
#     axis("off")
#     title("v = $a GHz")
#
# end

#############################################################################
#############################################################################
######################################  sky / dirty / rec_obj / error
# figure(3)
# clf()
# title("multiplot")
# e = 1
# for z in [8 16 24 32]
#     subplot(4,4,e)
#     axis("off")
#     a = nu[z]/1e9
#     imshow((abs(sky[:,:,z]).^0.25),cmap=ColorMap("spectral"))
#     title(string(L"$\nu \, = \,$","$a ", L"\, GHz"),fontsize=14)
#     e += 1
# end
# subplots_adjust(bottom=0.1, right=0.8, top=0.9)
# cax = axes([0.85, 0.3, 0.025, 0.6])
# colorbar(cax=cax)
# e = 5
# for z in [8 16 24 32]
#     subplot(4,4,e)
#     axis("off")
#     imshow((mydata[:,:,z]),cmap=ColorMap("spectral"))
#     e += 1
# end
# subplots_adjust(bottom=0.1, right=0.8, top=0.9)
# cax = axes([0.85, 0.3, 0.025, 0.6])
# colorbar(cax=cax)
#
# e = 9
# for z in [8 16 24 32]
#     subplot(4,4,e)
#     axis("off")
#     imshow((abs(x[:,:,z])/maximum(x[:,:,z])*maximum(sky[:,:,z])).^0.5,cmap=ColorMap("spectral"))
#     e += 1
# end
#
# subplots_adjust(bottom=0.1, right=0.8, top=0.9)
# cax = axes([0.85, 0.3, 0.025, 0.6])
# colorbar(cax=cax)
#
#
# e = 13
# for z in [8 16 24 32]
#     subplot(4,4,e)
#     axis("off")
#     imshow((abs(sky[:,:,z]-x[:,:,z])/(sum(sky[:,:,z]))).^0.25,cmap=ColorMap("spectral"))
#     e += 1
# end
#
# subplots_adjust(bottom=0.1, right=0.8, top=0.9)
# cax = axes([0.85, 0.1, 0.025, 0.17])
# colorbar(cax=cax)
#############################################################################
#############################################################################

# ################# PSF
# figure(4)
# clf()
# e=1
# for z in [8 16 24 32]
#
#     subplot(2,2,e)
#     axis("off")
#     a = nu[z]/1e9
#     imshow((abs(psf[:,:,z]).^0.25),cmap=ColorMap("spectral"))
#         title(string(L"$\nu \, = \,$","$a ", L"\, GHz"),fontsize=14)
#     e+=1
# end
#
# ################# Object
# figure(5)
# clf()
# e=1
# for z in [8 16 24 32]
#
#     subplot(2,2,e)
#     axis("off")
#     a = nu[z]/1e9
#     imshow((abs(sky[:,:,z]).^0.25),cmap=ColorMap("spectral"))
#         title(string(L"$\nu \, = \,$","$a ", L"\, GHz"),fontsize=14)
#     e+=1
# end

##################################
##################################
figure(6)
clf()
subplot(2,3,1)
    axis("off")
    colorbar(imshow((abs(x[:,:,1]).^0.25),cmap=ColorMap("spectral")))
    title("400 iterations w/o shaar")
    ylabel("puissance 1/4")
subplot(2,3,2)
    axis("off")
    colorbar(imshow((abs(x2[:,:,1]).^0.25),cmap=ColorMap("spectral")))
    title("700 iterations")
subplot(2,3,3)
    axis("off")
    colorbar(imshow((abs(sky[:,:,1]).^0.25),cmap=ColorMap("spectral")))
    title("original object")

    subplot(2,3,4)
        axis("off")
        colorbar(imshow((abs(x[:,:,1])),cmap=ColorMap("spectral")))
        title("400 iterations w/o shaar")
        ylabel("puissance 1")

    subplot(2,3,5)
        axis("off")
        colorbar(imshow((abs(x2[:,:,1])),cmap=ColorMap("spectral")))
        title("700 iterations")
    subplot(2,3,6)
        axis("off")
        colorbar(imshow((abs(sky[:,:,1])),cmap=ColorMap("spectral")))
        title("original object")

# subplot(1,3,1)
#     axis("off")
#     imshow(((x[:,:,1]).^1),cmap=ColorMap("spectral"))
# subplot(1,3,2)
#     axis("off")
#     imshow(((x2[:,:,1]).^1),cmap=ColorMap("spectral"))
# subplot(1,3,3)
#     axis("off")
#     imshow(((sky[:,:,1]).^1),cmap=ColorMap("spectral"))

##################################
##################################
# figure(7)
# clf()
# xt = linspace(1,lastiter,lastiter)
# plot(xt,log(tol2))
# plot(xt,log(tol3))
# plot(xt,log(tol4))
# plot(xt,log(tol5))

##################################
##################################
figure(6)
clf()
subplot(1,2,1)
    axis("off")
    (imshow((abs(x[:,:,1]).^0.25),cmap=ColorMap("spectral"),vmax=0.1))

subplot(1,2,2)
    axis("off")
    (imshow((abs(sky[:,:,1]).^0.25),cmap=ColorMap("spectral"),vmax=0.12))

    # subplots_adjust(bottom=0.1, right=0.8, top=0.9)
    subplots_adjust(bottom=0.1, right=0.8, top=1.1)
    cax = axes([0.85, 0.3, 0.025, 0.6])
    colorbar(cax=cax)


#!/usr/bin/env julia --startup-file=no
# Script for converting BATSRUS unstructured binary outputs to VTK formats.
# Example usage:
# julia -p 8 convert2vtk.jl
#
# Currently the relative location of the output files to be processed is "IO2".
#
# Hongyang Zhou, hyzhou@umich.edu

using Distributed
using Pkg
if "VisAna" ∈ keys(Pkg.installed())
   @everywhere using Batsrus
else
   @warn "Batsrus.jl not installed. Install the package by `Pkg.add("Batsrus")`"
end
@everywhere using Glob

dir = "IO2"

# Concatenate files
# Before deleting the .T
# you need to make sure all the .tec files have been concatenated!
headers = glob("*.T", dir)
@sync @distributed for header in headers
   name = basename(header)[1:end-2]
   println("header=$(header)")
   nameIn = glob(name*"*.tec", dir)
   nameOut= joinpath(dir, name*".dat")
   run(pipeline(`cat $(nameIn)`, nameOut))
   rm(header)
   rm.(nameIn)
end

filenamesIn = "*.dat"
filenames = Vector{String}(undef,0)
filesfound = glob(filenamesIn, dir)
filenames = vcat(filenames, filesfound)
# Do not work on files that have already been converted
# This won't work if new files are generated with exactly the same name!
filenames = [fname for fname in filenames if ~isfile(fname[1:end-3]*"vtu")]

@sync @distributed for outname in filenames
   println("filename=$(outname)")
   head, data, connectivity = readtecdata(outname)
   convertVTK(head, data, connectivity, outname[1:end-4])
end

# Choose whether or not to delete the original *.dat files.
#datFiles = glob("*.dat", dir)
# Parallel:
#@sync @distributed for file in datFiles
#   rm(file)
#end
# Serial (might be faster if I/O bound):
#rm.(datfiles)


@testset "Test dictionary time period conversion" begin
    faketimes1 = Dict("one" => Dates.Week(40), "two" => Dates.Day(6))
    faketimes2 = Dict("one" => Dates.Minute(40), "two" => Dates.Minute(6))
    faketimes3 = Dict("one" => Dates.Millisecond(3), "two" => Dates.Second(4), "three" => Dates.Minute(40), "four" => Dates.Hour(3))
    convert1 = IS.time_period_conversion(faketimes1)
    convert2 = IS.time_period_conversion(faketimes2)
    convert3 = IS.time_period_conversion(faketimes3)
    for dict in [convert1, convert2, convert3]
        for (k, v) in dict
            @test typeof(v) == Dates.Millisecond
        end
    end
end

@testset "Test time period conversion" begin
    fake1 = IS.time_period_conversion(Dates.Day(2))
    fake2 = IS.time_period_conversion(Dates.Hour(2))
    fake3 = IS.time_period_conversion(Dates.Minute(2))
    fake4 = IS.time_period_conversion(Dates.Second(2))
    fake5 = IS.time_period_conversion(Dates.Millisecond(2))
    fake_times = [fake1, fake2, fake3, fake4, fake5]
    for time_period in fake_times
        @test typeof(time_period) == Dates.Millisecond
    end
end


const ContactImplicitMPC = Main
include(joinpath(@__DIR__, "..", "src/dynamics", "hopper_3D", "visuals.jl"))
T = Float64
vis = Visualizer()
open(vis)

# get hopper model
s_sim = get_simulation("hopper_3D", "sine2_3D_lc", "sinusoidal")
model_sim = s_sim.model
env_sim = s_sim.env

s = get_simulation("hopper_3D", "flat_3D_lc", "flat")
model = s.model
env = s.env

nq = model.dim.q
nu = model.dim.u
nc = model.dim.c

# get trajectory
# ref_traj = deepcopy(get_trajectory(s.model, s.env,
#     joinpath(module_dir(), "src/dynamics/hopper_3D/gaits/gait_in_place.jld2"),
#     load_type = :joint_traj))
ref_traj = deepcopy(get_trajectory(s.model, s.env,
    joinpath(module_dir(), "src/dynamics/hopper_3D/gaits/gait_forward.jld2"),
    load_type = :joint_traj))

# time
H = ref_traj.H
h = ref_traj.h
N_sample = 10
H_mpc = 20
h_sim = h / N_sample
H_sim = 1200 # 12000

# barrier parameter
κ_mpc = 1.0e-4

obj = TrackingObjective(model, env, H_mpc,
    q = [Diagonal(1.0e-1 * [3,3,0.1,5e+1,5e+1,5e+1,10])   for t = 1:H_mpc],
    u = [Diagonal(1.0e-0 * [1e-1, 1e-1, 1e1]) for t = 1:H_mpc],
    γ = [Diagonal(1.0e-100 * ones(model.dim.c)) for t = 1:H_mpc],
    b = [Diagonal(1.0e-100 * ones(model.dim.c * friction_dim(env))) for t = 1:H_mpc])

# obj = TrackingObjective(model, env, H_mpc,
#     q = [Diagonal(1.0e-1 * [1,1,0.1,5e+1,5e+1,5e+1,10])   for t = 1:H_mpc],
#     u = [Diagonal(1.0e-0 * [1e-1, 1e-1, 1e1]) for t = 1:H_mpc],
#     γ = [Diagonal(1.0e-100 * ones(model.dim.c)) for t = 1:H_mpc],
#     b = [Diagonal(1.0e-100 * ones(model.dim.c * friction_dim(env))) for t = 1:H_mpc])

p = linearized_mpc_policy(ref_traj, s, obj,
    H_mpc = H_mpc,
    N_sample = N_sample,
    κ_mpc = κ_mpc,
    n_opts = NewtonOptions(
        r_tol = 3e-4,
        max_iter = 5),
    mpc_opts = LinearizedMPCOptions(
        # altitude_update = false,
        altitude_update = true,
        altitude_impact_threshold = 0.05,
        altitude_verbose = true,
        # live_plotting = true,
        )
    )

p = linearized_mpc_policy(ref_traj, s, obj,
    H_mpc = H_mpc,
    N_sample = N_sample,
    κ_mpc = κ_mpc,
	# mode = :configurationforce,
	mode = :configuration,
	ip_type = :mehrotra,
    n_opts = NewtonOptions(
		r_tol = 3e-4,
		max_iter = 5,
		max_time = ref_traj.h, # HARD REAL TIME
		),
    mpc_opts = LinearizedMPCOptions(
        # live_plotting=true,
        altitude_update = true,
        altitude_impact_threshold = 0.05,
        altitude_verbose = true,
        ),
	ip_opts = InteriorPointOptions(
		max_iter = 100,
		verbose = false,
		r_tol = 1.0e-4,
		κ_tol = 1.0e-4,
		diff_sol = true,
		# κ_reg = 1e-3,
		# γ_reg = 1e-1,
		solver = :empty_solver,
		),
    )

q1_ref = copy(ref_traj.q[2])
q0_ref = copy(ref_traj.q[1])
q1_sim = SVector{model.dim.q}(q1_ref)
q0_sim = SVector{model.dim.q}(copy(q1_sim - (q1_ref - q0_ref) / N_sample))
@assert norm((q1_sim - q0_sim) / h_sim - (q1_ref - q0_ref) / h) < 1.0e-8

sim = simulator(s_sim, q0_sim, q1_sim, h_sim, H_sim,
    p = p,
    ip_opts = InteriorPointOptions(
        r_tol = 1.0e-8,
        κ_init = 1.0e-8,
        κ_tol = 2.0e-8),
    sim_opts = SimulatorOptions(warmstart = true))

telap = @elapsed status = ContactImplicitMPC.simulate!(sim, verbose = true)
# @profiler status = ContactImplicitMPC.simulate!(sim)
H_sim * h_sim / (telap * 0.35)

plot_surface!(vis, env_sim, n=100, xlims=[-0.3, 1.5], ylims=[-1.1, 0.3])
visualize_robot!(vis, model, sim.traj)
plot_lines!(vis, model_sim, sim.traj.q[1:11000], offset=0.0)

plt = plot(layout=(3,1), legend=false)
plot!(plt[1,1], hcat(Vector.(vcat([fill(ref_traj.q[i], N_sample) for i=1:H]...))...)',
    color=:red, linewidth=3.0)
plot!(plt[1,1], hcat(Vector.(sim.traj.q)...)', color=:blue, linewidth=1.0)
plot!(plt[2,1], hcat(Vector.(vcat([fill(ref_traj.u[i][1:nu], N_sample) for i=1:H]...))...)',
    color=:red, linewidth=3.0)
plot!(plt[2,1], hcat(Vector.([u[1:nu] for u in sim.traj.u]*N_sample)...)', color=:blue, linewidth=1.0)
plot!(plt[3,1], hcat(Vector.([γ[1:nc] for γ in sim.traj.γ]*N_sample)...)', color=:blue, linewidth=1.0)

visualize_robot!(vis, model, sim.traj, sample=10)

# filename = "hopper_3d_sine"
# MeshCat.convert_frames_to_video(
#     "/home/simon/Downloads/$filename.tar",
#     "/home/simon/Documents/$filename.mp4", overwrite=true)
#
# convert_video_to_gif(
#     "/home/simon/Documents/$filename.mp4",
#     "/home/simon/Documents/$filename.gif", overwrite=true)


__precompile__()

module Gridap

using Reexport

include("Utils/files.jl")

include("CellValues/files.jl")

include("Fields/files.jl")

include("RefFEs/files.jl")

include("Integration/files.jl")

include("Geometry/files.jl")

include("Algebra/files.jl")

include("FESpaces/files.jl")

include("MultiField/files.jl")

include("Visualization/files.jl")

end # module


using CUDA: i32

group = addgroup!(SUITE, "kernel")

dummy_kernel() = nothing
group["launch"] = @benchmarkable @cuda dummy_kernel()

wanted_threads = 10000
group["occupancy"] = @benchmarkable begin
    kernel = @cuda launch=false dummy_kernel()
    config = launch_configuration(kernel.fun)
    threads = min($wanted_threads, config.threads)
    blocks = cld($wanted_threads, threads)
end

src = CUDA.rand(Float32, 512, 1000)
dest = similar(src)
function indexing_kernel(dest, src)
    i = (blockIdx().x-1i32) * blockDim().x + threadIdx().x
    @inbounds dest[i] = src[i]
    return
end
group["indexing"] = @async_benchmarkable @cuda threads=size(src,1) blocks=size(src,2) $indexing_kernel($dest, $src)

function checked_indexing_kernel(dest, src)
    i = (blockIdx().x-1i32) * blockDim().x + threadIdx().x
    dest[i] = src[i]
    return
end
group["indexing_checked"] = @async_benchmarkable @cuda threads=size(src,1) blocks=size(src,2) $checked_indexing_kernel($dest, $src)

function rand_kernel(dest::AbstractArray{T}) where {T}
    i = (blockIdx().x-1i32) * blockDim().x + threadIdx().x
    dest[i] = rand(T)
    return
end
group["rand"] = @async_benchmarkable @cuda threads=size(src,1) blocks=size(src,2) $rand_kernel($dest)


module Gallery

using CellwiseValues
using Base: @propagate_inbounds, @pure

export CellValueFromArray
import Base: iterate
import Base: length
import Base: eltype
import Base: size
import Base: getindex
import Base: IndexStyle

struct CellValueFromArray{T,N,V<:AbstractArray{T,N}} <: IndexCellValue{T,N}
  v::V
end

@propagate_inbounds function getindex(
  self::CellValueFromArray{T,N},I::Vararg{Int,N}) where {T,N}
  @inbounds self.v[I...]
end

size(self::CellValueFromArray) = size(self.v)

@pure IndexStyle(::Type{CellValueFromArray{T,N,V}}) where {T,N,V} = IndexStyle(V)

end # module Gallery


"Loads csv with timeslots formatted as in 'test/Sample data/timeslottest_output.csv' "
function loadtimeslots(path)
    colparser = Dict(:startTime => dateformat"H:M:S",:endTime => dateformat"H:M:S")
    res = loadtable(path,colparsers=colparser)|> @map((resourceID = _.resourceID, type= _.type,dayID = _.dayID,timeslotID = _.timeslotID,startTime = Time(_.startTime),endTime= Time(_.endTime),booked = _.booked == "true"  )) |> collect
    return table(res,pkey = [:resourceID,:dayID,:timeslotID])
end

"Saves any object to a CSV file"
function save(object,path::String)
    CSVFiles.save(path, object)
end

function loadresources(path)
    loadtable(path,indexcols= [:intID])
end
"Loads csv with patientdata formatted as in 'test/Sample data/patienttest_output.csv' "
function loadpatients(path)
        loadtable(path,indexcols= [:intID])
end
"Loads csv with \"time delta\" which is the number of days that need to be in between two types of visits. Data should be formatted as in 'test/Sample data/GUCHamb_timeDelta.csv' "
function loadTimeDelta(path_TimeDelta::String)
    loadndsparse(path_TimeDelta,indexcols=1:2)
end

"Loads csv with visitdata formatted as in 'test/Sample data/visittest_output.csv' "
function loadvisits(path)
    loadtable(path,indexcols= [:intID])
end

"Loads csv with a solution formatted as in 'test/Sample data/solutiontest_input.csv' "
function loadsolution(path)
    loadtable(path;indexcols=[:visitID])
end


module SeaMass

import HDF5
import LibExpat

export MzmlbSpectrum, SmbSpectrum

type MzmlbSpectrum
  mzs
  intensities
end

function MzmlbSpectrum(filename::AbstractString, spectrumID::Number=1)
  HDF5.h5open(filename, "r") do file
    spectrumIndex = file["mzML_spectrumIndex"][spectrumID:spectrumID+1] + 1

    mzML = LibExpat.xp_parse(String(file["mzML"][spectrumIndex[1]:spectrumIndex[2]-1]))

    arrayLength = parse(Int, mzML["@defaultArrayLength"][1])
    if (arrayLength > 0)
      mzsDataset = mzML["binaryDataArrayList/binaryDataArray/cvParam[@accession='MS:1000514']/../binary"]["@externalDataset"][1]
      mzsOffset = parse(Int, mzML["binaryDataArrayList/binaryDataArray/cvParam[@accession='MS:1000514']/../binary"]["@offset"][1]) + 1
      intensitiesDataset = mzML["binaryDataArrayList/binaryDataArray/cvParam[@accession='MS:1000515']/../binary"]["@externalDataset"][1]
      intensitiesOffset = parse(Int, mzML["binaryDataArrayList/binaryDataArray/cvParam[@accession='MS:1000515']/../binary"]["@offset"][1]) + 1

      MzmlbSpectrum(
        file[mzsDataset][mzsOffset:mzsOffset+arrayLength-1],
        file[intensitiesDataset][intensitiesOffset:intensitiesOffset+arrayLength-1]
      )
    else
      MzmlbSpectrum(Float64[], Float32[])
    end
  end
end

type SmbSpectrum
  locations
  counts
  exposure
end

function SmbSpectrum(filename::AbstractString, spectrumID::Number=1)
  HDF5.h5open(filename, "r") do file
    if (HDF5.has(file,"countsIndex"))
      countsIndex = file["countsIndex"][spectrumID:spectrumID+1] + 1
    else
      countsIndex = [1 size(file["counts"])[1]+1]
    end

    if (countsIndex[2] - countsIndex[1] > 0)
      if (HDF5.has(file,"binLocations"))
        locations = file["binLocations"][countsIndex[1]+spectrumID-1:countsIndex[2]+spectrumID-1]
      else
        if(HDF5.has(file,"sampleLocations"))
          locations = file["sampleLocations"][countsIndex[1]:countsIndex[2]-1]
        else
          locations = file["centroidLocations"][countsIndex[1]:countsIndex[2]-1]
        end
      end

      if (HDF5.has(file,"exposures"))
        exposures = file["exposures"][spectrumID][1]
      else
        exposures = 1.0
      end

      SmbSpectrum(
        locations,
        file["counts"][countsIndex[1]:countsIndex[2]-1],
        exposures
      )
    else
       SmbSpectrum(Float64[], Float32[], Float32[])
    end
  end
end

end # module


#= = # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Dst Index
#
#   World Data Center for Geomagnetism
#
#   For more information, see:
#
#       http://dx.doi.org/10.17593/14515-74000
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # = =#

################################################################################
#                       Private Structures and Variables
################################################################################

"""
_Dst_Structure

Structure to store the interpolations of the data in Dst files.

# Fields

* `Dst`: Dst index.

"""
struct _Dst_Structure
    Dst::_space_indices_itp_constant{SVector{24,Float64}}
end

# Remote files: *.html (prefered) or for.request (some months are missed)
#
# This set will be configured in the function `init_space_indices()`.
_dstfiles = RemoteFileSet(".dst files",
                    Dict{Symbol,RemoteFile}(),
                )

# Optional variable that will store the WDC data.
@OptionalData(_dst_data, _Dst_Structure,
          "Run `init_space_indices()` with `:dstfiles` in `enabled_files` array to initialize required data.")

################################################################################
#                               Public Functions
################################################################################

export get_Dst

#                                   Getters
# ==============================================================================

"""
get_Dst(DT::DateTime)

Return the Kp index at DateTime `DT`.

"""
function get_Dst(DT::DateTime)
    # @show DT
    Dst_day = get(_dst_data).Dst(datetime2julian(DT))
    # @show Kp_day
    # Get the hour of the day and return the appropriate Kp.
    # y, m, d, h, min, sec = JDtoDate(DT)
    # @show  floor(Int, h / 3)
    # @show Kp_day[ floor(Int, hour(DT) / 3) + 1 ]
    return Dst_day[  hour(DT) + 1 ]
end

################################################################################
#                              Private Functions
################################################################################

"""
_init_dstfiles(;force_download = false, local_dir = nothing, dstfiles_oldest_year = year(now())-3)

Initialize the data in the WDC files by creating `_dstfiles_data`. The
initialization process is composed of:

1. Download the files, if it is necessary;
2. Parse the files;
3. Create the interpolations and the structures.

If the keyword `force_download` is `true`, then the files will always be
downloaded.

The user can also specify a location for the directory with the WDC files using
the keyword `local_dir`. If it is `nothing`, which is the default, then the
file will be downloaded.

The user can select what is the oldest year in which the data will be downloaded
by the keyword `dstfiles_oldest_year`. By default, it will download the data
from 3 previous years.

The user can select what is the newest year in which the data will be downloaded
by the keyword `dstfiles_newest_year`. It it is `nothing`, which is the default,
then it is set to the current year.

"""
function _init_dstfiles(;force_download=false, local_dir=nothing,
                    dstfiles_oldest_year=year(now()) - 3,
                    dstfiles_newest_year=nothing)

    yms     = Int[]
    filepaths = String[]

    if local_dir == nothing
        local_dir = joinpath(path_si_root, "dst")
        isdir(local_dir) || mkpath(local_dir)
        (dstfiles_newest_year == nothing) && (dstfiles_newest_year = year(now()))
        _prepare_dst_remote_files(dstfiles_oldest_year, dstfiles_newest_year, local_dir)
        download(_dstfiles; force=force_download,)

        # Get the files available and sort them by the year.
        for (sym, dstfile) in _dstfiles.files
            #
            # The year must not be obtained by the data inside the file,
            # because it contains only 2 digits and will break in 2032.
            # We will obtain the year by the symbol of the remote file. The
            # symbol name is:
            #
            #       kpYYYY
            #
            # where `YYYY` is the year.
            push!(yms, parse(Int, String(sym)[4:9]))
            push!(filepaths, path(dstfile))
        end
    else
    # If the user provided a directory, check what files are available.
    # Notice that the name must be the same as the ones online.
        for (root, dirs, files) in walkdir(local_dir)
            for file in files
                if occursin(r"^dst[1-2][0-9][0-9][0-9].for.request$", file) ||
                    occursin(r"^dst[1-2][0-9][0-9][0-9].html$", file)
                    year = parse(Int, file[4:7])
                    # Check if the year is not older than the oldest year.
                    if year >= dstfiles_oldest_year
                        @info "Found Dst file `$file` related to the year `$year`."
                        push!(filepaths, joinpath(root, file))
                        push!(yms,     file[4:9])
                    end
                end
            end
        end
    end

    p = sortperm(yms)

    push!(_dst_data,       _parse_dstfiles(filepaths[p]))

    nothing
end

"""
_parse_dstfiles(filepaths::Vector{String}, years::Vector{Int})

Parse the Dst files with paths in `filepaths` related to the years in `years`.

**Notice that the files must be sorted by the year!**

"""
function _parse_dstfile_html_line(ln::SubString)
    Dst_k = zeros(Float64, 24)
    for i in 1:3
        for j in 1:8
            k = i * 8 - 8 + j
            Dst_k[k] = parse(Int, ln[ i - 1 + k * 4:i - 1 + 3 + k * 4])
        end
    end
    Dst_k
end

function _parse_dstfile_html(filepath::String,
                            DT::Vector{DateTime},
                            Dst::Vector{SVector{24,Float64}})

    bn = basename(filepath)
    y = parse(Int, bn[4:7])
    m = parse(Int, bn[8:9])

    open(filepath, "r") do file
        text = read(file, String)
        a = r"""
\n((.|\n)+?)\n
"""
        b = match(a, text)
        if b == nothing
            @warn "no match " * filepath
            return
        end

        alllines = split(b.captures[1], "\n")
        # utline = alllines[6]
        lines = alllines[8:end - 1]
        filter!(!isempty, lines)

        for ln in lines
            d   = parse(Int, ln[1:2])
            DT_k  = DateTime(y, m, d, 12, 0, 0)

            # Get the vector of Dst
            Dst_k = _parse_dstfile_html_line(ln)
            # Add data to the vector.
            push!(DT, DT_k)
            push!(Dst, Dst_k)
        end
    end

end

function _parse_dstfile_forrequest(filepath::String,
                                DT::Vector{DateTime},
                                Dst::Vector{SVector{24,Float64}})

    bn = basename(filepath)
    y  = parse(Int, bn[4:7])
    m  = parse(Int, bn[8:9])

    open(filepath, "r") do file

        for ln in eachline(file)
            # Get the Julian Day.
            d   = parse(Int, ln[9:10])

            # The DT of the data will be computed at noon. Hence, we will be
            # able to use the nearest-neighbor algorithm in the
            # interpolations.
            DT_k  = DateTime(y, m, d, 12, 0, 0)

            # Get the vector of Kps and Aps.
            Dst_k = zeros(Float64, 24)

            for i = 1:24
                Dst_k[i] = parse(Int, ln[17 + i * 4:20 + i * 4])
            end

            # Add data to the vector.
            push!(DT, DT_k)
            push!(Dst, Dst_k)
        end
    end
end

function _parse_dstfiles(filepaths::Vector{String}, )
    # Allocate the raw data.
    DT = DateTime[]
    Dst = SVector{24,Float64}[]

    for filepath in filepaths
        if endswith(filepath, "html")

            _parse_dstfile_html(filepath, DT, Dst)
        elseif endswith(filepath, "for.request")
            _parse_dstfile_forrequest(filepath, DT, Dst)
        else
            @warn "can't recognise filetype " * filepath
        end
    end

    # Create the interpolations for each parameter.
    knots    = (datetime2julian.(DT),)
    # Create the interpolations.
    itp_Dst = interpolate(knots, Dst, Gridded(Constant()))
    _Dst_Structure(itp_Dst)
end

"""
_prepare_dst_remote_files(oldest_year::Number, newest_year::Number)

Configure all the WDC remote files between `newest_year` and `oldest_year`.
Notice that previous years will never be updated whereas the current year will
be updated daily.

If `oldest_year` is greater than current year, then only the files from the
current year will be downloaded.

If `newest_year` is smaller than `oldest_year`, then only the files from the
`oldest_year` will be downloaded.

This function modifies the global variable `_wdcfiles`.

"""

function _prepare_dst_remote_files(oldest_year::Number, newest_year::Number, local_dir::String)
    # Get the current year.
    current_year = year(now())

    # If `oldest_year` is greater than current year, then consider only the
    # current year.
    (oldest_year > current_year) && (oldest_year = current_year)
    (newest_year < oldest_year)  && (newest_year = oldest_year)
    (newest_year > current_year) && (newest_year = current_year)

    # For the current year, we must update the remote file every day. Otherwise,
    # we do not need to update at all.

    # http://wdc.kugi.kyoto-u.ac.jp/dst_final/199412/index.html
    baseurl = "http://wdc.kugi.kyoto-u.ac.jp/"
    # end_time => dst type
    url_prefix = [
            DateTime(2014, 12, 31, 23, 59, 59) => "dst_final",
            DateTime(2017, 12, 31, 23, 59, 59) => "dst_provisional",
            now() => "dst_realtime",
        ]

    yms = Date(oldest_year):Month(1):Date(newest_year + 1) - Day(1)
    start_index = 1
    for ym in yms
        ymstr = @sprintf("%04d%02d",year(ym),month(ym))
        forrequest = string("dst",
                        @sprintf("%02d%02d",
                                    year(ym) % 100,
                                    month(ym)),
                        ".for.request",
                )
        httpindex = "index.html"
        # use html (for.request files are missed in some months)
        fileremote = httpindex

        filename = string("dst", ymstr, ".html")
        sym = Symbol(filename)
        for (idt, isp) in url_prefix[start_index:end]
            if ym > idt
                start_index += 1
                continue
            end
            file_y = @RemoteFile(
                    string(baseurl, isp, "/", ymstr, "/", fileremote),
                    file = filename, dir = joinpath(local_dir, isp),
                    updates = (isp == "dst_final") ? :never : :yearly,
                )
            merge!(_dstfiles.files, Dict(sym => file_y))
            break
        end
    end
    nothing
end


using Test
using MPI

MPI.Init()

comm = MPI.COMM_WORLD
root = 0
rank = MPI.Comm_rank(comm)
sz   = MPI.Comm_size(comm)
isroot = MPI.Comm_rank(comm) == root

for typ in Base.uniontypes(MPI.MPIDatatype)

    # Allocating
    A = typ[MPI.Comm_rank(comm) + 1]
    C = MPI.Gather(A, root, comm)
    if MPI.Comm_rank(comm) == root
        @test typeof(C) === Vector{typ}
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

    # Allocating, object
    A = convert(typ,MPI.Comm_rank(comm) + 1)
    C = MPI.Gather(A, root, comm)
    if MPI.Comm_rank(comm) == root
        @test typeof(C) === Vector{typ}
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

    # Allocating, explicit length
    A = typ[MPI.Comm_rank(comm) + 1, 0]
    C = MPI.Gather(A, 1, root, comm)
    if MPI.Comm_rank(comm) == root
        @test typeof(C) === Vector{typ}
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

    # Allocating, view
    A = typ[MPI.Comm_rank(comm) + 1, 0]
    C = MPI.Gather(view(A, 1:1), root, comm)
    if MPI.Comm_rank(comm) == root
        @test typeof(C) === Vector{typ}
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

    # Non Allocating
    A = typ[MPI.Comm_rank(comm) + 1]
    C = Array{typ}(undef, sz)
    MPI.Gather!(A, C, length(A), root, comm)
    if MPI.Comm_rank(comm) == root
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

    # In_place
    A = typ[rank + 1]
    B = Array{typ}(undef, sz);
    B[rank+1] = rank+1
    C = isroot ? B : A
    MPI.Gather_in_place!(C, 1, root, comm)
    if MPI.Comm_rank(comm) == root
        @test C == map(typ, collect(1:MPI.Comm_size(comm)))
    end

end

MPI.Finalize()
@test MPI.Finalized()


using PyCall
using FlightSims
using FSimROS
using UnPack
using DifferentialEquations

rclpy = pyimport("rclpy")
rosNode = pyimport("rclpy.node")
fsim_msg = pyimport("fsim_interfaces.msg")
std_msg = pyimport("std_msgs.msg")

struct Multicopter_ZOH_Input <: AbstractEnv
    multicopter::Multicopter
end

function FSimBase.State(env::Multicopter_ZOH_Input)
    State(env.multicopter)
end

function FSimBase.Dynamics!(env::Multicopter_ZOH_Input)
    @Loggable function dynamics!(dx, x, input, t)
        @nested_log FSimZoo._Dynamics!(env.multicopter)(dx, x, nothing, t; u=input)
    end
end

@pydef mutable struct StateNode <: rosNode.Node
    function __init__(self)
        rosNode.Node.__init__(self, "state_node")
        # publisher
        self.publisher_ = self.create_publisher(fsim_msg.PoseTwist, "state", 5)
        timer_period = 1/500
        self.i = 0
        self.t = nothing
        self.time_received = false
        function timer_callback(self)
            x = copy(self.simulator.integrator.u)
            msg = state_to_msg(self.env.multicopter, x)
            self.publisher_.publish(msg)  # publish
            self.get_logger().info("time: $(self.t), state: $(x)")
            if self.control_received && self.t != nothing
                step_until!(self.simulator, self.t)
            end
        end
        self.timer = self.create_timer(timer_period, () -> timer_callback(self))
        # env
        multicopter = LeeHexacopter()
        self.env = Multicopter_ZOH_Input(multicopter)
        state0 = State(self.env)()
        @unpack m, g = self.env.multicopter
        u0 = (m*g/6) * ones(6)  # initial control input (actually meaningless; for initialisation of simulator)
        self.simulator = Simulator(state0, Dynamics!(self.env), u0; tf=100)  # second
        step_until!(self.simulator, timer_period)  # to reduce startup latency
        reinit!(self.simulator)  # to reduce startup latency
        # subscriber
        self.control_received = false
        function listener_callback(self, msg_control)
            if !self.control_received
                self.control_received = true
            end
            input = [msg_control.u1, msg_control.u2, msg_control.u3, msg_control.u4, msg_control.u5, msg_control.u6]
            self.simulator.integrator.p = input
        end
        function listener_callback_time(self, msg)
            t = msg.data
            if !self.time_received
                self.time_received = true
                self.simulator.integrator.t = t
            end
            self.t = t
        end
        self.subscription = self.create_subscription(fsim_msg.RotorRateHexa, "control", msg -> listener_callback(self, msg), 10)
        self.subscription_time = self.create_subscription(std_msg.Float64, "time", msg -> listener_callback_time(self, msg), 10)
    end
end

function main(args=nothing)
    rclpy.init(args=args)
    println("state node started")
    state_node = StateNode()
    rclpy.spin(state_node)
    state_node.destroy_node()
    rclpy.shutdown()
end

main()


include("network.jl")

struct Discriminator
    initial
    model
end

@functor Discriminator
function Discriminator(
    in_channels::Int = 3,
    features::Any = [64, 128, 256, 512]
)
    layers = []
    channel = features[1]
    for index in range(2, length(features))
            if features[index] != last(features)
                push!(layers,
                    Block(channel, features[index], stride=2)
                )
            else
                push!(layers,
                    Block(channel, features[index], stride=1)
                )
            end
            channel = features[index]
    end
    push!(layers,
        Conv((4,4), channel => 1; stride=1, pad=1)
    )
return Discriminator(
    Chain(
        Conv((4,4), in_channels=>features[1]; stride=2, pad=1),
        x -> leakyrelu.(x, 0.2)
    ),
    Chain(layers...)
) |> gpu
end

function (net::Discriminator)(x)
    input = net.initial(x)
    return sigmoid.(net.model(input))
end

using Random
function test()
    img_channels = 3
    img_size = 100
    ## need to explicity type to avoid Slow fallback implementation
    ## https://discourse.julialang.org/t/flux-con-warning/49456
    x = randn(Float32, (img_size, img_size, img_channels, 5))
    preds = Discriminator()
    println(size(preds(x)))
end


using SolidStateDetectors
using IntervalSets
using SpecialFunctions
T=Float32

struct CustomChargeDensity{T, N} <: SolidStateDetectors.AbstractChargeDensity{T}
	G1::SolidStateDetectors.AbstractGeometry{T,N}
	G2::SolidStateDetectors.AbstractGeometry{T,N}
	L1::SolidStateDetectors.AbstractChargeDensity{T}
	L2::SolidStateDetectors.AbstractChargeDensity{T}
	G3::SolidStateDetectors.AbstractGeometry{T,N}
	L3::SolidStateDetectors.AbstractChargeDensity{T}
	G4::SolidStateDetectors.AbstractGeometry{T,N}
	L4::SolidStateDetectors.AbstractChargeDensity{T}
	G5::SolidStateDetectors.AbstractGeometry{T,N}
	L5::SolidStateDetectors.AbstractChargeDensity{T}
	G6::SolidStateDetectors.AbstractGeometry{T,N}
	L6::SolidStateDetectors.AbstractChargeDensity{T}
	G7::SolidStateDetectors.AbstractGeometry{T,N}
	L7::SolidStateDetectors.AbstractChargeDensity{T}
	G8::SolidStateDetectors.AbstractGeometry{T,N}
	L8::SolidStateDetectors.AbstractChargeDensity{T}
	G9::SolidStateDetectors.AbstractGeometry{T,N}
	L9::SolidStateDetectors.AbstractChargeDensity{T}
	G10::SolidStateDetectors.AbstractGeometry{T,N}
	L10::SolidStateDetectors.AbstractChargeDensity{T}
	G11::SolidStateDetectors.AbstractGeometry{T,N}
	L11::SolidStateDetectors.AbstractChargeDensity{T}
	G12::SolidStateDetectors.AbstractGeometry{T,N}
	L12::SolidStateDetectors.AbstractChargeDensity{T}
	G13::SolidStateDetectors.AbstractGeometry{T,N}
	L13::SolidStateDetectors.AbstractChargeDensity{T}
	G14::SolidStateDetectors.AbstractGeometry{T,N}
	L14::SolidStateDetectors.AbstractChargeDensity{T}
	G15::SolidStateDetectors.AbstractGeometry{T,N}
	L15::SolidStateDetectors.AbstractChargeDensity{T}
	G16::SolidStateDetectors.AbstractGeometry{T,N}
	L16::SolidStateDetectors.AbstractChargeDensity{T}
	G17::SolidStateDetectors.AbstractGeometry{T,N}
	L17::SolidStateDetectors.AbstractChargeDensity{T}
end

function SolidStateDetectors.get_charge_density(cdm::CustomChargeDensity{T}, pt::SolidStateDetectors.AbstractCoordinatePoint{T})::T where {T}
	(pt in cdm.G1) * SolidStateDetectors.get_charge_density(cdm.L1, pt) + 800000000000.0 * (1 + erf((sqrt(pt[1]^2+pt[2]^2)-(0.7))/(2*0.1)))/2 +
	(pt in cdm.G2) * SolidStateDetectors.get_charge_density(cdm.L2, pt) +
	(pt in cdm.G3) * SolidStateDetectors.get_charge_density(cdm.L3, pt) +
	(pt in cdm.G4) * SolidStateDetectors.get_charge_density(cdm.L4, pt) +
	(pt in cdm.G5) * SolidStateDetectors.get_charge_density(cdm.L5, pt) +
	(pt in cdm.G6) * SolidStateDetectors.get_charge_density(cdm.L6, pt) +
	(pt in cdm.G7) * SolidStateDetectors.get_charge_density(cdm.L7, pt) +
	(pt in cdm.G8) * SolidStateDetectors.get_charge_density(cdm.L8, pt) +
	(pt in cdm.G9) * SolidStateDetectors.get_charge_density(cdm.L9, pt) +
	(pt in cdm.G10) * SolidStateDetectors.get_charge_density(cdm.L10, pt) * erfc(-(pt[3]-(1.0074e-05))/(2*3.7e-08))/2 +
	(pt in cdm.G11) * SolidStateDetectors.get_charge_density(cdm.L11, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G12) * SolidStateDetectors.get_charge_density(cdm.L12, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G13) * SolidStateDetectors.get_charge_density(cdm.L13, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G14) * SolidStateDetectors.get_charge_density(cdm.L14, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G15) * SolidStateDetectors.get_charge_density(cdm.L15, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G16) * SolidStateDetectors.get_charge_density(cdm.L16, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2 +
	(pt in cdm.G17) * SolidStateDetectors.get_charge_density(cdm.L17, pt) * (1 + erf(-(pt[3]-(0.0019797316718427))/(2*1.3416407864998738e-07)))/2
end

G1 = SolidStateDetectors.Tube{T}(0..0.01627006165897972,0..2π,0..0.002,CartesianPoint{T}(-1.2390881971262907e-19, 9.912705577010326e-19, 0))
G2 = SolidStateDetectors.Tube{T}(0..0.01627006165897972,0..2π,0..1e-05,CartesianPoint{T}(-1.2390881971262907e-19, 9.912705577010326e-19, 0))
CD1 = SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,50000000000.0),(0.0f0,0.0f0,0.0f0))
CD2 = SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,-1.9e+16),(0.0f0,0.0f0,0.0f0))
G3 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(0, 0, 0.00199),0)
CD3= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G4 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(0.009020061658979717, 0.0, 0.00199),0)
CD4= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G5 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(0.0045100308294898596, 0.0078116025403784425, 0.00199),0)
CD5= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G6 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(-0.004510030829489857, 0.007811602540378445, 0.00199),0)
CD6= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G7 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(-0.009020061658979717, 1.1046389638781276e-18, 0.00199),0)
CD7= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G8 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(-0.004510030829489863, -0.007811602540378441, 0.00199),0)
CD8= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G9 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 2e-05,CartesianPoint{T}(0.004510030829489859, -0.0078116025403784425, 0.00199),0)
CD9= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G10 = SolidStateDetectors.Tube{T}(0..0.01627006165897972,0..2π,0..1.48e-07,CartesianPoint{T}(-1.2390881971262907e-19, 9.912705577010326e-19, 1.0001000000000001e-05))
CD10= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,-1.9e+16),(0.0f0,0.0f0,0.0f0))
G11 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(0, 0, 0.0019797316718427),0)
CD11= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G12 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(0.009020061658979717, 0.0, 0.0019797316718427),0)
CD12= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G13 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(0.0045100308294898596, 0.0078116025403784425, 0.0019797316718427),0)
CD13= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G14 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(-0.004510030829489857, 0.007811602540378445, 0.0019797316718427),0)
CD14= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G15 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(-0.009020061658979717, 1.1046389638781276e-18, 0.0019797316718427),0)
CD15= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G16 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(-0.004510030829489863, -0.007811602540378441, 0.0019797316718427),0)
CD16= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))
G17 = SolidStateDetectors.HexagonalPrism{T}(0, 0.00515, 5.366563145999495e-07,CartesianPoint{T}(0.004510030829489859, -0.0078116025403784425, 0.0019797316718427),0)
CD17= SolidStateDetectors.LinearChargeDensity{T}((0.0f0,0.0f0,1.9e+16),(0.0f0,0.0f0,0.0f0))

ccdm = CustomChargeDensity(G1,G2,CD1,CD2,G3,CD3,G4,CD4,G5,CD5,G6,CD6,G7,CD7,G8,CD8,G9,CD9,G10,CD10,G11,CD11,G12,CD12,G13,CD13,G14,CD14,G15,CD15,G16,CD16,G17,CD17)


using AdaGram

vm, dict = load_model(ARGS[1])
window = int(ARGS[2])

prepare_context(ctx) = map(int32, filter(v -> v > 0, map(w -> get(dict.word2id, lowercase(w), int32(-1)), split(ctx))))

while !eof(STDIN)
	word_full, ctx_num = split(readline())
	ctx_num = int(ctx_num)
	word = word_full[1:search(word_full, '.')-1]

	for i in 1:ctx_num
		left_ctx, right_ctx = split(readline(), '\t')

		left_ctx = prepare_context(left_ctx)
		right_ctx = prepare_context(right_ctx)

		ctx = vcat(right_ctx[1:min(length(right_ctx), window)], left_ctx[max(length(left_ctx)-window+1, 1):end])

		z = disambiguate(vm, dict.word2id[word], int32(ctx))
		s = indmax(z)
		println(word_full, " ", word_full, ".", i, " ", word_full, ".senses.", s)
	end
end


using WordCloud
using Random
words = [randstring(rand(1:8)) for i in 1:300]
weights = randexp(length(words))
wordcloud(words, weights) |> generate!


macro umode_labesl()
	:(Dict(:sy=>"sync",:fd=>"delay",:md=>"mean_delay",:sd=>"strob",:smd=>"strob_delay",:ga=>"general_async",:roa=>"random_order_async",:ssy=>"semi_sync"))
end

macro update_schemes()
	:(Dict(:sy=>:sy,:fd=>fixed_delay!,:md=>smoothed_fixed_delay!,:st=>strob!,:mst=>smoothed_strob!,:ga=>:ga,:roa=>:roa,:ssy=>semi_sync!))
end

function init_net!(net::Net2,node_indices::Vector{Int}=collect(vertices(net.graph));update_mode::Symbol=:sy,t0_mode::Symbol=:custom,init_h::Symbol=:last,init_array::Vector{Int}=Int[],init_constraints...)
	allunique(node_indices) || error("Duplicate node indices")
	intersect(vertices(net.graph),node_indices)==node_indices || error("Invalid node indices")
	umodes = @update_schemes
	haskey(umodes,update_mode) || error("Unexisting update scheme. Available schemes are :",keys(umodes))
	nodes_to_constraint = Int[]
	max_buffer_tlength = size(net.sim_buffer)[2]
	if !isempty(init_constraints)
		ids = [string(id) for (id,state) in init_constraints]
		allunique(ids) || error("Duplicate constraint specification")
		intersect(ids,(sigma.id for sigma in net.nodes))==ids || error("Trying to set unexisting nodes:",ids)
		all(i->i<:Union{Int,Bool,Vector{Int},Vector{Bool}},(typeof(state) for (id,state) in init_constraints)) || error("Wrong format to specify state")
		push!(nodes_to_constraint,(findfirst([sigma.id for sigma in net.nodes],id) for id in ids)...)
		all(map((x,y)->typeof(x) <: Vector ? intersect(x,y)==x : intersect([x],y)==[x],(state for (id,state) in init_constraints),(sigma.state_range for sigma in net.nodes[nodes_to_constraint]))) || error("Trying to set node to a forbidden state")
		node_indices = setdiff(node_indices,nodes_to_constraint)
		states = Vector{Vector{Int}}(length(init_constraints))
		for (ni,nsigma) in enumerate(nodes_to_constraint)
			state = init_constraints[ni][2]
			states[ni] = isempty(state) ? [net.nodes[nsigma].def_state] : (typeof(state)<:Vector ? state : [state])
		end
	end
	if max_buffer_tlength > 1
		if init_h==:default
			for ni in vcat(node_indices,nodes_to_constraint)
				net.sim_buffer[ni,1:end-1] = net.nodes[ni].def_state
			end
		elseif init_h==:rand
			for ni in node_indices
				net.sim_buffer[ni,1:end-1] = rand(net.nodes[ni].state_range,max_buffer_tlength-1)
			end
		elseif init_h!=:last
			error("Invalid initialization flag. Available options are\n:default for default, :rand for random")
		end
	end
	if t0_mode==:default
		net.sim_buffer[node_indices,end] = [sigma.def_state for sigma in net.nodes[node_indices]]
	elseif t0_mode==:custom
		if !isempty(init_array)
			length(init_array)==length(node_indices)||error("Initial configuration vector must have the same length of nodes vector")
			for (n,sigma) in enumerate(net.nodes[node_indices])
				if init_array[n] in sigma.state_range
					net.sim_buffer[node_indices[n],end] = init_array[n]
				else
					error("Trying to set node ",sigma.id," to a forbidden state, value ",init_array[n]," is out of possible states range ",sigma.state_range)
				end
			end
		end
	else
		error("Invalid initialization flag. Available options are\n:default for default, :rand for random :custom start from last available configuration")
	end
	for (ni,nsigma) in enumerate(nodes_to_constraint)
		state = states[ni]
		if length(state)<=max_buffer_tlength
			net.sim_buffer[ni,end-length(state)+1:end] = collect(state)
		else
			net.sim_buffer[ni,:] = collect(state[end-max_buffer_tlength+1:end])
		end
	end

	in_state_vals = nonzeros(net.in_states)
	# mu_in_state_vals = nonzeros(net.mu_in_states)
	nsigma_ins = rowvals(net.in_states)
	# tau_vals = nonzeros(net.in_taus)
	# if update_mode == :sy
	# 	for i in vertices(net.graph)
	# 		# j_in = nzrange(net.in_states,i)
	# 		# mu_in_state_vals[j_in] = in_state_vals[j_in] = net.sim_buffer[nsigma_ins[j_in],end]
	# 	end
	if update_mode == :fd || update_mode == :st
		for i in vertices(net.graph)
			for j in nzrange(net.in_states,i)
				in_state_vals[j] = net.sim_buffer[nsigma_ins[j],end-net.in_taus[nsigma_ins[j],i]]
			end
		end
	elseif update_mode == :md || update_mode == :mst
		mu_in_state_vals = nonzeros(net.mu_in_states)
		for i in vertices(net.graph)
			for j in nzrange(net.in_states,i)
				nsigma = nsigma_ins[j]
				tau = net.in_taus[nsigma,i]
				sigma_in = net.nodes[nsigma]
				if tau==0
					mu_in_state_vals[j] = in_state_vals[j] = net.sim_buffer[nsigma,end]
				else
					mu_in_state_vals[j] = mean_sigmak = mean(net.sim_buffer[nsigma,end-tau:end])
					in_state_vals[j] = sigma_in.state_range[findlast(mean_sigmak .>= sigma_in.thresholds)+1]
				end
			end
		end
	end
	return net.sim_buffer[:,end]
end

function rand_init_net!(net::Net2,node_indices::Vector{Int}=collect(vertices(net.graph));update_mode::Symbol=:sy,init_h::Symbol=:rand,init_constraints...)
	allunique(node_indices) || error("Duplicate node indices")
	intersect(vertices(net.graph),node_indices)==node_indices || error("Invalid node indices")
	umodes = @update_schemes
	haskey(umodes,update_mode) || error("Unexisting update scheme. Available schemes are :",keys(umodes))
	nodes_to_constraint = Int[]
	max_buffer_tlength = size(net.sim_buffer)[2]
	if !isempty(init_constraints)
		ids = [string(id) for (id,state) in init_constraints]
		allunique(ids) || error("Duplicate constraint specification")
		intersect(ids,(sigma.id for sigma in net.nodes))==ids || error("Trying to set unexisting node $(setdiff(ids,(sigma.id for sigma in net.nodes)))")
		all(i->i<:Union{Int,Bool,Vector{Int},Vector{Bool}},(typeof(state) for (id,state) in init_constraints)) || error("Wrong format to specify state")
		push!(nodes_to_constraint,(findfirst([sigma.id for sigma in net.nodes],id) for id in ids)...)
		all(map((x,y)->typeof(x) <: Vector ? intersect(x,y)==x : intersect([x],y)==[x],(state for (id,state) in init_constraints),(sigma.state_range for sigma in net.nodes[nodes_to_constraint]))) || error("Trying to set node to a forbidden state")
		node_indices = setdiff(node_indices,nodes_to_constraint)
		states = Vector{Vector{Int}}(length(init_constraints))
		for (ni,nsigma) in enumerate(nodes_to_constraint)
			state = init_constraints[ni][2]
			states[ni] = isempty(state) ? [net.nodes[nsigma].def_state] : unique(state)
			net.sim_buffer[nsigma,end] = length(state)>1 ? rand(state) : state[1]
		end
	end
	if max_buffer_tlength > 1
		if init_h==:default
			for ni in vcat(node_indices,nodes_to_constraint)
				net.sim_buffer[ni,1:end-1] = net.nodes[ni].def_state
			end
		elseif init_h==:rand
			for ni in node_indices
				net.sim_buffer[ni,1:end-1] = rand(net.nodes[ni].state_range,max_buffer_tlength-1)
			end
			for (n,ni) in enumerate(nodes_to_constraint)
				net.sim_buffer[ni,1:end-1] = rand(states[n],max_buffer_tlength-1)
			end
		elseif init_h!=:last
			error("Invalid initialization flag. Available options are\n:default for default, :rand for random")
		end
	end
	net.sim_buffer[node_indices,end] = [rand(sigma.state_range) for sigma in net.nodes[node_indices]]
	in_state_vals = nonzeros(net.in_states)
	nsigma_ins = rowvals(net.in_states)
	# tau_vals = nonzeros(net.in_taus)
	if update_mode == :fd || update_mode == :st
		for i in vertices(net.graph)
			for j in nzrange(net.in_states,i)
				in_state_vals[j] = net.sim_buffer[nsigma_ins[j],end-net.in_taus[nsigma_ins[j],i]]
			end
		end
	elseif update_mode == :md || update_mode == :mst
		mu_in_state_vals = nonzeros(net.mu_in_states)
		for i in vertices(net.graph)
			for j in nzrange(net.in_states,i)
				nsigma = nsigma_ins[j]
				tau = net.in_taus[nsigma,i]
				sigma_in = net.nodes[nsigma]
				if tau==0
					mu_in_state_vals[j] = in_state_vals[j] = net.sim_buffer[nsigma,end]
				else
					mu_in_state_vals[j] = mean_sigmak = mean(net.sim_buffer[nsigma,end-tau:end])
					in_state_vals[j] = sigma_in.state_range[findlast(mean_sigmak .>= sigma_in.thresholds)+1]
				end
			end
		end
	end
	# return net.sim_buffer[:,end]
	return nothing
end

macro updating_scheme(x)
	update_ex0 = quote
	end
	if eval(x) == :sy
		update_ex1 = quote
			setdiff(sigma_indices,forced_indices)
		end
		update_ex2 = quote
			sim[i,t+1] = net.nodes[i].rule[sim[nsigma_ins[nzrange(net.in_states,i)],t]]
		end
	elseif eval(x) == :ga
		update_ex1 = quote
			rand(sigma_indices)
		end
		update_ex2 = quote
			unchanged = setdiff(sigma_indices,forced_indices)
			if !(i in forced_indices)
				sim[i,t+1] = net.nodes[i].rule[sim[nsigma_ins[nzrange(net.in_states,i)],t]]
				unchanged = setdiff(unchanged,i)
			end
			# @show unchanged
			sim[unchanged,t+1] = sim[unchanged,t]
		end
	elseif eval(x) ==:roa
		update_ex0 = quote
			sim[:,t+1] = sim[:,t]
		end
		update_ex1 = quote
			shuffle(sigma_indices)
		end
		update_ex2 = quote
			if !(i in forced_indices)
				sim[i,t+1] = net.nodes[i].rule[sim[nsigma_ins[nzrange(net.in_states,i)],t+1]]
			end
		end
	end
	return esc(quote
		nsigma_ins = rowvals(net.in_states)
		sigma_noise_indices = [ni for (ni,eta) in sigma_noises]
		sigma_indices = 1:length(net.nodes)
		for t in i_t0:i_t0+steps-1
			$update_ex0
			forced_indices = Int[]
			# Updating forced nodes
			for (nsigma,rhythm_values) in nsigma_ext_forcing
				rhythm,forced_values = rhythm_values
				index_rhythm = mod(t_init+t-i_t0,length(rhythm))+1
				if rhythm[index_rhythm]
					#println("At t=",t_init+t+1-i_t0,",trying to fix node index ",nsigma," on rhythm index ",index_rhythm," to forced_value ",forced_values[1])
					sim[nsigma,t+1] = forced_values[1]
					push!(forced_indices,nsigma)
					nsigma_ext_forcing[nsigma][2] = circshift(forced_values,-1)
				end
			end
			# Apply the original rule except for the forced nodes
			for i in $update_ex1
				$update_ex2
				# if i in forced_indices
				# 	println("At t=",t_init+t+1-i_t0,",node index ",i," changed from ",sim[i,t]," to ",sim[i,t+1])
				# end
				# Add noise where specified, expect for those forced nodes
				if i in sigma_noise_indices
					eta = sigma_noises[findfirst(sigma_noise_indices,i)][2]
					true_value = sim[i,t+1]
					sim[i,t+1] = rand()<=eta ? rand(filter(x -> x!=true_value,net.nodes[i].state_range)) : true_value
				end
				# sim[i,t+1] = net.nodes[i].rule[sim[nsigma_ins[nzrange(net.in_states,i)],t]]
			end
		end
	end)
end

function evolve_net!(net::Net2{T},steps::Int;keep::Bool=true,update_mode::Symbol=:sy,sim::Array{T,2}=Array{T}(nv(net.graph),steps + 1 + (update_mode == :sy ? 0 : maximum(net.in_taus))),t_init::Int=0,noise_vector::Vector{Pair{Symbol,Float64}}=Pair{Symbol,Float64}[],forcing_rhythms::Dict{Symbol,Tuple{Vector{Bool},Vector{Int}}}=Dict{Symbol,Tuple{Vector{Bool},Vector{Int}}}()) where {T<:state_type}
	umodes = @update_schemes
	haskey(umodes,update_mode) || error("Unexisting update scheme. Available schemes are :",keys(umodes))
	steps>=0 || error("Steps must be a positive integer")
	hist_size = 1 + (update_mode == :sy ? 0 : maximum(net.in_taus))
	# Initialize simulation table (from t_init - tau0 to t_init)
	size(sim) == (nv(net.graph),steps + hist_size) || error("Array for storing results has incorrect dimensions ",size(sim), ". Expected array size is ",(nv(net.graph),steps + hist_size))
	if update_mode == :sy
		sim[:,1] = net.sim_buffer[:,end]
	else
		sim[:,1:hist_size] = net.sim_buffer[:]
	end
	sigma_noises = collect(findfirst(x->x.id==string(sigma),net.nodes)=>eta for (sigma,eta) in noise_vector)
	nsigma_ext_forcing = Dict(findfirst(x->x.id==string(sigma_symbol),net.nodes)=>collect(rhythm_states) for (sigma_symbol,rhythm_states) in forcing_rhythms)
	i_t0 = hist_size
	if update_mode == :sy
		@updating_scheme :sy
	elseif update_mode == :ga
		for (nsigma,rhythm_states) in nsigma_ext_forcing
			nsigma_ext_forcing[nsigma][1] = repeat(nsigma_ext_forcing[nsigma][1],inner=[length(net.nodes)])
			nsigma_ext_forcing[nsigma][2] = repeat(nsigma_ext_forcing[nsigma][2],inner=[length(net.nodes)])
		end
		@show nsigma_ext_forcing
		@updating_scheme :ga
	elseif update_mode == :roa
		@updating_scheme :roa
	else
		umodes[update_mode](net,sim,steps,hist_size,sigma_noises,sigma_ext_forcing,t_init)
	end
	buffer_size = size(net.sim_buffer)[2]
	if buffer_size == hist_size || steps > buffer_size
		net.sim_buffer[:] = sim[:,end-buffer_size+1:end]
	elseif steps <= hist_size
		net.sim_buffer[:,1:end-steps-hist_size] = net.sim_buffer[:,1+steps+hist_size:end]
		net.sim_buffer[:,end-steps-hist_size+1:end] = sim[:]
	else
		@show steps, hist_size,buffer_size
		error("Wrong dimensions")
	end
	if keep
		return sim
	else
		return sim[:,end-hist_size+1:end]
	end
end

function semi_sync!(net::Net2,sim::sim_type,steps::Int,i_t0::Int,x...)
	nsigma_ins = rowvals(net.in_states)
	Nsize = length(net.nodes)
	buff = deepcopy(sim[:,i_t0])
	for t in i_t0:i_t0+steps-1
		n = 0
		for s in 1:Nsize
			s_indices = find(x->x.sync_order==s,net.nodes)
			for i in s_indices
				sim[i,t+1] = net.nodes[i].rule[buff[nsigma_ins[nzrange(net.in_states,i)]]]
				# sim[i,t+1] = net.nodes[i].rule[sim[nsigma_ins[nzrange(net.in_states,i)],t]]
				n += 1
			end
			buff[s_indices] = sim[s_indices,t+1]
			if n==Nsize
				break
			end
			# sim[:,t+1] = buff[:]
		end
	end
	return nothing
end

function fixed_delay!(net::Net2,sim::sim_type,steps::Int,i_t0::Int,x...)
	nsigma_ins = rowvals(net.in_states)
	# tau_ins = nonzeros(net.in_taus)
	# in_state_vals = nonzeros(net.in_states)
	# mu_in_state_vals = nonzeros(net.mu_in_states)
	for t in i_t0:i_t0+steps-1
		for (i,sigma) in enumerate(net.nodes)
			j_in = nzrange(net.in_states,i)
			input = [sim[nsigma_ins[nj],t-net.in_taus[nsigma_ins[nj],i]] for nj in j_in]
			sim[i,t+1] = sigma.rule[input]
			# mu_in_state_vals[j_in] = in_state_vals[j_in] = input
			# in_state_vals[j_in] = input
		end
	end
	return nothing
end

function smoothed_fixed_delay!(net::Net2,sim::sim_type,steps::Int,i_t0::Int,x...)
	nsigma_ins = rowvals(net.in_states)
	# tau_ins = nonzeros(net.in_taus)
	in_state_vals = nonzeros(net.in_states)
	mu_in_state_vals = nonzeros(net.mu_in_states)
	for t in i_t0:i_t0+steps-1
		for (i,sigma) in enumerate(net.nodes)
			j_in = nzrange(net.in_states,i)
			sim[i,t+1] = sigma.rule[in_state_vals[j_in]]
		end
		for (i,sigma) in enumerate(net.nodes)
			for j in nzrange(net.in_states,i)
				nsigma = nsigma_ins[j]
				tau = net.in_taus[nsigma,i]
				sigma_in = net.nodes[nsigma]
				if tau==0
					mu_in_state_vals[j] = in_state_vals[j] = sim[nsigma,t+1]
				else
					mu_in_state_vals[j] += (sim[nsigma,t+1]-sim[nsigma,t-tau])/(tau+1)
					in_state_vals[j] = sigma_in.state_range[findlast(mu_in_state_vals[j] .>= sigma_in.thresholds)+1]
				end
			end
		end
	end
	return nothing
end

function strob!(net::Net2,sim::sim_type,steps::Int,i_t0::Int,t_init::Int=0,x...)
	nsigma_ins = rowvals(net.in_states)
	tau_ins = nonzeros(net.in_taus)
	in_state_vals = nonzeros(net.in_states)
	for t in i_t0:i_t0+steps-1
		for (i,sigma) in enumerate(net.nodes)
			j_in = nzrange(net.in_states,i)
			for j in j_in
				if mod(t_init+t-i_t0+1,net.in_taus[nsigma_ins[j],i]+1)==0
					# sigma.sigma_in_state[nsigmak] = sim[sigmak,t-sigma.tau_in[nsigmak]]
					in_state_vals[j] = sim[nsigma_ins[j],t]
				end
			end
			sim[i,t+1] = sigma.rule[in_state_vals[j_in]]
		end
	end
	return nothing
end

function smoothed_strob!(net::Net2,sim::sim_type,steps::Int,i_t0::Int,t_init::Int=0,x...)
	nsigma_ins = rowvals(net.in_states)
	# tau_ins = nonzeros(net.in_taus)
	in_state_vals = nonzeros(net.in_states)
	mu_in_state_vals = nonzeros(net.mu_in_states)
	for t in i_t0:i_t0+steps-1
		for (i,sigma) in enumerate(net.nodes)
			j_in = nzrange(net.in_states,i)
			for j in j_in
				nsigma = nsigma_ins[j]
				tau = net.in_taus[nsigma,i]
				sigma_in = net.nodes[nsigma]
				if tau==0
					mu_in_state_vals[j] = in_state_vals[j] = sim[nsigma,t]
				elseif mod(t_init+t-i_t0+1,tau+1)==0
					mean_sigmak = mu_in_state_vals[j] = mean(sim[nsigma,t-tau:t])
					in_state_vals[j] = sigma_in.state_range[findlast(mean_sigmak.>= sigma_in.thresholds)+1]
				end
			end
			sim[i,t+1] = sigma.rule[in_state_vals[j_in]]
		end
	end
	return nothing
end

function obs_rand_cond!(net::Net2{T},steps::Int,obs_node::Union{AbstractString,Int,Vector{String},Vector{Int}},nrand_init::Int=prod(length(sigma.state_range) for sigma in net.nodes);base_dir::AbstractString="",variant_name::AbstractString="",file_tag::AbstractString="",update_mode::Symbol=:sy,noise_vector::Vector{Pair{Symbol,Float64}}=Pair{Symbol,Float64}[],init_h::Symbol=:default,forcing_rhythms::Dict{Symbol,Tuple{Vector{Bool},Vector{Int}}}=Dict{Symbol,Tuple{Vector{Bool},Vector{Int}}}(),init_constraints...) where {T<:state_type}
	Nsize = length(net.nodes)
	dump_to_file = false
	moment = now(Dates.UTC)
	time_string = string(Dates.value(moment))
	base_dir_tag = join([variant_name,file_tag,time_string],"_")
	results_dir = joinpath(base_dir,base_dir_tag)
	if base_dir != ""
		if isdir(base_dir)
			dump_to_file = true
			if isdir(results_dir)
				println("Resuls will be overwritten onto ")
			else
				mkpath(results_dir)
				println("Resuls will be saved into ")
			end
			println(results_dir," folder")
		else
			error(base_dir, " is not a valid directory to save s")
		end
	end
	index_node_obs = Any[]
	if typeof(obs_node) <: Vector
		length(obs_node)!=0 || error("You must enter at least 1 node id or index")
		index_node_obs = typeof(obs_node) <: Vector{Int} ? obs_node : collect(findfirst(sigma->sigma.id==x,net.nodes) for x in obs_node)
		for (i,x) in enumerate(obs_node)
			0 < index_node_obs[i] <= Nsize || error("Unexisting node: $x")
		end
	else
		index_node_obs = typeof(obs_node) <: Int ? obs_node : findfirst(sigma->sigma.id==obs_node,net.nodes)
		0 < index_node_obs <= Nsize || error("Unexisting node: $obs_node")
	end
	length_obs = length(index_node_obs)
	obs = length_obs >=2 ? Vector{Tuple{Vararg{T,length_obs}}}() : Vector{T}()
	state_space_size = prod(length(sigma.state_range) for sigma in net.nodes)
	if(state_space_size==0) && all(x->isinteger(x)&&x>0,length(sigma.state_range) for sigma in net.nodes)
		state_space_size = 10000000000
	end
	if nrand_init>state_space_size
        nrand_init = state_space_size
        println("Oversampling error: Random initial conditions bigger than possible number of configurations")
    end
	println("Calculating ",length_obs >=2 ? join((sigma.id for sigma in net.nodes[index_node_obs]),',') : net.nodes[index_node_obs].id ," dynamics over $nrand_init random initial conditions")
	r = 0
	hist_size = 1 + (update_mode == :sy ? 0 : maximum(net.in_taus))
	t_size = steps+hist_size
	sim_result = Array{T,2}(Nsize,t_size)
	sigma_ext_forcing = collect(sigma_symbol=>rhythm_values[2] for (sigma_symbol,rhythm_values) in forcing_rhythms)
	# init_constraints = vcat(init_constraints,sigma_ext_forcing)
	if dump_to_file
		#f = Vector{GZip.GZipStream}(length_obs)
		filenames = String[]
		raw_dir = joinpath(results_dir,"raw")
		isdir(raw_dir) || mkpath(raw_dir)
		for sigma_i in index_node_obs
			# push!(filenames,joinpath(results_dir,join([join([net.nodes[sigma_i].id,"T$t_size","r$nrand_init"],"_"),"bin","gz"],".")))
			push!(filenames,joinpath(raw_dir,join([net.nodes[sigma_i].id,"bin","gz"],".")))
			# f[i] = GZip.open(filename,"w")
			f = GZip.open(filenames[end],"w")
			close(f)
		end
		println("Succesfully created files for storing simulations")
		# for (i,sigma_i) in enumerate(index_node_obs)
		# 	filename = joinpath(results_dir,join([join([join([net.nodes[sigma_i].id,"T$t_size","r$nrand_init"],"_"),file_tag],""),"dat","gz"],"."))
		# 	f[i] = GZip.open(filename,"a")
		# end
		if nrand_init<state_space_size
			while r < nrand_init
				rand_init_net!(net;update_mode=update_mode,init_h=init_h,init_constraints...)
				evolve_net!(net,steps;keep=false,update_mode=update_mode,sim=sim_result,noise_vector=noise_vector,forcing_rhythms=forcing_rhythms)
				# nbytes = Int[]
				# for (fi,obs_i) in enumerate(index_node_obs)
				# 	#push!(nbytes,write(f[fi],map(Int8,sim_result[obs_i,:])))
				# 	writedlm(f[fi],map(Int8,sim_result[obs_i,:])',',')
				# end
				for (fi,sigma_i) in enumerate(index_node_obs)
					GZip.open(filenames[fi],"a") do filex
						write(filex,sim_result[sigma_i,:])
					end
					# close(f)
				end
				#println(nbytes," written")
				r += 1
				if nrand_init >= 10 && mod(r,round(Int,nrand_init/10))==0
					@printf("%0.2f%% progress, %d out of %d initial conditions explored\n", 100*r/nrand_init, r, nrand_init)
				end
			end
		else
			println("Exhaustive exploration of state space")
			node_indices = collect(vertices(net.graph))
			nodes_to_constraint = Int[]
			if !isempty(init_constraints)
				ids = [string(id) for (id,state) in init_constraints]
				allunique(ids) || error("Duplicate constraint specification")
				intersect(ids,(sigma.id for sigma in net.nodes))==ids || error("Trying to set unexisting node")
				all(i->i<:Union{Int,Bool,Vector{Int},Vector{Bool}},(typeof(state) for (id,state) in init_constraints)) || error("Wrong format to specify state")
				push!(nodes_to_constraint,(findfirst([sigma.id for sigma in net.nodes],id) for id in ids)...)
				all(map((x,y)->typeof(x) <: Vector ? intersect(x,y)==x : intersect([x],y)==[x],(state for (id,state) in init_constraints),(sigma.state_range for sigma in net.nodes[nodes_to_constraint]))) || error("Trying to set node to a forbidden state")
				node_indices = setdiff(node_indices,nodes_to_constraint)
				states = Vector{Vector{Int}}(length(init_constraints))
				for (ni,nsigma) in enumerate(nodes_to_constraint)
					state = init_constraints[ni][2]
					states[ni] = isempty(state) ? [net.nodes[nsigma].def_state] : unique(state)
				end
			end
			ranges = Vector{Vector{Int}}(nv(net.graph))
			for nsigma in node_indices
				ranges[nsigma] = net.nodes[nsigma].state_range
			end
			for (ni,nsigma) in enumerate(nodes_to_constraint)
				ranges[nsigma] = states[ni]
			end
			itstspace = product(ranges...)
			for leave in itstspace
				init_net!(net;update_mode=update_mode,init_h=init_h,init_array=collect(leave))
				evolve_net!(net,steps;keep=false,update_mode=update_mode,sim_result=sim_result,noise_vector=noise_vector,forcing_rhythms=forcing_rhythms)
				for (fi,sigma_i) in enumerate(index_node_obs)
					GZip.open(filenames[fi],"a") do filex
						write(filex,sim_result[sigma_i,:])
					end
					# close(f)
				end
				r += 1
				if nrand_init>=10 && mod(r,round(Int,nrand_init/10))==0
					@printf("%0.2f%% progress, %d out of %d initial conditions explored\n", 100*r/nrand_init, r, nrand_init)
				end
			end
			nrand_init = r
		end
		# status_files = zeros(Int,length_obs)
		# for (fi,sigma_i) in enumerate(index_node_obs)
		# 	status_files[fi] = close(f[fi])
		# end
		# if isempty(find(status_files))
		# 	println("Simulations were succesfully stored for nodes ", join(obs_node,", "," and "))
		# else
		# 	println("Simulations couldn't be stored for nodes ", join(obs_node[find(status_files)],", "," nor "))
		# end
		# return status_files
		println("\nWriting metadata files")
		open(joinpath(results_dir,join([base_dir_tag,"meta"],".")), "w") do f
			println(f, "Date = ",string(moment)) # Univeral Time
			println(f, "umode = ",@umode_labesl()[update_mode]) # tag for updating scheme used in the simulation
        	println(f, "t0 = ",1-hist_size) # What time corresponds to first time instant, e.g. 1st column of array element corresponds to t=0 in synchronous update scheme
			println(f, "T = ",steps) # Amount of iterations
			println(f, "tsize = ",hist_size + steps) # Whole time series length
			println(f, "r = ",nrand_init) # Total amount of realizations
			println(f, "\nSampling space of initial conditions") # For each variable, it's sampling space is printed out
			init_constraints_lab = [string(nsymbol) for (nsymbol,nvals) in init_constraints]
			init_constraints_states = [nvals for (nsymbol,nvals) in init_constraints]
			for node in net.nodes
				print(f,node.id,"\t:\t")
				if node.id in init_constraints_lab
					println(f,join(init_constraints_states[findfirst(x->x==node.id,init_constraints_lab)],","))
				else
					println(f,join(node.state_range,","))
				end
			end
		end
		return nothing
	else
		if nrand_init<state_space_size
			while r < nrand_init
				rand_init_net!(net;update_mode=update_mode,init_h=init_h,init_constraints...)
				evolve_net!(net,steps;keep=false,update_mode=update_mode,sim=sim_result,noise_vector=noise_vector,forcing_rhythms=forcing_rhythms)
				if length_obs==1
					push!(obs,sim_result[index_node_obs,:]...)
				else
					push!(obs,(tuple(sim_result[index_node_obs,ti]...) for ti in 1:t_size)...)
				end
				r += 1
				if nrand_init>=10 && mod(r,round(Int,nrand_init/10))==0
					@printf("%0.2f%% progress, %d out of %d initial conditions explored\n", 100*r/nrand_init, r, nrand_init)
				end
			end
		else
			println("Exhaustive exploration of state space")
			node_indices = collect(vertices(net.graph))
			nodes_to_constraint = Int[]
			if !isempty(init_constraints)
				ids = [string(id) for (id,state) in init_constraints]
				allunique(ids) || error("Duplicate constraint specification")
				intersect(ids,(sigma.id for sigma in net.nodes))==ids || error("Trying to set unexisting node")
				all(i->i<:Union{Int,Bool,Vector{Int},Vector{Bool}},(typeof(state) for (id,state) in init_constraints)) || error("Wrong format to specify state")
				push!(nodes_to_constraint,(findfirst([sigma.id for sigma in net.nodes],id) for id in ids)...)
				all(map((x,y)->typeof(x) <: Vector ? intersect(x,y)==x : intersect([x],y)==[x],(state for (id,state) in init_constraints),(sigma.state_range for sigma in net.nodes[nodes_to_constraint]))) || error("Trying to set node to a forbidden state")
				node_indices = setdiff(node_indices,nodes_to_constraint)
				states = Vector{Vector{Int}}(length(init_constraints))
				for (ni,nsigma) in enumerate(nodes_to_constraint)
					state = init_constraints[ni][2]
					states[ni] = isempty(state) ? [net.nodes[nsigma].def_state] : unique(state)
				end
			end
			ranges = Vector{Vector{Int}}(nv(net.graph))
			for nsigma in node_indices
				ranges[nsigma] = net.nodes[nsigma].state_range
			end
			for (ni,nsigma) in enumerate(nodes_to_constraint)
				ranges[nsigma] = states[ni]
			end
			itstspace = product(ranges...)
			for leave in itstspace
				init_net!(net;update_mode=update_mode,init_h=init_h,init_array=collect(leave))
				evolve_net!(net,steps;keep=false,update_mode=update_mode,sim_result=sim_result,noise_vector=noise_vector,forcing_rhythms=forcing_rhythms)
				if length_obs==1
					push!(obs,sim_result[index_node_obs,:]...)
				else
					push!(obs,(tuple(sim_result[index_node_obs,ti]...) for ti in 1:t_size)...)
				end
				#push!(obs,sim_result[index_node_obs,:]...)
				r += 1
				if nrand_init>=10 && mod(r,round(Int,nrand_init/10))==0
					@printf("%0.2f%% progress, %d out of %d initial conditions explored\n", 100*r/nrand_init, r, nrand_init)
				end
			end
			nrand_init = r
		end
		return reshape(obs,round(Int,length(obs)/nrand_init),nrand_init)
	end
end


__precompile__()

module AutoUrban

using AutomotiveSimulator
using AutomotiveVisualization
using EzXML
using Printf
import Records: QueueRecord, Frame
using LinearAlgebra
using Distributions
import Cairo

export
	LatLonAccelDirection,
	AccelSteeringDirection,
    NextState

include("actions.jl")

export
    initialize_XML,
    get_lane_length,
    handle_junctions,
    convert_roadway!,
    Connection,
    Junction,
    connect_two_lane_general!,
    connect_two_seg!,
    connect_two_seg_general!,
    add_connection!,
    add_junction!,
    gen_connected_lanes,
    gen_intersection,
    gen_loop_roadway,
    add_line!,
    in_lanes,
    get_max_curvature

include("roadway/roadway_generation.jl")
include("roadway/roadway_perception.jl")
include("roadway/roadway_intersection.jl")
include("roadway/convert2xodr.jl")

export
    AccSteerDriver,
    IDMDriver,
    UrbanDriver,
    MultiPtsDriver,
    MultiPtsTurningDriver,
    MOBILDriver,
    excute_action!

include("drivermodels/acc_steer_drivers.jl")
include("drivermodels/IDMDriver.jl")
include("drivermodels/MOBILDriver.jl")
include("drivermodels/urban_drivers.jl")
include("drivermodels/multiPts_driver.jl")
include("drivermodels/multiPts_functions_GD.jl")
include("drivermodels/waypoints_setting_functions.jl")
include("drivermodels/multiPtsTurning_driver.jl")
include("drivermodels/render_drivers.jl")

end