module Traces

import ..JSON: json

using ..Utilities, ..TraceUtilities

export Scatter #, Bar, Box, HeatMap, Histogram

# Export only utiliity types that are needed in constructors
# export Font, Title, ColorBar, Gradient, Marker, Line, Error

mutable struct Scatter <: Trace
    type::ChartType
    attributes::Attributes
    stackgroup::String
    groupnorm::GroupNorm
    stackgaps::StackGaps
    mode::Mode
    hoveron::HoverOn
    line::Line
    connectgaps::Bool
    cliponaxis::Bool
    fill::Fill
    fillcolor::Color
    textposition::TextPosition
    textfont::Font
    error_x::Error
    error_y::Error

    function Scatter(;
        stackgroup::String = "",
        groupnorm::String = "",
        stackgaps::String = "infer zero",
        mode::String = "lines+markers",
        hoveron::String = "points",
        line::Line = Line(),
        connectgaps::Bool = false,
        cliponaxis::Bool = true,
        fill::String = "none",
        fillcolor::String = "#444",
        textposition::String = "middle center",
        textfont::Font = Font(),
        error_x::Error = Error(),
        error_y::Error = Error(),kwargs...)
        return new(
            ChartType("scatter"),
            Attributes(;kwargs...),
            stackgroup,
            GroupNorm(groupnorm),
            StackGaps(stackgaps),
            Mode(mode),
            HoverOn(hoveron),
            line,
            connectgaps,
            cliponaxis,
            Fill(fill),
            Color(fillcolor),
            TextPosition(textposition),
            textfont,
            error_x,
            error_y)
    end
end
default["scatter"] = Scatter()

# struct Bar <: Trace
#     type::ChartType
#     attributes::Attributes
#     cliponaxis::Bool
#     textposition::TextPosition
#     textfont::Font
#     insidetextfont::Font
#     outsidetextfont::Font
#     constraintext::ConstrainText
#     base::Float64
#     offset::Union{Float64,Vector{Float64}}
#     width::Union{Size{Float64},Vector{Size{Float64}}}
#     offsetgroup::String
#     alignmentgroup::String
#     error_x::Error
#     error_y::Error

#     function Bar(;
#         cliponaxis::Bool = true,
#         textposition::String = "none",
#         textfont::Font = Font(),

#         insidetextfont::Font = Font(),
#         outsidetextfont::Font = Font(),
#         constraintext::String = "both",
#         base::Float64,
#         offset::Union{Float64,Vector{Float64}},
#         width::Union{Size{Float64},Vector{Size{Float64}}},
#         offsetgroup::String,
#         alignmentgroup::String,
#         error_x::Error,
#         error_y::Error,

#         error_x::Error = Error(),
#         error_y::Error = Error(),kwargs...)
#         return new(
#             ChartType("bar"),
#             Attributes(;kwargs...),
#             cliponaxis,
#             TextPosition(textposition),
#             textfont,
#             )
#     end
# end

# function Base.display(::Figures.Display, scatter::Scatter)
#     println("Try to plot the scatter trace.")
#     s = diff(default["Scatter"],scatter)
#     println(json(s))
#     # id = isempty(Figures.current[]) ? p.plot.divid : Figures.current[]
#     # Pages.broadcast("script","""Plotly.newPlot("$(id)",$(p.plot.data),$(p.plot.layout),$(PlotlyJS.JSON.json(p.options)));""")
#     # println("""Plotly.newPlot("$(id)",$(p.plot.data),$(p.plot.layout),$(PlotlyJS.JSON.json(p.options)));""")
# end

# struct Box <: Trace
#     option::TraceOptions
#     whiskerwidth::RangeValue{Float64,(0,1),(true,true)}
#     notched::Bool
#     notchwidth::RangeValue{Float64,(0,.5),(true,true)}
#     boxpoints::BoxPoints
#     boxmean::BoxMean
#     jitter::RangeValue{Float64,(0,1),(true,true)}
#     pointpos::RangeValue{Float64,(-2,2),(true,true)}
#     hoveron::HoverOn
#     line::Line
#     fillcolor::String
#     width::Union{Size{Float64},Vector{Size{Float64}}}
#     offsetgroup::String
#     alignmentgroup::String
# end

# struct HeatMap <: Trace
#     options::TraceOptions
#     z::Vector{Float64}
#     transpose::Bool
#     xtype::CoordinateType
#     ytype::CoordinateType
#     zsmooth::ZSmooth
#     connectgaps::Bool
#     xgap::Size{Int}
#     ygap::Size{Int}
#     zhoverformat::Union{String,Vector{String}}
#     zauto::Bool
#     zmin::Float64
#     zmax::Float64
#     zmid::Float64
#     colorscale::ColorScale
#     autocolorscale::Bool
#     reversescale::Bool
#     showscale::Bool
#     colorbar::ColorBar
# end

# struct Histogram <: Trace
#     options::TraceOptions
#     histfunc::HistFunc
#     histnorm::Union{HistNorm,Nothing}
#     cumulative::Cumulative
#     nbinsx::Size{Int}
#     xbins::Bins
#     nbinsy::Size{Int}
#     ybins::Bins
#     autobinx::Bool
#     autobiny::Bool
#     offsetgroup::String
#     alignmentgroup::String
#     error_x::Error
#     error_y::Error
# end

end


module Command

using Random
using Base.Iterators

sequence(l::Int) = String(rand(['A','C','G','T'], l))
function line(s; col=80)
    n = length(s) ÷ col
    l =  ( s[(i-1)*col+1:(i*col)] for i in 1:n )

    length(s) % col == 0 && return join(l,"\n")
    return join(l,"\n") * "\n" * s[(n*col)+1:end]
end

function usage()
    println("usage: julia script/ancestors.jl -n [int] -l [int]")
    exit(2)
end

function main(args)
    N,L = 100, Int(1e5)
    i = 1
    while i ≤ length(args)
        if args[i] == "-N"
            N = parse(Int,args[i+1])
            i += 2
            continue
        end
        if args[i] == "-L"
            L = parse(Int,args[i+1])
            i += 2
            continue
        end
        break
    end

    for i in 1:N
        s = sequence(L)
        name = "isolate_$(i)"
        write(stdout, ">$(name)", '\n')
        write(stdout, line(s), '\n')
    end
    flush(stdout)
end

if abspath(PROGRAM_FILE) == @__FILE__
    main(ARGS)
end

end


# PATH uses the Float64 value 1e20 to represent +infinity.
const INFINITY = 1e20

###
### License.h
###

function c_api_License_SetString(license::String)
    return ccall((:License_SetString, PATH_SOLVER), Cint, (Ptr{Cchar},), license)
end

###
### Output_Interface.h
###

const c_api_Output_Log     = Cint(1 << 0)
const c_api_Output_Status  = Cint(1 << 1)
const c_api_Output_Listing = Cint(1 << 2)

mutable struct OutputInterface
    output_data::Ptr{Cvoid}
    print::Ptr{Cvoid}
    flush::Ptr{Cvoid}
end

function _c_flush(data::Ptr{Cvoid}, mode::Cint)
    io = unsafe_pointer_to_objref(data)::IO
    flush(io)
    return
end

function _c_print(data::Ptr{Cvoid}, mode::Cint, msg::Ptr{Cchar})
    if (
        mode & c_api_Output_Log == c_api_Output_Log ||
        # TODO(odow): decide whether to print the Output_Status. It has a lot of
        # information...
        # mode & c_api_Output_Status == c_api_Output_Status ||
        mode & c_api_Output_Listing == c_api_Output_Listing
    )
        io = unsafe_pointer_to_objref(data)::IO
        print(io, unsafe_string(msg))
    end
    return
end

function OutputInterface(io)
    _C_PRINT = @cfunction(_c_print, Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cchar}))
    _C_FLUSH = @cfunction(_c_flush, Cvoid, (Ptr{Cvoid}, Cint))
    return OutputInterface(pointer_from_objref(io), _C_PRINT, _C_FLUSH)
end

function c_api_Output_SetInterface(o::OutputInterface)
    return ccall(
        (:Output_SetInterface, PATH_SOLVER), Cvoid, (Ref{OutputInterface},), o
    )
end

###
### Options.h
###

mutable struct Options
    ptr::Ptr{Cvoid}
    function Options(ptr::Ptr{Cvoid})
        o = new(ptr)
        finalizer(c_api_Options_Destroy, o)
        return o
    end
end

Base.cconvert(::Type{Ptr{Cvoid}}, x::Options) = x
Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::Options) = x.ptr

function c_api_Options_Create()
    ptr = ccall((:Options_Create, PATH_SOLVER), Ptr{Cvoid}, ())
    return Options(ptr)
end

function c_api_Options_Destroy(o::Options)
    return ccall((:Options_Destroy, PATH_SOLVER), Cvoid, (Ptr{Cvoid},), o)
end

function c_api_Options_Default(o::Options)
    return ccall((:Options_Default, PATH_SOLVER), Cvoid, (Ptr{Cvoid},), o)
end

function c_api_Options_Display(o::Options)
    return ccall((:Options_Display, PATH_SOLVER), Cvoid, (Ptr{Cvoid},), o)
end

function c_api_Options_Read(o::Options, filename::String)
    return ccall(
        (:Options_Read, PATH_SOLVER), Cvoid, (Ptr{Cvoid}, Ptr{Cchar}), o, filename
    )
end

function c_api_Path_AddOptions(o::Options)
    return ccall((:Path_AddOptions, PATH_SOLVER), Cvoid, (Ptr{Cvoid},), o)
end

###
### MCP_Interface.h
###

mutable struct InterfaceData
    n::Cint
    nnz::Cint
    F::Function
    J::Function
    lb::Vector{Cdouble}
    ub::Vector{Cdouble}
    z::Vector{Cdouble}
    variable_names::Vector{String}
    constraint_names::Vector{String}
end

function _c_problem_size(
    id_ptr::Ptr{Cvoid}, n_ptr::Ptr{Cint}, nnz_ptr::Ptr{Cint}
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    n = unsafe_wrap(Array{Cint}, n_ptr, 1)
    n[1] = id_data.n
    nnz = unsafe_wrap(Array{Cint}, nnz_ptr, 1)
    nnz[1] = id_data.nnz
    return
end

function _c_bounds(
    id_ptr::Ptr{Cvoid},
    n::Cint,
    z_ptr::Ptr{Cdouble},
    lb_ptr::Ptr{Cdouble},
    ub_ptr::Ptr{Cdouble},
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    z = unsafe_wrap(Array{Cdouble}, z_ptr, n)
    lb = unsafe_wrap(Array{Cdouble}, lb_ptr, n)
    ub = unsafe_wrap(Array{Cdouble}, ub_ptr, n)
    for i = 1:n
        z[i] = id_data.z[i]
        lb[i] = id_data.lb[i]
        ub[i] = id_data.ub[i]
    end
    return
end

function _c_function_evaluation(
    id_ptr::Ptr{Cvoid}, n::Cint, x_ptr::Ptr{Cdouble}, f_ptr::Ptr{Cdouble}
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    x = unsafe_wrap(Array{Cdouble}, x_ptr, n)
    f = unsafe_wrap(Array{Cdouble}, f_ptr, n)
    err = id_data.F(n, x, f)
    return err
end

function _c_jacobian_evaluation(
    id_ptr::Ptr{Cvoid},
    n::Cint,
    x_ptr::Ptr{Cdouble},
    wantf::Cint,
    f_ptr::Ptr{Cdouble},
    nnz_ptr::Ptr{Cint},
    col_ptr::Ptr{Cint},
    len_ptr::Ptr{Cint},
    row_ptr::Ptr{Cint},
    data_ptr::Ptr{Cdouble}
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    x = unsafe_wrap(Array{Cdouble}, x_ptr, n)
    err = Cint(0)
    if wantf > 0
        f = unsafe_wrap(Array{Cdouble}, f_ptr, n)
        err += id_data.F(n, x, f)
    end
    nnz = unsafe_wrap(Array{Cint}, nnz_ptr, 1)
    col = unsafe_wrap(Array{Cint}, col_ptr, n)
    len = unsafe_wrap(Array{Cint}, len_ptr, n)
    row = unsafe_wrap(Array{Cint}, row_ptr, nnz[1])
    data = unsafe_wrap(Array{Cdouble}, data_ptr, nnz[1])
    err += id_data.J(n, nnz[1], x, col, len, row, data)
    nnz[1] = sum(len)
    return err
end

function _c_variable_name(
    id_ptr::Ptr{Cvoid},
    i::Cint,
    buf_ptr::Ptr{UInt8},
    buf_size::Cint
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    name = collect(codeunits(id_data.variable_names[i]))
    n = length(name)
    resize!(name, buf_size)
    name[min(buf_size, n + 1):end] .= UInt8('\0')
    GC.@preserve name begin
        unsafe_copyto!(buf_ptr, pointer(name), buf_size)
    end
    return
end

function _c_constraint_name(
    id_ptr::Ptr{Cvoid},
    i::Cint,
    buf_ptr::Ptr{UInt8},
    buf_size::Cint
)
    id_data = unsafe_pointer_to_objref(id_ptr)::InterfaceData
    name = collect(codeunits(id_data.constraint_names[i]))
    n = length(name)
    resize!(name, buf_size)
    name[min(buf_size, n + 1):end] .= UInt8('\0')
    GC.@preserve name begin
        unsafe_copyto!(buf_ptr, pointer(name), buf_size)
    end
    return
end

"""
    MCP_Interface

A storage struct that is used to pass problem-specific functions to PATH.
"""
mutable struct MCP_Interface
    interface_data::Ptr{Cvoid}
    problem_size::Ptr{Cvoid}
    bounds::Ptr{Cvoid}
    function_evaluation::Ptr{Cvoid}
    jacobian_evaluation::Ptr{Cvoid}
    # TODO(odow): the .h files I have don't include the hessian evaluation in
    #             MCP_Interface, but Standalone_Path.c includes it. Ask M.
    #             Ferris to look at the source.
    # Answer: there is an #ifdef to turn it on or off. In the GAMS builds it
    #         appears to be on, but we should be careful when updating PATH
    #         versions.
    hessian_evaluation::Ptr{Cvoid}
    start::Ptr{Cvoid}
    finish::Ptr{Cvoid}
    variable_name::Ptr{Cvoid}
    constraint_name::Ptr{Cvoid}
    basis::Ptr{Cvoid}

    function MCP_Interface(interface_data::InterfaceData)
        _C_PROBLEM_SIZE = @cfunction(
            _c_problem_size,
            Cvoid,
            (Ptr{Cvoid}, Ptr{Cint}, Ptr{Cint})
        )
        _C_BOUNDS = @cfunction(
            _c_bounds,
            Cvoid,
            (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble})
        )
        _C_FUNCTION_EVALUATION = @cfunction(
            _c_function_evaluation,
            Cint,
            (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble})
        )
        _C_JACOBIAN_EVALUATION = @cfunction(
            _c_jacobian_evaluation,
            Cint,
            (
                Ptr{Cvoid}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble},
                Ptr{Cint}, Ptr{Cint}, Ptr{Cint}, Ptr{Cint}, Ptr{Cdouble}
            )
        )
        if isempty(interface_data.variable_names)
            _C_VARIABLE_NAME = C_NULL
        else
            _C_VARIABLE_NAME = @cfunction(
                _c_variable_name,
                Cvoid,
                (Ptr{Cvoid}, Cint, Ptr{Cuchar}, Cint)
            )
        end
        if isempty(interface_data.constraint_names)
            _C_CONSTRAINT_NAME = C_NULL
        else
            _C_CONSTRAINT_NAME = @cfunction(
                _c_constraint_name,
                Cvoid,
                (Ptr{Cvoid}, Cint, Ptr{Cuchar}, Cint)
            )
        end

        return new(
            pointer_from_objref(interface_data),
            _C_PROBLEM_SIZE,
            _C_BOUNDS,
            _C_FUNCTION_EVALUATION,
            _C_JACOBIAN_EVALUATION,
            C_NULL,
            C_NULL,  # See TODO note in definition of fields above.
            C_NULL,
            _C_VARIABLE_NAME,
            _C_CONSTRAINT_NAME,
            C_NULL
        )
    end
end

mutable struct MCP
    n::Int
    ptr::Ptr{Cvoid}
    id_data::Union{Nothing, InterfaceData}
    function MCP(n::Int, ptr::Ptr{Cvoid})
        m = new(n, ptr, nothing)
        finalizer(c_api_MCP_Destroy, m)
        return m
    end
end

Base.cconvert(::Type{Ptr{Cvoid}}, x::MCP) = x
Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::MCP) = x.ptr

function c_api_MCP_Create(n::Int, nnz::Int)
    ptr = ccall((:MCP_Create, PATH_SOLVER), Ptr{Cvoid}, (Cint, Cint), n, nnz)
    return MCP(n, ptr)
end

function c_api_MCP_Destroy(m::MCP)
    if m.ptr === C_NULL
        return
    end
    ccall((:MCP_Destroy, PATH_SOLVER), Cvoid, (Ptr{Cvoid},), m)
    return
end

function c_api_MCP_SetInterface(m::MCP, interface::MCP_Interface)
    ccall(
        (:MCP_SetInterface, PATH_SOLVER),
        Cvoid,
        (Ptr{Cvoid}, Ref{MCP_Interface}),
        m, interface
    )
    return
end

function c_api_MCP_GetX(m::MCP)
    ptr = ccall((:MCP_GetX, PATH_SOLVER), Ptr{Cdouble}, (Ptr{Cvoid},), m)
    return copy(unsafe_wrap(Array{Cdouble}, ptr, m.n))
end

###
### Types.h
###

@enum(
    MCP_Termination,
    MCP_Solved = 1,
    MCP_NoProgress,
    MCP_MajorIterationLimit,
    MCP_MinorIterationLimit,
    MCP_TimeLimit,
    MCP_UserInterrupt,
    MCP_BoundError,
    MCP_DomainError,
    MCP_Infeasible,
    MCP_Error,
    MCP_LicenseError,
    MCP_OK
)

mutable struct Information
    # Double residual;	     /* Value of residual at final point             */
    residual::Cdouble
    # Double distance;	     /* Distance between initial and final point     */
    distance::Cdouble
    # Double steplength;	     /* Steplength taken                             */
    steplength::Cdouble
    # Double total_time;	     /* Amount of time spent in the code             */
    total_time::Cdouble

    # Double maximum_distance;   /* Maximum distance from init point allowed     */
    maximum_distance::Cdouble

    # Int major_iterations;	     /* Major iterations taken                       */
    major_iterations::Cint
    # Int minor_iterations;	     /* Minor iterations taken                       */
    mainor_iterations::Cint
    # Int crash_iterations;	     /* Crash iterations taken                       */
    crash_iterations::Cint
    # Int function_evaluations;  /* Function evaluations performed               */
    function_evaluations::Cint
    # Int jacobian_evaluations;  /* Jacobian evaluations performed               */
    jacobian_evaluations::Cint
    # Int gradient_steps;	     /* Gradient steps taken                         */
    gradient_steps::Cint
    # Int restarts;		     /* Restarts used                                */
    restarts::Cint

    # Int generate_output;       /* Mask where output can be displayed.          */
    generate_output::Cint
    # Int generated_output;      /* Mask where output displayed.                 */
    generated_output::Cint

    # Boolean forward;	     /* Move forward?                                */
    forward::Bool
    # Boolean backtrace;	     /* Back track?                                  */
    backtrace::Bool
    # Boolean gradient;	     /* Take gradient step?                          */
    gradient::Bool

    # Boolean use_start;         /* Use the starting point provided?             */
    use_start::Bool
    # Boolean use_basics;        /* Use the basis provided?                      */
    use_basics::Bool

    # Boolean used_start;        /* Was the starting point given used?           */
    used_start::Bool
    # Boolean used_basics;       /* Was the initial basis given used?            */
    used_basics::Bool

    function Information(;
        use_start::Bool = true,
    )
        return new(
            0.0, 0.0, 0.0, 0.0, 0.0,
            0, 0, 0, 0, 0, 0, 0,
            0, 0,
            false, false, false,
            use_start, false,
            false, false
        )
    end
end

###
### Path.h
###

"""
    c_api_Path_CheckLicense(n::Int, nnz::Int)

Check that the current license (stored in the environment variable
`PATH_LICENSE_STRING` if present) is valid for problems with `n` variables and
`nnz` non-zeros in the Jacobian.

Returns a nonzero value on successful completion, and a zero value on failure.
"""
function c_api_Path_CheckLicense(n::Int, nnz::Int)
    return ccall((:Path_CheckLicense, PATH_SOLVER), Cint, (Cint, Cint), n, nnz)
end

"""
    c_api_Path_Version()

Return a string of the PATH version.
"""
function c_api_Path_Version()
    ptr = ccall((:Path_Version, PATH_SOLVER), Ptr{Cchar}, ())
    return unsafe_string(ptr)
end

"""
    c_api_Path_Solve(m::MCP, info::Information)

Returns a MCP_Termination status.
"""
function c_api_Path_Solve(m::MCP, info::Information)
    return ccall(
        (:Path_Solve, PATH_SOLVER), Cint, (Ptr{Cvoid}, Ref{Information}), m, info
    )
end

###
### Standalone interface
###

"""
    solve_mcp(
        F::Function,
        J::Function
        lb::Vector{Cdouble},
        ub::Vector{Cdouble},
        z::Vector{Cdouble};
        nnz::Int = length(lb)^2,
        variable_name::Vector{String}=String[],
        constraint_name::Vector{String}=String[],
        silent::Bool = false,
        kwargs...
    )

Mathematically, the mixed complementarity problem is to find an x such that
for each i, at least one of the following hold:

   1.  F_i(x) = 0, lb_i <= (x)_i <= ub_i
   2.  F_i(x) > 0, (x)_i = lb_i
   3.  F_i(x) < 0, (x)_i = ub_i

where F is a given function from R^n to R^n, and lb and ub are prescribed
lower and upper bounds.

`z` is an initial starting point for the search.

`F` is a function `F(n::Cint, x::Vector{Cdouble}, f::Vector{Cdouble})` that
should calculate the function F(x) and store the result in `f`.
"""
function solve_mcp(
    F::Function,
    J::Function,
    lb::Vector{Cdouble},
    ub::Vector{Cdouble},
    z::Vector{Cdouble};
    nnz::Int = length(lb)^2,
    variable_names::Vector{String} = String[],
    constraint_names::Vector{String} = String[],
    silent::Bool = false,
    kwargs...
)
    @assert length(z) == length(lb) == length(ub)
    if !silent && isimmutable(stdout)
        @warn(
            "Disabling output because PATH.jl does not support writing to " *
            "this `stdout`. Are you using Atom-Hydrogen or Jupyter? See: " *
            "https://github.com/odow/PATH.jl/issues/10."
        )
        silent = true
    end
    out_io = silent ? IOBuffer() : stdout
    GC.@preserve out_io begin
    c_api_Output_SetInterface(OutputInterface(out_io))

    n = length(z)
    if n == 0
        return MCP_Solved, nothing, nothing
    end

    # Convert `Int` to `Float64` for check to avoid overflow.
    dnnz = min(1.0 * nnz, 1.0 * n^2)
    if dnnz > typemax(Cint)
        return MCP_Error, nothing, nothing
    end
    nnz = Int(dnnz + 1)

    o = c_api_Options_Create()
    c_api_Path_AddOptions(o)
    c_api_Options_Default(o)
    m = c_api_MCP_Create(n, nnz)
    m.id_data = InterfaceData(
        Cint(n), Cint(nnz),
        F, J, lb, ub, z,
        variable_names, constraint_names
    )
    m_interface = MCP_Interface(m.id_data)
    c_api_MCP_SetInterface(m, m_interface)
    if length(kwargs) > 0
        mktemp() do path, io
            println(io, "* Automatically generated by PATH.jl. Do not edit.")
            for (key, val) in kwargs
                println(io, key, " ", val)
            end
            close(io)
            c_api_Options_Read(o, path)
        end
    end
    c_api_Options_Display(o)
    info = Information(use_start = true)
    status = c_api_Path_Solve(m, info)
    end  # GC.@preserve
    X = c_api_MCP_GetX(m)
    # TODO(odow): I don't know why, but manually calling MCP_Destroy was
    # necessary to avoid a segfault on Julia 1.0 when using LUSOL. I guess it's
    # something to do with the timing of when things need to get freed on the
    # PATH side? i.e., MCP_Destroy before other things?
    c_api_MCP_Destroy(m)
    m.ptr = C_NULL
    return MCP_Termination(status), X, info
end

function _linear_function(M::AbstractMatrix, q::Vector)
    if size(M, 1) != size(M, 2)
        error("M not square! size = $(size(M))")
    elseif size(M, 1) != length(q)
        error("q is wrong shape. Expected $(size(M, 1)), got $(length(q)).")
    end
    return (n::Cint, x::Vector{Cdouble}, f::Vector{Cdouble}) -> begin
        f .= M * x .+ q
        return Cint(0)
    end
end

function _linear_jacobian(M::SparseArrays.SparseMatrixCSC{Cdouble, Cint})
    if size(M, 1) != size(M, 2)
        error("M not square! size = $(size(M))")
    end
    return (
        n::Cint,
        nnz::Cint,
        x::Vector{Cdouble},
        col::Vector{Cint},
        len::Vector{Cint},
        row::Vector{Cint},
        data::Vector{Cdouble}
    ) -> begin
        @assert n == length(x) == length(col) == length(len) == size(M, 1)
        @assert nnz == length(row) == length(data)
        @assert nnz >= SparseArrays.nnz(M)
        for i = 1:n
            col[i] = M.colptr[i]
            len[i] = M.colptr[i + 1] - M.colptr[i]
        end
        for (i, v) in enumerate(SparseArrays.rowvals(M))
            row[i] = v
        end
        for (i, v) in enumerate(SparseArrays.nonzeros(M))
            data[i] = v
        end
        return Cint(0)
    end
end

"""
    solve_mcp(;
        M::SparseArrays.SparseMatrixCSC{Cdouble, Cint},
        q::Vector{Cdouble},
        lb::Vector{Cdouble},
        ub::Vector{Cdouble},
        z::Vector{Cdouble};
        kwargs...
    )

Mathematically, the mixed complementarity problem is to find an x such that
for each i, at least one of the following hold:

   1.  F_i(x) = 0, lb_i <= (x)_i <= ub_i
   2.  F_i(x) > 0, (x)_i = lb_i
   3.  F_i(x) < 0, (x)_i = ub_i

where F is a function `F(x) = M * x + q` from R^n to R^n, and lb and ub are
prescribed lower and upper bounds.

`z` is an initial starting point for the search.
"""
function solve_mcp(
    M::SparseArrays.SparseMatrixCSC{Cdouble, Cint},
    q::Vector{Cdouble},
    lb::Vector{Cdouble},
    ub::Vector{Cdouble},
    z::Vector{Cdouble};
    kwargs...
)
    return solve_mcp(
        _linear_function(M, q),
        _linear_jacobian(M),
        lb,
        ub,
        z;
        nnz = SparseArrays.nnz(M),
        kwargs...
    )
end


using SQLite

db = SQLite.DB("pinglog-sqlite3.db")
df = SQLite.query(db,"SELECT date, dest, ping FROM pinglog;")

df[:date] = map(x->Dates.unix2datetime(parse(Float32, x)), df[:date])
df[:ping] = map(x->parse(Float32, x), df[:ping])
df = df[df[:date] .> DateTime("2017-12-08"), :]
pings_to_google = df[df[:dest] .== "google.com", [:date, :ping]]
pings_to_router = df[df[:dest] .== "192.168.0.1", [:date, :ping]]

using PlotlyJS
using Plots
plotlyjs()

p1 = Plots.plot(
    pings_to_google[:date],
    pings_to_google[:ping],
    title="Ping logs from rpi3",
    label="google.com",
    color="red")
p2 = Plots.plot(
    pings_to_router[:date],
    pings_to_router[:ping],
    xlabel="Time [DateTime]",
    label="router",
    color="blue")
Plots.plot(p1, p2, layout=(2,1),
    link=:x,
    ylabel="Ping [ms]")


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    DomainCollection(domain₁, domain₂, ...)
    DomainCollection([domain₁, domain₂, ...])

A collection of domains `domain₁`, `domain₂`, ...
"""
struct DomainCollection{T,N} <: AbstractDomain{T,N}
  domains::Vector{AbstractDomain{T,N}}
  offsets::Vector{Int}
end

function DomainCollection(domains::AbstractVector)
  T = coordtype(domains[1])
  N = ndims(domains[1])
  offsets = cumsum([npoints(d) for d in domains])
  DomainCollection{T,N}(domains, offsets)
end

DomainCollection(domains::Vararg) where {N,T} =
  DomainCollection([d for d in domains])

npoints(collection::DomainCollection) = collection.offsets[end]

function coordinates!(buff::AbstractVector{T},
                      collection::DomainCollection{T,N},
                      location::Int) where {N,T}
  k = findfirst(off -> location ≤ off, collection.offsets)
  l = k > 1 ? location - collection.offsets[k-1] : location
  coordinates!(buff, collection.domains[k], l)
end

# ------------
# IO methods
# ------------
function Base.show(io::IO, collection::DomainCollection{T,N}) where {N,T}
  ndomains = length(collection.domains)
  print(io, "$ndomains DomainCollection{$T,$N}")
end

function Base.show(io::IO, ::MIME"text/plain", collection::DomainCollection{T,N}) where {N,T}
  println(io, collection)
  for d in collection.domains
    println(io, "└─", d)
  end
end

"""
    DataCollection(data₁, data₂, ...)
    DataCollection([data₁, data₂, ...])

A collection of data `data₁`, `data₂`, ...
"""
struct DataCollection{T,N} <: AbstractData{T,N}
  data::Vector{AbstractData{T,N}}
  domain::DomainCollection{T,N}
  offsets::Vector{Int}
  variables::Dict{Symbol,Type}
  varnames::Vector{Symbol}
end

function DataCollection(data::AbstractVector)
  cdomain = DomainCollection(domain.(data))
  T = coordtype(cdomain)
  N = ndims(cdomain)
  offsets = cumsum([npoints(d) for d in data])
  vars    = reduce(merge, [variables(d) for d in data])
  vnames  = collect(keys(vars))
  DataCollection{T,N}(data, cdomain, offsets, vars, vnames)
end

DataCollection(data::Vararg) =
  DataCollection([d for d in data])

variables(collection::DataCollection) = collection.variables

function Base.getindex(collection::DataCollection, ind::Int, var::Symbol)
  k = findfirst(off -> ind ≤ off, collection.offsets)
  d = collection.data[k]
  if var ∈ collection.varnames
    i = k > 1 ? ind - collection.offsets[k-1] : ind
    getindex(d, i, var)
  else
    missing
  end
end

# ------------
# IO methods
# ------------
function Base.show(io::IO, collection::DataCollection{T,N}) where {N,T}
  ndata = length(collection.data)
  print(io, "$ndata DataCollection{$T,$N}")
end

function Base.show(io::IO, ::MIME"text/plain", collection::DataCollection{T,N}) where {N,T}
  println(io, collection)
  for d in collection.data
    println(io, "└─", d)
  end
end


"""
    Vocabulary(word,unk_cutoff =1 ,unk_label = "<unk>")

Stores language model vocabulary.
Satisfies two common language modeling requirements for a vocabulary:
- When checking membership and calculating its size, filters items
by comparing their counts to a cutoff value.
Adds a special "unknown" token which unseen words are mapped to.

# Example
```julia-repl
julia> words = ["a", "c", "-", "d", "c", "a", "b", "r", "a", "c", "d"]
julia> vocabulary = Vocabulary(words, 2)
  Vocabulary(Dict("<unk>"=>1,"c"=>3,"a"=>3,"d"=>2), 2, "<unk>")

julia> vocabulary.vocab
  Dict{String,Int64} with 4 entries:
   "<unk>" => 1
   "c"     => 3
   "a"     => 3
   "d"     => 2

Tokens with counts greater than or equal to the cutoff value will
be considered part of the vocabulary.
julia> vocabulary.vocab["c"]
 3

julia> "c" in keys(vocabulary.vocab)
 true

julia> vocabulary.vocab["d"]
 2

julia> "d" in keys(vocabulary.vocab)
 true

Tokens with frequency counts less than the cutoff value will be considered not
part of the vocabulary even though their entries in the count dictionary are
preserved.
julia> "b" in keys(vocabulary.vocab)
 false

julia> "<unk>" in keys(vocabulary.vocab)
 true

We can look up words in a vocabulary using its `lookup` method.
"Unseen" words (with counts less than cutoff) are looked up as the unknown label.
If given one word (a string) as an input, this method will return a string.
julia> lookup("a")
 'a'

julia> word = ["a", "-", "d", "c", "a"]

julia> lookup(vocabulary ,word)
 5-element Array{Any,1}:
  "a"
  "<unk>"
  "d"
  "c"
  "a"

If given a sequence, it will return an Array{Any,1} of the looked up words as shown above.

It's possible to update the counts after the vocabulary has been created.
julia> update(vocabulary,["b","c","c"])
 1

julia> vocabulary.vocab["b"]
 1
```
"""
mutable struct Vocabulary
vocab::Dict{String, Int64}
unk_cutoff::Int
unk_label::String
allword::Array{String, 1}
end

function Vocabulary(word::Vector{T}, unk_cutoff=1, unk_label="<unk>") where { T <: AbstractString}
    if unk_label in word
        error("unk_label is in vocab")
    else
    word= push!(word, unk_label)
    end
    vocab = countmap(word)
    for value in vocab
        if value[2]<unk_cutoff && value[1] != unk_label
            delete!(vocab, value[1])
        end
    end
    Vocabulary(vocab, unk_cutoff, unk_label, word)
end

function update(vocab::Vocabulary, words)
    vocab.allword = append!(vocab.allword, words)
    vocab.vocab=addcounts!(vocab.vocab, words)
end

"""
lookup a sequence or words in the vocabulary

Return an Array of String
"""
function lookup(voc::Vocabulary, word::Vector{T}) where { T <: AbstractString}
    look = []
    for w in word
        if w in keys(voc.vocab)
            push!(look, w)
        else
            push!(look, voc.unk_label)
        end
    end
    return look
end


module FunsTest

using Funs
using Base.Test

function inc(i::Integer)
    i+1
end

immutable Inc
    i::Int
    Inc(i) = new(i+1)
end

const incf = Fun{Int}(inc)

function main()
    # untyped functions
    let
        # Function
        v1 = fcall(inc, 1)
        @test v1 == 2

        # DataType
        v2 = fcall(Inc, 2)
        @test v2.i == 3

        # Fun
        v3 = fcall(incf, 3)
        @test v3 == 4
    end

    # typed function
    let
        # Function
        v1 = fcall(R=Int, inc, 1)
        @test v1 == 2

        # DataType
        v2 = fcall(R=Inc, Inc, 2)
        @test v2.i == 3

        # Fun
        v3 = fcall(R=Int, incf, 3)
        @test v3 == 4
    end

    # untyped macros
    let
        # Function
        v1 = @fcall inc(1)
        @test v1 == 2

        # DataType
        v2 = @fcall Inc(2)
        @test v2.i == 3

        # Fun
        v3 = @fcall incf(3)
        @test v3 == 4
    end

    # typed macros
    let
        # Function
        v1 = @fcall Int inc(1)
        @test v1 == 2

        # DataType
        v2 = @fcall Inc Inc(2)
        @test v2.i == 3

        # Fun
        v3 = @fcall Int incf(3)
        @test v3 == 4
    end
end

end


##########################################
# Master file for running all test cases #
##########################################
using Turing; turnprogress(false)
using Pkg
using Random
using Test

include("test_utils/AllUtils.jl")

# Begin testing.
@testset "Turing" begin
    @testset "core" begin
        include("core/ad.jl")
        include("core/container.jl")
    end

    @testset "inference" begin
        @testset "samplers" begin
            # FIXME: DynamicHMC version 1 has (??) a bug on 32bit platforms (but we were too
            # lazy to open an issue so Tamas doesn't know about it), retest with 2.0
            if Int === Int64 && Pkg.installed()["DynamicHMC"].major == 2
                include("contrib/inference/dynamichmc.jl")
            end
            include("inference/gibbs.jl")
            include("inference/hmc.jl")
            include("inference/is.jl")
            include("inference/mh.jl")
            include("inference/ess.jl")
            include("inference/AdvancedSMC.jl")
            include("inference/Inference.jl")
        end
    end

    @testset "variational" begin
        @testset "algorithms" begin
            include("variational/advi.jl")
        end
    end

    @testset "stdlib" begin
        include("stdlib/distributions.jl")
        # include("stdlib/RandomMeasures.jl")
    end

    @testset "utilities" begin
      # include("utilities/stan-interface.jl")
        include("utilities/util.jl")
    end
end


@testset "AffineConstraint" begin
    v = [_SV(_VI(i)) for i in 1:3]
    ac = Cerberus.AffineConstraint(v[1] + 2.0 * v[2] + 3.0 * v[3], _ET(3.0))
    @test _is_equal(
        ac.f,
        _CSAF([1.0, 2.0, 3.0], [_CVI(1), _CVI(2), _CVI(3)], 0.0),
    )
    @test ac.s == _ET(3.0)
end

function _test_polyhedron(p::Cerberus.Polyhedron)
    @test Cerberus.num_constraints(p) == 2
    @test Cerberus.num_constraints(p, _LT) == length(p.lt_constrs) == 1
    @test Cerberus.num_constraints(p, _GT) == length(p.gt_constrs) == 0
    @test Cerberus.num_constraints(p, _ET) == length(p.et_constrs) == 1
    et_constr = @inferred Cerberus.get_constraint(p, _ET, 1)
    @test et_constr === p.et_constrs[1]
    _is_equal(
        et_constr.f,
        _CSAF([1.0, 2.1, 3.0], [_CVI(1), _CVI(2), _CVI(3)], 0.0),
    )
    @test et_constr.s == _ET(3.0)

    lt_constr = @inferred Cerberus.get_constraint(p, _LT, 1)
    @test lt_constr === p.lt_constrs[1]
    @test _is_equal(lt_constr.f, _CSAF([-3.5, 1.2], [_CVI(1), _CVI(2)], 0.0))
    @test lt_constr.s == _LT(4.0)

    @test p.bounds == [_IN(0.5, 1.0), _IN(-1.3, 2.3), _IN(0.0, 1.0)]

    return nothing
end

@testset "Polyhedron" begin
    p = @inferred _build_polyhedron()
    _test_polyhedron(p)

    # TODO: Test throws on malformed Polyhedron
    @test_throws AssertionError Cerberus.Polyhedron(
        [
            Cerberus.AffineConstraint(
                _CSAF([1.0, 2.0], [_CVI(1), _CVI(2)], 0.0),
                _ET(1.0),
            ),
        ],
        [_IN(0.0, 1.0)],
    )
    @testset "ambient_dim" begin
        @test Cerberus.ambient_dim(p) == 3
    end
    @testset "num_constraints" begin
        @test Cerberus.num_constraints(p) == 2
    end
    @testset "add_variable" begin
        Cerberus.add_variable(p)
        @test Cerberus.ambient_dim(p) == 4
    end
    @testset "empty constructor" begin
        p = @inferred Cerberus.Polyhedron()
        @test Cerberus.ambient_dim(p) == 0
        @test Cerberus.num_constraints(p) == 0
    end
end

@testset "DMIPFormulation" begin
    fm = @inferred _build_dmip_formulation()
    _test_polyhedron(fm._feasible_region)
    @test _is_equal(fm.obj, _CSAF([1.0, -1.0], [_CVI(1), _CVI(2)], 0.0))
    @test isempty(fm.disjunction_formulaters)
    @test [Cerberus.get_variable_kind(fm, _CVI(i)) for i in 1:3] == [_ZO(), nothing, _ZO()]

    @testset "empty constructor" begin
        empty_fm = @inferred Cerberus.DMIPFormulation()
        @test Cerberus.num_variables(empty_fm) == 0
        @test Cerberus.ambient_dim(empty_fm._feasible_region) == 0
        @test Cerberus.num_constraints(empty_fm._feasible_region) == 0
        @test _is_equal(empty_fm.obj, _CSAF())
        @test isempty(empty_fm.disjunction_formulaters)
        @test isempty(empty_fm._variable_kind)
    end

    @testset "num_variables" begin
        @test @inferred Cerberus.num_variables(fm) == 3
    end

    @testset "add_variable" begin
        @inferred Cerberus.add_variable(fm)
        @test Cerberus.num_variables(fm) == 4
    end

    @testset "all_variables" begin
        av = @inferred Cerberus.all_variables(fm)
        @test av == [_CVI(i) for i in 1:4]
        Cerberus.add_variable(fm)
        @test Cerberus.all_variables(fm) == [_CVI(i) for i in 1:5]
    end

    @testset "get_variable_kind" begin
        @test Cerberus.get_variable_kind(fm, _CVI(1)) == _ZO()
        @test Cerberus.get_variable_kind(fm, _CVI(2)) === nothing
        @test Cerberus.get_variable_kind(fm, _CVI(3)) == _ZO()
        @test Cerberus.get_variable_kind(fm, _CVI(4)) === nothing
    end

    @testset "set_variable_kind!" begin
        @inferred Cerberus.set_variable_kind!(fm, _CVI(4), _GI())
        @test Cerberus.get_variable_kind(fm, _CVI(4)) == _GI()
    end

    @testset "num_constraints" begin
        @test @inferred Cerberus.num_constraints(fm) == 2
        @test @inferred Cerberus.num_constraints(fm, _CCI{_LT}) == 1
        @test @inferred Cerberus.num_constraints(fm, _CCI{_GT}) == 0
        @test @inferred Cerberus.num_constraints(fm, _CCI{_ET}) == 1
    end

    @testset "get_constraint(s)" begin
        lt_true = @inferred Cerberus.get_constraint(fm, _CCI{_LT}(1))
        @test _is_equal(lt_true.f, _CSAF([-3.5, 1.2], [_CVI(1), _CVI(2)], 0.0))
        @test lt_true.s == _LT(4.0)
        et_true = @inferred Cerberus.get_constraint(fm, _CCI{_ET}(1))
        @test _is_equal(
            et_true.f,
            _CSAF([1.0, 2.1, 3.0], [_CVI(1), _CVI(2), _CVI(3)], 0.0),
        )
        @test et_true.s == _ET(3.0)
        @test length(Cerberus.get_constraints(fm, _CCI{_LT})) == 1
        @test Cerberus.get_constraints(fm, _CCI{_LT}) == [lt_true]
        @test length(Cerberus.get_constraints(fm, _CCI{_GT})) == 0
        @test length(Cerberus.get_constraints(fm, _CCI{_ET})) == 1
        @test Cerberus.get_constraints(fm, _CCI{_ET}) == [et_true]
    end

    @testset "add_constraint" begin
        f = _CSAF([2.1, 5.4], [_CVI(3), _CVI(4)], 1.2)
        s = _GT(7.2)
        ac = Cerberus.AffineConstraint(f, s)
        @inferred Cerberus.add_constraint(fm, ac)
        @test Cerberus.num_constraints(fm, _CCI{_GT}) == 1
        @test Cerberus.get_constraint(fm, _CCI{_GT}(1)) == ac
        @test Cerberus.get_constraints(fm, _CCI{_GT})[1] == ac
    end

    @testset "isempty" begin
        @test !isempty(fm)
        empty_form = Cerberus.DMIPFormulation()
        @test isempty(empty_form)
        Cerberus.add_constraint(
            empty_form,
            Cerberus.AffineConstraint(_CSAF(), _LT(1.0)),
        )
        @test !isempty(empty_form)
    end

    @testset "get_bounds" begin
        l, u = @inferred Cerberus.get_bounds(fm, _CVI(1))
        @test Cerberus.get_bounds(fm, _CVI(1)) == (0.5, 1.0)
        @test Cerberus.get_bounds(fm, _CVI(2)) == (-1.3, 2.3)
        @test Cerberus.get_bounds(fm, _CVI(3)) == (0.0, 1.0)
        @test Cerberus.get_bounds(fm, _CVI(4)) == (-Inf, Inf)
    end

    @testset "set_bounds!" begin
        @inferred Cerberus.set_bounds!(fm, _CVI(4), _IN(5.7, 8.4))
        @test Cerberus.get_bounds(fm, _CVI(4)) == (5.7, 8.4)
    end

    # TODO: Test throws on malformed DMIPFormulation
end

function _test_gi_polyhedron(p::Cerberus.Polyhedron)
    @test Cerberus.num_constraints(p) == 1
    @test Cerberus.num_constraints(p, _LT) == 1
    @test Cerberus.num_constraints(p, _GT) == 0
    @test Cerberus.num_constraints(p, _ET) == 0
    lt_constr = @inferred Cerberus.get_constraint(p, _LT, 1)
    @test lt_constr === p.lt_constrs[1]
    _is_equal(
        lt_constr.f,
        _CSAF([1.3, 3.7, 2.4], [_CVI(1), _CVI(2), _CVI(3)], 0.0),
    )
    @test lt_constr.f.constant == 0.0
    @test lt_constr.s == _LT(5.5)

    @test p.bounds == [_IN(0.0, 4.5), _IN(0.0, 1.0), _IN(0.0, 3.0)]
end

@testset "General integer polyhedron/formulation" begin
    p = @inferred _build_gi_polyhedron()
    _test_gi_polyhedron(p)

    fm = @inferred _build_gi_dmip_formulation()
    _test_gi_polyhedron(fm._feasible_region)
    @test isempty(fm.disjunction_formulaters)
    @test [Cerberus.get_variable_kind(fm, _CVI(i)) for i in 1:3] == [nothing, _ZO(), _GI()]
end

struct DummyMethod <: DisjunctiveConstraints.AbstractDisjunctiveFormulation end
function Cerberus.new_variables_to_attach(
    ::Cerberus.DisjunctiveFormulater{DummyMethod},
)
    return [_ZO(), _GI(), nothing, nothing, nothing, nothing]
end

@testset "isempty(::DMIPFormulation)" begin
    form = Cerberus.DMIPFormulation()
    @test isempty(form)
    disjunction = Cerberus.Disjunction(
        _CSAF[],
        DisjunctiveConstraints.DisjunctiveSet(
            Matrix{Float64}(undef, 0, 0),
            Matrix{Float64}(undef, 0, 0),
        ),
    )
    formulater = Cerberus.DisjunctiveFormulater(disjunction, DummyMethod())
    Cerberus.attach_formulater!(form, formulater)
    @test !isempty(form)
    form = _build_dmip_formulation()
    @test !isempty(form)
end

@testset "attach_formulater!" begin
    form = _build_dmip_formulation()
    @test Cerberus.num_disjunctive_constraints(form) == 0
    @assert Cerberus.num_variables(form) == 3
    disjunction = Cerberus.Disjunction(
        _CSAF[],
        DisjunctiveConstraints.DisjunctiveSet(
            Matrix{Float64}(undef, 0, 0),
            Matrix{Float64}(undef, 0, 0),
        ),
    )
    formulater = Cerberus.DisjunctiveFormulater(disjunction, DummyMethod())
    @inferred Cerberus.attach_formulater!(form, formulater)
    @test Cerberus.num_variables(form) == 3 + 6
    @test length(form.disjunction_formulaters) == 1
    @test haskey(form.disjunction_formulaters, formulater)
    @test form.disjunction_formulaters[formulater] ==
          [_CVI(i) for i in (3+1):(3+6)]
    @test Cerberus.num_disjunctive_constraints(form) == 1

    @test_throws ArgumentError Cerberus.attach_formulater!(form, formulater)
end


function is_undirected(n::Int, cptr::Vector, rval::Vector)
    for col = OneTo(n)
        l1 = cptr[col+1]-1
        for i = 0 : (l1 - cptr[col])
            if rval[l1-i] <= col
                break
            end
            return false
        end
    end
    return true
end

@noinline function _check_type(n::Integer, T::Type)
    if 0 ≤ n && (!isbitstype(Ti) || n ≤ typemax(Ti))
        throw(ArgumentError("number of vertices (nv = $n) does not fit in eltype = $(T)"))
    end
    return nothing
end

# stronger check for debugging purposes
# && all(issorted(@view rowval[colptr[i]:colptr[i+1]-1]) for i=1:n)
_goodbuffers(g::AdjacencyMap) = _goodbuffers(nvertices(g), getcolptr(g), rowvals(g), metadata(g))
function _goodbuffers(n, cptr, rval, ::NoMetadata)
    if !(length(cptr) == n + 1 && cptr[end] - 1 == length(rval))
        throw(ArgumentError("Illegal buffers for AdjacencyMap construction $n $colptr $rowval $nzval"))
    end
    return nothing
end
function _goodbuffers(n, cptr, rval, m)
    if !(length(cptr) == n + 1 && cptr[end] - 1 == length(rval) == length(m))
        throw(ArgumentError("Illegal buffers for AdjacencyMap construction $n $colptr $rowval $nzval"))
    end
    return nothing
end

function _check_length(rowstr, rowval, minlen, Ti)
    throwmin(len, minlen, rowstr) = throw(ArgumentError("$len == length($rowstr) < $minlen"))
    throwmax(len, max, rowstr) = throw(ArgumentError("$len == length($rowstr) >= $max"))

    len = length(rowval)
    len >= minlen || throwmin(len, minlen, rowstr)
    !isbitstype(Ti) || len < typemax(Ti) || throwmax(len, typemax(Ti), rowstr)
    return nothing
end

_check_meta_length(ne, ::NoMetadata) = nothing
function _check_meta_length(ne, m)
    if ne != length(m)
        throw(ArgumentError("""
        cannot assign AdjacencyMap list of metadata with length different than number of edges:
        number of edges = $(Int(ne))
        length metadata list = $(length(m))
        """))
    end
    return nothing
end
# This is a bad but it's also the only thing the stops me from writing the print code for
# MetaAdjView here.
SparseArrays._checkbuffers(m::MetaAdjView) = _goodbuffers(parent(m))


using Signals
using StaticArrays
using BenchmarkTools
Signals.async_mode(false)
# write your own tests here
# @test 1

typ = Matrix
typ = SMatrix{4,4,Float64,16}

A = Signal(typ(rand(4,4)))
B = Signal(typ(rand(4,4)))

C = Signal(typ(rand(4,4)))
D = Signal(typ(rand(4,4)))
E = Signal(A,B) do a,b
    a*b
end
F = Signal(C,D) do a,b
    a*b
end
G = Signal(E,F) do e,f
    e*f
end
Z = typ(zeros(4,4))
A(Z)

@benchmark $A($Z)

@benchmark begin begin $A[] = $A[];end; $G(); end

Signals.debug_mode(true)

A("ff")

addprocs(1)
@everywhere using Signals
Signals.async_mode(true)

A = Signal(1)
B = remote_signal(x->x+1,A)

A(10)
A("ff")

@noinline f(a,b) = a+b
a5() = try
    f("ff",1)
catch
    st = catch_stacktrace()
    println(st)
end
b = Task(a5)
A = schedule(b)

using Signals
Signals.async_mode(true)
A_strict = Signal(1; strict_push = true)
B_strict = Signal(A_strict;state = 0) do a,state
    state.x += a
    a + 1
end

A_soft = Signal(1)
B_soft = Signal(A_soft;state = 0) do a,state
    state.x += a
    a + 1
end

A_soft(0);sleep(1) #restart the eventlopp

B_strict.state.x == 1
A_strict(10);A_strict(100);
yield();
B_strict.state.x == 111

A = 5
f() = begin
    global A
    while true
        if A == 6
            break;
        end
        sleep(0.001)
    end
end

B = @async f()
A = 6


module ReactiveMPTest

using Test, Documenter, ReactiveMP
using TestSetExtensions

include("test_helpers.jl")

using .ReactiveMPTestingHelpers

# doctest(ReactiveMP)

@testset ExtendedTestSet "ReactiveMP" begin

    enabled_tests = lowercase.(ARGS)

    function key_to_filename(key)
        splitted = split(key, ":")
        return length(splitted) === 1 ? string("test_", first(splitted), ".jl") : string(join(splitted[1:end - 1], "/"), "/test_", splitted[end], ".jl")
    end

    function filename_to_key(filename)
        splitted   = split(filename, "/")
        if length(splitted) === 1
            return replace(replace(first(splitted), ".jl" => ""), "test_" => "")
        else
            path, name = splitted[1:end - 1], splitted[end]
            return string(join(path, ":"), ":", replace(replace(name, ".jl" => ""), "test_" => ""))
        end
    end

    function addtests(filename)
        key = filename_to_key(filename)
        if isempty(enabled_tests) || key in enabled_tests
            include(filename)
        end
    end

    @testset "Testset helpers" begin
        @test key_to_filename(filename_to_key("distributions/test_normal_mean_variance.jl")) == "distributions/test_normal_mean_variance.jl"
        @test filename_to_key(key_to_filename("distributions:normal_mean_variance")) == "distributions:normal_mean_variance"
        @test key_to_filename(filename_to_key("test_message.jl")) == "test_message.jl"
        @test filename_to_key(key_to_filename("message")) == "message"
    end

    addtests("algebra/test_correction.jl")

    addtests("test_distributions.jl")
    addtests("distributions/test_common.jl")
    addtests("distributions/test_bernoulli.jl")
    addtests("distributions/test_beta.jl")
    addtests("distributions/test_categorical.jl")
    addtests("distributions/test_contingency.jl")
    addtests("distributions/test_dirichlet_matrix.jl")
    addtests("distributions/test_dirichlet.jl")
    addtests("distributions/test_gamma.jl")
    addtests("distributions/test_mv_normal_mean_covariance.jl")
    addtests("distributions/test_mv_normal_mean_precision.jl")
    addtests("distributions/test_mv_normal_weighted_mean_precision.jl")
    addtests("distributions/test_normal_mean_variance.jl")
    addtests("distributions/test_normal_mean_precision.jl")
    addtests("distributions/test_normal_weighted_mean_precision.jl")
    addtests("distributions/test_normal.jl")
    addtests("distributions/test_pointmass.jl")
    addtests("distributions/test_wishart.jl")

    addtests("test_message.jl")

    addtests("test_variable.jl")

    addtests("test_node.jl")
    addtests("nodes/test_addition.jl")

    addtests("rules/normal_mean_variance/test_out.jl")
    addtests("rules/normal_mean_variance/test_mean.jl")
    addtests("rules/normal_mean_variance/test_var.jl")

    addtests("rules/normal_mean_precision/test_out.jl")
    addtests("rules/normal_mean_precision/test_mean.jl")
    addtests("rules/normal_mean_precision/test_precision.jl")

    addtests("rules/mv_normal_mean_covariance/test_out.jl")
    addtests("rules/mv_normal_mean_covariance/test_mean.jl")

    addtests("rules/mv_normal_mean_precision/test_out.jl")
    addtests("rules/mv_normal_mean_precision/test_mean.jl")
    addtests("rules/mv_normal_mean_precision/test_precision.jl")

    addtests("rules/wishart/test_marginals.jl")
    addtests("rules/wishart/test_out.jl")

end

end


using ImageCore, Colors, ColorVectorSpace
using Test, Statistics

# Different access patterns (getindex)
function mysum_elt_boundscheck(A)
    s = zero(eltype(A))
    for a in A
        s += a
    end
    s
end
function mysum_index_boundscheck(A)
    s = zero(eltype(A))
    for I in eachindex(A)
        s += A[I]
    end
    s
end
function mysum_elt_inbounds(A)
    s = zero(eltype(A))
    @inbounds for a in A
        s += a
    end
    s
end
function mysum_index_inbounds_simd(A)
    s = zero(eltype(A))
    @inbounds @simd for I in eachindex(A)
        s += A[I]
    end
    s
end
# setindex!
function myfill1!(A, val)
    f = convert(eltype(A), val)
    for I in eachindex(A)
        A[I] = f
    end
    A
end
function myfill2!(A, val)
    f = convert(eltype(A), val)
    @inbounds @simd for I in eachindex(A)
        A[I] = f
    end
    A
end

# Rather than using BenchmarkTools (and thus run one test repeatedly,
# accumulating timings), we run the same test interleaving the two
# array types. This is designed to reduce the risk of spurious
# failure, particularly on shared machines like Travis where they may
# get "distracted" by other tasks
function test_getindex(f, ar, cv, n)
    t_ar = Array{Float64}(undef, n)
    t_cv = Array{Float64}(undef, n)
    f_ar = Ref(f(ar))
    f_cv = Ref(f(cv))
    for i = 1:n
        t_ar[i] = (tstart = time(); f_ar[] = f(ar); time()-tstart)
        t_cv[i] = (tstart = time(); f_cv[] = f(cv); time()-tstart)
    end
    median(t_ar), median(t_cv), f_ar
end
function test_setindex(f, ar, cv, n)
    t_ar = Array{Float64}(undef, n)
    t_cv = Array{Float64}(undef, n)
    for i = 1:n
        t_ar[i] = @elapsed f(ar, zero(eltype(ar)))
        t_cv[i] = @elapsed f(cv, zero(eltype(cv)))
    end
    median(t_ar), median(t_cv)
end

ssz = (1000,1000)
c = rand(RGB{Float64}, ssz...)
a = copy(reinterpretc(Float64, c))
vchan = channelview(c)
vcol = colorview(RGB, a)
cc_getindex_funcs = (mysum_elt_boundscheck,
                     mysum_index_boundscheck,
                     mysum_elt_inbounds,
                     mysum_index_inbounds_simd)
cc_setindex_funcs = (myfill1!,
                     myfill2!)
chanvtol = Dict(mysum_index_inbounds_simd => 20,   # @simd doesn't work for ChannelView :(
                mysum_elt_boundscheck => 20,
                myfill1! => 20,                    # crappy setindex! performance
                myfill2! => 20)
chanvdefault = 10
colvtol = Dict(mysum_elt_boundscheck=>5,
               mysum_index_boundscheck=>5)
colvdefault = 3

@info "Benchmark tests are warnings for now"
# @testset "benchmarks" begin
for (suite, testf) in ((cc_getindex_funcs, test_getindex),
                       (cc_setindex_funcs, test_setindex))
    for f in suite
        # Channelview
        t_ar, t_cv = testf(f, a, vchan, 10^2)
        tol = haskey(chanvtol, f) ? chanvtol[f] : chanvdefault
        if t_cv >= tol*t_ar
            @warn "ChannelView: failed on $f, time ratio $(t_cv/t_ar), tol $tol"
        end
        # @test t_cv < tol*t_ar
        # ColorView
        t_ar, t_cv = testf(f, c, vcol, 10^2)
        tol = haskey(colvtol, f) ? colvtol[f] : colvdefault
        if t_cv >= tol*t_ar
            @warn "ColorView: failed on $f, time ratio $(t_cv/t_ar), tol $tol"
        end
        # @test t_cv < tol*t_ar
    end
end
# end


# instantiate project
import Pkg
Pkg.activate(@__DIR__)
Pkg.instantiate()

const OUTPUT_FOLDER = "output"
const RESULTS_FILE = "runtimes.csv"

LONG = length(ARGS) == 1 && ARGS[1] ∈ ("LONG", "long")

function main()

    # --------------------------
    # Initialization
    # --------------------------
    if !isdir(OUTPUT_FOLDER)
        mkdir(OUTPUT_FOLDER)
        mkdir(joinpath(OUTPUT_FOLDER, "SDOF"))
        mkdir(joinpath(OUTPUT_FOLDER, "Clamped"))
        mkdir(joinpath(OUTPUT_FOLDER, "Heat1D"))
        mkdir(joinpath(OUTPUT_FOLDER, "Heat3D"))
    end
    global io = open(joinpath(OUTPUT_FOLDER, RESULTS_FILE), "w")
    global TARGET_FOLDER
    println("Running examples...")

    # --------------------------
    # Single-degree-of-freedom
    # --------------------------
    println("\n (1/5) Running single-degree-of-freedom (SDOF) model... \n")
    TARGET_FOLDER = joinpath(OUTPUT_FOLDER, "SDOF")
    include(joinpath("examples", "SDOF", "SDOF_Preliminaries.jl"))
    include(joinpath("examples", "SDOF", "SDOF_Time.jl"))
    include(joinpath("examples", "SDOF", "SDOF_PE_AD.jl"))

    # --------------------------
    # Clamped bar
    # --------------------------
    TARGET_FOLDER = joinpath(OUTPUT_FOLDER, "Clamped")
    println("\n (2/5) Running Clamped bar model\n")
    include(joinpath("examples", "Clamped", "Clamped.jl"))

    # --------------------------
    # Lambs Wave
    # --------------------------
    if LONG
        TARGET_FOLDER = joinpath(OUTPUT_FOLDER, "LambsWave")
        println("\n (3/5) Running Lambs wave propagation problem...\n")
        include(joinpath("examples", "LambsWave", "lambs_results.jl"))
    end

    # --------------------------
    # Heat 1D
    # --------------------------
    TARGET_FOLDER = joinpath(OUTPUT_FOLDER, "Heat1D")
    println("\n (4/5) Running one-dimensional heat transfer model...\n")
    include(joinpath("examples", "Heat1D", "Heat1D_TempProfile.jl"))
    include(joinpath("examples", "Heat1D", "Heat1D_TempGradient.jl"))

    # --------------------------
    # Heat 3D
    # --------------------------
    TARGET_FOLDER = joinpath(OUTPUT_FOLDER, "Heat3D")
    println("\n (5/5) Running three-dimensional concrete casting model...\n")
    include(joinpath("examples", "Heat3D", "Heat3D.jl"))

    print(io, "\n")
    println("Finished running benchmarks.")
    close(io)
    nothing
end

main()


include("task_func.jl")

function movements!(r, n)
    while !isborder(r, Ost)
        up = 0
        for _ = 1:n
            shag = goToSideWithShag!(r, Ost, n-1, save = true, checkBorder = true, mark = true)[1]
            goToSideWithShag!(r, West, shag)
            putmarker!(r)

            up += goToSideWithShag!(r, Nord, 1, save = true, checkBorder = true)[1]
        end
        goToSideWithShag!(r, Sud, up)
        goToSideWithShag!(r, Ost, 2*n, checkBorder = true)
    end

    goToSide!(r, West)
    goToSideWithShag!(r, Nord, n, checkBorder = true)
end

function draw!(r, n)
    listOfShags = []
    push!(listOfShags, goToSide!(r, Sud, save = true))
    push!(listOfShags, goToSide!(r, West, save = true))

    while !isborder(r, Nord)
        movements!(r, n)

        if isborder(r, Nord) && ismarker(r) break end
        goToSideWithShag!(r, Ost, n, checkBorder = true)

        movements!(r, n)
    end

    goToWestSud!(r)
    goToStart!(r, listOfShags)
end


function bernoulli(n)
    numerator, denominator = 0, 0

    return numerator, denominator
end


## UChar = Class("Char", 8)
## Stream = List(UChar, Variable, [:v, :u, :v1])

function readBin(x :: Char)
    if x == '0'
        -1.0
    else
        1.0
    end
end

function readChar(char_int :: UInt8)
    Object("u", UChar, [readBin(x) for x in bin(char_int, 8)])
end

function readVar(xs :: Array{UInt8,1})
    if length(xs) == 0
        Stream.empty
    else
        apply(Stream.cons, [readChar(xs[1]), readVar(xs[2:end])])
    end
end

function readVar(xs)
    readVar(string(xs).data)
end

function prog(x :: Symbol)
    [var, readVar(x)]
end

function prog(x :: Expr)
    if x.head == :local
        [definition, readVar(x.args[1].args[1]), prog(x.args[1].args[2])]
    elseif x.head == :(=)
        [assignment, readVar(x.args[1]), prog(x.args[2])]
    elseif x.head == :function
        [procedure, readVar(x.args[1].args[1]), prog(x.args[2])]
    elseif x.head == :call
        [func_call, prog(x.args[1]), prog(x.args[2])]
    elseif x.head == :block
        [Seq.cons, prog(x.args[1]), prog(x.args[2:end])]
    else
        Seq.empty
    end
end

function prog(x :: Array)
    if length(x) == 0
        Seq.empty
    else
        [Seq.cons, prog(x[1]), prog(x[2:end])]
    end
end

type REPL
    env :: Object
    ans :: Object
end

function repl(r :: REPL, command :: String)
    command = replace(command, "var ", "local ")
    t = toTree(prog(parse(command)))
    init_tree!(t)
    eval_tree!(t)
    o = Object("c", Prog, t.value[:value])
    r.ans = apply(f_eval, [o, r.env])
    apply!(s_eval, [o, r.env], r.env)
    r.ans
end


module SymArrays

export SymArray, contract, contract!, SymArr_ifsym, symgrp_size, symgrps, nsymgrps, storage_type

include("helpers.jl")

include("symarray.jl")

include("contractions.jl")

include("cuda_contractions.jl")

end # module


##===----------------------------------------------------------------------===##
##                                     YEPPP.JL                               ##
##                                                                            ##
##===----------------------------------------------------------------------===##
##                                                                            ##
##  This file provides access to Yeppp and handles checking for the existence ##
##  of the Yeppp library during import.                                       ##
##                                                                            ##
##===----------------------------------------------------------------------===##
module Yeppp
import Vectorize: functions, addfunction

using Libdl

# Cross-version compatibility
OS = Sys.KERNEL

# Attempt to locate Yeppp library before we proceed with compiling file
libyeppp_ = Libdl.find_library(["libyeppp"])
if libyeppp_ != "" # using system installed yeppp
    const global libyeppp = libyeppp_
else # using Vectorize.jl provided yppp
    currdir = @__FILE__
    bindir = currdir[1:end-12]*"deps/src/yeppp/binaries/"
    if OS == :Darwin
        @eval const global libyeppp = bindir*"macosx/x86_64/libyeppp.dylib"
    elseif OS == :Linux
        @eval const global libyeppp = bindir*"linux/x86_64/libyeppp.so"
    elseif OS == :NT
        @eval const global libyeppp = bindir*"windows/amd64/yeppp.dll"
    end
end

"""
`__init__()` Cvoid

This function initializes the Yeppp library whenever the module is imported.
This allows us to precompile the rest of the module without asking the user
to explictly initialize Yeppp! on load.
"""
function __init__()
    # Yeppp Initialization
    if isfile(libyeppp)
        status = ccall(("yepLibrary_Init", libyeppp), Cint,
                        (), )
        status != 0 && error("Error initializing Yeppp library (error: ", status, ")")
    end

    return true
end

## This defines the argument and return types for every yepCore function; this are as follow:
##
## (First Argument Type, Second Argument Type, Return Type
##
const yepppcore = ((Int8, Int8, Int8), (UInt8, UInt8, UInt16),  (Int16, Int16, Int16),
                   (UInt16, UInt16, UInt32), (Int32, Int32, Int32),
                   (UInt32, UInt32, UInt64), (Int64, Int64, Int64),
                   (Float32, Float32, Float32), (Float64, Float64, Float64))

## This defines the mapping between argument types and Yeppp function name specifications
const identifier = Dict(Int8 => "V8s", Int16 => "V16s", Int32 => "V32s", Int64 => "V64s",
                        UInt8 => "V8u", UInt16 => "V16u", UInt32 => "V32u", UInt64 => "V64u",
                        Float32 => "V32f", Float64 => "V64f")

#### Yeppp Arithmetic taking two arguments and returning vector
for (f, fname, name) in ((:add, "Add", "addition"),  (:sub, "Subtract", "subtraction"),
                   (:mul, "Multiply", "multiplication"), (:max, "Max", "maximum"),
                   (:min, "Min", "minimum"))
    for (argtype1, argtype2, returntype) in yepppcore
        # generate Yeppp function name
        yepppname = string("yepCore_$(fname)_", identifier[argtype1], identifier[argtype2],
                           "_", identifier[returntype])
        addfunction(functions, (f, (Array{Float64}, Array{Float64})), "Vectorize.Yeppp.$f")
        @eval begin
            @doc """
            `$($f)(X::Array{$($argtype1)}, Y::Array{$($argtype2)})`
            Implements element-wise **$($name)** over two **Array{$($argtype1)}**. Allocates
            memory to store result. *Returns:* **Array{$($returntype)}**
            """
            function ($f)(X::Array{$argtype1}, Y::Array{$argtype2})
                len = length(X)
                out = Array{$returntype}(undef, len)
                ccall(($(yepppname, libyeppp)), Cint,
                          (Ptr{$argtype1}, Ptr{$argtype2}, Ptr{$returntype},  Clonglong),
                          X, Y, out, len)
                return out
            end
        end
    end
end

#### YepppMath functions returning vectors
for (f, fname) in [(:sin, "Sin"),  (:cos, "Cos"),  (:tan, "Tan"), (:log, "Log"), (:exp, "Exp")]
    yepppname = string("yepMath_$(fname)_V64f_V64f")
    name = string(f)
    f! = Symbol("$(f)!")
    # register functions for build
    addfunction(functions, (f, (Array{Float64},)), "Vectorize.Yeppp.$f")
    addfunction(functions, (f!, (Array{Float64}, Array{Float64})), "Vectorize.Yeppp.$(f!)")
    @eval begin
         @doc """
        `$($f)(X::Array{Float64})`
        Implements element-wise **$($name)** over a **Array{Float64}**. Allocates
        memory to store result. *Returns:* **Array{Float64}**
        """
        function ($f)(X::Array{Float64}) # regular syntax
            out = Array{Float64}(undef, length(X))
            return ($f!)(out, X)
        end
        @doc """
        `$($f!)(result::Array{Float64}, X::Array{Float64})`
        Implements element-wise **$($name)** over a **Array{Float64}** and overwrites
        the result vector with computed value. *Returns:* **Array{Float64}** `result`
        """
        function ($f!)(out::Array{Float64}, X::Array{Float64}) # in place syntax
            len = length(X)
            ccall(($(yepppname, libyeppp)), Cint,
                  (Ptr{Float64}, Ptr{Float64},  Clonglong),
                  X, out, len)
            return out
        end
    end
end

## YepppCore - functions that return scalars
for (T, Tscalar) in ((Float32, "S32f"), (Float64, "S64f"))
    for (f, fname, name) in [(:sum, "Sum", "sum"), (:sumsqr, "SumSquares", "sum-of-squares")]
        yepppname = string("yepCore_$(fname)_", identifier[T], "_", Tscalar)
        addfunction(functions, (f, (Array{T},)), "Vectorize.Yeppp.$f")
        @eval begin
            @doc """
            `$($f)(X::Array{$($T)})`
            Computes the **$($name)** of a **Array{$($T)}**. Allocates
            memory to store result. *Returns:* **Array{$($T)}**
            """
            function ($f)(X::Array{$T})
                len = length(X)
                out = Array{$T}(undef, 1)
                ccall(($(yepppname, libyeppp)), Cint,
                      (Ptr{$T}, Ptr{$T},  Clonglong),
                      X, out, len)
                return out[1]
            end
        end
    end
end

end # End Module


abstract type sCPAAlphaModel <: CPAAlphaModel end

@newmodelsimple sCPAAlpha sCPAAlphaModel CPAAlphaParam

export sCPAAlpha
function sCPAAlpha(components::Vector{String}; userlocations::Vector{String}=String[], verbose::Bool=false)
    params = getparams(components, ["SAFT/CPA/sCPA/sCPA_like.csv"]; userlocations=userlocations, ignore_missing_singleparams=["Mw"], verbose=verbose)
    c1 = params["c1"]
    packagedparams = CPAAlphaParam(c1)
    model = sCPAAlpha(packagedparams, verbose=verbose)
    return model
end


include("tabular_approximator.jl")
include("neural_network_approximator.jl")


module IcanProj

using DataFrames
using NveData
using FileIO
using PyPlot
using ProgressMeter
using NetCDF
using CSV
using Printf
using Dates
using Statistics

#=
# Global settings

export get_options, get_wsh_info

include("global_settings.jl")

# Data structures

export WatershedData, VicRes, VicInput

include("data_structures.jl")

# Handle metadata

export load_metadata, clean_metadata, get_watershed_data, get_wsh_name, resolution_ind

include("metadata.jl")
=#

# Functions for handling vic data for simulations

export write_global_param, write_soil_params, write_vic_forcing, write_veg_params

export read_soil_params, read_veg_param

export read_vic_forcing, read_mtclim_hbv, read_mtclim_fsm

export read_fluxes, read_all_fluxes, read_snow, read_all_snow, read_results, read_all_results

include("vic_io.jl")

#=
# Functions for handling vic results (processing and loading)

export get_summary_tables, get_variable

include("handle_results.jl")

# Function for aggregating data (from finer to coarser grid resolution)

export average_forcings, average_soilparams, average_vegparams

include("vic_agg.jl")
=#

# Plotting functions

export analysis_elevation

include("plots/analysis_elevation.jl")

# Data handling functions

export create_netcdf, get_filename, load_result, load_time, load_time_slice, load_space_slice

include("data_netcdf.jl")

# Handle fsm results

export link_results, unify_results, project_results, link_table

include("resample_results.jl")

end


##################################################################################
# This file is part of StateSpaceEcon.jl
# BSD 3-Clause License
# Copyright (c) 2020, Bank of Canada
# All rights reserved.
##################################################################################

export SimData

"""
    SimData

Data structure containing the time series data for a simulation.

It is a collection of [`TSeries`](@ref) of the same frequency and containing
data for the same range. When used for simulation, the range must include the
initial conditions, the simulation range and the final conditions, although it
could extend beyond that. It must contain time series for all variables and
shocks in the model, although it might contain other time series.

"""
struct SimData{F <: Frequency,C <: AbstractMatrix{Float64}} <: AbstractMatrix{Float64}
    firstdate::MIT{F}
    columns::NamedTuple
    values::C

    # inner constructor enforces constraints.
    function SimData(fd::MIT, names::NTuple{N,Symbol}, values::AbstractMatrix) where {N}
        if N != size(values, 2)
            throw(ArgumentError("Number of names and columns don't match: $N ≠ $(size(values, 2))."))
        end
        columns = NamedTuple{names}([TSeries(fd, view(values, :, i)) for i in 1:N ])
        new{frequencyof(fd),typeof(values)}(fd, columns, values)
    end
end

# External constructors
"""
    SimData(fd, vars, data)

Construct an instance of SimData with the given variable names and data. The
first date is provided in the `fd::MIT` argument, which also contains
information about the frequency. The number of names must match the number of
columns in `data`. The names must be String or Symbol or anything that converts
to Symbol.

"""
SimData(fd, vars=(), data=zeros(0,0)) = SimData(fd, tuple(Symbol.(vars)...), data)
SimData(rng::AbstractUnitRange{<:Integer}) = SimData(first(rng), (), zeros(length(rng),0))

TimeSeriesEcon.firstdate(sd::SimData) = getfield(sd, :firstdate)
TimeSeriesEcon.lastdate(sd::SimData) = (firstdate(sd) - 1) + size(_values(sd), 1)
TimeSeriesEcon.frequencyof(::SimData{F}) where F <: Frequency = F
TimeSeriesEcon.mitrange(sd::SimData) = (firstdate(sd) - 1) .+ Base.axes1(sd)

@inline _columns(sd::SimData) = getfield(sd, :columns)
@inline _names(sd::SimData) = keys(getfield(sd, :columns))
@inline _values(sd::SimData) = getfield(sd, :values)
@inline _values_view(sd::SimData, i1, i2) = view(getfield(sd, :values), i1, i2)
@inline _values_slice(sd::SimData, i1, i2) = getindex(getfield(sd, :values), i1, i2)

Base.size(sd::SimData) = size(_values(sd))
Base.IndexStyle(sd::SimData) = IndexStyle(_values(sd))

# Indexing with integers falls back to AbstractArray
const FallbackType = Union{Integer,Colon,AbstractUnitRange{<:Integer},AbstractVector{<:Integer},CartesianIndex}
Base.getindex(sd::SimData, i1::FallbackType...) = getindex(_values(sd), i1...)
Base.setindex!(sd::SimData, val, i1::FallbackType...) = setindex!(_values(sd), val, i1...)

Base.similar(sd::SimData) = SimData(firstdate(sd), _names(sd), similar(_values(sd)))

Base.:(==)(a::SimData, b::SimData) = frequencyof(a) == frequencyof(b) && firstdate(a) == firstdate(b) && _values(a) == _values(b)

Base.dataids(sd::SimData) = Base.dataids(_values(sd))

export rawdata, colnames
rawdata(sd::SimData) = _values(sd)
colnames(sd::SimData) = keys(_columns(sd))
Base.pairs(sd::SimData) = pairs(_columns(sd))

const ColumnTypes = Union{Symbol,ModelVariable,AbstractString}
# Define dot access to columns
Base.propertynames(sd::SimData) = _names(sd)
Base.getproperty(sd::SimData, col::ColumnTypes) = getproperty(sd, Symbol(col))
function Base.getproperty(sd::SimData, col::Symbol)
    if col in _names(sd)
        return getfield(_columns(sd), col)
    else
        throw(BoundsError(sd, [col,]))
    end
end

# Access to columns by [:xyz] notation
Base.getindex(sd::SimData, col::ColumnTypes) = getproperty(sd, col)
Base.setindex!(sd::SimData, val, col::ColumnTypes) = setproperty!(sd, col, val)

Base.setproperty!(sd::SimData, col::ColumnTypes, val) = setproperty!(sd, Symbol(col), val)
function Base.setproperty!(sd::SimData, col::Symbol, val)
    try
        col = getproperty(sd, col)
    catch BoundsError
        error("Cannot assign new data column this way. Use hcat(sd, column=value, ...) instead")
    end
    if Base.mightalias(col, val)
        val = copy(val)
    end
    if val isa TSeries && frequencyof(val) == frequencyof(sd)
        rng = intersect(mitrange(sd), mitrange(val))
        col[rng] = val[rng].values
    else
        return setindex!(col.values, val, :)
    end
end

@inline Base.getindex(sd::SimData, cols::AbstractVector{<:Number}) = getindex(sd, (_names(sd)[cols])...)
# @inline Base.getindex(sd::SimData, cols::AbstractVector) = getindex(sd, tuple(map(Symbol, cols)...))
@inline Base.getindex(sd::SimData, cols::AbstractVector{<:ModelVariable}) = getindex(sd, tuple(map(Symbol, cols)...))
@inline Base.getindex(sd::SimData, cols::Tuple) = getindex(sd, tuple(map(Symbol, cols)...))

function Base.getindex(sd::SimData, cols::NTuple{N,Symbol}) where N
    SimData(firstdate(sd), tuple(cols...), hcat((getproperty(sd, c) for c in cols)...))
end

function Base.hcat(sd::SimData; KW...)
    l1 = size(sd, 1)
    as_vect(v::Number) = fill(Float64(v), l1)
    as_vect(v) = v
    names = (_names(sd)..., keys(KW)...)
    vals = hcat(_values(sd), (as_vect(v) for v in values(KW))...)
    return SimData(firstdate(sd), names, vals)
end

# Indexing access with MIT

function check_frequency(a, b)
    Fa = frequencyof(a)
    Fb = frequencyof(b)
    Fa == Fb || throw(ArgumentError("wrong frequency: expected $Fa got $Fb."))
end

macro if_same_frequency(a, b, expr)
    :( frequencyof($a) == frequencyof($b) ? $(expr) : throw(ArgumentError("Wrong frequency")) ) |> esc
end

# A single MIT materializes as a NamedTuple (row of the matrix with column names attached to the values)
Base.getindex(sd::SimData, i1::MIT, ::Colon) = sd[i1 - firstdate(sd) + 1, :]
function Base.getindex(sd::SimData, i1::MIT)
    check_frequency(sd, i1)
    if firstdate(sd) <= i1 <= lastdate(sd)
        return @inbounds NamedTuple{_names(sd)}(_values_view(sd, i1 - firstdate(sd) + 1, :))
    else
        throw(BoundsError(sd, i1))
    end
end

# Modifying a row in a table -> one must pass in a vector
Base.setindex!(sd::SimData, val, i1::MIT, ::Colon) = begin sd[i1 - firstdate(sd) + 1, :] = val end
function Base.setindex!(sd::SimData, val::AbstractVector{<:Real}, i1::MIT)
    check_frequency(sd, i1)
    if firstdate(sd) <= i1 <= lastdate(sd)
        row = i1 - firstdate(sd) + 1
        setindex!(_values_view(sd, row, :), val, :)
        return _values(sd)[row,:]
    else
        throw(BoundsError(sd, i1))
    end
end

function Base.setindex!(sd::SimData, val::NamedTuple, i1::MIT)
    check_frequency(sd, i1)
    if firstdate(sd) <= i1 <= lastdate(sd)
        for (n, v) in pairs(val)
            setindex!(getproperty(sd, n), v, i1)
        end
        return sd[i1]
    else
        throw(BoundsError(sd, i1))
    end
    return sd[i1]
end

# A selection of several rows returns a slice from the original SimData
function Base.getindex(sd::SimData, i1::AbstractUnitRange{<:MIT})
    check_frequency(sd, i1)
    if firstdate(sd) <= minimum(i1) <= maximum(i1) <= lastdate(sd)
        return SimData(first(i1), _names(sd), _values_slice(sd, i1 .- firstdate(sd) .+ 1, :))
    else
        throw(BoundsError(sd, i1))
    end
end

function Base.setindex!(sd::SimData, val, i1::AbstractUnitRange{<:MIT})
    check_frequency(sd, i1)
    if firstdate(sd) <= minimum(i1) <= maximum(i1) <= lastdate(sd)
        rows = i1 .- firstdate(sd) .+ 1
    else
        throw(BoundsError(sd, i1))
    end
    if val isa Number
        fill!(_values_view(sd, rows, :), val)
    else
        setindex!(_values_view(sd, rows, :), val, :, :)
    end
    return sd[rows,:]
end

# Base.getindex(sd::SimData, ::Colon, c) = getindex(sd, mitrange(sd), c)

Base.getindex(sd::SimData, r, c) = getproperty(sd, Symbol(c))[r]
Base.getindex(sd::SimData, r, c::AbstractVector) = getindex(sd, r, tuple(map(Symbol, c)...))
# Base.getindex(sd::SimData, r, c::Tuple) = getindex(sd, r, tuple(map(Symbol, c)...))

Base.getindex(sd::SimData, r::MIT, c::NTuple{N,Symbol}) where N = (a = sd[r]; NamedTuple{c}([a[cc] for cc in c]))
function Base.getindex(sd::SimData, r::AbstractUnitRange{<:MIT}, c::NTuple{N,Symbol}) where N
    check_frequency(sd, r)
    if firstdate(sd) <= first(r) <= last(r) <= lastdate(sd)
        col_inds = indexin(c, [_names(sd)...])
        for (cc, cn) in zip(c, col_inds)
            if cn === nothing
                throw(BoundsError(sd, [cc]))
            end
        end
        return SimData(first(r), c, _values_slice(sd, r .- firstdate(sd) .+ 1, col_inds))
    else
        throw(BoundsError(sd, r))
    end
end

Base.setindex!(sd::SimData, v, r, c::ColumnTypes) = setindex!(getproperty(sd, c), v, r)
Base.setindex!(sd::SimData, v, r, c::AbstractVector) = setindex!(sd, v, r, tuple(map(Symbol, c)...))
Base.setindex!(sd::SimData, v, r, c::Tuple) = setindex!(sd, v, r, tuple(map(Symbol, c)...))

function Base.setindex!(sd::SimData, v, r::MIT, c::NTuple{N,Symbol}) where N
    for (cc, vv) in zip(c, v)
        setindex!(sd, vv, r, cc)
    end
    return sd[r, c]
end

function Base.setindex!(sd::SimData, v, r::AbstractUnitRange{<:MIT}, c::NTuple{N,Symbol}) where N
    check_frequency(sd, r)
    if firstdate(sd) <= first(r) <= last(r) <= lastdate(sd)
        cols = indexin(c, [_names(sd)...])
        for (cc, cn) in zip(c, cols)
            if cn === nothing
                throw(BoundsError(sd, [cc]))
            end
        end
        rows = r .- firstdate(sd) .+ 1
        if v isa Number
            v = fill(Float64(v), length(r) * length(c))
        end
        setindex!(_values_view(sd, rows, cols), v, :, :)
    else
        throw(BoundsError(sd, r))
    end
end

#### Pretty printing

sprint_names(names) = length(names) > 10 ? "$(length(names)) variables" : "variables (" * join(names, ",") * ")"
Base.summary(io::IO, sd::SimData) = isempty(sd) ?
        print(IOContext(io, :limit => true), "Empty SimData with ", sprint_names(_names(sd)), " in ", mitrange(sd)) :
        print(IOContext(io, :limit => true), size(sd, 1), '×', size(sd, 2), " SimData with ", sprint_names(_names(sd)), " in ", mitrange(sd))

Base.show(io::IO, ::MIME"text/plain", sd::SimData) = show(io, sd)
function Base.show(io::IO, sd::SimData)
    summary(io, sd)
    isempty(sd) && return
    print(io, ":")
    limit = get(io, :limit, true)
    nval, nsym = size(sd)

    from = firstdate(sd)
    dheight, dwidth = displaysize(io)
    if get(io, :compact, nothing) === nothing
        io = IOContext(io, :compact => true)
    end
    dwidth -= 11

    names_str = let
        names = map(_names(sd)) do n
            sn = string(n)
            return sn[1:min(10, length(sn))]
        end
        reshape([names...], 1, :)
    end
    sd_with_names = [names_str; _values(sd)]
    A = Base.alignment(io, sd_with_names, axes(sd_with_names, 1), 1:nsym, dwidth, dwidth, 2)

    all_cols = true
    if length(A) ≠ nsym
        dwidth = div(dwidth - 1, 2)
        AL = Base.alignment(io, sd_with_names, axes(sd_with_names, 1), 1:nsym, dwidth, dwidth, 2)
        AR = reverse(Base.alignment(io, sd_with_names, axes(sd_with_names, 1), reverse(1:nsym), dwidth, dwidth, 2))
        Linds = [1:length(AL)...]
        Rinds = [nsym - length(AR) + 1:nsym...]
        all_cols = false
    end

    local vdots = "\u22ee"
    local hdots = " \u2026 "
    local ddots = " \u22f1 "

    print_aligned_val(io, v, (al, ar), showsep=true; sep=showsep ? "  " : "") = begin
        sv = sprint(print, v, context=io, sizehint=0)
        if v isa Number
            vl, vr = Base.alignment(io, v)
        else
            if length(sv) > al + ar
                sv = sv[1:al + ar - 1] * '…'
            end
            vl, vr = al, length(sv) - al
        end
        print(io, repeat(" ", al - vl), sv, repeat(" ", ar - vr), sep)
    end

    print_colnames(io, Lcols, LAligns, Rcols=[], RAligns=[]) = begin
        local nLcols = length(Lcols)
        local nRcols = length(Rcols)
        for (i, (col, align)) in enumerate(zip(Lcols, LAligns))
            print_aligned_val(io, names[col], align, i < nLcols)
        end
        nRcols == 0 && return
        print(io, hdots)
        for (i, (col, align)) in enumerate(zip(Rcols, RAligns))
            print_aligned_val(io, names[col], align, i < nRcols)
        end
    end

    print_rows(io, rows, Lcols, LAligns, Rcols=[], RAligns=[]) = begin
        local nLcols = length(Lcols)
        local nRcols = length(Rcols)
        for row in rows
            mit = from + (row - 1)
            print(io, '\n', lpad(mit, 8), " : ")
            for (i, (val, align)) in enumerate(zip(vals[row, Lcols], LAligns))
                print_aligned_val(io, val, align, i < nLcols)
            end
            nRcols == 0 && continue
            print(io, hdots)
            for (i, (val, align)) in enumerate(zip(vals[row, Rcols], RAligns))
                print_aligned_val(io, val, align, i < nRcols)
            end
        end
    end

    print_vdots(io, Lcols, LAligns, Rcols=[], RAligns=[]) = begin
        print(io, '\n', repeat(" ", 11))
        local nLcols = length(Lcols)
        local nRcols = length(Rcols)
        for (i, (col, align)) in enumerate(zip(Lcols, LAligns))
            print_aligned_val(io, vdots, align, i < nLcols)
        end
        nRcols == 0 && return
        print(io, ddots)
        for (i, (col, align)) in enumerate(zip(Rcols, RAligns))
            print_aligned_val(io, vdots, align, i < nRcols)
        end
    end

    names = _names(sd)
    vals = _values(sd)
    if !limit
        print(io, "\n", repeat(" ", 11))
        print_colnames(io, 1:nsym, A)
        print_rows(io, 1:nval, 1:nsym, A)
    elseif nval > dheight - 6 # all rows don't fit
                # unable to show all rows
        if all_cols
            print(io, "\n", repeat(" ", 11))
            print_colnames(io, 1:nsym, A)
            top = div(dheight - 6, 2)
            print_rows(io, 1:top, 1:nsym, A)
            print_vdots(io, 1:nsym, A)
            bot = nval - dheight + 7 + top
            print_rows(io, bot:nval, 1:nsym, A)
        else # not all_cols
            print(io, "\n", repeat(" ", 11))
            print_colnames(io, Linds, AL, Rinds, AR)
            top = div(dheight - 6, 2)
            print_rows(io, 1:top, Linds, AL, Rinds, AR)
            print_vdots(io, Linds, AL, Rinds, AR)
            bot = nval - dheight + 7 + top
            print_rows(io, bot:nval, Linds, AL, Rinds, AR)
        end # all_cols
    else # all rows fit
        if all_cols
            print(io, '\n', repeat(" ", 11))
            print_colnames(io, 1:nsym, A)
            print_rows(io, 1:nval, 1:nsym, A)
        else
            print(io, '\n', repeat(" ", 11))
            print_colnames(io, Linds, AL, Rinds, AR)
            print_rows(io, 1:nval, Linds, AL, Rinds, AR)
        end
    end
end


abstract type FEFunction <: CellField end

"""
"""
function get_free_values(f::FEFunction)
  @abstractmethod
end

"""
"""
function get_cell_dof_values(f::FEFunction)
  @abstractmethod
end

function get_cell_dof_values(f::FEFunction,cellids::AbstractArray)
  cell_values = get_cell_dof_values(f)
  lazy_map(Reindex(cell_values),cellids)
end

function get_cell_dof_values(f::FEFunction,cellids::SkeletonPair)
  cell_values_plus = get_cell_dof_values(f,cellids.plus)
  cell_values_minus = get_cell_dof_values(f,cellids.minus)
  cell_axes_plus = lazy_map(axes,cell_values_plus)
  cell_axes_minus = lazy_map(axes,cell_values_minus)
  cell_axes = lazy_map(cell_axes_plus,cell_axes_minus) do axp, axm
    (append_ranges([axp[1],axm[1]]),)
  end
  lazy_map(BlockArrayCooMap((2,),[(1,),(2,)]),cell_axes,cell_values_plus,cell_values_minus)
end

"""
"""
function get_fe_space(f::FEFunction)
  @abstractmethod
end

"""
"""
function test_fe_function(f::FEFunction)
  free_values = get_free_values(f)
  fe_space = get_fe_space(f)
  @test length(free_values) == num_free_dofs(fe_space)
  cell_values = get_cell_dof_values(f)
  trian = get_triangulation(f)
  @test length(cell_values) == num_cells(trian)
end

abstract type FESpace <: GridapType end

# Minimal FE interface (used by FEOperator)

"""
"""
function num_free_dofs(f::FESpace)
  @abstractmethod
end

"""
"""
function zero_free_values(f::FESpace)
  V = get_vector_type(f)
  allocate_vector(V,num_free_dofs(f))
end

function get_vector_type(fs::FESpace)
  @abstractmethod
end

"""
"""
function FEFunction(fe::FESpace, free_values)
  @abstractmethod
end

function CellField(fe::FESpace,cell_vals)
  @abstractmethod
end

function get_triangulation(fe::FESpace)
  @abstractmethod
end

# TODO this is quite hacky. Only needed by the zero mean space
function EvaluationFunction(fe::FESpace, free_values)
  FEFunction(fe,free_values)
end

"""
"""
function Base.zero(f::FESpace)
  free_values = zero_free_values(f)
  FEFunction(f,free_values)
end

function get_dof_value_type(f::FESpace)
  get_dof_value_type(get_cell_shapefuns(f),get_cell_dof_basis(f))
end

function get_dof_value_type(cell_shapefuns::CellField,cell_dof_basis::CellDof)
  cell_dof_values = cell_dof_basis(cell_shapefuns)
  eltype(eltype(cell_dof_values))
end

# Extended FEInterface used by Assemblers

"""
"""
function get_cell_dof_ids(f::FESpace)
  @abstractmethod
end

function get_cell_dof_ids(f::FESpace,cellids::AbstractArray)
  cell_dof_ids = get_cell_dof_ids(f)
  lazy_map(Reindex(cell_dof_ids),cellids)
end

function get_cell_dof_ids(f::FESpace,cellids::SkeletonPair)
  cell_ids_plus = get_cell_dof_ids(f,cellids.plus)
  cell_ids_minus = get_cell_dof_ids(f,cellids.minus)
  cell_axes_plus = lazy_map(axes,cell_ids_plus)
  cell_axes_minus = lazy_map(axes,cell_ids_minus)
  cell_axes = lazy_map(cell_axes_plus,cell_axes_minus) do axp, axm
    (append_ranges([axp[1],axm[1]]),)
  end
  lazy_map(BlockArrayCooMap((2,),[(1,),(2,)]),cell_axes,cell_ids_plus,cell_ids_minus)
end

"""
"""
function get_cell_shapefuns(f::FESpace)
  @abstractmethod
end

"""
"""
function get_cell_dof_basis(f::FESpace)
  @abstractmethod
end

function get_cell_shapefuns_trial(f::FESpace)
  v = get_cell_shapefuns(f)
  cell_v = get_data(v)
  cell_u = lazy_map(transpose,cell_v)
  FEBasis(cell_u,get_triangulation(v),TrialBasis(),DomainStyle(v))
end

# Skeleton-related

abstract type BasisStyle end
struct TrialBasis <: BasisStyle end
struct TestBasis <: BasisStyle end

struct FEBasis{BS<:BasisStyle,DS<:DomainStyle} <: CellField
  cell_basis::AbstractArray{<:AbstractArray{<:Field}}
  trian::Triangulation
  basis_style::BS
  domain_style::DS
  function FEBasis(
    cell_basis::AbstractArray{<:AbstractArray{<:Field}},
    trian::Triangulation,
    basis_style::BasisStyle,
    domain_style::DomainStyle)

    BS = typeof(basis_style)
    DS = typeof(domain_style)
    new{BS,DS}(Fields.MemoArray(cell_basis),trian,basis_style,domain_style)
  end
end

get_data(f::FEBasis) = f.cell_basis
get_triangulation(f::FEBasis) = f.trian
BasisStyle(::Type{FEBasis{BS,DS}}) where {BS,DS} = BS()
BasisStyle(::T) where T <: FEBasis = BasisStyle(T)
DomainStyle(::Type{FEBasis{BS,DS}}) where {BS,DS} = DS()
function gradient(a::FEBasis)
  f = GenericCellField(a.cell_basis,a.trian,a.domain_style)
  FEBasis(get_data(gradient(f)),a.trian,a.basis_style,a.domain_style)
end
function ∇∇(a::FEBasis)
  f = GenericCellField(a.cell_basis,a.trian,a.domain_style)
  FEBasis(get_data(∇∇(f)),a.trian,a.basis_style,a.domain_style)
end

function change_domain_skeleton(a::FEBasis,trian::SkeletonTriangulation,target_domain::DomainStyle)
  a_on_plus_trian = change_domain(a,trian.plus,target_domain)
  a_on_minus_trian = change_domain(a,trian.minus,target_domain)
  pair_in = SkeletonPair(get_data(a_on_plus_trian),get_data(a_on_minus_trian))
  pair_out = _fix_cell_basis_dofs_at_skeleton(pair_in,BasisStyle(a))
  plus = GenericCellField(pair_out.plus,trian,target_domain)
  minus = GenericCellField(pair_out.minus,trian,target_domain)
  plus, minus
end

function _fix_cell_basis_dofs_at_skeleton(pair,::TestBasis)
  cell_axes_plus = lazy_map(axes,pair.plus)
  cell_axes_minus = lazy_map(axes,pair.minus)
  function skeleton_axes_test(axp, axm)
    (append_ranges([axp[1],axm[1]]),)
  end
  cell_axes = lazy_map(skeleton_axes_test,cell_axes_plus,cell_axes_minus)
  plus = lazy_map(Fields.BlockFieldArrayCooMap((2,),[(1,)]),cell_axes,pair.plus)
  minus = lazy_map(Fields.BlockFieldArrayCooMap((2,),[(2,)]),cell_axes,pair.minus)
  SkeletonPair(plus,minus)
end

function _fix_cell_basis_dofs_at_skeleton(pair,::TrialBasis)
  cell_axes_plus = lazy_map(axes,pair.plus)
  cell_axes_minus = lazy_map(axes,pair.minus)
  function skeleton_axes_trial(axp, axm)
    r2 = append_ranges([axp[2],axm[2]])
    r1 = similar_range(r2,1)
    (r1,r2)
  end
  cell_axes = lazy_map(skeleton_axes_trial,cell_axes_plus,cell_axes_minus)
  plus = lazy_map(Fields.BlockFieldArrayCooMap((1,2),[(1,1)]),cell_axes,pair.plus)
  minus = lazy_map(Fields.BlockFieldArrayCooMap((1,2),[(1,2)]),cell_axes,pair.minus)
  SkeletonPair(plus,minus)
end

# Constraint-related

abstract type ConstraintStyle end
struct Constrained <: ConstraintStyle end
struct UnConstrained <: ConstraintStyle end

ConstraintStyle(::Type{<:FESpace}) = @abstractmethod
ConstraintStyle(::T) where T<:FESpace = ConstraintStyle(T)

has_constraints(::Type{T}) where T <:FESpace = ConstraintStyle(T) == Constrained()
has_constraints(::T) where T <: FESpace = has_constraints(T)

function get_cell_constraints(f::FESpace)
  get_cell_constraints(f,ConstraintStyle(f))
end

function get_cell_constraints(f,::UnConstrained)
  cell_axes = lazy_map(axes,get_data(get_cell_shapefuns(f)))
  identity_constraints(cell_axes)
end

function get_cell_constraints(f,::Constrained)
  @abstractmethod
end

function get_cell_constraints(f::FESpace,cellids::AbstractArray)
  lazy_map(Reindex(get_cell_constraints(f)),cellids)
end

function get_cell_constraints(f::FESpace,cellids::SkeletonPair)
  cell_constraints_plus = get_cell_constraints(f,cellids.plus)
  cell_constraints_minus = get_cell_constraints(f,cellids.minus)
  cell_axes_plus = lazy_map(axes,cell_constraints_plus)
  cell_axes_minus = lazy_map(axes,cell_constraints_minus)
  cell_axes = lazy_map(cell_axes_plus,cell_axes_minus) do axp, axm
    r1 = append_ranges([axp[1],axm[1]])
    r2 = append_ranges([axp[2],axm[2]])
    (r1,r2)
  end
  lazy_map(
    BlockArrayCooMap((2,2),[(1,1),(2,2)]),
    cell_axes,
    cell_constraints_plus,
    cell_constraints_minus)
end

function get_cell_isconstrained(f::FESpace)
  get_cell_isconstrained(f,ConstraintStyle(f))
end

function get_cell_isconstrained(f,::UnConstrained)
  Fill(false,length(get_cell_dof_ids(f)))
end

function get_cell_isconstrained(f,::Constrained)
  @abstractmethod
end

function get_cell_isconstrained(f::FESpace,cellids::AbstractArray)
  lazy_map(Reindex(get_cell_isconstrained(f)),cellids)
end

function get_cell_isconstrained(f::FESpace,cellids::SkeletonPair)
  plus = get_cell_isconstrained(f,cellids.plus)
  minus = get_cell_isconstrained(f,cellids.minus)
  lazy_map((l,r)-> l||r,plus,minus)
end

function attach_constraints_rows(f::FESpace,cellarr,cellids)
  attach_constraints_rows(f,cellarr,cellids,ConstraintStyle(f))
end

function attach_constraints_rows(f::FESpace,cellarr,cellids,::UnConstrained)
  cellarr
end

function attach_constraints_rows(f::FESpace,cellarr,cellids,::Constrained)
  cellconstr = get_cell_constraints(f,cellids)
  cellmask = get_cell_isconstrained(f,cellids)
  attach_constraints_rows(cellarr,cellconstr,cellmask)
end

function attach_constraints_cols(f::FESpace,cellarr,cellids)
  attach_constraints_cols(f,cellarr,cellids,ConstraintStyle(f))
end

function attach_constraints_cols(f::FESpace,cellarr,cellids,::UnConstrained)
  cellarr
end

function attach_constraints_cols(f::FESpace,cellarr,cellids,::Constrained)
  cellconstr = get_cell_constraints(f,cellids)
  cellmask = get_cell_isconstrained(f,cellids)
  attach_constraints_cols(cellarr,cellconstr,cellmask)
end

"""
"""
function test_fe_space(f::FESpace)
  trian = get_triangulation(f)
  @test isa(trian,Triangulation)
  free_values = zero_free_values(f)
  @test length(free_values) == num_free_dofs(f)
  V = get_vector_type(f)
  @test typeof(free_values) == V
  fe_function = FEFunction(f,free_values)
  test_fe_function(fe_function)
  fe_basis = get_cell_shapefuns(f)
  @test isa(has_constraints(f),Bool)
  @test isa(has_constraints(typeof(f)),Bool)
  @test length(get_cell_dof_ids(f)) == num_cells(fe_basis)
  @test length(get_cell_constraints(f)) == num_cells(fe_basis)
  @test length(get_cell_isconstrained(f)) == num_cells(fe_basis)
  @test CellField(f,get_cell_dof_ids(f)) != nothing
end

function test_fe_space(f::FESpace,matvecdata,matdata,vecdata)
  test_fe_space(f)

  cellmat, cellidsrows, cellidscols = matdata
  cm = attach_constraints_cols(f,cellmat,cellidscols)
  if ! has_constraints(f)
    @test cm === cellmat
  end
  cm = attach_constraints_rows(f,cellmat,cellidsrows)
  if ! has_constraints(f)
    @test cm === cellmat
  end

  cellvec, cellidsrows = vecdata
  cv = attach_constraints_rows(f,cellvec,cellidsrows)
  if ! has_constraints(f)
    @test cv === cellvec
  end

  cellmatvec, cellidsrows, cellidscols = matvecdata
  cmv = attach_constraints_cols(f,cellmatvec,cellidscols)
  if ! has_constraints(f)
    @test cmv === cellmatvec
  end
  cmv = attach_constraints_rows(f,cellmatvec,cellidsrows)
  if ! has_constraints(f)
    @test cmv === cellmatvec
  end

end