text:
#
# Copyright 2021 Clemens Cords
# Created on 26.12.2021 by clem (mail@clemens-cords.com)
#

"""
module with julia-functionality needed by the C++ side of jluna.
Most end-user should not call any function in this module, with `cppcall` being the only exception
"""
module jluna

    """
    `get_value_type_of_array(::Array{T}) -> Type`

    forward value type of array
    """
    function get_value_type_of_array(_::Array{T}) ::Type where T

        return T
    end

    """
    `get_reference_value(::Base.RefValue{T}) -> T`

    forward value of reference
    """
    function get_reference_value(ref::Base.RefValue{T}) ::T where T

        return ref[];
    end

    setindex!(str::String, c::Char, i::Int64) = setindex!(collect(str), c, i)

    """
    `is_number_only(::String) -> Bool`

    check whether a string can be transformed into a base 10 number
    """
    function is_number_only(x::String) ::Bool

        for s in x
            if s != '0' || s != '1' || s != '2' || s != '3' || s != '4' || s != '5' || s != '6' || s != '7' || s != '8' || s != '9'
                return false
            end
        end

        return true;
    end

    """
    `is_method_available(::Function, ::Any) -> Bool`

    check if method of given function is available for a specific variable
    """
    function is_method_available(f::Function, variable) ::Bool

        return hasmethod(f, Tuple{typeof(variable)})
    end

    """
    `get_function(::Symbol, ::Module) -> Function`

    exception-safe function access wrapper
    """
    function get_function(function_name::Symbol, m::Module) ::Function

        return m.eval(function_name)
    end
    export get_function

    """
    `exists(<:AbstractArray, ::Any) -> Bool`

    check if element exists in array
    """
    function exists(array::T, v::Any) ::Bool where T <: AbstractArray

        return !isempty(findall(x -> x == v, array))
    end

    """
    `tuple_at(::Tuple, ::Integer) -> Any`

    get nth element of tuple
    """
    function tuple_at(x::Tuple, i::Integer)
        return x[i]
    end

    """
    `tuple_length(::Tuple) -> Integer`

    get length of tuple
    """
    function tuple_length(x::Tuple{N}) where N
        return N
    end

    """
    `make_new(::String, xs...) -> Any`

    parse string to type, then call ctor with given args
    """
    function make_new(name::String, xs...) ::Any

        return Main.eval(:($(Meta.parse(name))($(xs...))))
    end

    """
    `make_vector(::T...) -> Vector{T}`

    wrap vector ctor in varargs argument, used by box/unbox
    """
    function make_vector(args::T...) ::Vector{T} where T

        return [args...]
    end

    """
    `make_vector(t::Type) -> Vector{t}`

    create empty vector of given type
    """
    function make_vector(t::Type) ::Vector{t}

        return Vector{t}()
    end

    """
    `make_vector(::Type{T}, ::Any...) -> Vector{T}`

    create vector by converting all elements to target type
    """
    function make_vector(type::Type{T}, xs...) where T
       return convert.(T, [xs...])
    end

    """
    `new_vector(::Integer, ::T) -> Vector{T}`

    create vector by deducing argument type
    """
    function new_vector(size::Integer, _::T) where T
        return Vector{T}(undef, size)
    end

    """
    `new_vector(::Integer, ::T) -> Vector{T}`

    create vector by deducing argument type
    """
    function new_vector(size::Integer, type::Type)
        return Vector{type}(undef, size)
    end

    """
    `make_set(::T...) -> Set{T}`

    wrap set ctor in varargs argument, used by box/unbox
    """
    function make_set(args::T...) ::Set{T} where T

        return Set([args...]);
    end

    """
    `make_set(::Type{T}, ::Any...) -> Set{T}`

    """
    function make_set(type::Type{T}, xs...) ::Set{T} where T

        return Set(make_vector(type, xs...));
    end

    """
    `make_pair(::T, ::U) -> Pair{T, U}`

    wrap pair ctor
    """
    function make_pair(a::T, b::U) ::Pair{T, U} where {T, U}

        return a => b
    end

    """
    `make_complex(:T, :T) -> Complex{T}`

    wrap complex ctor
    """
    function make_complex(real::T, imag::T) ::Complex{T} where T
        return Complex{T}(real, imag)
    end

    """
    `assert_isa(::T, ::Symbol) -> Nothing`

    throw assertion if x is not of named type
    """
    function assert_isa(x::Any, type::Type) ::Nothing

        if !(x isa type)
            throw(AssertionError("expected " * string(type) * " but got an object of type " * string(typeof(x))));
        end

        return nothing
    end

    """
    `convert(::T, symbol::Symbol) -> Any`

    convert value type, declared via symbol
    """
    function convert(x::T, symbol::Symbol) ::Any where T

        type = Main.eval(symbol);
        @assert type isa Type

        if T isa type
            return T
        end

        return Base.convert(type, x)
    end

    """
    `string_to_type` -> Type

    evaluate string to type name if possible
    """
    function string_to_type(str::String) ::Type

        res = Main.eval(Meta.parse(str))
        if !(res isa Type)
           throw(UndefVarError(Symbol(str)))
        end
        return res
    end

    """
    `invoke(function::Any, arguments::Any...) -> Any`

    wrap function call for non-function objects
    """
    function invoke(x::Any, args...) ::Any
        return x(args...)
    end

    """
    `create_or_assign(::Symbol, ::T) -> T`

    assign variable in main, or if none exist, create it and assign
    """
    function create_or_assign(symbol::Symbol, value::T) ::T where T
        return Main.eval(:($symbol = $value))
    end

    """
    `serialize(<:AbstractDict{T, U}) -> Vector{Pair{T, U}}`

    transform dict into array
    """
    function serialize(x::T) ::Vector{Pair{Key_t, Value_t}} where {Key_t, Value_t, T <: AbstractDict{Key_t, Value_t}}

        out = Vector{Pair{Key_t, Value_t}}()
        for e in x
            push!(out, e)
        end
        return out;
    end

    """
    `serialize(::Set{T}) -> Vector{T}`

    transform dict into array
    """
    function serialize(x::T) ::Vector{U} where {U, T <: AbstractSet{U}}

        out = Vector{U}()

        for e in x
            push!(out, e)
        end

        return out;
    end

    """
    `dot(::Array, field::Symbol) -> Any`

    wrapped dot operator
    """
    function dot(x::Array, field_name::Symbol) ::Any

        index_maybe = parse(Int, string(field_name));
        @assert index_maybe isa Integer
        return x[index_maybe];
    end
    export dot;

    """
    `dot(::Module, field::Symbol) -> Any`

    wrapped dot operator
    """
    dot(x::Module, field_name::Symbol) = return x.eval(field_name);

    """
    `dot(x::Any, field::Symbol) -> Any`

    wrapped dot operator, x.field
    """
    dot(x::Any, field_name::Symbol) = return eval(:($x.$field_name))

    """
    `unroll_type(::Type) -> Type`

    unroll type declaration
    """
    function unroll_type(type::Type) ::Type

        while hasproperty(type, :body)
            type = type.body
        end

        return type
    end

    """
    `is_name_typename(::Type, ::Type) -> Bool`

    unroll type declaration, then check if name is typename
    """
    function is_name_typename(type_in::Type, type_comparison::Type) ::Bool
        return getproperty(type_in, :name) == Base.typename(type_comparison)
    end

    """
    `get_n_fields(::Type) -> Int64`
    """
    function get_n_fields(type::Type) ::Int64
        return length(fieldnames(type))
    end

    """
    `get_fields(::Type) -> Vector{Pair{Symbol, Type}}`

    get field symbols and types, used by jluna::Type::get_fields
    """
    function get_fields(type::Type) ::Vector{Pair{Symbol, Type}}

        out = Vector{Pair{Symbol, Type}}();
        names = fieldnames(type)
        types = fieldtypes(type)

        for i in 1:(length(names))
            push!(out, names[i] => types[i])
        end

        return out
    end

    """
    `get_parameter(::Type) -> Vector{Pair{Symbol, Type}}`

    get parameter symbols and upper type limits, used by jluna::Type::get_parameters
    """
    function get_parameters(type::Type) ::Vector{Pair{Symbol, Type}}

        type = unroll_type(type)

        out = Vector{Pair{Symbol, Type}}();
        parameters = getproperty(type, :parameters)

        for i in 1:(length(parameters))
            push!(out, parameters[i].name => parameters[i].ub)
        end

        return out
    end

    """
    `get_n_parameters(::Type) -> Int64`
    """
    function get_n_parameters(type::Type) ::Int64

        type = unroll_type(type)

        return length(getproperty(type, :parameters))
    end

    """
    `assign_in_module(::Module, ::Symbol, ::T) -> T`

    assign variable in other module, throws if variable does not exist
    """
    function assign_in_module(m::Module, variable_name::Symbol, value::T) ::T where T

        if (!isdefined(m, variable_name))
            throw(UndefVarError(Symbol(string(m) * "." * string(variable_name))))
        end

        return Base.eval(m, :($variable_name = $value))
    end

    """
    `create_in_module(::Module, ::Symbol, ::T) -> T`

    assign variable in other module, if variable does not exist, create then assign
    """
    function create_or_assign_in_module(m::Module, variable_name::Symbol, value::T) ::T where T
        return Base.eval(m, :($variable_name = $value))
    end

    """
    `get_names(::Module) -> IdDict{Symbol, Any}`

    access all module members as dict
    """
    function get_names(m::Module) ::IdDict{Symbol, Any}

        out = IdDict{Symbol, Any}()

        for n in names(m; all = true)
            if string(n)[1] != '#'
                out[n] = m.eval(n)
            end
        end

        return out
    end

    """
    `get_nth_method(::Function, ::Integer) -> Method`

    wrap method access, used by jlune::Method
    """
    function get_nth_method(f::Function, i::Integer) ::Method

        return methods(f)[i]
    end

    """
    `get_return_type_of_nth_method(::Function, ::Integer) -> Type`

    used by jluna::Function to deduce method signature
    """
    function get_return_type_of_nth_method(f::Function, i::Integer) ::Type

        return Base.return_types(test)[i]
    end

    """
    `get_argument_type_of_nths_methods(::Function, ::Integer) -> Vector{Type}`

    used by jluna::Function to deduce method signature
    """
    function get_argument_types_of_nth_method(f::Function, i::Integer) ::Vector{Type}

        out = Vector{Type}()
        types = methods(f)[i].sig.types

        for i in 2:length(types)
            push!(out, types[i])
        end

        return out
    end

    """
    get_length_of_generator(::Base.Generator) -> Int64

    deduce length of Base.Generator object
    """
    function get_length_of_generator(gen::Base.Generator) ::Int64

        if (Base.haslength(gen))
            return length(gen)
        else
            # heuristically deduce length
            for i in Iterators.reverse(gen.iter.itr)
                if gen.iter.flt(i)
                    return i
                end
            end
        end
    end

    """
    offers verbose exception interface. Any call with safe_call will store
    the last exception and full stack trace as string in _last_exception and
    _last_message respectively
    """
    module exception_handler

        """
        exception Placeholder that signals that no exception occurred
        """
        struct NoException <: Exception end
        export NoException

        """
        current state of the exception handler

        _last_exception <:Exception
        _last_message ::String
        """
        mutable struct State
            _last_exception
            _last_message::String
            _exception_occurred::Bool
        end

        const _state = Ref{State}(State(NoException(), "", false));

        """
        `safe_call(::Expr, ::Module = Main) -> Any`

        call any line of code, update the handler then forward the result, if any
        """
        function safe_call(expr::Expr, m::Module = Main) ::Any

            if expr.head == :block
                expr.head = :toplevel
            end

            result = undef
            try
                result = Base.eval(m, expr)
                update()
            catch exc
                result = nothing
                update(exc)
            end

            return result
        end

        """
        `unsafe_call(::Expr, ::Module = Main) -> Any`

        call any line of code without updating the handler
        """
        function unsafe_call(expr::Expr, m::Module = Main) ::Any

            if expr.head == :block
                expr.head = :toplevel
            end

            return Base.eval(m, expr);
        end

        """
        `safe_call(::Function, ::Any...) -> Any`

        call any function, update the handler then forward the result, if any
        """
        function safe_call(f::Any, args...)

            result = undef
            try
                result = f(args...)
                update()
            catch exc
                result = nothing
                update(exc)
            end

            return result;
        end

        """
        `update(<:Exception) -> Nothing`

        update the handler after an exception was thrown
        """
        function update(exception::Exception) ::Nothing

            try
            global _state[]._last_message = sprint(Base.showerror, exception, catch_backtrace())
            global _state[]._last_exception = exception
            global _state[]._exception_occurred = true
            catch e end
            return nothing
        end

        """
        `update() -> Nothing`

        update the handler after *no* exception was thrown
        """
        function update() ::Nothing

            global _state[]._last_message = ""
            global _state[]._last_exception = NoException()
            global _state[]._exception_occurred = false
            return nothing
        end

        """
        `has_exception_occurred() -> Bool`

        is last exception type no "jluna.exception_handler.NoException"
        """
        function has_exception_occurred() ::Bool

            return _state[]._exception_occurred
        end

        """
        `get_last_message() -> String`

        wrapper for _state access
        """
        function get_last_message() ::String
            return _state[]._last_message
        end

        """
        `get_last_exception() -> Exception`

        wrapper for _state access
        """
        function get_last_exception() ::Exception
            return _state[]._last_exception
        end
    end

    """
    offers julia-side memory management for C++ jluna
    """
    module memory_handler

        const _current_id = Ref(UInt64(0));
        const _refs = Ref(Dict{UInt64, Base.RefValue{Any}}())
        const _ref_counter = Ref(Dict{UInt64, UInt64}())

        const _ref_id_marker = '#'
        const _refs_expression = Meta.parse("jluna.memory_handler._refs[]")

        # proxy id that is actually an expression, the ID of topmodule Main is
        ProxyID = Union{Expr, Symbol, Nothing}

        # make as unnamed
        make_unnamed_proxy_id(id::UInt64) = return Expr(:ref, Expr(:ref, _refs_expression, id))

        # make as named with owner and symbol name
        make_named_proxy_id(id::Symbol, owner_id::ProxyID) ::ProxyID = return Expr(:(.), owner_id, QuoteNode(id))

        # make as named with main as owner and symbol name
        make_named_proxy_id(id::Symbol, owner_id::Nothing) ::ProxyID = return id

        # make as named with owner and array index name
        make_named_proxy_id(id::Number, owner_id::ProxyID) ::ProxyID = return Expr(:ref, owner_id, convert(Int64, id))

        # assign to proxy id
        function assign(new_value::T, name::ProxyID) where T
            if new_value isa Symbol || new_value isa Expr
                return Main.eval(Expr(:(=), name, QuoteNode(new_value)))
            else
                return Main.eval(Expr(:(=), name, new_value));
            end
        end

        # eval proxy id
        evaluate(name::ProxyID) ::Any = return Main.eval(name)
        evaluate(name::Symbol) ::Any = return Main.eval(:($name))

        """
        `get_name(::ProxyID) -> String`

        parse name from proxy id
        """
        function get_name(id::ProxyID) ::String

            if length(id.args) == 0
                return "Main"
            end

            current = id
            while current.args[1] isa Expr && length(current.args) >= 2

                if current.args[2] isa UInt64
                    current.args[2] = convert(Int64, current.args[2])
                end

                current = current.args[1]
            end

            out = string(id)
            reg = r"\Q((jluna.memory_handler._refs[])[\E(.*)\Q])[]\E"
            captures = match(reg, out)

            if captures != nothing
                out = replace(out, reg => "<unnamed function proxy #" * string(tryparse(Int64, captures.captures[1])) * ">")
            end

            return out;
        end

        get_name(::Nothing) ::String = return "Main"
        get_name(s::Symbol) ::String = return string(s)
        get_name(i::Integer) ::String = return "[" * string(i) * "]"

        """
        `print_refs() -> Nothing`

        pretty print _ref state, for debugging
        """
        function print_refs() ::Nothing

            println("jluna.memory_handler._refs: ");
            for e in _refs[]
                println("\t", Int64(e.first), " => ", e.second[], " (", typeof(e.second[]), ")")
            end
        end

        """
        `create_reference(::UInt64, ::Any) -> UInt64`

        add reference to _refs
        """
        function create_reference(to_wrap::Any) ::UInt64

            global _current_id[] += 1;
            key::UInt64 = _current_id[];

            if (haskey(_refs[], key))
                _ref_counter[][key] += 1
            else
                _refs[][key] = Base.RefValue{Any}(to_wrap)
                _ref_counter[][key] = 1
            end

            return key;
        end

        create_reference(_::Nothing) ::UInt64 = return 0

        """
        `set_reference(::UInt64, ::T) -> Nothing`

        update the value of a reference in _refs without adding a new entry or changing it's key, ref pointers C++ side stay valid
        """
        function set_reference(key::UInt64, new_value::T) ::Base.RefValue{Any} where T

            _refs[][key] = Base.RefValue{Any}(new_value)
            return _refs[][key]
        end

        """
        `get_reference(::Int64) -> Any`

        access reference in _refs
        """
        function get_reference(key::UInt64) ::Any

            if (key == 0)
                return nothing
            end

           return _refs[][key]
        end

        """
        `free_reference(::UInt64) -> Nothing`

        free reference from _refs
        """
        function free_reference(key::UInt64) ::Nothing

            if (key == 0)
                return nothing;
            end

            if _refs[][key][] isa Module
                return
            end

            global _ref_counter[][key] -= 1
            count::UInt64 = _ref_counter[][key]

            if (count == 0)
                delete!(_ref_counter[], key)
                delete!(_refs[], key)
            end

            return nothing;
        end

        """
        `force_free() -> Nothing`

        immediately deallocate all C++-managed values
        """
        function force_free() ::Nothing

            for k in keys(_refs)
                delete!(_refs[], k)
                delete!(_ref_counter[], k)
            end

            return nothing;
        end
    end

    module _cppcall

        mutable struct State
            _arguments::Tuple
            _result::Any

            State() = new((), nothing)
        end

        _library_name = "<call jluna::State::initialize before using cppcall>"
        _state = Base.Ref{_cppcall.State}(State())

        """
        Wrapper object for unnamed functions, frees function once object is destroyed
        """
        mutable struct UnnamedFunctionProxy{N, Return_t}

            _id::Symbol
            _call::Function
            _n_args::Int64

            function UnnamedFunctionProxy{N, Return_t}(id::Symbol) where {N, Return_t}

                @assert(-1 <= N <= 4)

                _id = id
                x = new{N, Return_t}(id, function (xs...) Main.cppcall(_id, xs...) end, N)

                finalizer(function (t::UnnamedFunctionProxy{N, Return_t})
                    ccall((:free_function, _cppcall._library_name), Cvoid, (Csize_t,), hash(t._id))
                end, x)

                return x
            end
        end

        # call with signature (1x Any) -> [Any / Nothing]
        function (instance::UnnamedFunctionProxy{0, T})() ::T where T
            return instance._call();
        end

        # call with signature (1x Any) -> [Any / Nothing]
        function (instance::UnnamedFunctionProxy{1, T})(arg1) ::T where T
            return instance._call(arg1);
        end

        # call with signature (2x Any) -> [Any / Nothing]
        function (instance::UnnamedFunctionProxy{2, T})(arg1, arg2) ::T where T
            return instance._call(arg1, arg2);
        end

        # call with signature (3x Any) -> [Any / Nothing]
        function (instance::UnnamedFunctionProxy{3, T})(arg1, arg2, arg3) ::T where T
            return instance._call(arg1, arg2, arg3);
        end

        # call with signature (4x Any) -> [Any / Nothing]
        function (instance::UnnamedFunctionProxy{4, T})(arg1, arg2, arg3, arg4) ::T where T
            return instance._call(arg1, arg2, arg3, arg4);
        end

        # call with other signature
        function (instance::UnnamedFunctionProxy{Int64(-1), T})(args::Vector) ::T where T
            return instance._call(args...);
        end

        # ctor wrapper for jluna
        new_unnamed_function(s::Symbol, N::Integer, T::Type) = return UnnamedFunctionProxy{N, T}(s)

        """
        an exception thrown when trying to invoke cppcall with a function name that
        has not yet been registered via jluna::register_function
        """
        mutable struct UnregisteredFunctionNameException <: Exception

            _function_name::Symbol
        end
        Base.showerror(io::IO, e::UnregisteredFunctionNameException) = print(io, "cppcall.UnregisteredFunctionNameException: no C++ function with name :" * string(e._function_name) * " registered")

        """
        an exception thrown when the number of arguments does not match the number of arguments
        expected by the registered lambda
        """
        mutable struct TupleSizeMismatchException <: Exception

            _function_name::Symbol
            _expected::Int64
            _got::Int64
        end
        Base.showerror(io::IO, e::TupleSizeMismatchException) = print(io, "cppcall.TupleSizeMismatchException: C++ function with name :" * string(e._function_name) * " expects " * string(e._expected) * " arguments but was called with " * string(e._got))

        """
        `set_result(::Any) -> Nothing`

        modify _cppcall state result
        """
        function set_result(x::Any) ::Nothing

            global _cppcall._state[]._result = x
            return nothing
        end

        """
        `get_result() -> Any`

        access _cppcall result
        """
        function get_result() ::Any

            return _cppcall._state[]._result
        end

        """
        `set_arguments(xs...) -> Nothing`

        modify _cppcall state argument tuple
        """
        function set_arguments(xs...) ::Nothing

            global _cppcall._state[]._arguments = xs
            return nothing
        end

        """
        `get_result() -> Tuple`

        access _cppcall state argument tuple
        """
        function get_arguments() ::Tuple

            return _cppcall._state[]._arguments
        end

        function _set_library_name(new_library_name)
          global _library_name
          _library_name = new_library_name
        end
        """
        `verify_library() -> Bool`

        check if c_adapter library is available
        """
        function verify_library() ::Bool

            if isfile(_cppcall._library_name)
                return true
            end

            message = "when trying to initialize jluna.cppcall: "
            message *= "cannot find " * _cppcall._library_name

            println(sprint(Base.showerror, AssertionError(message), backtrace()))
            return false
        end
    end

    # obfuscate internal state to encourage using operator[] sytanx
    struct ProxyInternal

        _fieldnames_in_order::Vector{Symbol}
        _fields::Dict{Symbol, Union{Any, Missing}}
        ProxyInternal() = new(Vector{Symbol}(), Dict{Symbol, Union{Any, Missing}}())
    end

    # proxy as deepcopy of cpp-side usertype object
    struct Proxy

        _typename::Symbol
        _value::ProxyInternal

        Proxy(name::Symbol) = new(name, ProxyInternal())
    end
    export proxy
    new_proxy(name::Symbol) = return Proxy(name)

    function implement(template::Proxy, m::Module = Main) ::Type

        out::Expr = :(mutable struct $(template._typename) end)
        deleteat!(out.args[3].args, 1)

        for name in template._value._fieldnames_in_order
            push!(out.args[3].args, Expr(:(::), name, :($(typeof(template._value._fields[name])))))
        end

        new_call::Expr = Expr(:(=), Expr(:call, template._typename), Expr(:call, :new))

        for name in template._value._fieldnames_in_order
            push!(new_call.args[2].args, template._value._fields[name])
        end

        push!(out.args[3].args, new_call)
        Base.eval(m, out)
        return m.eval(template._typename)
    end
    export implement
end

using Main.jluna;

"""
`setindex!(::Proxy, <:Any, ::Symbol) -> Nothing`

extend base.setindex!
"""
function Base.setindex!(proxy::Main.jluna.Proxy, value, key::Symbol) ::Nothing

    if (!haskey(proxy._value._fields, key))
        push!(proxy._value._fieldnames_in_order, key)
    end

    proxy._value._fields[key] = value
    return nothing
end
export setindex!

"""
`getindex(::Proxy, ::Symbol) -> Any`

extend base.getindex
"""
function Base.getindex(proxy::Main.jluna.Proxy, value, key::Symbol) #::Auto
    return proxy._value._fields[key]
end
export getindex

"""
`cppall(::Symbol, ::Any...) -> Any`

Call a lambda registered via `jluna::State::register_function` using `xs...` as arguments.
After the C++-side function returns, return the resulting object
(or `nothing` if the C++ function returns `void`)

This function is not thread-safe and should not be used in a parallel context
"""
function cppcall(function_name::Symbol, xs...) ::Any

    id = hash(function_name)

    if !ccall((:is_registered, jluna._cppcall._library_name), Bool, (Csize_t,), id)
        throw(jluna._cppcall.UnregisteredFunctionNameException(function_name))
    end

    n_args_expected = ccall((:get_n_args, jluna._cppcall._library_name), Csize_t, (Csize_t,), id)
    if length(xs) != n_args_expected
        throw(jluna._cppcall.TupleSizeMismatchException(function_name, n_args_expected, length(xs)))
    end

    jluna._cppcall.set_arguments(xs...)
    jluna._cppcall.set_result(nothing)

    ccall((:call_function, jluna._cppcall._library_name), Cvoid, (Csize_t,), id)

    return jluna._cppcall.get_result()
end
export cppcall


text:
using QXTools.Circuits
using QXGraphDecompositions
using QXZoo
using QXTns

using LightGraphs

@testset "Test tensor network to graph conversion" begin
    # prepare circuit and network
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=true, no_output=true, decompose=false)

    # convert to graph without inputs or outputs
    g = convert_to_graph(tnc)
    @test LightGraphs.nv(g) == 3 # 3 vertices for gates
    @test LightGraphs.ne(g) == 2 # 2 edges between gates on 2nd qubit

    # convert to graph with both inputs and outputs
    add_input!(tnc)
    add_output!(tnc)
    g = convert_to_graph(tnc)
    @test LightGraphs.nv(g) == 9 # 3 gate, 3 input and 3 output
    @test LightGraphs.ne(g) == 8 # 2 between gates and 6 to inputs and outputs

    # prepare the circuit.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=true, no_output=true, decompose=false)
    add_input!(tnc)
    add_output!(tnc)

    # create the line graph for the circuit.
    g, symbol_map = convert_to_line_graph(tnc.tn)
    @test QXGraphDecompositions.nv(g) == 8 # 2 between gates and 6 to inputs and outputs
    @test QXGraphDecompositions.ne(g) == 13 # 1-qubit gate -> 1 edge, 2-qubit gate -> 6 edges. 1+6+6.

    # Check conversion to linegraph of network's hypergraph.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=false, decompose=true)
    g, symbol_map = convert_to_line_graph(tnc; use_hyperedges=true)
    @test QXGraphDecompositions.nv(g) == 6 # circuit has 6 hyperedges
    @test QXGraphDecompositions.ne(g) == 7
    @test length(symbol_map) == 6
    @test QXTns.counter(length.(values(symbol_map))) == Dict(4=>2, 2=>4)
end

@testset "Test QXGraphDecompositions contraction and slicing" begin
    # prepare the circuit.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=true, decompose=false)

    # test contraction plan
    plan = flow_cutter_contraction_plan(tnc; time=30)
    @test length(plan) == 5

    # test contracting the network
    output = contract_tn!(tnc, plan)
    output = reshape(output, prod(size(output)))
    ref = zeros(8); ref[[1,8]] .= 1/sqrt(2)
    @test all(output .≈ ref)

    # prepare another, un-contracted circuit.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=false, decompose=false)

    # Test contraction scheme function
    edges_to_slice, plan = contraction_scheme(tnc, 3; time = 15, hypergraph=false)
    @test length(vcat(edges_to_slice...)) >= 3 # Should have 3 edges to slice
    @test length(plan) == length(tnc) - 1 - 3 # modified plan should be smaller.

    # Test contraction scheme function
    edges_to_slice, plan = contraction_scheme(tnc, 0; time = 15)
    @test length(edges_to_slice) == 0 # Should have 0 edges to slice
    @test length(plan) == length(tnc) - 1

    # Test contraction plan creation with hypergraph.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=false, decompose=false)
    plan = flow_cutter_contraction_plan(tnc; time=30, hypergraph=true)
    @test length(plan) == 8

    # Test contraction scheme function with hypergraph.
    edges_to_slice, plan = contraction_scheme(tnc, 3; time = 15, hypergraph=true)
    # Should have 5 indices to slice (1 hyperedge with 2 indices)
    @test length(vcat(edges_to_slice...)) >= 5
    @test length(plan) == 3 # modified plan should be smaller.

    # Create a network consisting of one hyperedge which is too large for netcon to
    # contract. Test if fallback method for contracting large hyperedges produces a valid
    # contraction plan.
    circ = Circuit.Circ(1)
    for i in 1:40
        Circuit.add_gatecall!(circ, DefaultGates.z(1))
    end
    tnc = convert_to_tnc(circ, no_input=false, no_output=false, decompose=false)
    plan = flow_cutter_contraction_plan(tnc; time = 30, hypergraph=true)
    @test length(plan) == 41
end

@testset "Test flow cutter contraction" begin
    # A test circuit to test flow cutter on.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=true, decompose=false)

    # test contraction plan
    plan = flow_cutter_contraction_plan(tnc; time=15)
    @test length(plan) == 5

    # test contracting the network
    output = contract_tn!(tnc, plan)
    output = reshape(output, prod(size(output)))
    ref = zeros(8); ref[[1,8]] .= 1/sqrt(2)
    @test all(output .≈ ref)

    # A test circuit to test flow cutter on.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=true, decompose=false)

    # test contraction plan with 0 seconds so the min fill heursitic is used as a fall back
    # when flow cutter can't find a tree decomposition.
    plan = flow_cutter_contraction_plan(tnc; time=0)
    @test length(plan) == 5

    # test contracting the network
    output = contract_tn!(tnc, plan)
    output = reshape(output, prod(size(output)))
    ref = zeros(8); ref[[1,8]] .= 1/sqrt(2)
    @test all(output .≈ ref)
end

@testset "Test min fill contraction" begin
    # A test circuit to test the min fill heuristic on.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=true, decompose=false)

    # test contraction plan
    plan = min_fill_contraction_plan(tnc)
    @test length(plan) == 5

    # test contracting the network
    output = contract_tn!(tnc, plan)
    output = reshape(output, prod(size(output)))
    ref = zeros(8); ref[[1,8]] .= 1/sqrt(2)
    @test all(output .≈ ref)
end

@testset "Test netcon contraction" begin
    # prepare the circuit.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=true, decompose=false)

    # test contraction plan
    plan = netcon(tnc)
    @test length(plan) == 5

    # test contracting the network
    output = contract_tn!(tnc, plan)
    output = reshape(output, prod(size(output)))
    ref = zeros(8); ref[[1,8]] .= 1/sqrt(2)
    @test all(output .≈ ref)
end

@testset "Test elimination order to contraction plan conversion" begin
    # Prepare the circuit.
    circ = create_test_circuit()
    tnc = convert_to_tnc(circ, no_input=false, no_output=false, decompose=true)

    # Create the line graphs for the network's graph and hypergraph.
    lg, symbol_map = convert_to_line_graph(tnc)
    hyper_lg, hyper_symbol_map = convert_to_line_graph(tnc; use_hyperedges=true)

    # Get an elimination order for the line graphs.
    tw, order = QXTools.qxg.min_fill(lg)
    order = [symbol_map[edge] for edge in order]
    hyper_tw, hyper_order = QXTools.qxg.min_fill(hyper_lg)
    hyper_order = [hyper_symbol_map[edge] for edge in hyper_order]

    # Try converting the orders to contraction plans
    plan = QXTools.order_to_contraction_plan(order, tnc.tn)
    hyper_plan = QXTools.order_to_contraction_plan(hyper_order, tnc.tn)

    # Test contraction plan has correct length.
    @test length(plan) == 10
    @test length(hyper_plan) == 10
end

ext: jl


text:
"""
    module ExampleWeaver

Uses the package `Weave` to generate `Documenter`-compatible
markdown files, as well as pre-executed Juypter notebooks, from
normal `.jl` scripts contained in the "examples/" subfolder of
the package.

The resulting markdown and notebook documents will be stored at
"docs/src/generated/" of the package. Thus it is advised to add
that folder to your **toplevel** `.gitignore` file. Do not put a
`.gitignore` file into "docs/src/generated" itself, as that would
affect the build documentation as well.

Note the following additions to the usual `Weave`-compatible
comment syntax that is supported for the ".jl" scripts in the
"examples/" folder:

- Lines that begin with `# md` will only be included
  in the markdown file (with the `# md` prefix removed)

- Lines that begin with `# jp` will only be included
  in the Jupyter notebook (with the `# jp` prefix removed)
"""
module ExampleWeaver
using Weave

export

    listexamples,
    listmarkdown,
    listnotebooks,

    weave_markdown,
    weave_notebook,
    weave

# --------------------------------------------------------------------

const EXAMPLES_DIR  = abspath(joinpath(@__DIR__, "..", "examples"))
const GENERATED_DIR = abspath(joinpath(@__DIR__, "src", "generated"))

function _listfiles(dir, ext, fullpath=false)
    fnames = filter(fname->splitext(fname)[2]==ext, readdir(dir))
    fullpath ? map(fname->joinpath(dir, fname), fnames) : fnames
end

listexamples(fullpath=false) = _listfiles(EXAMPLES_DIR, ".jl", fullpath)
listmarkdown(fullpath=false) = _listfiles(GENERATED_DIR, ".md", fullpath)
listnotebooks(fullpath=false) = _listfiles(GENERATED_DIR, ".ipynb", fullpath)

# --------------------------------------------------------------------

function weave_markdown(scriptname; overwrite=false)
    splitext(scriptname)[2] == ".jl" || return
    name = splitext(scriptname)[1]
    # define all required paths
    scriptpath = joinpath(EXAMPLES_DIR, scriptname)
    processed_scriptpath = joinpath(GENERATED_DIR, name * ".jl")
    jmdpath = joinpath(GENERATED_DIR, name * ".jmd")
    mdpath = joinpath(GENERATED_DIR, name * ".md")
    # if markdown file already exists, only overwrite if requested
    if isfile(mdpath) && !overwrite
        @info "skipping markdown generation for \"$scriptname\" (file already exists)"
        return mdpath
    else
        @info "generating markdown \"$(name*".md")\" for \"$scriptname\""
        mkpath(GENERATED_DIR)
    end
    # load and pre-process script for markdown generation this
    # removes `# jp` and `#jp-only` lines and the `# md` prefix
    str_jl = read(scriptpath, String)
    str_jl = replace(str_jl, r"\n(#jp ).*" => "")
    str_jl = replace(str_jl, r"\n.*(#jl-only)" => "")
    str_jl = replace(str_jl, "\n#md " => "\n")
    write(processed_scriptpath, str_jl)
    # weave the .jl file into a .jmd file
    convert_doc(processed_scriptpath, jmdpath)
    # posprocess the .jmd and save it as .md for documenter
    str_md = read(jmdpath, String)
    str_md = replace(str_md, "```julia" => "```@example $name")
    write(mdpath, str_md)
    # cleanup temporary files
    rm(processed_scriptpath)
    rm(jmdpath)
    # return path to final .md file
    mdpath
end

function weave_notebook(scriptname; overwrite=false, execute=true)
    splitext(scriptname)[2] == ".jl" || return
    name = splitext(scriptname)[1]
    # define all required paths
    scriptpath = joinpath(EXAMPLES_DIR, scriptname)
    processed_scriptpath = joinpath(GENERATED_DIR, name * ".jl")
    jppath = joinpath(GENERATED_DIR, name * ".ipynb")
    # if notebook file already exists, only overwrite if requested
    if isfile(jppath) && !overwrite
        @info "skipping notebook generation for \"$scriptname\" (file already exists)"
        return jppath
    else
        @info "generating notebook \"$(name*".ipynb")\" for \"$scriptname\""
        mkpath(GENERATED_DIR)
    end
    # load and pre-process script for notebook generation this
    # removes `# md` and `#jp-only` lines and the `# jp` prefix
    str_jl = read(scriptpath, String)
    str_jl = replace(str_jl, r"\n(#md ).*" => "")
    str_jl = replace(str_jl, r"\n.*(#jl-only)" => "")
    str_jl = replace(str_jl, "\n#jp " => "\n")
    # additionally we slightly tweak the look of the references
    str_jl = replace(str_jl, r"\[\^(.*)\]:" => s"**\1**:") # references
    str_jl = replace(str_jl, r"\[\^(.*)\]" => s"[\1]") # citations
    write(processed_scriptpath, str_jl)
    # weave the .jl file into a .ipynb file
    convert_doc(processed_scriptpath, jppath)
    # execute notebook
    if execute
        sleep(1)
        @info "executing and overwrite notebook \"$(name*".ipynb")\""
        run(`jupyter nbconvert --ExecutePreprocessor.timeout=-1 --ExecutePreprocessor.allow_errors=True --to notebook --execute $(abspath(jppath)) --output $(name * ".ipynb")`)
    end
    # cleanup temporary files
    rm(processed_scriptpath)
    # return path to final .md file
    jppath
end

# --------------------------------------------------------------------

function weave(scriptname; overwrite=false, execute=true)
    md = weave_markdown(scriptname; overwrite=overwrite)
    jp = weave_notebook(scriptname; overwrite=overwrite, execute=execute)
    md, jp
end

function weave(; kw...)
    mds = String[]; jps = String[]
    for scriptname in listexamples()
	try
            md, jp = weave(scriptname; kw...)
            push!(mds, md)
            push!(jps, jp)
	catch err
	    println("error running scriptname:\n$err")
	end
    end
    mds, jps
end

end # module


text:
function meshpartition2(dmd,t,f,t2t,bcm,dim,elemtype,porder,nproc,metis)

print("run elementpartition...\n");
dmd = elementpartition2(dmd,t,t2t,nproc,metis);

print("run facepartition...\n");
dmd = facepartition2(dmd,t,f,bcm,dim,elemtype,porder,nproc);

return dmd;

end

max_stars_repo_name: glwagner/Exasim

max_issues_repo_name: glwagner/Exasim

max_forks_repo_name: glwagner/Exasim


text:
function losses(r::NeRFRenderer, batch; kwargs...)
    predictions = render_rays(r, batch[:, 1:2, :]; kwargs...)
    targets = batch[:, 3, :]
    coarse_loss = Flux.Losses.mse(predictions.coarse, targets)
    fine_loss = Flux.Losses.mse(predictions.fine, targets)
    total_loss = coarse_loss + fine_loss
    return (total=total_loss, coarse=coarse_loss, fine=fine_loss)
end


text:
# download and extract data if data/ directory does not exist
if !isfile("enwik8")
    enwik8 = download("http://www.mattmahoney.net/dc/enwik8.zip");
    run(`unzip $enwik8`)
    rm(enwik8)
end

if isfile("train.txt")
    println("Tokenized enwik8 already exists - skipping processing")
else
    f = open("enwik8", "r");
    data = Array{UInt8, 1}()
    readbytes!(f, data, typemax(Int));
    println("Length of enwik8: ", length(data));
    num_test_chars = 5000000;

    train_data = data[1:length(data) - (2 * num_test_chars)];
    valid_data = data[length(train_data) + 1: length(train_data) + num_test_chars];
    test_data = data[length(train_data) + num_test_chars + 1:end];

    for (fn, part) in [("train.txt", train_data), ("valid.txt", valid_data), ("test.txt", test_data)]
        println(fn, " will have ", length(part), " bytes")
        println("- Writing ", fn, " ...")
        open(fn * ".raw", "w") do f
            write(f, part);
        end;
        part_str = join([ ifelse((UInt8('\n') != c), string(c), "\n") for c in part ], " ");
        open(fn, "w") do f
            write(f, part_str);
        end;
    end;
end;


text:
# Set up model
model = Body{UnitQuaternion{Float64}}()
@test size(model) == (13,6)
x,u = rand(model)
z = KnotPoint(x,u,0.01)
q = orientation(model, x)
ir,iq,iv,iω,iu = RobotDynamics.gen_inds(model)

# Test analytical continuous-time Jacobian
RobotDynamics.velocity_frame(::Body) = :world
function AD_jacobian!(∇f::Matrix, model::AbstractModel, z::AbstractKnotPoint)
    ix, iu = z._x, z._u
	t = z.t
    f_aug(z) = dynamics(model, z[ix], z[iu], t)
    s = z.z
	ForwardDiff.jacobian!(∇f, f_aug, s)
end
F = zeros(13,19)
AD_jacobian!(F, model, z)

F2 = zero(F)
jacobian!(F2, model, z)
@test F2 ≈ F

ω = angular_velocity(model, x)
ForwardDiff.jacobian(q->Rotations.kinematics(UnitQuaternion(q,false), ω), Rotations.params(q))
Rotations.kinematics(q, ω)

D = RobotDynamics.DynamicsJacobian(13,6)
jacobian!(D, model, z)
@test D ≈ F

# @btime AD_jacobian!($F, $model, $z)
# @btime jacobian!($F, $model, $z)

# Test in body frame
RobotDynamics.velocity_frame(::Body) = :body
F3 = zeros(13,19)
AD_jacobian!(F3, model, z)
@test !(F3 ≈ F)
jacobian!(D, model, z)
@test D ≈ F3

# @btime AD_jacobian!($F, $model, $z)
# @btime jacobian!($F, $model, $z)

discrete_jacobian!(RK3, F2, model, z)
tmp = [RobotDynamics.DynamicsJacobian(13,6) for k = 1:3]
jacobian!(RK3, D, model, z, tmp)
@test F2 ≈ D

RobotDynamics.velocity_frame(::Body) = :world
discrete_jacobian!(RK3, F, model, z)
tmp = [RobotDynamics.DynamicsJacobian(13,6) for k = 1:3]
jacobian!(RK3, D, model, z, tmp)
@test F ≈ D

@test !(F ≈ F2)

# @btime discrete_jacobian!($RK3, $F, $model, $z)
# @btime jacobian!($RK3, $D, $model, $z, $tmp)

# Test state error functions
x0 = rand(model)[1]
dx = RobotDynamics.state_diff(model, x, x0)
@test dx[ir] ≈ (x - x0)[ir]
@test dx[iv .- 1] ≈ (x - x0)[iv]
@test dx[iω .- 1] ≈ (x - x0)[iω]
@test dx[4:6] ≈ orientation(model, x) ⊖ orientation(model, x0)

G0 = Rotations.∇differential(q)
G = zeros(state_dim(model), RobotDynamics.state_diff_size(model))
@test size(G) == (13,12)
RobotDynamics.state_diff_jacobian!(G, model, z)
@test G ≈ cat(I(3), G0, I(6), dims=(1,2))

b = @SVector rand(13)
∇G0 = Rotations.∇²differential(q, b[SA[4,5,6,7]])
∇G = zeros(RobotDynamics.state_diff_size(model), RobotDynamics.state_diff_size(model))
@test size(∇G) == (12,12)
RobotDynamics.∇²differential!(∇G, model, x, b)
@test ∇G ≈ cat(zeros(3,3), ∇G0, zeros(6,6), dims=(1,2))

# @btime RobotDynamics.state_diff_jacobian!($G, $model, $z)
# @btime RobotDynamics.∇²differential!($∇G, $model, $x, $b)


text:
s1 = SnakeCaseLower("my_naming_convention")
s2 = SnakeCaseUpper("VK_STRUCTURE_TYPE_PIPELINE")
c1 = CamelCaseLower("vkCreateCommandBuffer")
c2 = CamelCaseUpper("VkInstanceCreateInfo")

s2_novk = SnakeCaseUpper("STRUCTURE_TYPE_PIPELINE")
c1_novk = CamelCaseLower("createCommandBuffer")
c2_novk = CamelCaseUpper("InstanceCreateInfo")

const_s1 = SnakeCaseLower("this_is_some_message")
const_s2 = SnakeCaseUpper("THIS_IS_SOME_MESSAGE")
const_c1  = CamelCaseLower("thisIsSomeMessage")
const_c2  = CamelCaseUpper("ThisIsSomeMessage")

longstr = SnakeCaseLower("my_snake_case_with_many_words")

camel_split_l_c1 = CamelCaseLower("myCamel2")
camel_split_l_c2 = CamelCaseLower("myCamel2KHRExt")
camel_split_l_c3 = CamelCaseLower("myCamel2Ext4")

camel_split_u_c1 = CamelCaseUpper("MyCamel2")
camel_split_u_c2 = CamelCaseUpper("MyCamel2KHRExt")
camel_split_u_c3 = CamelCaseUpper("MyCamel2Ext4")

@testset "Naming conventions" begin
    include("naming_conventions/lib.jl")
    include("naming_conventions/vulkan.jl")
end


text:
using Checkpointing
using ReverseDiff
using Zygote
using ForwardDiff
using Enzyme
using Test

function f(x)
    return [(x[1]*(x[2]-2.0))^2, (x[1]-1.0)^2*x[2]^2]
end
x = [2.0,6.0]

J_Re = Checkpointing.jacobian(f, x, ReverseDiffADTool())
J_Zy = Checkpointing.jacobian(f, x, ZygoteADTool())
J_Fo = Checkpointing.jacobian(f, x, ForwardDiffADTool())
J_En = Checkpointing.jacobian(f, x, EnzymeADTool())
# J_Di = Checkpointing.jacobian(f, x, DiffractorADTool())

@test J_Re ≈ J_Zy ≈ J_Fo ≈ J_En


text:
######################################################
#
# Multifractal Model of Asset Returns (MMAR)
#
# (in the special case of H=1/2)
#
# Date:   2014-11-13
# Author: Hessel Tuinhof
#
# Function to simulate a MMAR process using a canonical
# binomomial cascade.
#
# Reference:
# - Riedi, Rudolf H.: Multifractal Processes
#   (http://www.stat.rice.edu/~riedi/Publ/PDF/MP.pdf)
#
# Required packages: 	Distributions
# Required functions: 	BinomialCanonical.jl
#
# Input:
#   m0 ... multiplier, float in (0,1)
#   k ... number of cascades, positive integer
#
# Output:
#   mmar ... simulated log returns
#
# Examples:
#   # Simulation of log returns
#   plot(MMAR(0.6, 10))
#
#   # Simulation of log price
#   plot(cumsum(MMAR(0.6, 10)))
#
######################################################

using Distributions
require("/home/htuinhof/documents/Uni/Masterthesis/src/cascades/BinomialCanonical.jl")

function MMAR(m0, k)
	T = 2^k
	N = linspace(0, 1, T)
	mmar = zeros(T)
	mu = BinomialCanonical(m0, k)
	mu_normalized = cumsum(mu) / cumsum(mu)[end]
	mmar[1] = rand(Normal(0, mu_normalized[1]))
	for i = 0:T-2
		mu_normalized[T-i] = mu_normalized[T-i] - mu_normalized[T-i-1]
		mmar[T-i] = rand(Normal(0, mu_normalized[T-i]))
	end
	return mmar
end

max_stars_repo_name: hesseltuinhof/masterthesis

max_issues_repo_name: hesseltuinhof/masterthesis

max_forks_repo_name: hesseltuinhof/masterthesis


text:
@testset "Envelope" begin
    @testset "Constructors" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        L1 = Line(x1,x1)

        en = Envelope(L1)
        @test isa(en,Envelope)
        @test length(en.L)==0
        @test length(en.env)==n
        @test length(gets(en))==0
        @test length(getr(en))==1
        @test length(getr(en)[1])==0
        @test en.env_set
        @test eltype(getr(en)[1])==Point{Float64}
        @test en.vbound == 0.0

        en = Envelope([L1,L1])
        @test isa(en,Envelope)
        @test length(en.L)==2
        @test length(en.env)==1
        @test length(gets(en))==0
        @test length(getr(en))==1
        @test length(getr(en)[1])==0
        @test eltype(getr(en)[1])==Point{Float64}
        @test isa(getr(en)[1],Vector{Point{Float64}})
        @test !en.env_set

        en = Envelope(1)
        @test isa(en,Envelope{Int})
        @test !en.env_set
        @test length(en.env)==1
    end
end

@testset "Testing Envelopes over 2 lines" begin
    @testset "upper_env test 1" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        x2 = collect(linspace(-1,9,n))
        L1 = Line(x1,x1)
        L2 = Line(x2,ones(n)*5)
        e = Envelope([L1,L2])
        upper_env!(e)
        @test issorted(getx(e))
        @test getx(e) == sort(unique(vcat(x1,x2)))
        @test gets(e)[1].x ≈ 5.0
        @test gets(e)[1].y ≈ 5.0
    end
    @testset "upper_env test 2" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        L1 = Line(x1,x1)
        L2 = Line(x1,ones(n)*5)
        e = Envelope([L1,L2])
        upper_env!(e)
        @test getx(e) == x1
        @test issorted(getx(e))
        @test gets(e)[1].x ≈ 5.0
        @test gets(e)[1].y ≈ 5.0
    end
    @testset "upper_env test 3" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        L1 = Line(x1,x1)
        L2 = Line(x1,5.0+0.3*x1)
        e = Envelope([L1,L2])
        upper_env!(e)
        @test getx(e) == x1
        @test issorted(getx(e))
        @test gets(e)[1].x ≈ 5.0/0.7
        @test gets(e)[1].y ≈ 5.0/0.7
    end
    @testset "upper_env test 4" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        x2 = collect(linspace(-1,9,n))
        L1 = Line(x1,x1)
        L2 = Line(x2,5.0+0.3*x2)
        e = Envelope([L1,L2])
        upper_env!(e)
        @test issorted(getx(e))
        @test getx(e) == sort(unique(vcat(x1,x2)))
        @test gets(e)[1].x ≈ 5.0/0.7
        @test gets(e)[1].y ≈ 5.0/0.7
    end
    @testset "upper_env test: decreasing " begin
        n = 15
        x1 = collect(linspace(0,10,n))
        x2 = collect(linspace(-1,9,n))
        L1 = Line(x1,x1[end:-1:1])
        L2 = Line(x2,ones(n)*5)
        e = Envelope([L1,L2])
        upper_env!(e)
        @test issorted(getx(e))
        @test getx(e) == sort(unique(vcat(x1,x2)))
        @test gets(e)[1].x ≈ 5.0
        @test gets(e)[1].y ≈ 5.0
    end
end
@testset "Testing Envelopes over more lines" begin
    @testset "upper_env test 1" begin
        n = 15
        x1 = collect(linspace(0,10,n))
        x2 = collect(linspace(-1,9,n))
        x3 = collect(linspace(-0.1,10,n))
        L1 = Line(x1,x1)
        L2 = Line(x2,ones(n)*5)
        L3 = Line(x3,(x3.^2)/8)
        e = Envelope([L1,L2,L3])
        upper_env!(e)
        @test issorted(getx(e))
        @test getx(e) == sort(unique(vcat(x1,x2,x3,gets(e)[2].x)))
        @test length(gets(e)) == 2
        @test gets(e)[1].x ≈ 5.0
        @test gets(e)[1].y ≈ 5.0
        @test isapprox(gets(e)[2].x,8.0,rtol=0.01)
        @test isapprox(gets(e)[2].y,8.0,rtol=0.01)
    end
    @testset "upper_env test 2" begin
        n = 10
        x1 = collect(linspace(1,10,n))
        L1 = Line(x1,x1.*(x1.<6))
        L2 = Line(x1,ones(n)*5.*(x1.>4))
        L3 = Line(x1,x1-3)
        e = Envelope([L1,L2,L3])
        upper_env!(e)
        @test issorted(getx(e))
        @test getx(e) == sort(unique(vcat(x1,[gets(e)[i].x for i in 1:2])))
        @test length(gets(e)) == 2
        @test gets(e)[1].x == 5.0
        @test gets(e)[1].y == 5.0
        @test gets(e)[2].x == 8.0
        @test gets(e)[2].y == 5.0
    end
end

@testset "splitLine" begin
    @testset "test 1: simple" begin
        x = [1,2,3,1.5,2.1,2.9]
        y = [1,1.5,1.7,1.2,1.8,2.1]
        L1 = Line(x,y)
        e = splitLine(L1)
        @test isa(e,Envelope)
        @test length(getx(e))==1
        @test length(gety(e))==1
        @test length(gets(e))==0
        @test length(getr(e))==1
        @test length(e.L) == 3-1
        @test eltype(e.L)== Line{Float64}

        @test extrema(e.L[1].x) == (1.0,3.0)
        @test extrema(e.L[2].x) == (1.5,2.9)
    end
    @testset "test 2: zig-zag" begin
        x = [1,2,3,4,5.0,0,2,3,4,5]
        y = [2,1,2,1,2.0,1,2,1,2,1]
        L = Line(x,y)
        e = splitLine(L)
        @test isa(e,Envelope)
        @test length(getx(e))==1
        @test length(gety(e))==1
        @test length(gets(e))==0
        @test length(getr(e))==1
        @test length(e.L) == 3-1

        upper_env!(e)
        @test issorted(getx(e))
        @test gety(e) == [3,2,3-4/3,2,1.5,2,1.5,2,1.5,2]
        @test getx(e) == [0,1,4/3,collect(2:0.5:5)...]
        @test gets(e)[1].x == 4/3

    end
    @testset "4 lines - fails!" begin
        f1(x) = ones(length(x))
        f2(x) = 0.5x
        f3(x) = x-2
        f4(x) = 2x-8
        x1 = collect(linspace(0.9,1.9,14))
        x2 = collect(linspace(1,7,19))
        x3 = collect(linspace(1,7,15))
        x4 = collect(linspace(1,8,25))
        X = [x1...,x2...,x3...,x4...]
        L = Line([x1...,x2...,x3...,x4...],vcat(f1(x1),f2(x2),f3(x3),f4(x4)))
        en = splitLine(L)

        @test isa(en,Envelope)
        @test length(getx(en))==1
        @test length(gety(en))==1
        @test length(gets(en))==0
        @test length(getr(en))==1
        @test length(en.L) == 7-3

        upper_env!(en)
        @test issorted(getx(en))
        @test gets(en) == [Point(2.0,1.0,i=1),Point(4.0,2.0,i=2),Point(6.0,4.0,i=3)]
        xx = unique(sort(vcat(X,vcat([gets(en)[i].x for i in 1:3]...))))
        @test getx(en) == xx
        yy = reshape(vcat([[f1(ix) f2(ix) f3(ix) f4(ix)] for ix in xx]...),length(xx),4)
        @test isapprox(gety(en)[:],findmax(yy,2)[1][:])

    end

    @testset "4 lines - passes!" begin
        f1(x) = ones(length(x))
        f2(x) = 0.5x
        f3(x) = x-2
        f4(x) = 2x-8
        x1 = collect(linspace(0.9,2.1,14))
        x2 = collect(linspace(1,7,19))
        x3 = collect(linspace(1,7,15))
        x4 = collect(linspace(1,8,25))
        X = [x1...,x2...,x3...,x4...]
        L = Line([x1...,x2...,x3...,x4...],vcat(f1(x1),f2(x2),f3(x3),f4(x4)))
        en = splitLine(L)

        @test isa(en,Envelope)
        @test length(getx(en))==1
        @test length(gety(en))==1
        @test length(gets(en))==0
        @test length(getr(en))==1
        @test length(en.L) == 7-3

        upper_env!(en)
        @test issorted(getx(en))
        @test gets(en) == [Point(2.0,1.0,i=1),Point(4.0,2.0,i=2),Point(6.0,4.0,i=3)]
        xx = unique(sort(vcat(X,vcat([gets(en)[i].x for i in 1:3]...))))
        @test getx(en) == xx
        yy = reshape(vcat([[f1(ix) f2(ix) f3(ix) f4(ix)] for ix in xx]...),length(xx),4)
        @test isapprox(gety(en)[:],findmax(yy,2)[1][:])

    end

end

max_stars_repo_name: floswald/MultiLine

max_issues_repo_name: floswald/MultiLine

max_forks_repo_name: floswald/MultiLine