using Base.Test
import Base: tuple_type_cons, tuple_type_head, tuple_type_tail, in, ==, isless, convert,
             length, eltype, start, next, done, show
using WeakRefStrings

fastmap(f, xs...) = map(f, xs...)
@generated function fastmap(f, xs::NTuple{N}...) where N
    args = [:(xs[$j][i])  for j in 1:nfields(xs)]
    :(Base.@ntuple $N i -> f($(args...)))
end

export @NT

eltypes(::Type{Tuple{}}) = Tuple{}
eltypes(::Type{T}) where {T<:Tuple} =
    tuple_type_cons(eltype(tuple_type_head(T)), eltypes(tuple_type_tail(T)))
eltypes(::Type{T}) where {T<:NamedTuple} = map_params(eltype, T)
eltypes(::Type{T}) where T <: Pair = map_params(eltypes, T)
eltypes(::Type{T}) where T<:AbstractArray{S, N} where {S, N} = S
Base.@pure astuple(::Type{T}) where {T<:NamedTuple} = Tuple{T.parameters...}
astuple(::Type{T}) where {T<:Tuple} = T

# sizehint, making sure to return first argument
_sizehint!(a::Array{T,1}, n::Integer) where {T} = (sizehint!(a, n); a)
_sizehint!(a::AbstractArray, sz::Integer) = a

# argument selectors
left(x, y) = x
right(x, y) = y

# tuple and NamedTuple utilities

@generated function ith_all(i, xs::Tuple)
    :(Base.@ntuple $(nfields(xs)) j -> xs[j][i])
end

@generated function ith_all(i, n::NamedTuple)
    Expr(:block,
         :(@Base._inline_meta),
         Expr(:tuple, [ Expr(:ref, Expr(:., :n, Expr(:quote, fieldname(n,f))), :i) for f = 1:nfields(n) ]...))
end

@generated function foreach(f, x::Union{NamedTuple, Tuple}, xs::Union{NamedTuple, Tuple}...)
    args = [:(getfield(getfield(xs, $j), i))  for j in 1:nfields(xs)]
    :(Base.@nexprs $(nfields(x)) i -> f(getfield(x, i), $(args...)); nothing)
end

@inline foreach(f, a::Pair) = (f(a.first); f(a.second))
@inline foreach(f, a::Pair, b::Pair) = (f(a.first, b.first); f(a.second, b.second))

fieldindex(x, i::Integer) = i
fieldindex(x::NamedTuple, s::Symbol) = findfirst(x->x===s, fieldnames(x))

astuple(t::Tuple) = t

@generated function astuple(n::NamedTuple)
    Expr(:tuple, [ Expr(:., :n, Expr(:quote, fieldname(n,f))) for f = 1:nfields(n) ]...)
end

# lexicographic order product iterator

import Base: length, eltype, start, next, done

abstract type AbstractProdIterator end

struct Prod2{I1, I2} <: AbstractProdIterator
    a::I1
    b::I2
end

product(a) = a
product(a, b) = Prod2(a, b)
eltype(::Type{Prod2{I1,I2}}) where {I1,I2} = Tuple{eltype(I1), eltype(I2)}
length(p::AbstractProdIterator) = length(p.a)*length(p.b)

function start(p::AbstractProdIterator)
    s1, s2 = start(p.a), start(p.b)
    s1, s2, (done(p.a,s1) || done(p.b,s2))
end

function prod_next(p, st)
    s1, s2 = st[1], st[2]
    v2, s2 = next(p.b, s2)
    doneflag = false
    if done(p.b, s2)
        v1, s1 = next(p.a, s1)
        if !done(p.a, s1)
            s2 = start(p.b)
        else
            doneflag = true
        end
    else
        v1, _ = next(p.a, s1)
    end
    return (v1,v2), (s1,s2,doneflag)
end

next(p::Prod2, st) = prod_next(p, st)
done(p::AbstractProdIterator, st) = st[3]

struct Prod{I1, I2<:AbstractProdIterator} <: AbstractProdIterator
    a::I1
    b::I2
end

product(a, b, c...) = Prod(a, product(b, c...))
eltype(::Type{Prod{I1,I2}}) where {I1,I2} = tuple_type_cons(eltype(I1), eltype(I2))

function next(p::Prod{I1,I2}, st) where {I1,I2}
    x = prod_next(p, st)
    ((x[1][1],x[1][2]...), x[2])
end

# sortperm with counting sort

sortperm_fast(x) = sortperm(x)
sortperm_fast(x::StringVector) = sortperm(convert(StringVector{WeakRefString{UInt8}}, x))

function sortperm_fast(v::Vector{T}) where T<:Integer
    n = length(v)
    if n > 1
        min, max = extrema(v)
        rangelen = max - min + 1
        if rangelen < div(n,2)
            return sortperm_int_range(v, rangelen, min)
        end
    end
    return sortperm(v, alg=MergeSort)
end

function sortperm_int_range(x::Vector{T}, rangelen, minval) where T<:Integer
    offs = 1 - minval
    n = length(x)

    where = fill(0, rangelen+1)
    where[1] = 1
    @inbounds for i = 1:n
        where[x[i] + offs + 1] += 1
    end
    cumsum!(where, where)

    P = Vector{Int}(n)
    @inbounds for i = 1:n
        label = x[i] + offs
        wl = where[label]
        P[wl] = i
        where[label] = wl+1
    end

    return P
end

# sort the values in v[i0:i1] in place, by array `by`
Base.@noinline function sort_sub_by!(v, i0, i1, by, order, temp)
    empty!(temp)
    sort!(v, i0, i1, MergeSort, order, temp)
end

Base.@noinline function sort_sub_by!(v, i0, i1, by::PooledArray, order, temp)
    empty!(temp)
    sort!(v, i0, i1, MergeSort, order, temp)
end

Base.@noinline function sort_sub_by!(v, i0, i1, by::Vector{T}, order, temp) where T<:Integer
    min = max = by[v[i0]]
    @inbounds for i = i0+1:i1
        val = by[v[i]]
        if val < min
            min = val
        elseif val > max
            max = val
        end
    end
    rangelen = max-min+1
    n = i1-i0+1
    if rangelen <= n
        sort_int_range_sub_by!(v, i0-1, n, by, rangelen, min, temp)
    else
        empty!(temp)
        sort!(v, i0, i1, MergeSort, order, temp)
    end
    v
end

# in-place counting sort of x[ioffs+1:ioffs+n] by values in `by`
function sort_int_range_sub_by!(x, ioffs, n, by, rangelen, minval, temp)
    offs = 1 - minval

    where = fill(0, rangelen+1)
    where[1] = 1
    @inbounds for i = 1:n
        where[by[x[i+ioffs]] + offs + 1] += 1
    end
    cumsum!(where, where)

    length(temp) < n && resize!(temp, n)
    @inbounds for i = 1:n
        xi = x[i+ioffs]
        label = by[xi] + offs
        wl = where[label]
        temp[wl] = xi
        where[label] = wl+1
    end

    @inbounds for i = 1:n
        x[i+ioffs] = temp[i]
    end
    x
end

function append_n!(X, val, n)
    l = length(X)
    resize!(X, l+n)
    for i in (1:n)+l
        @inbounds X[i] = val
    end
    X
end

const _namedtuple_cache = Dict{Tuple{Vararg{Symbol}}, Type}()
function namedtuple(fields...)
    if haskey(_namedtuple_cache, fields)
        return _namedtuple_cache[fields]
    else
        NT = eval(:(@NT($(fields...))))
        _namedtuple_cache[fields] = NT
        return NT
    end
end

"""
`arrayof(T)`

Returns the type of `Columns` or `Vector` suitable to store
values of type T. Nested tuples beget nested Columns.
"""
Base.@pure function arrayof(S)
    T = strip_unionall(S)
    if T == Union{}
        Vector{Union{}}
    elseif T<:Tuple
        Columns{T, Tuple{map(arrayof, T.parameters)...}}
    elseif T<:NamedTuple
        if nfields(T) == 0
            Columns{namedtuple(), namedtuple()}
        else
            Columns{T,namedtuple(fieldnames(T)...){map(arrayof, T.parameters)...}}
        end
    elseif T<:DataValue
        DataValueArray{T.parameters[1],1}
    elseif T<:Union{String,WeakRefString}
        StringArray{T, 1}
    elseif T<:Pair
        Columns{T, Pair{map(arrayof, T.parameters)...}}
    else
        Vector{T}
    end
end

@inline strip_unionall_params(T::UnionAll) = strip_unionall_params(T.body)
@inline strip_unionall_params(T) = map(strip_unionall, T.parameters)

Base.@pure function promote_union(T::Type)
    if isa(T, Union)
        return promote_type(T.a, promote_union(T.b))
    else
        return T
    end
end

Base.@pure function strip_unionall(T)
    if isleaftype(T) || T == Union{}
        return T
    elseif isa(T, TypeVar)
        T.lb === Union{} && return strip_unionall(T.ub)
        return Any
    elseif T == Tuple
        return Any
    elseif T<:Tuple
        if any(x->x <: Vararg, T.parameters)
            # we only keep known-length tuples
            return Any
        else
            return Tuple{strip_unionall_params(T)...}
        end
    elseif T<:NamedTuple
        if isa(T, Union)
            return promote_union(T)
        else
            NT = namedtuple(fieldnames(T)...)
            return NT{strip_unionall_params(T)...}
        end
    elseif isa(T, UnionAll)
        return Any
    elseif isa(T, Union)
        return promote_union(T)
    elseif T.abstract
        return T
    else
        return Any
    end
end

Base.@pure function _promote_op(f, ::Type{S}) where S
    t = Core.Inference.return_type(f, Tuple{Base._default_type(S)})
    strip_unionall(t)
end

Base.@pure function _promote_op(f, ::Type{S}, ::Type{T}) where {S,T}
    t = Core.Inference.return_type(f, Tuple{Base._default_type(S),
                                        Base._default_type(T)})
    strip_unionall(t)
end

@inline _map(f, p::Pair) = f(p.first) => f(p.second)
@inline _map(f, args...) = map(f, args...)

# The following is not inferable, this is OK because the only place we use
# this doesn't need it.

function _map_params(f, T, S)
    (f(_tuple_type_head(T), _tuple_type_head(S)),
     _map_params(f, _tuple_type_tail(T), _tuple_type_tail(S))...)
end

_map_params(f, T::Type{Tuple{}},S::Type{Tuple{}}) = ()

map_params(f, ::Type{T}, ::Type{S}) where {T,S} = f(T,S)
map_params(f, ::Type{T}) where {T} = map_params((x,y)->f(x), T, T)
map_params(f, ::Type{T}) where T <: Pair{S1, S2} where {S1, S2} = Pair{f(S1), f(S2)}
@inline _tuple_type_head(::Type{T}) where {T<:Tuple} = Base.tuple_type_head(T)
@inline _tuple_type_tail(::Type{T}) where {T<:Tuple} = Base.tuple_type_tail(T)

#function map_params{N}(f, T::Type{T} where T<:Tuple{Vararg{Any,N}}, S::Type{S} where S<: Tuple{Vararg{Any,N}})
Base.@pure function map_params(f, ::Type{T}, ::Type{S}) where {T<:Tuple,S<:Tuple}
    if nfields(T) != nfields(S)
        MethodError(map_params, (typeof(f), T,S))
    end
    Tuple{_map_params(f, T,S)...}
end

_tuple_type_head(T::Type{NT}) where {NT<: NamedTuple} = fieldtype(NT, 1)

Base.@pure function _tuple_type_tail(T::Type{NT}) where NT<: NamedTuple
    Tuple{Base.argtail(NT.parameters...)...}
end

Base.@pure @generated function map_params(f, ::Type{T}, ::Type{S}) where {T<:NamedTuple,S<:NamedTuple}
    if fieldnames(T) != fieldnames(S)
        MethodError(map_params, (T,S))
    end
    if nfields(T) == 0 && nfields(S) == 0
        return T
    end
    NT = Expr(:macrocall, :(NamedTuples.$(Symbol("@NT"))), fieldnames(T)...)
    :($NT{_map_params(f, T, S)...})
end

@inline function concat_tup(a::NamedTuple, b::NamedTuple)
    concat_tup_type(typeof(a), typeof(b))(a..., b...)
end
@inline concat_tup(a::Tup, b::Tup) = (a..., b...)
@inline concat_tup(a::Tup, b) = (a..., b)
@inline concat_tup(a, b::Tup) = (a, b...)
@inline concat_tup(a, b) = (a..., b...)

Base.@pure function concat_tup_type(T::Type{<:Tuple}, S::Type{<:Tuple})
    Tuple{T.parameters..., S.parameters...}
end

Base.@pure function concat_tup_type(::Type{T}, ::Type{S}) where {
           T<:NamedTuple,S<:NamedTuple}
    nfields(T) == 0 && nfields(S) == 0 ?
        namedtuple() :
        namedtuple(fieldnames(T)...,
                   fieldnames(S)...){T.parameters...,
                                     S.parameters...}
end

Base.@pure function concat_tup_type(T::Type, S::Type)
    Tuple{T,S}
end

# check to see if array has shared data
# used in flush! to create a copy of the arrays
function isshared(x)
    try
        resize!(x, length(x))
        false
    catch err
        if err isa ErrorException && err.msg == "cannot resize array with shared data"
            return true
        else
            rethrow(err)
        end
    end
end

compact_mem(x) = x
compact_mem(x::StringArray{String}) = convert(StringArray{WeakRefString{UInt8}}, x)


using HackThatBase
using Base.Test

func(x,y) = x + y
args = lminfo(func, (Int, Float64))

cd(splitdir(Base.find_source_file("inference.jl"))[1])
@hack W inference
result = run_inference(W, args)
@test isa(result[2], CodeInfo)
@test result[3] == Float64


using Test

using DataFrames

using ScottishTaxBenefitModel
using .Randoms
using .UCTransition: route_to_uc_or_legacy, route_to_uc_or_legacy!
using .ModelHousehold
using .RunSettings
using .Results
using .Intermediate
using .STBIncomes
using .ExampleHelpers

sys = get_system( scotland=true )
settings = Settings()
settings.means_tested_routing = modelled_phase_in

@testset "Transition Tests on Example HHlds" begin
    examples = get_all_examples()
    for (hht,hh) in examples
        println( "on hhld '$hht'")
        lhh = deepcopy( hh )
        bus = get_benefit_units( lhh )
        nbus = size(bus)[1]
        for bno in eachindex(bus)
            @test typeof(bus[bno]) <: BenefitUnit
            intermed = make_intermediate(
                bno,
                bus[bno],
                sys.lmt.hours_limits,
                sys.age_limits,
                sys.child_limits,
                nbus )
            route = route_to_uc_or_legacy(
                settings,
                bus[bno],
                intermed )
        end
    end
end # example tests

@testset "Live Data Transitions" begin

    if IS_LOCAL
        rc = @timed begin
            num_households,total_num_people,nhh2 = FRSHouseholdGetter.initialise( DEFAULT_SETTINGS )
        end
        r = 0
        df = DataFrame( typ=["Job Seekers", "Disabled", "W/Children", "W/Housing", "Other", "Total"], legacy=zeros(6), uc=zeros(6))
        println( "num_households=$num_households, num_people=$(total_num_people)")
        @time for hhno in 1:num_households
            hh = FRSHouseholdGetter.get_household( hhno )
            bus = get_benefit_units(hh)
            r += 1
            intermed = make_intermediate(
                hh,
                sys.hours_limits,
                sys.age_limits,
                sys.child_limits )
            im = intermed.hhint
            if on_mt_benefits( hh ) && ! (im.someone_pension_age)
                # on actual data !! FIXME the HoC thing is actually Benefit Units
                route = route_to_uc_or_legacy(
                    settings,
                    bus[1],
                    im )
                col = route == uc_bens ? :uc : :legacy
                n = 1
                if im.num_job_seekers > 0
                    row = "Job Seekers"
                    n = im.num_adults
                elseif im.limited_capacity_for_work
                    row = "Disabled"
                elseif im.num_children > 0
                    row = "W/Children"
                elseif im.benefit_unit_number == 1
                    row = "W/Housing"
                else
                    row = "Other"
                end
                k = ( df.typ .== row )
                println( "adding |$row| |$col| weight $(hh.weight)" )
                # println( col )
                df[ k, col ] .+= hh.weight*n
                df[ (df.typ.=="Total"), col ] .+= hh.weight
            end
        end
        df[:, :sum] = df.uc.+df.legacy
        df[:, :pctuc] .= 100.0 .* df.uc./(df.uc.+df.legacy)
        print( df )
    end

end

@testset "Single HH Test - UC Route checks" begin
    #
    # chasing a bug when using UC route but contrib benefits
    # are being set to zero
    #
    examples = get_all_examples()
    hh = deepcopy( examples[cpl_w_2_children_hh])
    hhres = init_household_result( hh )
    intermed = make_intermediate(
        hh,
        sys.hours_limits,
        sys.age_limits,
        sys.child_limits
    )
    settings.means_tested_routing = uc_full
    bus = get_benefit_units( hh )
    bres = hhres.bus[1]
    tozero!( bres, ALL_INCOMES ...)
    head = get_head( bus[1])
    hpid = head.pid
    for i in BENEFITS
        bres.pers[hpid].income[i] = 1.0
    end

    route_to_uc_or_legacy!( hhres, settings, hh, intermed )
    println( inctostr( bres.pers[hpid].income ))
    for i in LEGACY_MTBS
        println( "on $(iname(i))" )
        @test bres.pers[hpid].income[i] == 0
    end
    for i in [CONTRIB_EMPLOYMENT_AND_SUPPORT_ALLOWANCE, CONTRIB_JOBSEEKERS_ALLOWANCE]
        println( "on $(iname(i)) testing for zero " )
        @test bres.pers[hpid].income[i] == 1.0
    end
end


using GLMakie
GLMakie.activate!()
using Makie
using JLD
##############################

wait_for_key(prompt) = (print(stdout, prompt); read(stdin, 1); nothing)

function replot_problem_results(problem, problem_result_dir)
    println("Loading data from $problem_result_dir")
    result_data = load(joinpath(problem_result_dir, "data.jld"))["result_data"]
    objs = Float64[]

    r1_x = result_data["init_solve"]["minimizer"]
    fig1 = TopOpt.TopOptProblems.Visualization.visualize(problem, topology=r1_x, undeformed_mesh_color=(:black, 1.0),
        display_supports=false)
    hidedecorations!(fig1.current_axis.x)
    save(joinpath(problem_result_dir, "r1_replot.pdf"), fig1, pt_per_unit = 1)
    push!(objs, result_data["init_solve"]["minimum"] )

    for k in keys(result_data)
        if !occursin("deflate", k)
            continue
        end
        df_result_minimizer = result_data[k]["minimizer"]
        df_result_minimum = result_data[k]["minimum"]
        push!(objs, df_result_minimum)

        fig2 = TopOpt.TopOptProblems.Visualization.visualize(problem, topology=df_result_minimizer[1:end-1],
            undeformed_mesh_color=(:black, 1.0), display_supports=false)
        hidedecorations!(fig2.current_axis.x)
        save(joinpath(problem_result_dir, "$(k)_replot.pdf"), fig2, pt_per_unit = 1)
    end

    objs ./= minimum(objs)
    obj_fig = Figure(resolution=(800,800), font="Arial")
    ax = Axis(obj_fig[1,1])
    # ax.xlabel = "deflation iterations"
    # ax.ylabel = "objectives"
    lines!(ax, 0:1:length(objs)-1, objs)
    save(joinpath(problem_result_dir, "_obj_history_replot.pdf"), obj_fig)
end

function plot_runtime(problem_result_dir)
    println("Loading data from $problem_result_dir")
    result_data = load(joinpath(problem_result_dir, "data.jld"))["result_data"]
    runtimes = Float64[]
    push!(runtimes, result_data["init_solve"]["runtime"] )
    for k in keys(result_data)
        if !occursin("deflate", k)
            continue
        end
        push!(runtimes, result_data[k]["runtime"])
    end
    println(runtimes)
    obj_fig = Figure(resolution=(800,800), font="Arial")
    ax = Axis(obj_fig[1,1])
    lines!(ax, 0:1:length(runtimes)-1, runtimes)
    save(joinpath(problem_result_dir, "_runtimes.png"), obj_fig)
    println("Initial runtime $(runtimes[1]), avr deflated runtime $(sum(runtimes[2:end])/(length(runtimes)-1))")
end


# RUN: julia -e "import Brutus; Brutus.lit(:emit_lowered)" --startup-file=no %s 2>&1 | FileCheck %s

sle_int(x, y) = Base.sle_int(x, y)
emit(sle_int, Int64, Int64)

# CHECK: module  {
# CHECK-NEXT:   func nested @"Tuple{typeof(Main.sle_int), Int64, Int64}"(%arg0: !jlir<"typeof(Main.sle_int)">, %arg1: i64, %arg2: i64) -> i1 attributes {llvm.emit_c_interface} {
# CHECK-NEXT:     %0 = cmpi sle, %arg1, %arg2 : i64
# CHECK-NEXT:     return %0 : i1
# CHECK-NEXT:   }
# CHECK-NEXT: }

# CHECK:   llvm.func @"Tuple{typeof(Main.sle_int), Int64, Int64}"(%arg0: !llvm.ptr<struct<"struct_jl_value_type", opaque>>, %arg1: i64, %arg2: i64) -> i1 attributes {llvm.emit_c_interface, sym_visibility = "nested"} {
# CHECK-NEXT:     %0 = llvm.icmp "sle" %arg1, %arg2 : i64
# CHECK-NEXT:     llvm.return %0 : i1
# CHECK-NEXT:   }
# CHECK-NEXT:   llvm.func @"_mlir_ciface_Tuple{typeof(Main.sle_int), Int64, Int64}"(%arg0: !llvm.ptr<struct<"struct_jl_value_type", opaque>>, %arg1: i64, %arg2: i64) -> i1 attributes {llvm.emit_c_interface, sym_visibility = "nested"} {
# CHECK-NEXT:     %0 = llvm.call @"Tuple{typeof(Main.sle_int), Int64, Int64}"(%arg0, %arg1, %arg2) : (!llvm.ptr<struct<"struct_jl_value_type", opaque>>, i64, i64) -> i1
# CHECK-NEXT:     llvm.return %0 : i1
# CHECK-NEXT:   }
# CHECK-NEXT: }


using Cassette

Cassette.@context CounterCtx;

const binops = (
    (:add32, Core.Intrinsics.add_float, Float32),
    (:sub32, Core.Intrinsics.sub_float, Float32),
    (:mul32, Core.Intrinsics.mul_float, Float32),
    (:div32, Core.Intrinsics.div_float, Float32),
    (:add64, Core.Intrinsics.add_float, Float64),
    (:sub64, Core.Intrinsics.sub_float, Float64),
    (:mul64, Core.Intrinsics.mul_float, Float64),
    (:div64, Core.Intrinsics.div_float, Float64),
)

const unops = (
    (:sqrt32, Core.Intrinsics.sqrt_llvm, Float32),
    (:sqrt64, Core.Intrinsics.sqrt_llvm, Float64),
)

const ops = Iterators.flatten((binops, unops)) |> collect

@eval mutable struct Counter
    $((:($(op[1]) ::Int) for op in ops)...)
    Counter() = new($((0 for _ in 1:length(ops))...))
end

for typ1 in (Float32, Float64)
    @eval function Cassette.prehook(ctx::CounterCtx,
                                    op::Core.IntrinsicFunction,
                                    ::$typ1,
                                    ::$typ1)
        $(Expr(:block,
               (map(binops) do (name, op, typ2)
                  typ1 == typ2 || return :nothing
                  quote
                    if op == $op
                       ctx.metadata.$name += 1
                       return
                    end
                  end
                end)...))
    end

    @eval function Cassette.prehook(ctx::CounterCtx,
                                    op::Core.IntrinsicFunction,
                                    ::$typ1)
        $(Expr(:block,
               (map(unops) do (name, op, typ2)
                  typ1 == typ2 || return :nothing
                  quote
                    if op == $op
                       ctx.metadata.$name += 1
                       return
                    end
                  end
                end)...))
    end
end

# Relatively inefficient, but there should be no need for performance here...

flop(c::Counter) = sum(getfield(c, field) for field in fieldnames(Counter))

import Base: ==, *, show

function Base.show(io::IO, c::Counter)
    println(io, "Flop Counter:")
    for field in fieldnames(Counter)
        println(io, " $field: $(getfield(c, field))")
    end
end

function ==(c1::Counter, c2::Counter)
    all(getfield(c1, field)==getfield(c2, field) for field in fieldnames(Counter))
end

function *(n::Int, c::Counter)
    ret = Counter()
    for field in fieldnames(Counter)
        setfield!(ret, field, n*getfield(c, field))
    end
    ret
end


export SGD, Nesterov

export LearningRatePolicy, MomentumPolicy, SGDSolverParameters

@compat abstract type LearningRatePolicy end
@compat abstract type MomentumPolicy end


###
# Tainting of values coming from random number generators
###

# Cassette.@primitive function rand(::Type{T}) where {T <: Integer, __CONTEXT__<:TraceCtx}
#     resevoir = __trace__.metadata.rands
#     if length(resevoir) >= 1
#         vv = pop!(resevoir)::T
#     else
#         vv = rand(T)
#     end
#     sv = Sym(:rand, typeof(vv))
#     return Cassette.Box(__trace__.context, vv, sv)
# end


module Implementation

using Compat

import Base: isvatuple

check(T) = (T===Tuple || T===NTuple) && throw(ArgumentError("parameters of $T are undefined"))

# Define methods for up to N tuple parameters to be evaluated in the Type domain
const N=4
ie = :()
for p=0:N
    params = ntuple(i->Symbol(:P,i), p)
    global tgetindex, tlength, concatenate

    # Accessing by a constant value
    for i=1:p
        @eval tgetindex{$(params...)}(::Type{Tuple{$(params...)}}, ::Type{Val{$i}}) = $(params[i])
    end
    @eval tgetindex{$(params...), n}(t::Type{Tuple{$(params...)}}, ::Type{Val{n}}) = throw(BoundsError(t, n))

    # Accessing by a non-constant selects the proper value with an ifelse chain
    if p == 1
        ie = :(P1)
    elseif p > 1
        ie = :(ifelse(i == $p, $(params[p]), $ie))
    end
    @eval tgetindex{$(params...)}(t::Type{Tuple{$(params...)}}, i::Int) = (1 <= i <= $p || throw(BoundsError(t, i)); $ie)
    # It'd be nice to simply define the constant `i` computation in the type
    # domain instead of relying upon dispatch, but it's not constant-folding:
    # @eval getparam{$(params...), i}(t::Type{Tuple{$(params...)}}, ::Type{Val{i}}) = (1 <= i <= $p || throw(BoundsError(t, i)); $ie)

    # concatenation
    for q=0:N
        qarams = ntuple(i->Symbol(:Q,i), q)
        @eval concatenate{$(params...), $(qarams...)}(::Type{Tuple{$(params...)}},::Type{Tuple{$(qarams...)}}) = Tuple{$(params...), $(qarams...)}
    end

    # length
    @eval tlength{$(params...)}(t::Type{Tuple{$(params...)}}) = $p
end

# Fallbacks for the general case
function tcollect(T::Type)
    check(T)
    T.parameters # just return the svec, avoids allocations
end

function tlength(T::Type)
    check(T)
    length(T.parameters)
end

tgetindex{i}(T::Type, ::Type{Val{i}}) = tgetindex(T, i)
function tgetindex(T::Type, i::Integer)
    check(T)
    L = length(T.parameters)
    if isvatuple(T) && i >= L
        T.parameters[end].parameters[1]
    else
        1 <= i <= L || throw(BoundsError(T, i))
        T.parameters[i]
    end
end

tgetindex{I<:Integer}(T::Type, is::AbstractVector{I}) = Core.svec([tgetindex(T,i) for i in is]...)

function concatenate{T<:Tuple, S<:Tuple}(::Type{T}, ::Type{S})
    check(T); check(S);
    isvatuple(T) && throw(ArgumentError("cannot concatenate the varargs tuple $T with $S"))
    Tuple{T.parameters..., S.parameters...}
end

## Allow constructing Tuples like tuples with NTuple (akin to ntuple)
@compat (::Type{NTuple}){F}(f::F, n::Integer) = Tuple{ntuple(f, n)...}

end # module


abstract type Scaler{N} end

include("linear.jl")


include("../src/transducers.jl")

using Debug, Transducers
@debug function check_compose()
    fn(x) = x^2
  @bp
  transduce(dedupe(tmap(fn)), tpush!, Any[], [1, 1, 2, 2, 3, 3])
end


# Formulas for representing and working with linear-model-type expressions
# Original by Harlan D. Harris.  Later modifications by John Myles White
# and Douglas M. Bates.

## Formulas are written as expressions and parsed by the Julia parser.
## For example :(y ~ a + b + log(c))
## In Julia the & operator is used for an interaction.  What would be written
## in R as y ~ a + b + a:b is written :(y ~ a + b + a&b) in Julia.
## The equivalent R expression, y ~ a*b, is the same in Julia

## The lhs of a one-sided formula is 'nothing'
## The rhs of a formula can be 1

type Formula
    lhs::Union(Symbol, Expr,Nothing)
    rhs::Union(Symbol, Expr, Integer)
end

type Terms
    terms::Vector
    eterms::Vector                    # evaluation terms
    factors::Matrix{Int8}             # maps terms to evaluation terms
    order::Vector{Int}                # orders of rhs terms
    response::Bool       # indicator of a response, which is eterms[1] if present
    intercept::Bool      # is there an intercept column in the model matrix?
end

type ModelFrame
    df::AbstractDataFrame
    terms::Terms
    msng::BitArray
end

type ModelMatrix{T<:Union(Float32,Float64)}
    m::Matrix{T}
    assign::Vector{Int}
end

Base.size(mm::ModelMatrix) = size(mm.m)
Base.size(mm::ModelMatrix,dim...) = size(mm.m,dim...)

function Formula(ex::Expr)
    aa = ex.args
    if aa[1] != :~
        error("Invalid formula, top-level argument must be '~'.  Check parentheses.")
    end
    if length(aa) == 2 return Formula(nothing, aa[2]) end
    Formula(aa[2], aa[3])
end

function Base.show(io::IO, f::Formula)
    print(io, string("Formula: ", f.lhs == nothing ? "" : f.lhs, " ~ ", f.rhs))
end

## Return, as a vector of symbols, the names of all the variables in
## an expression or a formula
function allvars(ex::Expr)
    if ex.head != :call error("Non-call expression encountered") end
    [[allvars(a) for a in ex.args[2:]]...]
end
allvars(f::Formula) = unique(vcat(allvars(f.rhs), allvars(f.lhs)))
allvars(sym::Symbol) = [sym]
allvars(v) = Array(Symbol,0)

const specials = Set(:+,:-,:*,:/,:&,:|,:^) # special operators in formulas

function dospecials(ex::Expr)
    if ex.head != :call error("Non-call expression encountered") end
    a1 = ex.args[1]
    if !(a1 in specials) return ex end
    excp = copy(ex)
    excp.args = vcat(a1,map(dospecials, ex.args[2:]))
    if a1 != :* return excp end
    aa = excp.args
    a2 = aa[2]
    a3 = aa[3]
    if length(aa) > 3
        excp.args = vcat(a1, aa[3:])
        a3 = dospecials(excp)
    end
    :($a2 + $a3 + $a2 & $a3)
end
dospecials(a) = a

const associative = Set(:+,:*,:&)       # associative special operators

## If the expression is a call to the function s return its arguments
## Otherwise return the expression
function ex_or_args(ex::Expr,s::Symbol)
    if ex.head != :call error("Non-call expression encountered") end
    excp = copy(ex)
    a1 = ex.args[1]
    a2 = map(condense, ex.args[2:])
    if a1 == s return a2 end
    excp.args = vcat(a1, a2)
    excp
end
ex_or_args(a,s::Symbol) = a

## Condense calls like :(+(a,+(b,c))) to :(+(a,b,c))
## Also need to work out how to distribute & over +
function condense(ex::Expr)
    if ex.head != :call error("Non-call expression encountered") end
    a1 = ex.args[1]
    if !(a1 in associative) return ex end
    excp = copy(ex)
    excp.args = vcat(a1, map(x->ex_or_args(x,a1), ex.args[2:])...)
    excp
end
condense(a) = a

getterms(ex::Expr) = (ex.head == :call && ex.args[1] == :+) ? ex.args[2:] : ex
getterms(a) = a

ord(ex::Expr) = (ex.head == :call && ex.args[1] == :&) ? length(ex.args)-1 : 1
ord(a) = 1

const nonevaluation = Set(:&,:|)        # operators constructed from other evaluations
## evaluation terms - the (filtered) arguments for :& and :|, otherwise the term itself
function evt(ex::Expr)
    if ex.head != :call error("Non-call expression encountered") end
    if !(ex.args[1] in nonevaluation) return ex end
    filter(x->!isa(x,Number), vcat(map(getterms, ex.args[2:])...))
end
evt(a) = {a}

function Terms(f::Formula)
    rhs = condense(dospecials(f.rhs))
    tt = getterms(rhs)
    if !isa(tt,AbstractArray) tt = [tt] end
    tt = tt[!(tt .== 1)]             # drop any explicit 1's
    noint = (tt .== 0) | (tt .== -1) # should also handle :(-(expr,1))
    tt = tt[!noint]
    oo = int(map(ord, tt))           # orders of interaction terms
    if !issorted(oo)                 # sort terms by increasing order
        pp = sortperm(oo)
        tt = tt[pp]
        oo = oo[pp]
    end
    etrms = map(evt, tt)
    haslhs = f.lhs != nothing
    if haslhs
        unshift!(etrms, {f.lhs})
        unshift!(oo, 1)
    end
    ev = unique(vcat(etrms...))
    facs = int8(hcat(map(x->(s=Set(x...);map(t->int8(t in s), ev)),etrms)...))
    Terms(tt, ev, facs, oo, haslhs, !any(noint))
end

## Default NA handler.  Others can be added as keyword arguments
function na_omit(df::DataFrame)
    cc = complete_cases(df)
    df[cc,:], cc
end

## Trim the pool field of da to only those levels that occur in the refs
function dropUnusedLevels!(da::PooledDataArray)
    rr = da.refs
    uu = unique(rr)
    if length(uu) == length(da.pool) return da end
    T = eltype(rr)
    su = sort!(uu)
    dict = Dict(su, one(T):convert(T,length(uu)))
    da.refs = map(x->dict[x], rr)
    da.pool = da.pool[uu]
    da
end
dropUnusedLevels!(x) = x

function ModelFrame(f::Formula, d::AbstractDataFrame)
    trms = Terms(f)
    df,msng = na_omit(DataFrame(map(x->with(d,x),trms.eterms)))
    names!(df, map(string, trms.eterms))
    for c in df dropUnusedLevels!(c[2]) end
    ModelFrame(df, trms, msng)
end
ModelFrame(ex::Expr, d::AbstractDataFrame) = ModelFrame(Formula(ex), d)

function model_response(mf::ModelFrame)
    mf.terms.response || error("Model formula one-sided")
    array(mf.df[bool(mf.terms.factors[:,1])][:,1])
end

function contr_treatment(n::Integer, contrasts::Bool, sparse::Bool, base::Integer)
    if n < 2 error("not enought degrees of freedom to define contrasts") end
    contr = sparse ? speye(n) : eye(n) .== 1.
    if !contrasts return contr end
    if !(1 <= base <= n) error("base = $base is not allowed for n = $n") end
    contr[:,vcat(1:(base-1),(base+1):end)]
end
contr_treatment(n::Integer,contrasts::Bool,sparse::Bool) = contr_treatment(n,contrasts,sparse,1)
contr_treatment(n::Integer,contrasts::Bool) = contr_treatment(n,contrasts,false,1)
contr_treatment(n::Integer) = contr_treatment(n,true,false,1)
cols(v::PooledDataVector) = contr_treatment(length(v.pool))[v.refs,:]
cols(v::DataVector) = reshape(float64(v.data), (length(v),1))

function isfe(ex::Expr)                 # true for fixed-effects terms
    if ex.head != :call error("Non-call expression encountered") end
    ex.args[1] != :|
end
isfe(a) = true

## Expand the columns in an interaction term
function expandcols(trm::Vector)
    if length(trm) == 1 return float64(trm[1]) end
    if length(trm) == 2
        a = float64(trm[1])
        b = float64(trm[2])
        nca = size(a,2)
        ncb = size(b,2)
        return hcat([a[:,i].*b[:,j] for i in 1:nca, j in 1:ncb]...)
    end
    error("code for 3rd and higher order interactions not yet written")
end

nc(trm::Vector) = *([size(x,2) for x in trm]...)

function ModelMatrix(mf::ModelFrame)
    trms = mf.terms
    aa = {{ones(size(mf.df,1),int(trms.intercept))}}
    asgn = zeros(Int, (int(trms.intercept)))
    fetrms = bool(map(isfe, trms.terms))
    if trms.response unshift!(fetrms,false) end
    ff = trms.factors[:,fetrms]
    ## need to be cautious here to avoid evaluating cols for a factor with many levels
    ## if the factor doesn't occur in the fetrms
    rows = vec(bool(sum(ff,[2])))
    ff = ff[rows,:]
    cc = [cols(x[2]) for x in mf.df[:,rows]]
    for j in 1:size(ff,2)
        trm = cc[bool(ff[:,j])]
        push!(aa, trm)
        asgn = vcat(asgn, fill(j, nc(trm)))
    end
    ModelMatrix{Float64}(hcat([expandcols(t) for t in aa]...), asgn)
end

function coefnames(fr::ModelFrame)
    if fr.terms.intercept
        vnames = UTF8String["(Intercept)"]
    else
        vnames = UTF8String[]
    end
    # Need to only include active levels
    for term in fr.terms.terms
        if isa(fr.df[term], PooledDataArray)
            for lev in levels(fr.df[term])[2:end]
                push!(vnames, string(term, " - ", lev))
            end
        else
            push!(vnames, string(term))
        end
    end
    return vnames
end

# Expand dummy variables and equations
## function model_matrix(mf::ModelFrame)
##     ex = mf.formula.rhs[1]
##     # BUG: complete_cases doesn't preserve grouped columns
##     df = mf.df#[complete_cases(mf.df),1:ncol(mf.df)]
##     rdf = df[mf.y_indexes]
##     mdf = expand(ex, df)

##     # TODO: Convert to Array{Float64} in a cleaner way
##     rnames = colnames(rdf)
##     mnames = colnames(mdf)
##     r = Array(Float64,nrow(rdf),ncol(rdf))
##     m = Array(Float64,nrow(mdf),ncol(mdf))
##     for i = 1:nrow(rdf)
##       for j = 1:ncol(rdf)
##         r[i,j] = float(rdf[i,j])
##       end
##       for j = 1:ncol(mdf)
##         m[i,j] = float(mdf[i,j])
##       end
##     end

##     include_intercept = true
##     if include_intercept
##       m = hcat(ones(nrow(mdf)), m)
##       unshift!(mnames, "(Intercept)")
##     end

##     ModelMatrix(m, r, mnames, rnames)
##     ## mnames = {}
##     ## rnames = {}
##     ## for c in 1:ncol(rdf)
##     ##   r = hcat(r, float(rdf[c]))
##     ##   push!(rnames, colnames(rdf)[c])
##     ## end
##     ## for c in 1:ncol(mdf)
##     ##   m = hcat(m, mdf[c])
##     ##   push!(mnames, colnames(mdf)[c])
##     ## end
## end

## model_matrix(f::Formula, d::AbstractDataFrame) = model_matrix(ModelFrame(f, d))
## model_matrix(ex::Expr, d::AbstractDataFrame) = model_matrix(ModelFrame(Formula(ex), d))

## # TODO: Make a more general version of these functions
## # TODO: Be able to extract information about each column name
## function interaction_design_matrix(a::AbstractDataFrame, b::AbstractDataFrame)
##    cols = {}
##    col_names = Array(ASCIIString,0)
##    for i in 1:ncol(a)
##        for j in 1:ncol(b)
##           push!(cols, DataArray(a[:,i] .* b[:,j]))
##           push!(col_names, string(colnames(a)[i],"&",colnames(b)[j]))
##        end
##    end
##    DataFrame(cols, col_names)
## end

## function interaction_design_matrix(a::AbstractDataFrame, b::AbstractDataFrame, c::AbstractDataFrame)
##    cols = {}
##    col_names = Array(ASCIIString,0)
##    for i in 1:ncol(a)
##        for j in 1:ncol(b)
##            for k in 1:ncol(b)
##               push!(cols, DataArray(a[:,i] .* b[:,j] .* c[:,k]))
##               push!(col_names, string(colnames(a)[i],"&",colnames(b)[j],"&",colnames(c)[k]))
##            end
##        end
##    end
##    DataFrame(cols, col_names)
## end

## # Temporary: Manually describe the interactions needed for DataFrame Array.
## function all_interactions(dfs::Array{Any,1})
##     d = DataFrame()
##     if length(dfs) == 2
##       combos = ([1,2],)
##     elseif length(dfs) == 3
##       combos = ([1,2], [1,3], [2,3], [1,2,3])
##     else
##       error("interactions with more than 3 terms not implemented (yet)")
##     end
##     for combo in combos
##        if length(combo) == 2
##          a = interaction_design_matrix(dfs[combo[1]],dfs[combo[2]])
##        elseif length(combo) == 3
##          a = interaction_design_matrix(dfs[combo[1]],dfs[combo[2]],dfs[combo[3]])
##        end
##        d = insert!(d, a)
##     end
##     return d
## end

## # string(Expr) now quotes, which we don't want. This hacks around that, stealing
## # from print_to_string
## function formula_string(ex::Expr)
##     s = memio(0, false)
##     Base.show_unquoted(s, ex)
##     takebuf_string(s)
## end

## #
## # The main expression to DataFrame expansion function.
## # Returns a DataFrame.
## #

## function expand(ex::Expr, df::AbstractDataFrame)
##     f = eval(ex.args[1])
##     if method_exists(f, (FormulaExpander, Vector{Any}, DataFrame))
##         # These are specialized expander functions (+, *, &, etc.)
##         f(FormulaExpander(), ex.args[2:end], df)
##     else
##         # Everything else is called recursively:
##         expand(with(df, ex), formula_string(ex), df)
##     end
## end

## function expand(s::Symbol, df::AbstractDataFrame)
##     expand(with(df, s), string(s), df)
## end

## # TODO: make this array{symbol}?
## function expand(args::Array{Any}, df::AbstractDataFrame)
##     [expand(x, df) for x in args]
## end

## function expand(x, name::ByteString, df::AbstractDataFrame)
##     # If this happens to be a column group, then expand each and concatenate
##     if is_group(df, name)
##       preds = get_groups(df)[name]
##       dfs = [expand(symbol(x), df) for x in preds]
##       return cbind(dfs...)
##     end
##     # This is the default for expansion: put it right in to a DataFrame.
##     DataFrame({x}, [name])
## end

## #
## # Methods for expansion of specific data types
## #

## # Expand a PooledDataVector into a matrix of indicators for each dummy variable
## # TODO: account for NAs?
## function expand(poolcol::PooledDataVector, colname::ByteString, df::AbstractDataFrame)
##     newcol = {DataArray([convert(Float64,x)::Float64 for x in (poolcol.refs .== i)]) for i in 2:length(poolcol.pool)}
##     newcolname = [string(colname, ":", x) for x in poolcol.pool[2:length(poolcol.pool)]]
##     DataFrame(newcol, convert(Vector{ByteString}, newcolname))
## end

## #
## # Methods for Formula expansion
## #
## type FormulaExpander; end # This is an indictor type.

## function +(::FormulaExpander, args::Vector{Any}, df::AbstractDataFrame)
##     d = DataFrame()
##     for a in args
##         d = insert!(d, expand(a, df))
##     end
##     d
## end
## function (&)(::FormulaExpander, args::Vector{Any}, df::AbstractDataFrame)
##     interaction_design_matrix(expand(args[1], df), expand(args[2], df))
## end
## function *(::FormulaExpander, args::Vector{Any}, df::AbstractDataFrame)
##     d = +(FormulaExpander(), args, df)
##     d = insert!(d, all_interactions(expand(args, df)))
##     d
## end


##############################################################################
#
# Specialization of AbstractMPCCModel based on automatic differentiation
# Based on AbastractNLPModel:
# https://github.com/JuliaSmoothOptimizers/NLPModels.jl
#
# TODO:
# - export
# - doc
# - list of functions not adapted: jth_hprod, jth_hprod!, ghjvprod, ghjvprod!, jth_con, jth_congrad, jth_congrad!, jth_sparse_congrad,
# - nnzj for G and H
#
##############################################################################

# Methods to be overridden in other packages.
"""
    f = obj(nlp, x)
Evaluate ``f(x)``, the objective function of `mpcc` at `x`.
"""
obj(::AbstractMPCCModel, ::AbstractVector) =
  throw(NotImplementedError("obj"))

"""
    g = grad(nlp, x)
Evaluate ``∇f(x)``, the gradient of the objective function at `x`.
"""
function grad(nlp::AbstractMPCCModel, x::AbstractVector)
  g = similar(x)
  return grad!(nlp, x, g)
end

"""
    g = grad!(nlp, x, g)
Evaluate ``∇f(x)``, the gradient of the objective function at `x` in place.
"""
grad!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector) =
  throw(NotImplementedError("grad!"))

"""
    c = cons(nlp, x)
Evaluate ``c(x)``, the constraints at `x`.
"""
function cons(nlp::AbstractMPCCModel, x::AbstractVector)
  c = similar(x, nlp.meta.ncon)
  return cons!(nlp, x, c)
end

"""
    c = cons!(nlp, x, c)
    Return the vector of the constraints
    lc <= c(x) <= uc,
    lccG <= G(x),
    lccH <= H(x)
"""
function cons!(nlp :: AbstractMPCCModel, x :: AbstractVector, c :: AbstractVector)
  if nlp.meta.ncc > 0
   G, H = consG(nlp, x), consH(nlp, x)
  else
   G, H = Float64[], Float64[]
  end

  if nlp.meta.ncon > 0
   vnl = cons_nl(nlp, x)
  else
   vnl = Float64[]
  end

  c = vcat(vnl, G, H)
  return c
end

"""
    c = viol(nlp, x)
Evaluate ``c(x)``, the constraints at `x`.
"""
function viol(nlp::AbstractMPCCModel, x::AbstractVector)
  c = similar(x, nlp.meta.nvar+nlp.meta.ncon+3*nlp.meta.ncc)
  return viol!(nlp, x, c)
end

"""
    c = viol!(nlp, x, c)
    Return the vector of the constraints
    lx <= x <= ux
    lc <= c(x) <= uc,
    lccG <= G(x),
    lccH <= H(x),
    G(x) .* H(x) <= 0
"""
function viol!(mod :: AbstractMPCCModel, x :: AbstractVector, c :: AbstractVector)
  n, ncon, ncc = mod.meta.nvar, mod.meta.ncon, mod.meta.ncc
  if ncc > 0
   cG, cH = consG(mod, x), consH(mod, x)

   c[n+ncon+1:n+ncon+ncc]     = max.(mod.meta.lccG - cG, 0.0)
   c[n+ncon+ncc+1:n+ncon+2*ncc] = max.(mod.meta.lccH - cH, 0.0)
   c[n+ncon+2*ncc+1:n+ncon+3*ncc] = max.(cH.*cG, 0.0)
  end

  if mod.meta.ncon > 0
   cx = cons_nl(mod, x)
   c[n+1:n+ncon] = max.(max.(mod.meta.lcon - cx, 0.0),max.(cx - mod.meta.ucon, 0.0))
  end

  c[1:n] = max.(max.(mod.meta.lvar - x, 0.0),max.(x - mod.meta.uvar, 0.0))
  return c
end

"""
Evaluate ``c(x)``, the constraints at `x`.
"""
function cons_nl(nlp :: AbstractMPCCModel, x :: AbstractVector)
 c = similar(x, nlp.meta.ncon)
 return cons_nl!(nlp, x, c)
end

cons_nl!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector) =
  throw(NotImplementedError("cons_nl!"))

"""
Evaluate ``G(x)``, the constraints at `x`.
"""
function consG(nlp :: AbstractMPCCModel, x :: AbstractVector)
 c = similar(x, nlp.meta.ncc)
 return consG!(nlp, x, c)
end

consG!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector) =
       throw(NotImplementedError("consG!"))

"""
Evaluate ``G(x)``, the constraints at `x`.
"""
function consH(nlp :: AbstractMPCCModel, x :: AbstractVector)
 c = similar(x, nlp.meta.ncc)
 return consH!(nlp, x, c)
end

consH!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector) =
              throw(NotImplementedError("consH!"))

jth_con(::AbstractMPCCModel, ::AbstractVector, ::Integer) =
  throw(NotImplementedError("jth_con"))

function jth_congrad(nlp::AbstractMPCCModel, x::AbstractVector, j::Integer)
  g = Vector{eltype(x)}(undef, nlp.meta.nvar)
  return jth_congrad!(nlp, x, j, g)
end

jth_congrad!(::AbstractMPCCModel, ::AbstractVector, ::Integer, ::AbstractVector) =
  throw(NotImplementedError("jth_congrad!"))

jth_sparse_congrad(::AbstractMPCCModel, ::AbstractVector, ::Integer) =
  throw(NotImplementedError("jth_sparse_congrad"))

"""
    f, c = objcons(nlp, x)
Evaluate ``f(x)`` and ``c(x)`` at `x`.
"""
function objcons(nlp :: AbstractMPCCModel, x :: AbstractVector)
  c = similar(x, nlp.meta.ncon)
  return objcons!(nlp, x, c)
end

"""
    f = objcons!(nlp, x, c)
Evaluate ``f(x)`` and ``c(x)`` at `x`. `c` is overwritten with the value of ``c(x)``.
"""
function objcons!(nlp :: AbstractMPCCModel, x :: AbstractVector, c :: AbstractVector)
  f = obj(nlp, x)
  nlp.meta.ncon > 0 && cons!(nlp, x, c)
  return f, c
end

"""
    f, g = objgrad(nlp, x)
Evaluate ``f(x)`` and ``∇f(x)`` at `x`.
"""
function objgrad(nlp :: AbstractMPCCModel, x :: AbstractVector)
  g = similar(x)
  return objgrad!(nlp, x, g)
end

"""
    f, g = objgrad!(nlp, x, g)
Evaluate ``f(x)`` and ``∇f(x)`` at `x`. `g` is overwritten with the
value of ``∇f(x)``.
"""
function objgrad!(nlp :: AbstractMPCCModel, x :: AbstractVector, g :: AbstractVector)
  f = obj(nlp, x)
  grad!(nlp, x, g)
  return f, g
end

"""
    (rows,cols) = jac_structure(nlp)
Return the structure of the constraint's Jacobian in sparse coordinate format.
"""
function jac_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nnzj+2 * nlp.meta.nvar * nlp.meta.ncc)
  cols = Vector{Int}(undef, nlp.meta.nnzj+2 * nlp.meta.nvar * nlp.meta.ncc)
  return jac_structure!(nlp, rows, cols)
end

"""
    jac_structure!(nlp, rows, cols)
Return the structure of the constraint's Jacobian in sparse coordinate format in place.
"""
function jac_structure!(nlp :: AbstractMPCCModel, rows :: AbstractVector{<:Integer}, cols :: AbstractVector{<:Integer})
 m, n = nlp.meta.ncon+2*nlp.meta.ncc, nlp.meta.nvar
 I = ((i,j) for i = 1:m, j = 1:n)
 rows[1 : n*m] .= getindex.(I, 1)[:]
 cols[1 : n*m] .= getindex.(I, 2)[:]
 return rows, cols
end
#I = findall(!iszero, Hx)
#return (getindex.(I, 1), getindex.(I, 2), Hx[I])

"""
    vals = jac_coord!(nlp, x, vals)
Evaluate ``∇c(x)``, the constraint's Jacobian at `x` in sparse coordinate format,
rewriting `vals`.
"""
function jac_coord!(nlp :: AbstractMPCCModel, x :: AbstractVector, vals ::AbstractVector)
 m, n = nlp.meta.ncon+2*nlp.meta.ncc, nlp.meta.nvar
 Jx = vcat(jac_nl(nlp, x),jacG(nlp, x),jacH(nlp, x))
 vals[1 : n*m] .= Jx[:]
 return vals
end
"""
    vals = jac_coord(nlp, x)
Evaluate ``∇c(x)``, the constraint's Jacobian at `x` in sparse coordinate format.
"""
function jac_coord(nlp :: AbstractMPCCModel, x :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzj+2*nlp.meta.nvar * nlp.meta.ncc)
  return jac_coord!(nlp, x, vals)
end

"""
    Jx = jac(nlp, x)
Evaluate ``[∇c(x), -∇G(x), -∇H(x)]v``, the constraint's Jacobian at `x` as a sparse matrix.
"""
function jac(nlp :: AbstractMPCCModel, x :: AbstractVector)
  rows, cols = jac_structure(nlp)
  vals = jac_coord(nlp, x)
  return sparse(rows, cols, vals, nlp.meta.ncon+2*nlp.meta.ncc, nlp.meta.nvar)
end

"""
    (rows,cols) = jac_nl_structure(nlp)
Return the structure of the constraint's Jacobian in sparse coordinate format.
"""
function jac_nl_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nnzj)
  cols = Vector{Int}(undef, nlp.meta.nnzj)
  return jac_nl_structure!(nlp, rows, cols)
end

"""
    jac_nl_structure!(nlp, rows, cols)
Return the structure of the constraint's Jacobian in sparse coordinate format in place.
"""
jac_nl_structure!(:: AbstractMPCCModel, :: AbstractVector{<:Integer}, :: AbstractVector{<:Integer}) = throw(NotImplementedError("jac_nl_structure!"))

"""
    vals = jac_nl_coord!(nlp, x, vals)
Evaluate ``∇c(x)``, the constraint's Jacobian at `x` in sparse coordinate format,
rewriting `vals`.
"""
jac_nl_coord!(:: AbstractMPCCModel, :: AbstractVector, ::AbstractVector) = throw(NotImplementedError("jac_nl_coord!"))

"""
    vals = jac_nl_coord(nlp, x)
Evaluate ``∇c(x)``, the constraint's Jacobian at `x` in sparse coordinate format.
"""
function jac_nl_coord(nlp :: AbstractMPCCModel, x :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzj)
  return jac_nl_coord!(nlp, x, vals)
end

"""
    Jx = jac_nl(nlp, x)
Evaluate ``∇c(x)``, the constraint's Jacobian at `x` as a sparse matrix.
"""
function jac_nl(nlp :: AbstractMPCCModel, x :: AbstractVector)
  rows, cols = jac_nl_structure(nlp)
  vals = jac_nl_coord(nlp, x)
  return sparse(rows, cols, vals, nlp.meta.ncon, nlp.meta.nvar)
end

"""
    (rows,cols) = jacG_structure(nlp)
Return the structure of the constraint's Jacobian in sparse coordinate format.
"""
function jacG_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nvar * nlp.meta.ncc)
  cols = Vector{Int}(undef, nlp.meta.nvar * nlp.meta.ncc)
  return jacG_structure!(nlp, rows, cols)
end

"""
    jacG_structure!(nlp, rows, cols)
Return the structure of the constraint's Jacobian in sparse coordinate format in place.
"""
jacG_structure!(:: AbstractMPCCModel, :: AbstractVector{<:Integer}, :: AbstractVector{<:Integer}) = throw(NotImplementedError("jacG_structure!"))

"""
    vals = jacG_coord!(nlp, x, vals)
Evaluate ``∇G(x)``, the constraint's Jacobian at `x` in sparse coordinate format,
rewriting `vals`.
"""
jacG_coord!(:: AbstractMPCCModel, :: AbstractVector, ::AbstractVector) = throw(NotImplementedError("jacG_coord!"))

"""
    vals = jacG_coord(nlp, x)
Evaluate ``∇G(x)``, the constraint's Jacobian at `x` in sparse coordinate format.
"""
function jacG_coord(nlp :: AbstractMPCCModel, x :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nvar * nlp.meta.ncc)
  return jacG_coord!(nlp, x, vals)
end
"""
    Jx = jacG(nlp, x)
Evaluate ``∇G(x)``, the constraint's Jacobian at `x` as a sparse matrix.
"""
function jacG(nlp :: AbstractMPCCModel, x :: AbstractVector)
  rows, cols = jacG_structure(nlp)
  vals = jacG_coord(nlp, x)
  return sparse(rows, cols, vals, nlp.meta.ncc, nlp.meta.nvar)
end

"""
    (rows,cols) = jacH_structure(nlp)
Return the structure of the constraint's Jacobian in sparse coordinate format.
"""
function jacH_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nvar * nlp.meta.ncc)
  cols = Vector{Int}(undef, nlp.meta.nvar * nlp.meta.ncc)
  return jacH_structure!(nlp, rows, cols)
end

"""
    jacH_structure!(nlp, rows, cols)
Return the structure of the constraint's Jacobian in sparse coordinate format in place.
"""
jacH_structure!(:: AbstractMPCCModel, :: AbstractVector{<:Integer}, :: AbstractVector{<:Integer}) = throw(NotImplementedError("jacH_structure!"))

"""
    vals = jacH_coord!(nlp, x, vals)
Evaluate ``∇H(x)``, the constraint's Jacobian at `x` in sparse coordinate format,
rewriting `vals`.
"""
jacH_coord!(:: AbstractMPCCModel, :: AbstractVector, ::AbstractVector) = throw(NotImplementedError("jacH_coord!"))

"""
    vals = jacH_coord(nlp, x)
Evaluate ``∇H(x)``, the constraint's Jacobian at `x` in sparse coordinate format.
"""
function jacH_coord(nlp :: AbstractMPCCModel, x :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nvar * nlp.meta.ncc)
  return jacH_coord!(nlp, x, vals)
end
"""
    Jx = jacH(nlp, x)
Evaluate ``∇H(x)``, the constraint's Jacobian at `x` as a sparse matrix.
"""
function jacH(nlp :: AbstractMPCCModel, x :: AbstractVector)
  rows, cols = jacH_structure(nlp)
  vals = jacH_coord(nlp, x)
  return sparse(rows, cols, vals, nlp.meta.ncc, nlp.meta.nvar)
end

"""
    Jv = jprod(nlp, x, v)
Evaluate ``[∇c(x), -∇G(x), -∇H(x)]v``, the Jacobian-vector product at `x`.
"""
function jprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jv = similar(v, nlp.meta.ncon+2*nlp.meta.ncc)
  return jprod!(nlp, x, v, Jv)
end

"""
    Jv = jnlprod(nlp, x, v)
Evaluate ``∇c(x)v``, the Jacobian-vector product at `x`.
"""
function jnlprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jv = similar(v, nlp.meta.ncon)
  return jnlprod!(nlp, x, v, Jv)
end

"""
    JGv = jGprod(nlp, x, v)
Evaluate ``∇G(x)v``, the Jacobian-vector product at `x`.
"""
function jGprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jv = similar(v, nlp.meta.ncc)
  return jGprod!(nlp, x, v, Jv)
end

"""
    JHv = jprod(nlp, x, v)
Evaluate ``∇H(x)v``, the Jacobian-vector product at `x`.
"""
function jHprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jv = similar(v, nlp.meta.ncc)
  return jHprod!(nlp, x, v, Jv)
end

"""
    Jv = jprod!(nlp, x, v, Jv)
Evaluate ``[∇c(x), -∇G(x), -∇H(x)]v``, the Jacobian-vector product at `x` in place.
"""
function jprod!(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector)
  ncon, ncc = nlp.meta.ncon, nlp.meta.ncc
  if ncon > 0
      Jv[1:ncon] = jnlprod(nlp, x, v)
  end
  if ncc > 0
      Jv[ncon+1:ncon+ncc]       = - jGprod(nlp, x, v)
      Jv[ncon+ncc+1:ncon+2*ncc] = - jHprod(nlp, x, v)
  end
  return Jv
end

"""
 Jv = jnlprod!(nlp, x, v, Jv)
Evaluate ``∇c(x)v``, the Jacobian-vector product at `x` in place.
"""
jnlprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
    throw(NotImplementedError("jnlprod!"))

"""
  JGv = jGprod!(nlp, x, v, Jv)
Evaluate ``∇G(x)v``, the Jacobian-vector product at `x` in place.
"""
jGprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
    throw(NotImplementedError("jGprod!"))

"""
  JHv = jHprod!(nlp, x, v, Jv)
Evaluate ``∇H(x)v``, the Jacobian-vector product at `x` in place.
"""
jHprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
      throw(NotImplementedError("jHprod!"))

"""
    Jtv = jtprod(nlp, x, v, Jtv)
Evaluate ``∇c(x)^Tv``, the transposed-Jacobian-vector product at `x`.
"""
function jtprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jtv = similar(x)
  return jtprod!(nlp, x, v, Jtv)
end
"""
    Jtv = jnltprod(nlp, x, v, Jtv)
Evaluate ``[∇c(x), -∇G(x), -∇H(x)]v``, the transposed-Jacobian-vector product at `x`.
"""
function jnltprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jtv = similar(x)
  return jnltprod!(nlp, x, v, Jtv)
end
"""
    JGtv = jGtprod(nlp, x, v, Jtv)
Evaluate ``∇G(x)^Tv``, the transposed-Jacobian-vector product at `x`.
"""
function jGtprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jtv = similar(x)
  return jGtprod!(nlp, x, v, Jtv)
end
"""
    JHtv = jtprod(nlp, x, v, Jtv)
Evaluate ``∇H(x)^Tv``, the transposed-Jacobian-vector product at `x`.
"""
function jHtprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector)
  Jtv = similar(x)
  return jHtprod!(nlp, x, v, Jtv)
end

"""
    Jtv = jtprod!(nlp, x, v, Jtv)
Evaluate ``[∇c(x), -∇G(x), -∇H(x)]^Tv``, the transposed-Jacobian-vector product at `x` in place.
"""
function jtprod!(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector)
  ncon, ncc = nlp.meta.ncon, nlp.meta.ncc
  Jv = zeros(nlp.meta.nvar)
  if ncon > 0
     Jv += jnltprod(nlp, x, v[1:ncon])
  end
  if ncc > 0
      Jv += - jGtprod(nlp, x, v[ncon+1:ncon+ncc])
      Jv += - jHtprod(nlp, x, v[ncon+ncc+1:ncon+2*ncc])
  end
  return Jv
end

"""
 Jtv = jnltprod!(nlp, x, v, Jtv)
Evaluate ``∇c(x)^Tv``, the transposed-Jacobian-vector product at `x` in place.
"""
jnltprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
    throw(NotImplementedError("jnltprod!"))
"""
 JGtv = jGtprod!(nlp, x, v, Jtv)
 Evaluate ``∇G(x)^Tv``, the transposed-Jacobian-vector product at `x` in place.
"""
jGtprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
    throw(NotImplementedError("jGtprod!"))
"""
JHtv = jHtprod!(nlp, x, v, Jtv)
Evaluate ``∇H(x)^Tv``, the transposed-Jacobian-vector product at `x` in place.
"""
jHtprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
      throw(NotImplementedError("jHtprod!"))

"""
    J = jac_op(nlp, x)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`.
"""
function jac_op(nlp :: AbstractMPCCModel, x :: AbstractVector)
  prod = @closure v -> jprod(nlp, x, v)
  ctprod = @closure v -> jtprod(nlp, x, v)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

"""
    J = jac_op!(nlp, x, Jv, Jtv)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`. The values `Jv` and `Jtv` are used as preallocated storage for the
operations.
"""
function jac_op!(nlp :: AbstractMPCCModel, x :: AbstractVector,
                 Jv :: AbstractVector, Jtv :: AbstractVector)
  prod = @closure v -> jprod!(nlp, x, v, Jv)
  ctprod = @closure v -> jtprod!(nlp, x, v, Jtv)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

"""
    J = jacG_op(nlp, x)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`.
"""
function jacG_op(nlp :: AbstractMPCCModel, x :: AbstractVector)
  prod = @closure v -> jGprod(nlp, x, v)
  ctprod = @closure v -> jGtprod(nlp, x, v)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

"""
    J = jacG_op!(nlp, x, Jv, Jtv)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`. The values `Jv` and `Jtv` are used as preallocated storage for the
operations.
"""
function jacG_op!(nlp :: AbstractMPCCModel, x :: AbstractVector,
                 Jv :: AbstractVector, Jtv :: AbstractVector)
  prod = @closure v -> jGprod!(nlp, x, v, Jv)
  ctprod = @closure v -> jGtprod!(nlp, x, v, Jtv)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

"""
    J = jacH_op(nlp, x)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`.
"""
function jacH_op(nlp :: AbstractMPCCModel, x :: AbstractVector)
  prod = @closure v -> jHprod(nlp, x, v)
  ctprod = @closure v -> jHtprod(nlp, x, v)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

"""
    J = jacH_op!(nlp, x, Jv, Jtv)
Return the Jacobian at `x` as a linear operator.
The resulting object may be used as if it were a matrix, e.g., `J * v` or
`J' * v`. The values `Jv` and `Jtv` are used as preallocated storage for the
operations.
"""
function jacH_op!(nlp :: AbstractMPCCModel, x :: AbstractVector,
                 Jv :: AbstractVector, Jtv :: AbstractVector)
  prod   = @closure v -> jHprod!(nlp, x, v, Jv)
  ctprod = @closure v -> jHtprod!(nlp, x, v, Jtv)
  return LinearOperator{eltype(x)}(nlp.meta.ncon, nlp.meta.nvar,
                                   false, false, prod, ctprod, ctprod)
end

function jth_hprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, j::Integer)
  hv = Vector{eltype(x)}(undef, nlp.meta.nvar)
  return jth_hprod!(nlp, x, v, j, hv)
end

jth_hprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::Integer, ::AbstractVector) =
  throw(NotImplementedError("jth_hprod!"))

function ghjvprod(nlp::AbstractMPCCModel, x::AbstractVector, g::AbstractVector, v::AbstractVector)
  gHv = Vector{eltype(x)}(undef, nlp.meta.ncon)
  return ghjvprod!(nlp, x, g, v, gHv)
end

ghjvprod!(::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
  throw(NotImplementedError("ghjvprod!"))

"""
    (rows,cols) = hess_structure(nlp)
Return the structure of the Lagrangian Hessian in sparse coordinate format.
"""
function hess_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nnzh)
  cols = Vector{Int}(undef, nlp.meta.nnzh)
  return hess_structure!(nlp, rows, cols)
end

"""
    hess_structure!(nlp, rows, cols)
Return the structure of the Lagrangian Hessian in sparse coordinate format in place.
"""
hess_structure!(:: AbstractMPCCModel, ::AbstractVector{<: Integer}, ::AbstractVector{<: Integer}) = throw(NotImplementedError("hess_structure!"))

"""
    vals = hess_coord!(nlp, x, vals; obj_weight=1.0)
Evaluate the objective Hessian at `x` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
OBJECTIVE_HESSIAN, rewriting `vals`.
Only the lower triangle is returned.
"""
function hess_coord!(nlp :: AbstractMPCCModel, x :: AbstractVector, vals :: AbstractVector; obj_weight :: Real=1.0)
  return hess_coord!(nlp, x, zeros(nlp.meta.ncon), vals, obj_weight=obj_weight)
end

"""
    vals = hess_coord!(nlp, x, y, vals; obj_weight=1.0)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN, rewriting `vals`.
Only the lower triangle is returned.
"""
hess_coord!(nlp:: AbstractMPCCModel, :: AbstractVector, :: AbstractVector, ::AbstractVector; obj_weight :: Real=1.0) = throw(NotImplementedError("hess_coord!"))

"""
    vals = hess_coord(nlp, x; obj_weight=1.0)
Evaluate the objective Hessian at `x` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
OBJECTIVE_HESSIAN.
Only the lower triangle is returned.
"""
function hess_coord(nlp :: AbstractMPCCModel, x :: AbstractVector; obj_weight::Real=one(eltype(x)))
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzh)
  return hess_coord!(nlp, x, vals; obj_weight=obj_weight)
end

"""
    vals = hess_coord(nlp, x, y; obj_weight=1.0)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hess_coord(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector; obj_weight::Real=one(eltype(x)))
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzh)
  return hess_coord!(nlp, x, y, vals; obj_weight=obj_weight)
end

"""
    Hx = hess(nlp, x; obj_weight=1.0)
Evaluate the objective Hessian at `x` as a sparse matrix,
with objective function scaled by `obj_weight`, i.e.,
OBJECTIVE_HESSIAN.
Only the lower triangle is returned.
"""
function hess(nlp::AbstractMPCCModel, x::AbstractVector; obj_weight::Real=one(eltype(x)))
  rows, cols = hess_structure(nlp)
  vals = hess_coord(nlp, x, obj_weight=obj_weight)
  return tril(sparse(rows, cols, vals, nlp.meta.nvar, nlp.meta.nvar))
end

"""
    Hx = hess(nlp, x, y; obj_weight=1.0)
Evaluate the Lagrangian Hessian at `(x,y)` as a sparse matrix,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hess(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector; obj_weight::Real=one(eltype(x)))
  rows, cols = hess_structure(nlp)
  vals = hess_coord(nlp, x, y, obj_weight=obj_weight)
  return tril(sparse(rows, cols, vals, nlp.meta.nvar, nlp.meta.nvar))
end

"""
    (rows,cols) = hessG_structure(nlp)
Return the structure of the Lagrangian Hessian in sparse coordinate format.
"""
function hessG_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nnzh)
  cols = Vector{Int}(undef, nlp.meta.nnzh)
  return hessG_structure!(nlp, rows, cols)
end

"""
    hessG_structure!(nlp, rows, cols)
Return the structure of the Lagrangian Hessian in sparse coordinate format in place.
"""
hessG_structure!(:: AbstractMPCCModel, ::AbstractVector{<: Integer}, ::AbstractVector{<: Integer}) = throw(NotImplementedError("hessG_structure!"))

"""
    vals = hess_coord!(nlp, x, y, vals)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN, rewriting `vals`.
Only the lower triangle is returned.
"""
hessG_coord!(nlp:: AbstractMPCCModel, :: AbstractVector, :: AbstractVector, ::AbstractVector) = throw(NotImplementedError("hessG_coord!"))

"""
    vals = hessG_coord(nlp, x, y)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hessG_coord(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzh)
  return hessG_coord!(nlp, x, y, vals)
end

"""
    Hx = hessG(nlp, x, y)
Evaluate the Lagrangian Hessian at `(x,y)` as a sparse matrix,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hessG(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector)
  rows, cols = hessG_structure(nlp)
  vals = hessG_coord(nlp, x, y)
  return tril(sparse(rows, cols, vals, nlp.meta.nvar, nlp.meta.nvar))
end

"""
    (rows,cols) = hessG_structure(nlp)
Return the structure of the Lagrangian Hessian in sparse coordinate format.
"""
function hessH_structure(nlp :: AbstractMPCCModel)
  rows = Vector{Int}(undef, nlp.meta.nnzh)
  cols = Vector{Int}(undef, nlp.meta.nnzh)
  return hessH_structure!(nlp, rows, cols)
end

"""
    hessH_structure!(nlp, rows, cols)
Return the structure of the Lagrangian Hessian in sparse coordinate format in place.
"""
hessH_structure!(:: AbstractMPCCModel, ::AbstractVector{<: Integer}, ::AbstractVector{<: Integer}) = throw(NotImplementedError("hessH_structure!"))

"""
    vals = hessH_coord!(nlp, x, y, vals)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN, rewriting `vals`.
Only the lower triangle is returned.
"""
hessH_coord!(nlp:: AbstractMPCCModel, :: AbstractVector, :: AbstractVector, ::AbstractVector) = throw(NotImplementedError("hessH_coord!"))

"""
    vals = hessG_coord(nlp, x, y)
Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hessH_coord(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector)
  vals = Vector{eltype(x)}(undef, nlp.meta.nnzh)
  return hessH_coord!(nlp, x, y, vals)
end

"""
    Hx = hessH(nlp, x, y)
Evaluate the Lagrangian Hessian at `(x,y)` as a sparse matrix,
with objective function scaled by `obj_weight`, i.e.,
LAGRANGIAN_HESSIAN.
Only the lower triangle is returned.
"""
function hessH(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector)
  rows, cols = hessH_structure(nlp)
  vals = hessH_coord(nlp, x, y)
  return tril(sparse(rows, cols, vals, nlp.meta.nvar, nlp.meta.nvar))
end

"""
    Hv = hprod(nlp, x, v; obj_weight=1.0)
Evaluate the product of the objective Hessian at `x` with the vector `v`,
with objective function scaled by `obj_weight`, where the objective Hessian is
OBJECTIVE_HESSIAN.
"""
function hprod(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector; obj_weight::Real=one(eltype(x)))
  Hv = similar(x)
  return hprod!(nlp, x, v, Hv; obj_weight=obj_weight)
end

"""
    Hv = hprod(nlp, x, y, v; obj_weight=1.0)
Evaluate the product of the Lagrangian Hessian at `(x,y)` with the vector `v`,
with objective function scaled by `obj_weight`, where the Lagrangian Hessian is
LAGRANGIAN_HESSIAN.
"""
function hprod(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector, v::AbstractVector; obj_weight::Real=one(eltype(x)))
  Hv = similar(x)
  return hprod!(nlp, x, y, v, Hv; obj_weight=obj_weight)
end

"""
    Hv = hGprod(nlp, x, y, v)
Evaluate the product of the G Hessian at `(x,y)` with the vector `v`.
"""
function hGprod(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector, v::AbstractVector)
  Hv = similar(x)
  return hGprod!(nlp, x, y, v, Hv)
end

"""
    Hv = hHprod(nlp, x, y, v)
Evaluate the product of the G Hessian at `(x,y)` with the vector `v`.
"""
function hHprod(nlp::AbstractMPCCModel, x::AbstractVector, y::AbstractVector, v::AbstractVector)
  Hv = similar(x)
  return hHprod!(nlp, x, y, v, Hv)
end

"""
    Hv = hprod!(nlp, x, v, Hv; obj_weight=1.0)
Evaluate the product of the objective Hessian at `x` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the objective Hessian is
OBJECTIVE_HESSIAN.
"""
function hprod!(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, Hv::AbstractVector; obj_weight :: Real=1.0)
  return hprod!(nlp, x, zeros(nlp.meta.ncon), v, Hv, obj_weight=obj_weight)
end

"""
    Hv = hprod!(nlp, x, y, v, Hv; obj_weight=1.0)
Evaluate the product of the Lagrangian Hessian at `(x,y)` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the Lagrangian Hessian is
LAGRANGIAN_HESSIAN.
"""
hprod!(nlp::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector, ::AbstractVector; obj_weight :: Real=1.0) =
  throw(NotImplementedError("hprod!"))

"""
 Hv = hGprod!(nlp, x, v, Hv; obj_weight=1.0)
Evaluate the product of the objective Hessian at `x` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the objective Hessian is
OBJECTIVE_HESSIAN.
"""
function hGprod!(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, Hv::AbstractVector)
 return hGprod!(nlp, x, zeros(nlp.meta.ncon), v, Hv)
end

"""
Hv = hGprod!(nlp, x, y, v, Hv; obj_weight=1.0)
Evaluate the product of the Lagrangian Hessian at `(x,y)` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the Lagrangian Hessian is
LAGRANGIAN_HESSIAN.
"""
hGprod!(nlp::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
throw(NotImplementedError("hGprod!"))

"""
 Hv = hHprod!(nlp, x, v, Hv; obj_weight=1.0)
Evaluate the product of the objective Hessian at `x` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the objective Hessian is
OBJECTIVE_HESSIAN.
"""
function hHprod!(nlp::AbstractMPCCModel, x::AbstractVector, v::AbstractVector, Hv::AbstractVector)
 return hHprod!(nlp, x, zeros(nlp.meta.ncon), v, Hv)
end

"""
Hv = hHprod!(nlp, x, y, v, Hv; obj_weight=1.0)
Evaluate the product of the Lagrangian Hessian at `(x,y)` with the vector `v` in
place, with objective function scaled by `obj_weight`, where the Lagrangian Hessian is
LAGRANGIAN_HESSIAN.
"""
hHprod!(nlp::AbstractMPCCModel, ::AbstractVector, ::AbstractVector, ::AbstractVector, ::AbstractVector) =
throw(NotImplementedError("hHprod!"))

"""
    H = hess_op(nlp, x; obj_weight=1.0)
Return the objective Hessian at `x` with objective function scaled by
`obj_weight` as a linear operator. The resulting object may be used as if it were a
matrix, e.g., `H * v`. The linear operator H represents
OBJECTIVE_HESSIAN.
"""
function hess_op(nlp :: AbstractMPCCModel, x :: AbstractVector; obj_weight::Real=one(eltype(x)))
  prod = @closure v -> hprod(nlp, x, v; obj_weight=obj_weight)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hess_op(nlp, x, y; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator. The resulting object may be used as if it were a
matrix, e.g., `H * v`. The linear operator H represents
LAGRANGIAN_HESSIAN.
"""
function hess_op(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector; obj_weight::Real=one(eltype(x)))
  prod = @closure v -> hprod(nlp, x, y, v; obj_weight=obj_weight)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hessG_op(nlp, x, y; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator. The resulting object may be used as if it were a
matrix, e.g., `H * v`. The linear operator H represents
LAGRANGIAN_HESSIAN.
"""
function hessG_op(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector)
  prod = @closure v -> hGprod(nlp, x, y, v)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hessH_op(nlp, x, y; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator. The resulting object may be used as if it were a
matrix, e.g., `H * v`. The linear operator H represents
LAGRANGIAN_HESSIAN.
"""
function hessH_op(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector)
  prod = @closure v -> hHprod(nlp, x, y, v)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hess_op!(nlp, x, Hv; obj_weight=1.0)
Return the objective Hessian at `x` with objective function scaled by
`obj_weight` as a linear operator, and storing the result on `Hv`. The resulting
object may be used as if it were a matrix, e.g., `w = H * v`. The vector `Hv` is
used as preallocated storage for the operation.  The linear operator H
represents
OBJECTIVE_HESSIAN.
"""
function hess_op!(nlp :: AbstractMPCCModel, x :: AbstractVector, Hv :: AbstractVector; obj_weight::Real=one(eltype(x)))
  prod = @closure v -> hprod!(nlp, x, v, Hv; obj_weight=obj_weight)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hess_op!(nlp, x, y, Hv; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator, and storing the result on `Hv`. The resulting
object may be used as if it were a matrix, e.g., `w = H * v`. The vector `Hv` is
used as preallocated storage for the operation.  The linear operator H
represents
LAGRANGIAN_HESSIAN.
"""
function hess_op!(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector, Hv :: AbstractVector; obj_weight::Real=one(eltype(x)))
  prod = @closure v -> hprod!(nlp, x, y, v, Hv; obj_weight=obj_weight)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hessG_op!(nlp, x, y, Hv; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator, and storing the result on `Hv`. The resulting
object may be used as if it were a matrix, e.g., `w = H * v`. The vector `Hv` is
used as preallocated storage for the operation.  The linear operator H
represents
LAGRANGIAN_HESSIAN.
"""
function hessG_op!(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector, Hv :: AbstractVector)
  prod = @closure v -> hGprod!(nlp, x, y, v, Hv)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end

"""
    H = hessH_op!(nlp, x, y, Hv; obj_weight=1.0)
Return the Lagrangian Hessian at `(x,y)` with objective function scaled by
`obj_weight` as a linear operator, and storing the result on `Hv`. The resulting
object may be used as if it were a matrix, e.g., `w = H * v`. The vector `Hv` is
used as preallocated storage for the operation.  The linear operator H
represents
LAGRANGIAN_HESSIAN.
"""
function hessH_op!(nlp :: AbstractMPCCModel, x :: AbstractVector, y :: AbstractVector, Hv :: AbstractVector)
  prod = @closure v -> hHprod!(nlp, x, y, v, Hv)
  return LinearOperator{eltype(x)}(nlp.meta.nvar, nlp.meta.nvar,
                                   true, true, prod, prod, prod)
end


module Gcode

import Base.write

export G, write, move, meander

type G
    output::IO
    echo::Bool
    header::String
    footer::String
    position::Dict
    absolute::Bool
    feedrate::Real
    flavor::String
    view::Bool
end

function G(;output::String = "",
            echo::Bool = false,
            header::String = "",
            footer::String = "",
            position::Dict = Dict{String, Float64}(),
            absolute::Bool = true,
            feedrate::Real = 0,
            flavor::String = "Marlin",
            view::Bool = false)

    io = (output != "") ? open(output, "w") : STDOUT
    if header != ""
        write(io, readall(open(header, "r")))
    end

    return G(io,echo,header,footer,position,absolute,feedrate,flavor,view)
end

function format_args(;args...)
    str = ""
    for (key, val) in args
        str *= uppercase(string(" ", key, val))
    end
    return str
end

function write(g::G, x)
    str = string(x)
    write(g.output, str*"\n")
    if g.echo; println(str); end
end

function set_home!(g::G; args...):
    """ Set the current position to the given position without moving.

Example
-------
>>> # set the current position to X=0, Y=0
>>> g.set_home(0, 0)

"""
    write(g, "G92"*format_args(;args...))

    updateposition!(g, args)
end

function relative!(g::G):
    """ Enter relative movement mode, in general this method should not be
used, most methods handle it automatically.

"""
    write(g, "G91")
    g.absolute = false
end

function absolute!(g::G):
    """ Enter absolute movement mode, in general this method should not be
used, most methods handle it automatically.

"""
    write(g,"G90")
    g.absolute = true
end

function update_position!(g::G; args...)
    for (axis, delta) in args
        axis_str = string(axis)
        if haskey(g.position, axis_str)
            if !g.absolute
                g.position[axis_str] += delta
            else
                g.position[string(axis)] = delta
            end
        else
            merge!(g.position, [axis_str=>delta])
        end
    end
end

function feedrate!(g::G, rate)
    """ Set the feed rate (tool head speed) in mm/s

Parameters
----------
rate : float
The speed to move the tool head in mm/s.

"""
    write(g, rate)
end

function dwell(g::G, time)
    """ Pause code executions for the given amount of time.

Parameters
----------
time : float
Time in seconds to pause code execution.

"""
    write(g, "G4 P"*string(time))
end

function teardown(g::G)
    """ Close the outfile file after writing the footer if opened. This
method must be called once after all commands.

"""
    if g.footer != ""
        write(g.output, readall(open(g.footer, "r")))
    end
end

function home(self):
    """ Move the tool head to the home position (X=0, Y=0).
"""
    self.abs_move(x=0, y=0)
end

function move(g::G ; args...)
    """ Move the tool head to the given position. This method operates in
relative mode unless a manual call to `absolute` was given previously.
If an absolute movement is desired, the `abs_move` method is
recommended instead.

Examples
--------
>>> # move the tool head 10 mm in x and 10 mm in y
>>> g.move(x=10, y=10)
>>> # the x and y keywords may be omitted:
>>> g.move(10, 10)

>>> # move the A axis up 20 mm
>>> g.move(A=20)

"""
    update_position!(g;args...)

    args = format_args(;args...)
    write(g, "G1 " * args)
end

function abs_move(g::G ; args...)
    """ Same as `move` method, but positions are interpreted as absolute.
"""
    if !g.absolute
        absolute!(g)
        move(g;args...)
        relative!(g)
    else #already absolute
        move(g;args...)
    end
end

#function arc(self, direction='CW', radius='auto', helix_dim=None, helix_len=0,
#        **kwargs):
#    """ Arc to the given point with the given radius and in the given
#direction. If helix_dim and helix_len are specified then the tool head
#will also perform a linear movement through the given dimension while
#completing the arc.

#Parameters
#----------
#points : floats
#Must specify two points as kwargs, e.g. X=5, Y=5
#direction : str (either 'CW' or 'CCW') (default: 'CW')
#The direction to execute the arc in.
#radius : 'auto' or float (default: 'auto')
#The radius of the arc. A negative value will select the longer of
#the two possible arc segments. If auto is selected the radius will
#be set to half the linear distance to desired point.
#helix_dim : str or None (default: None)
#The linear dimension to complete the helix through
#helix_len : float
#The length to move in the linear helix dimension.

#Examples
#--------
#>>> # arc 10 mm up in y and 10 mm over in x with a radius of 20.
#>>> g.arc(x-10, y=10, radius=20)

#>>> # move 10 mm up on the A axis, arcing through y with a radius of 20
#>>> g.arc(A=10, y=0, radius=20)

#>>> # arc through x and y while moving linearly on axis A
#>>> g.arc(x=10, y=10, radius=50, helix_dim='A', helix_len=5)

#"""
#    msg = 'Must specify point with 2 dimensions as keywords, e.g. X=0, Y=10'
#    if len(kwargs) != 2:
#        raise RuntimeError(msg)
#    dimensions = [k.lower() for k in kwargs.keys()]
#    if 'x' in dimensions and 'y' in dimensions:
#        plane_selector = 'G17' # XY plane
#        axis = helix_dim
#    elif 'x' in dimensions:
#        plane_selector = 'G18' # XZ plane
#        dimensions.remove('x')
#        axis = dimensions[0].upper()
#    elif 'y' in dimensions:
#        plane_selector = 'G19' # YZ plane
#        dimensions.remove('y')
#        axis = dimensions[0].upper()
#    else:
#        raise RuntimeError(msg)

#    if direction == 'CW':
#        command = 'G2'
#    elif direction == 'CCW':
#        command = 'G3'

#    values = kwargs.values()
#    if self.movement_mode == 'relative':
#        dist = math.sqrt(values[0] ** 2 + values[1] ** 2)
#    else:
#        k = kwargs.keys()
#        cp = self.current_position
#        dist = math.sqrt(
#            (cp[k[0]] - values[0]) ** 2 + (cp[k[1]] - values[1]) ** 2
#        )
#    if radius == 'auto':
#        radius = dist / 2.0
#    elif radius < dist / 2.0:
#        msg = 'Radius {} to small for distance {}'.format(radius, dist)
#        raise RuntimeError(msg)

#    if axis is not None:
#        self.write('G16 X Y {}'.format(axis)) # coordinate axis assignment
#    self.write(plane_selector)
#    args = ' '.join([(k.upper() + str(v)) for k, v in kwargs.items()])
#    if helix_dim is None:
#        self.write('{} {} R{}'.format(command, args, radius))
#    else:
#        self.write('{} {} R{} G1 {}{}'.format(command, args, radius,
#                                              helix_dim.upper(), helix_len))
#        kwargs[helix_dim] = helix_len

#    self._update_current_position(**kwargs)
#end

#function abs_arc(self, direction='CW', radius='auto', **kwargs):
#    """ Same as `arc` method, but positions are interpreted as absolute.
#"""
#    self.absolute()
#    self.arc(direction=direction, radius=radius, **kwargs)
#    self.relative()
#end

function rect(g, x, y; direction::String="CW", start::String="LL"):
    """ Trace a rectangle with the given width and height.

Parameters
----------
x : float
The width of the rectangle in the x dimension.
y : float
The heigh of the rectangle in the y dimension.
direction : str (either 'CW' or 'CCW') (default: 'CW')
Which direction to complete the rectangle in.
start : str (either 'LL', 'UL', 'LR', 'UR') (default: 'LL')
The start of the rectangle - L/U = lower/upper, L/R = left/right
This assumes an origin in the lower left.

Examples
--------
>>> # trace a 10x10 clockwise square, starting in the lower left corner
>>> g.rect(10, 10)

>>> # 1x5 counterclockwise rect starting in the upper right corner
>>> g.rect(1, 5, direction='CCW', start='UR')

"""
    start = uppercase(start)
    direction = uppercase(direction)
    if direction == "CW"
        if start == "LL"
            move(g, y=y)
            move(g, x=x)
            move(g, y=-y)
            move(g, x=-x)
        elseif start == "UL"
            move(g, x=x)
            move(g, y=-y)
            move(g, x=-x)
            move(g, y=y)
        elseif start == "UR"
            move(g, y=-y)
            move(g, x=-x)
            move(g, y=y)
            move(g, x=x)
        elseif start == "LR"
            move(g, x=-x)
            move(g, y=y)
            move(g, x=x)
            move(g, y=-y)
        end
    elseif direction == "CCW"
        if start == "LL"
            move(g, x=x)
            move(g, y=y)
            move(g, x=-x)
            move(g, y=-y)
        elseif start == "UL"
            move(g, y=-y)
            move(g, x=x)
            move(g, y=y)
            move(g, x=-x)
        elseif start == "UR"
            move(g, x=-x)
            move(g, y=-y)
            move(g, x=x)
            move(g, y=y)
        elseif start == "LR"
            move(g, y=y)
            move(g, x=-x)
            move(g, y=-y)
            move(g, x=x)
        end
    end
end

function meander(g::G, x, y, spacing; start="LL", orientation="x", tail=false):
    """ Infill a rectangle with a square wave meandering pattern. If the
relevant dimension is not a multiple of the spacing, the spacing will
be tweaked to ensure the dimensions work out.

Parameters
----------
x : float
The width of the rectangle in the x dimension.
y : float
The heigh of the rectangle in the y dimension.
spacing : float
The space between parallel meander lines.
start : str (either 'LL', 'UL', 'LR', 'UR') (default: 'LL')
The start of the meander - L/U = lower/upper, L/R = left/right
This assumes an origin in the lower left.
orientation : str ('x' or 'y') (default: 'x')

Examples
--------
>>> # meander through a 10x10 sqaure with a spacing of 1mm starting in
>>> # the lower left.
>>> g.meander(10, 10, 1)

>>> # 3x5 meander with a spacing of 1 and with parallel lines through y
>>> g.meander(3, 5, spacing=1, orientation='y')

>>> # 10x5 meander with a spacing of 2 starting in the upper right.
>>> g.meander(10, 5, 2, start='UR')

"""
    start = uppercase(start)
    if start == "UL"
        x, y = x, -y
    elseif start == "UR"
        x, y = -x, -y
    elseif start == "LR"
        x, y = -x, y
    end

    # Major axis is the parallel lines, minor axis is the jog.
    orientation = lowercase(orientation)
    if orientation == "x"
        major, major_name = x, :x
        minor, minor_name = y, :y
    else
        major, major_name = y, :y
        minor, minor_name = x, :x
    end

    if minor > 0
        passes = ceil(minor / spacing)
    else
        passes = abs(floor(minor / spacing))
    end
    actual_spacing = minor / passes
    if abs(actual_spacing) != spacing
        msg = "meander spacing updated from $spacing to $actual_spacing"
        warn(msg)
        write(g, ";"*msg)
    end
    spacing = actual_spacing
    sign = 1
    relative!(g)
    for _ in 1:passes
        move(g;{major_name => (sign * major)}...)
        move(g;{minor_name => spacing}...)
        sign = -1 * sign
    end
    if tail == false
        move(g;{major_name => (sign * major)}...)
    end
end

end # module