# Creation of the automaton types
#@everywhere @eval abstract type EdgeEuclideanDistanceAutomaton2 <: Edge end
@everywhere struct EdgeEuclideanDistanceAutomaton2 <: Edge
    transitions::TransitionSet
    check_constraints::CheckConstraintsFunction
    update_state!::UpdateStateFunction
end
@everywhere @eval $(MarkovProcesses.generate_code_lha_type_def(:EuclideanDistanceAutomaton2,:EdgeEuclideanDistanceAutomaton2))

function create_euclidean_distance_automaton_2(m::ContinuousTimeModel, timeline::AbstractVector{Float64}, observations::AbstractVector{Float64}, sym_obs::VariableModel)
    # Requirements for the automaton
    @assert sym_obs in m.g "$(sym_obs) is not observed."
    @assert length(timeline) == length(observations) "Timeline and observations vectors don't have the same length"
    nbr_observations = length(observations)

    # Automaton types and functions
    model_name = Symbol(typeof(m))
    lha_name = :EuclideanDistanceAutomaton2
    edge_type = :EdgeEuclideanDistanceAutomaton2
    check_constraints = Symbol("check_constraints_$(lha_name)")
    update_state! = Symbol("update_state_$(lha_name)!")

    # Locations
    locations = [:l0, :lfinal]
    for i = 1:nbr_observations
        push!(locations, Symbol("l$(i)"))
    end

    ## Invariant predicates
    @everywhere true_inv_predicate(x::Vector{Int}) = true
    Λ_F = Dict{Location,InvariantPredicateFunction}()
    for loc in locations
        Λ_F[loc] = getfield(Main, :true_inv_predicate)
    end

    ## Init and final loc
    locations_init = [:l0]
    locations_final = [:lfinal]

    map_var_automaton_idx = Dict{VariableAutomaton,Int}(:t => 1, :n => 2, :d => 3)
    vector_flow = [1.0, 0.0, 0.0]
    flow = Dict{Location, Vector{Float64}}()
    for loc in locations
        flow[loc] = vector_flow
    end

    ## Edges
    idx_obs_var = getfield(m, :map_var_idx)[sym_obs]
    to_idx(var::Symbol) = map_var_automaton_idx[var] 

    id = MarkovProcesses.newid()
    basename_func = "$(model_name)_$(id)"
    edge_name(from_loc::Location, to_loc::Location, edge_number::Int) = 
    Symbol("Edge_$(lha_name)_$(basename_func)_$(from_loc)$(to_loc)_$(edge_number)")
    function check_constraints(from_loc::Location, to_loc::Location, edge_number::Int)
        return Symbol("check_constraints_$(edge_type)_$(from_loc)$(to_loc)_$(edge_number)_$(model_name)_$(id)")
    end
    function update_state!(from_loc::Location, to_loc::Location, edge_number::Int)
        return Symbol("update_state_$(edge_type)_$(from_loc)$(to_loc)_$(edge_number)_$(model_name)_$(id)!")
    end

    loc_nbr_obs = Symbol("l$(nbr_observations)")
    meta_funcs = quote
        # l0 loc
        # l0 => l1
        #struct $(edge_name(:l0, :l1, 1)) <: $(edge_type) transitions::Union{Nothing,Vector{Symbol}} end
        @everywhere $(check_constraints(:l0, :l1, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = true
        @everywhere $(update_state!(:l0, :l1, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = 
        (S_values[$(to_idx(:n))] = x[$(idx_obs_var)];
         S_values[$(to_idx(:d))] = 0.0;
         :l1)

        # lnbr_obs => lfinal
        #struct $(edge_name(loc_nbr_obs, :lfinal, 1)) <: $(edge_type) transitions::Union{Nothing,Vector{Symbol}} end
        @everywhere $(check_constraints(loc_nbr_obs, :lfinal, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = 
        S_values[$(to_idx(:t))] >= $(timeline[nbr_observations])
        @everywhere $(update_state!(loc_nbr_obs, :lfinal, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = 
        (S_values[$(to_idx(:d))] = S_values[$(to_idx(:d))]+(S_values[$(to_idx(:n))]-$(observations[nbr_observations]))^2;
         S_values[$(to_idx(:d))] = sqrt(S_values[$(to_idx(:d))]);
         :lfinal)

        # lnbr_obs => lnbr_obs
        #struct $(edge_name(loc_nbr_obs, loc_nbr_obs, 1)) <: $(edge_type) transitions::Union{Nothing,Vector{Symbol}} end
        @everywhere $(check_constraints(loc_nbr_obs, loc_nbr_obs, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = true 
        @everywhere $(update_state!(loc_nbr_obs, loc_nbr_obs, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = 
        (S_values[$(to_idx(:n))] = x[$(idx_obs_var)];
         $(Meta.quot(loc_nbr_obs)))
    end
    eval(meta_funcs)

    @eval begin
        map_edges = Dict{Location,Dict{Location,Vector{$(edge_type)}}}()
        for loc in $(locations)
            map_edges[loc] = Dict{Location,Vector{$(edge_type)}}()
        end

        # l0 loc
        # l0 => l1
        edge1 = EdgeEuclideanDistanceAutomaton2(nothing, $(check_constraints(:l0, :l1, 1)), $(update_state!(:l0, :l1, 1)))
        map_edges[:l0][:l1] = [edge1]

        # lnbr_obs => lfinal
        edge1 = EdgeEuclideanDistanceAutomaton2(nothing, $(check_constraints(loc_nbr_obs, :lfinal, 1)), $(update_state!(loc_nbr_obs, :lfinal, 1)))
        map_edges[$(Meta.quot(loc_nbr_obs))][:lfinal] = [edge1]
        # lnbr_obs => lnbr_obs
        edge1 = EdgeEuclideanDistanceAutomaton2([:ALL], $(check_constraints(loc_nbr_obs, loc_nbr_obs, 1)), $(update_state!(loc_nbr_obs, loc_nbr_obs, 1)))
        map_edges[$(Meta.quot(loc_nbr_obs))][$(Meta.quot(loc_nbr_obs))] = [edge1]
    end

    for i = 1:(nbr_observations-1)
        loci = Symbol("l$(i)")
        locip1 = Symbol("l$(i+1)")
        meta_funcs = quote
            #struct $(edge_name(loci, locip1, 1)) <: $(edge_type) transitions::Union{Nothing,Vector{Symbol}} end
            @everywhere $(check_constraints(loci, locip1, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) =
            S_values[$(to_idx(:t))] >= $(timeline[i])
            @everywhere $(update_state!(loci, locip1, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) =
            (S_values[$(to_idx(:d))] = S_values[$(to_idx(:d))]+(S_values[$(to_idx(:n))]-$(observations[i]))^2;
             $(Meta.quot(locip1)))

            #struct $(edge_name(loci, loci, 1)) <: $(edge_type) transitions::Union{Nothing,Vector{Symbol}} end
            @everywhere $(check_constraints(loci, loci, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = true 
            @everywhere $(update_state!(loci, loci, 1))(S_time::Float64, S_values::Vector{Float64}, x::Vector{Int}, p::Vector{Float64}) = 
            (S_values[$(to_idx(:n))] = x[$(idx_obs_var)];
             $(Meta.quot(loci)))
        end
        eval(meta_funcs)

        # loci => loci+1
        edge1 = EdgeEuclideanDistanceAutomaton2(nothing, getfield(Main, check_constraints(loci, locip1, 1)), getfield(Main, update_state!(loci, locip1, 1)))
        map_edges[loci][locip1] = [edge1]
        # loci => loci
        edge1 = EdgeEuclideanDistanceAutomaton2([:ALL], getfield(Main, check_constraints(loci, loci, 1)), getfield(Main, update_state!(loci, loci, 1)))
        map_edges[loci][loci] = [edge1]
    end

    ## Constants
    constants = Dict{Symbol,Float64}(:nbr_obs => nbr_observations)

    map_edges_transitions = Dict{Symbol, Dict{Symbol,Vector{TransitionSet}}}()
    map_edges_check_constraints = Dict{Symbol, Dict{Symbol,Vector{CheckConstraintsFunction}}}()
    map_edges_update_state = Dict{Symbol, Dict{Symbol,Vector{UpdateStateFunction}}}()

    # Updating types and simulation methods
    @everywhere @eval $(MarkovProcesses.generate_code_synchronized_model_type_def(model_name, lha_name))
    @everywhere @eval $(MarkovProcesses.generate_code_next_state(lha_name, edge_type))
    @everywhere @eval $(MarkovProcesses.generate_code_synchronized_simulation(model_name, lha_name, edge_type, m.f!, m.isabsorbing))

    A = EuclideanDistanceAutomaton2(m.transitions, locations, Λ_F, locations_init, locations_final, 
                                    map_var_automaton_idx, flow, 
                                    map_edges, map_edges_transitions, map_edges_check_constraints, map_edges_update_state,
                                    constants, m.map_var_idx)
    return A
end