using Base.Meta
using JuMP: _valid_model, _error_if_cannot_register, object_dictionary, variable_type
using JuMP.Containers

# Parse raw input to define the upper bound for an interval set
function _parse_one_operator_parameter(
    _error::Function, infoexpr::_ParameterInfoExpr, ::Union{Val{:<=}, Val{:≤}},
    upper)
    JuMP._set_upper_bound_or_error(_error, infoexpr, upper)
end

# Parse raw input to define the lower bound for an interval set
function _parse_one_operator_parameter(
    _error::Function, infoexpr::_ParameterInfoExpr, ::Union{Val{:>=}, Val{:≥}},
    lower)
    JuMP._set_lower_bound_or_error(_error, infoexpr, lower)
end

# Parse raw input to define the distribution for a dist set
function _parse_one_operator_parameter(
    _error::Function, infoexpr::_ParameterInfoExpr, ::Union{Val{:in}, Val{:∈}},
    value)
    # check if interval set
    if isexpr(value.args[1], :vect)
        JuMP._set_lower_bound_or_error(_error, infoexpr,
                                  JuMP._esc_non_constant(value.args[1].args[1]))
        JuMP._set_upper_bound_or_error(_error, infoexpr,
                                  JuMP._esc_non_constant(value.args[1].args[2]))
    else
        _set_or_error(_error, infoexpr, value)
    end
end

# Default function for unrecognized operators
function _parse_one_operator_parameter(
    _error::Function, infoexpr::_ParameterInfoExpr, ::Val{S}, value) where S
    _error("Unknown sense $S.")
end

# In that case we assume the variable is the lhs.
function _parse_parameter(_error::Function, infoexpr::_ParameterInfoExpr,
                          sense::Symbol, var, value)
    _parse_one_operator_parameter(_error, infoexpr, Val(sense),
                                JuMP._esc_non_constant(value))
    return var
end

# If the lhs is a number and not the rhs, we can deduce that the rhs is
# the variable.
function _parse_parameter(_error::Function, infoexpr::_ParameterInfoExpr,
                          sense::Symbol, value::Number, var)
    _parse_one_operator_parameter(_error, infoexpr,
                                  JuMP.reverse_sense(Val(sense)),
                                  JuMP._esc_non_constant(value))
    return var
end

# Parse raw input to define the upper and lower bounds for an interval set
function _parse_ternary_parameter(_error::Function, infoexpr::_ParameterInfoExpr,
                                  ::Union{Val{:<=}, Val{:≤}}, lower,
                                  ::Union{Val{:<=}, Val{:≤}}, upper)
    JuMP._set_lower_bound_or_error(_error, infoexpr, lower)
    JuMP._set_upper_bound_or_error(_error, infoexpr, upper)
end

# Parse raw input to define the upper and lower bounds for an interval set
function _parse_ternary_parameter(_error::Function, infoexpr::_ParameterInfoExpr,
                                  ::Union{Val{:>=}, Val{:≥}}, upper,
                                  ::Union{Val{:>=}, Val{:≥}}, lower)
    _parse_ternary_parameter(_error, infoexpr, Val(:≤), lower, Val(:≤), upper)
end

# Default for unrecognized operators
function _parse_ternary_parameter(_error::Function, infoexpr::_ParameterInfoExpr,
                                  ::Val, lvalue, ::Val, rvalue)
    _error("Use the form lb <= ... <= ub.")
end

# Interpret a param_expr from macro input
function _parse_parameter(_error::Function, infoexpr::_ParameterInfoExpr, lvalue,
                          lsign::Symbol, var, rsign::Symbol, rvalue)
    # lvalue lsign var rsign rvalue
    _parse_ternary_parameter(_error, infoexpr, Val(lsign),
                             JuMP._esc_non_constant(lvalue), Val(rsign),
                             JuMP._esc_non_constant(rvalue))
    return var
end

# Extend to increase the param group id
function _assert_valid_model_call(m, macrocode)
    # assumes m is already escaped
    quote
        JuMP._valid_model($m, $(quot(m.args[1])))
        $(m).next_param_id += 1
        $macrocode
    end
end

"""
    @infinite_parameter(model, kw_args...)

Add an *anonymous* infinite parameter to the model `model` described by the
keyword arguments `kw_args` and returns the parameter reference.

```julia
@infinite_parameter(model, expr, kw_args...)
```

Add a parameter to the model `model` described by the expression `expr`, the
positional arguments `args` and the keyword arguments `kw_args`. (note that in
the following the symbol `<=` can be used instead of `≤`, the symbol `>=`can
be used instead of `≥`, and the symbo `in` can be used instead of `∈`) The
expression `expr` can be of the form:
- `paramexpr` creating parameters described by `paramexpr`.
- `lb ≤ paramexpr ≤ ub` creating parameters described by `paramexpr` characterized
   by a continuous interval set with lower bound `lb` and upper bound `ub`.
- `paramexpr ∈ [lb, ub]` creating parameters described by `paramexpr` characterized
   by a continuous interval set with lower bound `lb` and upper bound `ub`.
- `paramexpr ∈ dist` creating parameters described by `paramexpr` characterized
   by the `Distributions.jl` distribution object `dist`.

The expression `paramexpr` can be of the form:
- `paramname` creating a scalar parameter of name `paramname`
- `paramname[...]` or `[...]` creating a container of parameters

The recognized keyword arguments in `kw_args` are the following:
- `base_name`: Sets the name prefix used to generate parameter names. It
  corresponds to the parameter name for scalar parameter, otherwise, the
  parameter names are set to `base_name[...]` for each index `...` of the axes
  `axes`.
- `lower_bound`: Sets the value of the parameter lower bound for an interval set.
- `upper_bound`: Sets the value of the parameter upper bound for an interval set.
- `set`: The `InfiniteSet` characterizing the parameters see [`IntervalSet`](@ref)
   and [`DistributionSet`](@ref).
- `distribution`: Sets the `Distributions.jl` distribution object that characterizes
  the parameters.
- `supports`: Sets the support points for the parameters.
- `num_supports`: Specifies the number of supports to be automatically generated.
                  Note that `supports` takes precedence. Defaults to 50.
- `sig_figs`: Specifies the number of significant digits that should be used
              in automatic support generation. Defaults to 5.
- `independent`: Specifies if the each parameter is independent from each other
  or not. Defaults to false.
- `container`: Specify the container type. Defaults to automatic

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP, Distributions; m = InfiniteModel())
julia> @infinite_parameter(m, 0 <= x <= 1)
x

julia> @infinite_parameter(m, y[i = 1:2] in Normal(), num_supports = 10)
2-element Array{ParameterRef,1}:
 y[1]
 y[2]

julia> z = @infinite_parameter(m, ["a", "b"], distribution = Uniform(), independent = true)
2-dimensional DenseAxisArray{ParameterRef,2,...} with index sets:
    Dimension 1, "a"
    Dimension 2, "b"
And data, a 1×1 Array{ParameterRef,2}:
 noname
```
"""
macro infinite_parameter(model, args...)
    esc_model = esc(model)

    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)

    # check to see if error_args are provided define error function
    error_args = filter(kw -> kw.args[1] == :error_args, kw_args)
    if length(error_args) != 0
        new_args = error_args[1].args[2]
        macro_name = :finite_parameter
    else
        new_args = args
        macro_name = :infinite_parameter
    end
    _error(str...) = JuMP._macro_error(macro_name, (model, new_args...),
                                       str...)

    # if there is only a single non-keyword argument, this is an anonymous
    # variable spec and the one non-kwarg is the model
    if length(extra) == 0
        x = gensym()
        anon_singleton = true
    elseif length(extra) == 1
        x = popfirst!(extra)
        anon_singleton = false
    else
        x = popfirst!(extra)
        arg = popfirst!(extra)
        _error("Unrecognized argument $arg provided.")
    end

    info_kw_args = filter(_is_set_keyword, kw_args)
    extra_kw_args = filter(kw -> kw.args[1] != :base_name && !InfiniteOpt._is_set_keyword(kw) && kw.args[1] != :error_args, kw_args)
    base_name_kw_args = filter(kw -> kw.args[1] == :base_name, kw_args)
    infoexpr = InfiniteOpt._ParameterInfoExpr(; JuMP._keywordify.(info_kw_args)...)

    # There are five cases to consider:
    # x                                         | type of x | x.head
    # ------------------------------------------+-----------+------------
    # param                                       | Symbol    | NA
    # param[1:2]                                  | Expr      | :ref
    # param <= ub or param[1:2] <= ub             | Expr      | :call
    # param in [lb, ub]                           | Expr      | :call
    # lb <= param <= ub or lb <= param[1:2] <= ub | Expr      | :comparison
    # In the three last cases, we call parse_variable
    explicit_comparison = isexpr(x, :comparison) || isexpr(x, :call)
    if explicit_comparison
        param = InfiniteOpt._parse_parameter(_error, infoexpr, x.args...)
    else
        param = x
    end

    anonvar = isexpr(param, :vect) || isexpr(param, :vcat) || anon_singleton
    anonvar && explicit_comparison && _error("Cannot use explicit bounds via " *
                                             ">=, <= with an anonymous parameter")
    parameter = gensym()
    name = JuMPC._get_name(param)
    if isempty(base_name_kw_args)
        base_name = anonvar ? "" : string(name)
    else
        base_name = esc(base_name_kw_args[1].args[2])
    end

    if !isa(name, Symbol) && !anonvar
        _error("Expression $name should not be used as a parameter name. Use " *
               "the \"anonymous\" syntax $name = @infinite_parameter(model, " *
               "...) instead.")
    end

    set = InfiniteOpt._constructor_set(_error, infoexpr)
    if isa(param, Symbol)
        # Easy case - a single variable
        buildcall = :( build_parameter($_error, $set, 1, $(extra...)) )
        JuMP._add_kw_args(buildcall, extra_kw_args)
        parametercall = :( add_parameter($esc_model, $buildcall, $base_name,
                                         macro_call = true) )
        creationcode = :($parameter = $parametercall)
    else
        # isa(param, Expr) || _error("Expected $param to be a parameter name") --> not needed... I think
        # We now build the code to generate the variables (and possibly the
        # SparseAxisArray to contain them)
        idxvars, indices = JuMPC._build_ref_sets(_error, param)
        buildcall = :( build_parameter($_error, $set, length(collect($indices)),
                                       $(extra...)) )
        JuMP._add_kw_args(buildcall, extra_kw_args)
        parametercall = :( add_parameter($esc_model, $buildcall,
                                         $(JuMP._name_call(base_name, idxvars)),
                                         macro_call = true, multi_dim = true) )
        creationcode = JuMPC.container_code(idxvars, indices, parametercall,
                                             requestedcontainer)
    end
    if anonvar
        # Anonymous variable, no need to register it in the model-level
        # dictionary nor to assign it to a variable in the user scope.
        # We simply return the variable
        macro_code = creationcode
    else
        # We register the variable reference to its name and
        # we assign it to a variable in the local scope of this name
        macro_code = JuMP._macro_assign_and_return(creationcode, parameter, name,
                                                   model_for_registering = esc_model)
    end
    return _assert_valid_model_call(esc_model, macro_code)
end

"""
    @finite_parameter(model::InfiniteModel, value)

Define and add an anonymous finite parameter to `model` and return its
parameter reference. Its value is equal to `value`.

```julia
    @finite_parameter(model::InfiniteModel, param_expr, value_expr)
```
Define and add a finite parameter(s) to `model` and return appropriate parameter
reference(s). The parameter(s) has/have value(s) indicated by the `value_expr`.
The expression `param_expr` can be of the form:
- `paramname` creating a scalar parameter of name `paramname`
- `paramname[...]` or `[...]` creating a container of parameters

The expression `value_expr` simply expresses the value of the parameter(s). This
is typically a number but could be an array indexed using an index defined in
`param_expr`.

The recognized keyword arguments in `kw_args` are the following:
- `base_name`: Sets the name prefix used to generate parameter names. It
  corresponds to the parameter name for scalar parameter, otherwise, the
  parameter names are set to `base_name[...]` for each index `...` of the axes
  `axes`.
- `container`: Specify the container type.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP; model = InfiniteModel())
julia> par = @finite_parameter(model, 2)
noname

julia> vals = [3, 2];

julia> pars = @finite_parameter(model, [i = 1:2], vals[i], base_name = "par")
2-element Array{ParameterRef,1}:
 par[1]
 par[2]

julia> @finite_parameter(model, par2, 42)
par2
```
"""
macro finite_parameter(model, args...)
    # define error message function
    _error(str...) = JuMP._macro_error(:finite_parameter, (model, args...),
                                       str...)
    # parse the arguments
    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)

    # check number of arguments
    if length(extra) == 0 || length(extra) > 2
        _error("Incorrect number of arguments. Must be of form " *
               "@finite_parameter(model, name_expr, value).")
    end

    # ensure unsupported keywords are not given
    # if length(kw_args) != 0
    #     _error("Unrecognized keyword arguments given. Only the container " *
    #            "keyword is recognized.")
    # end

    # parse the input information from `extra`
    x = popfirst!(extra)
    # we have a single anonymous expression
    if length(extra) == 0
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an " *
                                               "`InfiniteModel`."
            value = $x
            @assert isa(value, Number) "Value must be a number."
            @infinite_parameter($model, lower_bound = value, upper_bound = value,
                                supports = value, error_args = $args,
                                container = $requestedcontainer, ($(kw_args...)))
        end
    # we have a some sort of name expression or vector expression
    elseif isa(x, Symbol) || isexpr(x, :vect) || isexpr(x, :vcat) || isexpr(x, :ref)
        name_expr = x
        value_expr = extra[1]
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an " *
                                               "`InfiniteModel`."
            @infinite_parameter($model, $name_expr, lower_bound = $value_expr,
                                upper_bound = $value_expr, supports = $value_expr,
                                container = $requestedcontainer, ($(kw_args...)),
                                error_args = $args)
        end
    # we have some other syntax
    else
        _error("Unrecognized input format. Must be of form " *
               "@finite_parameter(model, value) or " *
               "@finite_parameter(model, name[i =..., ...], value_expr).")
    end
    esc(code)
end

## Define helper functions needed to parse infinite variable expressions
# Check rhs to to ensure is not a variable as best we can
function _check_rhs(arg1, arg2)
    if isexpr(arg2, :ref)
        if isexpr(arg2.args[2], :kw)
            temp = arg2
            arg2 = arg1
            arg1 = temp
        end
    # elseif isexpr(arg2, :call) --> infinite loop if both sides are :calls
    #     temp = arg2
    #     arg2 = arg1
    #     arg1 = temp
    end
    return arg1, arg2
end

# Assume variable on lhs, but check if swapped
function _less_than_parse(arg1, arg2)
    # check if swapped
    arg1_orig = arg1
    arg1, arg2 = _check_rhs(arg1, arg2)
    if arg1 != arg1_orig
        return _greater_than_parse(arg1, arg2)
    end
    # check if has parameter tuple
    if isexpr(arg1, :call)
        return Expr(:call, :<=, arg1.args[1], arg2),
                    Expr(:tuple, arg1.args[2:end]...)
    else
        return Expr(:call, :<=, arg1, arg2), nothing
    end
end

# Assume variable on lhs, but check if swapped
function _greater_than_parse(arg1, arg2)
    # check if swapped
    arg1_orig = arg1
    arg1, arg2 = _check_rhs(arg1, arg2)
    if arg1 != arg1_orig
        return _less_than_parse(arg1, arg2)
    end
    # check if has parameter tuple
    if isexpr(arg1, :call)
        return Expr(:call, :>=, arg1.args[1], arg2),
                    Expr(:tuple, arg1.args[2:end]...)
    else
        return Expr(:call, :>=, arg1, arg2), nothing
    end
end

# The variable is on the rhs
function _less_than_parse(arg1::Number, arg2)
    return _greater_than_parse(arg2, arg1)
end

# The variable is on the rhs
function _greater_than_parse(arg1::Number, arg2)
    return _less_than_parse(arg2, arg1)
end

# Assume variable on lhs
function _equal_to_parse(arg1, arg2)
    arg1, arg2 = _check_rhs(arg1, arg2)
    if isexpr(arg1, :call)
        return Expr(:call, :(==), arg1.args[1], arg2),
                    Expr(:tuple, arg1.args[2:end]...)
    else
        return Expr(:call, :(==), arg1, arg2), nothing
    end
end

# The variable is on the rhs
function _equal_to_parse(arg1::Number, arg2)
    return _equal_to_parse(arg2, arg1)
end

# Return the tuple of form (var_expr, param_tuple_expr)
function _parse_parameters(_error::Function, head::Val{:call}, args)
    if args[1] in [:<=, :≤]
        return _less_than_parse(args[2], args[3])
    elseif args[1] in [:>=, :≥]
        return _greater_than_parse(args[2], args[3])
    elseif args[1] == :(==)
        return _equal_to_parse(args[2], args[3])
    elseif !(args[1] in [:in, :∈])
        return args[1], Expr(:tuple, args[2:end]...)
    else
        first = args[1]
        _error("Invalid operator $first.")
    end
end

# Return the tuple of form (var_expr, param_tuple_expr)
function _parse_parameters(_error::Function, head::Val{:comparison}, args)
    if isexpr(args[3], :call)
        return Expr(:comparison, args[1:2]..., args[3].args[1], args[4:5]...),
                    Expr(:tuple, args[3].args[2:end]...)
    else
        return Expr(:comparison, args...), nothing
    end
end

"""
    @infinte_variable(model, kw_args...)

Add an *anonymous* infinite variable to the model `model` described by the
keyword arguments `kw_args` and returns the variable reference. Note that the
`parameter_refs` keyword is required in this case.

```julia
@infinite_variable(model, varexpr, args..., kw_args...)
```

Add an infinite variable to `model` described by the expression `var_expr`, the
positional arguments `args` and the keyword arguments `kw_args`. The expression
`varexpr` can either be (note that in the following the symbol `<=` can be used
instead of `≤` and the symbol `>=`can be used instead of `≥`) of the form:

- `varexpr` creating variables described by `varexpr`
- `varexpr ≤ ub` (resp. `varexpr ≥ lb`) creating variables described by
  `varexpr` with upper bounds given by `ub` (resp. lower bounds given by `lb`)
- `varexpr == value` creating variables described by `varexpr` with fixed values
   given by `value`
- `lb ≤ varexpr ≤ ub` or `ub ≥ varexpr ≥ lb` creating variables described by
  `varexpr` with lower bounds given by `lb` and upper bounds given by `ub`

The expression `varexpr` can be of the form:

- `varname` creating a scalar real variable of name `varname`
- `varname(params...)` creating a scalar real variable of name `varname` with
  infinite parameters `params...` see `parameter_refs` for format.
- `varname[...]` or `[...]` creating a container of variables.
- `varname[...](params...)` or `[...]` creating a container of variables with
  infinite parameters `params...` in the first case.

The recognized positional arguments in `args` are the following:

- `Bin`: Sets the variable to be binary, i.e. either 0 or 1.
- `Int`: Sets the variable to be integer, i.e. one of ..., -2, -1, 0, 1, 2, ...

The recognized keyword arguments in `kw_args` are the following:

- `parameter_refs`: This is mandatory if not specified in `varexpr`. Can be a
  single parameter reference, a single parameter array with parameters defined
  in the same call of [`@infinite_parameter`](@ref) (i.e., have same group ID),
  or a tuple where each element is either of the first two options listed.
- `base_name`: Sets the name prefix used to generate variable names. It
  corresponds to the variable name for scalar variable, otherwise, the
  variable names are set to `base_name[...]` for each index `...` of the axes
  `axes`. Furthermore, the parameter reference tuple is appended on the end of
  the name i.e., `base_name(params...)` or `base_name[...](params...)`.
- `lower_bound`: Sets the value of the variable lower bound.
- `upper_bound`: Sets the value of the variable upper bound.
- `start`: Sets the variable starting value used as initial guess in optimization.
- `binary`: Sets whether the variable is binary or not.
- `integer`: Sets whether the variable is integer or not.
- `container`: Specify the container type.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP, Distributions; model = InfiniteModel())
julia> @infinite_parameter(model, 0 <= t <= 1)
t

julia> @infinite_parameter(model, w[1:2] in Normal())
2-element Array{ParameterRef,1}:
 w[1]
 w[2]

julia> @infinite_variable(model, x(t, w) >= 0)
x(t, w)

julia> x = @infinite_variable(model, parameter_refs = (t, w), base_name = "x",
                              lower_bound = 0)
x(t, w)

julia> lb = [0, 1]; ub = [10, 12];

julia> @infinite_variable(model, lb[i] <= y[i = 1:2](t) <= ub[i], Int)
2-element Array{InfiniteVariableRef,1}:
 y[1](t)
 y[2](t)
```
"""
macro infinite_variable(model, args...)
    _error(str...) = JuMP._macro_error(:infinite_variable, (model, args...),
                                       str...)

    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)
    param_kw_args = filter(kw -> kw.args[1] == :parameter_refs, kw_args)

    # Check for easy case if it is anonymous single variable
    if length(extra) == 0
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an `InfiniteModel`."
            JuMP.@variable($model, ($(args...)), variable_type = Infinite,
                           macro_error = $_error)
        end
    else
        x = popfirst!(extra)

        # There are several cases to consider:
        # x                                                     | type of x | x.head
        # ------------------------------------------------------+-----------+------------
        # var                                                   | Symbol    | NA
        # var[1:2]                                              | Expr      | :ref
        # var(x, y)                                             | Expr      | :call
        # var[1:2](x, y)                                        | Expr      | :call
        # var <= ub or var[1:2] <= ub                           | Expr      | :call
        # var(x, y) <= ub or var[1:2](x, y) <= ub               | Expr      | :call
        # lb <= var <= ub or lb <= var[1:2] <= ub               | Expr      | :comparison
        # lb <= var(x, y) <= ub or lb <= var[1:2](x, y) <= ub   | Expr      | :comparison
        if isexpr(x, :comparison) || isexpr(x, :call)
            inf_expr, params = InfiniteOpt._parse_parameters(_error, Val(x.head),
                                                             x.args)
        else
            inf_expr = x
            params = nothing
        end

        # check for double specification of parameters
        if length(param_kw_args) != 0 && params != nothing
            _error("Cannot specify double specify the infinite parameter " *
                   "references.")
        end

        # easy case where we can don't need to parse extra args
        if length(args) == 1
            code = quote
                @assert isa($model, InfiniteModel) "Model must be an " *
                                                   "`InfiniteModel`."
                JuMP.@variable($model, ($(inf_expr)),
                               variable_type = Infinite,
                               parameter_refs = $params,
                               macro_error = $_error)
            end
        # here we need to parse the extra args and include them in the call
        else
            rest_args = [args[i] for i = 2:length(args)]
            if isa(params, Nothing)
                code = quote
                    @assert isa($model, InfiniteModel) "Model must be an " *
                                                       "`InfiniteModel`."
                    JuMP.@variable($model, ($(inf_expr)), ($(rest_args...)),
                                   variable_type = Infinite,
                                   macro_error = $_error)
                end
            else
                code = quote
                    @assert isa($model, InfiniteModel) "Model must be an " *
                                                       "`InfiniteModel`."
                    JuMP.@variable($model, ($(inf_expr)), ($(rest_args...)),
                                   variable_type = Infinite,
                                   parameter_refs = $params,
                                   macro_error = $_error)
                end
            end
        end
    end
    return esc(code)
end

"""
    @point_variable(model, kw_args...)

Add an *anonymous* point variable to the model `model` described by the
keyword arguments `kw_args` and returns the variable reference. Note that the
`infinite_variable_ref` and `parameter_values` keywords are required in this
case.

```julia
@point_variable(model, infvarexpr, varexpr, args..., kw_args...)
```

Add a point variable to `model` described by the expression `varexpr`, the
positional arguments `args`, and the keyword arguments `kw_args` and the
infinite variable expr `infvarexpr`. The expression `infvarexpr` specifies the
infinite variable this point variable corresponds to and the values at which the
parameters are evaluated and must be of the form: `infvar(param_values...)`
where the parameter values `param_values...` are listed in the same format as
they are in teh definition of `infvar`. The expression `varexpr` is used to
define variable specific bounds and whose name is used as an alias for the point
variable which is simply the infinite variable evaluated at the values indicated.
The expression `varexpr` can either be (note that in the following the symbol
`<=` can be used instead of `≤` and the symbol `>=`can be used instead of `≥`)
of the form:

- `varexpr` creating variables described by `varexpr`
- `varexpr ≤ ub` (resp. `varexpr ≥ lb`) creating variables described by
  `varexpr` with upper bounds given by `ub` (resp. lower bounds given by `lb`)
- `varexpr == value` creating variables described by `varexpr` with fixed values
   given by `value`
- `lb ≤ varexpr ≤ ub` or `ub ≥ varexpr ≥ lb` creating variables described by
  `varexpr` with lower bounds given by `lb` and upper bounds given by `ub`

 Note that be default a point variable inherits all of the same properties as
 the infinite variable it corresponds to, but that these can be overwritten
 by specifying properties such as lower bounds, fix values, etc.

The expression `varexpr` can be of the form:

- `varname` creating a scalar real variable of alias name `varname`
- `varname[...]` or `[...]` creating a container of variables.

The recognized positional arguments in `args` are the following:

- `Bin`: Sets the variable to be binary, i.e. either 0 or 1.
- `Int`: Sets the variable to be integer, i.e. one of ..., -2, -1, 0, 1, 2, ...

The recognized keyword arguments in `kw_args` are the following:

- `infinite_variable_ref`: Sets the infinite variable reference that the point
  variable is associated with.
- `parameter_refs`: Sets the values of the infinite parameters of the infinite
  variable at which this poitn variable is evaluated at. Must be of the same
  format of that specified for the parameters in the definition of the infinite
  variable.
- `base_name`: Sets the name prefix used to generate variable names. It
  corresponds to the variable name for scalar variable, otherwise, the
  variable names are set to `base_name[...]` for each index `...` of the axes
  `axes`. This serves as the alias for `infvarexpr` (the infinite variable
  evaluated at particular parameter values).
- `lower_bound`: Sets the value of the variable lower bound.
- `upper_bound`: Sets the value of the variable upper bound.
- `start`: Sets the variable starting value used as initial guess in optimization.
- `binary`: Sets whether the variable is binary or not.
- `integer`: Sets whether the variable is integer or not.
- `container`: Specify the container type.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP, Distributions; model = InfiniteModel())
julia> @infinite_parameter(model, 0 <= t <= 1)
t

julia> @infinite_parameter(model, w[1:2] in Normal())
2-element Array{ParameterRef,1}:
 w[1]
 w[2]

julia> @infinite_variable(model, x(t, w) >= 0)
x(t, w)

julia> @point_variable(model, x(0, [0, 0]), x0 <= 1)
x0

julia> x0 = @point_variable(model, x(0, [0, 0]), upper_bound = 1, base_name = "x0")
x0

julia> x0 = @point_variable(model, upper_bound = 1, base_name = "x0",
                            infinite_variable_ref = x, parameter_values = (0, [0, 0]))
x0

julia> @point_variable(model, x([0, 1][i], [0, 0]), xf[i = 1:2])
2-element Array{PointVariableRef,1}:
 xf[1]
 xf[2]

julia> lb = [0, 1]; ub = [10, 12];

julia> @infinite_variable(model, lb[i] <= y[i = 1:2](t) <= ub[i], Int)
2-element Array{InfiniteVariableRef,1}:
 y[1](t)
 y[2](t)

julia> @point_variable(model, y[i](0), y0[i = 1:2], Bin)
2-element Array{PointVariableRef,1}:
 y0[1]
 y0[2]

julia> y0 = @point_variable(model, [i = 1:2], binary = true, base_name = "y0",
                             infinite_variable_ref = y[i], parameter_values = 0)
2-element Array{PointVariableRef,1}:
 y0[1]
 y0[2]
```
"""
macro point_variable(model, args...)
    _error(str...) = JuMP._macro_error(:point_variable,
                                       (model, args...), str...)

    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)
    param_kw_args = filter(kw -> kw.args[1] == :infinite_variable_ref || kw.args[1] == :parameter_values, kw_args)

    # Check for easy case if it is anonymous single variable
    if length(extra) == 0
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an " *
                                               "`InfiniteModel`."
            JuMP.@variable($model, ($(args...)), variable_type = Point,
                           macro_error = $_error)
        end
    else
        x = popfirst!(extra)

        if isexpr(x, :call)
            if length(param_kw_args) != 0
                _error("Cannot double specify the infinite variable reference " *
                       "and/or its parameter values.")
            elseif x.args[1] in [:in, :<=, :>=, :(==), :≥, :≤, ∈]
                _error("Invalid input syntax.")
            end
            rest_args = [args[i] for i = 2:length(args)]
            inf_var = x.args[1]
            param_vals = Expr(:tuple, x.args[2:end]...)
            code = quote
                @assert isa($model, InfiniteModel) "Model must be an " *
                                                   "`InfiniteModel`."
                JuMP.@variable($model, ($(rest_args...)), variable_type = Point,
                               infinite_variable_ref = $inf_var,
                               parameter_values = $param_vals,
                               macro_error = $_error)
            end
        elseif isexpr(x, :vect) && length(extra) == 0
            code = quote
                @assert isa($model, InfiniteModel) "Model must be an " *
                                                   "`InfiniteModel`."
                JuMP.@variable($model, ($(args...)), variable_type = Point,
                               macro_error = $_error)
            end
        else
            _error("Invalid input syntax.")
        end
    end
    return esc(code)
end

"""
    @hold_variable(model, kw_args...)

Add an *anonymous* hold variable to the model `model` described by the
keyword arguments `kw_args` and returns the variable reference.

```julia
@hold_variable(model, varexpr, args..., kw_args...)
```

Add a hold variable to `model` described by the expression `varexpr`, the
positional arguments `args` and the keyword arguments `kw_args`. The expression
`varexpr` can either be (note that in the following the symbol `<=` can be used
instead of `≤` and the symbol `>=`can be used instead of `≥`) of the form:

- `varexpr` creating variables described by `varexpr`
- `varexpr ≤ ub` (resp. `varexpr ≥ lb`) creating variables described by
  `varexpr` with upper bounds given by `ub` (resp. lower bounds given by `lb`)
- `varexpr == value` creating variables described by `varexpr` with fixed values
   given by `value`
- `lb ≤ varexpr ≤ ub` or `ub ≥ varexpr ≥ lb` creating variables described by
  `varexpr` with lower bounds given by `lb` and upper bounds given by `ub`

The expression `varexpr` can be of the form:

- `varname` creating a scalar real variable of name `varname`
- `varname[...]` or `[...]` creating a container of variables.

The recognized positional arguments in `args` are the following:

- `Bin`: Sets the variable to be binary, i.e. either 0 or 1.
- `Int`: Sets the variable to be integer, i.e. one of ..., -2, -1, 0, 1, 2, ...

Specifiying a hold variable which applies only to sub-domain of the model's
infinite parameter(s) domain can be done via the `parameter_bounds` keyword
argument. It is specified as a tuple of parameter bound expressions which can
be of the form:

- `(param in [lb, ub], ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub` (note `∈` can be used in place of `in`)
- `(params in [lb, ub], ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(lb <= param <= ub, ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub`
- `(lb <= params <= ub, ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(param == value, ...)` enforcing `param` to be equal to `value`
- `(params == value, ...)` enforcing that all parameter references in `params`
                            each to be equal to `value`
- Any combination of the above forms. Must be inside parentheses and comma
  separated.

The other recognized keyword arguments in `kw_args` are the following:

- `base_name`: Sets the name prefix used to generate variable names. It
  corresponds to the variable name for scalar variable, otherwise, the
  variable names are set to `base_name[...]` for each index `...` of the axes
  `axes`.
- `lower_bound`: Sets the value of the variable lower bound.
- `upper_bound`: Sets the value of the variable upper bound.
- `start`: Sets the variable starting value used as initial guess in optimization.
- `binary`: Sets whether the variable is binary or not.
- `integer`: Sets whether the variable is integer or not.
- `container`: Specify the container type.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP; model = InfiniteModel(); @infinite_parameter(model, t in [0, 10]))
julia> @hold_variable(model, x)
x

julia> @hold_variable(model, 0 <= y <= 4, Bin)
y

julia> y = @hold_variable(model, lower_bound = 0, upper_bound = 4,
                            binary = true, base_name = "y")
y

julia> @hold_variable(model, z[2:3] == 0)
1-dimensional DenseAxisArray{HoldVariableRef,1,...} with index sets:
    Dimension 1, 2:3
And data, a 2-element Array{HoldVariableRef,1}:
 z[2]
 z[3]

julia> @hold_variable(model, d, parameter_bounds = (t in [0, 5]))
d
```
"""
macro hold_variable(model, args...)
    _error(str...) = JuMP._macro_error(:hold_variable, (model, args...),
                                       str...)
    # parse the arguments
    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)
    bound_kw_args = filter(kw -> kw.args[1] == :parameter_bounds, kw_args)

    # no bounds given so we don't need to do anything
    if length(bound_kw_args) == 0
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an " *
                                               "`InfiniteModel`."
            JuMP.@variable($model, ($(args...)), variable_type = Hold,
                           macro_error = $_error)
        end
    else
        x = bound_kw_args[1].args[2]
        if isexpr(x, :call) || isexpr(x, :tuple) || isexpr(x, :comparison)
           name_expr, bounds = InfiniteOpt._extract_bounds(_error, x.args,
                                                           Val(x.head))
        else
           _error("Unrecognized input format for parameter bounds. Must be of " *
                  "tuple with elements of form: par(s) in [lb, ub] or " *
                  "par(s) = value.")
        end
        extra_kw_args = filter(kw -> kw.args[1] != :parameter_bounds, kw_args)
        code = quote
            @assert isa($model, InfiniteModel) "Model must be an " *
                                               "`InfiniteModel`."
            JuMP.@variable($model, ($(extra...)), variable_type = Hold,
                           parameter_bounds = ($(bounds)), macro_error = $_error,
                           container = ($(requestedcontainer)),
                           ($(extra_kw_args...)))
        end
    end
    return esc(code)
end

## Helper function for parsing the parameter bounds and the name expression
# Check that name expression is in acceptable form or throw error
function _parse_name_expression(_error::Function, expr)
    if isexpr(expr, :ref) || isexpr(expr, :vect)|| isa(expr, Symbol) || isexpr(expr, :vcat)
        return expr
    else
        _error("Unrecognized input format for name expression. Must be of " *
               "form name[i = ..., ...](param_bounds).")
    end
end

# Return IntervalSet call given a :vect expression or error
function _make_interval_set(_error::Function, expr::Expr)
    if isexpr(expr, :vect) && length(expr.args) == 2
        return Expr(:call, :IntervalSet, expr.args...)
    else
        _error("Unrecognized input format for parameter bounds. Must be of form " *
        "par in [lb, ub], lb <= par <= ub,  or par = value.")
    end
end

# Return ParameterRef(s) as a vector if necessary (to handle JuMP arrays)
function _process_parameter(arg)
    return Expr(:call, :(InfiniteOpt._make_vector), arg)
end

## Return a bound pair of form param => IntervalSet(lb, ub) or error
# Call expressions using :in
function _make_bound_pair(_error::Function, expr::Expr,
                          ::Union{Val{:in}, Val{:∈}})
    set = _make_interval_set(_error, expr.args[3])
    param = _process_parameter(expr.args[2])
    return Expr(:call, :(=>), param, set)
end

# Call expressions using :(==)
function _make_bound_pair(_error::Function, expr::Expr, ::Val{:(==)})
    set = _make_interval_set(_error, Expr(:vect, expr.args[3], expr.args[3]))
    param = _process_parameter(expr.args[2])
    return Expr(:call, :(=>), param, set)
end

# Comparison expressions using something else
function _make_bound_pair(_error::Function, expr::Expr, first)
    _error("Invalid set format operators. Must be of form par in [lb, ub], " *
           "lb <= par <= ub, or par = value.")
end

# Comparison expressions using :(<=)
function _make_bound_pair(_error::Function, expr::Expr, ::Val{:(<=)},
                          ::Val{:(<=)})
    set = _make_interval_set(_error, Expr(:vect, expr.args[1], expr.args[5]))
    param = _process_parameter(expr.args[3])
    return Expr(:call, :(=>), param, set)
end

# Comparison expressions using :(>=)
function _make_bound_pair(_error::Function, expr::Expr, ::Val{:(>=)},
                          ::Val{:(>=)})
    set = _make_interval_set(_error, Expr(:vect, expr.args[5], expr.args[1]))
    param = _process_parameter(expr.args[3])
    return Expr(:call, :(=>), param, set)
end

# Comparison expressions using something else
function _make_bound_pair(_error::Function, expr::Expr, first, second)
    _error("Invalid set format operators. Must be of form par in [lb, ub], " *
           "lb <= par <= ub, or par = value.")
end

# General wrapper
function _make_bound_pair(_error::Function, expr::Expr)
    if isexpr(expr, :call)
        return _make_bound_pair(_error, expr, Val(expr.args[1]))
    elseif isexpr(expr, :comparison)
        return _make_bound_pair(_error, expr, Val(expr.args[2]), Val(expr.args[4]))
    else
        _error("Unrecognized input format for parameter bounds. Must be of form " *
               "par in [lb, ub], lb <= par <= ub, or par = value.")
    end
end

# Return a dictionary of parameter bounds given raw vector of call expressions
function _parse_parameter_bounds(_error::Function, args::Vector)
    dict_args = [_make_bound_pair(_error, arg) for arg in args]
    return Expr(:call, :ParameterBounds, Expr(:call, :Dict, dict_args...))
end

# Only 1 parameter bound is given thus dispatch as a vector to make dictionary
function _parse_parameter_bounds(_error::Function, expr::Expr)
    return _parse_parameter_bounds(_error, [expr])
end

## Return name expression and parameter dictionary expression separatedly
# (:call) In this case we are not sure if a name expression is given
function _extract_bounds(_error::Function, args::Vector, ::Val{:call})
    # check if only one parameter bound was given and nothing else
    if args[1] in [:in, :∈, :(==)]
        bounds = _parse_parameter_bounds(_error, Expr(:call, args...))
        return nothing, bounds
    # otherwise parameter bounds were given with a name expression
    else
        name_expr = _parse_name_expression(_error, args[1])
        bounds = _parse_parameter_bounds(_error, args[2:end])
        return name_expr, bounds
    end
end

# (:tuple) In this case we know mulitple bounds are given with no name expression
function _extract_bounds(_error::Function, args::Vector, ::Val{:tuple})
    bounds = _parse_parameter_bounds(_error, args)
    return nothing, bounds
end

# (:comparison) In this case we have a single interval set and nothing else
function _extract_bounds(_error::Function, args::Vector, ::Val{:comparison})
    bounds = _parse_parameter_bounds(_error, Expr(:comparison, args...))
    return nothing, bounds
end

"""
    @BDconstraint(model::InfiniteModel, [i = ..., ...](bound_expr), constr_expr;
                  [kw_args...])

Add an anonymous bounded constraint to `model` and return an appropriate
container of constraint reference(s).

```julia
    @BDconstraint(model::InfiniteModel, name[i = ..., ...](bound_expr),
                  constr_expr; [kw_args...])
```

Add a named bounded constraint to `model` and return an appropriate
container of constraint reference(s). This defines the constraint as expressed
in `constr_expr` over some sub-domain of parameters as indicated by `bound_expr`.
The format of `constr_expr` must follow the same syntax as that specified in
[`JuMP.@constraint`](@ref). For example, if we want to express the constraint
`2T(t, x) + 3y = 42`, the constraint expression `constr_expr` would be
`2 * T + 3 * y == 42`.

By default, `JuMP.@constraint` would express the above example constraint over
the whole domain of `t` and `x`. However, we can use `@BDconstraint` to express
this constraint over some sub-domain(s) via `bound_expr`. Here `(bound_expr)`
can be of the form:

- `(param in [lb, ub], ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub` (note `∈` can be used in place of `in`)
- `(params in [lb, ub], ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(lb <= param <= ub, ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub`
- `(lb <= params <= ub, ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(param == value, ...)` enforcing `param` to be equal to `value`
- `(params == value, ...)` enforcing that all parameter references in `params`
                            each to be equal to `value`
- Any combination of the above forms. Must be inside parentheses and comma
  separated.

Like typical constraints, the `container` keyword argument can be used to specify
the `JuMP` container type used to store the constraint references. Note this
macro errors if `bound_expr` is ommited or if some unrecognized syntax is used.

**Examples**
```julia-repl
julia> @BDconstraint(model, c1(t in [0, 1]), T^2 + z <= 1)
c1 : T(x, t)² + z ≤ 1.0, ∀ t ∈ [0, 1]

julia> @BDconstraint(model, c2[i = 1:3](x[i] in [0, 1]), T^2 + z + x[i] <= 1)
3-element Array{GeneralConstraintRef,1}:
 c2[1] : T(x, t)² + z + x[1] ≤ 1.0, ∀ x[1] ∈ [0, 1]
 c2[2] : T(x, t)² + z + x[2] ≤ 1.0, ∀ x[2] ∈ [0, 1]
 c2[3] : T(x, t)² + z + x[3] ≤ 1.0, ∀ x[3] ∈ [0, 1]

julia> @BDconstraint(model, (x == 0, t == 0), T^2 + z <= 1)
T(x, t)² + z ≤ 1.0, ∀ x[2] = 0, x[3] = 0, t = 0, x[1] = 0

julia> @BDconstraint(model, [i = 1:3](x[i] == 0), T^2 + z <= 1,
                     container = SparseAxisArray)
JuMP.Containers.SparseAxisArray{GeneralConstraintRef,1,Tuple{Any}} with 3 entries:
  [3]  =  T(x, t)² + z ≤ 1.0, ∀ x[3] = 0
  [2]  =  T(x, t)² + z ≤ 1.0, ∀ x[2] = 0
  [1]  =  T(x, t)² + z ≤ 1.0, ∀ x[1] = 0
```
"""
macro BDconstraint(model, args...)
    # define appropriate error message function
    _error(str...) = JuMP._macro_error(:BDconstraint, (model, args...), str...)

    # parse the arguments
    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)
    bound_kw_args = filter(kw -> kw.args[1] == :parameter_bounds, kw_args)

    # check for double specification of parameter bounds
    if length(bound_kw_args) != 0
        _error("Cannot double specify the parameter bounds. Use form " *
               "@BDconstraint(model, name[i=..., ...](par in [lb, ub], " *
               "par2 = value, ...), expr).")
    end

    # check that enough arguments are given
    if length(extra) != 2
        _error("Incorrect amount of arguments. Must be of form " *
               "@BDconstraint(model, name[i=..., ...](par in [lb, ub], " *
               "par2 = value, ...), expr).")
    # otherwise we have a 3 argument form
    else
        # process the 2nd argument for the parameter bounds if provided
        x = popfirst!(extra)
        if isexpr(x, :ref) || isexpr(x, :vect) || isa(x, Symbol) || isexpr(x, :vcat)
            _error("Must specify at least one parameter bound of the form " *
                   "@BDconstraint(model, name[i=..., ...](par in [lb, ub], " *
                   "par2 = value, ...), expr).")
        elseif isexpr(x, :call) || isexpr(x, :tuple) || isexpr(x, :comparison)
            name_expr, bounds = InfiniteOpt._extract_bounds(_error, x.args,
                                                            Val(x.head))
        else
            _error("Unrecognized input format for name expression. Must be of " *
                   "form @BDconstraint(model, name[i=..., ...](par in [lb, ub], " *
                   "par2 = value, ...), expr).")
        end
        # TODO check the expression type somewhere...
        # we have a single anonymous constraint
        if isa(name_expr, Nothing)
            code = quote
                @assert isa($model, InfiniteModel) "Model must be an " *
                                                   "`InfiniteModel`."
                JuMP.@constraint($model, ($(extra[1])),
                                 parameter_bounds = ($(bounds)), ($(kw_args...)),
                                 container = ($(requestedcontainer)))
            end
        # we have some sort of name expression in our constraint
        else
            code = quote
                @assert isa($model, InfiniteModel) "Model must be an " *
                                                   "`InfiniteModel`."
                JuMP.@constraint($model, ($(name_expr)), ($(extra[1])),
                                 parameter_bounds = ($(bounds)), ($(kw_args...)),
                                 container = ($(requestedcontainer)))
            end
        end
    end
    return esc(code)
end

# TODO add looping capability for multi-dimensional refs
"""
    @set_parameter_bounds(ref, bound_expr; [force = false])

Specify new parameter bounds for a constraint reference or hold variable
reference `ref`. These bounds correspond to bounding a constraint in an
equivalent way to using [`@BDconstraint`](@ref) or to limiting the scope of a
hold variable in an equivalent way to using the `parameter_bounds` keyword
argument in [`@hold_variable`](@ref). Here `(bound_expr)` can be of the form:

- `(param in [lb, ub], ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub` (note `∈` can be used in place of `in`)
- `(params in [lb, ub], ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(lb <= param <= ub, ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub`
- `(lb <= params <= ub, ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(param == value, ...)` enforcing `param` to be equal to `value`
- `(params == value, ...)` enforcing that all parameter references in `params`
                            each to be equal to `value`
- Any combination of the above forms. Must be inside parentheses and comma
  separated.

Errors if the constraint or variable corresponding to `ref` already has bounds.
However, using `force = true` can be used ignore the current bounds and
overwrite them with new ones. Also, note that bounds on dependent constraints
of hold variables will be updated to account for changes in hold variable bounds.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP; model = InfiniteModel())
julia> @infinite_parameter(model, t in [0, 10])
t

julia> @infinite_variable(model, x(t))
x(t)

julia> @hold_variable(model, y)
y

julia> @constraint(model, con, x + y == 0)
con : x(t) + y = 0.0

julia> @set_parameter_bounds(y, t in [0, 5])

julia> con
con : x(t) + y = 0.0, ∀ t ∈ [0, 5]

julia> @set_parameter_bounds(con, t == 0, force = true)

julia> con
con : x(t) + y = 0.0, ∀ t = 0
```
"""
macro set_parameter_bounds(ref, bound_expr, args...)
    # define appropriate error message function
    _error(str...) = JuMP._macro_error(:set_parameter_bounds,
                                       (ref, bound_expr, args...), str...)

    # parse the arguments
    extra, kw_args, requestedcontainer = JuMPC._extract_kw_args(args)
    extra_kw_args = filter(kw -> kw.args[1] != :force, kw_args)
    if length(extra) != 0
        _error("Too many positional arguments.")
    elseif length(extra_kw_args) != 0
        _error("Unrecognized keyword arguments.")
    end

    # parse the bounds
    x = bound_expr
    if isexpr(x, :call) || isexpr(x, :tuple) || isexpr(x, :comparison)
       name_expr, bounds = InfiniteOpt._extract_bounds(_error, x.args,
                                                       Val(x.head))
    else
       _error("Unrecognized input format for parameter bounds. Must be of " *
              "tuple with elements of form: par(s) in [lb, ub] or " *
              "par(s) = value.")
    end
    # prepare the code
    code = quote
        @assert(isa($ref, Union{GeneralConstraintRef, HoldVariableRef}),
                "Reference must correspond to a constraint or hold variable.")
        set_parameter_bounds($ref, $bounds; ($(args...)), _error = $_error)
    end
    return esc(code)
end

"""
    @add_parameter_bounds(ref, bound_expr)

Add new parameter bounds for a constraint reference or hold variable
reference `ref`. These bounds correspond to bounding a constraint in an
equivalent way to using [`@BDconstraint`](@ref) or to limiting the scope of a
hold variable in an equivalent way to using the `parameter_bounds` keyword
argument in [`@hold_variable`](@ref). Here `(bound_expr)` can be of the form:

- `(param in [lb, ub], ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub` (note `∈` can be used in place of `in`)
- `(params in [lb, ub], ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(lb <= param <= ub, ...)` enforcing `param` to be in a sub-domain from `lb`
                             to `ub`
- `(lb <= params <= ub, ...)` enforcing that all parameter references in `params`
                              each be a in sub-domain from `lb` to `ub`
- `(param == value, ...)` enforcing `param` to be equal to `value`
- `(params == value, ...)` enforcing that all parameter references in `params`
                            each to be equal to `value`
- Any combination of the above forms. Must be inside parentheses and comma
  separated.

Errors if the new bounds cause irreconcilable differences with existing measures
and constraints. For example, this occurs when adding hold variable bounds
that are outside the domain of a bounded constraint that uses that hold variable.
Also, note that bounds on dependent constraints of hold variables will be
updated to account for changes in hold variable bounds.

**Examples**
```jldoctest; setup = :(using InfiniteOpt, JuMP; model = InfiniteModel())
julia> @infinite_parameter(model, t in [0, 10])
t

julia> @infinite_parameter(model, q in [-2, 2])
q

julia> @infinite_variable(model, x(t, q))
x(t, q)

julia> @hold_variable(model, y)
y

julia> @constraint(model, con, x + y == 0)
con : x(t, q) + y = 0.0

julia> @add_parameter_bounds(y, t in [0, 5])

julia> con
con : x(t, q) + y = 0.0, ∀ t ∈ [0, 5]

julia> @add_parameter_bounds(con, q == 0)

julia> con
con : x(t, q) + y = 0.0, ∀ t ∈ [0, 5], q = 0
```
"""
macro add_parameter_bounds(ref, bound_expr)
    # define appropriate error message function
    _error(str...) = JuMP._macro_error(:add_parameter_bounds,
                                       (ref, bound_expr), str...)

    # parse the bounds
    x = bound_expr
    if isexpr(x, :call) || isexpr(x, :tuple) || isexpr(x, :comparison)
       name_expr, bounds = InfiniteOpt._extract_bounds(_error, x.args,
                                                       Val(x.head))
    else
       _error("Unrecognized input format for parameter bounds. Must be of " *
              "tuple with elements of form: par(s) in [lb, ub] or " *
              "par(s) = value.")
    end
    # prepare the code
    code = quote
        @assert(isa($ref, Union{GeneralConstraintRef, HoldVariableRef}),
                "Reference must correspond to a constraint or hold variable.")
        for (pref, set) in ($(bounds)).intervals
            add_parameter_bound($ref, pref, set.lower_bound, set.upper_bound,
                                 _error = $_error)
        end
    end
    return esc(code)
end


"""
Additional activity selector

returns a category of additional activity choosen by agent.

**Arguments**
* `feature_classes` : dictionary containing all possible types of features in the simulation

** Assumptions
 - there is maximum one waypoint
 -probability of driving directly to work is equal to 0.5
 -otherwise waypoint type is chosen randomly from the features categories
"""
function additional_activity(agent_profile::DataFrames.DataFrame, before::Bool,
	                         sim_data::OpenStreetMapXSim.SimData)
	if rand() < 0.5
		return nothing
	else
		return rand(keys(sim_data.feature_classes))
	end
end


using Colors
using DataFrames
using DataFramesMeta
using NearestNeighbors
using Random
using Statistics
using VegaLite

import Base.show
import Base64
import CairoMakie as MK
import ColorSchemes
import MultivariateStats

plot_molecules!(args...; kwargs...) = plot_molecules(args...; append=true, kwargs...)

function plot_molecules(df_spatial::DataFrame, polygons::Array{Matrix{Float64}, 1}=Matrix{Float64}[]; markersize=2,
        color::Union{Vector, Symbol, String}=:gene, size=(800, 800), poly_strokewidth=1, xlims=nothing, ylims=nothing, offset=(0, 0),
        is_noise::Union{Vector, BitArray, Symbol, Nothing}=nothing, annotation::Union{<:AbstractVector, Symbol, Nothing} = nothing,
        ann_colors::Union{Nothing, Dict} = nothing, legend=(annotation !== nothing), fontsize=8,
        noise_ann = nothing, shuffle_colors::Bool=false, append::Bool=false,
        polygon_kwargs::KWArgT=nothing, axis_kwargs::KWArgT=nothing, noise_kwargs::KWArgT=nothing, legend_kwargs::KWArgT=nothing, kwargs...)

    noise_args_default = (marker=:xcross, markersize=(0.75 * markersize), strokewidth=0, color="black");
    axis_args_default = (xticklabelsize=12, yticklabelsize=12, xticksvisible=false, xticklabelsvisible=false, yticksvisible=false, yticklabelsvisible=false);
    legend_args_default = (bgcolor=Colors.RGBA(1, 1, 1, 0.85),);
    polygon_args_default = (strokecolor="black", color="transparent", strokewidth=poly_strokewidth, label="")

    noise_kwargs = update_args(update_args(noise_args_default, Dict(kwargs...)), noise_kwargs)
    axis_kwargs = update_args(axis_args_default, axis_kwargs)
    legend_kwargs = update_args(legend_args_default, legend_kwargs)
    polygon_kwargs = update_args(polygon_args_default, polygon_kwargs)

    if typeof(color) === Symbol
        color = df_spatial[!,color]
    end

    if annotation !== nothing
        if typeof(annotation) === Symbol
            annotation = df_spatial[!,annotation]
        end

        annotation = ["$a" for a in annotation]
        if noise_ann !== nothing
            noise_ann = "$noise_ann"
        end
    end

    xlims = something(xlims, val_range(df_spatial.x))
    ylims = something(ylims, val_range(df_spatial.y))

    if !append
        fig = MK.Figure(resolution=size)
        fig[1, 1] = MK.Axis(fig; axis_kwargs...);
    end

    df_noise = nothing

    if typeof(is_noise) === Symbol
        is_noise = df_spatial[!,is_noise]
    end

    if is_noise !== nothing
        df_noise = df_spatial[is_noise,:]
        df_spatial = df_spatial[.!is_noise,:]
        color = color[.!is_noise]
    end

    if is_noise !== nothing
        MK.scatter!(df_noise.x .+ offset[1], df_noise.y .+ offset[2]; noise_kwargs...)
    end

    if annotation === nothing
        MK.scatter!(df_spatial.x .+ offset[1], df_spatial.y .+ offset[2]; color=color,
            strokewidth=0, markersize=markersize, kwargs...)
    else
        ann_vals = annotation[annotation .!= noise_ann] |> unique |> sort
        c_map = Colors.distinguishable_colors(length(ann_vals), Colors.colorant"#007a10", lchoices=range(20, stop=70, length=15))
        if shuffle_colors
            Random.shuffle!(c_map)
        end

        for (color, ann) in zip(c_map, ann_vals)
            style_dict = (ann_colors === nothing) ? Dict() : Dict(:color => ann_colors[ann])
            MK.scatter!(df_spatial.x[annotation .== ann] .+ offset[1], df_spatial.y[annotation .== ann] .+ offset[2];
                strokewidth=0, markersize=markersize, label=ann, color=color, style_dict..., kwargs...)
        end

        if noise_ann in annotation
            MK.scatter!(df_spatial.x[annotation .== noise_ann] .+ offset[1], df_spatial.y[annotation .== noise_ann] .+ offset[2];
                label=noise_ann, noise_kwargs...)
        end

        if legend
            MK.axislegend(;legend_kwargs...)
        end
    end

    if length(polygons) > 0
        MK.poly!([MK.Point2.(eachrow(p .+ [offset[1] offset[2]])) for p in polygons]; polygon_kwargs...)
    end

    MK.xlims!(MK.current_axis(), xlims .+ offset[1])
    MK.ylims!(MK.current_axis(), ylims .+ offset[2])

    return MK.current_figure()
end

function shuffle_labels(labels::Array{Int})
    new_labs = deepcopy(labels)
    mask = (new_labs .!= 0)
    new_labs[mask] = shuffle(1:maximum(labels))[new_labs[mask]]
    return new_labs
end

function shuffle_colors(colors::Vector)
    uniq_cols = unique(colors);
    col_ord = Dict(Pair.(uniq_cols, shuffle(1:length(uniq_cols))));
    return [uniq_cols[col_ord[tc]] for tc in colors]
end

function plot_expression_vectors(vecs...; gene_names::Vector{String}, min_expr_frac::Float64=0.025, alpha::Float64=0.5, text_offset::Float64=0.005,
        labels::Vector{String}=["y$i" for i in 1:length(vecs)], xrotation=-60, xticks::Bool=false, ylabel::String="Expression")
    y_vals = maximum(hcat(vecs...), dims=2) |> vec
    scale = sum(y_vals)

    p_df = DataFrame(:gene => gene_names)
    plt = @vlplot(x={:gene, title="Gene"}, tooltip=:gene, config={axisX={labels=xticks, ticks=false, labelAngle=xrotation}}, width=700, height=300)

    for (l,cnt) in zip(labels, vecs)
        p_df[!,l] = cnt
        plt += @vlplot({:bar, opacity=alpha}, y={l, title=ylabel}, color={datum=l})
    end
    p_df[!, :_yf] = y_vals
    p_df[!, :_yt] = y_vals .+ text_offset * scale

    return p_df |> (plt + @vlplot(:text, y=:_yt, text=:gene, transform=[{filter="datum._yf > $(min_expr_frac * scale)"}]))
end

### Tracing

function plot_num_of_cells_per_iterarion(tracer::Dict{Symbol, Any})
    if !(:n_components in keys(tracer)) || length(tracer[:n_components]) == 0
        error("No data about #components per iteration was stored")
    end

    n_components_per_iter = hcat(collect.(values.(tracer[:n_components]))...);
    labels = collect(keys(tracer[:n_components][1]));

    n_components_per_iter = n_components_per_iter[sortperm(labels),:]
    labels = sort(labels)

    fig = MK.Figure(resolution=(600, 400));
    axis_args = (xticksvisible=false, yticksvisible=false, xticklabelsize=14, yticklabelsize=14, xlabel="Iteration", ylabel="Num. cells",
        xlabelpadding=0, ylabelpadding=0, font="Helvetica")
    fig[1, 1] = MK.Axis(fig; title="Convergence", axis_args...);

    for i in 1:size(n_components_per_iter, 1)
        MK.lines!(n_components_per_iter[i,:], label="$(labels[i])", color=ColorSchemes.Dark2_8[mod(i - 1, 8) + 1])
    end
    MK.axislegend("Min #molecules"; position=(0.01, 1), nbanks = 2, titlesize=16, labelsize=14)
    MK.xlims!(MK.current_axis(), 0, size(n_components_per_iter, 2))
    MK.ylims!(MK.current_axis(), 0, maximum(n_components_per_iter))

    ax2 = MK.Axis(fig; bbox=MK.BBox(370, 550, 180, 330), axis_args...)
    subplot_start = ceil(Int, 0.75 * size(n_components_per_iter, 2))
    iter_vals = subplot_start:size(n_components_per_iter, 2)
    for i in 1:size(n_components_per_iter, 1)
        MK.lines!(iter_vals, n_components_per_iter[i, iter_vals], color=ColorSchemes.Dark2_8[mod(i - 1, 8) + 1])
    end

    MK.ylims!(MK.current_axis(), 0, maximum(n_components_per_iter[:, iter_vals]))

    return fig
end

### Diagnostics

function plot_clustering_convergence(clust_res::NamedTuple, title::String="")::VegaLite.VLSpec
    plt = DataFrame(:x => 1:length(clust_res.diffs), :diff => 100 .* clust_res.diffs, :change_frac => 100 .* clust_res.change_fracs) |>
        @vlplot(x={:x, title="Iteration"}, title=title, width=300, height=250) +
        @vlplot(:line, y={:diff, title="Change, %"}, color={datum="Max prob. difference"}) +
        @vlplot(:line, y={:change_frac}, color={datum="Molecules changed"})
end

### Colormaps

function map_to_colors(vals::Array{T, 1} where T; lims=nothing, palette::Vector=Colors.sequential_palette(0, 11))
    offset = (lims === nothing) ? minimum(vals) : lims[1]
    scale = (lims === nothing) ? maximum(vals) - offset : (lims[2] - lims[1])

    if lims !== nothing
        vals = min.(max.(vals, lims[1]), lims[2])
    end

    color_ticks = collect(range(0.0, 1.0, length=length(palette))) .* scale .+ offset
    colors = palette[floor.(Int, ((vals .- offset) ./ scale) .* (length(palette) - 1) .+ 1)]

    return Dict(:colors=>colors, :ticks=>color_ticks, :palette=>palette)
end

function distinguishable_colors(vals::T where T <: AbstractVector, args...; kwargs...)
    id_per_type = Dict(c => i for (i,c) in enumerate(unique(vals)));
    colors_uniq = Colors.distinguishable_colors(length(id_per_type), args...; kwargs...);
    colors = colors_uniq[get.(Ref(id_per_type), vals, 0)];

    return Dict(:colors=>colors, :ticks=>unique(vals), :palette=>colors_uniq)
end

plot_colorbar(colors; kwargs...) = plot_colorbar(colors[:ticks], colors[:palette]; kwargs...)

function plot_colorbar(color_ticks, palette; size=(500, 110), xticklabelsize=12,
        xlabelsize=14, xlabelpadding=0, kwargs...)
    fig = MK.Figure(resolution=size)
    fig[1, 1] = MK.Axis(fig; yticksvisible=false, yticklabelsvisible=false, xticksvisible=false, xticklabelsize=12,
        xlabelsize=14, xlabelpadding=0, kwargs...)
    MK.barplot!(color_ticks, ones(length(color_ticks)), color=palette)
    MK.xlims!(MK.current_axis(), val_range(color_ticks))

    return fig
end

### Utils

function Base.show(io::IO, m::MIME"text/html", fig::MK.Scene)
    io64 = Base64.IOBuffer();
    iob64 = Base64.Base64EncodePipe(io64)
    show(iob64, MIME("image/png"), fig);
    close(iob64)
    str = String(take!(io64))
    close(io64)

    print(io, "")
    # show(io, MIME("text/plain"), "")
end


struct ArbitraryMotion
    # e.g. ptpJointSpace!
    func::Function
    inputArg1       # e.g. positions, waitingPeriod, robotOrDepot
    inputArg2       # e.g. nothing, movablePart
    inputArg3       # e.g. nothing, waitingPeriod
end

mutable struct ReferencePath
    names::Vector{AbstractString}     # names are set by the user
    initPos::Vector{Float64}          # initial positions
    position::Vector{Float64}         # current reference position
    velocity::Vector{Float64}         # current reference velocity
    acceleration::Vector{Float64}     # current reference acceleration
    ptpPath::Union{PTP_path,Nothing}  # current PTP_path definition
    nextEventTime::Float64            # time at which the next event is triggered
    v_max::Vector{Float64}            # max velocity is set by the user
    a_max::Vector{Float64}            # max acceleration is set by the user
    refMotion::Vector{ArbitraryMotion} # stores explicit robot functions

    # both are updated in calculateRobotMovement
    instantiatedModel::ModiaLang.SimulationModel     # SimulationModel of model
    # scene::Composition.Scene                       # scene

    function ReferencePath(names::AbstractVector;
                position = zeros(size(names,1)),
                v_max    = ones(size(names,1)),
                a_max    = ones(size(names,2)))
        new(names, position, position, zeros(size(names,1)), zeros(size(names,1)), nothing, 0.001, v_max, a_max, ArbitraryMotion[])
    end
end

ReferencePath(; names, position, v_max, a_max) = ReferencePath(names, position=position, v_max=v_max, a_max=a_max)

getRefPathPosition(referencePath, index) = referencePath.position[index]
getRefPathPosition(; referencePath, index) = referencePath.position[index]

getRefPathInitPosition(referencePath, index) = referencePath.initPos[index]
getRefPathInitPosition(; referencePath, index) = referencePath.initPos[index]

## -------------------- utility functions ------------------------
function setAttributesReferencePath!(ref::ReferencePath, waitingPeriod::Float64)
    ref.nextEventTime = waitingPeriod
    ModiaLang.setNextEvent!(ref.instantiatedModel, ref.nextEventTime)
end

# getComponent(referencePath::ReferencePath, path::String) =
#    getPathComponent(referencePath.simulationState.model.model.assembly, path)

#--------------  Functions that can be executed at an event instant -------
### ------------------ ptpJointSpace! -------------------------------------
function ptpJointSpace!(ref::ReferencePath, positions::Matrix{Float64}, dummy1, dummy2)
    pos1 = size(transpose(ref.position))
    pos2 = size(positions)
    if pos1[2] != pos2[2]
        error("... from ptpJointSpace: Please check out dimensions of input $positions (= $pos2). Its dimensions should be (n, $(pos1[2])), n > 0.")
    end
    ref.ptpPath = PTP_path(ref.names,
    positions = [transpose(ref.position); positions],
    startTime = ModiaLang.getTime(ref.instantiatedModel),
    v_max     = ref.v_max,
    a_max     = ref.a_max)
    setAttributesReferencePath!(ref, pathEndTime(ref.ptpPath))
end

ptpJointSpace!(ref::ReferencePath, positions::Vector{Float64}, dummy1, dummy2) =
    ptpJointSpace!(ref, reshape(positions, :, 1), dummy1, dummy2)

ptpJointSpace!(ref::ReferencePath, positions::Float64, dummy1, dummy2) =
    ptpJointSpace!(ref, reshape([positions], :, 1), dummy1, dummy2)


#### Domain decomposition indexing
export DomainDecomp

import Base
abstract type Domain{N} end
struct GridMean{N} <: Domain{N} end
struct Environment{N} <: Domain{N} end
struct Updraft{N} <: Domain{N} end

"""
    get_param(::Type{Domain})

The number of domains in domain `Domain`
"""
get_param(::Type{GridMean{N}}) where N = N
get_param(::Type{Environment{N}}) where N = N
get_param(::Type{Updraft{N}}) where N = N

"""
    DomainDecomp{GM,EN,UD}

Decomposition of number of domains
 - `gm` number of grid-mean domain
 - `en` number of environment sub-domain
 - `ud` number of updraft sub-domains
"""
struct DomainDecomp{GM,EN,UD}
  function DomainDecomp(;gm::GM=0,en::EN=0,ud::UD=0) where {GM<:Int,EN<:Int,UD<:Int}
    @assert 0<=gm<=1
    @assert 0<=en<=1
    @assert 0<=ud
    return new{GridMean{gm},Environment{en},Updraft{ud}}()
  end
end

Base.sum(   ::DomainDecomp{GM,EN,UD}) where {GM,EN,UD} = get_param(GM)+get_param(EN)+get_param(UD)
get_param(  ::DomainDecomp{GM,EN,UD}) where {GM,EN,UD} = (get_param(GM),get_param(EN),get_param(UD))


#=
R ploting function
=#

function create_plot_ClaDS_ape()
    reval("""

    list.of.packages <- c("ape", "fields", "RColorBrewer")
    new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
    if(length(new.packages)){
		print(paste0("package ", list.of.packages, " should be installed in R."))
		print(paste0("Please install these packages within a R session before continuing."))
		}
	require(fields)
	require(ape)
	require(RColorBrewer)

    plot_ClaDS=function(phylo,rate1,rate2=NULL,same.scale=T,main=NULL,lwd=3,log=T, show_labels=F, minr = Inf, maxr = 0, show_legend = T,...){
        Colors = colorRampPalette(rev(c('darkred',brewer.pal(n = 8, name = "Spectral"),'darkblue')))(100)

     if(nrow(phylo[[1]]) <=1){
        plot(1000,xlim = c(0,1), ylim = c(0,2), axes = F, xlab = "", ylab = "")
        lines(c(0,1), c(1,1), lwd=lwd, col="steelblue2")
      }else{
        if(is.null(rate2)){
          if(log) {
              rate1=log(rate1)
              minr = log(minr)
              maxr = log(maxr)
              minr = min(c(minr, log(0.95)+max(rate1),min(rate1)))
              maxr = max(c(maxr, log(1.05)+min(rate1), max(rate1)))
              }else{
                minr = min(c(minr, 0.99*max(rate1),min(rate1)))
                maxr = max(c(maxr, 1.01*min(rate1), max(rate1)))
              }

            col = round( (rate1 - minr) / (maxr - minr)*99   )+1
            plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,main=main,edge.width =lwd,...)

            if(log){
              m10=floor(minr/log(10))
              M10=ceiling(maxr/log(10))
              if((M10-m10)<4){
                ticks=c(1,2,5)
              }else{
                ticks=1
              }

              ticks=as.vector(sapply(m10:M10,function(k){return(ticks*10^k)}))
              lt=length(ticks[ticks>exp(minr) & ticks<exp(maxr)])
              if(lt<4){
                  ticks=c(round(exp(minr),digit=2),max(0,-1*m10+(lt<2)),ticks,round(exp(maxr),digit = 2))
                  }
              if(show_legend) image.plot(z = c(minr,maxr),col = Colors, horizontal=T,legend.only = T,axis.args=list( at=log(ticks), labels=ticks))
            }else{
              if(show_legend) image.plot(z = as.matrix(rate1),col = Colors, horizontal=T,legend.only = T)
            }

        }else{
          if(log){
            rate1=log(rate1)
            rate2=log(rate2)
          }
          if(same.scale){
            min=min(min(rate1),min(rate2))
            max=max(max(rate1),max(rate2))
            par(mfrow=c(1,2))
            col = round(( (rate1 - min) / (max-min))*99   )+1
            plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
            col = round(( (rate2 - min) / (max-min))*99   )+1
            plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
            par(mfrow=c(1,1))
            if(log){
              m10=floor(min/log(10))
              M10=ceiling(max/log(10))
              if((M10-m10)<4){
                ticks=c(1,2,5)
              }else{
                ticks=1
              }
              ticks=as.vector(sapply(m10:M10,function(k){return(ticks*10^k)}))
              lt=length(ticks[ticks>exp(min) & ticks<exp(max)])
              if(lt<4){

                  ticks=c(round(exp(min),max(0,-1*m10+(lt<2))),ticks,round(exp(max),max(0,-1*M10+1+(lt<2))))
                  }
              # ticks=seq(min,max,length.out = 5)
              if(show_legend) image.plot(z = c(min,max),col = Colors, horizontal=T,legend.only = T,axis.args=list( at=log(ticks), labels=ticks))
            }else{
              if(show_legend) image.plot(z = c(min,max),col = Colors, horizontal=T,legend.only = T)
            }
          }else{
            par(mfrow=c(1,2))
            if(isTRUE(all.equal(rep(rate1[1],length(rate1)),rate1))){
              col=rep(1,length(rate1))
              plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
              if(log){

                if(show_legend) image.plot(z = c(exp(rate1[1]),2*exp(rate1[1])),col = Colors, horizontal=T,legend.only = T)
              }else{
                if(show_legend) image.plot(z = c(rate1[1],2*rate1[1]),col = Colors, horizontal=T,legend.only = T)
              }
            }else{
              col = round(( (rate1 - min(rate1)) / (max(rate1)-min(rate1)))*99   )+1
              plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
              if(log){
                min=min(rate1)
                max=max(rate1)
                m10=floor(min/log(10))
                M10=ceiling(max/log(10))
                if((M10-m10)<4){
                  ticks=c(1,2,5)
                }else{
                  ticks=1
                }
                ticks=as.vector(sapply(m10:M10,function(k){return(ticks*10^k)}))
                lt=length(ticks[ticks>exp(min) & ticks<exp(max)])
                if(lt<4){ticks=c(round(exp(min),max(0,-1*m10+(lt<2))),ticks,round(exp(max),max(0,-1*M10+1+(lt<2))))}
                if(show_legend) image.plot(z = c(min,max),col = Colors, horizontal=T,legend.only = T,axis.args=list( at=log(ticks), labels=ticks))
              }else{
                if(show_legend) image.plot(z = as.matrix(rate1),col = Colors, horizontal=T,legend.only = T)
              }
            }
            if(isTRUE(all.equal(rep(rate2[1],length(rate2)),rate2))){
              col=rep(1,length(rate2))
              plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
              if(log){
                if(show_legend) image.plot(z = c(exp(rate2[1]),2*exp(rate2[1])),col = Colors, horizontal=T,legend.only = T)
              }else{
                if(show_legend) image.plot(z = c(rate2[1],2*rate2[1]),col = Colors, horizontal=T,legend.only = T)
              }
            }else{
              col = round(( (rate2 - min(rate2)) / (max(rate2)-min(rate2)))*99   )+1
              plot(phylo, edge.color = Colors[col], show.tip.label = show_labels,edge.width =lwd,...)
              if(log){
                minr=min(rate2)
                maxr=max(rate2)
                m10=floor(minr/log(10))
                M10=ceiling(maxr/log(10))
                if((M10-m10)<4){
                  ticks=c(1,2,5)
                }else{
                  ticks=1
                }
                ticks=as.vector(sapply(m10:M10,function(k){return(ticks*10^k)}))
                lt=length(ticks[ticks>exp(min) & ticks<exp(max)])
                if(lt<4){ticks=c(round(exp(min),max(0,-1*m10+(lt<2))),ticks,round(exp(max),max(0,-1*M10+1+(lt<2))))}
                if(show_legend) image.plot(z = c(min,max),col = Colors, horizontal=T,legend.only = T,axis.args=list( at=log(ticks), labels=ticks))
              }else{
                if(show_legend) image.plot(z = as.matrix(rate2),col = Colors, horizontal=T,legend.only = T)
              }
            }
          }
          par(mfrow=c(1,1))
        }
      }
      if(!show_legend) return(list(Colors = Colors, col = col, minr = minr, maxr = maxr))
    }
    """)
end

#=
Plot a tree with branch rates from Julia
3 methods
=#

# specifying only the tree, .attribute[id] used as rates
"""
    plot_ClaDS(tree::Tree ; ln=true, show_labels = false, options="")

Plots a tree with branches colored according to their rates

# Arguments
- `tree::Tree`: the phylogeny to be plotted.

# Keyword arguments
- `ln::Bool`: Should rates be plotted on a log scale? Default to true.
- `show_labels::Bool`: Should tip labels be printed? Default to false.
- `options::String`: Additonal options for the ploting function.

	plot_ClaDS(tree::Tree, rates::Array{Number,1} ; ln=true, show_labels = false, options="")

# Additional arguments
- `rates::Array{Number,1} `: the speciation rates.
"""
function plot_ClaDS(tree::Tree ; id = 1, ln=true, show_labels = false, options="")
    plot_tree = Tree(tree.offsprings, 0., tree.attributes, tree.n_nodes)

    opt = options
    if length(options) > 0
        if options[1] != ","[1]
            opt = ", $options"
        end
    end

    edges, branch_lengths, rates, tip_labels = Tree2ape(plot_tree, id = 1)
    ntip = (tree.n_nodes + 1)/2
    @rput edges
    @rput branch_lengths
    @rput rates
    @rput ntip
    @rput ln
    @rput show_labels
    @rput tip_labels

    create_plot_ClaDS_ape()
    reval("""
        tree = list(edge = edges, Nnode = ntip - 1, edge.lengths = branch_lengths, tip.labels = tip_labels)
        class(tree) = "phylo"
        colors = plot_ClaDS(tree, rates, log=ln, show_labels = show_labels $opt);
    """)
end

# specifying the tree and a vector of rates

function plot_ClaDS(tree::Tree, rates::Array{Float64,1} ; id = 1, ln=true, round = false, options = "", show_labels=false)
    plot_tree = Tree(tree.offsprings, 0., tree.attributes, tree.n_nodes)

    opt = options
    if length(options) > 0
        if options[1] != ","[1]
            opt = ", $options"
        end
    end

    edges, branch_lengths, tree_rates, tip_labels = Tree2ape(plot_tree, id = 1)
    ntip = (tree.n_nodes + 1)/2
    @rput edges
    @rput branch_lengths
    @rput rates
    @rput ntip
    @rput ln
    @rput show_labels
    @rput tip_labels

    create_plot_ClaDS_ape()
    reval("""
        tree = list(edge = edges, Nnode = ntip - 1, edge.lengths = branch_lengths, tip.labels = tip_labels)
        class(tree) = "phylo"
        plot_ClaDS(tree, rates, log=ln, show_labels = show_labels $opt)
    """)
end

# diversity through time
function plot_DTT(co::CladsOutput; nyl = 1000, n_ltt = 100, alpha_col = 0.05, burn = 0.25,
	thin = 1, axes_cex = 1., lwd = 1., lab_cex = 1., force_ylim = [2.]) where {T <: Number}

	extant_tree = co.tree
	chains = co.chains
    npar = extant_tree.n_nodes + 3 + (extant_tree.n_nodes+1)/2
    ltt_times = co.time_points
    maps = co.DTT_mean

    new_tree = Tree(extant_tree.offsprings, 0., extant_tree.attributes)
    live_ltt = LTT(new_tree, ltt_times)[2]

    @rput chains
    @rput maps
    @rput npar
    @rput ltt_times
    @rput n_ltt
    @rput alpha_col
    @rput burn
    @rput thin
    @rput live_ltt
	@rput axes_cex
	@rput lab_cex
    @rput nyl
	@rput force_ylim
    reval("""
        require(coda)
        id_ltt = (npar+3):length(chains[[1]])
        n_row = length(chains[[1]][[1]])
        ini = floor(burn * n_row)
        if(ini == 0) ini = 1
        it = seq(ini, n_row, thin)
        npar2 = length(chains[[1]])
        map_chains = mcmc.list(lapply(1:3, function(i){
            mcmc(sapply(id_ltt, function(j){
                if(j<4 || (j>npar )){#}& j<(npar +3))) {
                    chains[[i]][[j]][it]
                }else{
                    log(chains[[i]][[j]][it])
                }
        }))}))
        unlist_chains = (sapply(1:length(id_ltt), function(k){
            c(map_chains[[1]][,k], map_chains[[2]][,k], map_chains[[3]][,k])
            }))
		y_max = max(unlist_chains)
        #maps_log=sapply(1:npar2, function(i){D=density(unlist_chains[,i]);
        #    return(D[[1]][which.max(D[[2]])])})

        means=sapply(1:length(id_ltt), function(i){if(T){log(mean(unlist_chains[,i]))}else{mean(unlist_chains[,i])}})
    """)

    reval("""
        library(scales)
        plot(100000, axes = F, xlim = range(ltt_times), ylim = range(log(c(2,force_ylim,y_max))), xlab = "time",
        ylab = "nb lineages", cex.lab = lab_cex)
        y_lab = c(2,5,10,50,100,200,500,1000,2000,5000,10000,20000,50000)
        y_lab = y_lab[y_lab<=y_max]
        if(nyl < length(y_lab)){
            y_lab = y_lab[unique(floor(seq(1,length(y_lab), length.out = nyl)))]
            }
        lines(ltt_times, log(live_ltt), col = "black", lwd = 6, lty = 1)

        id_plot = unique(floor(seq(ini, n_row, length.out = n_ltt)))#unique(floor(seq(2,length(chains[[1]][[1]]), length.out = n_ltt)))

        Ys = c()
        for (i in 1:3){
            for (j in id_plot){
                y = sapply(id_ltt, function(k){log(chains[[i]][[k]][j])})
                Ys = rbind(Ys,y)
                lines(ltt_times, y, col = alpha("deepskyblue2", alpha = alpha_col), lwd = 2)
                }
            }
		quant = sapply(1:ncol(Ys), function(i){quantile(log(unlist_chains[,i]), probs = c(0.05,0.95))})

        lines(ltt_times, quant[1,], col = "deepskyblue3", lwd = 2)
        lines(ltt_times, quant[2,], col = "deepskyblue3", lwd = 2)
        lines(ltt_times, log(maps[id_ltt]), col = "cadetblue1", lwd = 5, lty = 1)
        lines(ltt_times, log(maps[id_ltt]), col = "cadetblue4", lwd = 3, lty = 6)

        lines(ltt_times, means[1:length(id_ltt)], col = "darkseagreen1", lwd = 5, lty = 1)
        lines(ltt_times, means[1:length(id_ltt)], col = "darkseagreen4", lwd = 3, lty = 6)

        axis(1, cex.axis = axes_cex, lwd = 2, lwd.ticks = 2)
        axis(2, at = log(y_lab), lab = y_lab, cex.axis = axes_cex, lwd = 2, lwd.ticks = 2)

    """)
end

function plot_RTT(co::CladsOutput ;nplot = 50, miny = -1, maxy = 1, alpha_col = 0.05, burn = 0., axes_cex = 1., lwd = 1., lab_cex = 1., lay = 5,
	force_ylim = Array{Float64,1}(undef,0))

	times = co.time_points[2:end]
	RTT_map = co.RTT_map
    N = length(co.rtt_chains[1])
    t = times#[1:length(tr)]
	ini_plot = Int64(2+ floor(burn * N))
    plot_id = Array{Int64,1}(floor.(range(ini_plot, length=nplot, stop=N)))
    mean_mr = zeros(length(times))
    a = Array{Float64,1}(undef,0)
    map_mr = [deepcopy(a) for i in 1:length(times)]
	range_RTT = [minimum(minimum(minimum(co.rtt_chains)[2:end]));maximum(maximum(maximum(co.rtt_chains)[2:end]))]
    @rput t
    @rput miny
    @rput maxy
    @rput alpha_col
	@rput lab_cex
	@rput axes_cex
	@rput lay
	@rput RTT_map
	@rput range_RTT
	@rput force_ylim

    reval("""
		ylim = log(range(c(range_RTT, force_ylim)))#+c(miny,maxy)
        library(scales)
        plot(100000, axes = F, xlim = range(t), ylim = ylim,
        xlab = "time", ylab = "mean rate", cex.lab = lab_cex)
    """)
    n = 0
    colors = ["deepskyblue2" ; "deepskyblue2" ; "deepskyblue2"]

    #colors = ["deepskyblue2" ; "orange2" ; "orangered3"]
    for i in 1:3
        color = colors[i]
        @rput color
        for k in ini_plot:N
            y = co.rtt_chains[i][k]
            for j in 1:length(y)
                push!(map_mr[j],log(y[j]))
            end
			if k ∈ plot_id
            	@rput y
            	reval("""lines(t, log(y), col = alpha(color, alpha = alpha_col), lwd = 2)""")
			end
            mean_mr .+= y
            n += 1
        end
    end

    mean_mr ./= n
    @rput mean_mr
    @rput map_mr
    reval("""
        maps = log(RTT_map) #sapply(map_mr, function(x){D=density(x); return(D[[1]][which.max(D[[2]])])})
		#quant = sapply(map_mr, function(x){quantile(x, probs = c(0.05,0.95))})
		quant = sapply(map_mr, function(x){quantile(x, probs = c(0.05,0.95))})

    """)
    reval("""
		lwd = 2
        lines(t, quant[1,], col = "deepskyblue3", lwd = 2)
        lines(t, quant[2,], col = "deepskyblue3", lwd = 2)
        lines(t, maps, col = "darkseagreen1", lwd = 5, lty = 1)
        lines(t, maps, col = "darkseagreen4", lwd = 3, lty = 6)
        axis(1, cex.axis = axes_cex, lwd = lwd, lwd.ticks = lwd)
		labels_a2 = unique(signif(exp(seq(ylim[1], ylim[2], length.out = lay)), digits = 1))
		axis(2, at = log(labels_a2), labels = labels_a2, cex.axis = axes_cex, lwd = lwd, lwd.ticks = lwd)
    """)

end

function plot_density(co::CladsOutput, id_par::Int64; burn = 0.25)
	n_par = length(co.chains[1])
	n_tips = n_extant_tips(co.tree)
	n_edges = (n_tips - 1)*2
	nit = length(co.chains[1][1])
	init = Int64(ceil(burn * nit))
	color = "orange"

	if id_par >= 5 + n_edges + n_tips
		id_par += 2
	end

	if id_par <= n_par
		if id_par == 1				#σ
			name = "sigma"
			map = co.σ_map
		elseif id_par == 2			#α
			name = "alpha"
			map = co.α_map
		elseif id_par == 3			#ε
			name = "epsilon"
			map = co.ε_map
		elseif id_par == 4			#λ_0
			name = "lambda_0"
			map = co.λ0_map
		elseif id_par < 5 + n_edges #λ_i
			i = id_par - 4
			name = "lambda_$i"
			map = co.λi_map[i]
		elseif id_par < 5 + n_edges + n_tips						#λ_tip_i
			i = id_par - 4 - n_edges
			tip_name = tip_labels(co.tree)[i]
			name = "lambda_tip $i"
			if length(tip_name)>0
				name = "lambda_tip $tip_name"
			end
			map = co.λtip_map[i]
		else
			color = "#3498db"
			i = id_par - (6 + n_edges + n_tips)
			name = "number of lineages (time $(co.time_points[i]))"
			map = co.DTT_mean[i]
		end

		chain = [co.chains[i][id_par][j] for i in 1:length(co.chains) for j in init:length(co.chains[1][1])]
	else

		i = id_par - n_par
		name = "mean rate (time $(co.time_points[i]))"
		map = co.RTT_map[i]

		chain = [co.rtt_chains[i][j][id_par - n_par] for i in 1:length(co.rtt_chains) for j in init:length(co.rtt_chains[1])]
	end
	@rput chain
	@rput name
	@rput map
	@rput id_par
	@rput n_par
	@rput color
	reval("""
		if (id_par < 5 ){
			d = density(chain)
			plot(d\$x, d\$y, type = 'l', lwd = 3, xlab = name, ylab = "density")
		}else{
			d = density(log(chain))
			plot(exp(d\$x), d\$y, type = 'l', lwd = 3, xlab = name, ylab = "density", log = 'x')
		}
		abline(v = map, lwd = 3, col = color)

		color_q = adjustcolor("coral3", alpha.f = 0.1)
		quant = quantile(chain, c(0.025,0.975))
		polygon(quant[c(1,2,2,1)], c(-1,-1,100000,100000), col = color_q, border = NA)

	""")
end

function plot_chain(co::CladsOutput, id_par::Int64; burn = 0.25)
	n_par = length(co.chains[1])
	n_tips = n_extant_tips(co.tree)
	n_edges = (n_tips - 1)*2
	nit = length(co.chains[1][1])
	init = Int64(ceil(burn * nit))
	if id_par >= 5 + n_edges + n_tips
		id_par += 2
	end

	if id_par <= n_par
		if id_par == 1				#σ
			name = "sigma"
			map = co.σ_map
		elseif id_par == 2			#α
			name = "alpha"
			map = co.α_map
		elseif id_par == 3			#ε
			name = "epsilon"
			map = co.ε_map
		elseif id_par == 4			#λ_0
			name = "lambda_0"
			map = co.λ0_map
		elseif id_par < 5 + n_edges #λ_i
			i = id_par - 4
			name = "lambda_$i"
			map = co.λi_map[i]
		elseif id_par < 5 + n_edges + n_tips						#λ_tip_i
			i = id_par - 4 - n_edges
			tip_name = tip_labels(co.tree)[i]
			name = "lambda_tip $i"
			if length(tip_name)>0
				name = "lambda_tip $tip_name"
			end
			map = co.λtip_map[i]
		else
			i = id_par - (6 + n_edges + n_tips)
			name = "number of lineages (time $(co.time_points[i]))"
			map = co.DTT_mean[i]
		end

		chain = [co.chains[i][id_par][init:length(co.chains[1][1])] for i in 1:length(co.chains)]
	else

		i = id_par - n_par
		name = "mean rate (time $(co.time_points[i]))"
		map = co.RTT_map[i]

		chain = Array{Array{Float64,1},1}(undef,0)
		for i in 1:length(co.rtt_chains)
			push!(chain, [co.rtt_chains[i][j][id_par - n_par] for j in init:length(co.rtt_chains[1])])
		end
	end
	@rput chain
	@rput name
	@rput map
	@rput id_par
	@rput n_par

	reval("""
		require("coda")
		traceplot(mcmc.list(lapply(chain, mcmc)), ylab = name)

	""")
end

function convert_id(co::CladsOutput, id::String)
	n_par = length(co.chains[1])
	n_tips = n_extant_tips(co.tree)
	n_edges = (n_tips - 1)*2

	if occursin("σ",id) | occursin("sig",id)
		par_id = 1
	elseif occursin("α",id) | occursin("al",id)
		par_id = 2
	elseif occursin("ε",id) | occursin("ϵ",id) | occursin("eps",id) | occursin("turn",id)
		par_id = 3
	elseif occursin("λ0",id) | occursin("lambda0",id) | occursin("lambda_0",id) | occursin("lamb_0",id)
		par_id = 4
	elseif occursin(r"λ*tip",id) | occursin(r"lamb*tip",id)
		number = 0
		tip_id=split(id,"tip_")[end]
		re = r"^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$";
		if occursin(re, tip_id)
			number=parse(Int, tip_id)
		else
			dist = Inf
			tip_labs = tip_labels(co.tree)
			for itl in 1:length(tip_labs)
				d = Levenshtein()(tip_id, tip_labs[itl])
				if d < dist
					dist = d
					number = itl
					if d == 0
						break
					end
				end
			end
		end
		par_id = 4 + n_edges + number
	elseif occursin("λ",id) | occursin("lamb",id)
		number_as_string=split(id,"_")[end]
		number=parse(Int, number_as_string)
		par_id = 4 + number
	elseif occursin("lin",id) | occursin("div",id)
		number_as_string=split(id,"_")[end]
		number=parse(Int, number_as_string)
		par_id = 4 + n_edges + n_tips + number
	elseif occursin("rat",id)
		number_as_string=split(id,"_")[end]
		number=parse(Int, number_as_string)
		par_id = n_par - 2 + number
	else
		par_id = 1
	end

	return par_id
end

function plot_density(co::CladsOutput, id_par::String; burn = 0.25)
	id = convert_id(co, id_par)
	plot_density(co, id, burn = burn)
end

function plot_chain(co::CladsOutput, id_par::String; burn = 0.25)
	id = convert_id(co, id_par)
	plot_chain(co, id, burn = burn)
end


using Pkg: @pkg_str
# For reproducability only dependency this has is Luxor,
# and it was created with Luxor v1.6.0
pkg"add Luxor@v1.6"

using Luxor
using Random

const bridge_len = 50

function chain(jiggle=0)
    shaky_rotate(θ) = rotate(θ + jiggle*(rand()-0.5))

    ### 1
    shaky_rotate(0)
    sethue(Luxor.julia_red)
    link()
    m1 = getmatrix()

    ### 2
    sethue(Luxor.julia_green)
    translate(-50, 130);
    shaky_rotate(π/3);
    link()
    m2 = getmatrix()

    setmatrix(m1)
    sethue(Luxor.julia_red)
    overlap(-1.3π)
    setmatrix(m2)

    ### 3
    shaky_rotate(-π/3);
    translate(-120,80);
    sethue(Luxor.julia_purple)
    link()

    setmatrix(m2)
    setcolor(Luxor.julia_green)
    overlap(-1.5π)
end

function link()
    sector(50, 90, π, 0, :fill)
    sector(Point(0, bridge_len), 50, 90, 0, -π, :fill)

    rect(50,-3,40, bridge_len+6, :fill)
    rect(-50-40,-3,40, bridge_len+6, :fill)

    sethue("black")
    move(Point(-50, bridge_len))
    arc(Point(0,0), 50, π, 0, :stoke)
    arc(Point(0, bridge_len), 50, 0, -π, :stroke)

    move(Point(-90, bridge_len))
    arc(Point(0,0), 90, π, 0, :stoke)
    arc(Point(0, bridge_len), 90, 0, -π, :stroke)
    strokepath()
end

function overlap(ang_end)
    sector(Point(0, bridge_len), 50, 90, -0., ang_end, :fill)
    sethue("black")
    arc(Point(0, bridge_len), 50, 0, ang_end, :stoke)
    move(Point(90, bridge_len))
    arc(Point(0, bridge_len), 90, 0, ang_end, :stoke)

    strokepath()
end

# Actually draw it

Random.seed!(16)
Drawing(450,450, "logo.svg")
origin()
translate(50, -130);
chain(0.5)
finish()
preview()


type NetworkAddress
  time::Uint32
  services::Uint64
  IP::Array{Uint8}
  port::Uint16

  # Assume addresses come in the form IP:Port
  function NetworkAddress(address::String; services = SERVICES_NODE_NETWORK)

    # TODO: more rigorous IP address checking
    pieces = split(address, ":")
    ip = pieces[1:end-1]
    port = pieces[end]

    # If we have an ipv6 address (e.g., xxxx:...{6}...:xxxx)
    if length(ip) > 1
      ip = reduce(vcat, [Crypto.int2oct(uint16(parseint(x, 16))) for x in ip])
    else
      ip = split(ip, ".")
      ip = reduce(vcat, [Crypto.int2oct(uint8(x)) for x in ip])

      pad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff]
      ip = [pad, ip]
    end

    new(uint64(time()), uint64(services), ip, uint16(port))
  end
end

function convert(::Type{Array{Uint8}}, x::NetworkAddress)
  result = Array(Uint8, 0)

  append!(result, reverse(Crypto.int2oct(x.services)))
  append!(result, x.IP)
  append!(result, Crypto.int2oct(x.port))

  return result
end

type Version
  version::Uint32
  services::Uint64
  timestamp::Uint64
  addr_recv::NetworkAddress
  addr_from::NetworkAddress
  nonce::Array{Uint8}
  user_agent::Array{Uint8}
  start_height::Uint32
  relay::Bool

  # Take address in the form
  function Version(addr_recv::String, addr_from::String;
                   version      = BITCOIN_PROTOCOL_VERSION,
                   services     = SERVICES_NODE_NETWORK,
                   timestamp    = int(time()),
                   nonce        = Crypto.random(64),
                   user_agent   = "/Coin.jl:0.0.1/",
                   start_height = 0,
                   relay        = true)

    addr_recv = NetworkAddress(addr_recv, services = services)
    addr_from = NetworkAddress(addr_from, services = services)

    new(uint32(version),
        uint64(services),
        uint64(timestamp),
        addr_recv,
        addr_from,
        nonce,
        user_agent,
        uint32(start_height),
        relay)
  end
end