text:
show(x) = show(STDOUT::IO, x)

print(io::IO, s::Symbol) = (write(io,s);nothing)

function show(io::IO, x::ANY)
    t = typeof(x)::DataType
    show(io, t)
    print(io, '(')
    if t.names !== () || t.size==0
        recorded = false
        oid = object_id(x)
        shown_set = get(task_local_storage(), :SHOWNSET, nothing)
        if shown_set == nothing
            shown_set = Set()
            task_local_storage(:SHOWNSET, shown_set)
        end

        try
            if oid in shown_set
                print(io, "#= circular reference =#")
            else
                push!(shown_set, oid)
                recorded = true

                n = length(t.names)
                for i=1:n
                    f = t.names[i]
                    if !isdefined(x, f)
                        print(io, undef_ref_str)
                    else
                        show(io, x.(f))
                    end
                    if i < n
                        print(io, ',')
                    end
                end
            end
        catch e
            rethrow(e)

        finally
            if recorded delete!(shown_set, oid) end
        end
    else
        nb = t.size
        print(io, "0x")
        p = pointer_from_objref(x) + sizeof(Ptr{Void})
        for i=nb-1:-1:0
            print(io, hex(unsafe_load(convert(Ptr{Uint8}, p+i)), 2))
        end
    end
    print(io,')')
end

function show(io::IO, f::Function)
    if isgeneric(f)
        print(io, f.env.name)
    elseif isdefined(f, :env) && isa(f.env,Symbol)
        print(io, f.env)
    else
        print(io, "(anonymous function)")
    end
end

function show(io::IO, x::IntrinsicFunction)
    print(io, "(intrinsic function #", box(Int32,unbox(IntrinsicFunction,x)), ")")
end

function show(io::IO, x::UnionType)
    if is(x,None)
        print(io, "None")
    elseif is(x,Top)
        print(io, "Top")
    else
        print(io, "Union", x.types)
    end
end

show(io::IO, x::TypeConstructor) = show(io, x.body)

function show(io::IO, x::DataType)
    if isvarargtype(x)
        print(io, x.parameters[1], "...")
    else
        print(io, x.name.name)
        if length(x.parameters) > 0
            show_comma_array(io, x.parameters, '{', '}')
        end
    end
end

showcompact(io::IO, x) = show(io, x)
showcompact(x) = showcompact(STDOUT::IO, x)

showcompact_lim(io, x) = _limit_output ? showcompact(io, x) : show(io, x)
showcompact_lim(io, x::Number) = _limit_output ? showcompact(io, x) : print(io, x)

macro show(exs...)
    blk = Expr(:block)
    for ex in exs
        push!(blk.args, :(println($(sprint(show_unquoted,ex)*" => "),
                                  repr(begin value=$(esc(ex)) end))))
    end
    if !isempty(exs); push!(blk.args, :value); end
    return blk
end

show(io::IO, tn::TypeName) = print(io, tn.name)
show(io::IO, ::Nothing) = print(io, "nothing")
show(io::IO, b::Bool) = print(io, b ? "true" : "false")
show(io::IO, n::Signed) = (write(io, dec(n)); nothing)
show(io::IO, n::Unsigned) = print(io, "0x", hex(n,sizeof(n)<<1))
print(io::IO, n::Unsigned) = print(io, dec(n))

show{T}(io::IO, p::Ptr{T}) =
    print(io, is(T,None) ? "Ptr{Void}" : typeof(p), " @0x$(hex(unsigned(p), WORD_SIZE>>2))")

function show(io::IO, m::Module)
    if is(m,Main)
        print(io, "Main")
    else
        print(io, join(fullname(m),"."))
    end
end

function show(io::IO, l::LambdaStaticData)
    print(io, "AST(")
    show(io, uncompressed_ast(l))
    print(io, ")")
end

function show_delim_array(io::IO, itr::AbstractArray, op, delim, cl, delim_one, compact=false)
    print(io, op)
    newline = true
    first = true
    i = 1
    l = length(itr)
    if l > 0
        while true
            if !isassigned(itr, i)
                print(io, undef_ref_str)
                multiline = false
            else
                x = itr[i]
                multiline = isa(x,AbstractArray) && ndims(x)>1 && length(x)>0
                newline && multiline && println(io)
                compact ? showcompact_lim(io, x) : show(io, x)
            end
            i += 1
            if i > l
                delim_one && first && print(io, delim)
                break
            end
            first = false
            print(io, delim)
            if multiline
                println(io); println(io)
                newline = false
            else
                newline = true
            end
        end
    end
    print(io, cl)
end

function show_delim_array(io::IO, itr, op, delim, cl, delim_one)
    print(io, op)
    state = start(itr)
    newline = true
    first = true
    if !done(itr,state)
        while true
            x, state = next(itr,state)
            multiline = isa(x,AbstractArray) && ndims(x)>1 && length(x)>0
            newline && multiline && println(io)
            show(io, x)
            if done(itr,state)
                delim_one && first && print(io, delim)
                break
            end
            first = false
            print(io, delim)
            if multiline
                println(io); println(io)
                newline = false
            else
                newline = true
            end
        end
    end
    print(io, cl)
end

show_comma_array(io::IO, itr, o, c) = show_delim_array(io, itr, o, ',', c, false)
show(io::IO, t::Tuple) = show_delim_array(io, t, '(', ',', ')', true)

show(io::IO, s::Symbol) = show_unquoted(io, QuoteNode(s))
show(io::IO, tn::TypeName) = print(io, tn.name)
show(io::IO, ::Nothing) = print(io, "nothing")
show(io::IO, b::Bool) = print(io, b ? "true" : "false")
show(io::IO, n::Signed) = (write(io, dec(n)); nothing)
show(io::IO, n::Unsigned) = print(io, "0x", hex(n,sizeof(n)<<1))
print(io::IO, n::Unsigned) = print(io, dec(n))

## Abstract Syntax Tree (AST) printing ##

# Summary:
#   print(io, ex) defers to show_unquoted(io, ex)
#   show(io, ex) defers to show_unquoted(io, QuoteNode(ex))
#   show_unquoted(io, ex) does the heavy lifting
#
# AST printing should follow two rules:
#   1. parse(string(ex)) == ex
#   2. eval(parse(repr(ex))) == ex
#
# Rule 1 means that printing an expression should generate Julia code which
# could be reparsed to obtain the original expression. This code should be
# unambiguous and as readable as possible.
#
# Rule 2 means that showing an expression should generate a quoted version of
# print’s output. Parsing and then evaling this output should return the
# original expression.
#
# This is consistent with many other show methods, i.e.:
#   show(Set([1,2,3]))                # ==> "Set{Int64}([2,3,1])"
#   eval(parse("Set{Int64}([2,3,1])”) # ==> An actual set
# While this isn’t true of ALL show methods, it is of all ASTs.

typealias ExprNode Union(Expr, QuoteNode, SymbolNode, LineNumberNode,
                         LabelNode, GotoNode, TopNode)
print        (io::IO, ex::ExprNode)    = show_unquoted(io, ex)
show         (io::IO, ex::ExprNode)    = show_unquoted(io, QuoteNode(ex))
show_unquoted(io::IO, ex)              = show_unquoted(io, ex, 0, 0)
show_unquoted(io::IO, ex, indent::Int) = show_unquoted(io, ex, indent, 0)
show_unquoted(io::IO, ex, ::Int,::Int) = show(io, ex)

## AST printing constants ##

const indent_width = 4
const quoted_syms = Set{Symbol}([:(:),:(::),:(:=),:(=),:(==),:(===),:(=>)])
const uni_ops = Set{Symbol}([:(+), :(-), :(!), :(~), :(<:), :(>:)])
const bin_ops_by_prec = [
    "= := += -= *= /= //= .//= .*= ./= \\= .\\= ^= .^= %= .%= |= &= \$= => <<= >>= >>>= ~ .+= .-=",
    "?",
    "||",
    "&&",
    "-- -->",
    "> < >= <= == === != !== .> .< .>= .<= .== .!= .= .! <: >:",
    "|> <|",
    ": ..",
    "+ - .+ .- | \$",
    "<< >> >>> .<< .>> .>>>",
    "* / ./ % .% & .* \\ .\\",
    "// .//",
    "^ .^",
    "::",
    "."
]
const bin_op_precs = Dict{Symbol,Int}(merge([{symbol(op)=>i for op=split(bin_ops_by_prec[i])} for i=1:length(bin_ops_by_prec)]...))
const bin_ops = Set{Symbol}(keys(bin_op_precs))
const expr_infix_wide = Set([:(=), :(+=), :(-=), :(*=), :(/=), :(\=), :(&=),
    :(|=), :($=), :(>>>=), :(>>=), :(<<=), :(&&), :(||)])
const expr_infix = Set([:(:), :(<:), :(->), :(=>), symbol("::")])
const expr_calls  = [:call =>('(',')'), :ref =>('[',']'), :curly =>('{','}')]
const expr_parens = [:tuple=>('(',')'), :vcat=>('[',']'), :cell1d=>('{','}'),
                     :hcat =>('[',']'), :row =>('[',']')]

## AST decoding helpers ##

is_expr(ex, head::Symbol)         = (isa(ex, Expr) && (ex.head == head))
is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n

is_linenumber(ex::LineNumberNode) = true
is_linenumber(ex::Expr)           = is(ex.head, :line)
is_linenumber(ex)                 = false

is_quoted(ex)            = false
is_quoted(ex::QuoteNode) = true
is_quoted(ex::Expr)      = is_expr(ex, :quote, 1)

unquoted(ex::QuoteNode)  = ex.value
unquoted(ex::Expr)       = ex.args[1]

## AST printing helpers ##

const indent_width = 4

function show_expr_type(io::IO, ty)
    if !is(ty, Any)
        if is(ty, Function)
            print(io, "::F")
        elseif is(ty, IntrinsicFunction)
            print(io, "::I")
        else
            print(io, "::$ty")
        end
    end
end

show_linenumber(io::IO, line)       = print(io," # line ",line,':')
show_linenumber(io::IO, line, file) = print(io," # ",file,", line ",line,':')

# show a block, e g if/for/etc
function show_block(io::IO, head, args::Vector, body, indent::Int)
    print(io, head, ' ')
    show_list(io, args, ", ", indent)

    ind = is(head, :module) ? indent : indent + indent_width
    exs = (is_expr(body, :block) || is_expr(body, :body)) ? body.args : {body}
    for ex in exs
        if !is_linenumber(ex); print(io, '\n', " "^ind); end
        show_unquoted(io, ex, ind)
    end
    print(io, '\n', " "^indent)
end
show_block(io::IO,head,    block,i::Int) = show_block(io,head,{},   block,i)
show_block(io::IO,head,arg,block,i::Int) = show_block(io,head,{arg},block,i)

# show an indented list
function show_list(io::IO, items, sep, indent::Int, prec::Int=0)
    n = length(items)
    if n == 0; return end
    indent += indent_width
    show_unquoted(io, items[1], indent, prec)
    for item in items[2:end]
        print(io, sep)
        show_unquoted(io, item, indent, prec)
    end
end
# show an indented list inside the parens (op, cl)
function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0)
    print(io, op); show_list(io, items, sep, indent, prec); print(io, cl)
end

## AST printing ##

show_unquoted(io::IO, sym::Symbol, ::Int, ::Int)        = print(io, sym)
show_unquoted(io::IO, ex::LineNumberNode, ::Int, ::Int) = show_linenumber(io, ex.line)
show_unquoted(io::IO, ex::LabelNode, ::Int, ::Int)      = print(io, ex.label, ": ")
show_unquoted(io::IO, ex::GotoNode, ::Int, ::Int)       = print(io, "goto ", ex.label)
show_unquoted(io::IO, ex::TopNode, ::Int, ::Int)        = print(io,"top(",ex.name,')')

function show_unquoted(io::IO, ex::SymbolNode, ::Int, ::Int)
    print(io, ex.name)
    show_expr_type(io, ex.typ)
end

show_unquoted(io::IO, ex::QuoteNode, indent::Int, prec::Int) =
    show_unquoted_quote_expr(io, ex.value, indent, prec)

function show_unquoted_quote_expr(io::IO, value, indent::Int, prec::Int)
    if isa(value, Symbol) && !(value in quoted_syms)
        print(io, ":")
        print(io, value)
    else
        print(io, ":(")
        show_unquoted(io, value, indent+indent_width, 0)
        print(io, ")")
    end
end

# TODO: implement interpolated strings
function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int)
    head, args, nargs = ex.head, ex.args, length(ex.args)

    # dot (i.e. "x.y")
    if is(head, :(.))
        show_unquoted(io, args[1], indent + indent_width)
        print(io, '.')
        if is_quoted(args[2])
            show_unquoted(io, unquoted(args[2]), indent + indent_width)
        else
            print(io, '(')
            show_unquoted(io, args[2], indent + indent_width)
            print(io, ')')
        end

    # infix (i.e. "x<:y" or "x = y")
    elseif (head in expr_infix && nargs==2) || (is(head,:(:)) && nargs==3)
        show_list(io, args, head, indent)
    elseif head in expr_infix_wide && nargs == 2
        func_prec = get(bin_op_precs, head, 0)
        if func_prec < prec
            show_enclosed_list(io, '(', args, " $head ", ')', indent, func_prec)
        else
            show_list(io, args, " $head ", indent, func_prec)
        end

    # list (i.e. "(1,2,3)" or "[1,2,3]")
    elseif haskey(expr_parens, head)               # :tuple/:vcat/:cell1d
        op, cl = expr_parens[head]
        if head === :vcat && !isempty(args) && is_expr(args[1], :row)
            sep = ";"
        elseif head === :hcat || head === :row
            sep = " "
        else
            sep = ","
        end
        head !== :row && print(io, op)
        show_list(io, args, sep, indent)
        if is(head, :tuple) && nargs == 1; print(io, ','); end
        head !== :row && print(io, cl)

    # function call
    elseif haskey(expr_calls, head) && nargs >= 1  # :call/:ref/:curly
        func = args[1]
        func_prec = get(bin_op_precs, func, 0)
        func_args = args[2:end]

        # scalar multiplication (i.e. "100x")
        if func == :(*) && length(func_args)==2 && isa(func_args[1], Real) && isa(func_args[2], Symbol)
            if func_prec <= prec
                show_enclosed_list(io, '(', func_args, "", ')', indent, func_prec)
            else
                show_list(io, func_args, "", indent, func_prec)
            end

        # unary operator (i.e. "!z")
        elseif func in uni_ops && length(func_args) == 1
            show_unquoted(io, func, indent)
            if isa(func_args[1], Expr) || length(func_args) > 1
                show_enclosed_list(io, '(', func_args, ",", ')', indent, func_prec)
            else
                show_unquoted(io, func_args[1])
            end

        # binary operator (i.e. "x + y")
        elseif func in bin_ops
            if length(func_args) > 1
                sep = func_prec >= bin_op_precs[:(^)] ? "$func" : " $func "
                if func_prec <= prec
                    show_enclosed_list(io, '(', func_args, sep, ')', indent, func_prec)
                else
                    show_list(io, func_args, sep, indent, func_prec)
                end
            else
                # 1-argument call to normally-binary operator
                op, cl = expr_calls[head]
                print(io, "(")
                show_unquoted(io, func, indent)
                print(io, ")")
                show_enclosed_list(io, op, func_args, ",", cl, indent)
            end

        # normal function (i.e. "f(x,y)")
        else
            op, cl = expr_calls[head]
            show_unquoted(io, func, indent)
            show_enclosed_list(io, op, func_args, ",", cl, indent)
        end
    elseif is(head, :ccall)
        show_unquoted(io, :ccall, indent)
        show_enclosed_list(io, '(', args, ",", ')', indent)

    # comparison (i.e. "x < y < z")
    elseif is(head, :comparison) && nargs >= 3 && (nargs&1==1)
        comp_prec = minimum([get(bin_op_precs, comp, 0) for comp=args[2:2:end]])
        if comp_prec <= prec
            show_enclosed_list(io, '(', args, " ", ')', indent, comp_prec)
        else
            show_list(io, args, " ", indent, comp_prec)
        end

    # block with argument
    elseif head in (:for,:while,:function,:if,:module) && nargs==2
        show_block(io, head, args[1], args[2], indent); print(io, "end")

    # type declaration
    elseif is(head, :type) && nargs==3
        show_block(io, args[1] ? :type : :immutable, args[2], args[3], indent)
        print(io, "end")

    # empty return (i.e. "function f() return end")
    elseif is(head, :return) && nargs == 1 && is(args[1], nothing)
        print(io, head)

    # type annotation (i.e. "::Int")
    elseif is(head, symbol("::")) && nargs == 1
        print(io, "::")
        show_unquoted(io, args[1], indent)

    # var-arg declaration or expansion
    # (i.e. "function f(L...) end" or "f(B...)")
    elseif is(head, :(...)) && nargs == 1
        show_unquoted(io, args[1], indent)
        print(io, "...")

    elseif (nargs == 1 && head in (:return, :abstract, :const)) ||
                          head in (:local,  :global)
        print(io, head, ' ')
        show_list(io, args, ", ", indent)
    elseif is(head, :macrocall) && nargs >= 1
        show_list(io, args, ' ', indent)
    elseif is(head, :typealias) && nargs == 2
        print(io, "typealias ")
        show_list(io, args, ' ', indent)
    elseif is(head, :line) && 1 <= nargs <= 2
        show_linenumber(io, args...)
    elseif is(head, :if) && nargs == 3           # if/else
        show_block(io, "if",   args[1], args[2], indent)
        show_block(io, "else", args[3], indent)
        print(io, "end")
    elseif is(head, :try) && 3 <= nargs <= 4
        show_block(io, "try", args[1], indent)
        if is_expr(args[3], :block)
            show_block(io, "catch", is(args[2], false) ? [] : args[2], args[3], indent)
        end
        if nargs >= 4 && is_expr(args[4], :block)
            show_block(io, "finally", [], args[4], indent)
        end
        print(io, "end")
    elseif is(head, :let) && nargs >= 1
        show_block(io, "let", args[2:end], args[1], indent); print(io, "end")
    elseif is(head, :block) || is(head, :body)
        show_block(io, "begin", ex, indent); print(io, "end")
    elseif is(head, :quote) && nargs == 1
        show_unquoted_quote_expr(io, args[1], indent, 0)
    elseif is(head, :gotoifnot) && nargs == 2
        print(io, "unless ")
        show_list(io, args, " goto ", indent)
    elseif is(head, :string) && nargs == 1 && isa(args[1], String)
        show(io, args[1])
    elseif is(head, :null)
        print(io, "nothing")
    elseif is(head, :kw) && length(args)==2
        show_unquoted(io, args[1], indent+indent_width)
        print(io, '=')
        show_unquoted(io, args[2], indent+indent_width)
    elseif is(head, :string)
        a = map(args) do x
            if !isa(x,String)
                if isa(x,Symbol) && !(x in quoted_syms)
                    string("\$", x)
                else
                    string("\$(", sprint(show_unquoted,x), ")")
                end
            else
                sprint(print_escaped, x, "\"\$")
            end
        end
        print(io, '"', a..., '"')

    elseif is(head, :&) && length(args) == 1
        print(io, '&')
        show_unquoted(io, args[1])

    # print anything else as "Expr(head, args...)"
    else
        print(io, "\$(Expr(")
        show(io, ex.head)
        for arg in args
            print(io, ", ")
            show(io, arg)
        end
        print(io, "))")
    end

    show_expr_type(io, ex.typ)
end

# dump & xdump - structured tree representation like R's str()
# - dump is for the user-facing structure
# - xdump is for the internal structure
#
# x is the object
# n is the depth of traversal in nested types (5 is the default)
# indent is a character string of spaces that is incremented at
# each descent.
#
# Package writers may overload dump for other nested types like lists
# or DataFrames. If overloaded, check the nesting level (n), and if
# n > 0, dump each component. Limit to the first 10 entries. When
# dumping components, decrement n, and add two spaces to indent.
#
# Package writers should not overload xdump.

function xdump(fn::Function, io::IO, x, n::Int, indent)
    T = typeof(x)
    print(io, T, " ")
    if isa(T, DataType) && length(T.names) > 0
        println(io)
        if n > 0
            for field in T.names
                if field != symbol("")    # prevents segfault if symbol is blank
                    print(io, indent, "  ", field, ": ")
                    if isdefined(x,field)
                        fn(io, getfield(x, field), n - 1, string(indent, "  "))
                    else
                        println(io, undef_ref_str)
                    end
                end
            end
        end
    else
        println(io, x)
    end
end
function xdump(fn::Function, io::IO, x::Module, n::Int, indent)
    print(io, Module, " ")
    println(io, x)
end
function xdump_elts(fn::Function, io::IO, x::Array{Any}, n::Int, indent, i0, i1)
    for i in i0:i1
        print(io, indent, "  ", i, ": ")
        if !isdefined(x,i)
            println(io, undef_ref_str)
        else
            fn(io, x[i], n - 1, string(indent, "  "))
        end
    end
end
function xdump(fn::Function, io::IO, x::Array{Any}, n::Int, indent)
    println(io, "Array($(eltype(x)),$(size(x)))")
    if n > 0
        xdump_elts(fn, io, x, n, indent, 1, (length(x) <= 10 ? length(x) : 5))
        if length(x) > 10
            println(io, indent, "  ...")
            xdump_elts(fn, io, x, n, indent, length(x)-4, length(x))
        end
    end
end
xdump(fn::Function, io::IO, x::Symbol, n::Int, indent) = println(io, typeof(x), " ", x)
xdump(fn::Function, io::IO, x::Function, n::Int, indent) = println(io, x)
xdump(fn::Function, io::IO, x::Array, n::Int, indent) =
               (print(io, "Array($(eltype(x)),$(size(x))) ");
                show(io, x); println(io))

# Types
xdump(fn::Function, io::IO, x::UnionType, n::Int, indent) = println(io, x)
function xdump(fn::Function, io::IO, x::DataType, n::Int, indent)
    println(io, x, "::", typeof(x), " ", " <: ", super(x))
    if n > 0
        for idx in 1:min(10,length(x.names))
            if x.names[idx] != symbol("")    # prevents segfault if symbol is blank
                print(io, indent, "  ", x.names[idx], "::")
                if isa(x.types[idx], DataType)
                    xdump(fn, io, x.types[idx], n - 1, string(indent, "  "))
                else
                    println(io, x.types[idx])
                end
            end
        end
        if length(x.names) > 10
            println(io, indent, "  ...")
        end
    end
end

# dumptype is for displaying abstract type hierarchies like Jameson
# Nash's wiki page: https://github.com/JuliaLang/julia/wiki/Types-Hierarchy

function dumptype(io::IO, x, n::Int, indent)
    # based on Jameson Nash's examples/typetree.jl
    println(io, x)
    if n == 0   # too deeply nested
        return
    end
    typargs(t) = split(string(t), "{")[1]
    # todo: include current module?
    for m in (Core, Base)
        for s in names(m)
            if isdefined(m,s)
                t = eval(m,s)
                if isa(t, TypeConstructor)
                    if string(x.name) == typargs(t) ||
                        ("Union" == split(string(t), "(")[1] &&
                         any(map(tt -> string(x.name) == typargs(tt), t.body.types)))
                        targs = join(t.parameters, ",")
                        println(io, indent, "  ", s,
                                length(t.parameters) > 0 ? "{$targs}" : "",
                                " = ", t)
                    end
                elseif isa(t, UnionType)
                    if any(tt -> string(x.name) == typargs(tt), t.types)
                        println(io, indent, "  ", s, " = ", t)
                    end
                elseif isa(t, DataType) && super(t).name == x.name
                    # type aliases
                    if string(s) != string(t.name)
                        println(io, indent, "  ", s, " = ", t.name)
                    elseif t != Any
                        print(io, indent, "  ")
                        dump(io, t, n - 1, string(indent, "  "))
                    end
                end
            end
        end
    end
end

# For abstract types, use _dumptype only if it's a form that will be called
# interactively.
xdump(fn::Function, io::IO, x::DataType) = x.abstract ? dumptype(io, x, 5, "") : xdump(fn, io, x, 5, "")
xdump(fn::Function, io::IO, x::DataType, n::Int) = x.abstract ? dumptype(io, x, n, "") : xdump(fn, io, x, n, "")

# defaults:
xdump(fn::Function, io::IO, x) = xdump(xdump, io, x, 5, "")  # default is 5 levels
xdump(fn::Function, io::IO, x, n::Int) = xdump(xdump, io, x, n, "")
xdump(fn::Function, io::IO, args...) = error("invalid arguments to xdump")
xdump(fn::Function, args...) = xdump(fn, STDOUT::IO, args...)
xdump(io::IO, args...) = xdump(xdump, io, args...)
xdump(args...) = with_output_limit(()->xdump(xdump, STDOUT::IO, args...), true)

# Here are methods specifically for dump:
dump(io::IO, x, n::Int) = dump(io, x, n, "")
dump(io::IO, x) = dump(io, x, 5, "")  # default is 5 levels
dump(io::IO, x::String, n::Int, indent) =
               (print(io, typeof(x), " ");
                show(io, x); println(io))
dump(io::IO, x, n::Int, indent) = xdump(dump, io, x, n, indent)
dump(io::IO, args...) = error("invalid arguments to dump")
dump(args...) = with_output_limit(()->dump(STDOUT::IO, args...), true)

function dump(io::IO, x::Dict, n::Int, indent)
    println(io, typeof(x), " len ", length(x))
    if n > 0
        i = 1
        for (k,v) in x
            print(io, indent, "  ", k, ": ")
            dump(io, v, n - 1, string(indent, "  "))
            if i > 10
                println(io, indent, "  ...")
                break
            end
            i += 1
        end
    end
end

# More generic representation for common types:
dump(io::IO, x::DataType, n::Int, indent) = println(io, x.name)
dump(io::IO, x::DataType, n::Int) = dump(io, x, n, "")
dump(io::IO, x::DataType) = dump(io, x, 5, "")
dump(io::IO, x::TypeVar, n::Int, indent) = println(io, x.name)

alignment(x::Any) = (0, length(sprint(showcompact_lim, x)))
alignment(x::Number) = (length(sprint(showcompact_lim, x)), 0)
alignment(x::Integer) = (length(sprint(showcompact_lim, x)), 0)
function alignment(x::Real)
    m = match(r"^(.*?)((?:[\.eE].*)?)$", sprint(showcompact_lim, x))
    m == nothing ? (length(sprint(showcompact_lim, x)), 0) :
                   (length(m.captures[1]), length(m.captures[2]))
end
function alignment(x::Complex)
    m = match(r"^(.*[\+\-])(.*)$", sprint(showcompact_lim, x))
    m == nothing ? (length(sprint(showcompact_lim, x)), 0) :
                   (length(m.captures[1]), length(m.captures[2]))
end
function alignment(x::Rational)
    m = match(r"^(.*?/)(/.*)$", sprint(showcompact_lim, x))
    m == nothing ? (length(sprint(showcompact_lim, x)), 0) :
                   (length(m.captures[1]), length(m.captures[2]))
end

const undef_ref_str = "#undef"
const undef_ref_alignment = (3,3)

function alignment(
    X::AbstractVecOrMat,
    rows::AbstractVector, cols::AbstractVector,
    cols_if_complete::Integer, cols_otherwise::Integer, sep::Integer
)
    a = {}
    for j in cols
        l = r = 0
        for i in rows
            if isassigned(X,i,j)
                aij = alignment(X[i,j])
            else
                aij = undef_ref_alignment
            end
            l = max(l, aij[1])
            r = max(r, aij[2])
        end
        push!(a, (l, r))
        if length(a) > 1 && sum(map(sum,a)) + sep*length(a) >= cols_if_complete
            pop!(a)
            break
        end
    end
    if 1 < length(a) < size(X,2)
        while sum(map(sum,a)) + sep*length(a) >= cols_otherwise
            pop!(a)
        end
    end
    return a
end

function print_matrix_row(io::IO,
    X::AbstractVecOrMat, A::Vector,
    i::Integer, cols::AbstractVector, sep::String
)
    for k = 1:length(A)
        j = cols[k]
        if isassigned(X,i,j)
            x = X[i,j]
            a = alignment(x)
            sx = sprint(showcompact_lim, x)
        else
            a = undef_ref_alignment
            sx = undef_ref_str
        end
        l = repeat(" ", A[k][1]-a[1])
        r = repeat(" ", A[k][2]-a[2])
        print(io, l, sx, r)
        if k < length(A); print(io, sep); end
    end
end

function print_matrix_vdots(io::IO,
    vdots::String, A::Vector, sep::String, M::Integer, m::Integer
)
    for k = 1:length(A)
        w = A[k][1] + A[k][2]
        if k % M == m
            l = repeat(" ", max(0, A[k][1]-length(vdots)))
            r = repeat(" ", max(0, w-length(vdots)-length(l)))
            print(io, l, vdots, r)
        else
            print(io, repeat(" ", w))
        end
        if k < length(A); print(io, sep); end
    end
end

function print_matrix(io::IO, X::AbstractVecOrMat,
                      sz::(Integer, Integer) = (s = tty_size(); (s[1]-4, s[2])),
                      pre::String = " ",
                      sep::String = "  ",
                      post::String = "",
                      hdots::String = "  \u2026  ",
                      vdots::String = "\u22ee",
                      ddots::String = "  \u22f1  ",
                      hmod::Integer = 5, vmod::Integer = 5)
    rows, cols = sz
    cols -= length(pre) + length(post)
    presp = repeat(" ", length(pre))
    postsp = ""
    @assert strwidth(hdots) == strwidth(ddots)
    ss = length(sep)
    m, n = size(X,1), size(X,2)
    if m <= rows # rows fit
        A = alignment(X,1:m,1:n,cols,cols,ss)
        if n <= length(A) # rows and cols fit
            for i = 1:m
                print(io, i == 1 ? pre : presp)
                print_matrix_row(io, X,A,i,1:n,sep)
                print(io, i == m ? post : postsp)
                if i != m; println(io, ); end
            end
        else # rows fit, cols don't
            c = div(cols-length(hdots)+1,2)+1
            R = reverse(alignment(X,1:m,n:-1:1,c,c,ss))
            c = cols - sum(map(sum,R)) - (length(R)-1)*ss - length(hdots)
            L = alignment(X,1:m,1:n,c,c,ss)
            for i = 1:m
                print(io, i == 1 ? pre : presp)
                print_matrix_row(io, X,L,i,1:length(L),sep)
                print(io, i % hmod == 1 ? hdots : repeat(" ", length(hdots)))
                print_matrix_row(io, X,R,i,n-length(R)+1:n,sep)
                print(io, i == m ? post : postsp)
                if i != m; println(io, ); end
            end
        end
    else # rows don't fit
        t = div(rows,2)
        I = [1:t; m-div(rows-1,2)+1:m]
        A = alignment(X,I,1:n,cols,cols,ss)
        if n <= length(A) # rows don't fit, cols do
            for i in I
                print(io, i == 1 ? pre : presp)
                print_matrix_row(io, X,A,i,1:n,sep)
                print(io, i == m ? post : postsp)
                if i != I[end]; println(io, ); end
                if i == t
                    print(io, i == 1 ? pre : presp)
                    print_matrix_vdots(io, vdots,A,sep,vmod,1)
                    println(io, i == m ? post : postsp)
                end
            end
        else # neither rows nor cols fit
            c = div(cols-length(hdots)+1,2)+1
            R = reverse(alignment(X,I,n:-1:1,c,c,ss))
            c = cols - sum(map(sum,R)) - (length(R)-1)*ss - length(hdots)
            L = alignment(X,I,1:n,c,c,ss)
            r = mod((length(R)-n+1),vmod)
            for i in I
                print(io, i == 1 ? pre : presp)
                print_matrix_row(io, X,L,i,1:length(L),sep)
                print(io, i % hmod == 1 ? hdots : repeat(" ", length(hdots)))
                print_matrix_row(io, X,R,i,n-length(R)+1:n,sep)
                print(io, i == m ? post : postsp)
                if i != I[end]; println(io, ); end
                if i == t
                    print(io, i == 1 ? pre : presp)
                    print_matrix_vdots(io, vdots,L,sep,vmod,1)
                    print(io, ddots)
                    print_matrix_vdots(io, vdots,R,sep,vmod,r)
                    println(io, i == m ? post : postsp)
                end
            end
        end
    end
end

summary(x) = string(typeof(x))

dims2string(d) = length(d) == 0 ? "0-dimensional" :
                 length(d) == 1 ? "$(d[1])-element" :
                 join(map(string,d), 'x')

summary(a::AbstractArray) =
    string(dims2string(size(a)), " ", typeof(a))

function show_nd(io::IO, a::AbstractArray, limit, print_matrix, label_slices)
    if isempty(a)
        return
    end
    tail = size(a)[3:end]
    nd = ndims(a)-2
    function print_slice(idxs...)
        if limit
            for i = 1:nd
                ii = idxs[i]
                if size(a,i+2) > 10
                    if ii == 4 && all(x->x==1,idxs[1:i-1])
                        for j=i+1:nd
                            szj = size(a,j+2)
                            if szj>10 && 3 < idxs[j] <= szj-3
                                return
                            end
                        end
                        #println(io, idxs)
                        print(io, "...\n\n")
                        return
                    end
                    if 3 < ii <= size(a,i+2)-3
                        return
                    end
                end
            end
        end
        if label_slices
            print(io, "[:, :, ")
            for i = 1:(nd-1); print(io, "$(idxs[i]), "); end
            println(io, idxs[end], "] =")
        end
        slice = sub(a, 1:size(a,1), 1:size(a,2), idxs...)
        print_matrix(io, slice)
        print(io, idxs == tail ? "" : "\n\n")
    end
    cartesianmap(print_slice, tail)
end

function whos(m::Module, pattern::Regex)
    for v in sort(names(m))
        s = string(v)
        if isdefined(m,v) && ismatch(pattern, s)
            println(rpad(s, 30), summary(eval(m,v)))
        end
    end
end
whos() = whos(r"")
whos(m::Module) = whos(m, r"")
whos(pat::Regex) = whos(current_module(), pat)

# global flag for limiting output
# TODO: this should be replaced with a better mechanism. currently it is only
# for internal use in showing arrays.
_limit_output = false

function print_matrix_repr(io, X::AbstractArray)
    compact, prefix = array_eltype_show_how(eltype(X))
    prefix *= "["
    ind = " "^length(prefix)
    print(io, prefix)
    for i=1:size(X,1)
        i > 1 && print(io, ind)
        for j=1:size(X,2)
            j > 1 && print(io, " ")
            if !isassigned(X,i,j)
                print(io, undef_ref_str)
            else
                el = X[i,j]
                compact ? showcompact_lim(io, el) : show(io, el)
            end
        end
        if i < size(X,1)
            println(io)
        else
            print(io, "]")
        end
    end
end

# NOTE: this is a possible, so-far-unexported function, providing control of
# array output. Not sure I want to do it this way.
showarray(X::AbstractArray; kw...) = showarray(STDOUT, X; kw...)
function showarray(io::IO, X::AbstractArray;
                   header::Bool=true, limit::Bool=_limit_output,
                   sz = (s = tty_size(); (s[1]-4, s[2])), repr=false)
    rows, cols = sz
    header && print(io, summary(X))
    if !isempty(X)
        header && println(io, ":")
        if ndims(X) == 0
            if isassigned(X)
                return showcompact_lim(io, X[])
            else
                return print(io, undef_ref_str)
            end
        end
        if !limit
            rows = cols = typemax(Int)
        end
        if repr
            if ndims(X)<=2
                print_matrix_repr(io, X)
            else
                show_nd(io, X, limit, print_matrix_repr, false)
            end
        else
            punct = (" ", "  ", "")
            if ndims(X)<=2
                print_matrix(io, X, sz, punct...)
            else
                show_nd(io, X, limit,
                        (io,slice)->print_matrix(io,slice,sz,punct...),
                        !repr)
            end
        end
    end
end

show(io::IO, X::AbstractArray) = showarray(io, X, header=_limit_output, repr=!_limit_output)

function with_output_limit(thk, lim=true)
    global _limit_output
    last = _limit_output
    _limit_output = lim
    try
        thk()
    finally
        _limit_output = last
    end
end

showall(x) = showall(STDOUT, x)
function showall(io::IO, x)
    if _limit_output==false
        show(io, x)
    else
        with_output_limit(false) do
            show(io, x)
        end
    end
end

showlimited(x) = showlimited(STDOUT, x)
function showlimited(io::IO, x)
    if _limit_output==true
        show(io, x)
    else
        with_output_limit(true) do
            show(io, x)
        end
    end
end

# returns compact, prefix
function array_eltype_show_how(e)
    leaf = isleaftype(e)
    plain = e<:Number || e<:String
    if isa(e,DataType) && e === e.name.primary
        str = string(e.name)
    else
        str = string(e)
    end
    leaf&&plain, (e===Float64 || e===Int || (leaf && !plain) ? "" : str)
end

function show_vector(io::IO, v, opn, cls)
    e = eltype(v)
    compact = false
    if e !== Any
        compact, prefix = array_eltype_show_how(e)
        print(io, prefix)
    end
    if _limit_output && length(v) > 20
        show_delim_array(io, sub(v,1:10), opn, ",", "", false, compact)
        print(io, "  \u2026  ")
        n = length(v)
        show_delim_array(io, sub(v,(n-9):n), "", ",", cls, false, compact)
    else
        show_delim_array(io, v, opn, ",", cls, false, compact)
    end
end

show(io::IO, v::AbstractVector{Any}) = show_vector(io, v, "{", "}")
show(io::IO, v::AbstractVector)      = show_vector(io, v, "[", "]")

# printing BitArrays

# (following functions not exported - mainly intended for debug)

function print_bit_chunk(io::IO, c::Uint64, l::Integer)
    for s = 0 : l - 1
        d = (c >>> s) & 1
        print(io, "01"[d + 1])
        if (s + 1) & 7 == 0
            print(io, " ")
        end
    end
end

print_bit_chunk(io::IO, c::Uint64) = print_bit_chunk(io, c, 64)

print_bit_chunk(c::Uint64, l::Integer) = print_bit_chunk(STDOUT, c, l)
print_bit_chunk(c::Uint64) = print_bit_chunk(STDOUT, c)

function bitshow(io::IO, B::BitArray)
    if length(B) == 0
        return
    end
    for i = 1 : length(B.chunks) - 1
        print_bit_chunk(io, B.chunks[i])
        print(io, ": ")
    end
    l = (@_mod64 (length(B)-1)) + 1
    print_bit_chunk(io, B.chunks[end], l)
end
bitshow(B::BitArray) = bitshow(STDOUT, B)

bitstring(B::BitArray) = sprint(bitshow, B)

max_stars_repo_name: jakebolewski/julia

max_issues_repo_name: jakebolewski/julia

max_forks_repo_name: jakebolewski/julia


text:
struct DfPair
    df1::DataFrame
    df2::DataFrame
end

abstract type AbstractMatch end

struct Match <: AbstractMatch
    i::Int
    j::Int
    dist::Real
end

struct HalfMatch <: AbstractMatch
    ind::Int
    dist::Real
end

isless(x::AbstractMatch, y::AbstractMatch) = x.dist < y.dist;

abstract type StepParameters end

struct InitParams <: StepParameters
    entity_type::String
    left_sample::Int
    right_sample::Int
end

struct ExtendParams <: StepParameters
    rel_pair_ind::Int
    root_side::Int
end

struct MatchParams <: StepParameters
    needed_matches::Int
end

struct IntegrateParams <: StepParameters end

struct Normalizer
    means::Array{Real, 1}
    stds::Array{Real, 1}
end

struct SortedJoiner
    arr::Array{Int, 2}
    sorted_inds::Array{Array{Int, 1}, 1}
    sorted_views::Array{
        SubArray{Int, 1, Array{Int, 2}, Tuple{Array{Int, 1}, Int}, false},
        1,
    }
end

struct RelPair
    sj1::SortedJoiner
    sj2::SortedJoiner
    colmap::Tuple{String, String}
end

struct CorefSystem
    esp_dict::Dict{String, DfPair}
    normalizers::Dict{String, Normalizer}
    relps::Array{RelPair, 1}
end

mutable struct EntityRefSpace
    space::Array{Int, 2}
    height::Int
    width::Int
    entity_types::Array{String, 1}
end

struct SpacePair
    erspace1::EntityRefSpace
    erspace2::EntityRefSpace
end

heights(space_pair::SpacePair) = (space_pair.erspace1.height, space_pair.erspace2.height)
spaces(space_pair::SpacePair) = (space_pair.erspace1, space_pair.erspace2)
innerspaces(space_pair::SpacePair) = (ers.space for ers in spaces(space_pair))

mutable struct CorefProposal
    ind::Int
    latent_vars::Array{Real,1}
end

struct CorefSpace
    latent_match_vars::Dict{String, Array{Array{CorefProposal,1},1}}
    prepspace::Array{HalfMatch, 2}
    free_i::Array{Bool, 1}
    free_j::Array{Bool, 1}
    param_log::Array{StepParameters, 1}
end

mutable struct CorefResolver
    crs::CorefSystem
    crspace::CorefSpace
    space_pairs::Tuple{SpacePair, SpacePair}
    leftspace_main::Bool
end

function valid(proposal::CorefProposal)
    proposal.ind > 0
end

function parse(lvars::Array{Array{CorefProposal,1},1})
    out = Array{Array{Int, 2}, 1}()
    for (fun, lvar_arr) in zip([x -> x, reverse], lvars)
        for (i, propsal) in enumerate(lvar_arr)
            if valid(propsal)
                r = reshape(fun([i, propsal.ind]), (1, 2))
                (r in out) && continue
                push!(out, r)
            end
        end
    end
    vcat(out...)
end

result(crr::CorefResolver) = Dict(name=> parse(lvars) for (name, lvars) in crr.crspace.latent_match_vars)
main(crr::CorefResolver) = crr.space_pairs[crr.leftspace_main ? 1 : 2]
innerspaces(crr::CorefResolver) = (crr.leftspace_main ? x -> x : reverse)(crr.space_pairs)
switch!(crr::CorefResolver) = (crr.leftspace_main = !crr.leftspace_main)

ext: jl


text:
#unum-addition.jl
#Performs addition with unums.  Requires two unums to have the same
#environment signature.

doc"""
  `Unums.frac_add!(carry, ::Unum, fraction)`
  adds fraction into the the fraction value of unum.
"""
function frac_add!{ESS,FSS}(carry::UInt64, a::UnumSmall{ESS,FSS}, addin::UInt64)
  (carry, a.fraction) = i64add(carry, a.fraction, addin)
  return carry
end
function frac_add!{ESS,FSS}(carry::UInt64, a::UnumLarge{ESS,FSS}, addin::ArrayNum{FSS})
  i64add!(carry, a.fraction, addin)
end

doc"""
  `Unums.add(::Unum, ::Unum)` outputs a Unum OR Ubound corresponding to the sum
  of two unums.  This is bound to the (+) operation if options[:usegnum] is not
  set.  Note that in the case of degenerate unums, add may change the bit values
  of the individual unums, but the values will not be altered.
"""
@universal function add(a::Unum, b::Unum)
  #some basic checks out of the gate.
  (is_nan(a) || is_nan(b)) && return nan(U)
  is_zero(a) && return copy(b)
  is_zero(b) && return copy(a)

  #resolve degenerate conditions in both A and B before calculating the exponents.
  resolve_degenerates!(a)
  resolve_degenerates!(b)

  #go ahead and decode the a and b exponents, these will be used, a lot.
  _aexp = decode_exp(a)
  _bexp = decode_exp(b)

  #check to see if the signs on a and b are mismatched.
  if ((a.flags $ b.flags) & UNUM_SIGN_MASK) != z16
    #kick it to the unum_difference function which calculates numeric difference
    is_inward(b, a) ? unum_diff(a, b, _aexp, _bexp) : unum_diff(b, a, _bexp, _aexp)
  else
    #kick it to the unum_sum function which calculates numeric sum.
    (_aexp > _bexp) ? unum_sum(a, b, _aexp, _bexp) : unum_sum(b, a, _bexp, _aexp)
  end
end

#import the Base add operation and bind it to the add and add! functions
import Base.+
@bind_operation(+, add)

doc"""
  `Unums.unum_sum(::Unum, ::Unum, _aexp, _bexp)` outputs a Unum OR Ubound
  corresponding to the sum of two unums.  This function as prerequisites
  must have the exponent on a exceed the exponent on b.
"""
@universal function unum_sum(a::Unum, b::Unum, _aexp::Int64, _bexp::Int64)
  #basic secondary checks which eject early results.
  is_inf(a) && return inf(U, @signof a)
  is_inf(b) && return inf(U, @signof a)

  (_aexp + _bexp > max_exponent(ESS)) && return mmr(U, @signof a)

  if (is_exact(a) && is_exact(b))
    sum_exact(a, b, _aexp, _bexp)
  else
    sum_inexact(a, b, _aexp, _bexp)
  end
end

@universal function sum_exact(a::Unum, b::Unum, _aexp::Int64, _bexp::Int64)
  #first, decide if we need to deviate either a or b on account of their being
  #subnormal numbers.
  _a_subnormal = is_exp_zero(a)
  _b_subnormal = is_exp_zero(b)

  #modify the exponent values on a and b to accomodate subnormality.
  _aexp += _a_subnormal * 1
  _bexp += _b_subnormal * 1

  shift = to16(_aexp - _bexp) #this is the a exponent minus the b exponent.
  #if the two numbers are very divergent in magnitude, only need to flip the ulp.
  if (shift > (max_fsize(FSS) + o16))
    res = make_ulp!(copy(a))
    res.fsize = max_fsize(FSS)
    return res
  end

  #copy the b unum as the temporary result.
  result = copy(b)
  coerce_sign!(result, a)

  #check to see if "shift" is zero.
  if (shift == 0x0000)
    #initialize carry to be one, if we're not subnormal
    carry = (!_a_subnormal) * o64
  else
    carry = z64
    rsh_and_set_ubit!(result, shift)
    (_b_subnormal) || frac_set_bit!(result, shift)
  end

  #increment the carry if the left unum is not subnormal.
  carry += (!_a_subnormal) * o64

  #add the two fractionals parts together, and set the carry.
  carry = frac_add!(carry, result, a.fraction)

  #resolve the possibility that the carry contains more than one bit.
  resolve_carry!(carry, result, _aexp)

  #if we wound up still subnormal, then re-subnormalize the exponent. (exp - 1)
  result.exponent &= f64 * (carry != 0)

  #check to see if we're getting too big.
  (result.exponent > max_biased_exponent(ESS)) && return mmr(U, @signof a)
  #check to make sure we haven't done the inf hack, where the result exactly
  #equals inf.
  is_inf(result) && return mmr(U, @signof a)

  is_exact(result) && exact_trim!(result)
  trim_and_set_ubit!(result)

  return result
end

@universal function sum_inexact(a::Unum, b::Unum, _aexp::Int64, _bexp::Int64)
  #first, do the exact sum, to calculate the "base value" of the resulting sum.

  b = copy(b)
  coerce_sign!(b, a)

  if is_inf_ulp(a)
    base_value = sum_exact(a, b, _aexp, _bexp)

    make_ulp!(base_value)
    is_inf_ulp(base_value) && return base_value
    return is_positive(a) ? resolve_as_utype!(base_value, mmr(U, @signof(a))) : resolve_as_utype!(mmr(U, @signof(a)), base_value)
  end

  glba = glb(a)
  glbb = glb(b)
  luba = lub(a)
  lubb = lub(b)

  if is_zero(glbb)
    glbs = glba
  elseif is_zero(glba)
    glbs = glbb
  elseif is_inf(glba)
    glbs = glba
  elseif is_inf(glbb)
    glbs = glbb
  else
    glbs = is_inward(glbb, glba) ?
      sum_exact(glba, glbb, decode_exp(glba), decode_exp(glbb)) :
      sum_exact(glbb, glba, decode_exp(glbb), decode_exp(glba))
  end

  if is_zero(lubb)
    lubs = luba
  elseif is_zero(luba)
    lubs = lubb
  elseif is_inf(luba)
    lubs = luba
  elseif is_inf(lubb)
    lubs = lubb
  else
    lubs = is_inward(lubb, luba) ?
      sum_exact(luba, lubb, decode_exp(luba), decode_exp(lubb)) :
      sum_exact(lubb, luba, decode_exp(lubb), decode_exp(luba))
  end

  lower_sum = is_exact(glbs) ? (is_zero(glbs) ? pos_sss(U) : upper_ulp(glbs)) : glbs
  upper_sum = is_exact(lubs) ? (is_zero(lubs) ? neg_sss(U) : lower_ulp(lubs)) : lubs

  #describe(lower_sum)
  #describe(upper_sum)

  return resolve_as_utype!(lower_sum, upper_sum)
end

max_stars_repo_name: REX-Computing/unumjl

max_issues_repo_name: REX-Computing/unumjl

max_forks_repo_name: REX-Computing/unumjl


text:
# ---
# title: 589. N-ary Tree Preorder Traversal
# id: problem589
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Tree
# link: <https://leetcode.com/problems/n-ary-tree-preorder-traversal/description/>
# hidden: true
# ---
#
# Given an n-ary tree, return the _preorder_ traversal of its nodes' values.
#
# _Nary-Tree input serialization  is represented in their level order traversal,
# each group of children is separated by the null value (See examples)._
#
#
#
# **Follow up:**
#
# Recursive solution is trivial, could you do it iteratively?
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2018/10/12/narytreeexample.png)
#
#
#
#     Input: root = [1,null,3,2,4,null,5,6]
#     Output: [1,3,5,6,2,4]
#
#
# **Example 2:**
#
# ![](https://assets.leetcode.com/uploads/2019/11/08/sample_4_964.png)
#
#
#
#     Input: root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
#     Output: [1,2,3,6,7,11,14,4,8,12,5,9,13,10]
#
#
#
#
# **Constraints:**
#
#   * The height of the n-ary tree is less than or equal to `1000`
#   * The total number of nodes is between `[0, 10^4]`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


text:
using MeanFilters
using Test
using MeanFilters: meanfilter!
using ImageFiltering: imfilter, NA
using ImageFiltering.OffsetArrays: OffsetArray

@testset "1d" begin
    @test meanfilter([1,2,3,4], -1:1) ≈ [1.5, 2, 3, 3.5]
    @test meanfilter([1,2,3], (0:1,)) == [1.5, 2.5, 3.0]
    @test meanfilter([1,4], (0:1,)) == [2.5, 4]
    @test meanfilter([3,6], (0:1,)) == [4.5, 6]
    @test meanfilter([0,0,1,0,0], (-1:1,)) ≈ [0, 1/3, 1/3, 1/3, 0]
    @test meanfilter([0,0,1,0,0], (-1:0,)) ≈ [0,   0, 1/2, 1/2, 0]
    @test meanfilter([0,0,1,0,0], (-1:2,)) ≈ [1/3, 1/4, 1/4, 1/3, 0]
    @test meanfilter([1,0,0,0,0], (-1:2,)) ≈ [1/3, 1/4, 0, 0, 0]
    @test meanfilter([1,2,], (-0:0,)) ≈ [1,2]

    @test_throws ArgumentError meanfilter([1,2,3], (1:2, 2:3))
end

@testset "2d" begin
    arr = [1 2 3; 4 5 6]
    arr = randn(2,3)
    expected = copy(arr)

    @test meanfilter(arr, (1,1)) ≈ expected
    out = similar(arr)
    @test meanfilter!(out, arr, (1,1)) ≈ expected
    @test out ≈ expected

    arr = [1 2 3; 4 5 6]
    @test meanfilter([1 2 3; 4 5 6], (0:1, 0:0)) == [2.5 3.5 4.5; 4 5 6]
end

@testset "against ImageFiltering.imfilter" begin

    function meanfilter_reference(arr, window)
        ker = fill(1/prod(map(length,window)), window...)
        imfilter(arr, ker, NA())
    end

    for arr in [randn(1), randn(1,2), randn(1,2,3), randn(1,2,3,4), randn(1,2,3,4,5)]
        window = Tuple(0:0 for _ in 1:ndims(arr))
        @test meanfilter(arr, window) ≈ arr
        @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
    end

    for ndims in 1:2
        for _ in 1:100
            #ndims = rand(1:2)
            arr = randn(rand(1:10, ndims)...)
            window = Tuple(-rand(0:10):rand(0:10) for _ in 1:ndims)
            @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
        end
    end
    for ndims in 3:5
        #ndims = rand(1:2)
        arr = randn(rand(1:10, ndims)...)
        window = Tuple(-rand(0:2):rand(0:2) for _ in 1:ndims)
        @test meanfilter(arr, window) ≈ meanfilter_reference(arr, window)
    end
end


text:
function test_standardize()
    mat1 = [0. 0.; 0. 0.; 1. 1.; 1. 1.]
    mat2 = [-1. -1.; -1. -1.; 1. 1.; 1. 1.]
    @test isapprox(MVApp.standardize(mat1), mat2)
    @test isapprox(MVApp.reverse_standardization(mat2,mat1),mat1)
    mat3 = ones(3,2)
    mat4 = zeros(3,2)
    @test isapprox(MVApp.standardize(mat3), mat4)
    @test isapprox(MVApp.reverse_standardization(mat4,mat3),mat3)
end

function test_gradient()
    vec = [1., 2., 4., 7., 11., 16.]
    res = [1. , 1.5, 2.5, 3.5, 4.5, 5. ]
    @test isapprox(MVApp.gradient(vec), res)
    mat = [1. 2. 6.; 3. 4. 5.]
    res2 = [2.0  2.0  -1.0;2.0  2.0  -1.0]
    @test isapprox(MVApp.gradient(mat), res2)
end


text:
function asinh(x::DoubleFloat{T}) where {T<:AbstractFloat}
    result = abs(x)
    result = result + sqrt(square(result) + 1.0)
    result = log(result)
    return copysign(result, x)
end

function acosh(x::DoubleFloat{T}) where {T<:AbstractFloat}
    x < 1.0 && throw(DomainError("$x"))
    !isfinite(x) && return nan(typeof(x))
    result = x + sqrt(square(x) - 1.0)
    result = log(result)
    return result
end

function atanh(x::DoubleFloat{T}) where {T<:AbstractFloat}
    abs(x) > 1.0 && throw(DomainError("$x"))
    !isfinite(x) && return nan(typeof(x))
    twox = DoubleFloat{T}(x.hi+x.hi, x.lo+x.lo)
    result = 1.0 + twox / (1.0 - x)
    result = log(result)
    result = DoubleFloat{T}(result.hi*0.5, result.lo*0.5)
    return result
end

function acsch(x::DoubleFloat{T}) where {T<:AbstractFloat}
    iszero(x) && throw(DomainError("$x"))
    !isfinite(x) && return nan(typeof(x))
    result = inv(x)
    result = sinh(result)
    return result
end

function asech(x::DoubleFloat{T}) where {T<:AbstractFloat}
    iszero(x) && return inf(DoubleFloat{T})
    isone(x) && return zero(DoubleFloat{T})
    !isfinite(x) && return nan(typeof(x))
    (x < 0.0 || x > 1.0) && throw(DomainError("$x"))
    result = inv(x)
    result = cosh(result)
    return result
end

function acoth(x::DoubleFloat{T}) where {T<:AbstractFloat}
    isone(x) && return inf(DoubleFloat{T})
    isone(-x) && return -inf(DoubleFloat{T})
    (-1.0 < x < 1.0) && throw(DomainError("$x"))
    !isfinite(x) && return nan(typeof(x))
    result = inv(x)
    result = tanh(result)
    return result
end


text:
# Stats genrated by GenCard(): Health, Speed, Defence, Crit, Attack

function GenCard(_amntCards , _Cards)
  for i in 1:_amntCards
    # Generate tempStats
    tempHealth ::Int64 = rand((35:100))
    tempDefence ::Int64 = tempHealth < 50 ? rand((50:70)) : rand((10:30))
    tempAttack ::Int64 = tempDefence < 50 ? rand((14:18)) : rand((8:13))
    tempSpeed ::Int64 = tempAttack < 14 ? rand((10:14)) : rand((5:9))
    tempCrit ::Int64 = tempAttack < 14 ? rand((15:19)) : rand((11:14))
    tempCard ::Dict = Dict("Health"=>tempHealth, "Speed"=>tempSpeed, "Defence"=>tempDefence, "Attack" =>tempAttack, "Critical"=>tempCrit)

    push!(_Cards,tempCard)
    return _Cards
  end
end

max_stars_repo_name: NarutoXY/austroporean-cards

max_issues_repo_name: NarutoXY/austroporean-cards

max_forks_repo_name: NarutoXY/austroporean-cards


text:
"""
    TypeR()

Lorem ipsum dolor sit amet.
"""
struct TypeR

end


text:
"""
An ITensor is a tensor whose interface is
independent of its memory layout. Therefore
it is not necessary to know the ordering
of an ITensor's indices, only which indices
an ITensor has. Operations like contraction
and addition of ITensors automatically
handle any memory permutations.
"""
mutable struct ITensor{N}
  store::TensorStorage
  inds::IndexSet{N}

  # TODO: check that the storage is consistent with the
  # indices (possibly only in debug mode);
  """
      ITensor{N}(is::IndexSet{N}, st::TensorStorage)

  This is an internal constructor for an ITensor where the ITensor stores a view of the `NDTensors.TensorStorage`.
  """
  ITensor{N}(is,
             st::TensorStorage) where {N} = new{N}(st, is)

  ITensor{Any}(is,
               st::Empty) = new{Any}(st, is)
end

function ITensor{Any}(is,
                      st::TensorStorage)
  error("Can only make an ITensor with Any number of indices with NDTensors.Empty storage")
end

"""
    itensor(st::TensorStorage, is)

Constructor for an ITensor from a TensorStorage
and a set of indices.
The ITensor stores a view of the TensorStorage.
"""
itensor(st::TensorStorage, is) = ITensor{length(is)}(is, st)

"""
    ITensor(st::TensorStorage, is)

Constructor for an ITensor from a TensorStorage
and a set of indices.
The TensorStorage is copied (the ITensor
owns the storage data).
"""
ITensor(st::TensorStorage, is) = itensor(copy(st), is)

"""
    inds(T::ITensor)

Return the indices of the ITensor as an IndexSet.
"""
NDTensors.inds(T::ITensor) = T.inds

"""
    ind(T::ITensor, i::Int)

Get the Index of the ITensor along dimension i.
"""
NDTensors.ind(T::ITensor, i::Int) = inds(T)[i]

# Explicit import since there are some deprecations
# involving store for other types.
import .NDTensors: store

"""
    store(T::ITensor)

Return a view of the TensorStorage of the ITensor.
"""
store(T::ITensor) = T.store

"""
    data(T::ITensor)

Return a view of the raw data of the ITensor.

This is mostly an internal ITensor function, please
let the developers of ITensors.jl know if there is
functionality for ITensors that you would like
that is not currently available.
"""
data(T::ITensor) = NDTensors.data(store(T))

Base.similar(T::ITensor) = itensor(similar(tensor(T)))

Base.similar(T::ITensor,
             ::Type{ElT}) where {ElT<:Number} = itensor(similar(tensor(T),ElT))

setinds!(T::ITensor,is) = (T.inds = is; return T)

setstore!(T::ITensor,st::TensorStorage) = (T.store = st; return T)

setinds(T::ITensor, is) = itensor(store(T),is)

setstore(T::ITensor, st) = itensor(st,inds(T))

#
# Iteration over ITensors
#

"""
    CartesianIndices(A::ITensor)

Iterate over the CartesianIndices of an ITensor.
"""
Base.CartesianIndices(A::ITensor) = CartesianIndices(dims(A))

#
# ITensor constructors
#

"""
    ITensor(::Tensor)

Convert a Tensor to an ITensor with a copy of the storage and
indices.

To make an ITensor that shares the same storage as the Tensor,
is the function `itensor(::Tensor)`.
"""
ITensor(T::Tensor{<:Number,N}) where {N} = ITensor{N}(store(T),
                                                      inds(T))

ITensor{N}(T::Tensor{<:Number,N}) where {N} = ITensor{N}(store(T),
                                                         inds(T))

"""
    itensor(::Tensor)

Make an ITensor that shares the same storage as the Tensor and
has the same indices.
"""
itensor(T::Tensor) = itensor(store(T),
                             inds(T))

"""
    tensor(::ITensor)

Convert the ITensor to a Tensor that shares the same
storage and indices as the ITensor.
"""
NDTensors.tensor(A::ITensor) = tensor(store(A),inds(A))

"""
    ITensor([::Type{ElT} = Float64, ]inds)
    ITensor([::Type{ElT} = Float64, ]inds::Index...)

Construct an ITensor filled with zeros having indices `inds` and element type `ElT`. If the element type is not specified, it defaults to `Float64`.

The storage will have `NDTensors.Dense` type.
"""
function ITensor(::Type{ElT},
                 inds::Indices) where {ElT <: Number}
  return itensor(Dense(ElT, dim(inds)), inds)
end

ITensor(::Type{ElT},
        inds::Index...) where {ElT <: Number} = ITensor(ElT,
                                                        IndexSet(inds...))

# To fix ambiguity with QN Index version
ITensor(::Type{ElT}) where {ElT <: Number} = ITensor(ElT, IndexSet())

ITensor(is::Indices) = ITensor(Float64, is)

ITensor(inds::Index...) = ITensor(Float64, IndexSet(inds...))

# To fix ambiguity with QN Index version
ITensor() = ITensor(Float64, IndexSet())

"""
    ITensor([::Type{ElT} = Float64, ]::UndefInitializer, inds)
    ITensor([::Type{ElT} = Float64, ]::UndefInitializer, inds::Index...)

Construct an ITensor filled with undefined elements having indices `inds` and element type `ElT`. If the element type is not specified, it defaults to `Float64`.

The storage will have `NDTensors.Dense` type.
"""
function ITensor(::Type{ElT},
                 ::UndefInitializer,
                 inds::Indices) where {ElT <: Number}
  return itensor(Dense(ElT, undef, dim(inds)), inds)
end

ITensor(::Type{ElT},
        ::UndefInitializer,
        inds::Index...) where {ElT} = ITensor(ElT,
                                              undef,
                                              IndexSet(inds...))

ITensor(::UndefInitializer,
        inds::Indices) = ITensor(Float64, undef, inds)

ITensor(::UndefInitializer,
        inds::Index...) = ITensor(Float64, undef, IndexSet(inds...))

"""
    ITensor(x::Number, inds)
    ITensor(x::Number, inds::Index...)

Construct an ITensor with all elements set to `float(x)` and indices `inds`.

The storage will have `NDTensors.Dense` type.
"""
function ITensor(x::Number,
                 inds::Indices)
  return itensor(Dense(float(x),dim(inds)),inds)
end

ITensor(x::Number,
        inds::Index...) = ITensor(x, IndexSet(inds...))

#
# Empty ITensor constructors
#

"""
    emptyITensor([::Type{ElT} = Float64, ]inds)
    emptyITensor([::Type{ElT} = Float64, ]inds::Index...)

Construct an ITensor with storage type `NDTensors.Empty`, indices `inds`, and element type `ElT`. If the element type is not specified, it defaults to `Float64`.
"""
function emptyITensor(::Type{ElT},
                      inds::Indices) where {ElT <: Number}
  return itensor(EmptyTensor(ElT, inds))
end

function emptyITensor(::Type{ElT},
                     inds::Index...) where {ElT <: Number}
  return emptyITensor(ElT, IndexSet(inds...))
end

emptyITensor(is::Indices) = emptyITensor(Float64, is)

emptyITensor(inds::Index...) = emptyITensor(Float64,
                                            IndexSet(inds...))

function emptyITensor(::Type{ElT}) where {ElT <: Number}
  return itensor(EmptyTensor(ElT, IndexSet()))
end

emptyITensor() = emptyITensor(Float64)

"""
    emptyITensor([::Type{ElT} = Float64, ]::Type{Any})

Construct an ITensor with empty storage and `Any` number of indices.
"""
function emptyITensor(::Type{ElT}, ::Type{Any}) where {ElT <: Number}
  return itensor(EmptyTensor(ElT, IndexSet{Any}()))
end

emptyITensor(::Type{Any}) = emptyITensor(Float64, Any)

#
# Construct from Array
#

"""
    itensor(A::Array, inds)
    itensor(A::Array, inds::Index...)

Construct an ITensor from an Array `A` and indices `inds`.
The ITensor will be the closest floating point storage to the
Array (`float(A)`), and the storage will be a view of the Array
data if possible (if the Array already has floating point elements).
"""
function itensor(A::Array{<:Number},
                 inds::Indices)
  length(A) ≠ dim(inds) && throw(DimensionMismatch("In ITensor(::Array, ::IndexSet), length of Array ($(length(A))) must match total dimension of IndexSet ($(dim(inds)))"))
  return itensor(Dense(float(vec(A))), inds)
end

itensor(A::Array{<:Number},
        inds::Index...) = itensor(A, IndexSet(inds...))

"""
    ITensor(A::Array, inds)
    ITensor(A::Array, inds::Index...)

Construct an ITensor from an Array `A` and indices `inds`.
The ITensor will be the closest floating point storage to the
Array (`float(A)`), and the storage will store a copy of the Array
data.
"""
ITensor(A::Array{<:AbstractFloat},
        inds::Indices) = itensor(copy(A), inds)

ITensor(A::Array,
        inds::Indices) = itensor(A, inds)

ITensor(A::Array, inds::Index...) = ITensor(A, IndexSet(inds...))

#
# Diag ITensor constructors
#

"""
    diagITensor([::Type{ElT} = Float64, ]inds)
    diagITensor([::Type{ElT} = Float64, ]inds::Index...)

Make a sparse ITensor of element type `ElT` with only elements
along the diagonal stored. Defaults to having `zero(T)` along
the diagonal.

The storage will have `NDTensors.Diag` type.
"""
function diagITensor(::Type{ElT},
                     is::Indices) where {ElT}
  return itensor(Diag(ElT, mindim(is)), is)
end

diagITensor(::Type{ElT},
            inds::Index...) where {ElT} = diagITensor(ElT,
                                                      IndexSet(inds...))

diagITensor(is::Indices) = diagITensor(Float64, is)

diagITensor(inds::Index...) = diagITensor(Float64, IndexSet(inds...))

"""
    diagITensor(v::Vector{T}, inds)
    diagITensor(v::Vector{T}, inds::Index...)

Make a sparse ITensor with non-zero elements only along the diagonal.
The diagonal elements will be set to the values stored in `v` and
the ITensor will have element type `float(T)`.
The storage will have type `NDTensors.Diag`.
"""
function diagITensor(v::Vector{<:Number},
                     is::Indices)
  length(v) ≠ mindim(is) && error("Length of vector for diagonal must equal minimum of the dimension of the input indices")
  return itensor(Diag(float(v)), is)
end

function diagITensor(v::Vector{<:Number},
                     is::Index...)
  return diagITensor(v, IndexSet(is...))
end

"""
    diagITensor(x::Number, inds)
    diagITensor(x::Number, inds::Index...)

Make a sparse ITensor with non-zero elements only along the diagonal.
The diagonal elements will be set to the value `float(x)` and
the ITensor will have element type `float(eltype(x))`.
The storage will have `NDTensors.Diag` type.
"""
function diagITensor(x::Number,
                     is::Indices)
  return itensor(Diag(float(x), mindim(is)), is)
end

function diagITensor(x::Number,
                     is::Index...)
  return diagITensor(x, IndexSet(is...))
end

"""
    delta([::Type{ElT} = Float64, ]inds)
    delta([::Type{ElT} = Float64, ]inds::Index...)

Make a uniform diagonal ITensor with all diagonal elements
`one(ElT)`. Only a single diagonal element is stored.

This function has an alias `δ`.
"""
function delta(::Type{T},
               is::Indices) where {T<:Number}
  return itensor(Diag(one(T)), is)
end

function delta(::Type{T},
               is::Index...) where {T<:Number}
  return delta(T, IndexSet(is...))
end

delta(is::Indices) = delta(Float64, is)

delta(is::Index...) = delta(Float64, IndexSet(is...))

const δ = delta

"""
    setelt(ivs...)

Create an ITensor with all zeros except the specified value,
which is set to 1.

# Examples
```julia
i = Index(2,"i")
A = setelt(i=>2)
# A[i=>2] == 1, all other elements zero

j = Index(3,"j")
B = setelt(i=>1,j=>3)
# B[i=>1,j=>3] == 1, all other element zero
```
"""
function setelt(ivs::IndexValOrPairIndexInt...)
  A = emptyITensor(ind.(ivs)...)
  A[val.(ivs)...] = 1.0
  return A
end

"""
    dense(T::ITensor)

Make a new ITensor where the storage is the closest Dense storage,
avoiding allocating new data if possible.
For example, an ITensor with Diag storage will become Dense storage,
filled with zeros except for the diagonal values.
"""
function NDTensors.dense(A::ITensor)
  T = dense(tensor(A))
  return itensor(store(T), removeqns(inds(A)))
end

"""
    complex(T::ITensor)

Convert to the complex version of the storage.
"""
Base.complex(T::ITensor) = itensor(complex(tensor(T)))

function complex!(T::ITensor)
  ct = complex(tensor(T))
  setstore!(T,store(ct))
  setinds!(T,inds(ct))
  return T
end

Base.eltype(T::ITensor) = eltype(tensor(T))

"""
    order(A::ITensor)
    ndims(A::ITensor)

The number of indices, `length(inds(A))`.
"""
order(T::ITensor) = ndims(T)

Base.ndims(::ITensor{N}) where {N} = N

"""
    dim(A::ITensor)

The total dimension of the space the tensor lives in, `prod(dims(A))`.
"""
NDTensors.dim(T::ITensor) = dim(inds(T))

"""
    dim(A::ITensor, n::Int)

Get the nth dimension of the ITensors.
"""
NDTensors.dim(T::ITensor, n::Int) = dims(T)[n]

"""
    dims(A::ITensor)
    size(A::ITensor)

Tuple containing `dim(inds(A)[d]) for d in 1:ndims(A)`.
"""
(NDTensors.dims(T::ITensor{N})::NTuple{N,Int}) where {N} =
  dims(inds(T))

Base.axes(T::ITensor) = map(Base.OneTo, dims(T))

Base.size(T::ITensor) = dims(T)

Base.size(A::ITensor,
          d::Int) = dim(inds(A), d)

Base.copy(T::ITensor) = itensor(copy(tensor(T)))

"""
    Array{ElT}(T::ITensor, i:Index...)
    Array(T::ITensor, i:Index...)

    Matrix{ElT}(T::ITensor, row_i:Index, col_i::Index)
    Matrix(T::ITensor, row_i:Index, col_i::Index)

    Vector{ElT}(T::ITensor)
    Vector(T::ITensor)

Given an ITensor `T` with indices `i...`, returns
an Array with a copy of the ITensor's elements. The
order in which the indices are provided indicates
the order of the data in the resulting Array.
"""
function Base.Array{ElT, N}(T::ITensor{N},
                            is::Vararg{Index, N}) where {ElT, N}
  TT = tensor(permute(T, is...; always_copy = true))
  return Array{ElT, N}(TT)::Array{ElT, N}
end

function Base.Array{ElT}(T::ITensor{N},
                         is::Vararg{Index, N}) where {ElT, N}
  return Array{ElT, N}(T, is...)
end

function Base.Array(T::ITensor{N},
                    is::Vararg{Index, N}) where {N}
  return Array{eltype(T), N}(T, is...)::Array{<:Number, N}
end

function Base.Array{<:Any, N}(T::ITensor{N},
                              is::Vararg{Index, N}) where {N}
  return Array(T, is...)
end

function Base.Vector{ElT}(T::ITensor{1}) where {ElT}
  return Array{ElT}(T,inds(T)...)
end

function Base.Vector(T::ITensor{1})
  return Array(T,inds(T)...)
end

"""
    scalar(T::ITensor)

Extract the element of an order zero ITensor.

Same as `T[]`.
"""
scalar(T::ITensor) = T[]::Number

"""
    getindex(T::ITensor, I::Int...)

Get the specified element of the ITensor, using internal
Index ordering of the ITensor.

# Example
```julia
i = Index(2; tags = "i")
A = ITensor(2.0, i, i')
A[1, 2] # 2.0, same as: A[i => 1, i' => 2]
```
"""
function Base.getindex(T::ITensor{N},
                       I::Vararg{Int,N}) where {N}
  return tensor(T)[I...]::Number
end

# Version accepting CartesianIndex, useful when iterating over
# CartesianIndices
Base.getindex(T::ITensor{N},
              I::CartesianIndex{N}) where {N} = tensor(T)[I]::Number

"""
    getindex(T::ITensor, ivs...)

Get the specified element of the ITensor, using a list
of `IndexVal`s or `Pair{<:Index, Int}`.

# Example
```julia
i = Index(2; tags = "i")
A = ITensor(2.0, i, i')
A[i => 1, i' => 2] # 2.0, same as: A[i' => 2, i => 1]
```
"""
function Base.getindex(T::ITensor, ivs...)
  p = NDTensors.getperm(inds(T), ind.(ivs))
  vals = NDTensors.permute(val.(ivs), p)
  return T[vals...]::Number
end

function Base.getindex(T::ITensor)
  if order(T) != 0
    throw(DimensionMismatch("In scalar(T) or T[], ITensor T is not a scalar"))
  end
  return tensor(T)[]::Number
end

"""
    setindex!(T::ITensor, x::Number, I::Int...)

    setindex!(T::ITensor, x::Number, I::CartesianIndex)

Set the specified element of the ITensor, using internal
Index ordering of the ITensor.

# Example
```julia
i = Index(2; tags = "i")
A = ITensor(i, i')
A[1, 2] = 1.0 # same as: A[i => 1, i' => 2] = 1.0
```
"""
function Base.setindex!(T::ITensor, x::Number, I::Int...)
  fluxT = flux(T)
  if !isnothing(fluxT) && fluxT != flux(T, I...)
    error("In `setindex!`, the element you are trying to set is in a block that does not have the same flux as the other blocks of the ITensor. You may be trying to create an ITensor that does not have a well defined quantum number flux.")
  end
  TR = setindex!!(tensor(T), x, I...)
  setstore!(T, store(TR))
  return T
end

Base.setindex!(T::ITensor, x::Number, I::CartesianIndex) =
  setindex!(T, x, Tuple(I)...)

"""
    setindex!(T::ITensor, x::Number, ivs...)

Set the specified element of the ITensor using a list
of `IndexVal`s or `Pair{<:Index, Int}`.

# Example
```julia
i = Index(2; tags = "i")
A = ITensor(i, i')
A[i => 1, i' => 2] = 1.0 # same as: A[i' => 2, i => 1] = 1.0
```
"""
function Base.setindex!(T::ITensor, x::Number, ivs...)
  p = NDTensors.getperm(inds(T), ind.(ivs))
  vals = NDTensors.permute(val.(ivs), p)
  T[vals...] = x
  return T
end

function Base.setindex!(::ITensor{Any}, ::Number, ivs...)
  error("Cannot set the element of an emptyITensor(). Must define indices to set elements")
end

"""
    eachindex(A::ITensor)

Create an iterable object for visiting each element of the ITensor `A` (including structually
zero elements for sparse tensors).

For example, for dense tensors this may return `1:length(A)`, while for sparse tensors
it may return a Cartesian range.
"""
Base.eachindex(A::ITensor) = eachindex(tensor(A))

"""
    iterate(A::ITensor, args...)

Iterate over the elements of an ITensor.
"""
Base.iterate(A::ITensor, args...) = iterate(tensor(A), args...)

"""
    fill!(T::ITensor, x::Number)

Fill all values of the ITensor with the specified value.
"""
function Base.fill!(T::ITensor,
                    x::Number)
  # TODO: automatically switch storage type if needed?
  # Use broadcasting `T .= x`?
  fill!(tensor(T), x)
  return T
end

# TODO: name this `indexset` or `IndexSet`,
# or maybe just `inds`?
itensor2inds(A::ITensor) = inds(A)
itensor2inds(i::Index) = IndexSet(i)
itensor2inds(is::Vector{<:Index}) = IndexSet(is)
itensor2inds(is::Tuple{Vararg{<:Index}}) = IndexSet(is)
itensor2inds(A) = A

# in
hasind(A, i::Index) = i ∈ itensor2inds(A)

# issubset
hasinds(A, is) = is ⊆ itensor2inds(A)
hasinds(A, is::Index...) = hasinds(A, IndexSet(is...))

"""
    hasinds(is...)

Returns an anonymous function `x -> hasinds(x, is...)` which
accepts an ITensor or IndexSet and returns `true` if the
ITensor or IndexSet has the indices `is`.
"""
hasinds(is::Indices) = x -> hasinds(x, is)
hasinds(is::Vector{ <: Index}) = x -> hasinds(x, is)
hasinds(is::Index...) = hasinds(IndexSet(is...))

"""
    hascommoninds(A, B; kwargs...)

    hascommoninds(B; kwargs...) -> f::Function

Check if the ITensors or sets of indices `A` and `B` have
common indices.

If only one ITensor or set of indices `B` is passed, return a
function `f` such that `f(A) = hascommoninds(A, B; kwargs...)`
"""
hascommoninds(A, B; kwargs...) =
  !isnothing(commonind(A, B; kwargs...))

hascommoninds(B; kwargs...) = x -> hascommoninds(x, B; kwargs...)

# issetequal
hassameinds(A, B) =
  issetequal(itensor2inds(A), itensor2inds(B))

# intersect
commoninds(A...; kwargs...) =
  IndexSet(intersect(itensor2inds.(A)...; kwargs...))

commoninds(::Order{N}, A...; kwargs...) where {N} =
  intersect(Order(N), itensor2inds.(A)...; kwargs...)

# firstintersect
commonind(A...; kwargs...) =
  firstintersect(itensor2inds.(A)...; kwargs...)

# symdiff
noncommoninds(A...; kwargs...) =
  IndexSet(symdiff(itensor2inds.(A)...; kwargs...)...)

noncommoninds(::Order{N}, A...; kwargs...) where {N} =
  IndexSet{N}(symdiff(itensor2inds.(A)...; kwargs...)...)

# firstsymdiff
noncommonind(A...; kwargs...) =
  getfirst(symdiff(itensor2inds.(A)...; kwargs...))

# setdiff
uniqueinds(A...; kwargs...) =
  IndexSet(setdiff(itensor2inds.(A)...; kwargs...)...)

uniqueinds(::Order{N}, A...; kwargs...) where {N} =
  setdiff(Order(N), ITensors.itensor2inds.(A)...; kwargs...)

# firstsetdiff
uniqueind(A...; kwargs...) =
  firstsetdiff(itensor2inds.(A)...; kwargs...)

# union
unioninds(A...; kwargs...) =
  IndexSet(union(itensor2inds.(A)...; kwargs...)...)

unioninds(::Order{N}, A...; kwargs...) where {N} =
  IndexSet{N}(union(ITensors.itensor2inds.(A)...; kwargs...)...)

# firstunion
unionind(A...; kwargs...) =
  getfirst(union(itensor2inds.(A)...; kwargs...))

firstind(A...; kwargs...) =
  getfirst(itensor2inds.(A)...; kwargs...)

filterinds(A...; kwargs...) =
  filter(itensor2inds.(A)...; kwargs...)

# Faster version when no filtering is requested
filterinds(A::ITensor) = inds(A)

# For backwards compatibility
NDTensors.inds(A...; kwargs...) =
  filterinds(A...; kwargs...)

# in-place versions of priming and tagging
for fname in (:prime,
              :setprime,
              :noprime,
              :mapprime,
              :swapprime,
              :addtags,
              :removetags,
              :replacetags,
              :settags,
              :swaptags,
              :replaceind,
              :replaceinds,
              :swapind,
              :swapinds)
  @eval begin
    $fname(f::Function,
           A::ITensor,
           args...) = setinds(A,$fname(f,
                                       inds(A),
                                       args...))

    $(Symbol(fname,:!))(f::Function,
                        A::ITensor,
                        args...) = setinds!(A,$fname(f,
                                                     inds(A),
                                                     args...))

    $fname(A::ITensor,
           args...;
           kwargs...) = setinds(A,$fname(inds(A),
                                         args...;
                                         kwargs...))

    $(Symbol(fname,:!))(A::ITensor,
                        args...;
                        kwargs...) = setinds!(A,$fname(inds(A),
                                                       args...;
                                                       kwargs...))
  end
end

priming_tagging_doc = """
Optionally, only modify the indices with the specified keyword arguments.

# Arguments
- `tags = nothing`: if specified, only modify Index `i` if `hastags(i, tags) == true`.
- `plev = nothing`: if specified, only modify Index `i` if `hasplev(i, plev) == true`.

In both versions above, the ITensor storage is not modified or copied (so the first version returns an ITensor with a view of the original storage).
"""

@doc """
    prime(A::ITensor, plinc::Int = 1; <keyword arguments>) -> ITensor

    prime!(A::ITensor, plinc::Int = 1; <keyword arguments>)

Increase the prime level of the indices of an ITensor.

$priming_tagging_doc
""" prime(::ITensor, ::Any...)

@doc """
    setprime(A::ITensor, plev::Int; <keyword arguments>) -> ITensor

    setprime!(A::ITensor, plev::Int; <keyword arguments>)

Set the prime level of the indices of an ITensor.

$priming_tagging_doc
""" setprime(::ITensor, ::Any...)

@doc """
    noprime(A::ITensor; <keyword arguments>) -> ITensor

    noprime!(A::ITensor; <keyword arguments>)

Set the prime level of the indices of an ITensor to zero.

$priming_tagging_doc
""" noprime(::ITensor, ::Any...)

@doc """
    mapprime(A::ITensor, plold::Int, plnew::Int; <keyword arguments>) -> ITensor

    mapprime!(A::ITensor, plold::Int, plnew::Int; <keyword arguments>)

Set the prime level of the indices of an ITensor with prime level `plold` to `plnew`.

$priming_tagging_doc
""" mapprime(::ITensor, ::Any...)

@doc """
    swapprime(A::ITensor, pl1::Int, pl2::Int; <keyword arguments>) -> ITensor

    swapprime!(A::ITensor, pl1::Int, pl2::Int; <keyword arguments>)

Set the prime level of the indices of an ITensor with prime level `pl1` to `pl2`, and those with prime level `pl2` to `pl1`.

$priming_tagging_doc
""" swapprime(::ITensor, ::Any...)

@doc """
    addtags(A::ITensor, ts::String; <keyword arguments>) -> ITensor

    addtags!(A::ITensor, ts::String; <keyword arguments>)

Add the tags `ts` to the indices of an ITensor.

$priming_tagging_doc
""" addtags(::ITensor, ::Any...)

@doc """
    removetags(A::ITensor, ts::String; <keyword arguments>) -> ITensor

    removetags!(A::ITensor, ts::String; <keyword arguments>)

Remove the tags `ts` from the indices of an ITensor.

$priming_tagging_doc
""" removetags(::ITensor, ::Any...)

@doc """
    settags(A::ITensor, ts::String; <keyword arguments>) -> ITensor

    settags!(A::ITensor, ts::String; <keyword arguments>)

Set the tags of the indices of an ITensor to `ts`.

$priming_tagging_doc
""" settags(::ITensor, ::Any...)

@doc """
    replacetags(A::ITensor, tsold::String, tsnew::String; <keyword arguments>) -> ITensor

    replacetags!(A::ITensor, tsold::String, tsnew::String; <keyword arguments>)

Replace the tags `tsold` with `tsnew` for the indices of an ITensor.

$priming_tagging_doc
""" replacetags(::ITensor, ::Any...)

@doc """
    swaptags(A::ITensor, ts1::String, ts2::String; <keyword arguments>) -> ITensor

    swaptags!(A::ITensor, ts1::String, ts2::String; <keyword arguments>)

Swap the tags `ts1` with `ts2` for the indices of an ITensor.

$priming_tagging_doc
""" swaptags(::ITensor, ::Any...)

@doc """
    replaceind(A::ITensor, i1::Index, i2::Index) -> ITensor

    replaceind!(A::ITensor, i1::Index, i2::Index)

Replace the Index `i1` with the Index `i2` in the ITensor.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).
""" replaceind(::ITensor, ::Any...)

@doc """
    replaceinds(A::ITensor, inds1, inds2) -> ITensor

    replaceinds!(A::ITensor, inds1, inds2)

Replace the Index `inds1[n]` with the Index `inds2[n]` in the ITensor, where `n` runs from `1` to `length(inds1) == length(inds2)`.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

The storage of the ITensor is not modified or copied (the output ITensor is a view of the input ITensor).
""" replaceinds(::ITensor, ::Any...)

@doc """
    swapind(A::ITensor, i1::Index, i2::Index) -> ITensor

    swapind!(A::ITensor, i1::Index, i2::Index)

Swap the Index `i1` with the Index `i2` in the ITensor.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).
""" swapind(::ITensor, ::Any...)

@doc """
    swapinds(A::ITensor, inds1, inds2) -> ITensor

    swapinds!(A::ITensor, inds1, inds2)

Swap the Index `inds1[n]` with the Index `inds2[n]` in the ITensor, where `n` runs from `1` to `length(inds1) == length(inds2)`.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

The storage of the ITensor is not modified or copied (the output ITensor is a view of the input ITensor).
""" swapinds(::ITensor, ::Any...)

"""
    adjoint(A::ITensor)

For `A'` notation to prime an ITensor by 1.
"""
Base.adjoint(A::ITensor) = prime(A)

dirs(A::ITensor, is) = dirs(inds(A), is)

function Base.:(==)(A::ITensor, B::ITensor)
  return norm(A - B) == zero(promote_type(eltype(A),eltype(B)))
end

function Base.isapprox(A::ITensor,
                       B::ITensor;
                       kwargs...)
    B = permute(dense(B), inds(A))
    return isapprox(array(A), array(B); kwargs...)
end

Random.randn!(T::ITensor) = randn!(tensor(T))

"""
    randomITensor([::Type{ElT <: Number} = Float64, ]inds)

    randomITensor([::Type{ElT <: Number} = Float64, ]inds::Index...)

Construct an ITensor with type `ElT` and indices `inds`, whose elements are normally distributed random numbers. If the element type is not specified, it defaults to `Float64`.
"""
function randomITensor(::Type{S},
                       inds::Indices) where {S <: Number}
  T = ITensor(S, inds)
  randn!(T)
  return T
end

function randomITensor(::Type{S},
                       inds::Index...) where {S <: Number}
  return randomITensor(S, IndexSet(inds...))
end

# To fix ambiguity errors with QN version
function randomITensor(::Type{ElT}) where {ElT <: Number}
  return randomITensor(ElT, IndexSet())
end

randomITensor(inds::Indices) = randomITensor(Float64, inds)

randomITensor(inds::Index...) = randomITensor(Float64,
                                              IndexSet(inds...))

# To fix ambiguity errors with QN version
randomITensor() = randomITensor(Float64, IndexSet())

function combiner(inds::Indices;
                  kwargs...)
  tags = get(kwargs, :tags, "CMB,Link")
  new_ind = Index(prod(dims(inds)), tags)
  new_is = IndexSet(new_ind, inds...)
  return itensor(Combiner(), new_is)
end

combiner(inds::Index...;
         kwargs...) = combiner(IndexSet(inds...); kwargs...)

# Special case when no indices are combined (useful for generic code)
function combiner(; kwargs...)
  return itensor(Combiner(), IndexSet())
end

function combinedind(T::ITensor)
  if store(T) isa Combiner
    return inds(T)[1]
  end
  return nothing
end

combinedind(T::ITensor{0}) = nothing

LinearAlgebra.norm(T::ITensor) = norm(tensor(T))

function dag(T::ITensor; always_copy=false)
  TT = conj(tensor(T); always_copy=always_copy)
  return itensor(store(TT),dag(inds(T)))
end

"""
    permute(T::ITensors, inds; always_copy::Bool = false)
    permute(T::ITensors, inds::Index...; always_copy::Bool = false)

Return a new ITensor T with indices permuted according
to the input indices inds. The storage of the ITensor
is permuted accordingly.

If `always_copy = false`, it avoids copying data if possible.
Therefore, it may return a view. Use `always_copy = true`
if you never want it to return an ITensor with a view
of the original ITensor.
"""
function permute(T::ITensor{N},
                 new_inds; always_copy::Bool = false) where {N}
  perm = NDTensors.getperm(new_inds, inds(T))
  if !always_copy && NDTensors.is_trivial_permutation(perm)
    return T
  end
  Tp = permutedims(tensor(T), perm)
  return itensor(Tp)::ITensor{N}
end

permute(T::ITensor,
        inds::Index...; vargs...) = permute(T,
                                            IndexSet(inds...); vargs...)

Base.:*(T::ITensor, x::Number) = itensor(x*tensor(T))

Base.:*(x::Number, T::ITensor) = T*x

#TODO: make a proper element-wise division
Base.:/(A::ITensor, x::Number) = A*(1.0/x)

Base.:-(A::ITensor) = itensor(-tensor(A))

function Base.:+(A::ITensor{N}, B::ITensor{N}) where {N}
  C = copy(A)
  C .+= B
  return C
end

function Base.:-(A::ITensor{N}, B::ITensor{N}) where {N}
  C = copy(A)
  C .-= B
  return C
end

Base.:+(A::ITensor{Any}, B::ITensor) = copy(B)

Base.:+(A::ITensor, B::ITensor{Any}) = B + A

Base.:+(A::ITensor, B::ITensor) = error("cannot add ITensors with different numbers of indices")
Base.:-(A::ITensor, B::ITensor) = error("cannot subtract ITensors with different numbers of indices")

"""
    *(A::ITensor, B::ITensor)

Contract ITensors A and B to obtain a new ITensor. This
contraction `*` operator finds all matching indices common
to A and B and sums over them, such that the result will
have only the unique indices of A and B. To prevent
indices from matching, their prime level or tags can be
modified such that they no longer compare equal - for more
information see the documentation on Index objects.
"""
function Base.:*(A::ITensor, B::ITensor)
  (labelsA,labelsB) = compute_contraction_labels(inds(A),inds(B))
  CT = contract(tensor(A),labelsA,tensor(B),labelsB)
  C = itensor(CT)
  warnTensorOrder = get_warn_order()
  if !isnothing(warnTensorOrder) > 0 &&
     order(C) >= warnTensorOrder
     @warn "Contraction resulted in ITensor with $(order(C)) indices, which is greater than or equal to the ITensor order warning threshold $warnTensorOrder. You can modify the threshold with functions like `set_warn_order!(::Int)`, `reset_warn_order!()`, and `disable_warn_order!()`."
  end
  return C
end

# TODO: define for contraction order optimization
#Base.:*(A1::ITensor,
#        A2::ITensor,
#        A3::ITensor, As::ITensor...)

function LinearAlgebra.mul!(C::ITensor, A::ITensor, B::ITensor,
                            α::Number, β::Number=0)
  (labelsC,labelsA,labelsB) = compute_contraction_labels(inds(C),
                                                         inds(A),
                                                         inds(B))
  CT = NDTensors.contract!!(tensor(C), labelsC,
                          tensor(A), labelsA,
                          tensor(B), labelsB,
                          α, β)
  C = itensor(CT)
  return C
end

# This is necessary for now since not all types implement contract!!
# with non-trivial α and β
function LinearAlgebra.mul!(C::ITensor, A::ITensor, B::ITensor)
  (labelsC,labelsA,labelsB) = compute_contraction_labels(inds(C),
                                                         inds(A),
                                                         inds(B))
  CT = NDTensors.contract!!(tensor(C), labelsC,
                          tensor(A), labelsA,
                          tensor(B), labelsB)
  C = itensor(CT)
  return C
end

LinearAlgebra.dot(A::ITensor, B::ITensor) = (dag(A)*B)[]

"""
    exp(A::ITensor, Linds=Rinds', Rinds=inds(A,plev=0); ishermitian = false)

Compute the exponential of the tensor `A` by treating it as a matrix ``A_{lr}`` with
the left index `l` running over all indices in `Linds` and `r` running over all
indices in `Rinds`.

Only accepts index lists `Linds`,`Rinds` such that: (1) `length(Linds) +
length(Rinds) == length(inds(A))` (2) `length(Linds) == length(Rinds)` (3) For
each pair of indices `(Linds[n],Rinds[n])`, `Linds[n]` and `Rinds[n]` represent
the same Hilbert space (the same QN structure in the QN case, or just the same
length in the dense case), and appear in `A` with opposite directions.

When `ishermitian=true` the exponential of `Hermitian(A_{lr})` is
computed internally.
"""
function LinearAlgebra.exp(A::ITensor{N},
                           Linds,
                           Rinds; kwargs...) where N
  ishermitian=get(kwargs,:ishermitian,false)

  @debug begin
    if hasqns(A)
      @assert flux(A) == QN()
    end
  end

  NL = length(Linds)
  NR = length(Rinds)
  NL != NR && error("Must have equal number of left and right indices")
  N != NL + NR && error("Number of left and right indices must add up to total number of indices")

  # Linds, Rinds may not have the correct directions
  Lis = IndexSet(Linds...)
  Ris = IndexSet(Rinds...)

  # Ensure the indices have the correct directions,
  # QNs, etc.
  # First grab the indices in A, then permute them
  # correctly.
  Lis = permute(commoninds(A, Lis), Lis)
  Ris = permute(commoninds(A, Ris), Ris)

  for (l, r) in zip(Lis, Ris)
    if space(l) != space(r)
      error("In exp, indices must come in pairs with equal spaces.")
    end
    if hasqns(A)
      if dir(l) == dir(r)
        error("In exp, indices must come in pairs with opposite directions")
      end
    end
  end

  CL = combiner(Lis...; dir = Out)
  CR = combiner(Ris...; dir = In)
  AC = A * CR * CL
  expAT = ishermitian ? exp(Hermitian(tensor(AC))) : exp(tensor(AC))
  return itensor(expAT) * dag(CR) * dag(CL)
end

function LinearAlgebra.exp(A::ITensor;
                           kwargs...)
  Ris = filterinds(A; plev = 0)
  Lis = Ris'
  return exp(A, Lis, Ris; kwargs...)
end

"""
    product(A::ITensor, B::ITensor)

For matrix-like ITensors (ones with pairs of primed and
unprimed indices), perform a matrix product, i.e.
```julia
mapprime(prime(A) * B, 2, 1)
```
In the future, more general ITensors with other tag or
prime conventions may be supported.
"""
function product(A::ITensor,
                 B::ITensor)
  R = prime(A) * B
  return mapprime(R,2,1)
end

#######################################################################
#
# In-place operations
#

"""
    normalize!(T::ITensor)

Normalize an ITensor in-place, such that norm(T)==1.
"""
LinearAlgebra.normalize!(T::ITensor) = (T .*= 1/norm(T))

"""
    copyto!(B::ITensor, A::ITensor)

Copy the contents of ITensor A into ITensor B.
```
B .= A
```
"""
function Base.copyto!(R::ITensor{N}, T::ITensor{N}) where {N}
  R .= T
  return R
end

function Base.map!(f::Function,
                   R::ITensor{N},
                   T1::ITensor{N},
                   T2::ITensor{N}) where {N}
  R !== T1 && error("`map!(f, R, T1, T2)` only supports `R === T1` right now")
  perm = NDTensors.getperm(inds(R),inds(T2))
  TR,TT = tensor(R),tensor(T2)

  # TODO: Include type promotion from α
  TR = convert(promote_type(typeof(TR),typeof(TT)),TR)
  TR = permutedims!!(TR,TT,perm,f)

  setstore!(R,store(TR))
  setinds!(R,inds(TR))
  return R
end

"""
    axpy!(a::Number, v::ITensor, w::ITensor)
```
w .+= a .* v
```
"""
LinearAlgebra.axpy!(a::Number,
                    v::ITensor,
                    w::ITensor) = (w .+= a .* v)

"""
axpby!(a,v,b,w)

```
w .= a .* v + b .* w
```
"""
LinearAlgebra.axpby!(a::Number,
                     v::ITensor,
                     b::Number,
                     w::ITensor) = (w .= a .* v + b .* w)

"""
    scale!(A::ITensor,x::Number) = rmul!(A,x)

Scale the ITensor A by x in-place. May also be written `rmul!`.
```
A .*= x
```
"""
NDTensors.scale!(T::ITensor, α::Number) = (T .*= α)

LinearAlgebra.rmul!(T::ITensor, α::Number) = (T .*= α)

LinearAlgebra.lmul!(T::ITensor, α::Number) = (T .= α .* T)

"""
    mul!(A::ITensor, x::Number, B::ITensor)

Scalar multiplication of ITensor B with x, and store the result in A.
Like `A .= x .* B`.
"""
LinearAlgebra.mul!(R::ITensor,
                   α::Number,
                   T::ITensor) = (R .= α .* T)

LinearAlgebra.mul!(R::ITensor,
                   T::ITensor,
                   α::Number) = (R .= T .* α)

#
# Block sparse related functions
# (Maybe create fallback definitions for dense tensors)
#

hasqns(T::ITensor) = hasqns(inds(T))

NDTensors.nnz(T::ITensor) = nnz(tensor(T))

NDTensors.nnzblocks(T::ITensor) = nnzblocks(tensor(T))

NDTensors.nzblock(T::ITensor, args...) = nzblock(tensor(T), args...)

NDTensors.nzblocks(T::ITensor) = nzblocks(tensor(T))

NDTensors.blockoffsets(T::ITensor) = blockoffsets(tensor(T))

flux(T::ITensor, args...) = flux(inds(T), args...)

"""
    flux(T::ITensor)

Returns the flux of the ITensor.

If the ITensor is empty or it has no QNs, returns `nothing`.
"""
function flux(T::ITensor)
  (!hasqns(T) || isempty(T)) && return nothing
  @debug checkflux(T)
  bofs = blockoffsets(T)
  block1 = nzblock(bofs, 1)
  return flux(T, block1)
end

function checkflux(T::ITensor, flux_check)
  for b in nzblocks(T)
    fluxTb = flux(T, b)
    if fluxTb != flux_check
      error("Block $b has flux $fluxTb that is inconsistent with the desired flux $flux_check")
    end
  end
  return nothing
end

function checkflux(T::ITensor)
  b1 = first(nzblocks(T))
  fluxTb1 = flux(T, b1)
  return checkflux(T, fluxTb1)
end

function NDTensors.addblock!(T::ITensor,
                             args...)
  (!isnothing(flux(T)) && flux(T) ≠ flux(T, args...)) &&
   error("Block does not match current flux")
  TR = addblock!!(tensor(T), args...)
  setstore!(T, store(TR))
  return T
end

"""
    isempty(T::ITensor)

Returns `true` if the ITensor contains no elements.

An ITensor with `Empty` storage always returns `true`.
"""
Base.isempty(T::ITensor) = isempty(tensor(T))

#######################################################################
#
# Developer functions
#

"""
    array(T::ITensor)

Given an ITensor `T`, returns
an Array with a copy of the ITensor's elements,
or a view in the case the the ITensor's storage is Dense.
The ordering of the elements in the Array, in
terms of which Index is treated as the row versus
column, depends on the internal layout of the ITensor.
*Therefore this method is intended for developer use
only and not recommended for use in ITensor applications.*
"""
NDTensors.array(T::ITensor) = array(tensor(T))

"""
    matrix(T::ITensor)

Given an ITensor `T` with two indices, returns
a Matrix with a copy of the ITensor's elements,
or a view in the case the ITensor's storage is Dense.
The ordering of the elements in the Matrix, in
terms of which Index is treated as the row versus
column, depends on the internal layout of the ITensor.
*Therefore this method is intended for developer use
only and not recommended for use in ITensor applications.*
"""
function NDTensors.matrix(T::ITensor{2})
  return array(tensor(T))
end

"""
    vector(T::ITensor)

Given an ITensor `T` with one index, returns
a Vector with a copy of the ITensor's elements,
or a view in the case the ITensor's storage is Dense.
"""
function NDTensors.vector(T::ITensor{1})
  return array(tensor(T))
end

#######################################################################
#
# Printing, reading and writing ITensors
#

function Base.summary(io::IO,
                      T::ITensor)
  print(io,"ITensor ord=$(order(T))")
  if hasqns(T)
    println(io)
    for i in 1:order(T)
      print(io, inds(T)[i])
      println(io)
    end
  else
    for i in 1:order(T)
      print(io, " ", inds(T)[i])
    end
    println(io)
  end
  print(io, typeof(store(T)))
end

function Base.summary(io::IO,
                      T::ITensor{Any})
  print(io,"ITensor ord=$(order(T))")
  print(io," \n", typeof(inds(T)))
  print(io," \n", typeof(store(T)))
end

# TODO: make a specialized printing from Diag
# that emphasizes the missing elements
function Base.show(io::IO,
                   T::ITensor)
  println(io,"ITensor ord=$(order(T))")
  Base.show(io, MIME"text/plain"(), tensor(T))
end

function Base.show(io::IO,
                   mime::MIME"text/plain",
                   T::ITensor)
  summary(io,T)
end

function readcpp(io::IO,
                 ::Type{Dense{ValT}};
                 kwargs...) where {ValT}
  format = get(kwargs,:format,"v3")
  if format=="v3"
    size = read(io,UInt64)
    data = Vector{ValT}(undef,size)
    for n=1:size
      data[n] = read(io,ValT)
    end
    return Dense(data)
  else
    throw(ArgumentError("read Dense: format=$format not supported"))
  end
end

function readcpp(io::IO,
                 ::Type{ITensor};
                 kwargs...)
  format = get(kwargs,:format,"v3")
  if format=="v3"
    inds = readcpp(io,IndexSet;kwargs...)
    read(io,12) # ignore scale factor by reading 12 bytes
    storage_type = read(io,Int32)
    if storage_type==0 # Null
      store = Dense{Nothing}()
    elseif storage_type==1  # DenseReal
      store = readcpp(io,Dense{Float64};kwargs...)
    elseif storage_type==2  # DenseCplx
      store = readcpp(io,Dense{ComplexF64};kwargs...)
    elseif storage_type==3  # Combiner
      store = CombinerStorage(T.inds[1])
    #elseif storage_type==4  # DiagReal
    #elseif storage_type==5  # DiagCplx
    #elseif storage_type==6  # QDenseReal
    #elseif storage_type==7  # QDenseCplx
    #elseif storage_type==8  # QCombiner
    #elseif storage_type==9  # QDiagReal
    #elseif storage_type==10 # QDiagCplx
    #elseif storage_type==11 # ScalarReal
    #elseif storage_type==12 # ScalarCplx
    else
      throw(ErrorException("C++ ITensor storage type $storage_type not yet supported"))
    end
    return itensor(store,inds)
  else
    throw(ArgumentError("read ITensor: format=$format not supported"))
  end
end

function setwarnorder!(ord::Int)
  ITensors.GLOBAL_PARAMS["WarnTensorOrder"] = ord
end

function HDF5.write(parent::Union{HDF5File,HDF5Group},
                    name::AbstractString,
                    T::ITensor)
  g = g_create(parent,name)
  attrs(g)["type"] = "ITensor"
  attrs(g)["version"] = 1
  write(g,"inds", inds(T))
  write(g,"store", store(T))
end

#function HDF5.read(parent::Union{HDF5File,HDF5Group},
#                   name::AbstractString)
#  g = g_open(parent,name)
#
#  try
#    typestr = read(attrs(g)["type"])
#    type_t = eval(Meta.parse(typestr))
#    res = read(parent,"name",type_t)
#    return res
#  end
#  return
#end

function HDF5.read(parent::Union{HDF5File,HDF5Group},
                   name::AbstractString,
                   ::Type{ITensor})
  g = g_open(parent,name)
  if read(attrs(g)["type"]) != "ITensor"
    error("HDF5 group or file does not contain ITensor data")
  end
  inds = read(g,"inds",IndexSet)

  stypestr = read(attrs(g_open(g,"store"))["type"])
  stype = eval(Meta.parse(stypestr))

  store = read(g,"store",stype)

  return itensor(store,inds)
end

#
# Deprecations
#

@deprecate findindex(args...; kwargs...) firstind(args...; kwargs...)

@deprecate findinds(args...; kwargs...) inds(args...; kwargs...)

@deprecate commonindex(args...; kwargs...) commonind(args...; kwargs...)

@deprecate uniqueindex(args...; kwargs...) uniqueind(args...; kwargs...)

@deprecate replaceindex!(args...; kwargs...) replaceind!(args...; kwargs...)

@deprecate siteindex(args...; kwargs...) siteind(args...; kwargs...)

@deprecate linkindex(args...; kwargs...) linkind(args...; kwargs...)

@deprecate matmul(A::ITensor, B::ITensor) product(A, B)