text:
# _Constant--DecisionRef
Base.:(+)(lhs::_Constant, rhs::DecisionRef) = DAE(_VAE(convert(Float64, lhs)), _DAE(0.0, rhs => +one(Float64)))
Base.:(-)(lhs::_Constant, rhs::DecisionRef) = DAE(_VAE(convert(Float64, lhs)), _DAE(0.0, rhs => -one(Float64)))
Base.:(*)(lhs::_Constant, rhs::DecisionRef) = DAE(_VAE(0.0), _DAE(0.0, rhs => lhs))

# _Constant--DecisionAffExpr{C}
Base.:(+)(lhs::_Constant, rhs::DecisionAffExpr{C}) where C =
    DecisionAffExpr(convert(C,lhs) + rhs.variables,
                    copy(rhs.decisions))
Base.:(-)(lhs::_Constant, rhs::DecisionAffExpr{C}) where C =
    DecisionAffExpr(convert(C,lhs) - rhs.variables,
                    -rhs.decisions)
Base.:(*)(lhs::_Constant, rhs::DecisionAffExpr{C}) where C =
    DecisionAffExpr(convert(C,lhs) * rhs.variables,
                    convert(C,lhs) * rhs.decisions)

# _Constant--DecisionQuadExpr{C}
Base.:(+)(lhs::_Constant, rhs::DecisionQuadExpr{C}) where C =
    DecisionQuadExpr(convert(C,lhs) + rhs.variables,
                     copy(rhs.decisions),
                     copy(rhs.cross_terms))
Base.:(-)(lhs::_Constant, rhs::DecisionQuadExpr{C}) where C =
    DecisionQuadExpr(convert(C,lhs) - rhs.variables,
                     copy(rhs.decisions),
                     copy(rhs.cross_terms))
Base.:(*)(lhs::_Constant, rhs::DecisionQuadExpr{C}) where C =
    DecisionQuadExpr(convert(C,lhs) * rhs.variables,
                     convert(C,lhs) * rhs.decisions,
                     _map_cross_terms(c -> convert(C,lhs) * c,
                                      copy(rhs.cross_terms)))

#=
    VariableRef
=#

# VariableRef--DecisionRef
Base.:(+)(lhs::VariableRef, rhs::DecisionRef) = DAE(_VAE(0.0, lhs => 1.0), _DAE(0.0, rhs =>  1.0))
Base.:(-)(lhs::VariableRef, rhs::DecisionRef) = DAE(_VAE(0.0, lhs => 1.0), _DAE(0.0, rhs => -1.0))
function Base.:(*)(lhs::VariableRef, rhs::DecisionRef)
    result = zero(DQE)
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# VariableRef--_DecisionAffExpr{C}
Base.:(+)(lhs::VariableRef, rhs::_DecisionAffExpr{C}) where C = DecisionAffExpr{C}(_VariableAffExpr{C}(zero(C), lhs => 1.), copy(rhs))
Base.:(-)(lhs::VariableRef, rhs::_DecisionAffExpr{C}) where C = DecisionAffExpr{C}(_VariableAffExpr{C}(zero(C), lhs => 1.), -rhs)
function Base.:(*)(lhs::VariableRef, rhs::_DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# VariableRef--_DecisionQuadExpr{C}
function Base.:(+)(lhs::VariableRef, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, rhs)
    return result
end
function Base.:(-)(lhs::VariableRef, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# VariableRef--DecisionAffExpr{C}
Base.:(+)(lhs::VariableRef, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(lhs + rhs.variables, copy(rhs.decisions))
Base.:(-)(lhs::VariableRef, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(lhs - rhs.variables, -rhs.decisions)
function Base.:(*)(lhs::VariableRef, rhs::DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end
Base.:/(lhs::VariableRef, rhs::DecisionAffExpr) = error("Cannot divide a variable by an affine expression")

# VariableRef--DecisionQuadExpr{C}
function Base.:(+)(lhs::VariableRef, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::VariableRef, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end
Base.:(*)(lhs::VariableRef, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by a variable")

#=
    DecisionRef
=#

# DecisionRef
Base.:(-)(lhs::DecisionRef) = DAE(_VAE(0.0), _DAE(0.0, lhs => -1.0))

# DecisionRef--_Constant
Base.:(+)(lhs::DecisionRef, rhs::_Constant) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionRef, rhs::_Constant) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionRef, rhs::_Constant) = (*)(rhs, lhs)
Base.:(/)(lhs::DecisionRef, rhs::_Constant) = (*)(1.0 / rhs, lhs)

# DecisionRef--VariableRef
Base.:(+)(lhs::DecisionRef, rhs::VariableRef) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionRef, rhs::VariableRef) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionRef, rhs::VariableRef) = (*)(rhs, lhs)

# DecisionRef--DecisionRef
Base.:(+)(lhs::DecisionRef, rhs::DecisionRef) = DAE(_VAE(0.0), _DAE(0.0, lhs => 1.0, rhs => +1.0))
Base.:(-)(lhs::DecisionRef, rhs::DecisionRef) = DAE(_VAE(0.0), _DAE(0.0, lhs => 1.0, rhs => -1.0))
function Base.:(*)(lhs::DecisionRef, rhs::DecisionRef)
    result = zero(DQE)
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# DecisionRef--_VariableAffExpr{C}
Base.:(+)(lhs::DecisionRef, rhs::_VariableAffExpr{C}) where C = DecisionAffExpr{C}(rhs, _DecisionAffExpr{C}(zero(C), lhs => 1.))
Base.:(-)(lhs::DecisionRef, rhs::_VariableAffExpr{C}) where C = DecisionAffExpr{C}(-rhs, _DecisionAffExpr{C}(zero(C), lhs => 1.))
function Base.:(*)(lhs::DecisionRef, rhs::_VariableAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# DecisionRef--_VariableQuadExpr{C}
function Base.:(+)(lhs::DecisionRef, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, rhs)
    return result
end
function Base.:(-)(lhs::DecisionRef, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# DecisionRef--_DecisionAffExpr{C}
Base.:(+)(lhs::DecisionRef, rhs::_DecisionAffExpr{C}) where C = (+)(_DecisionAffExpr{C}(zero(C), lhs => 1.0),  rhs)
Base.:(-)(lhs::DecisionRef, rhs::_DecisionAffExpr{C}) where C = (+)(_DecisionAffExpr{C}(zero(C), lhs => 1.0), -rhs)
function Base.:(*)(lhs::DecisionRef, rhs::_DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# DecisionRef--_DecisionQuadExpr{C}
function Base.:(+)(lhs::DecisionRef, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expressoin(result, rhs)
    return result
end
function Base.:(-)(lhs::DecisionRef, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expressoin(result, lhs)
    JuMP.add_to_expressoin(result, -1., rhs)
    return result
end

# DecisionRef--DecisionAffExpr{C}
Base.:(+)(lhs::DecisionRef, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(copy(rhs.variables), lhs+rhs.decisions)
Base.:(-)(lhs::DecisionRef, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(-rhs.variables, lhs-rhs.decisions)
function Base.:(*)(lhs::DecisionRef, rhs::DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end
Base.:/(lhs::DecisionRef, rhs::DecisionAffExpr) = error("Cannot divide a decision by an affine expression")

# DecisionRef--DecisionQuadExpr{C}
function Base.:(+)(lhs::DecisionRef, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::DecisionRef, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end
Base.:(*)(lhs::DecisionRef, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by a variable")

#=
    _VariableAffExpr{C}
=#

# _VariableAffExpr--DecisionRef
Base.:(+)(lhs::_VariableAffExpr, rhs::DecisionRef) = (+)(rhs, lhs)
Base.:(-)(lhs::_VariableAffExpr, rhs::DecisionRef) = (+)(-rhs, lhs)
Base.:(*)(lhs::_VariableAffExpr, rhs::DecisionRef) = (*)(rhs, lhs)

# _VariableAffExpr{C}--_DecisionAffExpr{C}
Base.:(+)(lhs::_VariableAffExpr{C}, rhs::_DecisionAffExpr{C}) where C = DecisionAffExpr{C}(copy(lhs), copy(rhs))
Base.:(-)(lhs::_VariableAffExpr{C}, rhs::_DecisionAffExpr{C}) where C = DecisionAffExpr{C}(copy(lhs), -rhs)
function Base.:(*)(lhs::_VariableAffExpr{C}, rhs::_DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# _VariableAffExpr{C}--_DecisionQuadExpr{C}
function Base.:(+)(lhs::_VariableAffExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expressoin(result, rhs)
    return result
end
function Base.:(-)(lhs::_VariableAffExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# VariableRef{C}--DecisionAffExpr{C}
Base.:(+)(lhs::_VariableAffExpr{C}, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(lhs+rhs.variables, copy(rhs.decisions))
Base.:(-)(lhs::_VariableAffExpr{C}, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(lhs-rhs.variables, -rhs.decisions)
function Base.:(*)(lhs::_VariableAffExpr{C}, rhs::DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# _VariableAffExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::_VariableAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::_VariableAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end
Base.:(*)(lhs::_VariableAffExpr, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by an affine expression")

#=
    _DecisionAffExpr{C}
=#

# _DecisionAffExpr--VariableRef/DecisionRef
Base.:(+)(lhs::_DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(rhs, lhs)
Base.:(-)(lhs::_DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(-rhs, lhs)
Base.:(*)(lhs::_DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (*)(rhs, lhs)

# _DecisionAffExpr--_VariableAffExpr
Base.:(+)(lhs::_DecisionAffExpr, rhs::_VariableAffExpr) = (+)(rhs, lhs)
Base.:(-)(lhs::_DecisionAffExpr, rhs::_VariableAffExpr) = (+)(-rhs, lhs)
Base.:(*)(lhs::_DecisionAffExpr, rhs::_VariableAffExpr) = (*)(rhs, lhs)

# _DecisionAffExpr{C}--_VariableQuadExpr{C}
function Base.:(+)(lhs::_DecisionAffExpr{C}, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, rhs)
    return result
end
function Base.:(-)(lhs::_DecisionAffExpr{C}, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# _DecisionAffExpr{C}--DecisionAffExpr{C}
Base.:(+)(lhs::_DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(copy(rhs.variables), lhs+rhs.decisions)
Base.:(-)(lhs::_DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(-rhs.variables, lhs-rhs.decisions)
function Base.:(*)(lhs::_DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# _DecisionAffExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::_DecisionAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::_DecisionAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end
Base.:(*)(lhs::_DecisionAffExpr, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by an affine expression")

#=
    DecisionAffExpr{C}
=#

Base.:(-)(lhs::DecisionAffExpr{C}) where C = DecisionAffExpr{C}(-lhs.variables, -lhs.decisions)

# DecisionAffExpr--_Constant
Base.:(+)(lhs::DecisionAffExpr, rhs::_Constant) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionAffExpr, rhs::_Constant) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionAffExpr, rhs::_Constant) = (*)(rhs, lhs)
Base.:/(lhs::DecisionAffExpr, rhs::_Constant) = map_coefficients(c -> c/rhs, lhs)
function Base.:^(lhs::DecisionAffExpr{C}, rhs::Integer) where C
    if rhs == 2
        return lhs*lhs
    elseif rhs == 1
        return convert(DecisionQuadExpr{C}, lhs)
    elseif rhs == 0
        return one(GenericQuadExpr{C})
    else
        error("Only exponents of 0, 1, or 2 are currently supported.")
    end
end
Base.:^(lhs::DecisionAffExpr, rhs::_Constant) = error("Only exponents of 0, 1, or 2 are currently supported.")

# DecisionAffExpr--VariableRef/DecisionRef
Base.:(+)(lhs::DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionAffExpr, rhs::Union{VariableRef, DecisionRef}) = (*)(rhs, lhs)
Base.:/(lhs::DecisionAffExpr, rhs::VariableRef) = error("Cannot divide affine expression by a variable")
Base.:/(lhs::DecisionAffExpr, rhs::DecisionRef) = error("Cannot divide affine expression by a decision")

# DecisionAffExpr--_VariableAffExpr/_DecisionAffExpr/
Base.:(+)(lhs::DecisionAffExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr}) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionAffExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr}) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionAffExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr}) = (*)(rhs, lhs)

# DecisionAffExpr{C}--DecisionAffExpr{C}
Base.:(+)(lhs::DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C =
    DecisionAffExpr{C}(lhs.variables+rhs.variables,
                       lhs.decisions+rhs.decisions)
Base.:(-)(lhs::DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C =
    DecisionAffExpr{C}(lhs.variables-rhs.variables,
                       lhs.decisions-rhs.decisions)
function Base.:(*)(lhs::DecisionAffExpr{C}, rhs::DecisionAffExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs, rhs)
    return result
end

# DecisionAffExpr{C}--_VariableQuadExpr{C}
function Base.:(+)(lhs::DecisionAffExpr{C}, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, rhs)
    return result
end
function Base.:(-)(lhs::DecisionAffExpr{C}, rhs::_VariableQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# DecisionAffExpr{C}--_DecisionQuadExpr{C}
function Base.:(+)(lhs::DecisionAffExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, rhs)
    return result
end
function Base.:(-)(lhs::DecisionAffExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

Base.:(*)(lhs::DecisionAffExpr, rhs::Union{_VariableQuadExpr, _DecisionQuadExpr}) = error("Cannot multiply a quadratic expression by an affine expression")

# DecisionAffExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::DecisionAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::DecisionAffExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end
Base.:(*)(lhs::DecisionAffExpr, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by an affine expression")

#=
    _VariableQuadExpr{C}
=#

# _VariableQuadExpr--DecisionRef
Base.:(+)(lhs::_VariableQuadExpr, rhs::DecisionRef) = (+)(rhs, lhs)
Base.:(-)(lhs::_VariableQuadExpr, rhs::DecisionRef) = (+)(-rhs, lhs)

# _VariableQuadExpr--_DecisionAffExpr/DecisionAffExpr
Base.:(+)(lhs::_VariableQuadExpr, rhs::Union{_DecisionAffExpr, DecisionAffExpr}) = (+)(rhs, lhs)
Base.:(-)(lhs::_VariableQuadExpr, rhs::Union{_DecisionAffExpr, DecisionAffExpr}) = (+)(-rhs, lhs)
Base.:(*)(lhs::_VariableQuadExpr, rhs::DecisionAffExpr) = error("Cannot multiply a quadratic expression by an affine expression")

# _VariableQuadExpr{C}--_DecisionQuadExpr{C}
function Base.:(+)(lhs::_VariableQuadExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expressoin(result, rhs)
    return result
end
function Base.:(-)(lhs::_VariableQuadExpr{C}, rhs::_DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, lhs)
    JuMP.add_to_expression!(result, -1., rhs)
    return result
end

# _VariableQuadExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::_VariableQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::_VariableQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end

#=
    _DecisionQuadExpr{C}
=#

# _DecisionQuadExpr--VariableRef/DecisionRef
Base.:(+)(lhs::_DecisionQuadExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(rhs, lhs)
Base.:(-)(lhs::_DecisionQuadExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(-rhs, lhs)

# _DecisionQuadExpr--_VariableAffExpr
Base.:(+)(lhs::_DecisionQuadExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr, DecisionAffExpr}) = (+)(rhs, lhs)
Base.:(-)(lhs::_DecisionQuadExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr, DecisionAffExpr}) = (+)(-rhs, lhs)
Base.:(*)(lhs::_DecisionQuadExpr, rhs::DecisionAffExpr) = error("Cannot multiply a quadratic expression by an affine expression")

# _DecisionQuadExpr{C}--_VariableQuadExpr{C}
Base.:(+)(lhs::_DecisionQuadExpr, rhs::_VariableQuadExpr) = (+)(rhs, lhs)
Base.:(-)(lhs::_DecisionQuadExpr, rhs::_VariableQuadExpr) = (+)(-rhs, lhs)

# _DecisionQuadExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::_DecisionQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(rhs)
    JuMP.add_to_expression!(result, lhs)
    return result
end
function Base.:(-)(lhs::_DecisionQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs)
    return result
end

#=
    DecisionQuadExpr{C}
=#

function Base.:(-)(lhs::DecisionQuadExpr{C}) where C
    result = zero(DecisionQuadExpr{C})
    JuMP.add_to_expression!(result, -1., lhs.variables)
    JuMP.add_to_expression!(result, -1., lhs.decisions)
    for (vars, coef) in lhs.cross_terms
        JuMP._add_or_set!(result.cross_terms, vars, -coef)
    end
    return result
end

# DecisionQuadExpr--_Constant
Base.:(+)(lhs::DecisionQuadExpr, rhs::_Constant) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionQuadExpr, rhs::_Constant) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionQuadExpr, rhs::_Constant) = (*)(rhs, lhs)
Base.:(/)(lhs::DecisionQuadExpr, rhs::_Constant) = (*)(inv(rhs), lhs)

# DecisionQuadExpr--VariableRef/DecisionRef
Base.:(+)(lhs::DecisionQuadExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionQuadExpr, rhs::Union{VariableRef, DecisionRef}) = (+)(-rhs, lhs)
Base.:(/)(lhs::DecisionQuadExpr, rhs::Union{VariableRef, DecisionRef}) = error("Cannot divide a quadratic expression by a variable")

# DecisionQuadExpr--_VariableAffExpr/_DecisionAffExpr/
Base.:(+)(lhs::DecisionQuadExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr, DecisionAffExpr}) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionQuadExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr, DecisionAffExpr}) = (+)(-rhs, lhs)
Base.:(*)(lhs::DecisionQuadExpr, rhs::Union{_VariableAffExpr, _DecisionAffExpr, DecisionAffExpr}) = error("Cannot multiply a quadratic expression by an affine expression")
Base.:(/)(lhs::DecisionQuadExpr, rhs::Union{GenericAffExpr, DecisionAffExpr}) = error("Cannot divide a quadratic expression by an affine expression")

# DecisionQuadExpr{C}--_VariableQuadExpr/_DecisionQuadExpr/
Base.:(+)(lhs::DecisionQuadExpr, rhs::Union{_VariableQuadExpr, _DecisionQuadExpr}) = (+)(rhs, lhs)
Base.:(-)(lhs::DecisionQuadExpr, rhs::Union{_VariableQuadExpr, _DecisionQuadExpr}) = (+)(-rhs, lhs)

# DecisionQuadExpr{C}--DecisionQuadExpr{C}
function Base.:(+)(lhs::DecisionQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = copy(lhs)
    JuMP.add_to_expression!(result, rhs.variables)
    JuMP.add_to_expression!(result, rhs.decisions)
    add_cross_terms!(result_terms, terms) = begin
        for (vars, coef) in terms
            JuMP._add_or_set!(result_terms, vars, coef)
        end
    end
    add_cross_terms!(result.cross_terms, rhs.cross_terms)
    return result
end
function Base.:(-)(lhs::DecisionQuadExpr{C}, rhs::DecisionQuadExpr{C}) where C
    result = -rhs
    JuMP.add_to_expression!(result, lhs.variables)
    JuMP.add_to_expression!(result, lhs.decisions)
    add_cross_terms!(result_terms, terms) = begin
        for (vars, coef) in terms
            JuMP._add_or_set!(result_terms, vars, -coef)
        end
    end
    add_cross_terms!(result.cross_terms, lhs.cross_terms)
    return result
end
Base.:(*)(lhs::DecisionQuadExpr, rhs::DecisionQuadExpr) = error("Cannot multiply a quadratic expression by a quadratic expression")

Base.promote_rule(V::Type{DecisionRef}, R::Type{<:Real}) = DecisionAffExpr{T}
Base.promote_rule(V::Type{DecisionRef}, ::Type{<:DecisionAffExpr{T}}) where {T} = DecisionAffExpr{T}
Base.promote_rule(V::Type{DecisionRef}, ::Type{<:DecisionQuadExpr{T}}) where {T} = DecisionQuadExpr{T}
Base.promote_rule(::Type{<:DecisionAffExpr{S}}, R::Type{<:Real}) where S = DecisionAffExpr{promote_type(S, R)}
Base.promote_rule(::Type{<:DecisionAffExpr{S}}, ::Type{<:DecisionQuadExpr{T}}) where {S, T} = DecisionQuadExpr{promote_type(S, T)}
Base.promote_rule(::Type{<:DecisionQuadExpr{S}}, R::Type{<:Real}) where S = DecisionQuadExpr{promote_type(S, R)}


text:
module UMAP

using Arpack
using Distances
using LinearAlgebra
using LsqFit: curve_fit
using SimilaritySearch
using SparseArrays

include("utils.jl")
include("precomputedknns.jl")
include("layouts.jl")
include("embeddings.jl")
include("umap_.jl")

export umap, UMAP_, transform

end # module

ext: jl


text:
using Test
using Random

@testset "ParametrisedConvexApproximators" begin
    seed = 2021
    Random.seed!(seed)
    core()
    # auxiliary()
end

function core()
    include("basic.jl")
end

function auxiliary()
    include("solver.jl")
end


text:
"""
Package: Forecast

    acf(x::{AbstractVector,DataFrame},
        type = "cor",
        lag = Integer(ceil(10*log10(length(x)))),
        alpha = (0.95,0.99))

Compute the auto-correlation or auto-covariance for an univariate series.

The results are normalized to preserve homoscedasticity. The distribution used to normalize the data is an approximation of a Fisher Transformation via a Normal Distribution. There is a plot recipe for the returned object, if the type is `cor` the plot will also show confidence intervals for the given alpha values.

# Arguments
- `x`: Vector or uni-dimensional DataFrame of data.
- `type`: Valid values are "cor" for correlation (default) and "cov" for convariance.
- `lag`: Maximum number of lags.
- `alpha`: A tuple with two thresholds (t1,t2) with t1 <= t2 to plot confidence intervals. The default values are 0.95 and 0.99.

# Returns
A CCF object.

# Examples
```julia-repl
julia> x = rand(100);
res = acf(x; type="cor");
plot(res)
```
"""
function acf(df::DataFrame;
             type::String = "cor",
             lag::Integer = Integer(ceil(10*log10(length(x)))),
             alpha::Tuple{Real,Real} = (0.95,0.99))

    x = Array(df[:,eltype.(eachcol(df)) .<: Real])
    ccf(x, x; type, lag, alpha)
end

function acf(x::AbstractVector{<:Real};
             type::String = "cor",
             lag::Integer = Integer(ceil(10*log10(length(x)))),
             alpha::Tuple{Real,Real} = (0.95,0.99))

    ccf(x,x; type, lag, alpha)
end


text:
#=======================================================

Tests for the SkipList and SkipListSet types

=======================================================#

using Random, SkipLists, Test
using Base.Iterators: repeated

@testset "SkipList tests" begin
    Random.seed!(0)

    test_construct_list(SkipList)
    test_insert_into_list(SkipList)
    test_iterate_over_list(SkipList)
    test_list_membership(SkipList)
    test_add_duplicate_elements_to_list(SkipList)
    test_delete_from_list(SkipList)
end

@testset "SkiplistSet tests" begin
    Random.seed!(0)

    test_insert_into_skip_list_set(SkipListSet)
    test_delete_from_skip_list_set(SkipListSet)
    test_mixed_insert_delete_from_skip_list_set(SkipListSet)
end


text:
module Advection

export adv_flux_x, adv_flux_y, adv_flux_z
export nut_advection!
export nut_diffusion!

using KernelAbstractions

using PlanktonIndividuals.Grids
using PlanktonIndividuals.Architectures: device, array_type, Architecture
using PlanktonIndividuals.Fields

include("operaters.jl")
include("tracer_diffusion.jl")
include("DST3FL.jl")
include("multi_dim_adv.jl")

end


text:
# by lazarusA # HIDE
# using GLMakie # HIDE
using CairoMakie, Random
CairoMakie.activate!() # HIDE
let
    #### see also https://discourse.julialang.org/t/beautiful-makie-gallery/62523/31
    Random.seed!(123)
    n = 200
    x, y, color = randn(n)/2, randn(n), randn(n)
    fig = Figure(resolution = (700, 700))
    ax1 = Axis(fig,xgridstyle=:dash,ygridstyle=:dash,xtickalign=1, ytickalign=1)
    ax2 = Axis(fig,xgridstyle=:dash,ygridstyle=:dash,xtickalign=1, ytickalign=1)
    ax3 = Axis(fig,xgridstyle=:dash,ygridstyle=:dash,xtickalign=1, ytickalign=1)
    hist!(ax1, x, color = (:orange, 0.5), strokewidth = 0.5)
    density!(ax3,y,direction = :y,color = (:dodgerblue, 0.5),strokewidth = 0.5)
    scatter!(ax2, x, y, color = color, colormap=:plasma, markersize = 10,
        marker = :circle, strokewidth = 0)
    xlims!(ax1,-4,4)
    limits!(ax2,-4,4,-4,4)
    ylims!(ax3,-4,4)
    hideydecorations!(ax3, ticks = false, grid = false)
    hidexdecorations!(ax1, ticks = false, grid = false)
    fig[1,1] = ax2
    fig[1,2] = ax3
    fig[0,1] = ax1
    fig
    colsize!(fig.layout, 1, Relative(2/3))
    rowsize!(fig.layout, 1, Relative(1/3))
    colgap!(fig.layout, 10)
    rowgap!(fig.layout, 10)
    fig
    save(joinpath(@__DIR__, "output", "ScatterHistDensitySides.png"), fig, px_per_unit = 2) # HIDE
end
using Pkg # HIDE
Pkg.status(["CairoMakie","Random"]) # HIDE

max_stars_repo_name: MisterBiggs/BeautifulMakie

max_issues_repo_name: MisterBiggs/BeautifulMakie

max_forks_repo_name: MisterBiggs/BeautifulMakie


text:
# This file is a part of BAT.jl, licensed under the MIT License (MIT).

include("distribution_functions.jl")
include("standard_uniform.jl")
include("standard_normal.jl")
include("multimodal_cauchy.jl")
include("funnel.jl")
include("gaussian_shell.jl")


text:
# Draw fault tolerance plot
# Author: Thomas Minier
using Gadfly
using Compose
using RDatasets
using CalliPlots

custom_theme = Theme(
    major_label_font_size=18px,
    minor_label_font_size=15px,
    major_label_font="Computer Modern",
    major_label_color=colorant"#000000",
    minor_label_color=colorant"#000000"
)

Gadfly.push_theme(custom_theme)

serversNames = Dict(
    "http://curiosiphi:4000/watDiv_100" => "S1",
    "http://curiosiphi:4001/watDiv_100" => "S2",
    "http://curiosiphi:4002/watDiv_100" => "S3",
    "http://curiosiphi:4003/watDiv_100" => "S4",
    "http://curiosiphi:4004/watDiv_100" => "S5",
    "http://curiosiphi:3000/watDiv_100" => "S1",
    "http://curiosiphi:3001/watDiv_100" => "S2",
    "http://curiosiphi:3002/watDiv_100" => "S3",
    "http://curiosiphi:3003/watDiv_100" => "S4",
    "http://curiosiphi:3004/watDiv_100" => "S5",
)

function process(filename, approach)
    file = readtable(filename)
    for x in eachrow(file)
        x[:timestamp] = floor(x[:timestamp]/1000)
    end
    df = by(file, :timestamp, nrow)
    df[:approach] = approach
    return df
end

function process2(filename, approach)
    file = readtable(filename)
    #  rename servers
    for row in eachrow(file)
        row[:target] = serversNames[row[:target]]
        row[:timestamp] = floor(row[:timestamp]/1000)
    end
    df = by(file, :timestamp, x -> mean(x[:realTime]))
    df[:approach] = approach
    return df
end

load_2servers = process2("scripts/fault/fault_tolerance_3servers.csv", "normal")

annot_1 = Guide.annotation(compose(context(), text(2.0, 390.0, "Failure of S1"), fontsize(15pt)))
annot_2 = Guide.annotation(compose(context(), text(16.0, 475.0, "Failure of S3"), fontsize(15pt)))
circle_1 = Guide.annotation(compose(context(), circle(5.0, load_2servers[6, :x1], 0.4), fill("red")))
circle_2 = Guide.annotation(compose(context(), circle(20.0, load_2servers[21, :x1], 0.4), fill("red")))

p = plot(load_2servers, x=:timestamp, y=:x1, Geom.line, Geom.point, annot_1, annot_2, circle_1, circle_2,
Guide.xticks(ticks=[0,5,10,15,20,25,30,35,40,45,50]), Guide.xlabel("Elapsed time (seconds)"),
Guide.ylabel("avg. HTTP response time (ms)", orientation=:vertical))
draw(PDF("scripts/fault/fault_tolerance.pdf", 5inch, 3.5inch), p)
draw(PNG("scripts/fault/fault_tolerance.png", 7inch, 3inch), p)

max_stars_repo_name: Callidon/ulysses-tpf

max_issues_repo_name: Callidon/ulysses-tpf

max_forks_repo_name: Callidon/ulysses-tpf


text:
## Narmax regressors
"""
    NarmaxRegressors{T<:Basis}(basis::Vector{T}, yterms, uterms[, vterms])

Describe regressors to be used on a NARMAX estimation problem.
"""
struct NarmaxRegressors{T<:Basis}
    basis::Vector{T}
    noise_components_starts::Int
    yterms::Vector{Int}
    n_yterms::Int
    uterms::Vector{Int}
    n_uterms::Int
    vterms::Vector{Int}
    n_vterms::Int
    noise_terms_starts::Int
end

function NarmaxRegressors{T<:Basis}(basis::Vector{T},
                                    yterms::Vector{Int},
                                    uterms::Vector{Int},
                                    vterms::Vector{Int}=Vector{Int}(0))
    # Get dimensions
    n_yterms = length(yterms)
    n_uterms = length(uterms)
    n_vterms = length(vterms)
    # Order terms acording to the presence of noise terms
    m1, m2 = detach_terms(basis, [falses(n_uterms+n_yterms); trues(n_vterms)])
    basis = [m2; m1]
    noise_components_starts = length(m2) + 1
    noise_terms_start = n_uterms+n_yterms+1
    NarmaxRegressors(basis, noise_components_starts,
                     yterms, n_yterms, uterms, n_uterms,
                     vterms, n_vterms, noise_terms_start)
end

## Generate lagged terms matrix
function lag_matrix(reg::NarmaxRegressors,
                    y::Vector{Float64},
                    u::Vector{Float64},
                    v::Vector{Float64})
    # Construct signals
    signals = isempty(v) ? [y u] : [y u v]
    # Construct time lags
    if isempty(reg.vterms)
        time_lags = [reg.yterms, reg.uterms]
    else
        time_lags = [reg.yterms, reg.uterms, reg.vterms]
    end
    # Generate lag matrix
    lag(signals, time_lags)
end

## Generate regressors matrix
function regressor_matrix(reg::NarmaxRegressors,
                          y::Vector{Float64},
                          u::Vector{Float64},
                          v::Vector{Float64})
    # Construct signals
    M = lag_matrix(reg, y, u, v)
    evaluate(M, reg.basis)
end

## Generate all terms
"""
    generate_all(::Type{NarmaxRegressors}, ::Type{Monomials}, ny, nu, nv, order)

Generate a set of regressors containing all monomials:
```math
\\left(y[k-q_i] \\right)^{l_i} \\left(u[k-t_i] \\right)^{r_i}
\\left(e[k-w_i] \\right)^{s_i}
```
for which: ``1\\le q_i \\le n_y``; ``1 \\le t_i \\le n_u``;
 ``1 \\le w_i \\le n_e``; and, ``l_i + r_i + s_i = \\text{order}[i]``.
"""
function generate_all{T<:Basis}(::Type{NarmaxRegressors}, ::Type{T},
                                ny::Int, nu::Int, nv::Int, args...; kwargs...)
    basis = generate_all(T, ny+nu+nv, args...; kwargs...)
    return NarmaxRegressors(basis, collect(1:ny), collect(1:nu), collect(1:nv))
end

## Print regressors
function name_regressors(reg::NarmaxRegressors)
    ynames = [string("y[k-", k, "]") for k in reg.yterms]
    unames = [string("u[k-", k, "]") for k in reg.uterms]
    vnames = [string("v[k-", k, "]") for k in reg.vterms]
    names = [ynames; unames; vnames]
    n = length(reg.basis)
    str = Vector{String}(n)
    for i = 1:n
        str[i] = function_display(reg.basis[i], names)
    end
    return str
end

function Base.show(io::IO, reg::NarmaxRegressors)
    str = name_regressors(reg)
    n = length(str)
    for i = 1:n
        print(io, string(str[i], "\n"))
    end
end


text:
@testset "BCF" begin
    record = BCF.Record()
    @test !isfilled(record)
    @test occursin(r"^VariantCallFormat.BCF.Record: <not filled>", repr(record))
    @test_throws ArgumentError BCF.chrom(record)

    record = BCF.Record()
    record.sharedlen = 0x1c
    record.indivlen = 0x00
    # generated from bcftools 1.3.1 (htslib 1.3.1)
    record.data = parsehex("00 00 00 00 ff ff ff ff 01 00 00 00 01 00 80 7f 00 00 01 00 00 00 00 00 07 17 2e 00")
    record.filled = 1:lastindex(record.data)
    @test BCF.chrom(record) == 1
    record = BCF.Record(record)
    @test isa(record, BCF.Record)
    record = BCF.Record(record, chrom=4)
    @test BCF.chrom(record) == 4
    record = BCF.Record(record, pos=1234)
    @test BCF.pos(record) == 1234
    record = BCF.Record(record, qual=12.3)
    @test BCF.qual(record) == 12.3f0
    record = BCF.Record(record, id="rs1234")
    @test BCF.id(record) == "rs1234"
    record = BCF.Record(record, ref="AT")
    @test BCF.ref(record) == "AT"
    record = BCF.Record(record, alt=["ATT", "ACT"])
    @test BCF.alt(record) == ["ATT", "ACT"]
    record = BCF.Record(record, filter = [2, 3])
    @test BCF.filter(record) == [2, 3]
    record = BCF.Record(record, info=Dict(1 => Int8[42]))
    @test BCF.info(record) == [(1, 42)]
    @test BCF.info(record, simplify=false) == [(1, [42])]
    @test BCF.info(record, 1) == 42
    @test BCF.info(record, 1, simplify=false) == [42]

    bcfdir = joinpath(fmtdir, "BCF")
    reader = BCF.Reader(open(joinpath(bcfdir, "example.bcf")))
    let header = header(reader)
        @test length(findall(header, "fileformat")) == 1
        @test findall(header, "fileformat")[1] == VCF.MetaInfo("##fileformat=VCFv4.2")
        @test length(findall(header, "FORMAT")) == 4
    end
    record = BCF.Record()
    @test read!(reader, record) === record
    @test BCF.chrom(record) == 1
    @test BCF.pos(record) == 14370
    @test BCF.rlen(record) == 1
    @test BCF.id(record) == "rs6054257"
    @test BCF.ref(record) == "G"
    @test BCF.alt(record) == ["A"]
    @test BCF.qual(record) == 29.0
    @test BCF.filter(record) == [1]
    @test BCF.info(record) == [(1,3),(2,14),(3,0.5),(5,nothing),(6,nothing)]
    @test BCF.info(record, simplify = false) == [(1,[3]),(2,[14]),(3,[0.5]),(5,[]),(6,[])]
    @test BCF.genotype(record) == [(9,[[2,3],[4,3],[4,4]]),(10,[[48],[48],[43]]),(2,[[1],[8],[5]]),(11,[[51,51],[51,51],[-128,-128]])]
    @test BCF.genotype(record, 1) == [(9, [2,3]), (10, [48]), (2, [1]), (11, [51,51])]
    @test BCF.genotype(record, 1, 9) == [2,3]
    @test BCF.genotype(record, 1, 10) == [48]
    @test BCF.genotype(record, 2, 9) == [4,3]
    @test BCF.genotype(record, :, 9) == [[2,3],[4,3],[4,4]]
    @test occursin(r"^VariantCallFormat.BCF.Record:\n.*", repr(record))
    close(reader)

    # round-trip test
    for specimen in YAML.load_file(joinpath(bcfdir, "index.yml"))
        filepath = joinpath(bcfdir, specimen["filename"])
        records = BCF.Record[]
        reader = open(BCF.Reader, filepath)
        output = IOBuffer()
        writer = BCF.Writer(output, header(reader))
        for record in reader
            write(writer, record)
            push!(records, record)
        end
        # HACK: take the data buffer before closing the writer
        data = output.data
        close(reader)
        close(writer)

        records2 = BCF.Record[]
        for record in BCF.Reader(IOBuffer(data))
            push!(records2, record)
        end
        @test records == records2
    end
end


text:
struct DelayedError
    msg::S where S <: AbstractString
    dict::T where T <: AbstractDict
end

function process_delayed_error_list(list)::Nothing
    if isempty(list)
        @debug("There were no delayed errors.")
    else
        for x in list
            @error("Delayed error from earlier: $(x.msg)", x.dict...)
        end
        error("There were one or more delayed errors.")
    end
    return nothing
end

function process_delayed_error_list()::Nothing
    global delayed_error_list
    process_delayed_error_list(delayed_error_list)
    return nothing
end

function delayederror(msg::S; kwargs...)::Nothing where S <: AbstractString
    x = DelayedError(msg, Dict(kwargs...))
    global delayed_error_list
    push!(delayed_error_list, x,)
    @error("Delaying this error for later: $(x.msg)", x.dict...)
    return nothing
end

function delayederror(msg::Vararg{Any,N}; kwargs...)::Nothing where {N}
    delayederror(Main.Base.string(msg); kwargs...)
    return nothing
end

function delayedexit(n)::Nothing
    process_delayed_error_list()
    exit(n)
end

delayedexit() = delayedexit(0)


text:
using Statistics
using NCDatasets

using ArgParse
using JSON

include("LinearRegression.jl")
include("constants.jl")

function parse_commandline()

    s = ArgParseSettings()
    @add_arg_table s begin

        "--data-file"
            help = "Atm data file. New variable will be appended."
            arg_type = String
            required = true

        "--domain-file"
            help = "Domain file."
            arg_type = String
            required = true

        "--output-file"
            help = "Output file."
            arg_type = String
            required = true

        "--beg-year"
            help = "Year of begin."
            arg_type = Int64
            required = true

        "--end-year"
            help = "Year of end."
            arg_type = Int64
            required = true

    end

    return parse_args(ARGS, s)
end

parsed = parse_commandline()
print(json(parsed, 4))

output_file = parsed["output-file"]

Dataset(parsed["data-file"], "r") do ds

    beg_t = (parsed["beg-year"] - 1) * 12 + 1
    end_t = (parsed["end-year"] - 1) * 12 + 12

    rng = (:,:,beg_t:end_t)

    global hi    = replace(ds["hi"][rng...], missing=>NaN)
    global aice  = replace(ds["aice"][rng...], missing=>NaN) / 100.0

    global (Nx, Ny, Nt) = size(hi)

    if mod(Nt, 12) != 0
        ErrorException("Time record is not multiple of 12") |> throw
    end

    global nyears = Int64(Nt / 12)
end

Dataset(parsed["domain-file"], "r") do ds
    global mask = replace(ds["mask"], missing=>NaN)
    global area = replace(ds["area"], missing=>NaN)
end

mask_idx = (mask .== 0.0)
total_area = sum(area)

total_ice_volume = zeros(Float64, Nt)
total_ice_area   = zeros(Float64, Nt)

hi_trend         = zeros(Float64, Nx, Ny)
aice_trend       = zeros(Float64, Nx, Ny)

hi_trend_MA         = zeros(Float64, Nx, Ny, 12)
aice_trend_MA       = zeros(Float64, Nx, Ny, 12)

hi_MA            = zeros(Float64, Nx, Ny, 12)
aice_MA          = zeros(Float64, Nx, Ny, 12)

for var in [total_ice_volume, total_ice_area, hi_trend, aice_trend, hi_trend_MA, aice_trend_MA, hi_MA, aice_MA]
    var .= NaN
end

println("Total area: ", total_area)

# total ice volume and area
for i = 1:Nt
    tmp = area .* view(hi, :, :, i)
    total_ice_volume[i] = sum(tmp[mask_idx])
end
total_ice_volume .*= 4.0 * π * Re^2.0 / total_area

for i = 1:Nt
    tmp = area .* view(aice, :, :, i)
    total_ice_area[i] = sum(tmp[mask_idx])
end
total_ice_area /= 4.0 * π

# trend

x  = collect(Float64, 1:Nt)
xx = collect(Float64, 1:nyears)

for (var,  trend,      trend_MA,      MA      ) in [
    (hi,   hi_trend,   hi_trend_MA,   hi_MA   ),
    (aice, aice_trend, aice_trend_MA, aice_MA )
]
    for i=1:Nx, j=1:Ny

        d = view(var, i, j, :)

        if mask[i, j] != 0
            continue
        end

        trend[i, j] = LinearRegression(x,d)[2] * 12   # yearly trend

        d_month = reshape(d, 12, :)
        MA[i, j, :] = mean( d_month, dims=2 )[:, 1]

        for m = 1:12
            trend_MA[i, j, m] = LinearRegression(xx, d_month[m, :])[2]
        end

    end
end

Dataset(output_file, "c") do ds

    defDim(ds, "months", 12)
    defDim(ds, "time", Inf)
    defDim(ds, "Nx", Nx)
    defDim(ds, "Ny", Ny)

    for (varname, vardata, vardim, attrib) in [
        ("total_ice_volume", total_ice_volume, ("time", ),             Dict()),
        ("total_ice_area",   total_ice_area,   ("time", ),             Dict()),
        ("hi_trend",         hi_trend,         ("Nx", "Ny"),           Dict()),
        ("hi_trend_MA",      hi_trend_MA,      ("Nx", "Ny", "months"), Dict()),
        ("hi_MA",            hi_MA,            ("Nx", "Ny", "months"), Dict()),
        ("aice_trend",       aice_trend,       ("Nx", "Ny"),           Dict()),
        ("aice_trend_MA",    aice_trend_MA,    ("Nx", "Ny", "months"), Dict()),
        ("aice_MA",          aice_MA,          ("Nx", "Ny", "months"), Dict()),
    ]

        var = defVar(ds, varname, Float64, vardim)
        var.attrib["_FillValue"] = 1e20

        for (k, v) in attrib
            var.attrib[k] = v
        end

        rng = []
        for i in 1:length(vardim)
            push!(rng, 1:size(vardata)[i])
        end
        var[rng...] = vardata

    end
end

max_stars_repo_name: meteorologytoday/CESM-diagnostic

max_issues_repo_name: meteorologytoday/CESM-diagnostic

max_forks_repo_name: meteorologytoday/CESM-diagnostic


text:
using DataFrames, CSV, DelimitedFiles, Plots, MSM, Statistics

x = readdlm("data/btc.csv", ',')
x = vec(x)

n = length(x) - 240
res = DataFrame()
c = 0
for j in 2001:240:n
    m = MSMmodel(k = 6)
    fitglobal!(m, x[j-2000:j-1])
    s = predict(m, 500, 240)
    v = std(x[j:j+240])
    push!(res, (realised = v, msmmean = mean(s), msmsd = std(s)))
    println("$c done")
    c += 1
end

CSV.write("data/btcvol.csv", res)

plt = plot(res.realised, label = "realised", lw = 2)
plot!(plt, res.msmmean .- res.msmsd, label = "msm -1sd", lw = 2)
plot!(plt, res.msmmean .+ res.msmsd, label = "msm +1sd", lw = 2)

plt1 = plot(res.msmmean .- res.msmsd, fillrange = res.msmmean .+ res.msmsd, fillalpha = 0.35, color = :orange, label = "msm +/- 1σ")
plot!(plt1, res.msmmean, label = "msm mean", lw = 2, color = :blue)
plot!(plt1, res.realised, label = "realised", lw = 2, color = :red)
savefig(plt1, "btcvoljulia.png")

begin
    rv = readdlm("data/hvol.csv", ',')
    mv = readdlm("data/msmvol_mean.csv", ',')
    sd = readdlm("data/msmvol_sd.csv", ',')
    plt2 = plot(mv .- sd, fillrange = mv .+ sd, fillalpha = 0.35, color = :orange, label = "msm +/- 1σ")
    plot!(plt2, mv, label = "msm mean", lw = 2, color = :blue)
    plot!(plt2, rv, label = "realised", lw = 2, color = :red)
    savefig(plt2, "btcvolpy.png")
end


text:
# Inspiration for this code came from EvolvingGraphs:
# https://github.com/weijianzhang/EvolvingGraphs.jl
#
# The EvolvingGraphs.jl package is licensed under the MIT "Expat" License:
# Copyright (c) 2015: Weijian Zhang.
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.#
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
    katz_centrality(g, α=0.3)
Calculate the [Katz centrality](https://en.wikipedia.org/wiki/Katz_centrality)
of the graph `g` optionally parameterized by `α`. Return a vector representing
the centrality calculated for each node in `g`.
"""
function katz_centrality(g::AbstractGraph, α::Real=0.3)
    nvg = nv(g)
    v = ones(Float64, nvg)
    spI = speye(Float64, nvg)
    A = adjacency_matrix(g, :in, Bool)
    v = (spI - α*A)\v
    v /=  norm(v)
    return v
end

max_stars_repo_name: AsileBcd/JohnsonCycles

max_issues_repo_name: AsileBcd/JohnsonCycles

max_forks_repo_name: AsileBcd/JohnsonCycles


text:
#Heatmap plots
function PlotHeatmap(x::AbstractArray{T}, rows, cols, scale_min=0.0, scale_max=1.0;
                     label_id::String="", label_fmt="%.1f",
                     bounds_min=ImPlotPoint(0.0, 0.0),
                     bounds_max=ImPlotPoint(1.0, 1.0)) where {T<:ImPlotData}
    return PlotHeatmap(label_id, x, rows, cols, scale_min, scale_max, label_fmt, bounds_min,
                       bounds_max)
end


text:
module Dual

include("gap.jl")

import GraphIdx.Tree: ChildrenIndex, dfs_walk, root_node

function dual(
    x::Array{F,N},
    y::Array{F,N},
    post_order::Vector{I},
    parent::Vector{I},
    alpha_root::F=F(0.0),
) where {F,N,I}
    dual!(Vector{F}(undef, length(post_order)),
          x,
          y,
          post_order,
          parent,
          alpha_root)
end

"""
    dual!(α, [, x, y], post_order, parent)

Compute the tree dual and store it in `α`.
"""
function dual!(
    alpha::Vector{F},
    x::Array{F,N},
    y::Array{F,N},
    post_order::Vector{I},
    parent::Vector{I},
    alpha_root::F=F(0.0),
) where {F,N,I}
    alpha .= vec(x) .- vec(y)
    dual!(alpha, post_order, parent, alpha_root)
end

function dual!(
    alpha::Vector{F},
    post_order::Vector{I},
    parent::Vector{I},
    alpha_root::F=F(0.0),
) where {F <: Real,I <: Integer}
    for c in @view post_order[1:end - 1]
        let v = parent[c]
            alpha[v] += alpha[c]
        end
    end
    if alpha_root != F(0.0)
        let root = post_order[end], acc = eps() * length(parent)
            @assert abs(alpha[root]) <= acc "$(abs(alpha[root])) < = $acc"
            alpha[root] = alpha_root
        end
    end
    return alpha
end

dual(z::Vector{F}, parent::Vector{I},
     cidx::ChildrenIndex, alpha_root::F=F(0.0)) where {F,I} =
    dual!(copy(z), parent, cidx, alpha_root)

function dual!(
    alpha::Vector{F},
    parent::Vector{I},
    cidx::ChildrenIndex,
    alpha_root::F=F(0.0),
) where {F,I}
    for (i, v) in enumerate(parent)
        alpha[i] = i > v ? -alpha[i] : +alpha[i]
    end
    dfs_walk(cidx) do v::Int
        if v >= 0
            let p = parent[v]
                if p != v
                    alpha[p] += alpha[v]
                end
            end
        end
    end
    if alpha_root != F(0.0)
        let root = root_node(cidx), acc = eps() * length(parent)
            @assert abs(alpha[root]) <= acc "$(abs(alpha[root])) < = $acc"
            alpha[root] = alpha_root
        end
    end
    return alpha
end

"""
    primal_from_dual(y, α, graph)

If ``D`` is the oriented incidence matrix of `graph`,
return ``y + D'*α``.
"""
primal_from_dual(y::Array{F,N}, alpha::Vector{F}, graph::G) where {F,N,G} =
    primal_from_dual!(copy(y), alpha, graph)

"""

    primal_from_dual!(y, α, graph)

Similar to [`primal_from_dual`](@ref) but store the result in y.
"""
function primal_from_dual!(
    x::Array{F,N},
    alpha::Vector{F},
    graph::G,
)::Array{F,N} where {F <: Real,N,G}
    enumerate_edges(graph) do ei::Int, u::Int, v::Int, lam::Float64
        x[u] += alpha[ei]
        x[v] -= alpha[ei]
    end
    return x
end

end

max_stars_repo_name: EQt/treelas

max_issues_repo_name: EQt/treelas

max_forks_repo_name: EQt/treelas


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule SuiteSparse_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("SuiteSparse")
JLLWrappers.@generate_main_file("SuiteSparse", UUID("bea87d4a-7f5b-5778-9afe-8cc45184846c"))
end  # module SuiteSparse_jll


text:
#==============================================================================#
# SMS.jl
#
# This file is generated from:
# https://github.com/aws/aws-sdk-js/blob/master/apis/sms-2016-10-24.normal.json
#==============================================================================#

__precompile__()

module SMS

using AWSCore

"""
    using AWSSDK.SMS.create_replication_job
    create_replication_job([::AWSConfig], arguments::Dict)
    create_replication_job([::AWSConfig]; serverId=, seedReplicationTime=, frequency=, <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "CreateReplicationJob", arguments::Dict)
    sms([::AWSConfig], "CreateReplicationJob", serverId=, seedReplicationTime=, frequency=, <keyword arguments>)

# CreateReplicationJob Operation

The CreateReplicationJob API is used to create a ReplicationJob to replicate a server on AWS. Call this API to first create a ReplicationJob, which will then schedule periodic ReplicationRuns to replicate your server to AWS. Each ReplicationRun will result in the creation of an AWS AMI.

# Arguments

## `serverId = ::String` -- *Required*

## `seedReplicationTime = timestamp` -- *Required*

## `frequency = ::Int` -- *Required*

## `licenseType = "AWS" or "BYOL"`

## `roleName = ::String`

## `description = ::String`

# Returns

`CreateReplicationJobResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException`, `UnauthorizedOperationException`, `OperationNotPermittedException`, `ServerCannotBeReplicatedException`, `ReplicationJobAlreadyExistsException`, `NoConnectorsAvailableException` or `InternalError`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/CreateReplicationJob)
"""
@inline create_replication_job(aws::AWSConfig=default_aws_config(); args...) = create_replication_job(aws, args)

@inline create_replication_job(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "CreateReplicationJob", args)

@inline create_replication_job(args) = create_replication_job(default_aws_config(), args)

"""
    using AWSSDK.SMS.delete_replication_job
    delete_replication_job([::AWSConfig], arguments::Dict)
    delete_replication_job([::AWSConfig]; replicationJobId=)

    using AWSCore.Services.sms
    sms([::AWSConfig], "DeleteReplicationJob", arguments::Dict)
    sms([::AWSConfig], "DeleteReplicationJob", replicationJobId=)

# DeleteReplicationJob Operation

The DeleteReplicationJob API is used to delete a ReplicationJob, resulting in no further ReplicationRuns. This will delete the contents of the S3 bucket used to store SMS artifacts, but will not delete any AMIs created by the SMS service.

# Arguments

## `replicationJobId = ::String` -- *Required*

# Returns

`DeleteReplicationJobResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException`, `UnauthorizedOperationException`, `OperationNotPermittedException` or `ReplicationJobNotFoundException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/DeleteReplicationJob)
"""
@inline delete_replication_job(aws::AWSConfig=default_aws_config(); args...) = delete_replication_job(aws, args)

@inline delete_replication_job(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "DeleteReplicationJob", args)

@inline delete_replication_job(args) = delete_replication_job(default_aws_config(), args)

"""
    using AWSSDK.SMS.delete_server_catalog
    delete_server_catalog([::AWSConfig], arguments::Dict)
    delete_server_catalog([::AWSConfig]; )

    using AWSCore.Services.sms
    sms([::AWSConfig], "DeleteServerCatalog", arguments::Dict)
    sms([::AWSConfig], "DeleteServerCatalog", )

# DeleteServerCatalog Operation

The DeleteServerCatalog API clears all servers from your server catalog. This means that these servers will no longer be accessible to the Server Migration Service.

# Arguments

# Returns

`DeleteServerCatalogResponse`

# Exceptions

`UnauthorizedOperationException`, `OperationNotPermittedException`, `InvalidParameterException` or `MissingRequiredParameterException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/DeleteServerCatalog)
"""
@inline delete_server_catalog(aws::AWSConfig=default_aws_config(); args...) = delete_server_catalog(aws, args)

@inline delete_server_catalog(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "DeleteServerCatalog", args)

@inline delete_server_catalog(args) = delete_server_catalog(default_aws_config(), args)

"""
    using AWSSDK.SMS.disassociate_connector
    disassociate_connector([::AWSConfig], arguments::Dict)
    disassociate_connector([::AWSConfig]; connectorId=)

    using AWSCore.Services.sms
    sms([::AWSConfig], "DisassociateConnector", arguments::Dict)
    sms([::AWSConfig], "DisassociateConnector", connectorId=)

# DisassociateConnector Operation

The DisassociateConnector API will disassociate a connector from the Server Migration Service, rendering it unavailable to support replication jobs.

# Arguments

## `connectorId = ::String` -- *Required*

# Returns

`DisassociateConnectorResponse`

# Exceptions

`MissingRequiredParameterException`, `UnauthorizedOperationException`, `OperationNotPermittedException` or `InvalidParameterException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/DisassociateConnector)
"""
@inline disassociate_connector(aws::AWSConfig=default_aws_config(); args...) = disassociate_connector(aws, args)

@inline disassociate_connector(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "DisassociateConnector", args)

@inline disassociate_connector(args) = disassociate_connector(default_aws_config(), args)

"""
    using AWSSDK.SMS.get_connectors
    get_connectors([::AWSConfig], arguments::Dict)
    get_connectors([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "GetConnectors", arguments::Dict)
    sms([::AWSConfig], "GetConnectors", <keyword arguments>)

# GetConnectors Operation

The GetConnectors API returns a list of connectors that are registered with the Server Migration Service.

# Arguments

## `nextToken = ::String`

## `maxResults = ::Int`

# Returns

`GetConnectorsResponse`

# Exceptions

`UnauthorizedOperationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/GetConnectors)
"""
@inline get_connectors(aws::AWSConfig=default_aws_config(); args...) = get_connectors(aws, args)

@inline get_connectors(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "GetConnectors", args)

@inline get_connectors(args) = get_connectors(default_aws_config(), args)

"""
    using AWSSDK.SMS.get_replication_jobs
    get_replication_jobs([::AWSConfig], arguments::Dict)
    get_replication_jobs([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "GetReplicationJobs", arguments::Dict)
    sms([::AWSConfig], "GetReplicationJobs", <keyword arguments>)

# GetReplicationJobs Operation

The GetReplicationJobs API will return all of your ReplicationJobs and their details. This API returns a paginated list, that may be consecutively called with nextToken to retrieve all ReplicationJobs.

# Arguments

## `replicationJobId = ::String`

## `nextToken = ::String`

## `maxResults = ::Int`

# Returns

`GetReplicationJobsResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException` or `UnauthorizedOperationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/GetReplicationJobs)
"""
@inline get_replication_jobs(aws::AWSConfig=default_aws_config(); args...) = get_replication_jobs(aws, args)

@inline get_replication_jobs(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "GetReplicationJobs", args)

@inline get_replication_jobs(args) = get_replication_jobs(default_aws_config(), args)

"""
    using AWSSDK.SMS.get_replication_runs
    get_replication_runs([::AWSConfig], arguments::Dict)
    get_replication_runs([::AWSConfig]; replicationJobId=, <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "GetReplicationRuns", arguments::Dict)
    sms([::AWSConfig], "GetReplicationRuns", replicationJobId=, <keyword arguments>)

# GetReplicationRuns Operation

The GetReplicationRuns API will return all ReplicationRuns for a given ReplicationJob. This API returns a paginated list, that may be consecutively called with nextToken to retrieve all ReplicationRuns for a ReplicationJob.

# Arguments

## `replicationJobId = ::String` -- *Required*

## `nextToken = ::String`

## `maxResults = ::Int`

# Returns

`GetReplicationRunsResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException` or `UnauthorizedOperationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/GetReplicationRuns)
"""
@inline get_replication_runs(aws::AWSConfig=default_aws_config(); args...) = get_replication_runs(aws, args)

@inline get_replication_runs(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "GetReplicationRuns", args)

@inline get_replication_runs(args) = get_replication_runs(default_aws_config(), args)

"""
    using AWSSDK.SMS.get_servers
    get_servers([::AWSConfig], arguments::Dict)
    get_servers([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "GetServers", arguments::Dict)
    sms([::AWSConfig], "GetServers", <keyword arguments>)

# GetServers Operation

The GetServers API returns a list of all servers in your server catalog. For this call to succeed, you must previously have called ImportServerCatalog.

# Arguments

## `nextToken = ::String`

## `maxResults = ::Int`

# Returns

`GetServersResponse`

# Exceptions

`UnauthorizedOperationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/GetServers)
"""
@inline get_servers(aws::AWSConfig=default_aws_config(); args...) = get_servers(aws, args)

@inline get_servers(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "GetServers", args)

@inline get_servers(args) = get_servers(default_aws_config(), args)

"""
    using AWSSDK.SMS.import_server_catalog
    import_server_catalog([::AWSConfig], arguments::Dict)
    import_server_catalog([::AWSConfig]; )

    using AWSCore.Services.sms
    sms([::AWSConfig], "ImportServerCatalog", arguments::Dict)
    sms([::AWSConfig], "ImportServerCatalog", )

# ImportServerCatalog Operation

The ImportServerCatalog API is used to gather the complete list of on-premises servers on your premises. This API call requires connectors to be installed and monitoring all servers you would like imported. This API call returns immediately, but may take some time to retrieve all of the servers.

# Arguments

# Returns

`ImportServerCatalogResponse`

# Exceptions

`UnauthorizedOperationException`, `OperationNotPermittedException`, `InvalidParameterException`, `MissingRequiredParameterException` or `NoConnectorsAvailableException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/ImportServerCatalog)
"""
@inline import_server_catalog(aws::AWSConfig=default_aws_config(); args...) = import_server_catalog(aws, args)

@inline import_server_catalog(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "ImportServerCatalog", args)

@inline import_server_catalog(args) = import_server_catalog(default_aws_config(), args)

"""
    using AWSSDK.SMS.start_on_demand_replication_run
    start_on_demand_replication_run([::AWSConfig], arguments::Dict)
    start_on_demand_replication_run([::AWSConfig]; replicationJobId=, <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "StartOnDemandReplicationRun", arguments::Dict)
    sms([::AWSConfig], "StartOnDemandReplicationRun", replicationJobId=, <keyword arguments>)

# StartOnDemandReplicationRun Operation

The StartOnDemandReplicationRun API is used to start a ReplicationRun on demand (in addition to those that are scheduled based on your frequency). This ReplicationRun will start immediately. StartOnDemandReplicationRun is subject to limits on how many on demand ReplicationRuns you may call per 24-hour period.

# Arguments

## `replicationJobId = ::String` -- *Required*

## `description = ::String`

# Returns

`StartOnDemandReplicationRunResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException`, `UnauthorizedOperationException`, `OperationNotPermittedException` or `ReplicationRunLimitExceededException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/StartOnDemandReplicationRun)
"""
@inline start_on_demand_replication_run(aws::AWSConfig=default_aws_config(); args...) = start_on_demand_replication_run(aws, args)

@inline start_on_demand_replication_run(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "StartOnDemandReplicationRun", args)

@inline start_on_demand_replication_run(args) = start_on_demand_replication_run(default_aws_config(), args)

"""
    using AWSSDK.SMS.update_replication_job
    update_replication_job([::AWSConfig], arguments::Dict)
    update_replication_job([::AWSConfig]; replicationJobId=, <keyword arguments>)

    using AWSCore.Services.sms
    sms([::AWSConfig], "UpdateReplicationJob", arguments::Dict)
    sms([::AWSConfig], "UpdateReplicationJob", replicationJobId=, <keyword arguments>)

# UpdateReplicationJob Operation

The UpdateReplicationJob API is used to change the settings of your existing ReplicationJob created using CreateReplicationJob. Calling this API will affect the next scheduled ReplicationRun.

# Arguments

## `replicationJobId = ::String` -- *Required*

## `frequency = ::Int`

## `nextReplicationRunStartTime = timestamp`

## `licenseType = "AWS" or "BYOL"`

## `roleName = ::String`

## `description = ::String`

# Returns

`UpdateReplicationJobResponse`

# Exceptions

`InvalidParameterException`, `MissingRequiredParameterException`, `OperationNotPermittedException`, `UnauthorizedOperationException`, `ServerCannotBeReplicatedException`, `ReplicationJobNotFoundException` or `InternalError`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/sms-2016-10-24/UpdateReplicationJob)
"""
@inline update_replication_job(aws::AWSConfig=default_aws_config(); args...) = update_replication_job(aws, args)

@inline update_replication_job(aws::AWSConfig, args) = AWSCore.Services.sms(aws, "UpdateReplicationJob", args)

@inline update_replication_job(args) = update_replication_job(default_aws_config(), args)

end # module SMS

#==============================================================================#
# End of file
#==============================================================================#


text:
struct RelativePriceError <: CalibrationErrorType end
struct PriceError <: CalibrationErrorType end
struct ImpliedVolError <: CalibrationErrorType end

struct NaiveBasketType <: CalibrationBasketType end
struct MaturityStrikeByDeltaGammaBasketType <: CalibrationBasketType end

mutable struct CalibrationHelperCommon{CE <: CalibrationErrorType}
  marketValue::Float64
  calibrationErrorType::CE

  # CalibrationHelperCommon() = new{RelativePriceError}(0.0, RelativePriceError())
end

CalibrationHelperCommon() = CalibrationHelperCommon{RelativePriceError}(0.0, RelativePriceError())

mutable struct ImpliedVolatilityHelper{C <: CalibrationHelper} <: Function
  helper::C
  value::Float64
end

(iv::ImpliedVolatilityHelper)(x::Float64) = iv.value - black_price!(iv.helper, x)

# function operator(iv::ImpliedVolatilityHelper)
#   function _inner(x::Float64)
#     return iv.value - black_price!(iv.helper, x)
#   end
#
#   return _inner
# end

calibration_error(::RelativePriceError, helper::CalibrationHelper) =
  abs(helper.calibCommon.marketValue - model_value!(helper)) / helper.calibCommon.marketValue

# function calibration_error{C <: CalibrationHelper}(::RelativePriceError, helper::C)
#   @code_warntype model_value!(helper)
#   return abs(helper.calibCommon.marketValue - model_value!(helper)) / helper.calibCommon.marketValue
# end

function implied_volatility!(ch::CalibrationHelper, targetValue::Float64, accuracy::Float64, maxEvals::Int, minVol::Float64, maxVol::Float64)
  ivh = ImpliedVolatilityHelper(ch, targetValue)

  solv = BrentSolver(maxEvals)

  return solve(solv, ivh, accuracy, ch.volatility.value, minVol, maxVol)
end

function update_pricing_engine(ch::CalibrationHelper, pe::PricingEngine)
  newCh = clone(ch, pe)

  return newCh
end

mutable struct SwaptionHelper{Dm <: Dates.Period, Dl <: Dates.Period, TP <: TenorPeriod, DC_fix <: DayCount, DC_float <: DayCount, T <: YieldTermStructure, P <: PricingEngine, CE <: CalibrationErrorType, ST <: SwapType, B <: BusinessDayConvention, SP <: PricingEngine, II <: IborIndex} <: CalibrationHelper
  lazyMixin::LazyMixin
  exerciseDate::Date
  endDate::Date
  maturity::Dm
  swapLength::Dl
  volatility::Quote
  iborIndex::II
  fixedLegTenor::TP
  fixedLegDayCount::DC_fix
  floatingLegDayCount::DC_float
  strike::Float64
  nominal::Float64
  shift::Float64
  exerciseRate::Float64
  calibCommon::CalibrationHelperCommon{CE}
  yts::T
  pricingEngine::P
  swaption::Swaption{EuropeanExercise, SettlementPhysical, P, VanillaSwap{ST, DC_fix, DC_float, B, Leg, SP, II}}

  # SwaptionHelper{Dm, Dl, TP, DC_fix, DC_float, T, P, CE, ST, B, SP, STP, CUR, IB, IC, IDC, IT}(lazyMixin::LazyMixin, exerciseDate::Date, endDate::Date, maturity::Dm,
  #               swapLength::Dl, volatility::Quote, iborIndex::IborIndex{STP, CUR, IB, IC, IDC, IT}, fixedLegTenor::TP, fixedLegDayCount::DC_fix, floatingLegDayCount::DC_float,
  #               strike::Float64, nominal::Float64, shift::Float64, exerciseRate::Float64, calibCommon::CalibrationHelperCommon{CE}, yts::T, pe::P,
  #               swaption::Swaption{EuropeanExercise, SettlementPhysical, P, ST, DC_fix, DC_float, B, Leg, SP, STP, CUR, IB, IC, IDC, IT}) =
  #               new{Dm, Dl, TP, DC_fix, DC_float, T, P, CE, ST, B, SP, STP, CUR, IB, IC, IDC, IT}(lazyMixin, exerciseDate, endDate, maturity, swapLength, volatility, iborIndex,
  #               fixedLegTenor, fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, calibCommon, yts, pe, swaption)
end

function SwaptionHelper(lazyMixin::LazyMixin, exerciseDate::Date, endDate::Date, maturity::Dm, swapLength::Dl, volatility::Quote, iborIndex::II,
                        fixedLegTenor::TP, fixedLegDayCount::DC_fix, floatingLegDayCount::DC_float, strike::Float64, nominal::Float64, shift::Float64, exerciseRate::Float64,
                        calibCommon::CalibrationHelperCommon{CE}, yts::T,
                        pe::P) where {Dm <: Dates.Period, Dl <: Dates.Period, TP <: TenorPeriod, DC_fix <: DayCount, DC_float <: DayCount, T <: YieldTermStructure, P <: PricingEngine, CE <: CalibrationErrorType, II <: IborIndex}

  swaption = build_swaption(exerciseDate, endDate, maturity, swapLength, iborIndex, fixedLegTenor, fixedLegDayCount, floatingLegDayCount, strike, nominal, exerciseRate, yts, pe)

  SwaptionHelper{Dm, Dl, TP, DC_fix, DC_float, T, P, CE, typeof(swaption.swap.swapT), typeof(swaption.swap.paymentConvention), typeof(swaption.swap.pricingEngine), II}(lazyMixin, exerciseDate,
  endDate, maturity, swapLength, volatility, iborIndex, fixedLegTenor, fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, calibCommon, yts, pe,
  swaption)
end

SwaptionHelper(maturity::Dates.Period, swapLength::Dates.Period, volatility::Quote, iborIndex::IborIndex, fixedLegTenor::TenorPeriod, fixedLegDayCount::DayCount, floatingLegDayCount::DayCount,
              yts::YieldTermStructure, pe::PricingEngine, strike::Float64 = -1.0, nominal::Float64 = 1.0, shift::Float64 = 0.0,
              exerciseRate::Float64 = 0.0) =
              SwaptionHelper(LazyMixin(), Date(0), Date(0), maturity, swapLength, volatility, iborIndex, fixedLegTenor,
                fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, CalibrationHelperCommon(), yts, pe)

SwaptionHelper(expiryDate::Date, endDate::Date, volatility::Quote, iborIndex::IborIndex, fixedLegTenor::TenorPeriod, fixedLegDayCount::DayCount, floatingLegDayCount::DayCount,
              yts::YieldTermStructure, pe::PricingEngine = NullSwaptionEngine(), strike::Float64 = -1.0, nominal::Float64 = 1.0, shift::Float64 = 0.0,
              exerciseRate::Float64 = 0.0) =
              SwaptionHelper(LazyMixin(), expiryDate, endDate, Dates.Day(0), Dates.Day(0), volatility, iborIndex, fixedLegTenor,
                fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, CalibrationHelperCommon(), yts, pe)

# SwaptionHelper{Dm <: Dates.Period, Dl <: Dates.Period, TP <: TenorPeriod, DC_fix <: DayCount, DC_float <: DayCount, T <: YieldTermStructure, P <: PricingEngine, CE <: CalibrationErrorType, ST <: SwapType, B <: BusinessDayConvention, SP <: PricingEngine, STP <: TenorPeriod, CUR <: AbstractCurrency, IB <: BusinessCalendar, IC <: BusinessDayConvention, IDC <: DayCount, IT <: TermStructure}(lazyMixin::LazyMixin,
#               exerciseDate::Date, endDate::Date, maturity::Dm, swapLength::Dl, volatility::Quote, iborIndex::IborIndex{STP, CUR, IB, IC, IDC, IT}, fixedLegTenor::TP,
#               fixedLegDayCount::DC_fix, floatingLegDayCount::DC_float, strike::Float64, nominal::Float64, shift::Float64, exerciseRate::Float64,
#               calibCommon::CalibrationHelperCommon{CE}, yts::T, pe::P,
#               swaption::Swaption{EuropeanExercise, SettlementPhysical, P, ST, DC_fix, DC_float, B, Leg, SP, STP, CUR, IB, IC, IDC, IT}) =
#               SwaptionHelper{Dm, Dl, TP, DC_fix, DC_float, T, P, CE, ST, B, SP, STP, CUR, IB, IC, IDC, IT}(lazyMixin, exerciseDate, endDate, maturity, swapLength, volatility, iborIndex, fixedLegTenor,
#               fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, calibCommon, yts, pe, swaption)

# SwaptionHelper{Dm <: Dates.Period, Dl <: Dates.Period, TP <: TenorPeriod, DC_fix <: DayCount, DC_float <: DayCount, T <: YieldTermStructure, P <: PricingEngine, CE <: CalibrationErrorType, STP <: TenorPeriod, CUR <: AbstractCurrency, IB <: BusinessCalendar, IC <: BusinessDayConvention, IDC <: DayCount, IT <: TermStructure}(lazyMixin::LazyMixin,
#               exerciseDate::Date, endDate::Date, maturity::Dm, swapLength::Dl, volatility::Quote, iborIndex::IborIndex{STP, CUR, IB, IC, IDC, IT}, fixedLegTenor::TP,
#               fixedLegDayCount::DC_fix, floatingLegDayCount::DC_float, strike::Float64, nominal::Float64, shift::Float64, exerciseRate::Float64,
#               calibCommon::CalibrationHelperCommon{CE}, yts::T, pe::P) =
#               SwaptionHelper(lazyMixin, exerciseDate, endDate, maturity, swapLength, volatility, iborIndex, fixedLegTenor,
#               fixedLegDayCount, floatingLegDayCount, strike, nominal, shift, exerciseRate, calibCommon, yts, pe)

function perform_calculations!(swaptionHelper::SwaptionHelper)
  # calendar = swaptionHelper.iborIndex.fixingCalendar
  # fixingDays = swaptionHelper.iborIndex.fixingDays
  # convention = swaptionHelper.iborIndex.convention
  #
  # exerciseDate = swaptionHelper.exerciseDate == Date() ? advance(swaptionHelper.maturity, calendar, swaptionHelper.yts.referenceDate, convention) : swaptionHelper.exerciseDate
  #
  # startDate = advance(Dates.Day(fixingDays), calendar, exerciseDate, convention)
  #
  # endDate = swaptionHelper.endDate == Date() ? advance(swaptionHelper.swapLength, calendar, startDate, convention) : swaptionHelper.endDate
  #
  # fixedSchedule = QuantLib.Time.Schedule(startDate, endDate, swaptionHelper.fixedLegTenor, convention, convention, QuantLib.Time.DateGenerationForwards(), false, calendar)
  # floatSchedule = QuantLib.Time.Schedule(startDate, endDate, swaptionHelper.iborIndex.tenor, convention, convention, QuantLib.Time.DateGenerationForwards(), false, calendar)
  #
  # swapEngine = DiscountingSwapEngine(swaptionHelper.yts, false)
  #
  # swapT = Receiver()
  #
  # tempSwap = VanillaSwap(swapT, swaptionHelper.nominal, fixedSchedule, 0.0, swaptionHelper.fixedLegDayCount, swaptionHelper.iborIndex, 0.0, floatSchedule, swaptionHelper.floatingLegDayCount, swapEngine)
  #
  # forward = fair_rate(tempSwap)
  #
  # if swaptionHelper.strike == -1.0
  #   swaptionHelper.exerciseRate = forward
  # else
  #   swaptionHelper.exerciseRate = strike
  #   swapT = strike <= forward ? Receiver() : Payer()
  # end
  #
  # swap = VanillaSwap(swapT, swaptionHelper.nominal, fixedSchedule, swaptionHelper.exerciseRate, swaptionHelper.fixedLegDayCount, swaptionHelper.iborIndex, 0.0, floatSchedule, swaptionHelper.floatingLegDayCount, swapEngine)
  # exercise = EuropeanExercise(exerciseDate)
  #
  # swaptionHelper.swaption = Swaption(swap, exercise, swaptionHelper.pricingEngine)

  # calibration calc
  _calibration_calculate!(swaptionHelper)

  return swaptionHelper
end

function _calibration_calculate!(swaptionHelper::SwaptionHelper)
  swaptionHelper.calibCommon.marketValue = black_price!(swaptionHelper, swaptionHelper.volatility.value)

  return swaptionHelper
end

function add_times_to!(swaptionHelper::SwaptionHelper, times::Vector{Float64})
  calculate!(swaptionHelper)
  discretizedSwap = DiscretizedSwaption(swaptionHelper.swaption, reference_date(swaptionHelper.yts), swaptionHelper.yts.dc, NullLattice())
  times = vcat(times, mandatory_times(discretizedSwap))
  return times
end

function model_value!(sh::SwaptionHelper)
  calculate!(sh)
  # sh.swaption = update_pricing_engine(sh.swaption, sh.pricingEngine) # this might clone swaption
  # need to force recalc
  calculated!(sh.swaption, false)
  return npv(sh.swaption)
end

function black_price!(swaptionHelper::SwaptionHelper, sigma::Float64)
  calculate!(swaptionHelper)
  # stuff
  black = BlackSwaptionEngine(swaptionHelper.yts, Quote(sigma), Actual365(), swaptionHelper.shift)
  tempSwaption = update_pricing_engine(swaptionHelper.swaption, black)
  # swaptionHelper.swaption.pricingEngine = black
  value = npv(tempSwaption)
  return value
end

function underlying_swap!(swaptionHelper::SwaptionHelper)
  calculate!(swaptionHelper)
  return swaptionHelper.swaption.swap
end

function build_swaption(exerciseDate::Date, endDate::Date, maturity::Dates.Period, swapLength::Dates.Period, iborIndex::IborIndex,
                        fixedLegTenor::TenorPeriod, fixedLegDayCount::DayCount, floatingLegDayCount::DayCount, strike::Float64, nominal::Float64,
                        exerciseRate::Float64, yts::T, pe::PricingEngine) where {T <: TermStructure}
  calendar = iborIndex.fixingCalendar
  fixingDays = iborIndex.fixingDays
  convention = iborIndex.convention

  exerciseDate = exerciseDate == Date(0) ? advance(maturity, calendar, yts.referenceDate, convention) : exerciseDate

  startDate = advance(Dates.Day(fixingDays), calendar, exerciseDate, convention)

  endDate = endDate == Date(0) ? advance(swapLength, calendar, startDate, convention) : endDate

  fixedSchedule = QuantLib.Time.Schedule(startDate, endDate, fixedLegTenor, convention, convention, QuantLib.Time.DateGenerationForwards(), false, calendar)
  floatSchedule = QuantLib.Time.Schedule(startDate, endDate, iborIndex.tenor, convention, convention, QuantLib.Time.DateGenerationForwards(), false, calendar)

  swapEngine = DiscountingSwapEngine{T}(yts, false)

  swapT = Receiver()

  tempSwap = VanillaSwap(swapT, nominal, fixedSchedule, 0.0, fixedLegDayCount, iborIndex, 0.0, floatSchedule, floatingLegDayCount, swapEngine)

  forward = fair_rate(tempSwap)

  if strike == -1.0
    exerciseRate = forward
  else
    exerciseRate = strike
    swapT = strike <= forward ? Receiver() : Payer()
  end

  swap = VanillaSwap(swapT, nominal, fixedSchedule, exerciseRate, fixedLegDayCount, iborIndex, 0.0, floatSchedule, floatingLegDayCount, swapEngine)
  exercise = EuropeanExercise(exerciseDate)

  swaption = Swaption(swap, exercise, pe)

  return swaption
end

function build_swaption(sh::SwaptionHelper, pe::PricingEngine)
  # for cloning
  return build_swaption(sh.exerciseDate, sh.endDate, sh.maturity, sh.swapLength, sh.iborIndex, sh.fixedLegTenor, sh.fixedLegDayCount, sh.floatingLegDayCount,
                        sh.strike, sh.nominal, sh.exerciseRate, sh.yts, pe)
end

# clone functions #
function clone(swaptionHelper::SwaptionHelper{Dm, Dl, TP, DC_fix, DC_float, T, P, CE, ST, B, SP, II},
              pe::P2 = swaptionHelper.pricingEngine) where {Dm, Dl, TP, DC_fix, DC_float, T, P, CE, ST, B, SP, II, P2}
  lazyMixin, calibCommon = pe == swaptionHelper.pricingEngine ? (swaptionHelper.lazyMixin, swaptionHelper.calibCommon) : (LazyMixin(), CalibrationHelperCommon())
  swaption = build_swaption(swaptionHelper, pe)
  newSwaptionHelper = SwaptionHelper{Dm, Dl, TP, DC_fix, DC_float, T, P2, CE, ST, B, SP, II}(lazyMixin, swaptionHelper.exerciseDate, swaptionHelper.endDate, swaptionHelper.maturity, swaptionHelper.swapLength,
                      swaptionHelper.volatility, swaptionHelper.iborIndex, swaptionHelper.fixedLegTenor, swaptionHelper.fixedLegDayCount, swaptionHelper.floatingLegDayCount,
                      swaptionHelper.strike, swaptionHelper.nominal, swaptionHelper.shift, swaptionHelper.exerciseRate, calibCommon, swaptionHelper.yts,
                      pe, swaption)

  return newSwaptionHelper
end


text:
using DandelionWebSockets

import JSON
import Base.==
import DandelionSlack: on_event, on_reply, on_error, on_connect, on_disconnect,
                       EventTimestamp, RTMWebSocket, send_text, stop, RTMClient, makerequest,
                       wsconnect, HttpException
import DandelionWebSockets: @mock, @mockfunction, @expect, Throws, mock_match
import DandelionWebSockets: AbstractRetry, Retry, retry, reset, set_function

#
# A fake RTM event.
#

immutable FakeEvent <: DandelionSlack.OutgoingEvent
    value::UTF8String

    FakeEvent() = new("")
    FakeEvent(a::ASCIIString) = new(utf8(a))
end

==(a::FakeEvent, b::FakeEvent) = a.value == b.value

DandelionSlack.serialize(event::FakeEvent) = Dict{AbstractString, Any}("value" => event.value)

test_event_1 = FakeEvent("bar")
test_event_2 = FakeEvent("baz")

# Also add equality for all events, for testing convenience.
macro eventeq(r::Expr)
    quote
        function $(esc(:(==)))(a::$r, b::$r)
            if typeof(a) != typeof(b)
                return false
            end

            for name in fieldnames(a)
                af = getfield(a, name)
                bf = getfield(b, name)

                if isa(af, Nullable)
                    null_equals = isnull(af) && isnull(bf) || !isnull(af) && !isnull(bf) && get(af) == get(bf)
                    if !null_equals
                        return false
                    end
                else
                    if af != bf
                        return false
                    end
                end
            end

            return true
        end
    end
end

@eventeq DandelionSlack.OutgoingEvent
@eventeq DandelionSlack.Event
@eventeq DandelionSlack.EventError

function ==(a::RTMError, b::RTMError)
    return a.code == b.code && a.msg == b.msg
end

#
# Implement a mock WebSocket client that stores the events we send.
#

@mock MockWSClient AbstractWSClient
ws_client = MockWSClient()
@mockfunction(ws_client,
    send_text(::MockWSClient, ::UTF8String),
    stop(::MockWSClient),
    wsconnect(::MockWSClient, ::Requests.URI, ::WebSocketHandler))

#
# A mock RTMHandler to test that RTMWebSocket propagates messages correctly.
#

@mock MockRTMHandler RTMHandler
mock_handler = MockRTMHandler()
@mockfunction(mock_handler,
    on_reply(::MockRTMHandler, ::Int64, ::DandelionSlack.Event),
    on_event(::MockRTMHandler, ::DandelionSlack.Event),
    on_error(::MockRTMHandler, e::EventError),
    on_disconnect(::MockRTMHandler),
    on_connect(::MockRTMHandler))

#
# Fake requests and mocking the makerequests function.
#

immutable FakeRequests <: AbstractHttp end
fake_requests = FakeRequests()

post(::FakeRequests, uri::AbstractString; args...) = nothing

abstract AbstractMocker
@mock Mocker AbstractMocker
mocker = Mocker()
@mockfunction mocker makerequest(::Any, ::FakeRequests)

@mock MockRetry AbstractRetry
mock_retry = MockRetry()
@mockfunction mock_retry retry(::MockRetry) reset(::MockRetry) set_function(::MockRetry, ::Function)

token = Token("ABCDEF")
ok_status = Status(true, Nullable{UTF8String}(utf8("")), Nullable{UTF8String}(utf8("")))
# `fake_url` is what we get back from Slck. `expected_fake_url` is what the Slack URL needs to be
# converted to, in order to send it into Requests.
fake_url = utf8("ws://some/url")
expected_fake_url = Requests.URI("http://some/url")
fake_self = Self(UserId("U0"), SlackName("User 0"), 123, utf8(""))
fake_team = Team(TeamId("T0"), SlackName("Team 0"), utf8(""), utf8(""))
fake_rtm_start_response = RtmStartResponse(fake_url, fake_self, fake_team,
    [], [], [], Nullable{DandelionSlack.Mpim}(), [], [])

#
# Matching JSON
#

immutable JSONMatcher <: AbstractMatcher
    object::Dict{Any, Any}
end

mock_match(m::JSONMatcher, v::AbstractString) = m.object == JSON.parse(v)

#
# Tests
#

facts("RTM event register") do
    @fact DandelionSlack.find_event("message") --> MessageEvent
    @fact DandelionSlack.find_event("nosuchevent") --> nothing
end

facts("RTM events") do
    context("Event equality for testing") do
        @fact MessageEvent("a", ChannelId("b"), UserId("U0"), EventTimestamp("123")) -->
            MessageEvent("a", ChannelId("b"), UserId("U0"), EventTimestamp("123"))
        @fact MessageEvent("a", ChannelId("b"), UserId("U0"), EventTimestamp("123")) !=
            MessageEvent("b", ChannelId("c"), UserId("U0"), EventTimestamp("123")) --> true
        @fact OutgoingMessageEvent("a", ChannelId("b")) --> OutgoingMessageEvent("a", ChannelId("b"))
        @fact OutgoingMessageEvent("a", ChannelId("b")) != OutgoingMessageEvent("b", ChannelId("c")) --> true
    end

    context("Deserialize events") do
        message_json = """{"id": 1, "type": "message", "text": "Hello",
            "channel": "C0", "user": "U0", "ts": "123"}"""
        message = DandelionSlack.deserialize(MessageEvent, message_json)

        @fact message --> MessageEvent(utf8("Hello"), ChannelId("C0"), UserId("U0"), EventTimestamp("123"))
    end

    context("Propagate events from WebSocket to RTM") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        @expect mock_handler on_event(mock_handler,
            MessageEvent(utf8("Hello"), ChannelId(utf8("C0")), UserId("U0"), EventTimestamp("123")))

        on_text(rtm_ws, utf8("""{"type": "message", "channel": "C0",
            "text": "Hello", "user": "U0", "ts": "123"}"""))

        check(mock_handler)
        check(mock_retry)
    end

    context("Message ack event") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        @expect mock_handler on_reply(mock_handler, 1,
            MessageAckEvent(utf8("Hello"), Nullable(ChannelId("C0")), true, EventTimestamp("123")))

        on_text(rtm_ws,
            utf8("""{"reply_to": 1, "ok": true, "text": "Hello", "channel": "C0", "ts": "123"}"""))

        check(mock_handler)
        check(mock_retry)
    end

    context("Missing type key and not message ack") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        text = utf8("""{"reply_to": 1}""")
        @expect mock_handler on_error(mock_handler, MissingTypeError(text))

        on_text(rtm_ws, text)

        check(mock_handler)
        check(mock_retry)
    end

    context("Invalid JSON") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        text = utf8("""{"reply_to" foobarbaz""")

        @expect mock_handler on_error(mock_handler, InvalidJSONError(text))

        on_text(rtm_ws, text)

        check(mock_handler)
        check(mock_retry)
    end

    context("Unknown message type") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        text = utf8("""{"type": "nosuchtype"}""")

        @expect mock_handler on_error(mock_handler, UnknownEventTypeError(text, utf8("nosuchtype")))

        on_text(rtm_ws, text)

        check(mock_handler)
        check(mock_retry)
    end

    context("Missing required field") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        # No "text" field.
        text = utf8("""{"type": "message", "channel": "C0", "user": "U0", "ts": "123"}""")

        @expect mock_handler on_error(mock_handler, DeserializationError(utf8("text"), text, MessageEvent))

        on_text(rtm_ws, text)

        check(mock_handler)
        check(mock_retry)
    end

    context("Error event from Slack") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        @expect mock_handler on_event(mock_handler, ErrorEvent(RTMError(1, "Reason")))

        on_text(rtm_ws,
            utf8("""{"type": "error", "error": {"code": 1, "msg": "Reason"}}"""))

        check(mock_retry)
        check(mock_handler)
    end

    context("Retry connection on WebSocket close") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        @expect mock_handler on_disconnect(mock_handler)
        @expect mock_retry retry(mock_retry)

        state_closed(rtm_ws)

        check(mock_handler)
        check(mock_retry)
    end

    context("Successful connection") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        @expect mock_handler on_connect(mock_handler)
        @expect mock_retry reset(mock_retry)

        state_open(rtm_ws)

        check(mock_handler)
        check(mock_retry)
    end

    # This only tests that the callback functions exist, not that they actually do anything.
    # This is mostly for coverage.
    context("Existence of the rest of WebSocketHandler interface functions") do
        rtm_ws = RTMWebSocket(mock_retry)
        attach(rtm_ws, mock_handler)

        on_binary(rtm_ws, b"")
        state_connecting(rtm_ws)
        state_closing(rtm_ws)

        check(mock_handler)
        check(mock_retry)
    end
end

facts("RTMClient") do
    context("Send and receive events") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm_client = RTMClient(token;
                               connection_retry=mock_retry, ws_client_factory=x -> ws_client)
        attach(rtm_client, mock_handler)

        # `fake_rtm_start_response` uses `fake_url` as the URL we should connect to.
        @expect mocker makerequest(TypeMatcher(Any), fake_requests) (ok_status, fake_rtm_start_response)
        @expect ws_client wsconnect(ws_client, Requests.URI(fake_url), rtm_client.rtm_ws)
        rtm_connect(rtm_client; requests=fake_requests)

        # We will send two events from server to client, a HelloEvent and a message "A message".
        @expect mock_handler on_event(mock_handler, HelloEvent())
        @expect mock_handler on_event(mock_handler,
            MessageEvent("A message", ChannelId("C0"), UserId("U0"), EventTimestamp("12345.6789")))

        first_id = 1

        # ... then send one event from client to server, and then send the reply to that message.
        @expect ws_client send_text(ws_client, JSONMatcher(Dict{Any, Any}(
            "id" => first_id, "type" => "message", "text" => "Hello", "channel" => "C0")))
        @expect mock_handler on_reply(mock_handler, first_id,
            MessageAckEvent("Hello", Nullable(ChannelId("C0")), true, EventTimestamp("12345.6789")))

        # These are fake events from the WebSocket
        on_text(rtm_client.rtm_ws, utf8("""{"type": "hello"}"""))
        on_text(rtm_client.rtm_ws, utf8(
            """{"type": "message", "text": "A message", "channel": "C0", "user": "U0", "ts": "12345.6789"}"""))

        # Send a message, and then we fake a reply to it.
        m_id1 = send_event(rtm_client, OutgoingMessageEvent("Hello", ChannelId("C0")))
        on_text(rtm_client.rtm_ws,
            utf8("""{"ok": true, "reply_to": $(first_id), "text": "Hello", "channel": "C0", "ts": "12345.6789"}"""))

        check(ws_client)
        check(mocker)
        check(mock_handler)
        check(mock_retry)
    end

    context("Connection failed due to exception") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm_client = RTMClient(token;
                               connection_retry=mock_retry,
                               ws_client_factory=x -> ws_client)
        attach(rtm_client, mock_handler)

        # `fake_rtm_start_response` uses `fake_url` as the URL we should connect to.
        @expect mocker makerequest(TypeMatcher(Any), fake_requests) Throws(HttpException())
        @expect mock_handler on_disconnect(mock_handler)
        @expect mock_retry retry(mock_retry)
        rtm_connect(rtm_client; requests=fake_requests)

        check(ws_client)
        check(mocker)
        check(mock_handler)
        check(mock_retry)
    end

    context("Set retry function to rtm_connect") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm_client = RTMClient(token;
                               connection_retry=mock_retry,
                               ws_client_factory=x -> ws_client)
        attach(rtm_client, mock_handler)

        check(ws_client)
        check(mocker)
        check(mock_handler)
        check(mock_retry)
    end

    context("Sending events") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm = DandelionSlack.RTMClient(token;
                                       connection_retry=mock_retry,
                                       ws_client_factory=x -> ws_client)
        attach(rtm, mock_handler)

        @expect ws_client send_text(ws_client, JSONMatcher(
            Dict{Any,Any}("id" => 1, "value" => test_event_1.value)))
        @expect ws_client send_text(ws_client, JSONMatcher(
            Dict{Any,Any}("id" => 2, "value" => test_event_2.value)))

        DandelionSlack.send_event(rtm, test_event_1)
        DandelionSlack.send_event(rtm, test_event_2)

        check(mock_retry)
    end

    context("Send close on user request") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm = DandelionSlack.RTMClient(token;
                                       connection_retry=mock_retry,
                                       ws_client_factory=x -> ws_client)
        attach(rtm, mock_handler)

        @expect ws_client stop(ws_client)
        close(rtm)

        check(mock_retry)
    end

    context("Increasing message id") do
        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm = DandelionSlack.RTMClient(token;
                                       connection_retry=mock_retry,
                                       ws_client_factory=x -> ws_client)
        attach(rtm, mock_handler)

        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))

        message_id_1 = DandelionSlack.send_event(rtm, FakeEvent())
        message_id_2 = DandelionSlack.send_event(rtm, FakeEvent())
        message_id_3 = DandelionSlack.send_event(rtm, FakeEvent())

        @fact message_id_1 < message_id_2 < message_id_3 --> true

        check(mock_retry)
    end

    context("Throttling of events") do
        throttling = 0.2

        throttled_client = ThrottledWSClient(ws_client, throttling)

        @expect mock_retry set_function(mock_retry, TypeMatcher(Function))

        rtm_client = RTMClient(token;
                               connection_retry=mock_retry,
                               ws_client_factory=x -> throttled_client)
        attach(rtm_client, mock_handler)

        # `fake_rtm_start_response` uses `fake_url` as the URL we should connect to.
        @expect mocker makerequest(TypeMatcher(Any), fake_requests) (ok_status, fake_rtm_start_response)
        @expect ws_client wsconnect(ws_client, Requests.URI(fake_url), rtm_client.rtm_ws)
        rtm_connect(rtm_client; requests=fake_requests)

        # Send messages and verify that they are throttled.
        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))

        n = 5
        for i = 1:n
            send_event(rtm_client, OutgoingMessageEvent("Hello", ChannelId("C0")))
        end
        sleep(0.01)
        check(ws_client)

        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        # Wait for one throttling interval and verify that we haven't sent all messages yet.
        sleep(throttling)
        check(ws_client)

        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        @expect ws_client send_text(ws_client, TypeMatcher(UTF8String))
        # Sleep for the rest of the expected time and check that we have sent all messages.
        sleep(throttling * (n - 2) + 0.05)

        check(ws_client)
        check(mocker)
        check(mock_handler)
        check(mock_retry)
    end
end