baremodule Semirings
    using ..Types
end

SemiringUnion = Union{AbstractSemiring, libgb.GrB_Semiring}

function semiringnames(name)
    simple = name[5:end]
    containername = Symbol(simple, "_RIG_T")
    exportedname = Symbol(simple)
    return containername, exportedname
end

function createsemirings()
    builtins = ["GxB_PLUS_FIRST", "GxB_TIMES_FIRST", "GxB_ANY_FIRST", "GxB_PLUS_SECOND", "GxB_TIMES_SECOND", "GxB_ANY_SECOND", "GxB_MIN_PAIR", "GxB_MAX_PAIR", "GxB_PLUS_PAIR", "GxB_TIMES_PAIR", "GxB_ANY_PAIR", "GxB_MIN_MIN", "GxB_TIMES_MIN", "GxB_ANY_MIN", "GxB_MAX_MAX", "GxB_PLUS_MAX", "GxB_TIMES_MAX", "GxB_ANY_MAX", "GxB_PLUS_PLUS", "GxB_TIMES_PLUS", "GxB_ANY_PLUS", "GxB_MIN_MINUS", "GxB_MAX_MINUS", "GxB_PLUS_MINUS", "GxB_TIMES_MINUS", "GxB_ANY_MINUS", "GxB_TIMES_TIMES", "GxB_ANY_TIMES", "GxB_MIN_DIV", "GxB_MAX_DIV", "GxB_PLUS_DIV", "GxB_TIMES_DIV", "GxB_ANY_DIV", "GxB_MIN_RDIV", "GxB_MAX_RDIV", "GxB_PLUS_RDIV", "GxB_TIMES_RDIV", "GxB_ANY_RDIV", "GxB_MIN_RMINUS", "GxB_MAX_RMINUS", "GxB_PLUS_RMINUS", "GxB_TIMES_RMINUS", "GxB_ANY_RMINUS", "GxB_MIN_ISEQ", "GxB_MAX_ISEQ", "GxB_PLUS_ISEQ", "GxB_TIMES_ISEQ", "GxB_ANY_ISEQ", "GxB_MIN_ISNE", "GxB_MAX_ISNE", "GxB_PLUS_ISNE", "GxB_TIMES_ISNE", "GxB_ANY_ISNE", "GxB_MIN_ISGT", "GxB_MAX_ISGT", "GxB_PLUS_ISGT", "GxB_TIMES_ISGT", "GxB_ANY_ISGT", "GxB_MIN_ISLT", "GxB_MAX_ISLT", "GxB_PLUS_ISLT", "GxB_TIMES_ISLT", "GxB_ANY_ISLT", "GxB_MIN_ISGE", "GxB_MAX_ISGE", "GxB_PLUS_ISGE", "GxB_TIMES_ISGE", "GxB_ANY_ISGE", "GxB_MIN_ISLE", "GxB_MAX_ISLE", "GxB_PLUS_ISLE", "GxB_TIMES_ISLE", "GxB_ANY_ISLE", "GxB_MIN_LOR", "GxB_MAX_LOR", "GxB_PLUS_LOR", "GxB_TIMES_LOR", "GxB_ANY_LOR", "GxB_MIN_LAND", "GxB_MAX_LAND", "GxB_PLUS_LAND", "GxB_TIMES_LAND", "GxB_ANY_LAND", "GxB_MIN_LXOR", "GxB_MAX_LXOR", "GxB_PLUS_LXOR", "GxB_TIMES_LXOR", "GxB_ANY_LXOR", "GxB_LOR_NE", "GxB_LOR_EQ", "GxB_LAND_EQ", "GxB_LXOR_EQ", "GxB_EQ_EQ", "GxB_ANY_EQ", "GxB_LAND_NE", "GxB_LXOR_NE", "GxB_EQ_NE", "GxB_ANY_NE", "GxB_LOR_GT", "GxB_LAND_GT", "GxB_LXOR_GT", "GxB_EQ_GT", "GxB_ANY_GT", "GxB_LOR_LT", "GxB_LAND_LT", "GxB_LXOR_LT", "GxB_EQ_LT", "GxB_ANY_LT", "GxB_LOR_GE", "GxB_LAND_GE", "GxB_LXOR_GE", "GxB_EQ_GE", "GxB_ANY_GE", "GxB_LOR_LE", "GxB_LAND_LE", "GxB_LXOR_LE", "GxB_EQ_LE", "GxB_ANY_LE", "GxB_LOR_FIRST", "GxB_LAND_FIRST", "GxB_LXOR_FIRST", "GxB_EQ_FIRST", "GxB_LOR_SECOND", "GxB_LAND_SECOND", "GxB_LXOR_SECOND", "GxB_EQ_SECOND", "GxB_LOR_PAIR", "GxB_LAND_PAIR", "GxB_LXOR_PAIR", "GxB_EQ_PAIR", "GxB_LOR_LOR", "GxB_LXOR_LOR", "GxB_EQ_LOR", "GxB_LAND_LAND", "GxB_EQ_LAND", "GxB_LOR_LXOR", "GxB_LAND_LXOR", "GxB_LXOR_LXOR", "GxB_EQ_LXOR", "GxB_BOR_BOR", "GxB_BOR_BAND", "GxB_BOR_BXOR", "GxB_BOR_BXNOR", "GxB_BAND_BOR", "GxB_BAND_BAND", "GxB_BAND_BXOR", "GxB_BAND_BXNOR", "GxB_BXOR_BOR", "GxB_BXOR_BAND", "GxB_BXOR_BXOR", "GxB_BXOR_BXNOR", "GxB_BXNOR_BOR", "GxB_BXNOR_BAND", "GxB_BXNOR_BXOR", "GxB_BXNOR_BXNOR", "GxB_MIN_FIRSTI", "GxB_MAX_FIRSTI", "GxB_ANY_FIRSTI", "GxB_PLUS_FIRSTI", "GxB_TIMES_FIRSTI", "GxB_MIN_FIRSTI1", "GxB_MAX_FIRSTI1", "GxB_ANY_FIRSTI1", "GxB_PLUS_FIRSTI1", "GxB_TIMES_FIRSTI1", "GxB_MIN_FIRSTJ", "GxB_MAX_FIRSTJ", "GxB_ANY_FIRSTJ", "GxB_PLUS_FIRSTJ", "GxB_TIMES_FIRSTJ", "GxB_MIN_FIRSTJ1", "GxB_MAX_FIRSTJ1", "GxB_ANY_FIRSTJ1", "GxB_PLUS_FIRSTJ1", "GxB_TIMES_FIRSTJ1", "GxB_MIN_SECONDI", "GxB_MAX_SECONDI", "GxB_ANY_SECONDI", "GxB_PLUS_SECONDI", "GxB_TIMES_SECONDI", "GxB_MIN_SECONDI1", "GxB_MAX_SECONDI1", "GxB_ANY_SECONDI1", "GxB_PLUS_SECONDI1", "GxB_TIMES_SECONDI1", "GxB_MIN_SECONDJ", "GxB_MAX_SECONDJ", "GxB_ANY_SECONDJ", "GxB_PLUS_SECONDJ", "GxB_TIMES_SECONDJ", "GxB_MIN_SECONDJ1", "GxB_MAX_SECONDJ1", "GxB_ANY_SECONDJ1", "GxB_PLUS_SECONDJ1", "GxB_TIMES_SECONDJ1", "GrB_PLUS_TIMES_SEMIRING", "GrB_PLUS_MIN_SEMIRING", "GrB_MIN_PLUS_SEMIRING", "GrB_MIN_TIMES_SEMIRING", "GrB_MIN_FIRST_SEMIRING", "GrB_MIN_SECOND_SEMIRING", "GrB_MIN_MAX_SEMIRING", "GrB_MAX_PLUS_SEMIRING", "GrB_MAX_TIMES_SEMIRING", "GrB_MAX_FIRST_SEMIRING", "GrB_MAX_SECOND_SEMIRING", "GrB_MAX_MIN_SEMIRING", "GrB_LOR_LAND_SEMIRING", "GrB_LAND_LOR_SEMIRING", "GrB_LXOR_LAND_SEMIRING", "GrB_LXNOR_LOR_SEMIRING"]
    for name ∈ builtins
        containername, exportedname = semiringnames(name)
        structquote = quote
            struct $containername <: AbstractSemiring
                pointers::Dict{DataType, libgb.GrB_Semiring}
                name::String
                $containername() = new(Dict{DataType, libgb.GrB_Semiring}(), $name)
            end
        end
        @eval(Types, $structquote)
        constquote = quote
            const $exportedname = Types.$containername()
            export $exportedname
        end
        @eval(Semirings,$constquote)
    end
end

function load(rig::AbstractSemiring)
    booleans = ["GxB_LOR_FIRST", "GxB_LAND_FIRST", "GxB_LXOR_FIRST", "GxB_EQ_FIRST", "GxB_ANY_FIRST", "GxB_LOR_SECOND", "GxB_LAND_SECOND", "GxB_LXOR_SECOND", "GxB_EQ_SECOND", "GxB_ANY_SECOND", "GxB_LOR_PAIR", "GxB_LAND_PAIR", "GxB_LXOR_PAIR", "GxB_EQ_PAIR", "GxB_ANY_PAIR", "GxB_LOR_LOR", "GxB_LXOR_LOR", "GxB_EQ_LOR", "GxB_ANY_LOR", "GxB_LAND_LAND", "GxB_EQ_LAND", "GxB_ANY_LAND", "GxB_LOR_LXOR", "GxB_LAND_LXOR", "GxB_LXOR_LXOR", "GxB_EQ_LXOR", "GxB_ANY_LXOR", "GxB_LOR_EQ", "GxB_LAND_EQ", "GxB_LXOR_EQ", "GxB_EQ_EQ", "GxB_ANY_EQ", "GxB_LOR_GT", "GxB_LAND_GT", "GxB_LXOR_GT", "GxB_EQ_GT", "GxB_ANY_GT", "GxB_LOR_LT", "GxB_LAND_LT", "GxB_LXOR_LT", "GxB_EQ_LT", "GxB_ANY_LT", "GxB_LOR_GE", "GxB_LAND_GE", "GxB_LXOR_GE", "GxB_EQ_GE", "GxB_ANY_GE", "GxB_LOR_LE", "GxB_LAND_LE", "GxB_LXOR_LE", "GxB_EQ_LE", "GxB_ANY_LE", "GrB_LOR_LAND_SEMIRING", "GrB_LAND_LOR_SEMIRING", "GrB_LXOR_LAND_SEMIRING", "GrB_LXNOR_LOR_SEMIRING"]

    integers = ["GxB_PLUS_FIRST", "GxB_TIMES_FIRST", "GxB_ANY_FIRST", "GxB_PLUS_SECOND", "GxB_TIMES_SECOND", "GxB_ANY_SECOND", "GxB_MIN_PAIR", "GxB_MAX_PAIR", "GxB_PLUS_PAIR", "GxB_TIMES_PAIR", "GxB_ANY_PAIR", "GxB_MIN_MIN", "GxB_TIMES_MIN", "GxB_ANY_MIN", "GxB_MAX_MAX", "GxB_PLUS_MAX", "GxB_TIMES_MAX", "GxB_ANY_MAX", "GxB_PLUS_PLUS", "GxB_TIMES_PLUS", "GxB_ANY_PLUS", "GxB_MIN_MINUS", "GxB_MAX_MINUS", "GxB_PLUS_MINUS", "GxB_TIMES_MINUS", "GxB_ANY_MINUS", "GxB_TIMES_TIMES", "GxB_ANY_TIMES", "GxB_MIN_DIV", "GxB_MAX_DIV", "GxB_PLUS_DIV", "GxB_TIMES_DIV", "GxB_ANY_DIV", "GxB_MIN_RDIV", "GxB_MAX_RDIV", "GxB_PLUS_RDIV", "GxB_TIMES_RDIV", "GxB_ANY_RDIV", "GxB_MIN_RMINUS", "GxB_MAX_RMINUS", "GxB_PLUS_RMINUS", "GxB_TIMES_RMINUS", "GxB_ANY_RMINUS", "GxB_MIN_ISEQ", "GxB_MAX_ISEQ", "GxB_PLUS_ISEQ", "GxB_TIMES_ISEQ", "GxB_ANY_ISEQ", "GxB_MIN_ISNE", "GxB_MAX_ISNE", "GxB_PLUS_ISNE", "GxB_TIMES_ISNE", "GxB_ANY_ISNE", "GxB_MIN_ISGT", "GxB_MAX_ISGT", "GxB_PLUS_ISGT", "GxB_TIMES_ISGT", "GxB_ANY_ISGT", "GxB_MIN_ISLT", "GxB_MAX_ISLT", "GxB_PLUS_ISLT", "GxB_TIMES_ISLT", "GxB_ANY_ISLT", "GxB_MIN_ISGE", "GxB_MAX_ISGE", "GxB_PLUS_ISGE", "GxB_TIMES_ISGE", "GxB_ANY_ISGE", "GxB_MIN_ISLE", "GxB_MAX_ISLE", "GxB_PLUS_ISLE", "GxB_TIMES_ISLE", "GxB_ANY_ISLE", "GxB_MIN_LOR", "GxB_MAX_LOR", "GxB_PLUS_LOR", "GxB_TIMES_LOR", "GxB_ANY_LOR", "GxB_MIN_LAND", "GxB_MAX_LAND", "GxB_PLUS_LAND", "GxB_TIMES_LAND", "GxB_ANY_LAND", "GxB_MIN_LXOR", "GxB_MAX_LXOR", "GxB_PLUS_LXOR", "GxB_TIMES_LXOR", "GxB_ANY_LXOR", "GxB_LOR_NE", "GxB_LOR_EQ", "GxB_LAND_EQ", "GxB_LXOR_EQ", "GxB_EQ_EQ", "GxB_ANY_EQ", "GxB_LAND_NE", "GxB_LXOR_NE", "GxB_EQ_NE", "GxB_ANY_NE", "GxB_LOR_GT", "GxB_LAND_GT", "GxB_LXOR_GT", "GxB_EQ_GT", "GxB_ANY_GT", "GxB_LOR_LT", "GxB_LAND_LT", "GxB_LXOR_LT", "GxB_EQ_LT", "GxB_ANY_LT", "GxB_LOR_GE", "GxB_LAND_GE", "GxB_LXOR_GE", "GxB_EQ_GE", "GxB_ANY_GE", "GxB_LOR_LE", "GxB_LAND_LE", "GxB_LXOR_LE", "GxB_EQ_LE", "GxB_ANY_LE", "GrB_PLUS_TIMES_SEMIRING", "GrB_PLUS_MIN_SEMIRING", "GrB_MIN_PLUS_SEMIRING", "GrB_MIN_TIMES_SEMIRING", "GrB_MIN_FIRST_SEMIRING", "GrB_MIN_SECOND_SEMIRING", "GrB_MIN_MAX_SEMIRING", "GrB_MAX_PLUS_SEMIRING", "GrB_MAX_TIMES_SEMIRING", "GrB_MAX_FIRST_SEMIRING", "GrB_MAX_SECOND_SEMIRING", "GrB_MAX_MIN_SEMIRING"]

    unsignedintegers = ["GxB_PLUS_FIRST", "GxB_TIMES_FIRST", "GxB_ANY_FIRST", "GxB_PLUS_SECOND", "GxB_TIMES_SECOND", "GxB_ANY_SECOND", "GxB_MIN_PAIR", "GxB_MAX_PAIR", "GxB_PLUS_PAIR", "GxB_TIMES_PAIR", "GxB_ANY_PAIR", "GxB_MIN_MIN", "GxB_TIMES_MIN", "GxB_ANY_MIN", "GxB_MAX_MAX", "GxB_PLUS_MAX", "GxB_TIMES_MAX", "GxB_ANY_MAX", "GxB_PLUS_PLUS", "GxB_TIMES_PLUS", "GxB_ANY_PLUS", "GxB_MIN_MINUS", "GxB_MAX_MINUS", "GxB_PLUS_MINUS", "GxB_TIMES_MINUS", "GxB_ANY_MINUS", "GxB_TIMES_TIMES", "GxB_ANY_TIMES", "GxB_MIN_DIV", "GxB_MAX_DIV", "GxB_PLUS_DIV", "GxB_TIMES_DIV", "GxB_ANY_DIV", "GxB_MIN_RDIV", "GxB_MAX_RDIV", "GxB_PLUS_RDIV", "GxB_TIMES_RDIV", "GxB_ANY_RDIV", "GxB_MIN_RMINUS", "GxB_MAX_RMINUS", "GxB_PLUS_RMINUS", "GxB_TIMES_RMINUS", "GxB_ANY_RMINUS", "GxB_MIN_ISEQ", "GxB_MAX_ISEQ", "GxB_PLUS_ISEQ", "GxB_TIMES_ISEQ", "GxB_ANY_ISEQ", "GxB_MIN_ISNE", "GxB_MAX_ISNE", "GxB_PLUS_ISNE", "GxB_TIMES_ISNE", "GxB_ANY_ISNE", "GxB_MIN_ISGT", "GxB_MAX_ISGT", "GxB_PLUS_ISGT", "GxB_TIMES_ISGT", "GxB_ANY_ISGT", "GxB_MIN_ISLT", "GxB_MAX_ISLT", "GxB_PLUS_ISLT", "GxB_TIMES_ISLT", "GxB_ANY_ISLT", "GxB_MIN_ISGE", "GxB_MAX_ISGE", "GxB_PLUS_ISGE", "GxB_TIMES_ISGE", "GxB_ANY_ISGE", "GxB_MIN_ISLE", "GxB_MAX_ISLE", "GxB_PLUS_ISLE", "GxB_TIMES_ISLE", "GxB_ANY_ISLE", "GxB_MIN_LOR", "GxB_MAX_LOR", "GxB_PLUS_LOR", "GxB_TIMES_LOR", "GxB_ANY_LOR", "GxB_MIN_LAND", "GxB_MAX_LAND", "GxB_PLUS_LAND", "GxB_TIMES_LAND", "GxB_ANY_LAND", "GxB_MIN_LXOR", "GxB_MAX_LXOR", "GxB_PLUS_LXOR", "GxB_TIMES_LXOR", "GxB_ANY_LXOR", "GxB_LOR_NE", "GxB_LOR_EQ", "GxB_LAND_EQ", "GxB_LXOR_EQ", "GxB_EQ_EQ", "GxB_ANY_EQ", "GxB_LAND_NE", "GxB_LXOR_NE", "GxB_EQ_NE", "GxB_ANY_NE", "GxB_LOR_GT", "GxB_LAND_GT", "GxB_LXOR_GT", "GxB_EQ_GT", "GxB_ANY_GT", "GxB_LOR_LT", "GxB_LAND_LT", "GxB_LXOR_LT", "GxB_EQ_LT", "GxB_ANY_LT", "GxB_LOR_GE", "GxB_LAND_GE", "GxB_LXOR_GE", "GxB_EQ_GE", "GxB_ANY_GE", "GxB_LOR_LE", "GxB_LAND_LE", "GxB_LXOR_LE", "GxB_EQ_LE", "GxB_ANY_LE", "GxB_BOR_BOR", "GxB_BOR_BAND", "GxB_BOR_BXOR", "GxB_BOR_BXNOR", "GxB_BAND_BOR", "GxB_BAND_BAND", "GxB_BAND_BXOR", "GxB_BAND_BXNOR", "GxB_BXOR_BOR", "GxB_BXOR_BAND", "GxB_BXOR_BXOR", "GxB_BXOR_BXNOR", "GxB_BXNOR_BOR", "GxB_BXNOR_BAND", "GxB_BXNOR_BXOR", "GxB_BXNOR_BXNOR", "GrB_PLUS_TIMES_SEMIRING", "GrB_PLUS_MIN_SEMIRING", "GrB_MIN_PLUS_SEMIRING", "GrB_MIN_TIMES_SEMIRING", "GrB_MIN_FIRST_SEMIRING", "GrB_MIN_SECOND_SEMIRING", "GrB_MIN_MAX_SEMIRING", "GrB_MAX_PLUS_SEMIRING", "GrB_MAX_TIMES_SEMIRING", "GrB_MAX_FIRST_SEMIRING", "GrB_MAX_SECOND_SEMIRING", "GrB_MAX_MIN_SEMIRING"]

    floats = ["GxB_PLUS_FIRST", "GxB_TIMES_FIRST", "GxB_ANY_FIRST", "GxB_PLUS_SECOND", "GxB_TIMES_SECOND", "GxB_ANY_SECOND", "GxB_MIN_PAIR", "GxB_MAX_PAIR", "GxB_PLUS_PAIR", "GxB_TIMES_PAIR", "GxB_ANY_PAIR", "GxB_MIN_MIN", "GxB_TIMES_MIN", "GxB_ANY_MIN", "GxB_MAX_MAX", "GxB_PLUS_MAX", "GxB_TIMES_MAX", "GxB_ANY_MAX", "GxB_PLUS_PLUS", "GxB_TIMES_PLUS", "GxB_ANY_PLUS", "GxB_MIN_MINUS", "GxB_MAX_MINUS", "GxB_PLUS_MINUS", "GxB_TIMES_MINUS", "GxB_ANY_MINUS", "GxB_TIMES_TIMES", "GxB_ANY_TIMES", "GxB_MIN_DIV", "GxB_MAX_DIV", "GxB_PLUS_DIV", "GxB_TIMES_DIV", "GxB_ANY_DIV", "GxB_MIN_RDIV", "GxB_MAX_RDIV", "GxB_PLUS_RDIV", "GxB_TIMES_RDIV", "GxB_ANY_RDIV", "GxB_MIN_RMINUS", "GxB_MAX_RMINUS", "GxB_PLUS_RMINUS", "GxB_TIMES_RMINUS", "GxB_ANY_RMINUS", "GxB_MIN_ISEQ", "GxB_MAX_ISEQ", "GxB_PLUS_ISEQ", "GxB_TIMES_ISEQ", "GxB_ANY_ISEQ", "GxB_MIN_ISNE", "GxB_MAX_ISNE", "GxB_PLUS_ISNE", "GxB_TIMES_ISNE", "GxB_ANY_ISNE", "GxB_MIN_ISGT", "GxB_MAX_ISGT", "GxB_PLUS_ISGT", "GxB_TIMES_ISGT", "GxB_ANY_ISGT", "GxB_MIN_ISLT", "GxB_MAX_ISLT", "GxB_PLUS_ISLT", "GxB_TIMES_ISLT", "GxB_ANY_ISLT", "GxB_MIN_ISGE", "GxB_MAX_ISGE", "GxB_PLUS_ISGE", "GxB_TIMES_ISGE", "GxB_ANY_ISGE", "GxB_MIN_ISLE", "GxB_MAX_ISLE", "GxB_PLUS_ISLE", "GxB_TIMES_ISLE", "GxB_ANY_ISLE", "GxB_MIN_LOR", "GxB_MAX_LOR", "GxB_PLUS_LOR", "GxB_TIMES_LOR", "GxB_ANY_LOR", "GxB_MIN_LAND", "GxB_MAX_LAND", "GxB_PLUS_LAND", "GxB_TIMES_LAND", "GxB_ANY_LAND", "GxB_MIN_LXOR", "GxB_MAX_LXOR", "GxB_PLUS_LXOR", "GxB_TIMES_LXOR", "GxB_ANY_LXOR", "GxB_LOR_NE", "GxB_LOR_EQ", "GxB_LAND_EQ", "GxB_LXOR_EQ", "GxB_EQ_EQ", "GxB_ANY_EQ", "GxB_LAND_NE", "GxB_LXOR_NE", "GxB_EQ_NE", "GxB_ANY_NE", "GxB_LOR_GT", "GxB_LAND_GT", "GxB_LXOR_GT", "GxB_EQ_GT", "GxB_ANY_GT", "GxB_LOR_LT", "GxB_LAND_LT", "GxB_LXOR_LT", "GxB_EQ_LT", "GxB_ANY_LT", "GxB_LOR_GE", "GxB_LAND_GE", "GxB_LXOR_GE", "GxB_EQ_GE", "GxB_ANY_GE", "GxB_LOR_LE", "GxB_LAND_LE", "GxB_LXOR_LE", "GxB_EQ_LE", "GxB_ANY_LE", "GrB_PLUS_TIMES_SEMIRING", "GrB_PLUS_MIN_SEMIRING", "GrB_MIN_PLUS_SEMIRING", "GrB_MIN_TIMES_SEMIRING", "GrB_MIN_FIRST_SEMIRING", "GrB_MIN_SECOND_SEMIRING", "GrB_MIN_MAX_SEMIRING", "GrB_MAX_PLUS_SEMIRING", "GrB_MAX_TIMES_SEMIRING", "GrB_MAX_FIRST_SEMIRING", "GrB_MAX_SECOND_SEMIRING", "GrB_MAX_MIN_SEMIRING"]

    positionals = ["GxB_MIN_FIRSTI", "GxB_MAX_FIRSTI", "GxB_ANY_FIRSTI", "GxB_PLUS_FIRSTI", "GxB_TIMES_FIRSTI", "GxB_MIN_FIRSTI1", "GxB_MAX_FIRSTI1", "GxB_ANY_FIRSTI1", "GxB_PLUS_FIRSTI1", "GxB_TIMES_FIRSTI1", "GxB_MIN_FIRSTJ", "GxB_MAX_FIRSTJ", "GxB_ANY_FIRSTJ", "GxB_PLUS_FIRSTJ", "GxB_TIMES_FIRSTJ", "GxB_MIN_FIRSTJ1", "GxB_MAX_FIRSTJ1", "GxB_ANY_FIRSTJ1", "GxB_PLUS_FIRSTJ1", "GxB_TIMES_FIRSTJ1", "GxB_MIN_SECONDI", "GxB_MAX_SECONDI", "GxB_ANY_SECONDI", "GxB_PLUS_SECONDI", "GxB_TIMES_SECONDI", "GxB_MIN_SECONDI1", "GxB_MAX_SECONDI1", "GxB_ANY_SECONDI1", "GxB_PLUS_SECONDI1", "GxB_TIMES_SECONDI1", "GxB_MIN_SECONDJ", "GxB_MAX_SECONDJ", "GxB_ANY_SECONDJ", "GxB_PLUS_SECONDJ", "GxB_TIMES_SECONDJ", "GxB_MIN_SECONDJ1", "GxB_MAX_SECONDJ1", "GxB_ANY_SECONDJ1", "GxB_PLUS_SECONDJ1", "GxB_TIMES_SECONDJ1"]
    name = rig.name
    if name ∈ booleans
        rig.pointers[Bool] = load_global(name * "_BOOL")
    end

    if name ∈ integers
        rig.pointers[Int8] =load_global(name * "_INT8")
        rig.pointers[Int16] = load_global(name * "_INT16")
        rig.pointers[Int32] = load_global(name * "_INT32")
        rig.pointers[Int64] = load_global(name * "_INT64")
    end

    if name ∈ unsignedintegers
        rig.pointers[UInt8] =load_global(name * "_UINT8")
        rig.pointers[UInt16] = load_global(name * "_UINT16")
        rig.pointers[UInt32] = load_global(name * "_UINT32")
        rig.pointers[UInt64] = load_global(name * "_UINT64")
    end

    if name ∈ floats
        rig.pointers[Float32] = load_global(name * "_FP32")
        rig.pointers[Float64] = load_global(name * "_FP64")
    end
end

multiplyop(rig::libgb.GrB_Semiring) = libgb.GxB_Semiring_multiply(rig)
addop(rig::libgb.GrB_Semiring) = libgb.GxB_Semiring_add(rig)

xtype(rig::SemiringUnion) = xtype(addop(rig))
ytype(rig::SemiringUnion) = ytype(addop(rig))
ztype(rig::SemiringUnion) = ztype(addop(rig))


import POMDPs: transition, reward, initialstate_distribution
import POMDPs: generate_sr, generate_o

struct W <: POMDP{Int, Bool, Int} end
println("Warning expected:")
@test_throws MethodError initialstate(W(), Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_s(W(), 1, true, Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_sr(W(), 1, true, Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_o(W(), 1, true, 2, Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_so(W(), 1, true, Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_sor(W(), 1, true, Random.GLOBAL_RNG)
println("Warning expected:")
@test_throws MethodError generate_or(W(), 1, true, 2, Random.GLOBAL_RNG)

mutable struct B <: POMDP{Int, Bool, Bool} end

transition(b::B, s::Int, a::Bool) = Int[s+a]
@test implemented(generate_s, Tuple{B, Int, Bool, MersenneTwister})
@test generate_s(B(), 1, false, Random.GLOBAL_RNG) == 1

@test !@implemented generate_sor(::B, ::Int, ::Bool, ::MersenneTwister)
# @test_throws MethodError generate_sor(B(), 1, false, Random.GLOBAL_RNG)

reward(b::B, s::Int, a::Bool, sp::Int) = -1.0
@test generate_sr(B(), 1, false, Random.GLOBAL_RNG) == (1, -1.0)

generate_o(b::B, s::Int, a::Bool, sp::Int, rng::AbstractRNG) = sp
@test @implemented generate_o(b::B, s::Int, a::Bool, sp::Int, rng::AbstractRNG)
@test @implemented generate_so(b::B, s::Int, a::Bool, rng::MersenneTwister)
@test @implemented generate_sor(b::B, s::Int, a::Bool, rng::MersenneTwister)
@test generate_sor(B(), 1, true, Random.GLOBAL_RNG) == (2, 2, -1.0) # should throw sor error

initialstate_distribution(b::B) = Int[1,2,3]
@test initialstate(B(), Random.GLOBAL_RNG) in initialstate_distribution(B())

mutable struct C <: POMDP{Nothing, Nothing, Nothing} end
generate_sr(c::C, s::Nothing, a::Nothing, rng::AbstractRNG) = nothing, 0.0
generate_o(c::C, s::Nothing, a::Nothing, sp::Nothing, rng::AbstractRNG) = nothing
@test @implemented generate_sor(::C, ::Nothing, ::Nothing, ::MersenneTwister)
@test generate_sor(C(), nothing, nothing, Random.GLOBAL_RNG) == (nothing, nothing, 0.0)


# This file is part of Kpax3. License is MIT.

include("joint/densities.jl")

include("likelihoods/log_likelihood.jl")
include("likelihoods/marginal_likelihood.jl")
include("likelihoods/merge.jl")
include("likelihoods/split.jl")
include("likelihoods/biased_random_walk.jl")

include("partitioncols/aminoacids/densities.jl")
include("partitioncols/aminoacids/simulate.jl")

include("partitionrows/ewenspitman/densities.jl")
include("partitionrows/ewenspitman/mcmc.jl")

include("loss_functions/binder.jl")


module Metamaterials

using Devices, Clipper
import Devices: Meta, GDSMeta, AbstractPolygon, Coordinate

export double_spiral!, rectangular_meander!, interdigit_capacitor!,
    lumped_elem_resonator!, lumped_elem_resonator_with_Ck!, negative,
    cross_polygon

include("double_spiral.jl")
include("interdigit_capacitor.jl")
include("rectangular_meander.jl")

"""
    lumped_elem_resonator!{T,S}(c::Cell{T,S}, Lr_width, Lr_height,
        Cr_overlap, conductor_width, conductor_gap, meta::Meta=GDSMeta(0,0);
        in_length=zero(T), out_length=zero(T))
Create a positive trace of symmetric lumped-element resonator with
specified double spiral coil dimensions (`Lr_width`, `Lr_height`), capacitor
overlap (`Cr_overlap`). The conductor properties are defined with
`conductor_width` and `conductor_gap`. The length of the conductor that goes
in and out of the resonator can be specified by keyword arguments
`in_length` and `out_length`.
"""
function lumped_elem_resonator!(c::Cell{T,S}, Lr_width, Lr_height, Cr_overlap,
    conductor_width, conductor_gap, meta::Meta=GDSMeta(0,0);
    in_length=zero(T), out_length=zero(T)) where {T,S}

    # width and height of the resonator as a whole
    width, height = Lr_width, 2 * Lr_height + Cr_overlap + conductor_gap

    # constants for rectangular coils
    connect_length = Cr_overlap / 2 + conductor_gap

    # constants for interdigitial capacitor
    terminal_width = zero(T)
    terminal_length = Lr_width - conductor_gap - conductor_width

    # cells
    upper_coil = Cell{T,S}(uniquename("upper_coil_Lr"))
    double_spiral!(upper_coil, Lr_width, Lr_height, conductor_width,
        conductor_gap, meta, in_length=connect_length, out_length=out_length)

    lower_coil = Cell{T,S}(uniquename("lower_coil_Lr"))
    double_spiral!(lower_coil, Lr_width, Lr_height, conductor_width,
        conductor_gap, meta, clockwise=false, in_length=connect_length,
        out_length=in_length)

    cap = Cell{T,S}(uniquename("capacitor_Cr"))
    interdigit_capacitor!(cap, terminal_width, terminal_length, Cr_overlap,
        conductor_width, conductor_gap, conductor_gap, meta)

    # rendering cells
    pointoffset = Point(-width, Lr_height + connect_length + in_length)

    trans_upper_coil = Translation(pointoffset...)
    upper_coil_polys = [trans_upper_coil(p) for
        p in polygon.(elements(upper_coil))]

    trans_lower_coil = Translation(pointoffset...) ∘ Rotation(π)
    lower_coil_polys = [trans_lower_coil(p) for
        p in polygon.(elements(lower_coil))]

    capoffset = pointoffset + Point(conductor_width + conductor_gap,
        connect_length)
    trans_cap = Translation(capoffset...) ∘ Rotation(-π/2)
    cap_polys = [trans_cap(p) for p in polygon.(elements(cap))]

    for p in upper_coil_polys
        render!(c, p, meta)
    end
    for p in cap_polys
        render!(c, p, meta)
    end
    for p in lower_coil_polys
        render!(c, p, meta)
    end
    return c
end

    """ lumped_elem_resonator_with_Ck!{T,S}(c::Cell{T,S}, Lr_width, Lr_height,
            Cr_overlap, Ck_overlap, Ck_terminal_width, conductor_width,
            conductor_gap, meta::Meta=GDSMeta(0,0); ground_length=zero(T),
            Ck_terminal_width2=Ck_terminal_width)
    Create a positive trace of symmetric lumped-element resonator with coupling
    capacitor. The coil dimensions (`Lr_width`, `Lr_height`) and capacitor
    overlap (`Cr_overlap`) defines the resonator. The terminal width
    (`Ck_terminal_width`) and the overlap (`Ck_overlap`) characterizes the
    coupling capacitor. The conductor properties are defined with
    `conductor_width` and `conductor_gap`. The width of the terminal from the
    origin and the length of the conductor into the ground can be specified with
    keyword arguments `Ck_terminal_width2` and `ground_length`, respectively.
    """
    function lumped_elem_resonator_with_Ck!(c::Cell{T,S}, Lr_width, Lr_height,
        Cr_overlap, Ck_overlap, Ck_terminal_width, conductor_width,
        conductor_gap, meta::Meta=GDSMeta(0,0); ground_length=zero(T),
        Ck_terminal_width2=Ck_terminal_width) where {T,S}

        # resonator
        res = Cell{T,S}(uniquename("lumped_elem_res_LrCr"))
        lumped_elem_resonator!(res, Lr_width, Lr_height, Cr_overlap,
            conductor_width, conductor_gap, meta,
            in_length=Ck_terminal_width+conductor_gap,
            out_length=ground_length)

        # capacitor
        cap = Cell{T,S}(uniquename("coupling_cap_Ck"))
        Ck_terminal_length = Lr_width - 2 * conductor_width
        interdigit_capacitor!(cap, Ck_terminal_width, Ck_terminal_length,
            Ck_overlap, conductor_width, conductor_gap, conductor_gap, meta,
            terminal_width2=Ck_terminal_width2)

        # offset point
        p_x = -Ck_terminal_length/2
        p_y = Ck_overlap+2*conductor_gap+Ck_terminal_width+Ck_terminal_width2
        pointoffset = Point(p_x, p_y)

        Ck_ref = CellReference(cap, pointoffset, rot=-π/2)
        res_ref = CellReference(res,
        Point(Lr_width/2, Ck_overlap + 2 * conductor_gap + Ck_terminal_width2))

        push!(c.refs, Ck_ref)
        push!(c.refs, res_ref)
        return c
    end

    """
        negative{T,S}(c::Cell{T,S}, mask::AbstractPolygon,
            meta::Meta=GDSMeta(0,0))
    Take whatever is drawn in the cell off from the mask polygon. i.e. create a
    negative cell drawing within the extent of mask. The references of the
    returned cell is discarded and only the flattened cell remains.
    """
    function negative(c::Cell{T,S}, mask::AbstractPolygon{U},
        meta::Meta=GDSMeta(0,0)) where {T,S,U<:Coordinate}
        c = flatten!(c)
        poly = polygon.(elements(c))

        neg = clip(Clipper.ClipTypeDifference, [mask], poly)[1]
        cell = Cell{T,S}(uniquename("negative_$(name(c))"))
        render!(cell, neg, meta)
        return cell
    end
    function negative(c::Cell{T,S}, mask::AbstractVector{U},
        meta::Meta=GDSMeta(0,0)) where {T,S,U<:AbstractPolygon}
        c = flatten!(c)
        poly = polygon.(elements(c))

        neg = clip(Clipper.ClipTypeDifference, mask, poly)[1]
        cell = Cell{T,S}(uniquename("negative_$(name(c))"))
        render!(cell, neg, meta)
        return cell
    end

end # module


# run_alg.jl
using SPECTrecon
include("eml_em.jl")


MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true

function MOI.set(model::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense)
    if sense == MOI.MIN_SENSE
        model.inner.objective_sense = :Min
    elseif sense == MOI.MAX_SENSE
        model.inner.objective_sense = :Max
    elseif sense == MOI.FEASIBILITY_SENSE
        model.inner.objective_sense = :Feasibility
    else
        error("Unsupported objective sense: $sense")
    end
    return
end

MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{SAF}) = true
MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{SQF}) = true

function MOI.set(model::Optimizer, ::MOI.ObjectiveFunction{F}, obj::F) where {F<:Union{SV, SAF, SQF}}
    model.inner.objective_expr = to_expr(obj)
    return
end


using Test
using NeXLSpectrum

include("window.jl")
include("detefficiency.jl")
include("spectrum.jl")
include("emsa.jl")
include("brukerspx.jl")
include("filterfit.jl")
include("hyperspectrum.jl")
include("line.jl")
include("multikev.jl")
include("standardize.jl")
include("multidet.jl")


abstract type Boundary end
abstract type BoundaryMedium end

abstract type BoundaryFlatSubtypes end

struct PeriodicBoundaryCondition<:BoundaryMedium end

defaultFlatBoundary(x,t) = 0
defaultFlatBoundary(x,y,t) = 0
defaultFlatBoundary(x,y,z,t) = 0

struct DirichletBoundaryCondition<:BoundaryMedium
    fMin::Function
    fMax::Function
end
function DirichletBoundaryCondition(f::Function)
    return DirichletBoundaryCondition(f,f)
end
function DirichletBoundaryCondition()
    return DirichletBoundaryCondition(defaultFlatBoundary,defaultFlatBoundary)
end
struct DirichletBoundaryCondition_NewmannBoundaryCondition<:BoundaryMedium
    fMin::Function
    fMax::Function
end
function DirichletBoundaryCondition_NewmannBoundaryCondition()
    return DirichletBoundaryCondition_NewmannBoundaryCondition(defaultFlatBoundary,defaultFlatBoundary)
end

struct NewmannBoundaryCondition<:BoundaryMedium
    fMin::Function
    fMax::Function
end
function NewmannBoundaryCondition(f::Function)
    return NewmannBoundaryCondition(f,f)
end
function NewmannBoundaryCondition()
    return NewmannBoundaryCondition(defaultFlatBoundary,defaultFlatBoundary)
end
struct NewmannBoundaryCondition_DirichletBoundaryCondition<:BoundaryMedium
    fMin::Function
    fMax::Function
end
function NewmannBoundaryCondition_DirichletBoundaryCondition()
    return NewmannBoundaryCondition_DirichletBoundaryCondition(defaultFlatBoundary,defaultFlatBoundary)
end


using Zeros
using Test
using Test: @inferred

@testset "ambiguities" begin
    ambiguities = detect_ambiguities(Base, Zeros)
    for a in ambiguities
        println(a[1], "\n", a[2], "\n")
    end
    @test length(ambiguities) <= 10
end

const Z = Zero()
const I = One()

@testset "Promotion" begin
    @test promote_type(One, Bool) === Bool
    @test promote_type(One, Int32) === Int32
    @test promote_type(One, Int64) === Int64
    @test promote_type(One, Float32) === Float32
    @test promote_type(One, Float64) === Float64
    @test promote_type(One, Complex{Bool}) === Complex{Bool}
    @test promote_type(One, Complex{Float64}) === Complex{Float64}
    @test promote_type(Zero, Bool) === Bool
    @test promote_type(Zero, Int32) === Int32
    @test promote_type(Zero, Int64) === Int64
    @test promote_type(Zero, Float32) === Float32
    @test promote_type(Zero, Float64) === Float64
    @test promote_type(Zero, Complex{Bool}) === Complex{Bool}
    @test promote_type(Zero, Complex{Float64}) === Complex{Float64}
end

@testset "Real" begin
    @test iszero(Z) === true
    @test isone(I) === true
    @test isone(Z) === false
    @test iszero(I) === false
    @test Int(Z) === 0
    @test Int(I) === 1
    @test Float64(Z) === 0.0
    @test Float64(I) === 1.0
    @test float(Z) === 0.0
    @test float(I) === 1.0
    @test Z + 1 === 1
    @test I + 1 === 2
    @test Z + I === I
    @test Z - I === -1
    @test Zero(0) === Z
    @test One(1) === I
    @test one(Z) === I
    @test one(I) === I
    @test zero(Z) === Z
    @test zero(I) === Z
    @test Z === Zero()
    @test I === One()
    @test Z == Z
    @test I == I
    @test Z == 0
    @test I == 1
    @test sizeof(Z) == 0
    @test float(Z) === 0.0
    @test float(I) === 1.0
    @test -Z === Z
    @test -I === -1
    @test +Z === Z
    @test +I === I
    @test 2*Z === Z
    @test Z*Z === Z
    @test I*I === I
    @test 2*I === 2
    @test 2/I === 2.0
    @test I*2 === 2
    @test 2.0*Z === Z
    @test 2.0*I === 2.0
    @test Z*3 === Z
    @test Z/2 === Z
    @test Z/I === Z
    @test Z-Z === Z
    @test Z+Z === Z
    @test Z*Z === Z
    @test 1-Z === 1
    @test 1.0-Z === 1.0
    @test Z-1 == -1
    @test Z+1 == 1
    @test 2+Z == 2
    @test (Z < Z) == false
    @test (Z > Z) == false
    @test Z <= Z
    @test Z >= Z
    @test Z < 3
    @test Z > -2.0
    @test ldexp(Z, 3) === Z
    @test copysign(Z, 3) === Z
    @test copysign(Z, -1) === Z
    @test flipsign(Z, -1) === Z
    @test sign(Z) === Z
    @test round(Z) === Z
    @test floor(Z) === Z
    @test ceil(Z) === Z
    @test trunc(Z) === Z
    @test significand(Z) === Z
    @test sign(I) === I
    @test round(I) === I
    @test floor(I) === I
    @test ceil(I) === I
    @test trunc(I) === I
    @test significand(I) === I
    @test !isodd(Z)
    @test iseven(Z)
    @test isodd(I)
    @test !iseven(I)
    @test string(Z) == "𝟎"
    @test string(I) == "𝟏"
    @test mod(Z, 3) === Z
    @test mod(Z, 3.0) === Z
    @test mod(I, I) == mod(1, 1)
    @test mod(I, I) === Z
    @test rem(I, I) == rem(1, 1)
    @test rem(I, I) === Z
    @test rem(Z, 3) === Z
    @test rem(Z, 3.0) === Z
    @test modf(Z) === (Z, Z)
    @test I/I === I
    @test 1.0/I === 1.0
    @test 1/I === 1.0

    @test log(I) == log(1)
    @test log(I) === Z
    @test log2(I) == log2(1)
    @test log2(I) === Z
    @test log10(I) == log10(1)
    @test log10(I) === Z
    @test log1p(Z) == log1p(0)
    @test log1p(Z) === Z
    @test exp(Z) == exp(0)
    @test exp(Z) === I
    @test exp2(Z) == exp2(0)
    @test exp2(Z) === I
    @test exp10(Z) == exp10(0)
    @test exp10(Z) === I
    @test expm1(Z) == expm1(0)
    @test expm1(Z) === Z
    @test sin(Z) == sin(0)
    @test sin(Z) === Z
    @test cos(Z) == cos(0)
    @test cos(Z) === I
    @test sincos(Z) == sincos(0)
    @test sincos(Z) === (Z, I)
    @test tan(Z) == tan(0)
    @test tan(Z) === Z
    @test asin(Z) == asin(0)
    @test asin(Z) === Z
    @test atan(Z) == atan(0)
    @test atan(Z) === Z
    @test sinpi(Z) == sinpi(0)
    @test sinpi(Z) === Z
    @test sinpi(I) == sinpi(1)
    @test sinpi(I) === Z
    @test cospi(Z) == cospi(0)
    @test cospi(Z) === I
    @test sinh(Z) == sinh(0)
    @test sinh(Z) === Z
    @test cosh(Z) == cosh(0)
    @test cosh(Z) === I
    @test tanh(Z) == tanh(0)
    @test tanh(Z) === Z
    @test sqrt(Z) == sqrt(0)
    @test sqrt(Z) === Z
    @test sqrt(I) == sqrt(1)
    @test sqrt(I) === I
    @test cbrt(Z) == cbrt(0)
    @test cbrt(Z) === Z
    @test cbrt(I) == cbrt(1)
    @test cbrt(I) === I

    @test 3.0^Z === I
    @test 3.0f0^Z === I
    @test 3^Z === I
    @test 3^Z === I
    @test Z^Z === I
    @test I^Z === I

    @test I//I isa Rational{One}
    @test I//I + I//I == 2

    @test sum(fill(I, 23)) == 23
    @inferred sum(fill(I, 23))
    @test sum(One[]) === 0
    @test sum([One()]) === 1

    @test sum(fill(Z, 23)) === Z
    @inferred sum(fill(Z, 23))
    @test sum(Zero[]) === Z
    @test sum([Zero()]) === Z

    @test prod(fill(I, 23)) == 1
    @inferred prod(fill(I, 23))
    @test prod(One[]) === I
    @test prod([One()]) === I

    @test prod(fill(Z, 23)) === false
    @inferred prod(fill(Zero(), 23))
    @test prod(Zero[]) === true
    @test prod([Zero()]) === false

    @test false + One() === 1
    @test One() + false === 1

    @test false + Zero() === false
    @test Zero() + false === false

    @test Integer(Z) === 0
    @test Integer(I) === 1

    @test AbstractFloat(Z) === 0.0
    @test AbstractFloat(I) === 1.0

    @test Zero(Z) === Z
    @test One(I) === I

    @test inv(I) === I

    @test div(3, I) === 3
    @test div(Int32(3), I) === Int32(3)

    @test Bool(Z) === false
    @test Bool(I) === true
    @test Int32(Z) === Int32(0)
    @test Int32(I) === Int32(1)
    @test Int64(Z) === 0
    @test Int64(I) === 1
    @test Float32(Z) === 0.0f0
    @test Float32(I) === 1.0f0
    @test Float64(Z) === 0.0
    @test Float64(I) === 1.0

    @test Zero()^0 === true
    @test Zero()^1 === false

    # Bypass litteral_pow
    n0 = 0
    @test Zero()^n0 === true
    n1 = 1
    @test Zero()^n1 === false

    @test hypot(-7, Z) === 7
    @test hypot(Z, -7) === 7
    @test hypot(-7.0, Z) === 7.0
    @test hypot(Z, -7.0) === 7.0
    @test hypot(Z, Z) === Z

    @test I:3 === Base.OneTo(3)
end

# @testset "muladd" begin
#     @test fma(Z,1,Z) === Z
#     @test fma(Z,1.0,Z) === Z
#     @test muladd(Z,1,Z) === Z
#     @test muladd(Z,1.0,Z) === Z
#     @test fma(Z,1,3) === 3
#     @test fma(Z,1.0,3) === 3.0
#     @test muladd(Z,1,3) === 3
#     @test muladd(Z,1.0,3) === 3.0
#     @test fma(Z,Z,Z) === Z
#     @test muladd(Z,Z,Z) === Z
# end

@testset "Complex" begin
    @test Z*im === Z
    @test im*Z === Z
    @test real(Z) === Z
    @test imag(Z) === Z
    @test Z+(2+3im) === 2+3im
    @test (2+3im)+Z === 2+3im
    @test Z-(2+3im) === -2-3im
    @test (2+3im)-Z === 2+3im
    @test Z*(2+3im) === Z
    @test (2.0+3.0im)*Z === Z
    @test Z/(2+3im) === Z

    @test Complex(Z,Z) == Z
    @test Complex(1,Z) == 1
    @test Complex(1.0,Z) == 1.0
    @test Complex(true,Z) == true
end

@testset "testzero" begin
    @test testzero(3) === 3
    @test testzero(3+3im) === 3+3im
    @test testzero(0) === Z
    @test testzero(0+0im) === Z
end

@testset "error handling" begin
    @test_throws InexactError Zero(1)
    @test_throws InexactError convert(Zero, 0.1)
    @test_throws DivideError 1.0/Z
    @test_throws DivideError (1.0+2.0im)/Z
    @test_throws DivideError Z/Z
    @test_throws DivideError I/Z
    @test_throws DivideError inv(Z)
    @test_throws InexactError Zero(One())
    @test_throws InexactError One(Zero())
    @test_throws DivideError mod(Z, Z)
    @test_throws DivideError rem(Z, Z)
    @test_throws MethodError convert(Irrational{:π}, Zero())
    @test_throws MethodError convert(Irrational{:π}, One())
end

if VERSION ≥ v"1.2" # no test on 1.0 because of BenchmarkTools dependency
    # Test `MyComplex` example type
    include("complex2_example.jl")
    @testset "Complex example" begin
        @test Complex2(Zero(), 2)*Complex2(Zero(), 3) === Complex2(-6, Zero())
        @test abs(i) === One()
    end
end

Zeros.@pirate Base

@testset "piracy" begin
    @test +() === Z
    @test *() === I
    @test zero() === Z
    @test one() === I
    @test sum([]) == 0
    @test prod([]) == 1
    @test sin(π) == 0
    @test tan(π) == 0
end


mesh_files = [("Abaqus", ".inp")]
for (file_type, ext) in mesh_files
    @testset "Import $file_type" begin
        for T in Floats
            @testset "$T" begin
                @testset "TriangleMesh" begin
                    ref_mesh = setup_TriangleMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/tri"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end

                @testset "QuadrilateralMesh" begin
                    ref_mesh = setup_QuadrilateralMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/quad"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end

                @testset "MixedPolygonMesh" begin
                    ref_mesh = setup_MixedPolygonMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/tri_quad"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end

                @testset "QuadraticTriangleMesh" begin
                    ref_mesh = setup_QuadraticTriangleMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/tri6"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end

                @testset "QuadraticQuadrilateralMesh" begin
                    ref_mesh = setup_QuadraticQuadrilateralMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/quad8"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end

                @testset "MixedQuadraticPolygonMesh" begin
                    ref_mesh = setup_MixedQuadraticPolygonMesh(T, UInt8)
                    mesh = import_mesh("./mesh/mesh_files/tri6_quad8"*ext, T)
                    @test mesh.name == ref_mesh.name
                    for i in eachindex(ref_mesh.vertices)
                        @test mesh.vertices[i] ≈ ref_mesh.vertices[i]
                    end
                    @test mesh.polytopes == ref_mesh.polytopes
                    @test mesh.groups == ref_mesh.groups
                end
            end
        end
    end
end


using Printf
import ModiaResult

# the sol is different for modia than ModelingToolkit
function reportMinMax(name::String,modelName::String,result)

    (time, signal, signalType) = ModiaResult.rawSignal(result, name)

    y_min, i_min = findmin(signal[1])
    y_max, i_max = findmax(signal[1])
    t_min = time[1][i_min]
    t_max = time[1][i_max]
    print("$(name): $(modelName)\n")
    print("var\ttime\tvalue\n")
    print("min\t$(t_min)\t$(y_min)\n")
    print("max\t$(t_max)\t$(y_max)\n\n")
    return y_min, t_min, y_max, t_max
end

function errorCheck(measured, predicted)
    error_abs = measured-predicted
    error_rel = (measured-predicted)/measured

    println("Measured\tPredicted\tM-P\t\t(M-P)/M")
    @printf("%.2e\t%.2e\t%.2e\t%.2e\n\n",ustrip(measured),ustrip(predicted),ustrip(error_abs),ustrip(error_rel))
end


# FIXME see whether we can remove the broadcast calls required for 0.7

println("\n\n\nStarting runtests.jl $(join(ARGS, " ")) ...")

using FunctionalData, FunctionalDataUtils, Test #, Colors
using Statistics

macro shouldtestset(a,b) length(ARGS) < 1 || ARGS[1] == a ?  :(@testset $a $b) : nothing end
macro shouldtestset2(a,b) length(ARGS) < 2 || ARGS[2] == a ?  :(@testset $a $b) : nothing end

@shouldtestset "machinelearning" begin
    @shouldtestset2 "randbase" begin
        # @test_equal size(randbase(2,10)) (2,10)
        # @test_almostequal sqrt(sum(randbase(2,10).^2, 2)) [1. 1.]'
        # @test_almostequal (@p randbase 2 10  | (.^) _ 2 |  sum _ 2) [1. 1.]'
    end
    @shouldtestset2 "randproj" begin
    end
    @shouldtestset2 "loocv" begin
    end
end

@shouldtestset "computing" begin
    @shouldtestset2 "fasthash" begin
        @test fasthash(1) == "20eae029a26a15420f9ed6d7a9999e823f251b9d44adea5a504e1d8a4f7c5512"
        @test fasthash(1.) == "cb5415fc791f65b70b303678e3601d60ac527cc4cdd3a10d1f85b7423b270112"
        @test fasthash('a') == "94923c7a4899a5baf82f29551f5e4652e3b7ee50699bbd0b4ddba1538e5963b2"
        @test fasthash("a") == "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
        @test fasthash(["a"]) == "da3811154d59c4267077ddd8bb768fa9b06399c486e1fc00485116b57c9872f5"
        @test fasthash([1]) == "7c9fa136d4413fa6173637e883b6998d32e1d675f88cddff9dcbcf331820f4b8"
        @test fasthash(UInt8[1]) == "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"
        @test fasthash(Int8[1]) == "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"
        @test fasthash(UInt16[1]) == "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254"
        @test fasthash(Int16[1]) == "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254"
        @test fasthash(UInt32[1]) == "67abdd721024f0ff4e0b3f4c2fc13bc5bad42d0b7851d456d88d203d15aaa450"
        @test fasthash(Int32[1]) == "67abdd721024f0ff4e0b3f4c2fc13bc5bad42d0b7851d456d88d203d15aaa450"
        @test fasthash(UInt64[1]) == "7c9fa136d4413fa6173637e883b6998d32e1d675f88cddff9dcbcf331820f4b8"
        @test fasthash(Int64[1]) == "7c9fa136d4413fa6173637e883b6998d32e1d675f88cddff9dcbcf331820f4b8"
        @test fasthash(Any[1,"test"]) == "b59f20a7e3513ef702971ec5cbd288f19d760df46d49d5d27edb82fe73fc257b"
        data = Any[1,1., 'a', "a", rand(10,200), zeros(1000,10000), Dict("a" => 1, 2 => "b")]
        r = [fasthash(x) for x in data]
        @test fasthash(1:2) == "48adce367d9daa9d76033a4693b20d7fab23d357bf97e49a036f2dd32b1afff4"
        @test fasthash(1:3:10) == "dc29cadf1701b9023b1170a8101b40928770dbbd967e2740afa4986499522ca3"
    end
end

@shouldtestset "computervision" begin
    @shouldtestset2 "warp" begin
        img = rand(10,20,3)
        a = rand(3:9,2,4)
        b = copy(a)
        b[:,end] = 10
        warp(img,a,b)
        @test size(warp(img,a,b))  ==  (10,20,3)
        @test size(warp(img,a,b,(8,9)))  ==  (8,9,3)

    end
    @shouldtestset2 "iimg" begin
        a_ = [1 2 3]
        @test size(iimg(a_)) == (1,3)
        @test typeof(iimg(a_)) == Array{Float64,2}
        @test iimg(a_) == [1 3 6]
        @test iimg([1 2 3; 4 5 6]) == [1.0 3.0 6.0; 5.0 12.0 21.0]
        @test iimg(ones(1,2,3)) == float(cat([1 2],[2 4],[3 6],dims=3))
    end

    @shouldtestset2 "interp3" begin
        a = [o for m in 1:2, n in 1:2, o in 1:2]
        @test interp3(a,1.,1.,1.5) == 1.5
        @test interp3(a,2.,2.,1.5) == 1.5
        a = [n for m in 1:2, n in 1:2, o in 1:2]
        @test interp3(a,1.,1.5,1.) == 1.5
        @test interp3(a,2.,1.5,2.) == 1.5
        a = [m for m in 1:2, n in 1:2, o in 1:2]
        @test interp3(a,1.5,1.,1.) == 1.5
        @test interp3(a,1.5,2.,2.) == 1.5
    end

    @shouldtestset2 "interp3with01coords" begin
        a = [o for m in 1:2, n in 1:2, o in 1:2]
        @test interp3with01coords(a,0.,0.,0.5) == 1.5
        @test interp3with01coords(a,1.,1.,0.5) == 1.5
        a = [n for m in 1:2, n in 1:2, o in 1:2]
        @test interp3with01coords(a,0.,0.5,0.) == 1.5
        @test interp3with01coords(a,1.,0.5,1.) == 1.5
        a = [m for m in 1:2, n in 1:2, o in 1:2]
        @test interp3with01coords(a,0.5,0.,0.) == 1.5
        @test interp3with01coords(a,0.5,1.,1.) == 1.5
    end

    @shouldtestset2 "resize" begin
        @test resize(ones(Int,1,1), [1, 1]) == ones(Int, 1, 1)
        @test resize(ones(Float64,1,1), 1) == ones(Float64, 1, 1)
        @test resize(ones(Float64,1,1,1), [1, 1, 1]) == ones(Float64, 1, 1, 1)
        @test resize(ones(Int,1,1), [1, 10]) == ones(Int, 1, 10)
        @test maximum(abs.(resize([1. 2.], [1, 4])-[1. 1.33333 1.66666 2.])) < 0.01
        @test resize([1. 3.], [1, 3]) == [1. 2. 3.]
    end

    @shouldtestset2 "bwlabel" begin
        img = [1 2 3]
        @test bwlabel(img) == img

        img = [1 2 3 0 5 6]
        @test bwlabel(img) == [1 2 3 1 5 6]
        @test bwlabel(img,4) == [1 2 3 4 5 6]

        img = [1 1 0 5 2 2; 3 3 0 0 4 4]
        bwlabel(img)
    end

    @shouldtestset2 "border" begin
        img = zeros(5,7)
        img[3,4] = 1
        @test border(img)  ==  img
        img[2:4,3:6] = 1
        b = copy(img)
        b[3,4:5] = 0
        @test border(img)  ==  b
    end

    @shouldtestset2 "bwdist" begin
        a = [0 0 0 1 0 0]
        @test bwdist(a)  ==  [3 2 1 0 1 2]
        @test bwdist([1 10;2 12], [1 10; 1 10])  ==  [1,2]
    end

    @shouldtestset2 "rle" begin
        data = [1 1 2 2 2 3 3 3 3]
        @test unrle(rle(data)) == data
        data = rand(1:5, 1, 1000)
        @test unrle(rle(data)) == data
        for t = [Int16, Int32, Int64, Float32, Float64]
            for s1 = 1:10, s2 = 1:10
                data = rand(t, s1, s2)
                @test unrle(rle(data)) == data
            end
        end
    end

    @shouldtestset2 "resizeminmax" begin
        f(a) = size(resizeminmax(a, [30,40], [130, 140]))
        @test f(ones(1,1)) == (30,40)
        @test f(ones(100,200)) == (85,140)
        @test f(ones(10,200)) == (30,140)
        @test f(ones(50,10)) == (125,40)
        @test f(ones(50,60)) == (50,60)
        @test f(ones(1,600)) == (30,140)
        @test f(ones(600,1)) == (130,40)
    end

    @shouldtestset2 "blocks" begin
        a = ones(16,32)
        ind = stridedblocksub(a, 8; keepshape = true)
        @test size(ind) == (2,4)
        ind = stridedblocksub(a, 8)
        @test len(ind) == 8
        ind =  stridedblocksub(a, 8, 1)
        @test len(ind) == 9*25
        a = flatten(Any[ones(2,2) 2*ones(2,2); 3*ones(2,2) 4*ones(2,2)])
        subs = stridedblocksub(a, 2)
    end

    @shouldtestset2 "meshgrid" begin
        @test meshgrid(2,3)  ==  [1 2 1 2 1 2; 1 1 2 2 3 3]
        @test meshgrid(1:2,1:3)  ==  [1 2 1 2 1 2; 1 1 2 2 3 3]
        @test meshgrid(2:3,2:4)  ==  [2 3 2 3 2 3; 2 2 3 3 4 4]
        @test meshgrid(2:3,[2, 0, 4])  ==  [2 3 2 3 2 3; 2 2 0 0 4 4]
        @test centeredmeshgrid(2:3,2:4)  ==  [0 1 0 1 0 1; -1 -1 0 0 1 1]
    end

    # FIXME reenable once MultivariateStats works on 0.7
    # @shouldtestset2 "inpointcloud" begin
    #     l = row(linspace(0,1,100))
    #     o = row(ones(100))
    #     shape2d = [l o flipdim(l,2) 0*o; 0*o l o flipdim(l,2)]*10 .+ [2.5;5.5]
    #     shouldbe2d = zeros(30,30)
    #     shouldbe2d[3:12,6:15] = 1
    #     coords = meshgrid(30,30)
    #     result2d = reshape(map(coords, x->inpointcloud(x, shape2d)), 30,30);
    #     @test result2d  ==  shouldbe2d

    #     plate1 = [meshgrid(1:10,1:10); zeros(1,100)]
    #     plate2 = [meshgrid(1:10,1:10); 10*ones(1,100)]
    #     plate3 = plate1[[1, 3, 2],:]
    #     plate4 = plate2[[1, 3, 2],:]
    #     plate5 = plate1[[3, 1, 2],:]
    #     plate6 = plate2[[3, 1, 2],:]
    #     shape3d = [plate1 plate2 plate3 plate4 plate5 plate6] .+ [2.5;5.5;5.5]

    #     shouldbe3d = zeros(30,30,30)
    #     shouldbe3d[3:12,6:15,6:15] = 1
    #     coords = meshgrid(30,30,30)
    #     result3d = reshape(map(coords, x->inpointcloud(x, shape3d)), 30,30,30);
    #     result3d == shouldbe3d
    #     @test sum(result3d-shouldbe3d)  ==  38
    # end

    @shouldtestset2 "sampler" begin
        image = rand(200,100)
        sampler = makeSampler(image, 32)
        coords = asint(broadcast(+, 50, 50*randn(2,100)))
        @test size(@p map coords sampler) == (32,32,100)
        out = zeros(1024,1)
        pos = col([50,50])
        @test size(sample!(out, pos, sampler)) == (1024,1)

        sampler = makeSampler(image, 32, col = true)
        @test size(sample(pos, sampler)) == (1024,1)

        sampler = makeSampler(image, 32, col = true, normmeanstd = true)
        r = sample(pos, sampler)
        @test abs(mean(r))  < 1e-5
        @test abs(std(r)-1) < 1e-5

        image = [1 2 3 4 5; 10 20 30 40 50; 11 22 33 44 55]
        sampler = makeSampler(image, 3, centered = false)
        @test sample(col([1,2]), sampler) == [2 3 4; 20 30 40; 22 33 44]
        sampler = makeSampler(image, 3, centered = true)
        @test sample(col([1,2]), sampler) == [1 2 3; 10 20 30; 11 22 33]
        @test sample(col([1,3]), sampler) == [2 3 4; 20 30 40; 22 33 44]

        image = ones(20,10)
        sampler = makeSampler(image, 3, centered = false)
        sample(sampler, col([1 1]))
        sample(sampler,siz(image))

        sampler = makeSampler(image, 3, centered = true)
        sample(sampler, col([1 1]))
        sample(sampler,siz(image))
    end
end

# @shouldtestset "graphics" begin
#     @shouldtestset2 "jetcolormap" begin
#         r = jetcolormap(10)
#         @test all(broadcast(>=, r, 0)) == true
#         @test all(broadcast(<=, r, 1)) == true
#     end

#     @shouldtestset2 "jetcolors" begin
#         @test jetcolors(1:3) ≈ [0.0 0.5198019801980198 0.5396039603960396; 0.0 1.0 0.0; 0.5396039603960396 0.4801980198019802 0.0]
#         @test jetcolors(1:3,2,4) ≈ [0.0 0.0 0.5198019801980198; 0.0 0.0 1.0; 0.5396039603960396 0.5396039603960396 0.4801980198019802]
#         # @test jetcolorants(1:3) == Any[RGB{Float64}(0.0,0.0,0.5396039603960396),RGB{Float64}(0.5198019801980198,1.0,0.4801980198019802),RGB{Float64}(0.5396039603960396,0.0,0.0)] # disabled until Colors works on 0.7
#     end

#     @shouldtestset2 "asimagesc" begin
#         img = [1 2 3 4 5 6 7 8 9]
#         r = asimagescrgb(img)
#         @test size(r,3) == 3
#     end

#     @shouldtestset2 "blocksvisu" begin
#         @test blocksvisu([1 2 3]) == [1 0 3; 0 0 0; 2 0 0]
#         @test blocksvisu([1 2 3; 1 2 3; 1 2 3; 1 2 3]) == [1 1 0 3 3; 1 1 0 3 3; 0 0 0 0 0; 2 2 0 0 0; 2 2 0 0 0]
#     end

#     @shouldtestset2 "overlaygradient" begin
#         img = rand(100,200)
#         sp = zeros(size(img))
#         sp[25:75, 50:150] = 1
#         @test size(overlaygradient(img, sp)) == (100,200,3)
#     end
# end

@shouldtestset "numerical" begin
    @shouldtestset2 "nanfunctions" begin
        @test nanmedian([1 2 3], 1)  ==  [1.0 2.0 3.0]
        @test isequal(nanmedian([1 2 NaN], 1),  [1.0 2.0 NaN]) == true
        @test nanmedian([1 2 3], 2)  ==  row([2.0])
        @test nanmedian([1 2 NaN], 2)  ==  row([1.5])
        @test nanmedian([1, 2, NaN])  ==  1.5
    end
    @shouldtestset2 "distance" begin
        @test distance(1,1)  ==  0
        @test distance(1.,1.)  ==  0.
        @test distance([1,2,3],[2,2,3])  ==  1
        @test distance([0 1; 0 1],[0 1; 0 1])  ==  [0. sqrt(2); sqrt(2) 0.]
    end
    @shouldtestset2 "norms" begin
        @test normsum([1 2 1])  ==  [1/4 1/2 1/4]
        @test norm01([1 2 3])  ==  [0 1/2 1]
        @test normeuclid([1 1])  ==  broadcast(/, 1, [sqrt(2) sqrt(2)])
        @test normmeanstd([1 2 3])  ==  [-1 0 1]
        @test normquantile(collect(0:10))  ≈  asfloat64(collect(0:10))/10
        @test normquantile(collect(0:2:20))  ≈  asfloat64(collect(0:10))/10
    end
    @shouldtestset2 "extrema" begin
        @test (@p maximum [1 2 3; 4 5 0] (x->x[2]))  ==  [2 5]'
        @test (@p minimum [1 2 3; 4 5 0] (x->x[2]))  ==  [3 0]'
    end
    @shouldtestset2 "valuemap" begin
        @test valuemap([0 1 2 3], [10 20 30])  ==  [0. 10. 20. 30.]
    end
    @shouldtestset2 "clamp" begin
        @test clamp(1,1,1)  ==  1
        @test clamp(0,1,2)  ==  1
        @test clamp(2,1,2)  ==  2
        @test clamp([-1 2 3 10; -5 10 11  100],[1 10],[3 15])  ==  [1 2 3 3; 10 10 11 15]
        @test clamp(Float32[-1 2 3 10; -5 10 11  100],[1 10],[3 15])  ==  [1 2 3 3; 10 10 11 15]
        @test clamp([1,2],[3,4],[5,6])   ==  [3,4]
        @test clamp([0 1 11; -5 3 20], rand(10,15))  ==  [1 1 10; 1 3 15]
        @test clamp([0 1 11; -5 3 20; -4 3 5], rand(10,15,4))  ==  [1 1 10; 1 3 15; 1 3 4]
    end
    @shouldtestset2 "rand" begin
        @test size(randsiz(siz(rand(2,3)))) == (2,3)
        @test eltype(randsiz([2 3]', Float32)) == Float32
    end
    @shouldtestset2 "plus" begin
        @test (@p plus zeros(2,3) ones(2,1))  ==  ones(2,3)
        @test (@p minus zeros(2,3) ones(2,1))  ==  -ones(2,3)
        @test (@p times ones(2,3) zeros(2,1))  ==  zeros(2,3)
        @test (@p divby ones(2,3) 2*ones(2,1))  ==  0.5*ones(2,3)
    end
end

@shouldtestset "utils" begin
    @shouldtestset2 "histdict" begin
        d = [Dict(:a => 1), Dict(:a => 1), Dict(:a => 10)]
        r = Dict(1 => 2, 10 => 1)
        @test histdict(d, :a)  ==  r
        @test histdict([1,1,10])  ==  r
    end
end

println("done!\n")


function test_noc(bridged_mock, F, S, n)
    @test MOI.get(bridged_mock, MOI.NumberOfConstraints{F, S}()) == n
    @test length(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{F, S}())) == n
    @test ((F, S) in MOI.get(bridged_mock, MOI.ListOfConstraints())) == !iszero(n)
end

# Test deletion of bridge
function test_delete_bridge(
    m::MOIB.AbstractBridgeOptimizer, ci::MOI.ConstraintIndex{F, S}, nvars::Int,
    nocs::Tuple; used_bridges = 1, num_bridged = 1) where {F, S}
    function num_bridges()
        return count(bridge -> bridge !== nothing, m.bridges) +
            length(m.single_variable_constraints)
    end
    start_num_bridges = num_bridges()
    @test MOI.get(m, MOI.NumberOfVariables()) == nvars
    test_noc(m, F, S, num_bridged)
    for noc in nocs
        test_noc(m, noc...)
    end
    @test MOI.is_valid(m, ci)
    MOI.delete(m, ci)
    @test_throws MOI.InvalidIndex{typeof(ci)} MOI.delete(m, ci)
    try
        MOI.delete(m, ci)
    catch err
        @test err.index == ci
    end
    @test !MOI.is_valid(m, ci)
    @test num_bridges() == start_num_bridges - used_bridges
    test_noc(m, F, S, num_bridged - 1)
    # As the bridge has been removed, if the constraints it has created where not removed, it wouldn't be there to decrease this counter anymore
    @test MOI.get(m, MOI.NumberOfVariables()) == nvars
    for noc in nocs
        test_noc(m, noc...)
    end
end


module TestResolve

using Test
using TreeParzen
import TreeParzen: Types, Resolve, Trials

@testset "Resolve.node(::Param)" begin

    param = Dict{Symbol, Types.AbstractDelayed}(:b => HP.Normal(:a, 1.0, 2.0))

    # example to catch the key is present
    vals = Trials.ValsDict(:a => 1)
    @test_throws KeyError Resolve.node(param, vals)
    # example when the key is not present
    val = Trials.ValsDict(:c => 1)
    @test haskey(Resolve.node(param, val), :b)

end

end


function reallocate_ipfs(
    existing_ipfs::Vector{T}, proposed_ipfs::Vector{T}
) where {T<:AbstractString}
    return existing_ipfs ∩ proposed_ipfs
end
function open_ipfs(
    proposed_ipfs::Vector{T}, reallocate_ipfs::Vector{T}
) where {T<:AbstractString}
    return setdiff(proposed_ipfs, reallocate_ipfs)
end
function close_ipfs(
    existing_ipfs::Vector{T}, reallocate_ipfs::Vector{T}, frozenlist::Vector{T}
) where {T<:AbstractString}
    return setdiff(setdiff(existing_ipfs, reallocate_ipfs), frozenlist)
end


### Esta función hace que una celda se vacíe
function Celda_Vacia!(v::Vehiculo)
    v.velocidad = int8(-1)
    v.tipo = int8(-1)
    v.cambio = int8(0)
    v.num = 0
end

## Esta función vuelve un obstáculo a una celda
function Obstaculo!(v::Vehiculo)
    v.velocidad = int8(-1)
    v.tipo = int8(-2)
    v.cambio = int8(0)
    v.num = int8(0)
end

## Esta función cambia los valores de una celda
function Cambiar_Vehiculo!(v, vel, pos, tipo, cambio, num)
    v.velocidad = int8(vel)
    v.posicion = int64(pos)
    v.tipo = int8(tipo)
    v.cambio = int8(cambio)
    v.num = num
end

## Esta función inserta un vehículo con probabilidad p de ser un camión en las primeras 6 celdas de la carretera
## Hay que tener cuidado con Insertar_Carro! para solo llamarla en celdas vacias
function Insertar_Carro!(carretera, num::Int64, p::Float64, vmax::Array{Int8, 1} = Int8[3, 5], frontera_izq::Int8 = int8(5) )
    #frontera_izq = 5
    #vmax = [3, 5]

    lim = frontera_izq + int8(1)
    while carretera[lim].tipo == -1 && lim < (frontera_izq+vmax[2])
        lim += int8(1)
    end
    v_nueva = ( rand() <= p ? vmax[1] : vmax[2] )
    pos_nueva = min( lim - vmax[2], v_nueva)
    tipo = (v_nueva == vmax[1] ? 1 : 2 )
    Cambiar_Vehiculo!(carretera[pos_nueva], v_nueva, pos_nueva, tipo, 1, num)

    lim = 0; v_nueva = 0; pos_nueva = 0; tipo = 0;
    frontera_izq = 0; vmax = 0
end

## Esta función da la posición del vehiculo de adelante cuando nos colocamos en una celda vacía
## Si llamamos la función desde un coche, regresará la posición de este mismo
## La funcion Vehiculo_Adelante cuenta a los obstáculos
function Vehiculo_Adelante( posicion, Carretera, N)
    i = posicion
    while i < N && Carretera[i].tipo == -1
        i += 1
    end
    i
end

## Esta función da la posición del vehiculo de atrás
## Si nos colocamos en un vehículo, igual dará la posición del vehículo de atrás. Si no hay entonces
## regresará la última celda.
## La funcion Vehiculo_Atras NO cuenta los obstáculos
function Vehiculo_Atras( posicion, Carretera, N, frontera_izq::Int8 = int8(5) )
    #frontera_izq = 5

    i = posicion - 1
    while i > frontera_izq && (Carretera[i].tipo == -1 || Carretera[i].tipo == -2)
        i -= 1
    end

    if i > frontera_izq
        return i
    else
        return N
    end
end

## Esta función da la posición del vehículo más adelante en una carretera
function Pos_izq(carretera)

    gap = carretera[end].posicion

    while gap >= 1 && (carretera[gap].tipo == -1 || carretera[gap].tipo == -2)
        gap -= 1
    end
    gap
end

## Esta función da la misma posición que la de arriba pero sin contar las primeras 9 celdas
function Pos_izq9(carretera)

    gap = carretera[end].posicion

    while gap >= 9 && (carretera[gap].tipo == -1 || carretera[gap].tipo == -2)
        gap -= 1
    end
    gap
end

## Esta función da la posición del vehículo más atrás en la carretera
function Pos_der(carretera)
    x = carretera[1].posicion
    i = 1
    while x < carretera[end].posicion && (carretera[i].tipo == -1 ||carretera[i].tipo == -2)
        x += 1
        i += 1
    end
    x
end

## Esta función da la última celda vacía en una carretera
function Pos_siguiente(carretera)
    x = carretera[end].posicion
    i = length(carretera)
    while x > carretera[1].posicion && (carretera[i].tipo == 1 || carretera[i].tipo == 2)
        x -= 1
        i -= 1
    end
    x
end


using MarketData
FactCheck.setstyle(:compact)
FactCheck.onlystats(true)

facts("macro tests") do

    case1  = @select ohlc where Open
    case2  = @select ohlc when monthname "October"
    case3  = @select ohlc when year 2000
    case4  = @select ohlc where Low when monthname "January"
    case5  = @select ohlc where High when year 2001
    case6  = @select ohlc where Close .> 100
    case7  = @select ohlc where Open .> Close
    case8  = @select ohlc where Open .> 100 when dayname "Friday"
    case9  = @select ohlc where Close .> Low when dayname "Monday"
    case10 = @select ohlc where High .> 100 when month 2
    case11 = @select ohlc where Open .> Low when month 12

    context("@select ohlc where Open") do
        @fact length(case1)  --> 500
        @fact case1.colnames --> ["Open"]
    end

    context("@select ohlc when monthname October") do
        @fact length(case2)          --> 45
        @fact length(case2.colnames) --> 4
        @fact case2.timestamp[1]     --> Date(2000,10,2)
        @fact last(case2.timestamp)  --> Date(2001,10,31)
    end

    context("@select ohlc when year 2000") do
        @fact length(case3)          --> 252
        @fact length(case3.colnames) --> 4
        @fact case3.timestamp[1]     --> Date(2000,1,3)
        @fact last(case3.timestamp)  --> Date(2000,12,29)
    end

    context("@select ohlc where Low when monthname January") do
        @fact length(case4)         --> 41
        @fact case4.colnames        --> ["Low"]
        @fact case4.timestamp[1]    --> Date(2000,1,3)
        @fact last(case4.timestamp) --> Date(2001,1,31)
    end

    context("@select ohlc where High when year 2001") do
        @fact length(case5)         --> 248
        @fact case5.colnames        --> ["High"]
        @fact case5.timestamp[1]    --> Date(2001,1,2)
        @fact last(case5.timestamp) --> Date(2001,12,31)
    end

    context("@select ohlc where Close .> 100") do
        @fact length(case6)         --> 500
        @fact case6.colnames        --> ["Close.>100"]
        @fact case6.timestamp[1]    --> Date(2000,1,3)
        @fact last(case6.timestamp) --> Date(2001,12,31)
        @fact sum(case6.values)     --> 89
    end

    context("@select ohlc where Open .> Close") do
        @fact length(case7)         --> 500
        @fact case7.colnames        --> ["Open.>Close"]
        @fact case7.timestamp[1]    --> Date(2000,1,3)
        @fact last(case7.timestamp) --> Date(2001,12,31)
        @fact sum(case7.values)     --> 252
    end

    context("@select ohlc where Open .> 100 when dayname Friday") do
        @fact length(case8)         --> 101
        @fact case8.colnames        --> ["Open.>100"]
        @fact case8.timestamp[1]    --> Date(2000,1,7)
        @fact last(case8.timestamp) --> Date(2001,12,28)
        @fact sum(case8.values)     --> 16
    end

    context("@select ohlc where Close .> Low when dayname Monday") do
        @fact length(case9)         --> 95
        @fact case9.colnames        --> ["Close.>Low"]
        @fact case9.timestamp[1]    --> Date(2000,1,3)
        @fact last(case9.timestamp) --> Date(2001,12,31)
        @fact sum(case9.values)     --> 95
    end

    context("@select ohlc where High .> 100 when month 2") do
        @fact length(case10)         --> 39
        @fact case10.colnames        --> ["High.>100"]
        @fact case10.timestamp[1]    --> Date(2000,2,1)
        @fact last(case10.timestamp) --> Date(2001,2,28)
        @fact sum(case10.values)     --> 20
    end

    context("@select ohlc where Open .> Low when month 12") do
        @fact length(case11)         --> 40
        @fact case11.colnames        --> ["Open.>Low"]
        @fact case11.timestamp[1]    --> Date(2000,12,1)
        @fact last(case11.timestamp) --> Date(2001,12,31)
        @fact sum(case11.values)     --> 37
    end
end


using GridLayoutBase
using GridLayoutBase: GridSubposition
using Test
using Observables

include("debugrect.jl")

@testset "GridLayout Zero Outside AlignMode" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Outside(0))
    dr = layout[1, 1] = DebugRect()

    @test computedbboxobservable(dr)[] == bbox

    dr.topprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 900)
    dr.bottomprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 100, 900)
    dr.leftprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(100, 1000, 100, 900)
    dr.rightprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(100, 900, 100, 900)

    dr2 = layout[1, 2] = DebugRect()
    @test layout.nrows == 1 && layout.ncols == 2
    colgap!(layout, 1, Fixed(0))

    @test computedbboxobservable(dr)[].widths == computedbboxobservable(dr2)[].widths == Float32[400.0, 800.0]
end

@testset "GridLayout Outside AlignMode" begin
    bbox = Observable(BBox(0, 1000, 0, 1000)) # just as observable for bbox conversion test
    layout = GridLayout(bbox = bbox, alignmode = Outside(100, 200, 50, 150))
    dr = layout[1, 1] = DebugRect()

    @test computedbboxobservable(dr)[] == BBox(100, 800, 50, 850)

    dr.topprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(100, 800, 50, 750)
    dr.bottomprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(100, 800, 150, 750)
    dr.leftprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(200, 800, 150, 750)
    dr.rightprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(200, 700, 150, 750)
end

@testset "GridLayout Inside AlignMode" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Inside())
    dr = layout[1, 1] = DebugRect()

    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 1000)

    dr.topprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 1000)
    dr.bottomprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 1000)
    dr.leftprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 1000)
    dr.rightprot[] = 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 1000)
end

@testset "GridLayout Mixed AlignMode" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Mixed(left = 0, top = 100))
    dr = layout[1, 1] = DebugRect()

    @test GridLayoutBase.protrusion(layout, Left()) == 0
    @test GridLayoutBase.protrusion(layout, Right()) == 0
    @test GridLayoutBase.protrusion(layout, Bottom()) == 0
    @test GridLayoutBase.protrusion(layout, Top()) == 0

    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 900)

    dr.topprot[] = 100
    @test GridLayoutBase.protrusion(layout, Top()) == 0
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 800)

    dr.bottomprot[] = 100
    @test GridLayoutBase.protrusion(layout, Bottom()) == 100
    @test computedbboxobservable(dr)[] == BBox(0, 1000, 0, 800)

    dr.leftprot[] = 100
    @test GridLayoutBase.protrusion(layout, Left()) == 0
    @test computedbboxobservable(dr)[] == BBox(100, 1000, 0, 800)

    dr.rightprot[] = 100
    @test GridLayoutBase.protrusion(layout, Right()) == 100
    @test computedbboxobservable(dr)[] == BBox(100, 1000, 0, 800)

    layout.alignmode[] = Mixed(
        left = Protrusion(50),
        right = Protrusion(60),
        bottom = Protrusion(70),
        top = Protrusion(80),
    )

    # set layout to forced protrusion alignment
    @test GridLayoutBase.protrusion(layout, Left()) == 50
    @test GridLayoutBase.protrusion(layout, Right()) == 60
    @test GridLayoutBase.protrusion(layout, Bottom()) == 70
    @test GridLayoutBase.protrusion(layout, Top()) == 80
    # bb of dr only depends on its protrusions now
    @test computedbboxobservable(dr)[] == BBox(100, 1000, 0, 800)

    # force different protrusions for dr
    dr.alignmode[] = Mixed(
        left = Protrusion(50),
        right = Protrusion(60),
        bottom = Protrusion(70),
        top = Protrusion(80),
    )
    # bb should follow
    @test computedbboxobservable(dr)[] == BBox(50, 940, 70, 920)
end

@testset "alignmodes and known size" begin
    gl = GridLayout()
    dr = gl[1, 1] = DebugRect(width = 200, height = 300, alignmode = Mixed(left = 1, right = 2, bottom = 3, top = 4))
    @test reportedsizeobservable(dr)[] == (203, 307)
    dr.alignmode[] = Inside()
    @test reportedsizeobservable(dr)[] == (200, 300)
    dr.alignmode[] = Mixed(left = Protrusion(10), right = Protrusion(10), bottom = Protrusion(10), top = Protrusion(10))
    # shouldn't this be without the protrusions? have to check implementation again
    @test_broken reportedsizeobservable(dr)[] == (200, 300)
end

@testset "assigning content to protrusions" begin

    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Outside(0))
    subgl = layout[1, 1] = GridLayout()

    subgl[1, 1, Left()] = DebugRect(width = Fixed(100))
    @test GridLayoutBase.protrusion(subgl, Left()) == 100

    subgl[1, 1, Top()] = DebugRect(height = 50)
    @test GridLayoutBase.protrusion(subgl, Top()) == 50

    subgl[1, 1, Right()] = DebugRect(width = 120)
    @test GridLayoutBase.protrusion(subgl, Right()) == 120

    subgl[1, 1, Bottom()] = DebugRect(height = 40)
    @test GridLayoutBase.protrusion(subgl, Bottom()) == 40

    subgl[1, 1, TopLeft()] = DebugRect(width = 200, height = 200)
    @test GridLayoutBase.protrusion(subgl, Left()) == 200
    @test GridLayoutBase.protrusion(subgl, Top()) == 200

    subgl[1, 1, TopRight()] = DebugRect(width = 210, height = 210)
    @test GridLayoutBase.protrusion(subgl, Right()) == 210
    @test GridLayoutBase.protrusion(subgl, Top()) == 210

    subgl[1, 1, BottomRight()] = DebugRect(width = 220, height = 220)
    @test GridLayoutBase.protrusion(subgl, Right()) == 220
    @test GridLayoutBase.protrusion(subgl, Bottom()) == 220

    subgl[1, 1, BottomLeft()] = DebugRect(width = 230, height = 230)
    @test GridLayoutBase.protrusion(subgl, Left()) == 230
    @test GridLayoutBase.protrusion(subgl, Bottom()) == 230

    # dr = subgl[1, 1, GridLayoutBase.Outer()] = DebugRect()
    # @test computedbboxobservable(dr)[].widths == (1000, 1000)
end

@testset "resizing through indexing out of range and trim!" begin

    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Outside(0))

    dr = layout[1, 1] = DebugRect()
    @test size(layout) == (1, 1)

    layout[1, 2] = dr
    @test size(layout) == (1, 2)

    layout[3, 2] = dr
    @test size(layout) == (3, 2)

    layout[4, 4] = dr
    @test size(layout) == (4, 4)

    layout[0, 1] = dr
    @test size(layout) == (5, 4)

    layout[1, 0] = dr
    @test size(layout) == (5, 5)

    layout[-1, -1] = dr
    @test size(layout) == (7, 7)

    layout[3, 3] = dr
    trim!(layout)
    @test size(layout) == (1, 1)

    layout[2:3, 4:5] = dr
    @test size(layout) == (3, 5)

    trim!(layout)
    @test size(layout) == (2, 2)
end

@testset "manually deleting rows / cols" begin
    layout = GridLayout(4, 4)

    deletecol!(layout, 2)
    @test size(layout) == (4, 3)

    deleterow!(layout, 3)
    @test size(layout) == (3, 3)

    deleterow!(layout, 1)
    @test size(layout) == (2, 3)

    deletecol!(layout, 1)
    @test size(layout) == (2, 2)

    deleterow!(layout, 2)
    @test size(layout) == (1, 2)

    deletecol!(layout, 2)
    @test size(layout) == (1, 1)

    @test_throws ErrorException deletecol!(layout, 2)
    @test_throws ErrorException deleterow!(layout, 2)
    @test_throws ErrorException deletecol!(layout, 1)
    @test_throws ErrorException deleterow!(layout, 1)

    dr = layout[1, 2] = DebugRect()
    @test length(layout.content) == 1
    deletecol!(layout, 2)
    @test isempty(layout.content)

    dr = layout[2, 1] = DebugRect()
    @test length(layout.content) == 1
    deleterow!(layout, 2)
    @test isempty(layout.content)
end

@testset "inserting rows / cols" begin

    layout = GridLayout(4, 4)
    dr1 = layout[1, 1] = DebugRect()
    dr2 = layout[1, 2:3] = DebugRect()
    dr3 = layout[1, 4] = DebugRect()
    dr4 = layout[2:3, 1] = DebugRect()
    dr5 = layout[4, 1] = DebugRect()
    dr6 = layout[2:3, 2:3] = DebugRect()
    dr7 = layout[1, 3] = DebugRect()
    dr8 = layout[3, 1] = DebugRect()

    @test_throws ErrorException insertcols!(layout, 0, 1)
    @test_throws ErrorException insertcols!(layout, 5, 1)
    @test_throws ErrorException insertrows!(layout, 0, 1)
    @test_throws ErrorException insertrows!(layout, 5, 1)

    insertcols!(layout, 3, 2)
    @test gridcontent(dr1).span.cols == 1:1
    @test gridcontent(dr2).span.cols == 2:5
    @test gridcontent(dr3).span.cols == 6:6

    insertrows!(layout, 3, 2)
    @test gridcontent(dr1).span.rows == 1:1
    @test gridcontent(dr4).span.rows == 2:5
    @test gridcontent(dr5).span.rows == 6:6

    @test gridcontent(dr6).span.cols == 2:5
    @test gridcontent(dr6).span.rows == 2:5

    @test gridcontent(dr7).span.cols == 5:5
    @test gridcontent(dr8).span.rows == 5:5
end

@testset "setting col and row sizes and gaps" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(3, 3, bbox = bbox, alignmode = Outside(0))

    colsize!(layout, 1, Fixed(10))
    @test layout.colsizes[1] == Fixed(10)

    colsize!(layout, 2, 20)
    @test layout.colsizes[2] == Fixed(20)

    rowsize!(layout, 1, Relative(0.2))
    @test layout.rowsizes[1] == Relative(0.2)

    rowsize!(layout, 2, 15.3)
    @test layout.rowsizes[2] == Fixed(15.3)

    @test_throws ErrorException colsize!(layout, 4, Auto())
    @test_throws ErrorException rowsize!(layout, 0, Auto())

    colgap!(layout, 1, Fixed(10))
    @test layout.addedcolgaps[1] == Fixed(10)
    rowgap!(layout, 2, Relative(0.3))
    @test layout.addedrowgaps[2] == Relative(0.3)

    colgap!(layout, 10)
    @test all(layout.addedcolgaps .== Ref(Fixed(10)))
    rowgap!(layout, 20)
    @test all(layout.addedrowgaps .== Ref(Fixed(20)))

    colgap!(layout, Fixed(30))
    @test all(layout.addedcolgaps .== Ref(Fixed(30)))
    rowgap!(layout, Fixed(40))
    @test all(layout.addedrowgaps .== Ref(Fixed(40)))

    @test_throws ErrorException colgap!(layout, 10, Fixed(10))
    @test_throws ErrorException rowgap!(layout, 10, Fixed(10))
end

@testset "some constructors" begin
    @test Outside() == Outside(0f0)
    @test Auto(2).ratio == 2.0
end

@testset "gridlayout constructor colsizes" begin
    gl = GridLayout(2, 2; colsizes = Fixed(10), rowsizes = Relative(0.5))

    @test gl.colsizes == GridLayoutBase.ContentSize[Fixed(10), Fixed(10)]
    @test gl.rowsizes == GridLayoutBase.ContentSize[Relative(0.5), Relative(0.5)]

    gl2 = GridLayout(2, 2;
        colsizes = [Fixed(10), Relative(0.3)],
        rowsizes = [Auto(false), Auto(true)])
    @test gl2.colsizes == GridLayoutBase.ContentSize[Fixed(10), Relative(0.3)]
    @test gl2.rowsizes == GridLayoutBase.ContentSize[Auto(false), Auto(true)]

    @test_throws ErrorException GridLayout(; colsizes = "abc")
    @test_throws ErrorException GridLayout(; rowsizes = missing)

    gl3 = GridLayout(3, 3; addedcolgaps = Fixed(20), addedrowgaps = Fixed(0))
    @test gl3.addedcolgaps == GridLayoutBase.GapSize[Fixed(20), Fixed(20)]
    @test gl3.addedrowgaps == GridLayoutBase.GapSize[Fixed(0), Fixed(0)]

    @test_throws ErrorException GridLayout(3, 1; addedcolgaps = "abc")
    @test_throws ErrorException GridLayout(1, 3; addedrowgaps = "abc")

    @test_throws ErrorException GridLayout(3, 1; addedcolgaps = [Fixed(20)])
    @test_throws ErrorException GridLayout(3, 1; addedrowgaps = [Fixed(30)])

    @test_throws ErrorException GridLayout(3, 1; rowsizes = [Fixed(20)])
    @test_throws ErrorException GridLayout(3, 2; colsizes = [Relative(0.4)])

    @test_throws ErrorException GridLayout(0, 1)
    @test_throws ErrorException GridLayout(1, 0)
end

@testset "printing gridlayouts" begin
    gl = GridLayout(3, 3)
    gl[1, 1] = DebugRect()
    gl[2:3, 4:5] = DebugRect()

    text_long = repr(MIME"text/plain"(), gl)
    @test text_long == """
    GridLayout[3, 5] with 2 children
     ┣━ [1, 1] DebugRect
     ┗━ [2:3, 4:5] DebugRect
    """
    text_short = repr(gl)
    @test text_short == "GridLayout[3, 5] (2 children)"

    subgl = gl[1, 2] = GridLayout()
    subgl[1:5, 3] = DebugRect()

    text_longer = repr(MIME"text/plain"(), gl)
    # this is actually a bit buggy with the newline space space newline at the end
    @test text_longer == "GridLayout[3, 5] with 3 children\n ┣━ [1, 1] DebugRect\n ┣━ [2:3, 4:5] DebugRect\n ┗━ [1, 2] GridLayout[5, 3] with 1 children\n   ┗━ [1:5, 3] DebugRect\n  \n"

    gl3 = GridLayout()
    gl4 = gl3[1, 1] = GridLayout()
    gl4[1, 1] = DebugRect()
    gl3[2, 2] = DebugRect()
    text_long_downconnection = repr(MIME"text/plain"(), gl3)

    # this is also a bit buggy for the same reason as above
    @test text_long_downconnection == "GridLayout[2, 2] with 2 children\n ┣━ [1, 1] GridLayout[1, 1] with 1 children\n ┃ ┗━ [1, 1] DebugRect\n ┃\n ┗━ [2, 2] DebugRect\n"
end

@testset "vector and array assigning" begin
    gl = GridLayout()
    gl[1, 1:3] = [DebugRect() for i in 1:3]
    @test size(gl) == (1, 3)

    gl2 = GridLayout()
    gl2[2:3, 2] = [DebugRect() for i in 1:2]
    @test size(gl2) == (3, 2)

    gl3 = GridLayout()
    gl3[1:3, 1:4] = [DebugRect() for i in 1:12]
    @test size(gl3) == (3, 4)

    gl4 = GridLayout()
    gl4[1:3, 1:4] = [DebugRect() for i in 1:3, j in 1:4]
    @test size(gl4) == (3, 4)

    @test_throws ErrorException gl[1, 1:3] = [DebugRect() for i in 1:2]
    @test_throws ErrorException gl[1:3, 2] = [DebugRect() for i in 1:2]
    @test_throws ErrorException gl[1:3, 1:3] = [DebugRect() for i in 1:10]
    @test_throws ErrorException gl[1:3, 1:3] = [DebugRect() for i in 1:3, j in 1:4]

    gl5 = GridLayout()
    gl5[] = [DebugRect() for i in 1:2, j in 1:3]
    @test size(gl5) == (2, 3)

    gl6 = GridLayout()
    gl6[:v] = [DebugRect() for i in 1:3]
    @test size(gl6) == (3, 1)

    gl7 = GridLayout()
    gl7[:h] = [DebugRect() for i in 1:3]
    @test size(gl7) == (1, 3)

    @test_throws ErrorException gl7[:abc] = [DebugRect() for i in 1:3]
    @test_throws ErrorException gl7[] = [DebugRect() for i in 1:3]
end

@testset "grid api" begin

    gl1 = grid!([1:2, 1:2] => DebugRect(), [3, :] => DebugRect())
    @test size(gl1) == (3, 2)
    @test gl1.content[1].span == GridLayoutBase.Span(1:2, 1:2)
    @test gl1.content[2].span == GridLayoutBase.Span(3:3, 1:2)

    gl2 = grid!([DebugRect() for i in 1:3, j in 1:2])
    @test size(gl2) == (3, 2)
    for i in 1:3, j in 1:2
        n = (i - 1) * 2 + j
        @test gl2.content[n].span == GridLayoutBase.Span(i:i, j:j)
    end

    gl3 = vbox!(DebugRect(), DebugRect())
    @test size(gl3) == (2, 1)
    for i in 1:2
        @test gl3.content[i].span == GridLayoutBase.Span(i:i, 1:1)
    end

    gl4 = hbox!(DebugRect(), DebugRect())
    @test size(gl4) == (1, 2)
    for i in 1:2
        @test gl4.content[i].span == GridLayoutBase.Span(1:1, i:i)
    end
end

@testset "gridnest" begin
    layout = GridLayout()
    dr = layout[1:2, 3:4] = DebugRect()
    subgl = gridnest!(layout, 1:2, 3:4)

    @test size(subgl) == (2, 2)
    @test subgl.content[1].span == GridLayoutBase.Span(1:2, 1:2)
end

@testset "invalid removal" begin
    layout = GridLayout()
    dr = layout[1, 1] = DebugRect()
    # remove the item outside of the normal path
    deleteat!(layout.content, 1)
    # place the item somewhere else, this should error now
    @test_throws ErrorException layout[1, 2] = dr
end

@testset "equal protrusion gaps" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(bbox = bbox, alignmode = Outside(0))
    subgl = layout[1, 1] = GridLayout(3, 3, equalprotrusiongaps = (true, true),
        addedcolgaps = Fixed(0), addedrowgaps = Fixed(0))
    subgl[1, 1, BottomRight()] = DebugRect(width = 100, height = 100)

    dr1 = subgl[1, 1] = DebugRect()
    dr2 = subgl[2, 2] = DebugRect()
    dr3 = subgl[3, 3] = DebugRect()

    @test width(computedbboxobservable(dr1)[]) ≈ (1000 - 2 * 100) / 3.0f0
    @test width(computedbboxobservable(dr2)[]) ≈ (1000 - 2 * 100) / 3.0f0
    @test width(computedbboxobservable(dr3)[]) ≈ (1000 - 2 * 100) / 3.0f0
    @test height(computedbboxobservable(dr1)[]) ≈ (1000 - 2 * 100) / 3.0f0
    @test height(computedbboxobservable(dr2)[]) ≈ (1000 - 2 * 100) / 3.0f0
    @test height(computedbboxobservable(dr3)[]) ≈ (1000 - 2 * 100) / 3.0f0
end

@testset "getindex gridposition" begin
    layout = GridLayout()
    dr = layout[2, 2] = DebugRect()

    @test layout[2, 2] == GridPosition(layout, 2, 2)
    @test layout[end, end] == GridPosition(layout, 2, 2)
    @test_throws ErrorException layout[2, 2, end]

    gp = GridPosition(layout, 1, 1)
    gp[] = dr
    @test gridcontent(dr).span == GridLayoutBase.Span(1:1, 1:1)
end

@testset "gridposition contents" begin
    layout = GridLayout()
    dr1 = layout[1, 1] = DebugRect()
    dr2 = layout[1, 2] = DebugRect()
    dr3 = layout[2, 1] = DebugRect()
    dr4 = layout[2, 2] = DebugRect()

    co = contents(layout[1, 1])
    @test co == [dr1]

    co = contents(layout[1:2, 1])
    @test co == [dr1, dr3]

    co = contents(layout[2, 1:2])
    @test co == [dr3, dr4]

    co = contents(layout[:, :])
    @test co == [dr1, dr2, dr3, dr4]

    dr5 = layout[2, 2, Right()] = DebugRect()

    co = contents(layout[:, :]) # implicit Inner() side
    @test co == [dr1, dr2, dr3, dr4]

    @test contents(layout[2, 2, Right()]) == [dr5]

    dr6 = layout[1:2, 2] = DebugRect()
    @test contents(layout[1:2, 2]) == [dr2, dr4, dr6]
    @test contents(layout[1:2, 2], exact = true) == [dr6]

    @test contents(layout) == [dr1, dr2, dr3, dr4, dr5, dr6]
end

@testset "span containment" begin
    Span = GridLayoutBase.Span
    @test Span(1:2, 2:3) in Span(1:2, 2:3)
    @test Span(1:2, 2:3) in Span(0:3, 1:4)
    @test !(Span(0:3, 1:4) in Span(1:2, 2:3))
    @test !(Span(1:2, 1:4) in Span(1:2, 2:3))
    @test !(Span(0:3, 2:3) in Span(1:2, 2:3))
end

@testset "layoutobservables undefined" begin
    struct MyType end
    @test_throws ErrorException GridLayoutBase.layoutobservables(MyType())
end

@testset "determine gridlayout size" begin
    gl = GridLayout(;alignmode = Outside(0))
    gl[1, 1] = DebugRect(width = 800, height = 600)

    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Row()) == 600
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Col()) == 800

    gl[1, 1, Left()] = DebugRect(width = 200)
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Col()) == 800 + 200
    gl[1, 1, Right()] = DebugRect(width = 200)
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Col()) == 800 + 200 + 200

    gl[1, 1, Top()] = DebugRect(height = 100)
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Row()) == 600 + 100
    gl[1, 1, Bottom()] = DebugRect(height = 100)
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Row()) == 600 + 100 + 100

    gl.alignmode[] = Outside(50)
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Col()) == 800 + 200 + 200 + 2 * 50
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Row()) == 600 + 100 + 100 + 2 * 50

    gl.alignmode[] = Inside()
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Col()) == 800
    @test GridLayoutBase.determinedirsize(gl, GridLayoutBase.Row()) == 600
end

@testset "alignment" begin
    boxwidth = 1000
    boxheight = 800

    gl = GridLayout(; bbox = (0, boxwidth, 0, boxheight), alignmode = Outside(0))
    gl[1, 1] = DebugRect(width = boxwidth, height = boxheight)
    halign = Observable{Any}(:center)
    valign = Observable{Any}(:center)

    rectwidth = 400
    rectheight = 200

    dr = gl[1, 1] = DebugRect(width = rectwidth, height = 200, halign = halign, valign = valign)

    @test computedbboxobservable(dr)[] == BBox(boxwidth/2 - rectwidth/2, boxwidth/2 + rectwidth/2, boxheight/2 - rectheight/2, boxheight/2 + rectheight/2)
    halign[] = :left
    @test computedbboxobservable(dr)[] == BBox(0, rectwidth, boxheight/2 - rectheight/2, boxheight/2 + rectheight/2)
    halign[] = :right
    @test computedbboxobservable(dr)[] == BBox(boxwidth - rectwidth, boxwidth, boxheight/2 - rectheight/2, boxheight/2 + rectheight/2)
    valign[] = :top
    @test computedbboxobservable(dr)[] == BBox(boxwidth - rectwidth, boxwidth, boxheight - rectheight, boxheight)
    valign[] = :bottom
    @test computedbboxobservable(dr)[] == BBox(boxwidth - rectwidth, boxwidth, 0, rectheight)

    valign[] = 0.5
    @test computedbboxobservable(dr)[] == BBox(boxwidth - rectwidth, boxwidth,  boxheight/2 - rectheight/2, boxheight/2 + rectheight/2)
    halign[] = 0.5
    @test computedbboxobservable(dr)[] == BBox(boxwidth/2 - rectwidth/2, boxwidth/2 + rectwidth/2,  boxheight/2 - rectheight/2, boxheight/2 + rectheight/2)

    @test_throws ErrorException valign[] = :abc
    valign[] = 0.5
    @test_throws ErrorException halign[] = :abc
end

@testset "invalid gridlayout construction" begin
    @test_throws ErrorException GridLayout(2, 2; rowsizes = [Fixed(10)])
    @test_throws ErrorException GridLayout(2, 2; colsizes = [Fixed(10)])
    @test_throws ErrorException GridLayout(2, 2; addedrowgaps = [Fixed(10), Fixed(10)])
    @test_throws ErrorException GridLayout(2, 2; addedcolgaps = [Fixed(10), Fixed(10)])
    @test_throws ErrorException GridLayout(2, 2; rowsizes = [Fixed(10), Fixed(10)], addedrowgaps = [Fixed(10), Fixed(10)])
    @test_throws ErrorException GridLayout(2, 2; colsizes = [Fixed(10), Fixed(10)], addedcolgaps = [Fixed(10), Fixed(10)])
end

@testset "gridlayoutspec" begin
    dr = DebugRect()
    dr2 = DebugRect()
    dr3 = DebugRect()

    spec = GridLayoutSpec([
            (1, 1) => dr,
            (1:2, 4:5, Left()) => dr2,
            (:, 0) => GridLayoutSpec([
                (1:3, 2:4) => dr3
            ])
        ];
        alignmode = Outside()
    )

    gl = GridLayout(spec)
    @test contents(gl)[1] isa DebugRect
    @test contents(gl)[2] isa DebugRect
    @test contents(gl)[3] isa GridLayout
    @test contents(contents(gl)[3])[1] isa DebugRect
end

@testset "tellsize" begin
    dr = DebugRect(width = 100, height = 200, tellwidth = true, tellheight = false)

    @test dr.layoutobservables.reportedsize[] == (100, nothing)
end

@testset "aspect sizes referencing auto sizes" begin
    bbox = BBox(0, 1000, 0, 1000)
    layout = GridLayout(2, 2, bbox = bbox, alignmode = Outside(0))

    colgap!(layout, 0)
    rowgap!(layout, 0)

    dr = layout[1, 1] = DebugRect()

    colsize!(layout, 1, Aspect(1, 1.5))
    @test computedbboxobservable(dr)[] == BBox(0, 750, 500, 1000)

    colsize!(layout, 1, Auto())

    rowsize!(layout, 1, Aspect(1, 1.5))
    @test computedbboxobservable(dr)[] == BBox(0, 500, 250, 1000)

    @test_throws ErrorException colsize!(layout, 1, Aspect(1, 1))
end

@testset "integer rect2 suggestedbbox" begin
    layout = GridLayout(bbox = Observable(GridLayoutBase.GeometryBasics.Rect(0, 10, 20, 30)))
    @test suggestedbboxobservable(layout)[] == GridLayoutBase.GeometryBasics.Rect2f(0, 10, 20, 30)
end

@testset "GridSubpositions" begin
    l = GridLayout()
    gp = l[1, 1]
    @test gp isa GridPosition
    gsp = gp[1, 1]
    @test gsp isa GridSubposition
    @test isempty(contents(gp))
    r = gsp[] = DebugRect()
    @test content(gp) isa GridLayout
    @test content(gsp) == r

    r2 = l[1, 1][1, 2] = DebugRect()
    @test content(l[1, 1][1, 2]) == r2
    r3 = l[1, 1][1, 3][1, 1] = DebugRect()
    @test content(l[1, 1][1, 3][1, 1]) == r3
end

@testset "Parents" begin
    g = GridLayout()
    @test GridLayoutBase.parent(g) === nothing
    @test GridLayoutBase.top_parent(g) === nothing
    g2 = GridLayout()
    @test GridLayoutBase.parent(g2) === nothing
    g[1, 1] = g2
    @test GridLayoutBase.parent(g2) === g
    @test GridLayoutBase.top_parent(g2) === nothing
    @test GridLayoutBase.top_parent_grid(g2) === g

    g3 = GridLayout()
    @test GridLayoutBase.parent(g3) === nothing
    g2[1, 1] = g3
    @test GridLayoutBase.parent(g3) === g2
    @test GridLayoutBase.top_parent(g3) === nothing
    @test GridLayoutBase.top_parent_grid(g3) === g

    # make some arbitrary parent object with an id
    p = Dict()
    g.parent = p
    @test GridLayoutBase.parent(g) === p
    @test GridLayoutBase.top_parent(g) === p
    @test GridLayoutBase.top_parent(g2) === p
    @test GridLayoutBase.top_parent(g3) === p
    @test GridLayoutBase.top_parent_grid(g2) === g
    @test GridLayoutBase.top_parent_grid(g3) === g
end

@testset "Number of updates" begin
    g = GridLayout()
    n = Ref(0)
    on(g.layoutobservables.suggestedbbox) do _
        n[] += 1
    end

    g2 = GridLayout()
    g[1, 1] = g2
    @test n[] == 1

    m = Ref(0)
    on(g2.layoutobservables.suggestedbbox) do _
        m[] += 1
    end
    for i in 1:10
        g2[1, i] = GridLayout()
    end
    # one update for each gridlayout
    @test m[] == 10
    # g shouldn't have changed
    @test n[] == 1
    with_updates_suspended(g2) do
        for i in 1:10
            g2[1, i] = GridLayout()
        end
    end
    # only one update should have happened at the end
    @test m[] == 11
    # still nothing for g
    @test n[] == 1
end


function AbstractMultipleLinearRegression()
    return AbstractMultipleLinearRegression(())
end

function equals(obj::Object, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function estimate_error_variance(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateErrorVariance", jdouble, ())
end

function estimate_regressand_variance(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateRegressandVariance", jdouble, ())
end

function estimate_regression_parameters(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateRegressionParameters", Vector{jdouble}, ())
end

function estimate_regression_parameters_standard_errors(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateRegressionParametersStandardErrors", Vector{jdouble}, ())
end

function estimate_regression_parameters_variance(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateRegressionParametersVariance", Vector{Vector{jdouble}}, ())
end

function estimate_regression_standard_error(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateRegressionStandardError", jdouble, ())
end

function estimate_residuals(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "estimateResiduals", Vector{jdouble}, ())
end

function get_class(obj::Object)
    return jcall(obj, "getClass", Class, ())
end

function hash_code(obj::Object)
    return jcall(obj, "hashCode", jint, ())
end

function is_no_intercept(obj::AbstractMultipleLinearRegression)
    return jcall(obj, "isNoIntercept", jboolean, ())
end

function new_sample_data(obj::AbstractMultipleLinearRegression, arg0::Vector{jdouble}, arg1::jint, arg2::jint)
    return jcall(obj, "newSampleData", void, (Vector{jdouble}, jint, jint), arg0, arg1, arg2)
end

function notify(obj::Object)
    return jcall(obj, "notify", void, ())
end

function notify_all(obj::Object)
    return jcall(obj, "notifyAll", void, ())
end

function set_no_intercept(obj::AbstractMultipleLinearRegression, arg0::jboolean)
    return jcall(obj, "setNoIntercept", void, (jboolean,), arg0)
end

function to_string(obj::Object)
    return jcall(obj, "toString", JString, ())
end

function wait(obj::Object)
    return jcall(obj, "wait", void, ())
end

function wait(obj::Object, arg0::jlong)
    return jcall(obj, "wait", void, (jlong,), arg0)
end

function wait(obj::Object, arg0::jlong, arg1::jint)
    return jcall(obj, "wait", void, (jlong, jint), arg0, arg1)
end


"""
    julia2onnx(x)

Convert argument from Julia-friendly to ONNX-friendly format.
For example:

 * tensors are reshaped from column-major to row-major
 * shape tuples are rearranged accordingly
 * attributes in dicts are renamed, their values are converted too

 The reverse operation is available as [onnx2julia](@ref).
"""
julia2onnx(x::AbstractArray) = reshape(x, reverse(size(x)))

"""
    onnx2julia(x)

The reverse of [julia2onnx](@ref).
"""
onnx2julia(x::AbstractArray) = reshape(x, reverse(size(x)))


#  point in time observation that has relevance to markets

import Base: show, getindex

immutable MarketSignal <: AbstractTimeSeries
    timestamp::Vector{Date}
    values::BitArray
    colnames::Vector{ASCIIString}
end

function show(io::IO, ms::MarketSignal)
  # variables
  nrow          = size(ms.values, 1)
  ncol          = size(ms.values, 2)
  spacetime     = strwidth(string(ms.timestamp[1])) + 3
  firstcolwidth = strwidth(ms.colnames[1])
  colwidth      = Int[]
      for m in 1:ncol
          push!(colwidth, max(strwidth(ms.colnames[m]), 5))
      end

  # summary line
  print(io, @sprintf("%dx%d %s %s to %s", nrow, ncol, typeof(ms), string(ms.timestamp[1]), string(ms.timestamp[nrow])))
  println(io, "")
  println(io, "")

  # row label line
   print(io, ^(" ", spacetime), ms.colnames[1], ^(" ", colwidth[1] + 2 -firstcolwidth))

   for p in 2:length(colwidth)
     print(io, ms.colnames[p], ^(" ", colwidth[p] - strwidth(ms.colnames[p]) + 2))
   end
   println(io,"")

  # timestamp and values line
    if nrow > 7
        for i in 1:4
            print(io, ms.timestamp[i], " | ")
        for j in 1:ncol
            print(io, rpad(ms.values[i,j], colwidth[j] + 2, " "))
        end
        println(io, "")
        end
        println(io, '\u22EE')
        for i in nrow-3:nrow
            print(io, ms.timestamp[i], " | ")
        for j in 1:ncol
            print(io, rpad(ms.values[i,j], colwidth[j] + 2, " "))
        end
        println(io, "")
        end
    else
        for i in 1:nrow
            print(io, ms.timestamp[i], " | ")
        for j in 1:ncol
            print(io, rpad(ms.values[i,j], colwidth[j] + 2, " "))
        end
        println(io, "")
        end
    end
end

# convert bool to float and squish a continuous stream to unique, discrete occurances

function discretesignal(ba::TimeArray{Bool,1})
#function discretesignal(ms::MarketSignal)
    iba = float64(ba.values)
    idx = [1]
    for b in 2:length(ba)
        if ba.values[b] !== ba.values[b-1]
            push!(idx, b)
        end
    end
    TimeArray(ba.timestamp[idx], iba[idx], ["signal"])
end


export Recommender
export isbuilt, check_build_status, build!, recommend, predict, ranking

abstract type Recommender end

function check_build_status(recommender::Recommender)
    if !isbuilt(recommender)
        error("Recommender $(typeof(recommender)) is not built before making recommendation")
    end
end

isbuilt(recommender::Recommender) = true

function build!(recommender::Recommender; kwargs...)
    error("build! is not implemented for recommender type $(typeof(recommender))")
end

function recommend(recommender::Recommender, u::Int, k::Int, candidates::Array{Int})
    d = Dict{Int,Float64}()
    for candidate in candidates
        score = ranking(recommender, u, candidate)
        d[candidate] = score
    end
    sort(collect(d), by=tuple->last(tuple), rev=true)[1:k]
end

function predict(recommender::Recommender, u::Int, i::Int)
    error("predict is not implemented for recommender type $(typeof(recommender))")
end

# Return a ranking score of item i for user u
function ranking(recommender::Recommender, u::Int, i::Int)
    check_build_status(recommender)
    predict(recommender, u, i)
end


plural(::Type{Nanosecond}) = Nanoseconds
plural(::Type{Microsecond}) = Microseconds
plural(::Type{Millisecond}) = Milliseconds
plural(::Type{Second}) = Seconds
plural(::Type{Minute}) = Minutes
plural(::Type{Hour}) = Hours
plural(::Type{Day}) = Days
plural(::Type{Week}) = Weeks
plural(::Type{Month}) = Months
plural(::Type{Year}) = Years

Nanoseconds(x::Period) = Nanosecond(x)
Nanoseconds(x::CompoundPeriod) = sum(map(Nanosecond, x.periods))
Nanoseconds(x::ReverseCompoundPeriod) = sum(map(Nanosecond, x.cperiod.periods))

nanoseconds(x::T) where {T<:Union{Period,Compounds}} = Nanoseconds(x).value

TimeUnits(x::CompoundPeriod) = sum(map(mintype(x), x.periods))
TimeUnits(x::ReverseCompoundPeriod) = sum(map(mintype(x), x.cperiod.periods))
TimeUnits(x::Period) = x

Microseconds(x::T) where {T<:Nanosecond} = floor(x, Microsecond)
Microseconds(x::T) where {T<:Period} = Microsecond(x)
Microseconds(x::T) where {T<:Compounds} = Microseconds(TimeUnits(x))

microseconds(x::T) where {T<:Union{Period,Compounds}} = Microseconds(x).value

Milliseconds(x::T) where {T<:Union{Nanosecond, Microsecond}} = floor(x, Millisecond)
Milliseconds(x::T) where {T<:Period} = Millisecond(x)
Milliseconds(x::T) where {T<:Compounds} = Milliseconds(TimeUnits(x))

milliseconds(x::T) where {T<:Union{Period,Compounds}} = Milliseconds(x).value

Seconds(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond}} = floor(x, Second)
Seconds(x::T) where {T<:Period} = Second(x)
Seconds(x::T) where {T<:Compounds} = Seconds(TimeUnits(x))

seconds(x::T) where {T<:Union{Period,Compounds}} = Seconds(x).value

Minutes(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second}} = floor(x, Minute)
Minutes(x::T) where {T<:Period} = Minute(x)
Minutes(x::T) where {T<:Compounds} = Minutes(TimeUnits(x))

minutes(x::T) where {T<:Union{Period,Compounds}} = Minutes(x).value

Hours(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second, Minute}} = floor(x, Hour)
Hours(x::T) where {T<:Period} = Hour(x)
Hours(x::T) where {T<:Compounds} = Hours(TimeUnits(x))

hours(x::T) where {T<:Union{Period,Compounds}} = Hours(x).value

Days(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second, Minute, Hour}} = floor(x, Day)
Days(x::T) where {T<:Period} = Day(x)
Days(x::T) where {T<:Compounds} = Days(TimeUnits(x))

days(x::T) where {T<:Union{Period,Compounds}} = Days(x).value

Weeks(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second, Minute, Hour, Day}} = floor(x, Week)
Weeks(x::T) where {T<:Period} = Week(x)
Weeks(x::T) where {T<:Compounds} = Weeks(TimeUnits(x))

weeks(x::T) where {T<:Union{Period,Compounds}} = Weeks(x).value

Months(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second, Minute, Hour, Day}} = Month(0)
Months(x::T) where {T<:Year} = floor(x, Month)
Months(x::T) where {T<:Period} = Month(x)
Months(x::CompoundPeriod) = sum(map(Month, x.periods))
Months(x::ReverseCompoundPeriod) = sum(map(Month, x.cperiod.periods))

months(x::T) where {T<:Union{Period,Compounds}} = Months(x).value

Years(x::T) where {T<:Union{Nanosecond, Microsecond, Millisecond, Second, Minute, Hour, Day}} = Year(0)
Years(x::T) where {T<:Month} = floor(x, Year)
Years(x::T) where {T<:Period} = Year(x)
Years(x::CompoundPeriod) = sum(map(Year, x.periods))
Years(x::ReverseCompoundPeriod) = sum(map(Year, x.cperiod.periods))

years(x::T) where {T<:Union{Period,Compounds}} = Years(x).value

for (P, Q) in ((:Years,:years), (:Months,:months), (:Weeks,:weeks), (:Days,:days),
               (:Hours,:hours), (:Minutes,:minutes), (:Seconds,:seconds),
               (:Milliseconds,:milliseconds), (:Microseconds,:microseconds),
               (:Nanoseconds,:nanoseconds))
    @eval begin
        $P(x::DateTime) = $P(CompoundPeriod(x))
        $Q(x::DateTime) = $Q(CompoundPeriod(x))
    end
end

for (P, Q) in ((:Years,:years), (:Months,:months), (:Days, :days))
    @eval begin
        $P(x::Date) = $P(CompoundPeriod(x))
        $Q(x::Date) = $Q(CompoundPeriod(x))
    end
end

for (P, Q) in ((:Hours,:hours), (:Minutes,:minutes), (:Seconds,:seconds),
               (:Milliseconds,:milliseconds), (:Microseconds,:microseconds),
               (:Nanoseconds,:nanoseconds))
    @eval begin
        $P(x::Time) = $P(CompoundPeriod(x))
        $Q(x::Time) = $Q(CompoundPeriod(x))
    end
end


"""

# StructuralCausalModels

StructuralCausalModels.jl provides functionality to analyse directed acyclic
graph (DAG) based causal models as described in `StatisticalRethinking`, `Causal
Inference in Statistics` and `Cause and Correlation in Biology`.

My initial goal for this package is to have a way to apply SCM ideas to the
examples in the context of StatisticalRethinking, i.e. a working version of
`basis_set()`, `d_separation()`, `m_separation()` and `adjustment_sets()`.

SCM can be used as an alias to StructuralCausalModels.
"""
module StructuralCausalModels

using Reexport

@reexport using DataStructures, LinearAlgebra, Statistics
@reexport using Distributions, NamedArrays, DataFrames, CSV
@reexport using Combinatorics

src_path = @__DIR__

using DocStringExtensions: SIGNATURES, FIELDS, TYPEDEF

const scm_src_path = @__DIR__

"""

SCM

Alias for StructuralCausalModels
"""
const SCM = StructuralCausalModels

"""

# scm_path

Relative path using the StructuralCausalModels.jl src/ directory.

### Example to get access to the data subdirectory
```julia
scm_path("..", "data")
```

Part of the API, exported.
"""
scm_path(parts...) = normpath(joinpath(scm_src_path, parts...))

SymbolList = Union{Symbol, Vector{Symbol}}
SymbolListOrNothing = Union{SymbolList, Nothing}
OrderedDictOrNothing = Union{OrderedDict, Nothing}
NamedArrayOrNothing = Union{NamedArray, Nothing}
DataFrameOrNothing = Union{DataFrame, Nothing}
ModelDefinition = Union{OrderedDict, AbstractString, NamedArray}

include("types/DAG.jl")
include("types/Path.jl")
include("types/BasisSet.jl")
include("types/ConditionalIndependence.jl")

include("methods/dag_methods.jl")
include("methods/basis_set.jl")
include("methods/shipley_test.jl")
include("methods/d_separation.jl")
include("methods/pcor.jl")
include("methods/pcor_test.jl")
include("methods/induced_covariance_graph.jl")
include("methods/all_paths.jl")
include("methods/open_paths.jl")
include("methods/backdoor_paths.jl")
include("methods/adjustment_sets.jl")
include("methods/graph_utils.jl")
include("methods/ancestral_graph.jl")
include("methods/ribbon_graph.jl")
include("methods/maximize.jl")
include("methods/m_separation.jl")

include("utils/show_dag_path.jl")
include("utils/ggm_conversions.jl")
include("utils/dagitty_conversions.jl")
include("utils/graphviz_conversions.jl")

export
  SCM,
  scm_path

end # module