"""
    Relax

Used to dispatch relaxations to a standard
"""
struct Relax <: AbstractCacheAttribute end

"""
    RelaxAA
"""
struct RelaxAA <: AbstractCacheAttribute
    v::Vector{Int}
end
RelaxAA() = RelaxAA(Int[])

struct RelaxAAInfo <: AbstractCacheAttribute
    v::Vector{Int}
end
RelaxAAInfo() = RelaxAAInfo(Int[])

"""
    RelaxMulEnum
"""
struct RelaxMulEnum <: AbstractCacheAttribute
    v::Vector{Int}
    use_info::Bool
end
RelaxMulEnum() = RelaxMulEnum(Int[], false)
RelaxMulEnum(x::Vector{Int64}) = RelaxMulEnum(x, false)
RelaxMulEnum(x::Bool) = RelaxMulEnum(Int[], x)

struct RelaxMulEnumInner <: AbstractCacheAttribute
    v::Vector{Int}
    use_info::Bool
end
RelaxMulEnumInner() = RelaxMulEnumInner(Int[], false)
RelaxMulEnumInner(x::Vector{Int64}) = RelaxMulEnumInner(x, false)
RelaxMulEnumInner(x::Bool) = RelaxMulEnumInner(Int[], x)

const RELAX_ATTRIBUTE = Union{Relax,RelaxAA,RelaxAAInfo,RelaxMulEnum,RelaxMulEnumInner,RelaxInterval}
const RELAX_ONLY_ATTRIBUTE = Union{Relax,RelaxAA,RelaxAAInfo,RelaxMulEnum,RelaxMulEnumInner}

mutable struct MCBoxPnt{Q,N,T}
    v::MC{N,T}
    box::Vector{MC{N,T}}
end

cv(x::MCBoxPnt{Q,N,T}) where {Q,N,T} = x.v.cv
cc(x::MCBoxPnt{Q,N,T}) where {Q,N,T} = x.v.cc
cv_grad(x::MCBoxPnt{Q,N,T}) where {Q,N,T} = x.v.cv_grad
cc_grad(x::MCBoxPnt{Q,N,T}) where {Q,N,T} = x.v.cc_grad

cv(x::MC{N,T}) where {N,T} = x.cv
cc(x::MC{N,T}) where {N,T} = x.cc
cv_grad(x::MC{N,T}) where {N,T} = x.cv_grad
cc_grad(x::MC{N,T}) where {N,T} = x.cc_grad

function zero(::Type{MCBoxPnt{Q,N,T}}) where {Q,N,T}
    MCBoxPnt{Q,N,T}(zero(MC{N,T}), zeros(MC{N,T}, Q))
end
function setindex!(d::MCBoxPnt{Q,N,T}, x::MC{N,T}, i::Int) where {Q,N,T}
    d.box[i] = x
end

Base.@kwdef mutable struct RelaxCache{V,N,T<:RelaxTag} <: AbstractCache
    ic::IntervalCache                      = IntervalCache{Float64}()
    dp::Vector{Float64}                    = Float64[]
    dP::Vector{Interval{Float64}}          = Interval{Float64}[]
    _set::Vector{MC{N,T}}                  = MC{N,T}[]
    _num::Vector{Float64}                  = Float64[]
    _is_num::Vector{Bool}                  = Bool[]
    _info::Vector{V}                       = V[]
    _subexpression_set::Dict{Int,MC{N,T}}  = Dict{Int,MC{N,T}}()
    _subexpression_num::Dict{Int,Float64}  = Dict{Int,Float64}()
    _subexpression_is_num::Dict{Int,Bool}  = Dict{Int,Bool}()
    _subexpression_info::Dict{Int,MC{N,T}} = Dict{Int,MC{N,T}}()
    _cv_grad_buffer::Vector{Float64}       = Float64[]
    _cc_grad_buffer::Vector{Float64}       = Float64[]
    _set_mv_buffer::Vector{MC{N,T}}        = MC{N,T}[]
    _num_mv_buffer::Vector{Float64}        = Float64[]
    _info_mv_buffer::Vector{V}             = V[]
    _mult_temp::V                          = zero(V)
    ϵ_sg::Float64                          = 1E-6
    post::Bool                             = false
    cut::Bool                              = false
    cut_interval::Bool                     = false
    first_eval::Bool                       = true
    use_apriori_mul::Bool                  = false
end
function RelaxCache{N,T}(::Relax, n::Int, m::Int, p::Int) where {N,T<:RelaxTag}
    RelaxCache{N,T}(_set                 = zeros(MC{N,T}, n),
                    _num                 = zeros(Float64, n),
                    _is_num              = zeros(Bool, n),
                    _info                = zeros(MC{N,T}, n),
                    _cv_grad_buffer      = zeros(Float64, p),
                    _cc_grad_buffer      = zeros(Float64, p),
                    _set_mv_buffer       = zeros(MC{N,T}, p),
                    _num_mv_buffer       = zeros(p),
                    _info_mv_buffer      = zeros(MC{N,T}, p),
                    )
end
function initialize!(c::RelaxCache{V,N,T}, g::DirectedTree) where {V,N,T<:RelaxTag}

    n = node_count(g)
    m = dep_subexpr_count(g)
    p = length(sparsity(g, 1))

    c._num                 = zeros(n)
    c._is_num              = zeros(Bool, n)
    c._set                 = zeros(MC{N,T}, n)
    c._cv_grad_buffer      = zeros(p)
    c._cc_grad_buffer      = zeros(p)
    c._set_mv_buffer       = zeros(MC{N,T}, p)
    c._num_mv_buffer       = zeros(p)
    c._mult_temp           = zero(V)

    for i = 1:n
        ni = node(g, i)
        if node_class(ni) == CONSTANT
            c._is_num[i] = true
            c._num[i] = g.constant_values[first_index(ni)]
        end
    end

    for i = 1:n
        push!(c._info, zero(V))
    end
    for i = 1:p
        push!(c._info_mv_buffer, zero(V))
    end
    return
end

function copy_subexpr!(rc::RelaxCache{V,N,T}, ds, dn, din, di) where {V,N,T<:RelaxTag}
    rc._subexpression_set = ds
    rc._subexpression_num = dn
    rc._subexpression_is_num = din
    rc._subexpression_info = di
    return
end

mc_type(rc::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag} = MC{N,T}

###
###
### Access functions for RelaxCache.
###
###
set(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag}  = b._set[i]
num(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag}  = b._num[i]
set_or_num(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = !is_num(b, i) ? set(b,i) : num(b,i)
info(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = b._info[i]
is_num(b::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag}       = b._is_num
is_num(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = b._is_num[i]
interval(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = Interval{Float64}(set(b, i))
subexpression_set(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = b._subexpression_set[i]
subexpression_num(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = b._subexpression_num[i]
subexpression_is_num(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = b._subexpression_is_num[i]
function Base.setindex!(b::RelaxCache{V,N,T}, v::Float64, i::Int) where {V,N,T<:RelaxTag}
    b._is_num[i] = true
    b._num[i] = v
    nothing
end
function Base.setindex!(b::RelaxCache{V,N,T}, v::MC{N,T}, i::Int) where {V,N,T<:RelaxTag}
    b._is_num[i] = false
    b._set[i] = v
    nothing
end
store_info!(b::RelaxCache{V,N,T}, v::V, i::Int) where {V,N,T<:RelaxTag} = (b._info[i] = v; nothing)

function store_subexpression_set!(b::RelaxCache{V,N,T}, v::MC{N,T}, i::Int) where {V,N,T<:RelaxTag}
    b._subexpression_is_num[i] = false
    b._subexpression_set[i] = v
    nothing
end
function store_subexpression_num!(b::RelaxCache{V,N,T}, v, i::Int) where {V,N,T<:RelaxTag}
    b._subexpression_is_num[i] = true
    b._subexpression_num[i] = v
    nothing
end

first_eval(t::RELAX_ONLY_ATTRIBUTE, b::RelaxCache) = b.first_eval
first_eval(t::RelaxInterval, b::RelaxCache) = b.first_eval
val(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = val(b.ic, i)
lbd(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = lbd(b.ic, i)
ubd(b::RelaxCache{V,N,T}, i::Int) where {V,N,T<:RelaxTag} = ubd(b.ic, i)
_set_input(b::RelaxCache{V,N,T}, n::Int) where {V,N,T<:RelaxTag} = view(b._set_mv_buffer, 1:n)
_num_input(b::RelaxCache{V,N,T}, n::Int) where {V,N,T<:RelaxTag} = view(b._num_mv_buffer, 1:n)

include(joinpath(@__DIR__, "utilities.jl"))
include(joinpath(@__DIR__, "forward_propagation.jl"))
include(joinpath(@__DIR__, "reverse_propagation.jl"))

function fprop!(t::RELAX_ATTRIBUTE, g::DAT, b::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag}
    for k = node_count(g):-1:1
        c = node_class(g, k)
        (c == EXPRESSION)    && (fprop!(t, Expression(), g, b, k);    continue)
        (c == VARIABLE)      && (fprop!(t, Variable(), g, b, k);      continue)
        (c == SUBEXPRESSION) && (fprop!(t, Subexpression(), g, b, k); continue)
    end
    nothing
end

function rprop!(t::RELAX_ATTRIBUTE, g::DAT, b::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag}
    flag = r_init!(t, g, b)
    for k = 1:node_count(g)
        nt = node_class(g, k)
        if nt == EXPRESSION
            flag = rprop!(t, Expression(), g, b, k)
        elseif nt == VARIABLE
            flag = rprop!(t, Variable(), g, b, k)
        elseif nt == SUBEXPRESSION
            flag = rprop!(t, Subexpression(), g, b, k)
        end
    end
    return flag
end

function display_expr(g, i)
    n = node(g, i)
    nc = node_class(n)
    if nc == VARIABLE
        return "x[$(first_index(n))]"
    elseif nc == PARAMETER
        return "p[$(first_index(n))]"
    elseif nc == EXPRESSION
        ex_typ = ex_type(n)
        ex_sym = ALL_ATOM_DICT[ex_typ]
        ctup = tuple(children(n)...)
        return "$(ex_sym)$(ctup)"
    elseif nc == CONSTANT
        return "c[$(first_index(n))]"
    end
end

function display_table!(g::DAT, b::RelaxCache{V,N,T}) where {V,N,T<:RelaxTag}
    nc = node_count(g)
    val = [b._is_num[i] ? b._num[i] : b._set[i] for i in 1:nc]
    exr = [display_expr(g, i)  for i in 1:nc]
    data = hcat(exr, b._is_num, val)
    show(pretty_table(data, header = ["Expr", "Is Num", "Val"]; show_row_number = true))
end


# Insert graph data into an adjacency table.
using JLD

# Iterate through files
(@isdefined Nfile) || (Nfile = 8)
for i = 1:Nfile
    inserttime = @elapsed begin

        # Create filename
        fname = joinpath(Base.source_dir(),"data", string(i))
        println("On file: "*string(i))

        # Load associative array.
        #E = loadassoc(fname*".E.jld")
        E = load(fname*".E.jld")["E"]

        # Insert associative array.
        put(Tedge,E)

        #Compute in and out degree.
        Edeg = putCol(sum(E,1)',"Degree,")

        # Accumulate in and out degrees.
        put(TedgeDeg,Edeg)

    end

    println("Time: "*string(inserttime)*", Edges/sec: "*string((2*nnz(E)+nnz(Edeg))./inserttime))
end

println("Table entries: "*string(nnz(Tedge)));


metadata = fill("metadata", 4)
an1 = ArrayNode(rand(Float32, 3, 4))
b = BagNode(an1, [1:4, 0:-1], metadata)
an2 = ArrayNode(NGramMatrix(["test", "skunk", "mill", "julia"], 3, 10, 17))
wb = WeightedBagNode(an2, [1:2,3:4], Float32[1,2,3,4], metadata)
n1 = ProductNode((b=b,wb=wb))
an3 = ArrayNode(SparseMatrixCSC(rand(Float32, 10, 2)))
n2 = ProductNode((n1, an3))

n2m = reflectinmodel(n2)
n1m, an3m = n2m.ms
bm, wbm = n1m.ms
an1m = bm.im
an2m = wbm.im

ss = ["GGGCGGCGA", "CCTCGCGGG", "TTTTCGCTATTTATGAAAATT", "TTCCGGTTTAAGGCGTTTCCG"]
base_arr = collect("ACGT")
possibilities = product(base_arr, base_arr, base_arr) |> collect |> x->reshape(x, 1, :) .|> (x->reduce(*, x)) |> x->x[:]

struct NumberNode
    n::Int
    chs::Vector{NumberNode}
end
import HierarchicalUtils: NodeType, LeafNode, InnerNode, noderepr, children
NodeType(::Type{NumberNode}) = InnerNode()
noderepr(n::NumberNode) = string(n.n)
children(n::NumberNode) = n.chs

function Mill.unpack2mill(ds::LazyNode{:Codons})
    s = ds.data
    ss = map(x -> reduce.(*, partition(x, 3)),s)
    x = reduce(hcat, map(x->Flux.onehotbatch(x, possibilities), ss))
    BagNode(ArrayNode(x), Mill.length2bags(length.(ss)))
end

# specification of printing
NodeType(::Type{<:LazyNode{:Codons}}) = InnerNode()
children(n::LazyNode{:Codons}) = (n.data,)
NodeType(n::Vector{<:AbstractString}) = InnerNode()
noderepr(n::Vector{<:AbstractString}) = "Array $(length(n)) items"
NodeType(n::AbstractString) = LeafNode()
children(n::Vector{<:AbstractString}) = (n...,)

t2 = treemap(n2, n2m) do (k1, k2), chs
    NumberNode(rand(1:10), collect(chs))
end

function buf_printtree(data; kwargs...)
    buf = IOBuffer()
    printtree(buf, data; kwargs...)
    String(take!(buf))
end

@testset "list traversal" begin
    for (n1, n2) in NodeIterator(n2, n2m)
        @test list_traversal(n1) == list_traversal(n2)
    end
end

@testset "getindex on strings" begin
    @test n2[""] === n2
    @test n2["E"] === n1
    @test n2["I"] === b
    @test n2["K"] === an1
    @test n2["M"] === wb
    @test n2["O"] === an2
    @test n2["U"] === an3

    @test n2m[""] === n2m
    @test n2m["E"] === n1m
    @test n2m["I"] === bm
    @test n2m["K"] === an1m
    @test n2m["M"] === wbm
    @test n2m["O"] === an2m
    @test n2m["U"] === an3m
end

@testset "children" begin
    @test Set(children(n2)) == Set([n1, an3])
    @test Set(children(n1)) == Set([b, wb])
    @test Set(children(b)) == Set([an1])
    @test Set(children(wb)) == Set([an2])

    @test Set(children(n2m)) == Set([n1m, an3m])
    @test Set(children(n1m)) == Set([bm, wbm])
    @test Set(children(bm)) == Set([an1m])
    @test Set(children(wbm)) == Set([an2m])
end

@testset "nchildren" begin
    @test nchildren(an1) == nchildren(an2) == nchildren(an3) == 0
    @test nchildren(b) == nchildren(wb) == 1
    @test nchildren(n1) == nchildren(n2) == 2

    @test nchildren(an1m) == nchildren(an2m) == nchildren(an3m) == 0
    @test nchildren(bm) == nchildren(wbm) == 1
    @test nchildren(n1m) == nchildren(n2m) == 2
end

@testset "nnodes" begin
    @test nnodes(an1) == nnodes(an2) == nnodes(an3) == 1
    @test nnodes(wb) == nnodes(b) == 2
    @test nnodes(n1) == nnodes(b) + nnodes(wb) + 1
    @test nnodes(n2) == nnodes(n1) + nnodes(an3) + 1

    @test nnodes(an1m) == nnodes(an2m) == nnodes(an3m) == 1
    @test nnodes(wbm) == nnodes(bm) == 2
    @test nnodes(n1m) == nnodes(bm) + nnodes(wbm) + 1
    @test nnodes(n2m) == nnodes(n1m) + nnodes(an3m) + 1
end

@testset "nleafs" begin
    @test nleafs(an1) == nleafs(an2) == nleafs(an3) == 1
    @test nleafs(wb) == nleafs(b) == 1
    @test nleafs(n1) == nleafs(b) + nleafs(wb)
    @test nleafs(n2) == nleafs(n1) + nleafs(an3)

    @test nleafs(an1m) == nleafs(an2m) == nleafs(an3m) == 1
    @test nleafs(wbm) == nleafs(bm) == 1
    @test nleafs(n1m) == nleafs(bm) + nleafs(wbm)
    @test nleafs(n2m) == nleafs(n1m) + nleafs(an3m)
end

@testset "NodeIterator" begin
    @test Set(NodeIterator(n2)) == Set([an1, an2, an3, b, wb, n1, n2])

    @test Set(NodeIterator(n2m)) == Set([an1m, an2m, an3m, bm, wbm, n1m, n2m])
end

@testset "LeafIterator" begin
    @test Set(LeafIterator(n2)) == Set([an1, an2, an3])

    @test Set(LeafIterator(n2m)) == Set([an1m, an2m, an3m])
end

@testset "TypeIterator" begin
    @test Set(TypeIterator(AbstractMillNode, n2)) == Set(NodeIterator(n2))
    @test Set(TypeIterator(AbstractBagNode, n2)) == Set([b, wb])
    @test Set(TypeIterator(AbstractProductNode, n2)) == Set([n1, n2])

    @test Set(TypeIterator(AbstractMillModel, n2m)) == Set(NodeIterator(n2m))
    @test Set(TypeIterator(BagModel, n2m)) == Set([bm, wbm])
    @test Set(TypeIterator(ProductModel, n2m)) == Set([n1m, n2m])
end

@testset "Iteration over multiple trees" begin
    @test NodeIterator(n2, n2m) |> collect == collect(zip(NodeIterator(n2) |> collect, NodeIterator(n2m) |> collect))
end

@testset "printtree" begin
    @test buf_printtree(n2, trav=true) ==
        """
        ProductNode with 2 obs [""]
          ├── ProductNode with 2 obs ["E"]
          │     ├─── b: BagNode with 2 obs ["I"]
          │     │         └── ArrayNode(3×4 Array with Float32 elements) ["K"]
          │     └── wb: WeightedBagNode with 2 obs ["M"]
          │               └── ArrayNode(17×4 NGramMatrix with Int64 elements) ["O"]
          └── ArrayNode(10×2 SparseMatrixCSC with Float32 elements) ["U"]
        """

    @test buf_printtree(n2m, trav=true) ==
        """
        ProductModel … ↦ ArrayModel(Dense(20, 10)) [""]
          ├── ProductModel … ↦ ArrayModel(Dense(20, 10)) ["E"]
          │     ├─── b: BagModel … ↦ BagCount([SegmentedMean(10); SegmentedMax(10)]) ↦ ArrayModel(Dense(21, 10)) ["I"]
          │     │         └── ArrayModel(Dense(3, 10)) ["K"]
          │     └── wb: BagModel … ↦ BagCount([SegmentedMean(10); SegmentedMax(10)]) ↦ ArrayModel(Dense(21, 10)) ["M"]
          │               └── ArrayModel(Dense(17, 10)) ["O"]
          └── ArrayModel(Dense(10, 10)) ["U"]
        """
end

@testset "LazyNode" begin
	ds = LazyNode{:Codons}(ss)
	m = Mill.reflectinmodel(ds)
	@test nchildren(ds) == 1
	@test nleafs(ds) == 4

    @test buf_printtree(ds, trav=true) ==
        """
        LazyNode{Codons} with 4 obs [""]
          └── Array 4 items ["U"]
                ├── "GGGCGGCGA" ["Y"]
                ├── "CCTCGCGGG" ["c"]
                ├── "TTTTCGCTATTTATGAAAATT" ["g"]
                └── "TTCCGGTTTAAGGCGTTTCCG" ["k"]
        """

    @test buf_printtree(m, trav=true) ==
        """
        LazyModel{Codons} [""]
          └── BagModel … ↦ BagCount([SegmentedMean(10); SegmentedMax(10)]) ↦ ArrayModel(Dense(21, 10)) ["U"]
                └── ArrayModel(Dense(64, 10)) ["k"]
        """
end


using Stopping

import Stopping: _init_max_counters, fill_in!, _resources_check!, _unbounded_problem_check!, _optimality_check
import Stopping: start!, stop!, update_and_start!, update_and_stop!, reinit!, status

"""
Type: MPCCStopping (specialization of GenericStopping)
Methods: start!, stop!, update_and_start!, update_and_stop!, fill_in!, reinit!, status

Stopping structure for non-linear programming problems using NLPModels.
    Input :
       - pb         : an AbstractMPCCModel
       - state      : The information relative to the problem, see GenericState
       - (opt) meta : Metadata relative to stopping criterion, see *StoppingMeta*.
       - (opt) main_stp : Stopping of the main loop in case we consider a Stopping
                          of a subproblem.
                          If not a subproblem, then nothing.
       - (opt) listofstates : ListStates designed to store the history of States.
       - (opt) user_specific_struct : Contains any structure designed by the user.

`MPCCStopping(:: AbstractMPCCModel, :: AbstractState; meta :: AbstractStoppingMeta = StoppingMeta(), main_stp :: Union{AbstractStopping, Nothing} = nothing, list :: Union{ListStates, Nothing} = nothing, user_specific_struct :: Any = nothing, kwargs...)`

 Note:
 * optimality_check : takes two inputs (AbstractMPCCModel, MPCCAtX)
 and returns a Float64 to be compared at 0.
 * designed for MPCCAtX State. Constructor checks that the State has the
 required entries.

 Warning:
 * optimality_check does not necessarily fill in the State.
 """
mutable struct MPCCStopping <: AbstractStopping

    # problem
    pb :: AbstractMPCCModel

    # Common parameters
    meta      :: AbstractStoppingMeta

    # current state of the problem
    current_state :: AbstractState

    # Stopping of the main problem, or nothing
    main_stp :: Union{AbstractStopping, Nothing}

    listofstates :: Union{ListStates, Nothing}

    # User-specific structure
    user_specific_struct :: Any

    function MPCCStopping(pb            :: AbstractMPCCModel,
                         current_state  :: AbstractState;
                         meta           :: AbstractStoppingMeta = StoppingMeta(;max_cntrs = _init_max_counters_mpcc(), optimality_check = MStat),
                         main_stp       :: Union{AbstractStopping, Nothing} = nothing,
                         listofstates   :: Union{ListStates, Nothing} = nothing,
                         user_specific_struct :: Any = nothing,
                         kwargs...)

        if !(isempty(kwargs))
           meta = StoppingMeta(;max_cntrs = _init_max_counters_mpcc(), optimality_check = MStat, kwargs...)
        end

        #current_state is an AbstractState with requirements
        try
            current_state.evals
            current_state.fx, current_state.gx, current_state.Hx
            #if there are bounds:
            current_state.mu
            if pb.meta.ncon > 0 #if there are constraints
               current_state.Jx, current_state.cx, current_state.lambda
            end
        catch
            throw("error: missing entries in the given current_state")
        end

        return new(pb, meta, current_state, main_stp, listofstates, user_specific_struct)
    end

end

"""
MPCCStopping(pb): additional default constructor
The function creates a Stopping where the State is by default and the
optimality function is the function KKT().

key arguments are forwarded to the classical constructor.
"""
function MPCCStopping(pb :: AbstractMPCCModel; kwargs...)

 #Create a default MPCCAtX
 nlp_at_x = MPCCAtX(pb.meta.x0, pb.meta.y0)
 admissible = (x,y; kwargs...) -> MStat(x,y; kwargs...)

 return MPCCStopping(pb, nlp_at_x, optimality_check = admissible; kwargs...)
end

"""
_init_max_counters_mpcc(): initialize the maximum number of evaluations on each of
                        the functions present in the MPCCCounters.
"""
function _init_max_counters_mpcc(; obj     :: Int64 = 40000,
                                   grad    :: Int64 = 40000,
                                   cons    :: Int64 = 40000,
                                   consG   :: Int64 = 40000,
                                   consH   :: Int64 = 40000,
                                   jcon    :: Int64 = 40000,
                                   jgrad   :: Int64 = 40000,
                                   jac     :: Int64 = 40000,
                                   jacG    :: Int64 = 40000,
                                   jacH    :: Int64 = 40000,
                                   jprod   :: Int64 = 40000,
                                   jGprod  :: Int64 = 40000,
                                   jHprod  :: Int64 = 40000,
                                   jtprod  :: Int64 = 40000,
                                   jGtprod :: Int64 = 40000,
                                   jHtprod :: Int64 = 40000,
                                   hess    :: Int64 = 40000,
                                   hessG   :: Int64 = 40000,
                                   hessH   :: Int64 = 40000,
                                   hprod   :: Int64 = 40000,
                                   hGprod  :: Int64 = 40000,
                                   hHprod  :: Int64 = 40000,
                                   jhprod  :: Int64 = 40000,
                                   sum     :: Int64 = 40000*11)

  cntrs = Dict([(:neval_obj,       obj), (:neval_grad,   grad),
                (:neval_cons,     cons), (:neval_jcon,   jcon),
                (:neval_consG,   consG), (:neval_consH,   consH),
                (:neval_jgrad,   jgrad), (:neval_jac,    jac),
                (:neval_jacG,     jacG), (:neval_jacH,     jacH),
                (:neval_jprod,   jprod), (:neval_jtprod, jtprod),
                (:neval_jGprod, jGprod), (:neval_jGtprod, jGtprod),
                (:neval_jHprod, jHprod), (:neval_jHtprod, jHtprod),
                (:neval_hess,     hess), (:neval_hprod,  hprod),
                (:neval_hessG,   hessG), (:neval_hessH,   hessH),
                (:neval_hGprod,  hGprod),(:neval_hHprod,  hHprod),
                (:neval_jhprod, jhprod), (:neval_sum,    sum)])

 return cntrs
end

"""
fill_in!: a function that fill in the required values in the State

kwargs are forwarded to _compute_mutliplier function
"""
function fill_in!(stp     :: MPCCStopping,
                  x       :: Iterate;
                  fx      :: Iterate    = nothing,
                  gx      :: Iterate    = nothing,
                  Hx      :: Iterate    = nothing,
                  cx      :: Iterate    = nothing,
                  Jx      :: Iterate    = nothing,
                  lambda  :: Iterate    = nothing,
                  mu      :: Iterate    = nothing,
                  lambdaG :: Iterate    = nothing,
                  lambdaH :: Iterate    = nothing,
                  cGx     :: Iterate    = nothing,
                  JGx     :: MatrixType = nothing,
                  cHx     :: Iterate    = nothing,
                  JHx     :: MatrixType = nothing,
                  matrix_info :: Bool   = true,
                  kwargs...)

 gfx = fx == nothing  ? obj(stp.pb, x)   : fx
 ggx = gx == nothing  ? grad(stp.pb, x)  : gx

 if Hx == nothing && matrix_info
   gHx = hess(stp.pb, x)
 else
   gHx = Hx
 end

 if stp.pb.meta.ncon > 0
     gJx = Jx == nothing ? jac_nl(stp.pb, x)  : Jx
     gcx = cx == nothing ? cons_nl(stp.pb, x) : cx
 else
     gJx = Jx
     gcx = cx
 end

 gcGx, gcHx, gJGx, gJHx = cGx, cHx, JGx, JHx
 if stp.pb.meta.ncc > 0
     gcGx = cGx == nothing ? consG(stp.pb, x) : cGx
     gcHx = cHx == nothing ? consH(stp.pb, x) : cHx
     gJGx = JGx == nothing ? jacG(stp.pb, x)  : JGx
     gJHx = JHx == nothing ? jacH(stp.pb, x)  : JHx
 end

 #update the Lagrange multiplier if one of the 2 is asked
 #if (stp.pb.meta.ncc > 0 && stp.pb.meta.ncon > 0 && has_bounds(stp.pb)) && (lambdaG == nothing || lambdaH == nothing ||lambda == nothing || mu == nothing)
 if (stp.pb.meta.ncc > 0) && (lambdaG == nothing || lambdaH == nothing ||lambda == nothing || mu == nothing)
  lb, lc, lG, lH = _compute_mutliplier(stp.pb, x, ggx, gcx, gJx, gcGx, gJGx, gcHx, gJHx, kwargs...)
 #elseif (stp.pb.meta.ncon > 0 && has_bounds(stp.pb)) && (lambda == nothing || mu == nothing)
 elseif (stp.pb.meta.ncon > 0 || has_bounds(stp.pb)) && (lambda == nothing || mu == nothing)
  lb, lc = _compute_mutliplier(stp.pb.mp, x, ggx, gcx, gJx, kwargs...)
  lG, lH = lambdaG, lambdaH
 elseif  stp.pb.meta.ncon == 0 && !has_bounds(stp.pb) && lambda == nothing
  lb, lc = mu, stp.current_state.lambda
  lG, lH = lambdaG, lambdaH
 else
  lb, lc = mu, lambda
  lG, lH = lambdaG, lambdaH
 end

 return update!(stp.current_state, x=x, fx = gfx,    gx = ggx, Hx = gHx,
                                        cx = gcx,    Jx = gJx, mu = lb,
                                        lambda = lc, lambdaG = lG, lambdaH = lH,
                                        cGx = gcGx, cHx = gcHx,
                                        JGx = gJGx, JHx = gJHx)
end

"""
_resources_check!: check if the optimization algorithm has exhausted the resources.
                   This is the MPCC specialized version that takes into account
                   the evaluation of the functions following the sum_counters
                   structure from MPCC.

Note: function uses counters in stp.pb, and update the counters in the state
"""
function _resources_check!(stp    :: MPCCStopping,
                           x      :: Iterate)

  cntrs = stp.pb.counters
  update!(stp.current_state, evals = cntrs)

  max_cntrs = stp.meta.max_cntrs

  # check all the entries in the counter
  max_f = false
  for f in fieldnames(MPCCCounters)
      max_f = max_f || (getfield(cntrs, f) > max_cntrs[f])
  end

 # Maximum number of function and derivative(s) computation
 max_evals = sum_counters(stp.pb) > max_cntrs[:neval_sum]

 # global user limit diagnostic
 stp.meta.resources = max_evals || max_f

 return stp
end

"""
_unbounded_problem_check!: This is the NLP specialized version that takes into account
                   that the problem might be unbounded if the objective or the
                   constraint function are unbounded.

Note: * evaluate the objective function if state.fx is void.
      * evaluate the constraint function if state.cx is void.
"""
function _unbounded_problem_check!(stp  :: MPCCStopping,
                                   x    :: Iterate)

 if stp.current_state.fx == nothing
	 stp.current_state.fx = obj(stp.pb, x)
 end
 f_too_large = norm(stp.current_state.fx) >= stp.meta.unbounded_threshold

 c_too_large = false
 if stp.pb.meta.ncon != 0 #if the problems has constraints, check |c(x)|
  if stp.current_state.cx == nothing
   stp.current_state.cx = cons(stp.pb, x)
  end
  c_too_large = norm(stp.current_state.cx) >= abs(stp.meta.unbounded_threshold)
 end

 stp.meta.unbounded_pb = f_too_large || c_too_large

 return stp
end

################################################################################
# Nonlinear problems admissibility functions
# Available: unconstrained_check(...), optim_check_bounded(...), KKT
################################################################################
include("mpcc_admissible_functions.jl")

################################################################################
# Functions computing Lagrange multipliers of a nonlinear problem
# Available: _compute_mutliplier(...)
################################################################################
include("nlp_compute_multiplier.jl")


# L!Type is the type of Llama types.
abstract L!Type

# L! is the type of Llama values.
abstract L!
type L!int <: L! val::Int64 end
type L!str <: L! val::String end
type L!Object <: L!
  _type::L!Type
  constr::String
  values::Array{L!, 1}
  attributes::Dict{String, L!}
  extends::Union(L!, Nothing)
end

L!Object(_type::L!Type, constr::String) =
  L!Object(_type, constr, [], Dict(), Nothing())

# A constraint on a type. Asserts `name` exists of type `_type`.
type L!Constraint
  name::String
  _type::L!Type
end

# A trait is a set of one or more constraints.
type L!Trait
  constraints::Set{L!Constraint}
end

# A type variable, for polymorphic types.
type L!TVar <: L!Type name::String end

# A constant type. Defines what constructors and attributes are legal,
# and might have a parent.
type L!TConst <: L!Type
  name::String
  constructors::Array{String, 1}
  parent::Union(L!TConst, Nothing)
  attributes::Dict{String, L!Type}
end

type L!TApply <: L!Type
  left::L!Type
  right::L!Type
end

L!TConst(name::String) = L!TConst(name, [], Nothing(), Dict())

L!TApply(t1::L!Type, t2::L!Type, t3::L!Type) = L!TApply(L!TApply(t1, t2), t3)
function L!TApply(types)
  if length(types) < 2
    error("Not enough types to apply, need at least 2")
  end
  t = L!TApply(types[1], types[2])
  for t_ in types[3:end]
    t = L!TApply(t, t_)
  end
  t
end

L!absfunc!t = L!TConst("->")
L!func!t(from::L!Type, to::L!Type) = L!TApply(L!absfunc!t, from, to)
L!int!t = L!TConst("Int")
L!str!t = L!TConst("Str")

function L!tuple(types::Array{L!Type, 1})
  tup = L!TConst("Tuple($(length(types)))")
  if length(types) == 0
    return tup
  elseif length(types) == 1
    return types[1]
  end
  L!TApply(vcat([tup], types))
end

f = L!func!t(L!int!t, L!str!t)

println(f)
println()
println(L!tuple([L!int!t, L!str!t, f]))

type L!ValueSet
  values::Dict{L!Type, L!}
end

function single(v::L!)
  L!ValueSet({v._type => v})
end

function addval(vset::L!ValueSet, v::L!)
  insert!(vset.values, v._type, v)
end

i = L!int(1)
Maybe = L!TConst("Maybe")

println("Ok!")


module STLFile

using TriMeshes

export STL_ASCII, STL_BIN

function STL_ASCII(n::Net)
	STL_ASCII(n, STDOUT)
end

function STL_ASCII(n::Net, fn::String)
	fid = open(fn, "w+")
	STL_ASCII(n, fid)
	close(fid)
end

function STL_ASCII(n::Net, io::IO)
	println(io, "solid Mesh.jl")
	for f in n.Faces
		abf = n.Vertices[f.AB.From]
		abt = n.Vertices[f.AB.To]
		bct = n.Vertices[f.BC.To]
		println(io, "facet normal 0 0 0")
		println(io, "\touter loop")
		println(io, "\t\tvertex $(abf.x), $(abf.y), $(abf.z)")
		println(io, "\t\tvertex $(abt.x), $(abt.y), $(abt.z)")
		println(io, "\t\tvertex $(bct.x), $(bct.y), $(bct.z)")
		println(io, "\tendloop")
	end
	println(io, "endsolid Mesh.jl")
end

function STL_BIN(n::Net)
	STL(n, STDOUT)
end

function STL_BIN(n::Net, fn::String)
	fid = open(fn, "w+")
	STL(n, fid)
	close(fid)
end

function STL_BIN(n::Net, io)
	for k in 1:10
		write(io, Int64(0))
	end
	write(io, Int32(length(n.Faces)))
	for f in n.Faces
		write(io, Float32(0), Float32(0), Float32(0),  f.AB.From.x, f.AB.From.y, f.AB.From.z, f.AB.To.x, f.AB.To.y, f.AB.To.z, f.BC.To.x, f.BC.To.y, f.BC.To.z)
	end
	write(io, Int16(0))
end

### STAHP

end


using JLD2
using Plots
using ProgressMeter

include("experiments/markov_chain_var.jl")
include("plot/plot_utils.jl")

function main()

    plot_settings = Dict{Symbol, Any}(
        :linewidth=>2,
        # :color=>[:purple, :blue, :purple, :blue, :green],
        # :linestyle=>[:solid, :solid, :dot, :dot, :solid],
        :color=>[:green, :blue, :green, :purple],
        :linestyle=>[:solid, :solid, :dot, :solid],
        :grid=>false,
        :size=>(432,244),
        :tick_direction=>:out,
        # :ylim=>(0,0.2),
        :legend=>false
    )
    policies = ["easy", "hard", "hardest"]
    # policies = ["hardest"]
    num_runs = 100
    measure_steps=0:5:250
    x = convert(Array{Float64,1}, collect(measure_steps))
    lck = Threads.Mutex()

    # plot_algorithms = ["IS", "IR", "BC-IR", "WIS-Buffer", "WIS-Batch"]
    plot_algorithms = ["IS", "IR", "VTrace_1.0", "WIS-Batch"]

    # Threads.@threads for policy in policies
    @showprogress 0.1 "Policy: " for policy in policies
    # for policy in policies
        ret = measure_variance_over_time_norm(
            num_runs, 15000, 1000, 16,
            policy_settings[policy][1], policy_settings[policy][2];
            measure_steps=measure_steps, random_value=false, norm_l=1)
        lock(lck)

        p = plot_dict(x,
                      filter(p -> p[1] in plot_algorithms, ret[2]),
                      filter(p -> p[1] in plot_algorithms, ret[3]);
                      plot_order=plot_algorithms,
                      plot_settings...)
        savefig(p, "markov_chain_variance_l1_$(policy).pdf")
        @save "markov_chain_variance_l1_$policy.jld" ret
        unlock(lck)
    end

end

main()


immutable Color
    r::UInt8
    g::UInt8
    b::UInt8
    a::UInt8

    function Color(r::Integer, g::Integer, b::Integer)
        r=clamp(r, 0, 255); g=clamp(g, 0, 255); b=clamp(b, 0, 255);
        new(UInt8(r), UInt8(g), UInt8(b), UInt8(255))
    end

    function Color(r::Integer, g::Integer, b::Integer, a::Integer)
        r=clamp(r, 0, 255); g=clamp(g, 0, 255); b=clamp(b, 0, 255); a=clamp(a, 0, 255)
        new(UInt8(r), UInt8(g), UInt8(b), UInt8(a))
    end
end

const black = Color(0, 0, 0)
const white = Color(255, 255, 255)
const red = Color(255, 0, 0)
const green = Color(0, 255, 0)
const blue = Color(0, 0, 255)
const yellow = Color(255, 255, 0)
const magenta = Color(255, 0, 255)
const cyan = Color(0, 255, 255)
const transparent = Color(0, 0, 0, 0)


"""
The BoundingBox type holds two Points, `corner1` and `corner2`.

    BoundingBox(;centered=true)     # the bounding box of the Drawing
    BoundingBox(s::AbstractString)  # the bounding box of a text string
    BoundingBox(pt::Array)          # the bounding box of a polygon

`BoundingBox(;centered=true)` returns a BoundingBox the same size and position as the current drawing, assuming
the origin (0, 0) is at the center.

The `centered` option defaults to `true`, and assumes the drawing is currently
centered. If `false`, the function assumes that the origin is at the top left
of the drawing. So this function doesn't really work if the current matrix has
been modified (by `translate()`, `scale()`, `rotate()` etc.)
"""
mutable struct BoundingBox
   corner1::Point
   corner2::Point
end

function Base.show(io::IO, bbox::BoundingBox)
  print(io, " ⤡ ", bbox.corner1, " : ", bbox.corner2)
end

function BoundingBox(;centered=true)
    if centered
        # ignore current matrix
        r = BoundingBox(
        Point(-current_width()/2, -current_height()/2),
        Point(current_width()/2, current_height()/2))
    else
        b = getmatrix()
        setmatrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0])
        r = BoundingBox(Point(0, 0), Point(current_width(), current_height()))
        setmatrix(b)
    end
    return r
end

"""
    BoundingBox(pointlist::Array)

Return the BoundingBox of a polygon (array of points).
"""
function BoundingBox(pointlist::Array{Point, 1})
    lowx, lowy = pointlist[1].x, pointlist[1].y
    highx, highy = pointlist[end].x, pointlist[end].y
    for p in pointlist
        p.x < lowx  && (lowx  = p.x)
        p.y < lowy  && (lowy  = p.y)
        p.x > highx && (highx = p.x)
        p.y > highy && (highy = p.y)
    end
    return BoundingBox(Point(lowx, lowy), Point(highx, highy))
end

"""
    BoundingBox(str::AbstractString)

Return a BoundingBox that just encloses a text string, given the current font
selection.
"""
function BoundingBox(str::AbstractString)
    xbearing, ybearing, width, height, xadvance, yadvance = textextents(str)
    lcorner = Point(xbearing, ybearing)
    ocorner = Point(lcorner.x + width, lcorner.y + height)
    return BoundingBox(lcorner, ocorner)
end

#state is either 1 or 2!
function Base.iterate(bbox::BoundingBox)
    return (bbox.corner1, 2)
end

function Base.iterate(bbox::BoundingBox, state)
    if state > 2
        return
    end
    state == 1 ? (bbox.corner1, state + 1) : state == 2 ? (bbox.corner2, state + 1) : error("this should never happen")
end

Base.last(bbox::BoundingBox) = bbox.corner2

Base.size(bbox::BoundingBox) = (2)

function Base.length(bbox::BoundingBox)
    return 2
end

unsafe_length(bbox::BoundingBox) = length(bbox)

Base.eltype(::Type{BoundingBox}) = Point

Base.lastindex(bbox::BoundingBox) = 2

function Base.getindex(bbox::BoundingBox, i::Int)
    if i == 1
        return bbox.corner1
    else
        return bbox.corner2
    end
end

Base.getindex(bbox::BoundingBox, i::Number) = bbox[convert(Int, i)]
Base.getindex(bbox::BoundingBox, I) = [bbox[i] for i in I]

function Base.setindex(bbox::BoundingBox, v::Point, i::Number)
    if i == 1
        return BoundingBox(v, bbox.corner2)
    else
        return BoundingBox(bbox.corner1, v)
    end
end

# basics
# adding a number to a box makes the box larger all round
+(z::Number, bb1::BoundingBox)              = BoundingBox(bb1.corner1 - z,          bb1.corner2 + z)
+(bb1::BoundingBox, z::Number)              = BoundingBox(bb1.corner1 - z,          bb1.corner2 + z)

# subtracting a number makes the box smaller all round
-(z::Number, bb1::BoundingBox)              = BoundingBox(bb1.corner1 + z,          bb1.corner2 - z)
-(bb1::BoundingBox, z::Number)              = BoundingBox(bb1.corner1 + z,          bb1.corner2 - z)

function (*)(bb::BoundingBox, s::Real)
    c = midpoint(bb...)
    newcorner1 = between(c, bb.corner1, s)
    newcorner2 = between(c, bb.corner2, s)
    return BoundingBox(newcorner1, newcorner2)
end

(*)(s::Real, bb::BoundingBox) = bb * s

function (/)(bb::BoundingBox, s::Real)
    c = midpoint(bb...)
    newcorner1 = between(c, bb.corner1, 1/s)
    newcorner2 = between(c, bb.corner2, 1/s)
    return BoundingBox(newcorner1, newcorner2)
end

(/)(s::Real, bb::BoundingBox) = bb / s

# modifying with tuples shifts the box in x and y
+(bb1::BoundingBox, shift::NTuple{2, Real}) = BoundingBox(
    Point(bb1.corner1.x + shift[1], bb1.corner1.y + shift[2]),
    Point(bb1.corner2.x + shift[1], bb1.corner2.y + shift[2]))

-(bb1::BoundingBox, shift::NTuple{2, Real}) = BoundingBox(
    Point(bb1.corner1.x - shift[1], bb1.corner1.y - shift[2]),
    Point(bb1.corner2.x - shift[1], bb1.corner2.y - shift[2]))

# if you add two bounding boxes, you get the biggest enclosing box
function +(bb1::BoundingBox, bb2::BoundingBox)
    pts = [bb1.corner1, bb1.corner2, bb2.corner1, bb2.corner2]
    lox, hix = sort!(pts, by = a -> a.x, lt  = (a, b) -> a < b)[[1, end]]
    loy, hiy = sort!(pts, by = a -> a.y, lt  = (a, b) -> a < b)[[1, end]]
    return BoundingBox(Point(lox.x, loy.y), Point(hix.x, hiy.y))
end

"""
    boxwidth(bb::BoundingBox)

Return the width of bounding box `bb`.
"""
boxwidth(bb::BoundingBox)       = abs(bb.corner1.x - bb.corner2.x)

"""
    boxheight(bb::BoundingBox)

Return the height of bounding box `bb`.
"""
boxheight(bb::BoundingBox)      = abs(bb.corner1.y - bb.corner2.y)

"""
    boxdiagonal(bb::BoundingBox)

Return the length of the diagonal of bounding box `bb`.
"""
boxdiagonal(bb::BoundingBox)    = hypot(boxwidth(bb), boxheight(bb))

"""
    boxaspectratio(bb::BoundingBox)

Return the aspect ratio (the height divided by the width) of bounding box `bb`.
"""
boxaspectratio(bb::BoundingBox) = boxheight(bb)/boxwidth(bb)

"""
    convert(Point, bbox::BoundingBox)

Convert a BoundingBox to a four-point clockwise polygon.

    convert(Vector{Point}, BoundingBox())
"""
function Base.convert(::Type{Vector{Point}}, bbox::BoundingBox)
    return [bbox.corner1,
            Point(bbox.corner2.x, bbox.corner1.y),
            bbox.corner2,
            Point(bbox.corner1.x, bbox.corner2.y)]
end

"""
    box(bbox::BoundingBox, :action;
            vertices=false)

Make a box using the bounds in `bbox`.

Use `vertices=true` to return an array of the four corner points: bottom left,
top left, top right, bottom right.
"""
function box(bbox::BoundingBox, action::Symbol=:nothing;
        vertices=false)
    if vertices
        botleft  = Point(bbox.corner1.x, bbox.corner2.y)
        topleft  = bbox.corner1
        topright = Point(bbox.corner2.x, bbox.corner1.y)
        botright = bbox.corner2
        return [botleft, topleft, topright, botright]
    else
        box(bbox.corner1, bbox.corner2, action)
    end
end

"""
    poly(bbox::BoundingBox, :action; kwargs...)

Make a polygon around the BoundingBox in `bbox`.
"""
poly(bbox::BoundingBox, action::Symbol=:nothing; kwargs...) =
    poly(convert(Vector{Point}, bbox), action; kwargs...)

"""
    prettypoly(bbox::BoundingBox, :action; kwargs...)

Make a decorated polygon around the BoundingBox in `bbox`. The vertices are in
the order: bottom left, top left, top right, and bottom right.
"""
prettypoly(bbox::BoundingBox, action::Symbol=:nothing; kwargs...) =
    prettypoly(box(bbox, vertices=true), action; kwargs...)

"""
    boundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox)
    boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)

Return true if the two bounding boxes intersect.
"""
function boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)
    minax, maxax = minmax(acorner1.x, acorner2.x)
    minay, maxay = minmax(acorner1.y, acorner2.y)

    minbx, maxbx = minmax(bcorner1.x, bcorner2.x)
    minby, maxby = minmax(bcorner1.y, bcorner2.y)

    maxax < minbx && return false
    maxbx < minax && return false

    maxay < minby && return false
    maxby < minay && return false
    return true # boxes overlap
end

boundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox) =
    boundingboxesintersect(bbox1.corner1, bbox1.corner2, bbox2.corner1, bbox2.corner2)

"""
    intersectboundingboxes(bb1::BoundingBox, bb2::BoundingBox)

Return a BoundingBox that's an intersection of the two bounding boxes.
"""
function intersectboundingboxes(bb1::BoundingBox, bb2::BoundingBox)
    !boundingboxesintersect(bb1, bb2) && return BoundingBox(O, O)
    minax, maxax = minmax(bb1.corner1.x, bb1.corner2.x)
    minay, maxay = minmax(bb1.corner1.y, bb1.corner2.y)

    minbx, maxbx = minmax(bb2.corner1.x, bb2.corner2.x)
    minby, maxby = minmax(bb2.corner1.y, bb2.corner2.y)
    return BoundingBox(Point(max(minax, minbx),
                             max(minay, minby)),
                       Point(min(maxax, maxbx),
                             min(maxay, maxby)))
end

"""
    isinside(p::Point, bb:BoundingBox)

Returns `true` if `pt` is inside bounding box `bb`.
"""
isinside(p::Point, bb::BoundingBox) = (bb.corner1.x <= p.x <= bb.corner2.x) &&
    (bb.corner1.y <= p.y <= bb.corner2.y)

"""
    midpoint(bb::BoundingBox)

Returns the point midway between the two points of the BoundingBox. This should
also be the center, unless I've been very stupid...
"""
midpoint(bb::BoundingBox) = midpoint(bb...)

"""
    between(bb::BoundingBox, x)

Find a point between the two corners of a BoundingBox corresponding to `x`,
where `x` is typically between 0 and 1.
"""
between(bb::BoundingBox, k=0.5) = between(bb[1], bb[2], k)

"""
    boxtopleft(bb::BoundingBox)

Return the point at the top left of the BoundingBox `bb`.

```
• ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
```

"""
boxtopleft(bb::BoundingBox)        = bb[1]

"""
    boxtopcenter(bb::BoundingBox)

Return the point at the top center of the BoundingBox `bb`.
```
⋅ • ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
```

"""
boxtopcenter(bb::BoundingBox) = midpoint(bb.corner1, bb.corner2) - (0, boxheight(bb)/2)

"""
    boxtopright(bb::BoundingBox)

Return the point at the top right of the BoundingBox `bb`.
```
⋅ ⋅ •
⋅ ⋅ ⋅
⋅ ⋅ ⋅
```

"""
boxtopright(bb::BoundingBox)       = Point(bb[2].x, bb[1].y)

"""
    boxmiddleleft(bb::BoundingBox)

Return the point at the middle left of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
• ⋅ ⋅
⋅ ⋅ ⋅
```

"""
boxmiddleleft(bb::BoundingBox)     = Point(bb[1].x, midpoint(bb[1], bb[2]).y)

"""
    boxmiddlecenter(bb::BoundingBox)

Return the point at the center of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
⋅ • ⋅
⋅ ⋅ ⋅
```
"""
boxmiddlecenter(bb::BoundingBox)   = midpoint(bb[1], bb[2])

"""
    boxmiddleright(bb::BoundingBox)

Return the point at the midde right of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
⋅ ⋅ •
⋅ ⋅ ⋅
```
"""
boxmiddleright(bb::BoundingBox)    = Point(bb[2].x, midpoint(bb[1], bb[2]).y)

"""
    boxbottomleft(bb::BoundingBox)

Return the point at the bottom left of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
⋅ ⋅ ⋅
• ⋅ ⋅
```
"""
boxbottomleft(bb::BoundingBox)     = Point(bb[1].x, bb[2].y)

"""
    boxbottomcenter(bb::BoundingBox)

Return the point at the bottom center of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ • ⋅
```
"""
boxbottomcenter(bb::BoundingBox)   = midpoint(bb.corner1, bb.corner2) + (0, boxheight(bb)/2)

"""
    boxbottomright(bb::BoundingBox)

Return the point at the bottom right of the BoundingBox `bb`.
```
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ •
```
"""
boxbottomright(bb::BoundingBox)    = bb[2]

# legacy defs will be deprecated
boxtop(bb::BoundingBox) = boxtopcenter(bb)
boxbottom(bb::BoundingBox) = boxbottomcenter(bb)

"""
    pointcrossesboundingbox(pt, bbox::BoundingBox)

Find and return the point where a line from the center of bounding box
`bbox` to point `pt` would, if continued, cross the edges of the box.
"""
function pointcrossesboundingbox(pt, bbox::BoundingBox)
    minpt, maxpt = extrema(bbox)
    mp = midpoint(minpt, maxpt)
    midX, midY = mp.x, mp.y

    m = (midY - pt.y) / (midX - pt.x)
    if pt.x <= midX # left
        min_Xy = m * (minpt.x - pt.x) + pt.y
        if minpt.y <= min_Xy && min_Xy <= maxpt.y
            return Point(minpt.x, min_Xy)
        end
    end
    if pt.x >= midX # right
        max_Xy = m * (maxpt.x - pt.x) + pt.y
        if minpt.y <= max_Xy && max_Xy <= maxpt.y
            return Point(maxpt.x, max_Xy)
        end
    end
    if pt.y <= midY # top
        min_Yx = (minpt.y - pt.y) / m + pt.x
        if minpt.x <= min_Yx && min_Yx <= maxpt.x
            return Point(min_Yx, minpt.y)
        end
    end
    if pt.y >= midY # bottom
        max_Yx = (maxpt.y - pt.y) / m + pt.x
        if minpt.x <= max_Yx && max_Yx <= maxpt.x
            return Point(max_Yx, maxpt.y)
        end
    end
    if pt.x == midX && pt.y == midY
        return Point(pt.x, pt.y)
    end
    error("oh dear, something has gone wrong but I don't know what")
end


"""
    restrictedSequencingDD(model::SequencingModel, maxWidth::Int;rootNode::Union{RestrictedSequencingNode,RelaxedSequencingNode,RelaxedStateSequencingNode,Nothing}=nothing,bestKnownSolution::Union{Vector{Int},Nothing}=nothing,bestKnownValue::Union{T,Nothing}=nothing, rrbSetting::Union{RRBSetting,Nothing}=nothing, rrbPacket=nothing, rootDomain::BitVector=BitVector(),debugOn::Bool=false)where{T<:Real}

A quick restricted decision diagram for sequencing problems of fixed length
Returns: bestKnownSolution (Vector{Int}), bestKnownValue (Real), exact {Bool}
'exact' is true if the dd is exact

# Arguments
- `model::SequencingModel`: The sequencing problem
- `maxWidth::Int`: The max width allowed in a decision diagram layer
- `rootNode::Union{RestrictedSequencingNode,RelaxedSequencingNode,RelaxedStateSequencingNode,Nothing}`: The starting node for the DD
- `bestKnownSolution::Union{Vector{Int},Nothing}`: An optional parameter that gives the DD a starting solution
- `bestKnownValue::Union{T,Nothing}`: An optional parameter that gives the DD a starting bound
- `rrbSetting::Union{RRBSetting,Nothing}`: An optional parameter that chooses an rough relaxed bounding method
- `rrbPacket`: Data to be passed to the rrbFunction
- `rootDomain::BitVector`: The domain of the root node if it is a RelaxedStateSequencingNode
- `debugOn::Bool`: An optional parameter that can be used to turn on debug statements in other functions
"""
function restrictedSequencingDD(model::SequencingModel, maxWidth::Int;rootNode::Union{RestrictedSequencingNode,RelaxedSequencingNode,RelaxedStateSequencingNode,Nothing}=nothing,bestKnownSolution::Union{Vector{Int},Nothing}=nothing,bestKnownValue::Union{T,Nothing}=nothing, rrbSetting::Union{RRBSetting,Nothing}=nothing, rrbPacket=nothing, rootDomain::BitVector=BitVector(),debugOn::Bool=false)where{T<:Real}
    doSecondLayerDomainCheck = false
    if isnothing(rootNode)
        rootNode = createRestrictedRootNode(model)
        queue = Vector{RestrictedSequencingNode}([rootNode])
        pathLength = length(rootNode.path)
    elseif isa(rootNode,RelaxedSequencingNode)
        pathLength = length(rootNode)
        rootDomain = falses(length(model))
        for arc in rootNode.arcsOut
            rootDomain[arc.assignment] = true
        end
        queue = Vector{RestrictedSequencingNode}([RestrictedSequencingNode(copy(rootNode.bestSeq),rootNode.allDown, rootNode.lengthToRoot,rootDomain)])
        if model.precedenceConstraints
            doSecondLayerDomainCheck = true
        end
    elseif isa(rootNode,RelaxedStateSequencingNode)
        pathLength = length(rootNode)
        queue = Vector{RestrictedSequencingNode}([RestrictedSequencingNode(copy(rootNode.bestSeq),rootNode.allDown, rootNode.lengthToRoot,rootDomain)])
        if model.precedenceConstraints
            doSecondLayerDomainCheck = true
        end
    else
        queue = Vector{RestrictedSequencingNode}([rootNode])
        pathLength = length(rootNode.path)
    end

    #track exactness
    exact = true

    #iterate down through the layers
    for layerIndex in 1:length(model)-pathLength
        if layerIndex == 2 && doSecondLayerDomainCheck
            secondLayerDomainCheck(model, queue)
        end
        #initialize array for new layer
        newLayer = Vector{RestrictedSequencingNode}()
        #for each node in the queue
        for node in queue
            if length(node.path)>0
                state = getState(node)
            else
                state = nothing
            end
            #for each unvisited location of the node
            for i in 1:length(model)
                #if i is in the domain add the new node
                if inDomain(node, i)
                    newNode = makeDecision(model, node, state, i)

                    if !isnothing(rrbSetting) && layerIndex+pathLength<length(model)-2
                        if  !isBetterSolutionValue(model, sequencingRRB(rrbSetting, newNode, rrbPacket), bestKnownValue)
                            continue
                        end
                    end

                    #if worth adding, add it
                    if isnothing(bestKnownValue) || model.objective.type==maximization
                        addToSortedLayer!(model, newLayer, newNode)
                    elseif isBetterSolutionValue(model, newNode.value, bestKnownValue)
                        addToSortedLayer!(model, newLayer, newNode)
                    end
                end
            end#end domain loop
            if length(newLayer)>maxWidth
                if exact
                    exact = false
                end
                newLayer = newLayer[1:maxWidth]
            end
        end#node in queue loop
        #trim queue
        if length(newLayer)>maxWidth
            if exact
                exact = false
            end
            queue = newLayer[1:maxWidth]
        elseif isempty(newLayer)
            return bestKnownSolution, bestKnownValue, exact
        else
            queue = newLayer
        end
    end#iterate over layers loop

    #update result
    if isnothing(bestKnownValue) || isBetterSolutionValue(model, queue[1].value, bestKnownValue)
        bestKnownSolution = queue[1].path
        bestKnownValue = queue[1].value
    end
    return bestKnownSolution, bestKnownValue, exact
end

"""
    makeDecision(model::SequencingModel, rootNode::RestrictedSequencingNode, state::Union{Nothing, Int}, decision::Int)

A method that generates the child node resulting from adding an arc to an existing node

# Arguments
- `model::SequencingModel`: The Sequencing problem
- `rootNode::RestrictedSequencingNode`: The starting node for the decision
- `state::Union{Nothing, Int}`: The last node visited
- `decision::Int`: The node being visited
"""
function makeDecision(model::SequencingModel, rootNode::RestrictedSequencingNode, state::Union{Nothing, Int}, decision::Int)
    #update path
    newPath = copy(rootNode.path)
    push!(newPath, decision)

    #update visited
    newVisited = copy(rootNode.visited)
    newVisited[decision] = true

    #update domain
    newDomain = copy(rootNode.domain)
    newDomain[decision] = false
    #handle precedence constraints
    if model.precedenceConstraints
        if haskey(model.precedenceP2F,decision)
            for i in model.precedenceP2F[decision]
                #check each of the relevant precedence constraints
                #if satisfied, put in the domain
                if haskey(model.precedenceF2P,i)
                    if checkPrecedence(newVisited,model.precedenceF2P[i])
                        newDomain[i] = true
                    end
                end
            end
        end
    end

    #update value
    if isnothing(state)
        return RestrictedSequencingNode(newPath, newVisited, rootNode.value, newDomain)
    else
        return RestrictedSequencingNode(newPath, newVisited, rootNode.value + evaluateDecision(model.objective, state, decision), newDomain)
    end
end

"""
    addToSortedLayer!(model::SequencingModel, layer::Vector{RestrictedSequencingNode}, newNode::RestrictedSequencingNode

A method that generates the child node resulting from adding an arc to an existing node

# Arguments
- `model::SequencingModel`: The Sequencing problem
- `layer::Vector{RestrictedSequencingNode}`: The list to add to
- `newNode::RestrictedSequencingNode`: the node to insert
"""
function addToSortedLayer!(model::SequencingModel, layer::Vector{RestrictedSequencingNode}, newNode::RestrictedSequencingNode)
    if isempty(layer)
        push!(layer, newNode)
    else
        insert!(
            layer,
            searchsorted(layer, newNode, by = x -> valueSequencingNode(model, x)).start,
            newNode
        )
    end
end

"""
    valueSequencingNode(model::CVRPModel, node::RestrictedCVRPNode)

Return a quick heuristic value of a node, lower is better

# Arguments
- `model::SequencingModel`: The sequencing problem
- `newNode::RestrictedSequencingNode`: the node to value
"""
function valueSequencingNode(model::SequencingModel, node::RestrictedSequencingNode)
    if model.objective.type == minimization
        return node.value
    else
        return (-1 * node.value)
    end
end

"""
    secondLayerDomainCheck(model::SequencingModel, queue::Vector{RestrictedSequencingNode})

A method that fixes the domain of the first nodes generated from a relaxed node

# Arguments
- `model::SequencingModel`: The Sequencing problem
- `queue::Vector{RestrictedSequencingNode}`: The list of nodes to check
"""
function secondLayerDomainCheck(model::SequencingModel, queue::Vector{RestrictedSequencingNode})
    for node in queue
        for i in 1:length(node.domain)
            if !node.visited[i] && !node.domain[i] && hasKey(model.objective, getState(node), i)
                #handle precedence constraints
                if haskey(model.precedenceF2P,i)
                    if checkPrecedence(node.visited,model.precedenceF2P[i])
                        node.domain[i] = true
                    end
                else
                    node.domain[i] = true
                end
            end
        end
    end
end


## random number generation

using Random
import RandomNumbers

# global state

# we provide 128 bytes of random state, shared across the thread block using shared memory.
# the same amount of seeding data is available in constant memory, set during initial load.

@eval @inline function global_random_seed()
    ptr = Base.llvmcall(
        $("""@global_random_seed = weak addrspace($(AS.Constant)) externally_initialized global [32 x i32] zeroinitializer, align 32
             define i8 addrspace($(AS.Constant))* @entry() #0 {
                 %ptr = getelementptr inbounds [32 x i32], [32 x i32] addrspace($(AS.Constant))* @global_random_seed, i64 0, i64 0
                 %untyped_ptr = bitcast i32 addrspace($(AS.Constant))* %ptr to i8 addrspace($(AS.Constant))*
                 ret i8 addrspace($(AS.Constant))* %untyped_ptr
             }
             attributes #0 = { alwaysinline }
          """, "entry"), LLVMPtr{UInt32, AS.Constant}, Tuple{})
    CuDeviceArray((32,), ptr)
end

@eval @inline function global_random_state()
    ptr = Base.llvmcall(
        $("""@global_random_state = weak addrspace($(AS.Shared)) global [32 x i32] zeroinitializer, align 32
             define i8 addrspace($(AS.Shared))* @entry() #0 {
                 %ptr = getelementptr inbounds [32 x i32], [32 x i32] addrspace($(AS.Shared))* @global_random_state, i64 0, i64 0
                 %untyped_ptr = bitcast i32 addrspace($(AS.Shared))* %ptr to i8 addrspace($(AS.Shared))*
                 ret i8 addrspace($(AS.Shared))* %untyped_ptr
             }
             attributes #0 = { alwaysinline }
          """, "entry"), LLVMPtr{UInt32, AS.Shared}, Tuple{})
    CuDeviceArray((32,), ptr)
end

function initialize_random_seeds!(mod)
    seed = CuGlobal{NTuple{32,UInt32}}(mod, "global_random_seed")
    seed[async=true] = Tuple(rand(UInt32, 32))
end

@device_override Random.make_seed() = clock(UInt32)

@generated function make_full_seed(seed::Integer)
    quote
        s_0 = seed % UInt32
        Base.@nexprs 32 i->s_i = xorshift(s_{i-1})
        Base.@ncall 32 tuple s
    end
end

# helpers

function xorshift(x::UInt32)::UInt32
    x = xor(x, x << 13)
    x = xor(x, x >> 17)
    x = xor(x, x << 5)
    return x
end

# generators

"""
    SharedTauswortheGenerator()

A maximally equidistributed combined Tausworthe generator.

The generator uses 32 bytes of random state stored in shared memory. This memory is
zero-initialized; When the first random number is generated, each block will derive an
initial state from the seed provided during module compilation, and the block identifier.
Each block will then indepentendly, but deterministically use that state to generate random
numbers. Within a block, the 32-bytes of random state are identical, so the warp identifier
will be mixed in to ensure uniqueness across warps. Finally, the first warp of each block
updates the shared state.

!!! warning

    Although the numbers obtained from this generator "look OK", they do not pass the
    SmallCrush test suite, so the generator should be deemed experimental.
"""
struct SharedTauswortheGenerator <: RandomNumbers.AbstractRNG{UInt32}
end

@inline function Base.getproperty(rng::SharedTauswortheGenerator, field::Symbol)
    if field === :seed
        global_random_seed()
    elseif field === :state
        global_random_state()
    end
end

@device_override Random.default_rng() = SharedTauswortheGenerator()

function Random.seed!(rng::SharedTauswortheGenerator, seed::Integer)
    Random.seed!(rng, make_full_seed(seed))
    return
end

"""
    Random.seed!(rng::SharedTauswortheGenerator, seeds)

Seed the on-device Tausworthe generator with a 32-element tuple or array of UInt32 seeds.
This function only needs to be called by a single warp (i.e. 32 threads) from each block,
but it does not hurt to call it from all threads (as long as threads are synchronized after
that). It should always be called by at least 32 threads to ensure the random state is fully
initialized, even if you will be using the generator from fewer threads!
"""
@inline Base.@propagate_inbounds function Random.seed!(rng::SharedTauswortheGenerator, seed)
    state = initial_state(seed)
    @inbounds rng.state[laneid()] = state
    return
end

@inline Base.@propagate_inbounds function initial_state(seeds)
    z = seeds[laneid()]

    # mix-in the block id to ensure unique values across blocks
    # XXX: is this OK? shouldn't we use a generator that allows skipping ahead?
    #      https://stackoverflow.com/questions/11692785/efficient-xorshift-skip-ahead
    blockId = blockIdx().x + (blockIdx().y - 1) * gridDim().x +
                             (blockIdx().z - 1) * gridDim().x * gridDim().y
    z = xorshift(z ⊻ blockId%UInt32)

    return z
end

# NOTE: @propagate_inbounds for when we know the passed seed contains at least 32 elements
# XXX: without @inline @propagate_inbounds, we get local memory accesses (e.g. with `rand!`)

#const TausShift1 = CuConstantMemory{UInt32}((6, 2, 13, 3))
#const TausShift2 = CuConstantMemory{UInt32}((13, 27, 21, 12))
#const TausShift3 = CuConstantMemory{UInt32}((18, 2, 7, 13))
#const TausOffset = CuConstantMemory{UInt32}((4294967294, 4294967288, 4294967280, 4294967168))
# XXX: constant memory isn't supported yet, so let's resort to llvmcall
for (name, vals) in ("TausShift1" => (6, 2, 13, 3),
                     "TausShift2" => (13, 27, 21, 12),
                     "TausShift3" => (18, 2, 7, 13),
                     "TausOffset" => (4294967294, 4294967288, 4294967280, 4294967168))
    @eval @inline function $(Symbol(name))()
        ptr = Base.llvmcall(
            $("""@$(name) = weak addrspace($(AS.Constant)) global [4 x i32] [i32 $(vals[1]), i32 $(vals[2]), i32 $(vals[3]), i32 $(vals[4])], align 4
                define i8 addrspace($(AS.Constant))* @entry() #0 {
                    %ptr = getelementptr inbounds [4 x i32], [4 x i32] addrspace($(AS.Constant))* @$(name), i64 0, i64 0
                    %untyped_ptr = bitcast i32 addrspace($(AS.Constant))* %ptr to i8 addrspace($(AS.Constant))*
                    ret i8 addrspace($(AS.Constant))* %untyped_ptr
                }
                attributes #0 = { alwaysinline }
            """, "entry"), LLVMPtr{UInt32, AS.Constant}, Tuple{})
        CuDeviceArray((4,), ptr)
    end
end

function TausStep(z::Unsigned, S1::Integer, S2::Integer, S3::Integer, M::Unsigned)
    b = (((z << S1) ⊻ z) >> S2)
    return (((z & M) << S3) ⊻ b)
end

"""
    Random.rand(rng::SharedTauswortheGenerator, UInt32)

Generate a byte of random data using the on-device Tausworthe generator.

!!! warning

    This functions performs synchronization, so should only be called uniformly (i.e. by all
    threads in a block). Do not call it from a branch that diverges within a block, or your
    kernel may deadlock.
"""
function Random.rand(rng::SharedTauswortheGenerator, ::Type{UInt32})
    @inline pow2_mod1(x, y) = (x-1)&(y-1) + 1

    threadId = UInt32(threadIdx().x + (threadIdx().y - 1) * blockDim().x +
                                      (threadIdx().z - 1) * blockDim().x * blockDim().y)
    warpId = (threadId-UInt32(1)) >> 5 + UInt32(1)  # fld1
    i = pow2_mod1(threadId, 32)
    j = pow2_mod1(threadId, 4)

    @inbounds begin
        # get state
        z = rng.state[i]
        if z == 0
            z = initial_state(rng.seed)
        end

        # mix-in the warp id to ensure unique values across blocks.
        # we have max 1024 threads per block, so can safely shift by 16 bits.
        # XXX: see comment in `initial_state`
        z = xorshift(z ⊻ (warpId << 16))

        sync_threads()

        # advance & update state
        S1, S2, S3, M = TausShift1()[j], TausShift2()[j], TausShift3()[j], TausOffset()[j]
        state = TausStep(z, S1, S2, S3, M)
        if warpId == 1
            rng.state[i] = state
        end

        sync_threads()

        # generate based on 4 bytes of state
        mask = active_mask()
        i1 = pow2_mod1(threadId+1, 32)
        i2 = pow2_mod1(threadId+2, 32)
        i3 = pow2_mod1(threadId+3, 32)
        if active_mask() == typemax(UInt32)
            # we have a full warp, so we can safely shuffle.
            # get the warp-local states from neighbouring threads.
            s1 = shfl_sync(FULL_MASK, state, i1)
            s2 = shfl_sync(FULL_MASK, state, i2)
            s3 = shfl_sync(FULL_MASK, state, i3)
        else
            # can't shuffle, so fall back to fetching block-global state.
            # this results is less entropy, as it reuses data from the first warp.
            s1 = rng.state[i1]
            s2 = rng.state[i2]
            s3 = rng.state[i3]
        end
        state ⊻ s1 ⊻ s2 ⊻ s3
    end
end


#-------------------------------------------------------------------
#* EMSO Model Library (EML) Copyright (C) 2004 - 2007 ALSOC.
#*
#* This LIBRARY is free software; you can distribute it and/or modify
#* it under the therms of the ALSOC FREE LICENSE as available at
#* http://www.enq.ufrgs.br/alsoc.
#*
#* EMSO Copyright (C) 2004 - 2007 ALSOC, original code
#* from http://www.rps.eng.br Copyright (C) 2002-2004.
#* All rights reserved.
#*
#* EMSO is distributed under the therms of the ALSOC LICENSE as
#* available at http://www.enq.ufrgs.br/alsoc.
#*
#*--------------------------------------------------------------------
#* Models to simulate a power plant.
#*--------------------------------------------------------------------
#* Author: Argimiro R. Secchi
#* $Id: power_plant.mso 195 2007-03-07 20:30:12Z arge $
#*-------------------------------------------------------------------
# Declaracao de tipos
type CorrenteVap
	CorrenteVap()=begin
		PP=outers.PP
		new(
			CorrenteZ(),
			DanaPlugin(Dict{Symbol,Any}(
				:Brief=>"External Physical Properties"
			)),
			[
				:(H = PP.VapourEnthalpy(T, P, z)),
				:(S = PP.VapourEntropy(T, P, z)),
			],
			[
				"Vapour Enthalpy","Vapour Entropy",
			],
			[:PP,],
		)
	end
	_base_1::CorrenteZ
	PP::DanaPlugin
	equations::Array{Expr,1}
	equationNames::Array{String,1}
	parameters::Array{Symbol,1}
	attributes::Dict{Symbol,Any}
end
export CorrenteVap
function setEquationFlow(in::CorrenteVap)
	addEquation(1)
	addEquation(2)
end
function atributes(in::CorrenteVap,_::Dict{Symbol,Any})
	fields::Dict{Symbol,Any}=Dict{Symbol,Any}()
	fields[:Pallete]=false
	fields[:Brief]="Vapour Material Stream"
	fields[:Info]="Model for vapour material streams.
	This model should be used only when the phase of the stream
	is known ''a priori''."
	drive!(fields,_)
	return fields
end
CorrenteVap(_::Dict{Symbol,Any})=begin
	newModel=CorrenteVap()
	newModel.attributes=atributes(newModel,_)
	newModel
end


# Julia wrapper for header: /usr/local/include/sodium.h
# Automatically generated using Clang.jl wrap_c, version 0.0.0

@c Int32 sodium_init () libsodium
@c Int32 crypto_auth_hmacsha512256 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_auth_hmacsha512256_verify (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c size_t crypto_auth_bytes () libsodium
@c size_t crypto_auth_keybytes () libsodium
@c Ptr{Uint8} crypto_auth_primitive () libsodium
@c Int32 crypto_auth (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_auth_verify (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_auth_hmacsha256 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_auth_hmacsha256_verify (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305_open (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305_keypair (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305_beforenm (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_curve25519xsalsa20poly1305_open_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c size_t crypto_box_publickeybytes () libsodium
@c size_t crypto_box_secretkeybytes () libsodium
@c size_t crypto_box_beforenmbytes () libsodium
@c size_t crypto_box_noncebytes () libsodium
@c size_t crypto_box_zerobytes () libsodium
@c size_t crypto_box_boxzerobytes () libsodium
@c size_t crypto_box_macbytes () libsodium
@c Ptr{Uint8} crypto_box_primitive () libsodium
@c Int32 crypto_box_keypair (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_beforenm (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_open_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_box_open (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_core_hsalsa20 (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_core_salsa20 (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_core_salsa2012 (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_core_salsa208 (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_generichash_blake2b (Ptr{Uint8}, size_t, Ptr{Uint8}, Uint64, Ptr{Uint8}, size_t) libsodium
@c Int32 crypto_generichash_blake2b_init (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, size_t, size_t) libsodium
@c Int32 crypto_generichash_blake2b_update (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_generichash_blake2b_final (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, size_t) libsodium
@c size_t crypto_generichash_bytes_min () libsodium
@c size_t crypto_generichash_bytes_max () libsodium
@c size_t crypto_generichash_keybytes_min () libsodium
@c size_t crypto_generichash_keybytes_max () libsodium
@c size_t crypto_generichash_blockbytes () libsodium
@c Ptr{Uint8} crypto_generichash_primitive () libsodium
@c Int32 crypto_generichash (Ptr{Uint8}, size_t, Ptr{Uint8}, Uint64, Ptr{Uint8}, size_t) libsodium
@c Int32 crypto_generichash_init (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, size_t, size_t) libsodium
@c Int32 crypto_generichash_update (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_generichash_final (Ptr{crypto_generichash_blake2b_state}, Ptr{Uint8}, size_t) libsodium
@c Int32 crypto_hash_sha512 (Ptr{Uint8}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_hash (Ptr{Uint8}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_hash_sha256 (Ptr{Uint8}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_hashblocks_sha256 (Ptr{Uint8}, Ptr{Uint8}, Uint64) libsodium
@c Int32 crypto_hashblocks_sha512 (Ptr{Uint8}, Ptr{Uint8}, Uint64) libsodium
@c Ptr{Uint8} crypto_onetimeauth_poly1305_ref_implementation_name () libsodium
@c Int32 crypto_onetimeauth_poly1305_set_implementation (Ptr{crypto_onetimeauth_poly1305_implementation},) libsodium
@c Ptr{crypto_onetimeauth_poly1305_implementation} crypto_onetimeauth_pick_best_implementation () libsodium
@c Int32 crypto_onetimeauth_poly1305 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_onetimeauth_poly1305_verify (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c size_t crypto_onetimeauth_bytes () libsodium
@c size_t crypto_onetimeauth_keybytes () libsodium
@c Ptr{Uint8} crypto_onetimeauth_primitive () libsodium
@c Int32 crypto_onetimeauth (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_onetimeauth_verify (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_scalarmult_curve25519 (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_scalarmult_curve25519_base (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c size_t crypto_scalarmult_bytes () libsodium
@c size_t crypto_scalarmult_scalarbytes () libsodium
@c Ptr{Uint8} crypto_scalarmult_primitive () libsodium
@c Int32 crypto_scalarmult_base (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_scalarmult (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_secretbox_xsalsa20poly1305 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_secretbox_xsalsa20poly1305_open (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c size_t crypto_secretbox_keybytes () libsodium
@c size_t crypto_secretbox_noncebytes () libsodium
@c size_t crypto_secretbox_zerobytes () libsodium
@c size_t crypto_secretbox_boxzerobytes () libsodium
@c Ptr{Uint8} crypto_secretbox_primitive () libsodium
@c Int32 crypto_secretbox (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_secretbox_open (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_shorthash_siphash24 (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c size_t crypto_shorthash_bytes () libsodium
@c size_t crypto_shorthash_keybytes () libsodium
@c Ptr{Uint8} crypto_shorthash_primitive () libsodium
@c Int32 crypto_shorthash (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_ed25519 (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_ed25519_open (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_ed25519_keypair (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_ed25519_seed_keypair (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c size_t crypto_sign_bytes () libsodium
@c size_t crypto_sign_publickeybytes () libsodium
@c size_t crypto_sign_secretkeybytes () libsodium
@c Ptr{Uint8} crypto_sign_primitive () libsodium
@c Int32 crypto_sign_seed_keypair (Ptr{Uint8}, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_keypair (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_sign (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_open (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_edwards25519sha512batch (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_edwards25519sha512batch_open (Ptr{Uint8}, Ptr{Uint64}, Ptr{Uint8}, Uint64, Ptr{Uint8}) libsodium
@c Int32 crypto_sign_edwards25519sha512batch_keypair (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xsalsa20 (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xsalsa20_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xsalsa20_beforenm (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xsalsa20_afternm (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xsalsa20_xor_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c size_t crypto_stream_keybytes () libsodium
@c size_t crypto_stream_noncebytes () libsodium
@c Ptr{Uint8} crypto_stream_primitive () libsodium
@c Int32 crypto_stream (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_aes128ctr (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_aes128ctr_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_aes128ctr_beforenm (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_aes128ctr_afternm (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_aes128ctr_xor_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa20 (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa20_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa20_beforenm (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa20_afternm (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa20_xor_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa2012 (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa2012_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa2012_beforenm (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa2012_afternm (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa2012_xor_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa208 (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa208_xor (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa208_beforenm (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa208_afternm (Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_stream_salsa208_xor_afternm (Ptr{Uint8}, Ptr{Uint8}, Uint64, Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_verify_16 (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 crypto_verify_32 (Ptr{Uint8}, Ptr{Uint8}) libsodium
@c Int32 randombytes_set_implementation (Ptr{randombytes_implementation},) libsodium
@c None randombytes (Ptr{Uint8}, Uint64) libsodium
@c Ptr{Uint8} randombytes_implementation_name () libsodium
@c uint32_t randombytes_random () libsodium
@c None randombytes_stir () libsodium
@c uint32_t randombytes_uniform (uint32_t,) libsodium
@c None randombytes_buf (Ptr{Void}, size_t) libsodium
@c Int32 randombytes_close () libsodium
@c Ptr{Uint8} randombytes_salsa20_implementation_name () libsodium
@c uint32_t randombytes_salsa20_random () libsodium
@c None randombytes_salsa20_random_stir () libsodium
@c uint32_t randombytes_salsa20_random_uniform (uint32_t,) libsodium
@c None randombytes_salsa20_random_buf (Ptr{Void}, size_t) libsodium
@c Int32 randombytes_salsa20_random_close () libsodium
@c Ptr{Uint8} randombytes_sysrandom_implementation_name () libsodium
@c uint32_t randombytes_sysrandom () libsodium
@c None randombytes_sysrandom_stir () libsodium
@c uint32_t randombytes_sysrandom_uniform (uint32_t,) libsodium
@c None randombytes_sysrandom_buf (Ptr{Void}, size_t) libsodium
@c Int32 randombytes_sysrandom_close () libsodium
@c Ptr{Uint8} _sodium_alignedcalloc (Ptr{Ptr{Uint8}}, size_t) libsodium
@c None sodium_memzero (Ptr{Void}, size_t) libsodium
@c Int32 sodium_memcmp (Ptr{Void}, Ptr{Void}, size_t) libsodium
@c Ptr{Uint8} sodium_version_string () libsodium
@c Int32 sodium_library_version_major () libsodium
@c Int32 sodium_library_version_minor () libsodium


#
# Generic version of geotransform, fall through to including the handlers as the final two inputs
#
"""
  geotransform - function to transfom points between coordinate systems

  Usage:

    geotransform(::Type{T}, X)

        Transform geodesy point X to the coordinate system defined by T

        Examples:
            geotransform(ECEF,                   LLA{WGS84}(0,0,0))
            geotransform(ECEF{WGS84},            LLA{WGS84}(0,0,0))
            geotransform(CRS{SRID{:EPSG, 4978}}, LLA{WGS84}(0,0,0))
            geotransform(ENU{LLA{WGS84}(0,0,0)}, LLA{WGS84}(1,1,10))  # ENU frame centered on LLA{WGS84}(0,0,0)

    geotransform(::Type{T}, X, ref)

        Transform geodesy point X to the coordinate system defined by T using the reference data in ref

        Examples:
            geotransform(ECEF{UnknownDatum}, LLA{UnknownDatum}(0,0,0), Geodesy.eWGS84)  # supply the ellipse to use since there's no datum info
            geotransform(ENU, LLA{WGS84}(1,1,10), LLA{WGS84}(0,0,0))                    # ENU frame centered on LLA{WGS84}(0,0,0)

    geotransform(::Type{T}, X, handler_output, handler_input)

        Transform geodesy point X to the coordinate system defined by T using the package indicated by the package handlers

        Example:
            geotransform(ECEF{WGS84}, LLA{WGS84}(0,0,0), Geodesy.Proj4Handler, Geodesy.Proj4Handler)  # Get the Proj4 package to do the transform

    geotransform(::Type{T}, X, ref, handler_output, handler_input)

        Transform geodesy point X to the coordinate system defined by T using the reference data ref and package indicated by the handlers

        Example:

            # Get the Proj4 package to do the transform by suppling the SRIDs seperately
            geotransform(ECEF{UnknownDatum}, LLA{UnknownDatum}(0,0,0), (SRID{:epsg, 4978}, SRID{:epsg, 4326}), Geodesy.Proj4Handler, Geodesy.Proj4Handler)

"""
geotransform(X)         = geotransform(X, get_handler(X))
geotransform(X, Y)      = geotransform(X, Y, get_handler(X), get_handler(Y))
geotransform(X, Y, ref) = geotransform(X, Y, ref, get_handler(X), get_handler(Y))  # with a reference point

# error message for when no handler is defined
function geotransform{Handler1}(X, ::Type{Handler1})
    tOut = isa(X, DataType) ? X : typeof(X)
    tIn = !isa(Y, DataType) ? typeof(Y) : error("geotransform: expected a point type as the second input, got DataType: $(Y)")
    error("No methods defined to transform from $(tIn) to $(tOut)")
end
function geotransform{Handler1, Handler2}(X, Y, ::Type{Handler1}, ::Type{Handler2})
    tOut = isa(X, DataType) ? X : typeof(X)
    tIn = !isa(Y, DataType) ? typeof(Y) : error("geotransform: expected a point type as the second input, got DataType: $(Y)")
    error("No methods defined to transform from $(tIn) to $(tOut)")
end
function geotransform{Handler1, Handler2}(X, Y, ref, ::Type{Handler1}, ::Type{Handler2})
    tOut = isa(X, DataType) ? X : typeof(X)
    tIn = !isa(Y, DataType) ? typeof(Y) : error("geotransform: expected a point type as the second input, got DataType: $(Y)")
    error("No methods defined to transform from $(tIn) to $(tOut) using a reference point")
end

#
# version that return the transform parameters
#
geotransform_params(X)         = geotransform_params(X, get_handler(X))
geotransform_params(X, Y)      = geotransform_params(X, Y, get_handler(X), get_handler(Y))
geotransform_params(X, Y, ref) = geotransform_params(X, Y, ref, get_handler(X), get_handler(Y))  # with a reference point

#
# vectorized version (Proj4 point need special handling)
#
geotransform_vector{T}(X::Vector{T})                              = geotransform_vector(X, get_handler(eltype(T)))
geotransform_vector{T, U <: AbstractVector}(::Type{T}, Y::U)      = geotransform_vector(T, Y, get_handler(T), get_handler(eltype(U)))
geotransform_vector{T, U <: AbstractVector}(::Type{T}, Y::U, ref) = geotransform_vector(T, Y, ref, get_handler(T), get_handler(eltype(U)))  # with a reference point

#
# rules for infering one datum from another
#

# the methods
select_datum(::Type{UnknownDatum}, ::Type{UnknownDatum}) = UnknownDatum
select_datum{T <: KnownDatum}(::Type{T}, ::Type{T}) = T
select_datum{T1 <: KnownDatum, T2 <: KnownDatum}(::Type{T1}, ::Type{T2}) = error("Inputs type and output type have different datums")
select_datum{T <: KnownDatum}(::Type{UnknownDatum}, ::Type{T}) = T
select_datum{T <: KnownDatum}(::Type{T}, ::Type{UnknownDatum}) = T

# from 3 options
select_datum{T,U,V}(::Type{T}, ::Type{U}, ::Type{V}) = select_datum(select_datum(get_datum(T), get_datum(U)), get_datum(V))

# fall through methods
select_datum{T,U}(::Type{T}, ::Type{U}) = select_datum(get_datum(T), get_datum(U))
select_datum{T,U}(::T, ::Type{U})       = select_datum(get_datum(T), get_datum(U))
select_datum{T,U}(::Type{T}, ::U)       = select_datum(get_datum(T), get_datum(U))
select_datum{T,U}(::T, ::U)             = select_datum(get_datum(T), get_datum(U))

##########################################
# Special case, allow LL <-> LLA
##########################################

geotransform{T <: LL, U <: LLA}(::Type{T}, lla::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = LL{select_datum(add_param(T,U), U)}(lla.lon, lla.lat)
geotransform{T <: LLA, U <: LL}(::Type{T}, ll::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})  = LLA{select_datum(add_param(T,U), U)}(lla.lon, lla.lat, 0.0)

###############################
### LLA to ECEF coordinates ###
###############################

# reference ellipse inferred from the in / output types
function geotransform{T <: ECEF, U <: Union{LL, LLA}}(::Type{T}, X::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    oT = add_param(T, U)  # insert the datum if needed
    datum = select_datum(get_datum(oT), get_datum(U))
    ellipse = ellipsoid(datum)
    lla_to_ecef(oT, X, ellipse)
end

# reference ellipse specified (N.B. output type is assumed to be what the user wants)
function geotransform{T <: ECEF, U <: Union{LL, LLA}}(::Type{T}, X::U, ellipse::Ellipsoid, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    oT = add_param(T, U)  # insert the datum if needed
    lla_to_ecef(oT, X, ellipse)
end

# worker function
function lla_to_ecef{T}(::Type{T}, ll, ellipse::Ellipsoid)
     ϕdeg, λdeg, h = get_lat(ll), get_lon(ll), get_alt(ll)

    sinϕ, cosϕ = sind(ϕdeg), cosd(ϕdeg)
    sinλ, cosλ = sind(λdeg), cosd(λdeg)

    N = ellipse.a / sqrt(1 - ellipse.e² * sinϕ^2)  # Radius of curvature (meters)

    x = (N + h) * cosϕ * cosλ
    y = (N + h) * cosϕ * sinλ
    z = (N * (1 - ellipse.e²) + h) * sinϕ

    return T(x, y, z)
end

###############################
### ECEF to LLA coordinates ###
###############################

# reference ellipse inferred from the in / output types (always using LL to parameterise atm)
function geotransform{T <: Union{LL, LLA}, U <: ECEF}(::Type{T}, X::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    oT = add_param(T, U)  # insert the datum if needed
    datum = select_datum(get_datum(oT), get_datum(U))
    ellipse = ellipsoid(datum)
    ecef_to_lla(oT, X, ellipse)
end

# reference ellipse specified (N.B. output type is assumed to be what the user wants)
function geotransform{T <: Union{LL, LLA}, U <: ECEF}(::Type{T}, X::U, ellipse::Ellipsoid, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    oT = add_param(T, U)  # insert the datum if needed
    lla_to_ecef(oT, X, ellipse)
end

# worker function
function ecef_to_lla{T <: Union{LL, LLA}}(::Type{T}, ecef, ellipse::Ellipsoid)
    x, y, z = getX(ecef), getY(ecef), getZ(ecef)

    p = hypot(x, y)
    θ = atan2(z*ellipse.a, p*ellipse.b)
    λ = atan2(y, x)
     ϕ = atan2(z + ellipse.e′² * ellipse.b * sin(θ)^3, p - ellipse.e²*ellipse.a*cos(θ)^3)

    N = ellipse.a / sqrt(1 - ellipse.e² * sin(ϕ)^2)  # Radius of curvature (meters)
    h = p / cos(ϕ) - N

    lla = T(rad2deg(λ), rad2deg(ϕ), h)

    return lla
end

# TESTING - compare accuracy of this method to ecef_to_lla using Geographic lib as a ground truth
function ecef_to_lla_test{T <: Union{LL, LLA}}(::Type{T}, ecef, ellipse::Ellipsoid)

    ellipse = ellipsoid(T)

    # termination tolerances for convergence
    hTol = 1e-6
    latTol = hTol / ellipse.a

    # go back into Geodetic via iterative method
    X, Y, Z = getX(ecef), getY(ecef), getZ(ecef)
    lon = atan2(Y, X)
    R = sqrt(X*X + Y*Y)

    # initialisation for iterative lat and h solution
    h = 0.0
    lat = atan2(Z,(1.0 - ellipse.e²) * R)

    converged = false
    maxIter = 10
    i=1
    while (!converged) && (i <= maxIter)
        Nlat = ellipse.a/(sqrt(1.0 - ellipse.e² * (sin(lat))^2))
        hNew = R/cos(lat) - Nlat
        latNew = atan2(Z, R * (1.0 -  ellipse.e² * Nlat / (Nlat+hNew)))
        converged = abs(latNew - lat) < latTol && abs(h-hNew) < hTol
        lat = latNew
        h = hNew
        i=i+1
    end

    return T(rad2deg(lon), rad2deg(lat), h)

end

###############################
### ECEF to ENU coordinates ###
###############################

# calling with not enough / too much info on the reference point
geotransform(::Type{ENU}, ecef::ECEF, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = error("You must supply the reference point in the ENU type or as an additional input")
geotransform{T <: Union{LL, LLA}}(::Type{ENU{T}}, ecef::ECEF, ll_ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = error("Don't specify both the reference point in the type and provide it as an argument")

# get the reference location from the input type
geotransform{T <: ENU, U <: ECEF}(::Type{T}, X::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = ecef_to_enu(T, X, get_refloc(T))

# the reference point is user specified
geotransform(::Type{ENU_NULL}, ecef::ECEF, ll_ref::Union{LLA, LL}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = ecef_to_enu(ENU_NULL, ecef, ll_ref)
geotransform(::Type{ENU}, ecef::ECEF, ll_ref::Union{LLA, LL}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = ecef_to_enu(ENU_NULL, ecef, ll_ref)

# if given an arbitrary reference, convert it to LL
geotransform(::Type{ENU}, ecef::ECEF, ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = geotransform(T, ecef, LL(ref), GeodesyHandler, GeodesyHandler)
geotransform(::Type{ENU_NULL}, ecef::ECEF, ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = geotransform(T, ecef, LL(ref), GeodesyHandler, GeodesyHandler)

# worker function
function ecef_to_enu{T, U <: ECEF, V <: Union{LLA, LL}}(::Type{T}, ecef::U, ll_ref::V)
    ϕdeg, λdeg, h =  get_lat(ll_ref), get_lon(ll_ref), get_alt(ll_ref)

    # get the reference point in ecef
    ecef_ref = V <: LL ? geotransform(U, ll_ref) : geotransform(U, LL(ll_ref))  # omit height here when calculating the reference point in ECEF.  Add it back at the end
    Δx = ecef.x - ecef_ref.x
    Δy = ecef.y - ecef_ref.y
    Δz = ecef.z - ecef_ref.z

    # Compute rotation matrix
    sinλ, cosλ = sind(λdeg), cosd(λdeg)
    sinϕ, cosϕ = sind(ϕdeg), cosd(ϕdeg)

    # R = [     -sinλ       cosλ  0.0
    #      -cosλ*sinϕ -sinλ*sinϕ cosϕ
    #       cosλ*cosϕ  sinλ*cosϕ sinϕ]
    #
    # east, north, up = R * [Δx, Δy, Δz]
    east  =      -sinλ * Δx +       cosλ * Δy +  0.0 * Δz
    north = -cosλ*sinϕ * Δx + -sinλ*sinϕ * Δy + cosϕ * Δz
    up    =  cosλ*cosϕ * Δx +  sinλ*cosϕ * Δy + sinϕ * Δz

    return T(east, north, up - h)  # I think the height vector is the same direction as the ellipsoidal height at the ENU's origin
end

###############################################
### Transformation matrices for ECEF -> ENU ###
###############################################

# reference point in the template
geotransform_params{R <: Union{LLA, LL}, T <: ECEF}(::Type{ENU{R}}, ::Type{T}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = geotransform_params(T, ENU, R, GeodesyHandler, GeodesyHandler)

# reference point as a parameter
function geotransform_params{T <: ECEF, Ref <: Union{LLA, LL}}(::Type{ENU}, ::Type{T}, ll_ref::Ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})

    ϕdeg, λdeg, h =  get_lat(ll_ref), get_lon(ll_ref), get_alt(ll_ref)

    # Compute rotation matrix
    sinλ, cosλ = sind(λdeg), cosd(λdeg)
    sinϕ, cosϕ = sind(ϕdeg), cosd(ϕdeg)

    # Reference
    ecef_ref = Ref <: LL ? geotransform(T, ll_ref) : geotransform(T, LL(ll_ref))  # omit height here when calculating the reference point in ECEF.  Add it back at the end

    R = @fsa([-sinλ        cosλ        0.0;
              -cosλ*sinϕ   -sinλ*sinϕ    cosϕ;
               cosλ*cosϕ    sinλ*cosϕ    sinϕ])

    t = -R * Vec(ecef_ref.x, ecef_ref.y, ecef_ref.z) - @fsa([0.0, 0.0, h])
    return (R, t)

end

###############################
### ENU to ECEF coordinates ###
###############################

# convert to ECEF when the reference position is included in the ENU template
function geotransform{T <: ECEF, U}(::Type{T}, enu::ENU{U}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})   # I cant template U into anything because its a value type :-(
    ref = (isa(U, LL) || isa(U, LLA)) ? U : LL(U)
    enu_to_ecef(add_param(T, U), enu, ref)
end

# convert to ECEF when the LL reference position is not included in the ENU template
geotransform{T <: ECEF}(::Type{T}, enu::ENU_NULL, ll_ref::Union{LLA, LL}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = enu_to_ecef(add_param(T, typeof(ll_ref)), enu, ll_ref)

# if given an arbitrary reference, convert it to LL
geotransform{T <: ECEF}(::Type{T}, enu::ENU_NULL, ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = geotransform(T, enu, LL(ref), GeodesyHandler, GeodesyHandler)

# spit an error if provided with a reference point in the template and as a parameter
geotransform{T <: ECEF}(::Type{T}, enu::ENU_NULL, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = error("You must supply the reference point in the ENU type or as an additional input")
geotransform{T <: ECEF}(::Type{T}, enu::ENU, ll_ref::Union{LLA, LL}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = error("Dont supply both the reference point in thee template and as a parameter")

# worker function
function enu_to_ecef{T <: ECEF, U <: Union{LL, LLA}}(::Type{T}, enu::ENU, ll_ref::U)

    ϕdeg, λdeg, h =  get_lat(ll_ref), get_lon(ll_ref), get_alt(ll_ref)

    # Reference
    ecef_ref = U <: LL ? geotransform(T, ll_ref) : geotransform(T, LL(ll_ref))  # omit height here when calculating the reference point in ECEF.  Add it back later

    # Compute rotation matrix
    sinλ, cosλ = sind(λdeg), cosd(λdeg)
    sinϕ, cosϕ = sind(ϕdeg), cosd(ϕdeg)

    # Rᵀ = [-sinλ -cosλ*sinϕ cosλ*cosϕ
    #        cosλ -sinλ*sinϕ sinλ*cosϕ
    #         0.0       cosϕ      sinϕ]
    # Δx, Δy, Δz = Rᵀ * [east, north, up]
    Δx = -sinλ * enu.east + -cosλ*sinϕ * enu.north + cosλ*cosϕ * (enu.up + h)
    Δy =  cosλ * enu.east + -sinλ*sinϕ * enu.north + sinλ*cosϕ * (enu.up + h)
    Δz =   0.0 * enu.east +       cosϕ * enu.north +      sinϕ * (enu.up + h)

    X = ecef_ref.x + Δx
    Y = ecef_ref.y + Δy
    Z = ecef_ref.z + Δz

    return T(X,Y,Z)
end

###############################################
### Transformation matrices for ENU -> ECEF ###
###############################################

geotransform_params{T <: ECEF, R <: Union{LLA, LL}}(::Type{T}, ::Type{ENU{R}}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) = geotransform_params(T, ENU, R, GeodesyHandler, GeodesyHandler)

# no LLA reference provided in the ENU type so a LLA ref must be supplied
# N.B. this will ignore the LLA refernce in ENU template
function geotransform_params{T <: ECEF, U <: ENU, Ref <: Union{LLA, LL}}(::Type{T}, ::Type{U}, ll_ref::Ref, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})

    ϕdeg, λdeg, h =  get_lat(ll_ref), get_lon(ll_ref), get_alt(ll_ref)

    # Compute rotation matrix
    sinλ, cosλ = sind(λdeg), cosd(λdeg)
    sinϕ, cosϕ = sind(ϕdeg), cosd(ϕdeg)

    # Reference
    ecef_ref = Ref <: LL ? geotransform(T, ll_ref) : geotransform(T, LL(ll_ref))  # omit height here when calculating the reference point in ECEF.  Add it back later

    R = @fsa([-sinλ  -cosλ*sinϕ    cosλ*cosϕ;
               cosλ  -sinλ*sinϕ    sinλ*cosϕ;
               0.0    cosϕ            sinϕ])

    t = Vec(ecef_ref.x, ecef_ref.y, ecef_ref.z) + R * @fsa([0.0, 0.0, h])

    return (R, t)

end

##############################
### LLA to ENU coordinates ###
##############################

# no convert the point to ECEF then go from there
geotransform{T <: ENU, U <: Union{LLA, LL}}(::Type{T}, lla::U, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) =
    geotransform(T, ECEF{select_datum(T, U)}(lla), GeodesyHandler, GeodesyHandler)

# convert the point to ECEF then go from there
geotransform{T <: ENU, U <: Union{LLA, LL}, V}(::Type{T}, lla::U, ref::V, ::Type{GeodesyHandler}, ::Type{GeodesyHandler}) =
    geotransform(T, ECEF{select_datum(T, U, V)}(lla), ref, GeodesyHandler, GeodesyHandler)

################################
### ENU to LLA coordinates   ###
################################

geotransform{T <: Union{LLA, LL}}(::Type{T}, enu::ENU_NULL, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})  = error("You must supply the reference point in the ENU type or as an additional input")

# no convert the point to ECEF then go from there
function geotransform{T <: Union{LLA, LL}, U}(::Type{T}, enu::ENU{U}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    eT = ECEF{select_datum(T, U)}  # find a datum somewhere
    T(geotransform(eT, enu, GeodesyHandler, GeodesyHandler))
end

# convert the point to ECEF then go from there
function geotransform{T <: Union{LLA, LL}, U <: ENU, V}(::Type{T}, enu::U, ref::V, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})
    eT = ECEF{select_datum(T, U, V)} # find a datum somewhere
    T(geotransform(eT, enu, ref, GeodesyHandler, GeodesyHandler))
end

#############################################
### Decimal <=> Degrees, Minutes, Seconds ###
#############################################

function decimal2dms(x::Float64)
    d = trunc(x, 0)
    ms = 60 * abs(x - d)
    m = trunc(ms, 0)
    s = 60 * rem(ms, 1)

    return d, m, s
end

function dms2decimal(d::Float64, m::Float64, s::Float64)
    signbit(d) ? d - m/60 - s/3600 :
                 d + m/60 + s/3600
end

#################################################
### A vectorized version of the transforms
#################################################

# vectorizied transform for geodesy types
function geotransform_vector{T, U}(::Type{T}, X::Vector{U}, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})

    # make sure the output parameter is filled
    oT =  add_param(T, U)
    Xout = Vector{oT}(length(X))
    @inbounds for i = 1:length(X)
        Xout[i] = geotransform(oT, X[i])
    end
    return Xout
end

# vectorizied transform for geodesy types
function geotransform_vector{T, U, V}(::Type{T}, X::Vector{U}, ll_ref::V, ::Type{GeodesyHandler}, ::Type{GeodesyHandler})

    # make sure the output parameter is filled
    oT =  add_param(T, U)
    Xout = Vector{oT}(length(X))
    @inbounds for i = 1:length(X)
        Xout[i] = geotransform(oT, X[i], ll_ref)
    end
    return Xout
end


import Automa
import Automa.RegExp: @re_str
const re = Automa.RegExp

sp = re" "
tab = re"\t"
lf = re"\n"
num = re.cat(re.rep1(re"[ \t]"), "\n")
label = num

actions = Automa.compile(
    re.cat("  ", num)       => :(num(:push, 2)),
    re" \n "                => :(emit(:dup)),
    re.cat(" \t ", num)     => :(num(:copy, 3)),
    re" \n\t"               => :(emit(:swap)),
    re" \n\n"               => :(emit(:discard)),
    re.cat(" \t\n", num)    => :(num(:slide, 3)),
    re"\t   "               => :(emit(:add)),
    re"\t  \t"              => :(emit(:sub)),
    re"\t  \n"              => :(emit(:mul)),
    re"\t \t "              => :(emit(:div)),
    re"\t \t\t"             => :(emit(:mod)),
    re"\t\t "               => :(emit(:heap_write)),
    re"\t\t\t"              => :(emit(:heap_read)),
    re.cat("\n  ", label)   => :(label(:label, 3)),
    re.cat("\n \t", label)  => :(label(:call, 3)),
    re.cat("\n \n", label)  => :(label(:jump, 3)),
    re.cat("\n\t ", label)  => :(label(:jump_zero, 3)),
    re.cat("\n\t\t", label) => :(label(:jump_nega, 3)),
    re"\n\t\n"              => :(emit(:return)),
    re"\n\n\n"              => :(emit(:exit)),
    re"\t\n  "              => :(emit(:char_out)),
    re"\t\n \t"             => :(emit(:num_out)),
    re"\t\n\t "             => :(emit(:char_in)),
    re"\t\n\t\t"            => :(emit(:num_in))
)

context = Automa.CodeGenContext()

@eval function tokenize(data)
    $(Automa.generate_init_code(context, actions))
    p_end = p_eof = sizeof(data)
    tokens = Tuple{Symbol,String}[]

    emit(kind) = push!(tokens, (kind, data[ts:te]))

    # name, l: symbol of operation, length of matched string
    function num(name, l)
        push!(tokens, (name, data[ts:ts+l-1]))
        str = data[ts+l:te-1]
        str = replace(str, r"\A\t" => "-")
        str = replace(str, r"\A " => "+")
        str = replace(str, r" " => "0")
        str = replace(str, r"\t" => "1")
        push!(tokens, (:num, string(parse(Int, str, base=2))))
    end

    function label(name, l)
        push!(tokens, (name, data[ts:ts+l-1]))
        push!(tokens, (:label_name, data[ts+l:te-1]))
    end

    while p ≤ p_eof && cs > 0
        $(Automa.generate_exec_code(context, actions))
    end
    if cs < 0
        error("failed to tokenize")
    end
    return tokens
end