text:
#
# libbson
#

function bson_oid_init(oid_ref::Ref{BSONObjectId}, context::Ptr{Cvoid})
    ccall((:bson_oid_init, libbson), Cvoid, (Ref{BSONObjectId}, Ptr{Cvoid}), oid_ref, context)
end

function bson_oid_init_from_string(oid_ref::Ref{BSONObjectId}, oid_string::AbstractString)
    ccall((:bson_oid_init_from_string, libbson), Cvoid,
          (Ref{BSONObjectId}, Cstring),
          oid_ref, oid_string)
end

function bson_oid_to_string(oid::BSONObjectId) :: String
    buffer_len = 25
    buffer = zeros(UInt8, buffer_len)

    ccall((:bson_oid_to_string, libbson), Cvoid,
          (Ref{BSONObjectId}, Ref{UInt8}),
          Ref(oid), Ref(buffer, 1))

    @assert buffer[end] == 0
    return String(buffer[1:end-1])
end

function bson_oid_compare(oid1::BSONObjectId, oid2::BSONObjectId)
    ccall((:bson_oid_compare, libbson), Cint,
          (Ref{BSONObjectId}, Ref{BSONObjectId}),
          Ref(oid1), Ref(oid2))
end

function bson_oid_get_time_t(oid::BSONObjectId)
    ccall((:bson_oid_get_time_t, libbson), Clong, (Ref{BSONObjectId},), Ref(oid))
end

function bson_oid_is_valid(str::String)
    str_length = Csize_t(length(str))
    ccall((:bson_oid_is_valid, libbson), Bool, (Cstring, Csize_t), str, str_length)
end

function bson_append_oid(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::BSONObjectId)
    ccall((:bson_append_oid, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Ref{BSONObjectId}),
          bson_document, key, key_length, value)
end

function bson_append_int32(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Int32)
    ccall((:bson_append_int32, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Cint),
          bson_document, key, key_length, value)
end

function bson_append_int64(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Int64)
    ccall((:bson_append_int64, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Clonglong),
          bson_document, key, key_length, value)
end

function bson_append_utf8(bson_document::Ptr{Cvoid},
                          key::AbstractString, key_length::Int, value::AbstractString, len::Int)
    ccall((:bson_append_utf8, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Cstring, Cint),
          bson_document, key, key_length, value, len)
end

function bson_append_bool(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Bool)
    ccall((:bson_append_bool, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Bool),
          bson_document, key, key_length, value)
end

function bson_append_double(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Float64)
    ccall((:bson_append_double, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Cdouble),
          bson_document, key, key_length, value)
end

function bson_append_decimal128(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Dec128)
    ccall((:bson_append_decimal128, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Ref{Dec128}),
          bson_document, key, key_length, Ref(value))
end

function bson_append_date_time(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Int64)
    ccall((:bson_append_date_time, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Clonglong),
          bson_document, key, key_length, value)
end

function bson_append_document(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Ptr{Cvoid})
    ccall((:bson_append_document, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Ptr{Cvoid}),
          bson_document, key, key_length, value)
end

function bson_append_array(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::Ptr{Cvoid})
    ccall((:bson_append_array, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Ptr{Cvoid}),
          bson_document, key, key_length, value)
end

function bson_append_binary(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int,
                            subtype::BSONSubType, value::Vector{UInt8}, val_length::UInt32)

    ccall((:bson_append_binary, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, BSONSubType, Ptr{Cvoid}, Culong),
          bson_document, key, key_length, subtype, value, val_length)
end

function bson_append_code(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::String)
    ccall((:bson_append_code, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint, Cstring),
          bson_document, key, key_length, value)
end

function bson_append_null(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int)
    ccall((:bson_append_null, libbson), Bool,
          (Ptr{Cvoid}, Cstring, Cint),
          bson_document, key, key_length)
end

function bson_new()
    ccall((:bson_new, libbson), Ptr{Cvoid}, ())
end

function bson_new_from_json(data::String, len::Int=-1)
    ccall((:bson_new_from_json, libbson), Ptr{Cvoid},
          (Ptr{UInt8}, Cssize_t, Ptr{Cvoid}),
          data, len, C_NULL)
end

function bson_destroy(bson_document::Ptr{Cvoid})
    ccall((:bson_destroy, libbson), Cvoid, (Ptr{Cvoid},), bson_document)
end

function bson_as_canonical_extended_json(bson_document::Ptr{Cvoid})
    ccall((:bson_as_canonical_extended_json, libbson), Cstring,
          (Ptr{Cvoid}, Ptr{Cvoid}),
          bson_document, C_NULL)
end

function bson_as_relaxed_extended_json(bson_document::Ptr{Cvoid})
    ccall((:bson_as_relaxed_extended_json, libbson), Cstring,
          (Ptr{Cvoid}, Ptr{Cvoid}),
          bson_document, C_NULL)
end

function bson_copy(bson_document::Ptr{Cvoid})
    ccall((:bson_copy, libbson), Ptr{Cvoid}, (Ptr{Cvoid},), bson_document)
end

function bson_has_field(bson_document::Ptr{Cvoid}, key::AbstractString)
    ccall((:bson_has_field, libbson), Bool, (Ptr{Cvoid}, Cstring), bson_document, key)
end

function bson_count_keys(bson_document::Ptr{Cvoid})
    ccall((:bson_count_keys, libbson), UInt32, (Ptr{Cvoid},), bson_document)
end

function bson_iter_init(iter_ref::Ref{BSONIter}, bson_document::Ptr{Cvoid})
    ccall((:bson_iter_init, libbson), Bool, (Ref{BSONIter}, Ptr{Cvoid}), iter_ref, bson_document)
end

function bson_iter_next(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_next, libbson), Bool, (Ref{BSONIter},), iter_ref)
end

function bson_iter_find(iter_ref::Ref{BSONIter}, key::AbstractString)
    ccall((:bson_iter_find, libbson), Bool, (Ref{BSONIter}, Cstring), iter_ref, key)
end

function bson_iter_init_find(iter_ref::Ref{BSONIter}, bson_document::Ptr{Cvoid}, key::AbstractString)
    ccall((:bson_iter_init_find, libbson), Bool,
          (Ref{BSONIter}, Ptr{Cvoid}, Cstring),
          iter_ref, bson_document, key)
end

function bson_iter_type(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_type, libbson), BSONType, (Ref{BSONIter},), iter_ref)
end

function bson_iter_key(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_key, libbson), Cstring, (Ref{BSONIter},), iter_ref)
end

function bson_iter_int32(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_int32, libbson), Int32, (Ref{BSONIter},), iter_ref)
end

function bson_iter_int64(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_int64, libbson), Int64, (Ref{BSONIter},), iter_ref)
end

function bson_iter_utf8(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_utf8, libbson), Cstring, (Ref{BSONIter}, Ptr{Cvoid}), iter_ref, C_NULL)
end

function bson_iter_bool(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_bool, libbson), Bool, (Ref{BSONIter},), iter_ref)
end

function bson_iter_double(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_double, libbson), Float64, (Ref{BSONIter},), iter_ref)
end

function bson_iter_decimal128(iter_ref::Ref{BSONIter})
    x = Ref{Dec128}()
    success = ccall((:bson_iter_decimal128, libbson), Bool, (Ref{BSONIter}, Ref{Dec128}), iter_ref, x)
    success || error("Unexpected data type when iterating decimal128")
    x[]
end

function bson_iter_oid(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_oid, libbson), Ptr{BSONObjectId}, (Ref{BSONIter},), iter_ref)
end

function bson_iter_date_time(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_date_time, libbson), Clonglong, (Ref{BSONIter},), iter_ref)
end

function bson_iter_timestamp(iter_ref::Ref{BSONIter}) :: BSONTimestamp
    timestamp = Ref{UInt32}()
    increment = Ref{UInt32}()

    ccall(
        (:bson_iter_timestamp, libbson),
        Cvoid,
        (Ref{BSONIter}, Ref{UInt32}, Ref{UInt32}),
        iter_ref, timestamp, increment
    )

    return BSONTimestamp(timestamp[], increment[])
end

function bson_iter_recurse(iter_ref::Ref{BSONIter}, child_iter_ref::Ref{BSONIter})
    ccall((:bson_iter_recurse, libbson), Bool, (Ref{BSONIter}, Ref{BSONIter}), iter_ref, child_iter_ref)
end

function bson_iter_code(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_code, libbson), Cstring, (Ref{BSONIter}, Ptr{UInt32}), iter_ref, C_NULL)
end

function bson_iter_binary(iter_ref::Ref{BSONIter}, length_ref::Ref{UInt32}, buffer_ref::Ref{Ptr{UInt8}})
    bsonsubtype_ref = Ref(BSON_SUBTYPE_BINARY)
    bson_iter_binary(iter_ref, bsonsubtype_ref, length_ref, buffer_ref)
end

function bson_iter_binary(iter_ref::Ref{BSONIter}, bsonsubtype_ref::Ref{BSONSubType}, length_ref::Ref{UInt32}, buffer_ref::Ref{Ptr{UInt8}})
    ccall((:bson_iter_binary, libbson), Cvoid,
          (Ref{BSONIter}, Ref{BSONSubType}, Ref{UInt32}, Ref{Ptr{UInt8}}),
          iter_ref, bsonsubtype_ref, length_ref, buffer_ref)
end

function bson_iter_value(iter_ref::Ref{BSONIter})
    ccall((:bson_iter_value, libbson), Ptr{Cvoid}, (Ref{BSONIter},), iter_ref)
end

function bson_free(mem::Ptr{Cvoid})
    ccall((:bson_free, libbson), Cvoid, (Ptr{Cvoid},), mem)
end

function bson_reader_destroy(bson_reader_handle::Ptr{Cvoid})
    ccall((:bson_reader_destroy, libbson), Cvoid, (Ptr{Cvoid},), bson_reader_handle)
end

function bson_reader_new_from_file(filepath::AbstractString, bson_error_ref::Ref{BSONError})
    ccall((:bson_reader_new_from_file, libbson), Ptr{Cvoid},
          (Cstring, Ref{BSONError}),
          filepath, bson_error_ref)
end

function bson_reader_new_from_data(data::Ptr{UInt8}, data_length::Integer)
    ccall((:bson_reader_new_from_data, libbson), Ptr{Cvoid}, (Ptr{UInt8}, Csize_t), data, data_length)
end

function bson_reader_read(bson_reader_handle::Ptr{Cvoid}, reached_eof_ref::Ref{Bool})
    ccall((:bson_reader_read, libbson), Ptr{Cvoid},
          (Ptr{Cvoid}, Ref{Bool}),
          bson_reader_handle, reached_eof_ref)
end

function bson_writer_destroy(bson_writer_handle::Ptr{Cvoid})
    ccall((:bson_writer_destroy, libbson), Cvoid, (Ptr{Cvoid},), bson_writer_handle)
end

function bson_writer_begin(bson_writer_handle::Ptr{Cvoid}, bson_document_handle_ref::Ref{Ptr{Cvoid}})
    ccall((:bson_writer_begin, libbson), Bool,
          (Ptr{Cvoid}, Ref{Ptr{Cvoid}}),
          bson_writer_handle, bson_document_handle_ref)
end

function bson_writer_end(bson_writer_handle::Ptr{Cvoid})
    ccall((:bson_writer_end, libbson), Cvoid, (Ptr{Cvoid},), bson_writer_handle)
end

function bson_writer_new(buffer_handle_ref::Ref{Ptr{UInt8}}, buffer_length_ref::Ref{Csize_t},
                         offset::Csize_t, realloc_func::Ptr{Cvoid}, realloc_func_ctx::Ptr{Cvoid})

    ccall((:bson_writer_new, libbson), Ptr{Cvoid},
         (Ref{Ptr{UInt8}}, Ref{Csize_t}, Csize_t, Ptr{Cvoid}, Ptr{Cvoid}),
         buffer_handle_ref, buffer_length_ref, offset, realloc_func, realloc_func_ctx)
end

function bson_writer_get_length(bson_writer_handle::Ptr{Cvoid})
    ccall((:bson_writer_get_length, libbson), Csize_t, (Ptr{Cvoid},), bson_writer_handle)
end

function bson_copy_to(src_bson_handle::Ptr{Cvoid}, dst_bson_handle::Ptr{Cvoid})
    ccall((:bson_copy_to, libbson), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), src_bson_handle, dst_bson_handle)
end

function bson_copy_to_noinit(src_bson_handle::Ptr{Cvoid}, dst_bson_handle::Ptr{Cvoid})

    # This hack will create a key that is not present in src_bson
    # since bson_copy_to_excluding_noinit requires at least one `exclude` arg.
    # This is needed because there's no `bson_copy_to_noinit` in libbson.
    function exclude_key(bson_handle::Ptr{Cvoid})
        new_exclude_key() = "___" * string(Int(rand(UInt16)))

        exclude = new_exclude_key()
        bson = BSON(bson_handle, enable_finalizer=false) # disable finalizer

        while haskey(bson, exclude)
            exclude = new_exclude_key()
        end

        return exclude
    end

    exclude = exclude_key(src_bson_handle)

    bson_copy_to_excluding_noinit(src_bson_handle, dst_bson_handle, exclude)
end

function bson_copy_to_excluding_noinit(src_bson_handle::Ptr{Cvoid}, dst_bson_handle::Ptr{Cvoid},
                                       exclude::AbstractString)
    @ccall libbson.bson_copy_to_excluding_noinit(src_bson_handle::Ptr{Cvoid},
                                                 dst_bson_handle::Ptr{Cvoid},
                                                 exclude::Cstring;
                                                 C_NULL::Cstring)::Cvoid
end

function bson_json_reader_new_from_file(filepath::AbstractString, bson_error_ref::Ref{BSONError})
    ccall((:bson_json_reader_new_from_file, libbson), Ptr{Cvoid},
          (Cstring, Ref{BSONError}),
          filepath, bson_error_ref
        )
end

function bson_json_reader_destroy(reader_handle::Ptr{Cvoid})
    ccall((:bson_json_reader_destroy, libbson), Cvoid,
          (Ptr{Cvoid},),
          reader_handle)
end

function bson_json_reader_read(reader_handle::Ptr{Cvoid}, bson_handle::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})
    ccall((:bson_json_reader_read, libbson), Cint,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          reader_handle, bson_handle, bson_error_ref)
end

#
# libmongoc
#

function mongoc_init()
    ccall((:mongoc_init, libmongoc), Cvoid, ())
end

function mongoc_cleanup()
    ccall((:mongoc_cleanup, libmongoc), Cvoid, ())
end

function mongoc_uri_new_with_error(uri_string::String, bson_error_ref::Ref{BSONError})
    ccall((:mongoc_uri_new_with_error, libmongoc), Ptr{Cvoid},
          (Cstring, Ref{BSONError}),
          uri_string, bson_error_ref)
end

function mongoc_uri_destroy(uri_handle::Ptr{Cvoid})
    ccall((:mongoc_uri_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), uri_handle)
end

function mongoc_client_new_from_uri(uri_handle::Ptr{Cvoid})
    ccall((:mongoc_client_new_from_uri, libmongoc), Ptr{Cvoid}, (Ptr{Cvoid},), uri_handle)
end

function mongoc_client_destroy(client_handle::Ptr{Cvoid})
    ccall((:mongoc_client_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), client_handle)
end

function mongoc_client_set_appname(client_handle::Ptr{Cvoid}, appname::String)
    ccall((:mongoc_client_set_appname, libmongoc), Bool, (Ptr{Cvoid}, Cstring), client_handle, appname)
end

function mongoc_client_get_database(client_handle::Ptr{Cvoid}, db_name::String)
    ccall((:mongoc_client_get_database, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Cstring),
          client_handle, db_name)
end

function mongoc_client_find_databases_with_opts(client_handle::Ptr{Cvoid}, bson_opts::Ptr{Cvoid})
    ccall((:mongoc_client_find_databases_with_opts, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}),
          client_handle, bson_opts)
end

function mongoc_client_start_session(client_handle::Ptr{Cvoid}, session_options_handle::Ptr{Cvoid},
                                     bson_error_ref::Ref{BSONError})

    ccall((:mongoc_client_start_session, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          client_handle, session_options_handle, bson_error_ref)
end

function mongoc_client_session_destroy(session_handle::Ptr{Cvoid})
    ccall((:mongoc_client_session_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), session_handle)
end

function mongoc_client_session_append(session_handle::Ptr{Cvoid}, bson_opts::Ptr{Cvoid},
                                      bson_error_ref::Ref{BSONError})

    ccall((:mongoc_client_session_append, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          session_handle, bson_opts, bson_error_ref)
end

function mongoc_client_session_start_transaction(session_handle::Ptr{Cvoid},
                                                 transaction_options_handle::Ptr{Cvoid},
                                                 bson_error_ref::Ref{BSONError})

    ccall((:mongoc_client_session_start_transaction, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          session_handle, transaction_options_handle, bson_error_ref)
end

function mongoc_client_session_abort_transaction(session_handle::Ptr{Cvoid},
                                                 bson_error_ref::Ref{BSONError})

    ccall((:mongoc_client_session_abort_transaction, libmongoc), Bool,
          (Ptr{Cvoid}, Ref{BSONError}),
          session_handle, bson_error_ref)
end

function mongoc_client_session_commit_transaction(session_handle::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                                  bson_error_ref::Ref{BSONError})

    ccall((:mongoc_client_session_commit_transaction, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          session_handle, bson_reply, bson_error_ref)
end

function mongoc_client_session_in_transaction(session_handle::Ptr{Cvoid})
    ccall((:mongoc_client_session_in_transaction, libmongoc), Bool, (Ptr{Cvoid},), session_handle)
end

function mongoc_client_pool_new(uri_handle::Ptr{Cvoid})
    ccall((:mongoc_client_pool_new, libmongoc), Ptr{Cvoid}, (Ptr{Cvoid},), uri_handle)
end

function mongoc_client_pool_destroy(client_pool_handle::Ptr{Cvoid})
    ccall((:mongoc_client_pool_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), client_pool_handle)
end

function mongoc_client_pool_pop(client_pool_handle::Ptr{Cvoid})
    ccall((:mongoc_client_pool_pop, libmongoc), Ptr{Cvoid}, (Ptr{Cvoid},), client_pool_handle)
end

function mongoc_client_pool_try_pop(client_pool_handle::Ptr{Cvoid})
    ccall((:mongoc_client_pool_try_pop, libmongoc), Ptr{Cvoid}, (Ptr{Cvoid},), client_pool_handle)
end

function mongoc_client_pool_push(client_pool_handle::Ptr{Cvoid}, client_handle::Ptr{Cvoid})
    ccall((:mongoc_client_pool_push, libmongoc), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), client_pool_handle, client_handle)
end

function mongoc_client_pool_max_size(client_pool_handle::Ptr{Cvoid}, max_pool_size::UInt32)
    ccall((:mongoc_client_pool_max_size, libmongoc), Cvoid, (Ptr{Cvoid}, UInt32), client_pool_handle, max_pool_size)
end

function mongoc_session_opts_new()
    ccall((:mongoc_session_opts_new, libmongoc), Ptr{Cvoid}, ())
end

function mongoc_session_opts_destroy(session_options_handle::Ptr{Cvoid})
    ccall((:mongoc_session_opts_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), session_options_handle)
end

function mongoc_session_opts_get_causal_consistency(session_options_handle::Ptr{Cvoid})
    ccall((:mongoc_session_opts_get_causal_consistency, libmongoc), Bool,
          (Ptr{Cvoid},),
          session_options_handle)
end

function mongoc_session_opts_set_causal_consistency(session_options_handle::Ptr{Cvoid},
                                                    casual_consistency::Bool)

    ccall((:mongoc_session_opts_set_causal_consistency, libmongoc), Cvoid,
          (Ptr{Cvoid}, Bool),
          session_options_handle, casual_consistency)
end

function mongoc_database_destroy(database_handle::Ptr{Cvoid})
    ccall((:mongoc_database_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), database_handle)
end

function mongoc_database_get_collection(database_handle::Ptr{Cvoid}, collection_name::String)
    ccall((:mongoc_database_get_collection, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Cstring),
          database_handle, collection_name)
end

function mongoc_database_find_collections_with_opts(database_handle::Ptr{Cvoid}, bson_opts::Ptr{Cvoid})
    ccall((:mongoc_database_find_collections_with_opts, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}),
          database_handle, bson_opts)
end

function mongoc_database_command_simple(database_handle::Ptr{Cvoid}, bson_command::Ptr{Cvoid},
                                        read_prefs::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                        bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_command_simple, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          database_handle, bson_command, read_prefs, bson_reply, bson_error_ref)
end

function mongoc_database_write_command_with_opts(database_handle::Ptr{Cvoid}, bson_command::Ptr{Cvoid},
                                                 bson_opts_handle::Ptr{Cvoid},
                                                 bson_reply_ref::Ref{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_write_command_with_opts, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{Cvoid}, Ref{BSONError}),
           database_handle, bson_command, bson_opts_handle, bson_reply_ref, bson_error_ref)
end

function mongoc_database_read_command_with_opts(database_handle::Ptr{Cvoid}, bson_command::Ptr{Cvoid},
                                                 read_prefs_handle::Ptr{Cvoid}, bson_opts_handle::Ptr{Cvoid},
                                                 bson_reply_ref::Ref{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_read_command_with_opts, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{Cvoid}, Ref{BSONError}),
           database_handle, bson_command, read_prefs_handle, bson_opts_handle, bson_reply_ref, bson_error_ref)
end

function mongoc_database_add_user(database_handle::Ptr{Cvoid}, username::String, password::String,
                                  bson_roles::Ptr{Cvoid}, bson_custom_data::Ptr{Cvoid},
                                  bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_add_user, libmongoc), Bool,
          (Ptr{Cvoid}, Cstring, Cstring, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          database_handle, username, password, bson_roles, bson_custom_data, bson_error_ref)
end

function mongoc_database_remove_user(database_handle::Ptr{Cvoid}, username::String,
                                     bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_remove_user, libmongoc), Bool,
          (Ptr{Cvoid}, Cstring, Ref{BSONError}),
          database_handle, username, bson_error_ref)
end

function mongoc_database_drop_with_opts(database_handle::Ptr{Cvoid},
        opts_handle::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_database_drop_with_opts, libmongoc),
          Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          database_handle, opts_handle, bson_error_ref
        )
end

function mongoc_collection_command_simple(collection_handle::Ptr{Cvoid}, bson_command::Ptr{Cvoid},
                                          read_prefs::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                          bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_command_simple, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_command, read_prefs, bson_reply, bson_error_ref)
end

function mongoc_collection_read_command_with_opts(collection_handle::Ptr{Cvoid}, bson_command::Ptr{Cvoid},
                                                    read_prefs::Ptr{Cvoid}, bson_opts_handle::Ptr{Cvoid},
                                                    bson_reply::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_command_simple, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           collection_handle, bson_command, read_prefs, bson_opts_handle, bson_reply, bson_error_ref)
end

function mongoc_collection_destroy(collection_handle::Ptr{Cvoid})
    ccall((:mongoc_collection_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), collection_handle)
end

function mongoc_collection_insert_one(collection_handle::Ptr{Cvoid}, bson_document::Ptr{Cvoid},
                                      bson_options::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                      bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_insert_one, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_document, bson_options, bson_reply, bson_error_ref)
end

function mongoc_collection_find_with_opts(collection_handle::Ptr{Cvoid}, bson_filter::Ptr{Cvoid},
                                          bson_opts::Ptr{Cvoid}, read_prefs::Ptr{Cvoid})

    ccall((:mongoc_collection_find_with_opts, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
          collection_handle, bson_filter, bson_opts, read_prefs)
end

function mongoc_collection_count_documents(collection_handle::Ptr{Cvoid}, bson_filter::Ptr{Cvoid},
                                           bson_opts::Ptr{Cvoid}, read_prefs::Ptr{Cvoid},
                                           bson_reply::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_count_documents, libmongoc), Int64,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_filter, bson_opts, read_prefs, bson_reply, bson_error_ref)
end

function mongoc_collection_create_bulk_operation_with_opts(collection_handle::Ptr{Cvoid},
                                                           bson_opts::Ptr{Cvoid})

    ccall((:mongoc_collection_create_bulk_operation_with_opts, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}),
          collection_handle, bson_opts)
end

function mongoc_collection_delete_one(collection_handle::Ptr{Cvoid}, bson_selector::Ptr{Cvoid},
                                      bson_opts::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                      bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_delete_one, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_selector, bson_opts, bson_reply, bson_error_ref)
end

function mongoc_collection_delete_many(collection_handle::Ptr{Cvoid}, bson_selector::Ptr{Cvoid},
                                       bson_opts::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                       bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_delete_many, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_selector, bson_opts, bson_reply, bson_error_ref)
end

function mongoc_collection_update_one(collection_handle::Ptr{Cvoid}, bson_selector::Ptr{Cvoid},
                                      bson_update::Ptr{Cvoid}, bson_opts::Ptr{Cvoid},
                                      bson_reply::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_update_one, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_selector, bson_update, bson_opts, bson_reply, bson_error_ref)
end

function mongoc_collection_update_many(collection_handle::Ptr{Cvoid}, bson_selector::Ptr{Cvoid},
                                       bson_update::Ptr{Cvoid}, bson_opts::Ptr{Cvoid},
                                       bson_reply::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_update_many, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_selector, bson_update, bson_opts, bson_reply, bson_error_ref)
end

function mongoc_collection_replace_one(collection_handle::Ptr{Cvoid}, bson_selector::Ptr{Cvoid},
                                       bson_replacement::Ptr{Cvoid}, bson_opts::Ptr{Cvoid},
                                       bson_reply::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_replace_one, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          collection_handle, bson_selector, bson_replacement, bson_opts, bson_reply, bson_error_ref)
end

function mongoc_collection_aggregate(collection_handle::Ptr{Cvoid}, flags::QueryFlags,
                                     bson_pipeline::Ptr{Cvoid}, bson_opts::Ptr{Cvoid},
                                     read_prefs::Ptr{Cvoid})

    ccall((:mongoc_collection_aggregate, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
          collection_handle, flags, bson_pipeline, bson_opts, read_prefs)
end

function mongoc_cursor_destroy(cursor_handle::Ptr{Cvoid})
    ccall((:mongoc_cursor_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), cursor_handle)
end

function mongoc_cursor_next(cursor_handle::Ptr{Cvoid}, bson_document_ref::Ref{Ptr{Cvoid}})
    ccall((:mongoc_cursor_next, libmongoc), Bool,
          (Ptr{Cvoid}, Ref{Ptr{Cvoid}}),
          cursor_handle, bson_document_ref)
end

function mongoc_cursor_set_limit(cursor_handle::Ptr{Cvoid}, limit::Int)
    ccall((:mongoc_cursor_set_limit, libmongoc), Bool, (Ptr{Cvoid}, Int64), cursor_handle, limit)
end

function mongoc_cursor_error(cursor_handle::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})
    ccall((:mongoc_cursor_error, libmongoc), Bool,
          (Ptr{Cvoid}, Ref{BSONError}),
          cursor_handle, bson_error_ref)
end

function mongoc_bulk_operation_destroy(bulk_operation_handle::Ptr{Cvoid})
    ccall((:mongoc_bulk_operation_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), bulk_operation_handle)
end

function mongoc_bulk_operation_insert_with_opts(bulk_operation_handle::Ptr{Cvoid},
                                                bson_document::Ptr{Cvoid},
                                                bson_options::Ptr{Cvoid},
                                                bson_error_ref::Ref{BSONError})

    ccall((:mongoc_bulk_operation_insert_with_opts, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          bulk_operation_handle, bson_document, bson_options, bson_error_ref)
end

function mongoc_bulk_operation_replace_one_with_opts(bulk_operation_handle::Ptr{Cvoid},
                                                     bson_selector::Ptr{Cvoid},
                                                     bson_replacement::Ptr{Cvoid},
                                                     bson_options::Ptr{Cvoid},
                                                     bson_error_ref::Ref{BSONError})

    ccall((:mongoc_bulk_operation_replace_one_with_opts, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          bulk_operation_handle, bson_selector, bson_replacement, bson_options, bson_error_ref)
end

function mongoc_bulk_operation_execute(bulk_operation_handle::Ptr{Cvoid}, bson_reply::Ptr{Cvoid},
                                       bson_error_ref::Ref{BSONError})

    ccall((:mongoc_bulk_operation_execute, libmongoc), UInt32,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          bulk_operation_handle, bson_reply, bson_error_ref)
end

function mongoc_collection_drop_with_opts(collection_handle::Ptr{Cvoid},
                                          bson_opts_handle::Ptr{Cvoid},
                                          bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_drop_with_opts, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           collection_handle, bson_opts_handle, bson_error_ref)
end

function mongoc_find_and_modify_opts_new()
    ccall((:mongoc_find_and_modify_opts_new, libmongoc),
           Ptr{Cvoid},
           ())
end

function mongoc_find_and_modify_opts_destroy(handle::Ptr{Cvoid})
    ccall((:mongoc_find_and_modify_opts_destroy, libmongoc),
           Cvoid,
           (Ptr{Cvoid},),
           handle)
end

function mongoc_find_and_modify_opts_set_update(opts_handle::Ptr{Cvoid},
                bson_handle::Ptr{Cvoid})

    ccall((:mongoc_find_and_modify_opts_set_update, libmongoc),
           Bool,
           (Ptr{Cvoid}, Ptr{Cvoid}),
           opts_handle, bson_handle)
end

function mongoc_find_and_modify_opts_set_sort(opts_handle::Ptr{Cvoid},
                bson_handle::Ptr{Cvoid})

    ccall((:mongoc_find_and_modify_opts_set_sort, libmongoc),
           Bool,
           (Ptr{Cvoid}, Ptr{Cvoid}),
           opts_handle, bson_handle)
end

function mongoc_find_and_modify_opts_set_fields(opts_handle::Ptr{Cvoid},
                                                bson_handle::Ptr{Cvoid})

    ccall((:mongoc_find_and_modify_opts_set_fields, libmongoc),
           Bool,
           (Ptr{Cvoid}, Ptr{Cvoid}),
           opts_handle, bson_handle)
end

function mongoc_find_and_modify_opts_set_flags(opts_handle::Ptr{Cvoid},
                                               flags::FindAndModifyFlags)

    ccall((:mongoc_find_and_modify_opts_set_flags, libmongoc),
           Bool,
           (Ptr{Cvoid}, FindAndModifyFlags),
           opts_handle, flags)
end

function mongoc_find_and_modify_opts_set_bypass_document_validation(
        opts_handle::Ptr{Cvoid}, bypass::Bool)

    ccall((:mongoc_find_and_modify_opts_set_bypass_document_validation, libmongoc),
           Bool,
           (Ptr{Cvoid}, Bool),
           opts_handle, bypass)
end

function mongoc_collection_find_and_modify_with_opts(collection_handle::Ptr{Cvoid},
        query_bson_handle::Ptr{Cvoid}, opts_handle::Ptr{Cvoid},
        result_bson_handle::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_collection_find_and_modify_with_opts, libmongoc),
           Bool,
           (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           collection_handle, query_bson_handle,
           opts_handle, result_bson_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_new(database_handle::Ptr{Cvoid}, bson_opts_handle::Ptr{Cvoid},
                                  read_prefs::Ptr{Cvoid}, bson_error_ref::Ref{BSONError})

    ccall((:mongoc_gridfs_bucket_new, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          database_handle, bson_opts_handle, read_prefs, bson_error_ref)
end

function mongoc_gridfs_bucket_destroy(bucket_handle::Ptr{Cvoid})
    ccall((:mongoc_gridfs_bucket_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), bucket_handle)
end

function mongoc_stream_destroy(stream_handle::Ptr{Cvoid})
    ccall((:mongoc_stream_destroy, libmongoc), Cvoid, (Ptr{Cvoid},), stream_handle)
end

function mongoc_stream_flush(stream_handle::Ptr{Cvoid})
    ccall((:mongoc_stream_flush, libmongoc), Cint, (Ptr{Cvoid},), stream_handle)
end

function mongoc_stream_close(stream_handle::Ptr{Cvoid})
    ccall((:mongoc_stream_close, libmongoc), Cint, (Ptr{Cvoid},), stream_handle)
end

function mongoc_stream_file_new_for_path(path::AbstractString, flags::Integer, mode::Integer)
    ccall((:mongoc_stream_file_new_for_path, libmongoc), Ptr{Cvoid},
          (Cstring, Cint, Cint),
          path, flags, mode)
end

function mongoc_stream_read(
        stream_handle::Ptr{Cvoid},
        buffer_handle::Ptr{UInt8},
        count::Integer,
        min_bytes::Integer,
        timeout_msec::Integer)

    ccall((:mongoc_stream_read, libmongoc), Csize_t,
          (Ptr{Cvoid}, Ptr{UInt8}, Csize_t, Csize_t, Cint),
          stream_handle, buffer_handle, count, min_bytes, timeout_msec)
end

function mongoc_stream_write(
        stream_handle::Ptr{Cvoid},
        buffer_handle::Ptr{UInt8},
        count::Integer,
        timeout_msec::Integer)

    ccall((:mongoc_stream_write, libmongoc), Csize_t,
          (Ptr{Cvoid}, Ptr{UInt8}, Csize_t, Cint),
           stream_handle, buffer_handle, count, timeout_msec)
end

function mongoc_gridfs_bucket_upload_from_stream_with_id(
        bucket_handle::Ptr{Cvoid},
        bson_value_file_id_handle::Ptr{Cvoid},
        filename::AbstractString,
        source_stream_handle::Ptr{Cvoid},
        bson_opts_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError}
    )

    ccall((:mongoc_gridfs_bucket_upload_from_stream_with_id, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
          bucket_handle, bson_value_file_id_handle, filename, source_stream_handle,
          bson_opts_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_download_to_stream(
        bucket_handle::Ptr{Cvoid},
        bson_value_file_id_handle::Ptr{Cvoid},
        destination_stream_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError}
    )

    ccall((:mongoc_gridfs_bucket_download_to_stream, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           bucket_handle, bson_value_file_id_handle, destination_stream_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_open_download_stream(
        bucket_handle::Ptr{Cvoid},
        bson_value_file_id_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError}
    )

    ccall((:mongoc_gridfs_bucket_open_download_stream, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           bucket_handle, bson_value_file_id_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_open_upload_stream_with_id(
        bucket_handle::Ptr{Cvoid},
        bson_value_file_id_handle::Ptr{Cvoid},
        filename::AbstractString,
        bson_opts_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError})

    ccall((:mongoc_gridfs_bucket_open_upload_stream_with_id, libmongoc), Ptr{Cvoid},
           (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}, Ref{BSONError}),
            bucket_handle, bson_value_file_id_handle, filename,
            bson_opts_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_abort_upload(stream_handle::Ptr{Cvoid})
    ccall((:mongoc_gridfs_bucket_abort_upload, libmongoc), Bool,
          (Ptr{Cvoid},), stream_handle)
end

function mongoc_gridfs_bucket_find(
        bucket_handle::Ptr{Cvoid},
        bson_filter_handle::Ptr{Cvoid},
        bson_opts_handle::Ptr{Cvoid})

    ccall((:mongoc_gridfs_bucket_find, libmongoc), Ptr{Cvoid},
          (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
          bucket_handle, bson_filter_handle, bson_opts_handle)
end

function mongoc_gridfs_bucket_delete_by_id(
        bucket_handle::Ptr{Cvoid},
        bson_value_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError})

    ccall((:mongoc_gridfs_bucket_delete_by_id, libmongoc), Bool,
          (Ptr{Cvoid}, Ptr{Cvoid}, Ref{BSONError}),
           bucket_handle, bson_value_handle, bson_error_ref)
end

function mongoc_gridfs_bucket_stream_error(
        stream_handle::Ptr{Cvoid},
        bson_error_ref::Ref{BSONError}
    )

    ccall((:mongoc_gridfs_bucket_stream_error, libmongoc), Bool,
          (Ptr{Cvoid}, Ref{BSONError}),
           stream_handle, bson_error_ref)
end

function mongoc_set_log_handler(cfunction::Ptr{Cvoid})
    ccall(
        (:mongoc_log_set_handler, libmongoc),
        Cvoid,
        (Ptr{Cvoid}, Ptr{Cvoid}),
        cfunction, C_NULL
    )
end


text:
function Base.show(io::IO, texture::Texture)
    print(io, "[Texture]\n",
        " - type: ", texture.type, '\n',
        " - embedded [", texture.embedded != 0, "]: ", texture.embedded, '\n',
        " - file: ", texture.file, '\n',
        " - size: ", size(texture.data), '\n',
        " - eltype: ", eltype(texture.data), '\n',
    )
end

function Base.show(io::IO, material::Material)
    print(io, "[Material]\n",
        " - ambient: ", material.ambient, '\n',
        " - diffuse: ", material.diffuse, '\n',
        " - specular: ", material.specular, '\n',
        " - emissive: ", material.emissive, '\n',
        " - shininess: ", material.shininess, '\n',
        " - shinpercent: ", material.shinpercent, '\n',
        " - opacity: ", material.opacity, '\n',
        " - transparencyfactor: ", material.transparencyfactor, '\n',
        " - reflectivity: ", material.reflectivity, '\n',
        " - refracti: ", material.refracti, '\n',
        " - bumpscaling: ", material.bumpscaling, '\n',
        " - texture types [", length(material.textures), "]:\n",
    )
    for (type, textures) in material.textures
        print(io, type, " [", length(textures), "]: \n")
        for (i, texture) in enumerate(textures)
            texture_repr = split(repr(texture), '\n'; keepempty=false)
            print(io, "\t[", i, "]: ", texture_repr[1], '\n')
            map(r -> print(io, '\t', r, '\n'), texture_repr[2:end])
        end
    end
    print(io, '\n')
end

function Base.show(io::IO, mesh::MetaMesh)
    print(io, "[MetaMesh]\n",
        " - material idx: ", mesh.material_idx, '\n',
        " - vertices: ", length(coordinates(mesh.mesh)), '\n',
        " - attributes: ", propertynames(mesh.mesh), '\n',
    )
end

function Base.show(io::IO, node::Node, level = 0)
    shift = repeat('\t', level) * '|'

    print(io, "[Node]\n", shift, " - meshes [", length(node.meshes), "]:\n")
    for (i, mesh) in enumerate(node.meshes)
        mesh_repr = split(repr(mesh), '\n'; keepempty=false)
        print(io, shift, '[', i, "]: ", mesh_repr[1], '\n')
        map(r -> print(io, shift, r, '\n'), mesh_repr[2:end])
    end
    println(io, shift, "----------------")
    print(io, shift, " - children [", length(node.children), "]:\n")
    for (i, child) in enumerate(node.children)
        print(io, shift, '[', i, "]: ")
        Base.show(io, child, level + 1)
    end
end

ext: jl


text:
"""
    krbf(X, Y; gamma = 1)
Compute a Radial-Basis-Function (RBF) kernel Gram matrix.
* `X` : Data.
* `Y` : Data.
* `gamma` : Scale parameter.

Given matrices `X` (n, p) and `Y` (m, p), the function returns
the (n, m) Gram matrix K(X, Y) = Phi(X) * Phi(Y)'.

The RBF kernel between two vectors x and y is computed by
exp(-gamma * ||x - y||^2).

## References

Scholkopf, B., Smola, A.J., 2002. Learning with kernels: support vector machines,
regularization, optimization, and beyond, Adaptive computation and machine learning.
MIT Press, Cambridge, Mass.

"""
function krbf(X, Y; gamma = 1)
    exp.(-gamma * euclsq(X, Y))
end

"""
    kpol(X, Y; degree = 1, gamma = 1, coef0 = 0)
Compute a polynomial kernel Gram matrix.
* `X` : Data.
* `Y` : Data.
* `degree` : Degree of the polynom.
* `gamma` : Scale of the polynom.
* `coef0` : Offset of the polynom.

Given matrices `X` (n, p) and `Y` (m, p), the function returns
the (n, m) Gram matrix K(X, Y) = Phi(X) * Phi(Y)'.

The polynomial kernel between two vectors x and y is computed by
(gamma * (x' * y) + coef0)^degree.

## References

Scholkopf, B., Smola, A.J., 2002. Learning with kernels: support vector machines,
regularization, optimization, and beyond, Adaptive computation and machine learning.
MIT Press, Cambridge, Mass.

"""
function kpol(X, Y; degree = 1, gamma = 1, coef0 = 0)
    K = gamma * X * Y' .+ coef0
    if degree > 1
        zK = copy(K)
        @inbounds for i = 1:(degree - 1)
            K .= K .* zK
        end
    end
    K
end


text:
module QuadraticModelsXpress

export xpress

using QuadraticModels, SolverCore, SparseMatricesCOO
using LinearAlgebra, SparseArrays
using Xpress

const xpress_statuses = Dict(0 => :unknown,
                             1 => :acceptable,
                             2 => :infeasible,
                             3 => :exception,
                             4 => :max_eval,
                             5 => :unbounded,
                             6 => :exception,
                             7 => :exception,
                             8 => :exception)

function sparse_csr(I, J, V, m=maximum(I), n=maximum(J))
    csrrowptr = zeros(Int, m+1)
    # Compute the CSR form's row counts and store them shifted forward by one in csrrowptr
    coolen = length(I)
    min(length(J), length(V)) >= coolen || throw(ArgumentError("J and V need length >= length(I) = $coolen"))
    @inbounds for k in 1:coolen
        Ik = I[k]
        if 1 > Ik || m < Ik
            throw(ArgumentError("row indices I[k] must satisfy 1 <= I[k] <= m"))
        end
        csrrowptr[Ik+1] += 1
    end

    # Compute the CSR form's rowptrs and store them shifted forward by one in csrrowptr
    countsum = 1
    csrrowptr[1] = 1
    @inbounds for i in 2:(m+1)
        overwritten = csrrowptr[i]
        csrrowptr[i] = countsum
        countsum += overwritten
    end

    # Counting-sort the column and nonzero values from J and V into csrcolval and csrnzval
    # Tracking write positions in csrrowptr corrects the row pointers
    csrcolval = zeros(Int, length(I))
    csrnzval = zeros(length(I))
    @inbounds for k in 1:coolen
        Ik, Jk = I[k], J[k]
        if 1 > Jk || n < Jk
            throw(ArgumentError("column indices J[k] must satisfy 1 <= J[k] <= n"))
        end
        csrk = csrrowptr[Ik+1]
        csrrowptr[Ik+1] = csrk + 1
        csrcolval[csrk] = Jk
        csrnzval[csrk] = V[k]
    end
    csrrowptr = csrrowptr[1:end-1]
    return csrrowptr, csrcolval, csrnzval
end

function xpress(QM::QuadraticModel{T, S, M1, M2};
                method="b", kwargs...) where {T, S, M1 <: SparseMatrixCOO, M2 <: SparseMatrixCOO}

    Xpress.init()
    prob = Xpress.XpressProblem()
    # use kwargs change to presolve, scaling and crossover mode
    # example: xpress(QM, presolve=0, bargapstop=1e-8)
    for (k, v) in kwargs
        if k==:presolve
            Xpress.setintcontrol(prob, Xpress.Lib.XPRS_PRESOLVE, v)  # 0 no presolve, 1=presolve
        elseif k==:scaling
            Xpress.setintcontrol(prob, Xpress.Lib.XPRS_SCALING, v)  # 0 no scaling
        elseif k==:crossover
            Xpress.setintcontrol(prob, Xpress.Lib.XPRS_CROSSOVER, v)  # 0 no crossover
        elseif k==:bargapstop
            Xpress.setdblcontrol(prob, Xpress.Lib.XPRS_BARGAPSTOP, v)
        elseif k==:barprimalstop
            Xpress.setdblcontrol(prob, Xpress.Lib.XPRS_BARPRIMALSTOP, v)
        elseif k==:bardualstop
            Xpress.setdblcontrol(prob, Xpress.Lib.XPRS_BARDUALSTOP, v)
        end
    end

    srowtypes = fill(Cchar('A'), QM.meta.ncon)
    if length(QM.meta.jinf) > 0
        error("infeasible bounds in A")
    end
    p_low, p_upp, p_rng, p_fix, p_free = 1, 1, 1, 1, 1
    for j=1:QM.meta.ncon
        if length(QM.meta.jlow) > 0 && QM.meta.jlow[p_low] == j
            srowtypes[j] = Cchar('G')
            if (p_low < length(QM.meta.jlow)) p_low += 1 end
        elseif length(QM.meta.jupp) > 0 && QM.meta.jupp[p_upp] == j
            srowtypes[j] = Cchar('L')
            if (p_upp < length(QM.meta.jupp)) p_upp += 1 end
        elseif length(QM.meta.jrng) > 0 && QM.meta.jrng[p_rng] == j
            srowtypes[j] = Cchar('R')
            if (p_rng < length(QM.meta.jrng)) p_rng += 1 end
        elseif length(QM.meta.jfix) > 0 && QM.meta.jfix[p_fix] == j
            srowtypes[j] = Cchar('E')
            if (p_fix < length(QM.meta.jfix)) p_fix += 1 end
        elseif length(QM.meta.jfree) > 0 && QM.meta.jfree[p_free] == j
            srowtypes[j] = Cchar('N')
            if (p_free < length(QM.meta.jfree)) p_free += 1 end
        else
            error("A error")
        end
    end
    rhs = zeros(QM.meta.ncon)
    drange = zeros(QM.meta.ncon)
    for j = 1:QM.meta.ncon
        if QM.meta.lcon[j] != -Inf && QM.meta.ucon[j] != Inf
            rhs[j] = QM.meta.ucon[j]
            drange[j] = QM.meta.ucon[j] - QM.meta.lcon[j]
        elseif QM.meta.lcon[j] != -Inf && QM.meta.ucon[j] == Inf
            rhs[j] = QM.meta.lcon[j]
        elseif QM.meta.lcon[j] == -Inf && QM.meta.ucon[j] != Inf
            rhs[j] = QM.meta.ucon[j]
        else
            rhs[j] = Xpress.Lib.XPRS_PLUSINFINITY
        end
    end

    A = sparse(QM.data.A.rows, QM.data.A.cols, QM.data.A.vals, QM.meta.ncon, QM.meta.nvar)
    lvar, uvar = zeros(QM.meta.nvar), zeros(QM.meta.nvar)

    for i=1:QM.meta.nvar
        QM.meta.lvar[i] == -Inf ? lvar[i] = Xpress.Lib.XPRS_MINUSINFINITY : lvar[i] = QM.meta.lvar[i]
        QM.meta.uvar[i] == Inf ? uvar[i] = Xpress.Lib.XPRS_PLUSINFINITY : uvar[i] = QM.meta.uvar[i]
    end

    if QM.meta.nnzh > 0
        Xpress.loadqp(prob, QM.meta.name, QM.meta.nvar, QM.meta.ncon, srowtypes, rhs, drange,
                      QM.data.c, convert(Array{Cint,1}, A.colptr.-1), C_NULL,
                      convert(Array{Cint,1}, A.rowval.-1), A.nzval, lvar, uvar, QM.meta.nnzh,
                      convert(Array{Cint,1}, QM.data.H.rows.-1), convert(Array{Cint,1}, QM.data.H.cols.-1),
                      QM.data.H.vals)
    else
        Xpress.loadlp(prob, "", QM.meta.nvar, QM.meta.ncon, srowtypes, rhs, drange,
                      QM.data.c,convert(Array{Cint,1}, A.colptr.-1), C_NULL,convert(Array{Cint,1}, A.rowval.-1),
                      A.nzval, lvar, uvar)
    end
    Xpress.chgobj(prob, [0], [-QM.data.c0])

    start_time = time()
    Xpress.lpoptimize(prob, method)
    elapsed_time = time() - start_time

    x, y, s = zeros(QM.meta.nvar), zeros(QM.meta.ncon), zeros(QM.meta.nvar)
    Xpress.getsol(prob, x, C_NULL, y, s)
    baritcnt = Xpress.getintattrib(prob, Xpress.Lib.XPRS_BARITER)
    objval = Xpress.getdblattrib(prob, Xpress.Lib.XPRS_LPOBJVAL)
    status = Xpress.getintattrib(prob, Xpress.Lib.XPRS_LPSTATUS)
    p_feas = Xpress.getdblattrib(prob, Xpress.Lib.XPRS_BARPRIMALINF)
    d_feas = Xpress.getdblattrib(prob, Xpress.Lib.XPRS_BARDUALINF)

    stats = GenericExecutionStats(get(xpress_statuses, status, :unknown),
                                  QM, solution = x,
                                  objective = objval,
                                  primal_feas = p_feas,
                                  dual_feas = d_feas,
                                  iter = Int64(baritcnt),
                                  multipliers = y,
                                  elapsed_time = elapsed_time)
    Xpress.destroyprob(prob)
    Xpress.free()

    return stats
end

end

max_stars_repo_name: geoffroyleconte/QuadraticModelsXpress

max_issues_repo_name: geoffroyleconte/QuadraticModelsXpress

max_forks_repo_name: geoffroyleconte/QuadraticModelsXpress


text:
using HorizonSideRobots
include("../basics.jl")

md"""
            ДАНО: Робот - в юго-западном углу поля, на котором расставлено некоторое количество маркеров

            РЕЗУЛЬТАТ: Функция вернула значение средней температуры всех замаркированных клеток
"""

"""Поиск в глубину, робот проходится по всем клеткам и возвращает пару значений: (сумма температур на маркированных клетках, их кол-во)"""
function count_some_temperature_with_dfs!(r, visited, cell, side, COUNT=0, SUM=0)
    is_not_at_start = side != false

    if is_not_at_start
        cheker = isborder(r, side) == false
    else
        cheker = true
    end

    if (cell in visited) == false && cheker
        if is_not_at_start
            move!(r, side)
        end
        push!(visited, cell)

        if ismarker(r)
            SUM += temperature(r)
            COUNT += 1
        end

        for tempSide in [Nord, Ost, Sud, West]
            ans = count_some_temperature_with_dfs!(r, visited, side_to_i_j(tempSide, cell), tempSide, COUNT, SUM)
            if ans != false
                move!(r, opposite_side(tempSide))
                SUM += ans[1]
                COUNT += ans[2]
            end
        end

        return (SUM, COUNT)

    else
        return false
    end
end

 """Решение задачи 10"""
 function solve_10!(r)
    (M, N), (I, J) = get_field_size_and_position!(r)
    answer = count_some_temperature_with_dfs!(r, Set(), (I, J), false)

    return Float64(answer[1] / answer[2])
end


text:
# automatically generated by the FlatBuffers compiler, do not modify

# module: Example

@enum Color::Int8 begin
    ColorRed = 1
    ColorGreen = 2
    ColorBlue = 8
end


text:
module Sim
include("./name2idx/parameters.jl")
include("./name2idx/species.jl")
include("./set_model.jl")
include("./observable.jl")

using .C
using .V

using DelayDiffEq

const normalization = true
#=
if true, simulation results in each observable
are divided by their maximum values
=#
const dt = 1.0
t = collect(0.0:1.0:360.0)  # 0, 1, 2, ..., 360 [min.]
const simtime = t[end]
const sstime = 1000.0  # time to reach steady state

const conditions = ["WT"]

simulations = Array{Float64,3}(
    undef, length(observables), length(t), length(conditions)
)

function solvedde(
        diffeq::Function,u0::Vector{Float64},history::Vector{Float64},
        tspan::Tuple{Float64,Float64},p::Vector{Float64},tau::Float64)
    h(p,t) = history
    lags = [tau]
    prob = DDEProblem(diffeq,u0,h,tspan,p;constant_lags=lags)
    alg = MethodOfSteps(BS3())
    sol = solve(
        prob,alg,saveat=dt,abstol=1e-9,reltol=1e-9,dtmin=1e-8,verbose=false
    )
    return sol
end

function get_steady_state(
        p::Vector{Float64},u0::Vector{Float64},
        sstime::Float64,tau::Float64)::Vector{Float64}
    # get steady state (t<0)
    param::Vector{Float64} = copy(p)
    param[C.term] = 1.0
    history::Vector{Float64} = u0
    tspan::Tuple{Float64,Float64} = (0.0,sstime)
    sol = solvedde(diffeq,u0,history,tspan,param,tau)
    return sol[:,end]
end

function get_time_course(
        p::Vector{Float64},u0::Vector{Float64},
        sstime::Float64,simtime::Float64,tau::Float64)
    param::Vector{Float64} = copy(p)
    param[C.term] = 0.0
    u1::Vector{Float64} = get_steady_state(p,u0,sstime,tau)
    history::Vector{Float64} = u1
    tspan::Tuple{Float64,Float64} = (0.0,simtime)
    try
        sol = solvedde(diffeq,u1,history,tspan,param,tau)
        return ifelse(length(sol.t) == length(t), sol, nothing)
    catch
        return nothing
    end
end

function simulate!(p::Vector{Float64}, u0::Vector{Float64})::Union{Bool, Nothing}
    for (i,condition) in enumerate(conditions)
        if condition == "WT"
            # pass
        end
        sol = get_time_course(p,u0,sstime,simtime,p[C.delayrnae])
        if sol === nothing
            return false
        else
            @inbounds @simd for j in eachindex(t)
                simulations[observables_index("Nuclear_NFkB"),j,i] = (
                    sol.u[j][V.NFKBn]
                )
            end
        end
    end
end
end # module

max_stars_repo_name: himoto/paramEstim

max_issues_repo_name: himoto/paramEstim

max_forks_repo_name: himoto/paramEstim


text:
import Base.*

# Overload matrix-vector multiplication for BSR matrix
function *(A::BSR_Matrix, x::AbstractVector{Float64})
    convert(Vector{Float64}, A.matrix*x)
end


text:
using Test
using ProbNumDiffEq
using OrdinaryDiffEq
using DiffEqDevTools
using Plots

using DiffEqProblemLibrary.ODEProblemLibrary: importodeproblems;
importodeproblems();
import DiffEqProblemLibrary.ODEProblemLibrary: prob_ode_fitzhughnagumo
prob = prob_ode_fitzhughnagumo

test_sol = TestSolution(solve(prob, Vern7(), abstol=1 / 10^14, reltol=1 / 10^14))

@testset "appxtrue" begin
    appxsol = appxtrue(solve(prob, EK1()), test_sol)
    @test appxsol.errors isa Dict
    @test :final in keys(appxsol.errors)
    @test :l2 in keys(appxsol.errors)
    @test :L2 in keys(appxsol.errors)
    @test :l∞ in keys(appxsol.errors)
    @test :L∞ in keys(appxsol.errors)

    appxsol = appxtrue(solve(prob, EK1(smooth=false), dense=false), test_sol)
    @test appxsol.errors isa Dict
    @test :final in keys(appxsol.errors)
    @test :l2 in keys(appxsol.errors)
    @test :l∞ in keys(appxsol.errors)
end

@testset "WorkPrecision" begin
    abstols = 1.0 ./ 10.0 .^ (6:13)
    reltols = 1.0 ./ 10.0 .^ (3:10)
    wp = WorkPrecision(
        prob,
        EK1(smooth=false),
        abstols,
        reltols;
        appxsol=test_sol,
        save_everystep=false,
        maxiters=100000,
        numruns=10,
    )
    @test wp isa WorkPrecision
    @test plot(wp) isa AbstractPlot
end

@testset "WorkPrecisionSet" begin
    abstols = 1.0 ./ 10.0 .^ (6:13)
    reltols = 1.0 ./ 10.0 .^ (3:10)
    setups = [
        Dict(:alg => EK0(order=4))
        Dict(:alg => EK1(order=5))
    ]
    wps = WorkPrecisionSet(
        prob,
        abstols,
        reltols,
        setups;
        appxsol=test_sol,
        error_estimate=:L2,
        maxiters=100000,
        numruns=10,
    )
    @test wps isa WorkPrecisionSet
    @test plot(wps) isa AbstractPlot
end


text:
## Packages
using LinearAlgebra
using Plots

## Parameters
α = 0.333;
δ = 0.1;
β = 0.975;
A = 1.05;
nk = 1000; # number of capital grids
tol = 1e-5;
maxDiff = 10.0;
iter = 0;
maxiter = 5000;

## Steady state of capital
kss = ((1/β+δ-1)/(α*A))^(1/(α-1));

## Capital grids
kmin = 0.5*kss;
kmax = 1.5*kss;
k_grid = LinRange(kmin, kmax,nk);
kp_grid = LinRange(kmin, kmax,nk);

# VFI
V_old = zeros(nk);
V_new = zeros(nk);
kpol = zeros(nk);

while maxDiff >= tol && iter < maxiter
    iter = iter+1;
    println("Current iteration is $iter")

    temp = zeros(nk, nk);
    V_old = V_new;

    # Take care of negative consumption
    for i = 1:nk
        for j = 1:nk
            c = A*(k_grid[i]^α)+(1-δ)*k_grid[i]-kp_grid[j];
            if c < 0
                temp[i,j] = -10.0;
            else
                temp[i,j] = log(c)+β*V_old[j];
            end
        end
    end

    # Find max and argmax
    V_new,argmax = findmax(temp,dims=2);

    for i = 1:nk
        kpol[i] = k_grid[argmax[i][2]];
    end

    maxDiff = maximum(abs.(V_new-V_old));
    println("maxDiff = $maxDiff")
end

plot(k_grid,k_grid,label = "", color = :black, linestyle = :dash)
plot!(k_grid,kpol,label = "Capital policy")

plot(k_grid,V_new,label = "Value Function")

# This is a check when δ = 1
# plot!(k_grid,α*β*A*k_grid.^α,label = "true")


text:
module NeuralGRN

using DiffEqFlux, DifferentialEquations, Flux, LightGraphs, SimpleWeightedGraphs,
    Plots, StatsPlots, GraphRecipes, CUDA
using BSON: @save, @load

include("problem.jl")
include("models.jl")

export TemporalGRNProblem, SteadyStateGRNProblem, GenerativePropODE,
    ModelPropODE, DeepPropODE
export plot_problem_graph, generate

end


text: const JAVA_HOME="<path to java home>"


text:
# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:light
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.4'
#       jupytext_version: 1.2.4
#   kernelspec:
#     display_name: Julia 1.3.1
#     language: julia
#     name: julia-1.3
# ---

# # Single Particle Simulation Example
#
#

#
#
#

#
# Here we simulate the trajectory of a particle drifting in an idealized flow which is a crude representation for an ocean eddy.
#
# To start, we set up the flow field as solid body rotation (around a central point in a doubly periodic domain). At the end, exercises are provided (e.g. add a a convergence term).
#
# This demonstrates `Julia` differential equation solvers that are broadly applicable, as well as simple methods to create plots and movies.
#
# - 1. setup the software and initialize example
# - 2. simulate trajectories & plot results
# - 3. experiment with parameters (user)

# + {"slideshow": {"slide_type": "subslide"}, "cell_type": "markdown"}
# ### For More Documentation
#
# On Julia :
# - <https://julialang.org>
# - <https://docs.julialang.org>
#
# On this notebook :
# - <https://docs.juliadiffeq.org/latest>
# - <https://en.wikipedia.org/wiki/Displacement_(vector)>
# - <https://juliaclimate.github.io/IndividualDisplacements.jl/dev>
# - <https://juliaclimate.github.io/MeshArrays.jl/dev>

# + {"slideshow": {"slide_type": "slide"}, "cell_style": "center", "cell_type": "markdown"}
# ## 1.1 Import Software
#
# The following `Julia` Packages are used for solving differential equations and plotting results.

# + {"cell_style": "center"}
using OrdinaryDiffEq, Plots
using IndividualDisplacements, MeshArrays
include("helper_functions.jl")

# + {"slideshow": {"slide_type": "subslide"}, "cell_type": "markdown"}
# ## 1.2  Gridded Domain Example
#
# The `SetPeriodicDomain` helper function sets up a basic grid of size `np x np`.

# + {"slideshow": {"slide_type": "-"}}
np=16
Γ=SetupPeriodicDomain(np);

# +
#?SetupPeriodicDomain
#show(Γ["XC"])
#scatter(Γ["XC"][1],Γ["YC"][1],leg=:none,title="grid points")

# + {"slideshow": {"slide_type": "subslide"}, "cell_type": "markdown"}
# ## 1.3 Define Time Period & Velocity Fields
#
# For convenience, at the end we store all parameters in `𝑃` (a dictionary).
#
# _Code cell is skipped at first in presentation mode_

# + {"slideshow": {"slide_type": "skip"}}
#time range
t0=0.0
t1=0.98*2*pi
#t1=3.0*2*pi
𝑇 = (t0,t1)

#solid-body rotation around central location
i=Int(np/2+1)
u=-(Γ["YG"].-Γ["YG"][1][i,i])
v=(Γ["XG"].-Γ["XG"][1][i,i])

#add some convergence to / divergence from central location
d=0.0
#d=-0.1
u=u+d*(Γ["XG"].-Γ["XG"][1][i,i])
v=v+d*(Γ["YG"].-Γ["YG"][1][i,i])

#store everything in a dictionnary
𝑃=Dict("u0" => u, "u1" => u, "v0" => v, "v1" => v, "t0" => t0, "t1" => t1)
𝑃=merge(𝑃,Γ)

# + {"slideshow": {"slide_type": "slide"}, "cell_type": "markdown"}
# ## 1.4 Define initial position and time period
# -

u0=np*[1/3,1/3]
du=fill(0.0,2);

# + {"slideshow": {"slide_type": "slide"}, "cell_type": "markdown"}
# ## 2.1 solve for particle trajectory
#
# - `ODEProblem` formulates the differential equation along with the time period `𝑇`, parameters `𝑃`
# - `solve` then performs the integration over `𝑇`, starting from `u0`, using the `Tsit5` solver
#
# _Tsit5 - Tsitouras 5/4 Runge-Kutta method. (free 4th order interpolant)._ ([from the docs](https://docs.sciml.ai/dev/solvers/ode_solve/#))
#
# _Interested in additional documentation? Try `?ODEProblem` or `?solve`_
# -

prob = ODEProblem(⬡,u0,𝑇,𝑃)
sol = solve(prob,Tsit5(),reltol=1e-8);

# + {"slideshow": {"slide_type": "-"}, "cell_type": "markdown"}
# ## 2.2 Post-Process Output
#
# _(Not much in the case of this example)_
# -

x,y=sol[1,:],sol[2,:]
nt=length(x)

# + {"slideshow": {"slide_type": "slide"}, "cell_type": "markdown"}
# ## 2.3 plot result (particle trajectory)
#
# - define `myplot` convenience function
# - generate animation using `myplot`

# + {"slideshow": {"slide_type": "-"}}
myplot(i)=plot(x[1:i],y[1:i],linewidth=2,arrow = 2,
    title="Solid body rotation / Spiral example",leg=false,
    xaxis="x",yaxis="y",xlims=(0,np),ylims=(0,np))

# + {"slideshow": {"slide_type": "subslide"}}
p=Int(ceil(nt/100))
anim = @animate for i ∈ 1:p:nt
    myplot(i)
end
pth=tempdir()*"/"
gif(anim, pth*"SolidBodyRotation.gif", fps = 15)
# + {"slideshow": {"slide_type": "subslide"}, "cell_type": "markdown"}
# ### Want a single plot?
#
# Try uncommenting one line at a time in the next cell below.

# + {"slideshow": {"slide_type": "-"}}
#plt=myplot(nt)
#scatter!(plt,[u0[1]],[u0[2]])
#savefig(plt,pth*"SolidBodyRotation.png")

# + {"slideshow": {"slide_type": "subslide"}, "cell_type": "markdown"}
# ### Want to change other parameters?
#
# Try uncommenting, in the code above, these lines:
#
# - `#?SetupPeriodicDomain`
# - `#show(Γ["XC"])`
# - `#t1=3.0*2*pi`
# - `#d=-0.1`
# -

max_stars_repo_name: gaelforget/MarineEcosystemNotebooks

max_forks_repo_name: gaelforget/MarineEcosystemNotebooks


text:
"""
    simdat_crossed([RNG], subj_n, item_n;
                   subj_btwn=nothing, item_btwn=nothing, both_win=nothing,
                   subj_prefix="S", item_prefix="I")

Return a row table with a design specified by the:
* number of subjects (`subj_n`),
* number of items (`item_n`)
* between-subject factors (`subj_btwn`)
* between-item factors (`item_btwn`)
* within-subject/item factors (`both_win`)

If a factor is both between-subject and between-item, put it in both
`subj_btwn` and `item_btwn` with the same keys and the same levels.

Factors should be specified as dictionaries in the following format:
```julia
Dict(
    :factor1_name => ["F1_level1", "F1_level2"],
    :factor2_name => ["F2_level1", "F2_level2", "F2_level3"]
)
```

In addition to design, the rowtable contains a field `dv` pre-populated
with N(0,1) noise as a basis for further simulating a design.

!!! note
    The number of subjects/items must divide the number of combinations of between subject/item factor levels.
    In other words, this function assumes a balanced design and will throw an error if that is not possible.
"""
simdat_crossed(args...; kwargs...) = simdat_crossed(Random.GLOBAL_RNG, args...; kwargs...)

function simdat_crossed(rng::AbstractRNG, subj_n=1, item_n=1;
    subj_btwn=nothing, item_btwn=nothing, both_win=nothing,
    subj_prefix="S", item_prefix="I")

    # set up subj table
    if isnothing(subj_btwn)
        subj_vals = [nlevels(subj_n, subj_prefix)]
        sb_vars = []
    else
        sc = values(subj_btwn) |> collect
        nlev = prod(length, sc)
        if mod(subj_n, nlev) != 0
            throw(ArgumentError("Number of subjects is not a multiple of the number of between-subject levels"))
        end
        subj_prod = Iterators.product(sc...)
        subj_vals = columntable(subj_prod) |> collect
        subj_vals = vcat([nlevels(subj_n, subj_prefix)], repeat.(subj_vals, subj_n ÷ nlev))
        sb_vars = collect(keys(subj_btwn))
    end

    subj_names = vcat(["subj"], sb_vars)
    subj = (; (Symbol(k) => v for (k,v) in zip(subj_names, subj_vals))...)

    # set up item table
    if isnothing(item_btwn)
        item_vals = [nlevels(item_n, item_prefix)]
        ib_vars = []
    else
        ic = values(item_btwn) |> collect
        nlev = prod(length, ic)
        if mod(item_n, nlev) != 0
            throw(ArgumentError("Number of items is not a multiple of the number of between-subject levels"))
        end
        item_prod = Iterators.product(ic...)
        item_vals = columntable(item_prod) |> collect
        item_vals = vcat([nlevels(item_n, item_prefix)], repeat.(item_vals, item_n ÷ nlev))
        ib_vars = collect(keys(item_btwn))
    end

    item_names = vcat(["item"], ib_vars)
    item = (; (Symbol(k) => v for (k,v) in zip(item_names, item_vals))...)

    # set up within both table
    if (isnothing(both_win))
        # cross the subject and item tables
        design = factorproduct(subj, item)
    else
        wc = values(both_win) |> collect
        win_prod = Iterators.product(wc...)
        win_vals = columntable(win_prod) |> collect
        win_names = collect(keys(both_win))
        win = (; (Symbol(k) => v for (k,v) in zip(win_names, win_vals))...)

        # cross the subject and item tables with any within factors
        design = factorproduct(subj, item, win)
    end

    dv = randn(rng, length(design))
    design = [ merge(dd, (; :dv => vv) ) for (dd, vv) in zip(design, dv) ]

    design
end


text:
using Documenter, UniversalTensorBoard

makedocs(;
    modules=[UniversalTensorBoard],
    format=:html,
    pages=[
        "Home" => "index.md",
    ],
    repo="https://github.com/oxinabox/UniversalTensorBoard.jl/blob/{commit}{path}#L{line}",
    sitename="UniversalTensorBoard.jl",
    authors="Lyndon White (aka oxinabox)",
    assets=[],
)

deploydocs(;
    repo="github.com/oxinabox/UniversalTensorBoard.jl",
    target="build",
    julia="0.6",
    deps=nothing,
    make=nothing,
)


text:
# using much of the name space of LoopVectorization; seems more organized to set it aside.
module LoopSetDerivatives

using ..ReverseDiffExpressions: Model, diffsym

using ReverseDiffExpressionsBase, LoopVectorization, UnPack, OffsetArrays
using ReverseDiffExpressionsBase: DiffRule, InstructionArgs, DERIVATIVERULES
using LoopVectorization:
    LoopSet, operations, isload, iscompute, isstore, isconstant,
    constant, memload, compute, memstore, instruction,
    loopdependencies, reduceddependencies, reducedchildren,
    vptr, name, lower, parents, identifier,
    Operation,
    add_op!, add_load!, add_store!,
    ArrayReferenceMeta, ArrayReference,
    NOPARENTS, NOTAREFERENCE, NODEPENDENCY

include("diffrule_operation.jl")

struct ∂LoopSet
    fls::LoopSet
    rls::LoopSet
    lsold::LoopSet
    tracked_ops::Vector{Bool}
    visited_ops::Vector{Bool}
    fops::Vector{Operation}
    ropsargs::Vector{Vector{Operation}}
    diffops::Vector{DiffRuleOperation}
    stored_ops::Vector{Int}
    opsparentsfirst::Vector{Int}
    temparrays::Vector{ArrayReferenceMeta}
    # model::Model
end
function copyloopmeta!(lsdest::LoopSet, lssrc::LoopSet)
    append!(lsdest.loopsymbols, lssrc.loopsymbols)
    append!(lsdest.loops, lssrc.loops)
end
function copypreamblemeta!(lsdest::LoopSet, lssrc::LoopSet)
    append!(lsdest.preamble.args, lssrc.preamble.args)
    append!(lsdest.prepreamble.args, lssrc.prepreamble.args)end
function copymeta!(lsdest::LoopSet, lssrc::LoopSet)
    copypreamblemeta!(lsdest, lssrc)
    append!(lsdest.syms_aliasing_refs, lssrc.syms_aliasing_refs)
    append!(lsdest.refs_aliasing_syms, lssrc.refs_aliasing_syms)
    append!(lsdest.preamble_symsym, lssrc.preamble_symsym)
    append!(lsdest.preamble_symint, lssrc.preamble_symint)
    append!(lsdest.preamble_symfloat, lssrc.preamble_symfloat)
    append!(lsdest.preamble_zeros, lssrc.preamble_zeros)
    append!(lsdest.preamble_funcofeltypes, lssrc.preamble_funcofeltypes)
    append!(lsdest.includedarrays, lssrc.includedarrays)
    append!(lsdest.outer_reductions, lssrc.outer_reductions)
    copyloopmeta!(lsdest, lssrc)
    nothing
end
function ∂LoopSet_init(lsold::LoopSet)
    mod = lsold.mod; nops = length(operations(lsold))
    ∂LoopSet(
        LoopSet(mod), LoopSet(mod), lsold,
        fill(false, nops), fill(false, nops),
        sizehint!(Operation[], nops),
        [Operation[] for _ ∈ 1:nops],
        # sizehint!(DiffRuleOperation[], nops),
        Vector{DiffRuleOperation}(undef, nops),
        fill(-1, nops), sizehint!(Int[], nops),
        ArrayReferenceMeta[]
    )
end
function ∂LoopSet(lsold::LoopSet, tracked_vars)
    nops = length(operations(lsold))
    ∂ls = ∂LoopSet_init(lsold)
    resize!(∂ls.fls.operations, nops)
    copymeta!(∂ls.fls, lsold)
    copypreamblemeta!(∂ls.rls, lsold)
    copyloopmeta!(∂ls.rls, lsold)
    determine_parents_first_order!(∂ls)
    determine_stored_computations!(∂ls)
    update_tracked!(∂ls, tracked_vars)
    forward_pass!(∂ls)
    reverse_pass!(∂ls)
    ∂ls
end
firstpass(∂ls::∂LoopSet) = ∂ls.fls
secondpass(∂ls::∂LoopSet) = ∂ls.rls
LoopVectorization.lower(∂ls::∂LoopSet) = lower(firstpass(∂ls))
∂lower(∂ls::∂LoopSet) = lower(secondpass(∂ls))
istracked(∂ls::∂LoopSet, i::Int) = ∂ls.tracked_ops[i]
istracked(∂ls::∂LoopSet, op::Operation) = ∂ls.tracked_ops[identifier(op)]
visited(∂ls::∂LoopSet, i::Int) = ∂ls.visited_ops[i]
visited(∂ls::∂LoopSet, op::Operation) = ∂ls.visited_ops[identifier(op)]
track!(∂ls::∂LoopSet, i::Int) = (∂ls.tracked_ops[i] = true)
track!(∂ls::∂LoopSet, op::Operation) = (∂ls.tracked_ops[identifier(op)] = true)
visit!(∂ls::∂LoopSet, i::Int) = (∂ls.visited_ops[i] = true)
visit!(∂ls::∂LoopSet, op::Operation) = (∂ls.visited_ops[identifier(op)] = true)

function append_parent_ids_first!(opsparentsfirst, visited, op)
    visited[identifier(op)] && return
    for opp ∈ parents(op)
        append_parent_ids_first!(opsparentsfirst, visited, opp)
    end
    push!(opsparentsfirst, identifier(op))
    visited[identifier(op)] = true
    return
end
function determine_parents_first_order!(opsparentsfirst::Vector{Int}, visited::Vector{Bool}, ops::Vector{Operation})
    for op ∈ ops
        append_parent_ids_first!(opsparentsfirst, visited, op)
    end
    opsparentsfirst
end
function determine_parents_first_order!(∂ls::∂LoopSet)
    @unpack lsold, visited_ops, opsparentsfirst = ∂ls
    fill!(visited_ops, false)
    determine_parents_first_order!(opsparentsfirst, visited_ops, operations(lsold))
end

function determine_stored_computations!(∂ls::∂LoopSet)
    @unpack lsold, stored_ops = ∂ls
    for op ∈ operations(lsold)
        if isstore(op)
            parent = first(parents(op))
            opid = identifier(op)
            stored_ops[opid] = opid
            stored_ops[identifier(parent)] = opid
        end
    end
end

function istracked(op::Operation, tracked_ops::Vector{Bool}, tracked_vars::Set{Symbol})
    tracked_ops[identifier(op)] && return true
    (isload(op) && op.ref.ref.array ∈ tracked_vars) && return true
    any(opp -> istracked(opp, tracked_ops, tracked_vars), parents(op))
end
function update_tracked!(∂ls::∂LoopSet, tracked_vars)
    @unpack lsold, tracked_ops, visited_ops, opsparentsfirst = ∂ls
    fill!(visited_ops, false)
    ops = operations(lsold)
    for i ∈ lsold.outer_reductions
        tracked_ops[i] = true
        # track!(vartracker, name(ops[i]))
        # push!(tracked_vars
    end
    for i ∈ opsparentsfirst
        op = ops[i]
        if istracked(op, tracked_ops, tracked_vars)
            tracked_ops[i] = true
            isstore(op) || push!(tracked_vars, name(op))
        end
    end
end

include("forward_pass.jl")
include("reverse_pass.jl")

end # module


text:
using SexSpecificMigrations
using ForwardPopGenSimulations

using Base.Test

const fpgs = ForwardPopGenSimulations
const sm = SexSpecificMigrations

chrs = LocusList(
    [(Autosome, 2),
     (Autosome, 3),
     (XChromosome, 1),
     (YChromosome, 2),
     (Mitochondrion, 1)]
)
@test [chr for chr in chrs] == chrs.val
chrs = LocusList(
    (Autosome, 2),
    (Autosome, 3),
    (XChromosome, 1),
    (YChromosome, 2),
    (Mitochondrion, 1)
)
@test length(chrs) == 5
@test [chr for chr in chrs] == chrs.val

popsize = PopulationSizes(1=>(Female=>4, Male=>5), 2=>(Male=>6, Female=>7))
mut = MutationRates(Female=>0.5, Male=>0.2)
rec = RecombinationRates(Male=>0.2, Female=>0.1)
propm = ProportionsOfMigrants(1=>(Female=>0.2, Male=>0.3), 2=>(Female=>0.01, Male=>0.02))
fitm = FitnessOfMigrants(1=>(Female=>0.9, Male=>1.0), 2=>(Female=>0.2, Male=>0.1))
fitt = FitnessOfForeignTraits(1=>(Female=>0.8, Male=>0.9), 2=>(Female=>0.7, Male=>0.6))
tt = TraitTransmissionRates(Mother=>(Daughter=>0.2, Son=>0.3), Father=>(Daughter=>0.4, Son=>0.5))
@test length(popsize) == 2
@test length(propm) == 2
@test length(fitm) == 2
@test length(fitt) == 2
@test popsize[1] == (4, 5)
@test popsize[2] == (7, 6)
@test mut[1] == 0.5
@test mut[2] == 0.2
@test rec[1] == 0.1
@test rec[2] == 0.2
@test propm[1] == (0.2, 0.3)
@test propm[2] == (0.01, 0.02)
@test fitm[1] == (0.9, 1.0)
@test fitm[2] == (0.2, 0.1)
@test fitt[1] == (0.8, 0.9)
@test fitt[2] == (0.7, 0.6)
@test tt[1] == (0.2, 0.3)
@test tt[2] == (0.4, 0.5)
traits = Array{Float64}(10000)
for i = eachindex(traits)
    traits[i] = sm.trait(tt[1][1], tt[2][1], 1, 2, 1)
end
@test 0.35 < countnz(traits .== 2) / 10000 < 0.45
for i = eachindex(traits)
    traits[i] = sm.trait(tt[1][1], tt[2][1], 2, 1, 1)
end
@test 0.15 < countnz(traits .== 2) / 10000 < 0.25
for i = eachindex(traits)
    traits[i] = sm.trait(tt[1][1], tt[2][1], 2, 2, 1)
end
@test 0.47 < countnz(traits .== 2) / 10000 < 0.57

llist   = LocusList((Autosome, 1), (XChromosome, 1), (YChromosome, 1), (Mitochondrion, 1))
popsize = PopulationSizes(1=>(Female=>2, Male=>2), 2=>(Female=>2, Male=>2))
mut     = MutationRates(Female=>0.1, Male=>0.2)
rec     = RecombinationRates(Female=>0.2, Male=>0.3)
propm   = ProportionsOfMigrants(1=>(Female=>0.3, Male=>0.2), 2=>(Female=>0.2, Male=>0.1))
fitm    = FitnessOfMigrants(1=>(Female=>0.8, Male=>0.9), 2=>(Female=>0.7, Male=>0.6))
fitt    = FitnessOfForeignTraits(1=>(Female=>0.5, Male=>0.6), 2=>(Female=>0.6, Male=>0.7))
tt      = TraitTransmissionRates(Mother=>(Daughter=>0.9, Son=>0.2), Father=>(Daughter=>0.3, Son=>0.9))
params = ModelParameters(llist, popsize, mut, rec, propm, fitm, fitt, tt)
pops = sm.createpopulations(params)
@test length(pops) == 2
sexes = [Female, Male]
muts = [0.1, 0.2]
recs = [0.5, 0.5, 0.5, 0.5]
chrts = [Autosome, XChromosome, YChromosome, Mitochondrion]
for deme in eachindex(pops)
    for i in 1:2
        # For some reason, I can't compare SexSpecificPopulation to another with equality,
        # so I resorted to compare those field-by-field.

        @test sm.get(pops[deme][i], :deme) == deme
        @test size(pops[deme][i].data) == (2, 2 * 4)
        @test length(pops[deme][i]) == 2
        @test sm.get(pops[deme][i], :number_of_loci) == 4
        @test sm.get(pops[deme][i], :sex) == sexes[i]
        @test sm.get(pops[deme][i], :mutation_rate) == muts[i]
        for j = 1:sm.get(pops[deme][i], :number_of_loci)
            @test sm.get(pops[deme][i], :recombination_rate, j) == recs[j]
            @test sm.get(pops[deme][i], :type_of_locus, j) == chrts[j]
        end
        @test sm.get(pops[deme][i], :proportion_of_migrant) == propm[deme][i]
        @test sm.get(pops[deme][i], :fitness_of_foreign_trait) == fitt[deme][i]
        @test sm.get(pops[deme][i], :fitness_of_migrant) == fitm[deme][i]
        @test sm.get(pops[deme][i], :transmission_rate_of_trait, Daughter) == tt[i][1]
        @test sm.get(pops[deme][i], :transmission_rate_of_trait, Son) == tt[i][2]
        for j in 1:length(pops[deme][i])
            @test pops[deme][i][Val{:trait}, j] == deme
            pops[deme][i][Val{:trait}, j] = deme + 1
            @test pops[deme][i][Val{:trait}, j] == deme + 1
        end
        for j in eachindex(pops[deme][i])
            @test pops[deme][i][j] == 0
            pops[deme][i][j] = i
            @test pops[deme][i][j] == i
        end
        for j = 1:2, k = 1:2
            pops[deme][i][j, k] = 10 * j + k
            @test pops[deme][i][j, k] == 10 * j + k
        end
    end
end

pops = sm.createpopulations(params)
core = sm.BasicData()
@test core.state == 0
sm.initialize!(core, pops)
ans = (
    ([1 3 0 5 6 8 0 10; 2 4 0 0 7 9 0 0],
     [11 13 0 0 15 17 0 0; 12 0 14 0 16 0 18 0]),
    ([19 21 0 23 24 26 0 28; 20 22 0 0 25 27 0 0],
     [29 31 0 0 33 35 0 0; 30 0 32 0 34 0 36 0])
 )
@test pops[1][1].data == ans[1][1]
@test pops[1][2].data == ans[1][2]
@test pops[2][1].data == ans[2][1]
@test pops[2][2].data == ans[2][2]
@test maximum(collect(keys(sm.db(core)))) == 36
@test core.state == 36

for i = 1:2, j = 1:2
    for k in eachindex(pops[i][j].data)
        pops[i][j].data[k] = pops[i][j].data[k] == 0 ? 0 : pops[i][j].data[k] + 100
    end
end
for key in sort(collect(keys(core.db)))
    key == 0 && continue
    core.db[key + 100] = core.db[key]
    if core.db[key].parent !== UndefGene
        core.db[key + 100].parent = core.db[core.db[key].parent.id + 100]
    end
end
for key in filter(x -> 0 < x < 100, keys(core.db))
    delete!(core.db, key)
end
core = sm.reinitialize!(core, pops)
@test core.state == 36
@test pops[1][1].data == ans[1][1]
@test pops[1][2].data == ans[1][2]
@test pops[2][1].data == ans[2][1]
@test pops[2][2].data == ans[2][2]

nreps = 10000
rates = (0.0, 0.5, 1.0)
sexes = (Female, Male)
chrvec = Array{Int}(nreps)
statevec = Array{Bool}(nreps)
for psex in sexes, csex in sexes, rate in rates
    for i = eachindex(chrvec)
        chr, state = sm.selectchr(1, rate, Val{Autosome}, Val{psex}, Val{csex})
        chrvec[i] = chr
        statevec[i] = state
    end
    @test rate - 0.05 < countnz(chrvec[:] .== 2) / nreps < rate + 0.05
    @test all(statevec[:] .== true) == true
end

for csex in sexes, rate in rates
    for i = eachindex(chrvec)
        chr, state = sm.selectchr(1, rate, Val{XChromosome}, Val{Female}, Val{csex})
        chrvec[i] = chr
        statevec[i] = state
    end
    @test rate - 0.05 < countnz(chrvec[:] .== 2) / nreps < rate + 0.05
    @test all(statevec[:] .== true) == true

    chr, state = sm.selectchr(1, rate, Val{XChromosome}, Val{Male}, Val{csex})
    if csex == Female
        @test chr == 1
        @test state == true
    else
        @test chr == 1
        @test state == false
    end
end

for psex in sexes, csex in sexes, rate in rates
    chr, state = sm.selectchr(1, rate, Val{YChromosome}, Val{psex}, Val{csex})
    if psex == Male && csex == Male
        @test chr == 2
        @test state == true
    else
        @test chr == 1
        @test state == false
    end

    chr, state = sm.selectchr(1, rate, Val{Mitochondrion}, Val{psex}, Val{csex})
    if psex == Female && csex == Female
        @test chr == 1
        @test state == true
    else
        @test chr == 1
        @test state == false
    end
end

params2 = ModelParameters(
    chrs,
    PopulationSizes(1=>(Female=>2, Male=>2), 2=>(Female=>2, Male=>2)),
    MutationRates(Female=>0.1, Male=>0.2),
    RecombinationRates(Female=>0.2, Male=>0.3),
    ProportionsOfMigrants(1=>(Female=>0.3, Male=>0.2), 2=>(Female=>0.2, Male=>0.1)),
    FitnessOfMigrants(1=>(Female=>0.8, Male=>0.9), 2=>(Female=>0.7, Male=>0.6)),
    FitnessOfForeignTraits(1=>(Female=>0.5, Male=>0.6), 2=>(Female=>0.6, Male=>0.7)),
    TraitTransmissionRates(Mother=>(Daughter=>0.9, Son=>0.2), Father=>(Daughter=>0.3, Son=>0.9))
)
chrs = LocusList(
    [(Autosome, 2),
     (Autosome, 3),
     (XChromosome, 1),
     (YChromosome, 2),
     (Mitochondrion, 1)]
)

core = BasicData()
core.state = 1
core.t = 1
root = fpgs.GeneRecord(1, 1)
root.id = 1
insert!(core.db, root)

# sm.mutate!(core, 1, 0.0, 1, 1)
# @test core.state == 1
# @test haskey(core.db, 2) == true
# @test core.db[2].state == 1
# @test core.db[2].epoch == 1
# @test core.db[2].event == fpgs.Transmission()
# core.t = 2
# sm.mutate!(core, 2, 1.0, 1, 1)
# @test core.state == 2
# @test core.db[3].state == 2
# @test core.db[3].epoch == 2
# @test core.db[3].event == fpgs.Mutation()
# @test core.db[3].parent == core.db[2]

function init(params)
    core = BasicData()
    sm.settmax!(core, 10)
    parpops = sm.createpopulations(params)
    sm.initialize!(core, parpops)
    chpops  = sm.createpopulations(params)
    parpops, chpops, core #, cr
end

ffa1, ffa2 = [0, 0], [0, 0]
mfa1, mfa2 = [0, 0], [0, 0]
ffx1, ffx2 = [0, 0], [0, 0]
fma1, fma2 = [0, 0], [0, 0]
mma1, mma2 = [0, 0], [0, 0]
for i = 1:10000
    parpops, chpops, core = init(params)
    sm.reproduce!(core, parpops[1], (1, 1), chpops[1][1], 1)
    gdb = sm.db(core)
    pid = gdb[chpops[1][1][1,1]].parent.id
    if pid == parpops[1][1][1,1]
        ffa1[1] += 1
    elseif pid == parpops[1][1][2,1]
        ffa1[2] += 1
    end
    pid = gdb[chpops[1][1][2,1]].parent.id
    if pid == parpops[1][2][1,1]
        mfa1[1] += 1
    elseif pid == parpops[1][2][2,1]
        mfa1[2] += 1
    end
    pid = gdb[chpops[1][1][1,2]].parent.id
    if pid == parpops[1][1][1,2]
        ffx1[1] += 1
    elseif pid == parpops[1][1][2,2]
        ffx1[2] += 1
    end
    pid = gdb[chpops[1][1][2,2]].parent.id
    @test pid == parpops[1][2][1,2]
    pid = gdb[chpops[1][1][1,4]].parent.id
    @test pid == parpops[1][1][1,4]
    @test chpops[1][1][1,3] == chpops[1][1][2,3] == chpops[1][1][2,4] == 0

    sm.reproduce!(core, parpops[2], (2, 2), chpops[1][1], 2)
    pid = gdb[chpops[1][1][1,5]].parent.id
    if pid == parpops[2][1][1,5]
        ffa2[1] += 1
    elseif pid == parpops[2][1][2,5]
        ffa2[2] += 1
    end
    pid = gdb[chpops[1][1][2,5]].parent.id
    if pid == parpops[2][2][1,5]
        mfa2[1] += 1
    elseif pid == parpops[2][2][2,5]
        mfa2[2] += 1
    end
    pid = gdb[chpops[1][1][1,6]].parent.id
    if pid == parpops[2][1][1,6]
        ffx2[1] += 1
    elseif pid == parpops[2][1][2,6]
        ffx2[2] += 1
    end
    pid = gdb[chpops[1][1][2,6]].parent.id
    @test pid == parpops[2][2][1,6]
    pid = gdb[chpops[1][1][1,8]].parent.id
    @test pid == parpops[2][1][1,8]
    @test chpops[1][1][1,7] == chpops[1][1][2,7] == chpops[1][1][2,8] == 0

    sm.reproduce!(core, parpops[1], (1, 1), chpops[1][2], 1)
    pid = gdb[chpops[1][2][1,1]].parent.id
    if pid == parpops[1][1][1,1]
        fma1[1] += 1
    elseif pid == parpops[1][1][2,1]
        fma1[2] += 1
    end
    pid = gdb[chpops[1][2][2,1]].parent.id
    if pid == parpops[1][2][1,1]
        mma1[1] +=1
    elseif pid == parpops[1][2][2,1]
        mma1[2] += 1
    end
    pid = gdb[chpops[1][2][2,3]].parent.id
    @test pid == parpops[1][2][2,3]
    @test chpops[1][2][2,2] == chpops[1][2][1,3] == chpops[1][2][1,4] == chpops[1][2][2,4] == 0

    sm.reproduce!(core, parpops[2], (2, 2), chpops[1][2], 2)
    pid = gdb[chpops[1][2][1,5]].parent.id
    if pid == parpops[2][1][1,5]
        fma2[1] += 1
    elseif pid == parpops[2][1][2,5]
        fma2[2] += 1
    end
    pid = gdb[chpops[1][2][2,5]].parent.id
    if pid == parpops[2][2][1,5]
        mma2[1] += 1
    elseif pid == parpops[2][2][2,5]
        mma2[2] += 1
    end
    pid = gdb[chpops[1][2][2,7]].parent.id
    @test pid == parpops[2][2][2,7]
    @test chpops[1][2][2,6] == chpops[1][2][1,7] == chpops[1][2][1,8] == chpops[1][2][2,8] == 0
end

@test sum(ffa1) == sum(ffa2) == sum(mfa1) == sum(mfa2) == sum(ffx1) == sum(ffx2) == 10000
@test sum(fma1) == sum(fma2) == sum(mma1) == sum(mma2) == 10000
@test 0.45 <= ffa1[1] / sum(ffa1) <= 0.55
@test 0.45 <= mfa1[1] / sum(mfa1) <= 0.55
@test 0.45 <= ffx1[1] / sum(ffx1) <= 0.55
@test 0.45 <= ffa2[1] / sum(ffa2) <= 0.55
@test 0.45 <= mfa2[1] / sum(mfa2) <= 0.55
@test 0.45 <= ffx2[1] / sum(ffx2) <= 0.55
@test 0.45 <= fma1[1] / sum(mfa1) <= 0.55
@test 0.45 <= mma1[1] / sum(mma1) <= 0.55
@test 0.45 <= fma2[1] / sum(mfa2) <= 0.55
@test 0.45 <= mma2[1] / sum(mma2) <= 0.55

parpops, chpops, core = init(params)
sm.initialize!(core, pops)

data = Array{Int}(16)
sm.listgenes!(data, parpops, 1)
@test data == [1, 2, 6, 7, 11, 12, 15, 16, 19, 20, 24, 25, 29, 30, 33, 34]
data = Array{Int}(12)
sm.listgenes!(data, parpops, 2)
@test data == [3, 4, 8, 9, 13, 17, 21, 22, 26, 27, 31, 35]
data = Array{Int}(4)
sm.listgenes!(data, parpops, 3)
@test data == [14, 18, 32, 36]
sm.listgenes!(data, parpops, 4)
@test data == [5, 10, 23, 28]


text:
using Plots

using FourierGPE
## Initialize simulation
L = (18.0,18.0)
N = (128,128)
sim = Sim(L,N)
@unpack_Sim sim

## set simulation parameters
μ = 12.0

# Time dependent potential function (here trivial t dep)
import FourierGPE.V
V(x,y,t) = 0.5*(x^2 + y^2)
ψ0(x,y,μ,g) = sqrt(μ/g)*sqrt(max(1.0-V(x,y,0.0)/μ,0.0)+im*0.0)

## make initial state
x,y = X
ψi = ψ0.(x,y',μ,g)
ϕi = kspace(ψi,sim)
@pack_Sim! sim

## evolve
@time sol = runsim(sim)

## ground state
ϕg = sol[end]
ψg = xspace(ϕg,sim)
showpsi(x,y,ψg)

## set simulation parameters
γ = 0.0
t = LinRange(ti,tf,Nt)
ϕi = kspace(ψg,sim)
# reltol = 1e-7
# alg = DP5()

## vortex
using VortexDistributions
R(w) = sqrt(2*μ/w^2)
R(1)
rv = 3.
healinglength(x,y,μ,g) = 1/sqrt(g*abs2(ψ0(x,y,μ,g)))
ξ0 = healinglength.(0.,0.,μ,g)
ξ = healinglength(rv,0.,μ,g)

pv = PointVortex(rv,0.,1)
vi = ScalarVortex(ξ,pv)

psi = Torus(copy(ψg),x,y)
vortex!(psi,vi)

ψi .= psi.ψ
ϕi = kspace(ψi,sim)
showpsi(x,y,psi.ψ)
## compare precession with Fetter JLTP 2010
ξ = 1/sqrt(μ)
Rtf = R(1)
Ωm = 3*log(Rtf/ξ/sqrt(2))/2/Rtf^2
Ωv = Ωm/(1-rv^2/Rtf^2)

# or a precession period
Tv = 2*pi/Ωv

ti = 0.; tf = Tv
t = LinRange(ti,tf,Nt)

@pack_Sim! sim
@time solv = runsim(sim)

## analyse
ϕf = solv[end]
ψf = xspace(ϕf,sim)

showpsi(x,y,ψf)

anim = @animate for i in eachindex(t)
    ψ = xspace(solv[i],sim)
    showpsi(x,y,ψ)
end

saveto = joinpath(@__DIR__,"vortex_precession.gif")
gif(anim,saveto,fps=25)


text:
#
# An example of using this package to (not quite) reproduce hbai ineq time-series from:
# https://www.gov.uk/government/statistics/households-below-average-income-199495-to-201819
# for GB and also Scotland, for income and also wages.
# See:
# Data Tables->income-values-and-inequality-measures-hbai-1994-95-2018-19-tables.ods
# sheets 2_2tsBHC/AHC
#
# We're using hbai household level data data 1994/5->2018/19 from [UKDS](https://www.ukdataservice.ac.uk/)
# (series UKDA-5828).
#
#
# An example of using this package to (not quite) reproduce hbai ineq time-series from:
# https://www.gov.uk/government/statistics/households-below-average-income-199495-to-201819
# for GB and also Scotland, for income and also wages.
# See:
# Data Tables->income-values-and-inequality-measures-hbai-1994-95-2018-19-tables.ods
# sheets 2_2tsBHC/AHC
#
# We're using hbai household level data data 1994/5->2018/19 from [UKDS](https://www.ukdataservice.ac.uk/)
# (series UKDA-5828).
#
# dependencies: run these once only
using Pkg
Pkg.add( "PovertyAndInequalityMeasures" )
Pkg.add( "DataFrames" )
Pkg.add( "CSV" )
Pkg.add( "Plots" )
Pkg.add( "PyPlot" )
Pkg.add( "StatsKit" )
Pkg.add( "RegressionTables" )
Pkg.add( "GLM" )

using StatsKit
using RegressionTables
using GLM
using PovertyAndInequalityMeasures
using DataFrames
using Plots
using CSV
# using PyPlot

# note PyPlot is only 1 of several possible rendering engines
pyplot()
Plots.PyPlotBackend()

# .. change this, obvs:
datadir="/mnt/data/hbai/tab/"
start_year = 1994
end_year = 2018
n = end_year - start_year + 1

# dataframe for annual results
out=DataFrame(
    year=zeros(Int,n),
    scot_pop=zeros(n),
    sco_gini_bhc=zeros(n),
    sco_gini_ahc=zeros(n),
    sco_gini_wage=zeros(n),
    sco_palma_bhc=zeros(n),
    sco_palma_ahc=zeros(n),
    gb_pop=zeros(n),
    gb_gini_bhc=zeros(n),
    gb_gini_ahc=zeros(n),
    gb_palma_bhc=zeros(n),
    gb_palma_ahc=zeros(n),
    sco_atkinson_ahc_1=zeros(n),
    sco_atkinson_bhc_1=zeros(n),
    sco_atkinson_ahc_2=zeros(n),
    sco_atkinson_bhc_2=zeros(n)
    )

r = 0
for y in start_year:end_year
    global r
    r += 1
    # filenames weirdness ..
    post = (y in 2002:2016) ? "_g4" : ""
    pre = y >= 2017 ? "h" : "hbai"
    fn = "$(datadir)$(pre)"*"$y"[3:end]*"$(y+1)"[3:end]*"$post.tab"
    println(fn)
    # load each year & jam varnames to lower case
    hbai =  CSV.File(fn)|>DataFrame
    lcnames = Symbol.(lowercase.(string.(names(hbai))))
    rename!(hbai, lcnames)
    # make scottish subset
    scot = hbai[(hbai.gvtregn .== 12),:]
    # sub-subset with just positive wages for hhlds - crude measure of
    # wage inequality - better to use individual level series
    scot_wages = scot[(scot.egrernhh.>0),:]
    # make gb subset, since nireland included from 2002 on, but not before
    gb = hbai[(hbai.gvtregn .!== 13),:]
    # make scottish ineq with: bhc and ahc incomes; oecd equiv; individual weights
    sco_ineq_bhc = make_inequality( scot,:gs_newpp,:s_oe_bhc )
    sco_ineq_ahc = make_inequality( scot,:gs_newpp,:s_oe_ahc )
    # inequality of gross wages for hh
    sco_ineq_wage = make_inequality( scot_wages,:gs_newpp,:egrernhh ) # very rough need indiv level really
    sco_popn = sum( scot.gs_newpp )
    #ditto gb
    gb_ineq_bhc = make_inequality( gb,:gs_newpp,:s_oe_bhc )
    gb_ineq_ahc = make_inequality( gb,:gs_newpp,:s_oe_ahc )
    gb_popn = sum( gb.gs_newpp )
    @assert gb_popn ≈ gb_ineq_ahc.total_population

    out[r, :year] = y
    out[r, :scot_pop] = sco_popn
    out[r, :sco_gini_bhc] = sco_ineq_bhc.gini
    out[r, :sco_gini_ahc] = sco_ineq_ahc.gini
    out[r, :sco_gini_wage] = sco_ineq_wage.gini
    out[r, :gb_pop] = gb_popn
    out[r, :gb_gini_bhc] = gb_ineq_bhc.gini
    out[r, :gb_gini_ahc] =  gb_ineq_ahc.gini
    out[r, :sco_palma_bhc] = sco_ineq_bhc.palma
    out[r, :sco_palma_ahc] = sco_ineq_ahc.palma
    out[r, :gb_palma_bhc] = gb_ineq_bhc.palma
    out[r, :gb_palma_ahc] = gb_ineq_ahc.palma
    out[r, :sco_atkinson_ahc_1] = sco_ineq_ahc.atkinson[4]
    out[r, :sco_atkinson_bhc_1] = sco_ineq_bhc.atkinson[4]
    out[r, :sco_atkinson_ahc_2] = sco_ineq_ahc.atkinson[8]
    out[r, :sco_atkinson_bhc_2] = sco_ineq_bhc.atkinson[8]
end

out.snp = out.year .>= 2007 # snp dummy

gini = plot(
    out.year,
    [out.sco_gini_ahc,out.sco_gini_bhc],
    title="Scotland: Gini",
    ylims=(0.2,0.5),
    labels=["AHC" "BHC"])

palma = plot(
    out.year,
    [out.sco_palma_ahc,out.sco_palma_bhc],
    title="Scotland: Palma",
    ylims=(0,2),
    labels=["AHC" "BHC"])

wage  = plot(
    out.year,
    [out.sco_gini_wage],
    title="Scotland: Wage Gini",
    ylims=(0.2,0.5),
    labels="Wages")

atkin = plot(
    out.year,
    [out.sco_atkinson_ahc_1, out.sco_atkinson_bhc_1,
        out.sco_atkinson_ahc_2, out.sco_atkinson_bhc_2 ],
    title="Atkinson α=1,2",
    labels=["AHC α=1" "BHC α=1" "AHC α=2" "BHC α=2"])

sc_gb_gini = plot(
    out.year,
    [out.sco_gini_ahc, out.sco_gini_bhc, out.gb_gini_ahc, out.gb_gini_bhc],
    ylims=(0.2,0.5),
    title="Scotland vs GB (Gini)",
    labels=[ "Sco:AHC" "Sco:BHC" "GB:AHC" "GB:BHC"])

grid = plot( gini, palma, wage, sc_gb_gini, layout = (2, 2),
    legend = :outerbottomright,
    size=(1024,640),
    # legendfontsize=6,
    titlefontsize=8 )
# .. and so on

savefig(grid, "~/tmp/scotland_inequality.svg" )

savefig(grid, "~/tmp/scotland_inequality.png")

savefig(grid, "~/tmp/scotland_inequality.pdf" )

sco_gini_r1 = GLM.lm( @formula( sco_gini_ahc ~ year ), out )
sco_gini_r2 = GLM.lm( @formula( sco_gini_bhc ~ year ), out )
sco_gini_r3 = GLM.lm( @formula( sco_gini_ahc ~ year+snp ), out )
sco_gini_r4 = GLM.lm( @formula( sco_gini_bhc ~ year+snp ), out )
sco_gini_r5 = GLM.lm( @formula( log(sco_gini_ahc) ~ year ), out )
sco_gini_r6 = GLM.lm( @formula( log(sco_gini_bhc) ~ year ), out )
sco_gini_r7 = GLM.lm( @formula( log(sco_gini_ahc) ~ year+snp ), out )
sco_gini_r8 = GLM.lm( @formula( log(sco_gini_bhc) ~ year+snp ), out )

sco_palma_r1 = GLM.lm( @formula( sco_palma_ahc ~ year ), out )
sco_palma_r2 = GLM.lm( @formula( sco_palma_bhc ~ year ), out )
sco_palma_r3 = GLM.lm( @formula( sco_palma_ahc ~ year+snp ), out )
sco_palma_r4 = GLM.lm( @formula( sco_palma_bhc ~ year+snp ), out )
sco_palma_r5 = GLM.lm( @formula( log(sco_palma_ahc) ~ year ), out )
sco_palma_r6 = GLM.lm( @formula( log(sco_palma_bhc) ~ year ), out )
sco_palma_r7 = GLM.lm( @formula( log(sco_palma_ahc) ~ year+snp ), out )
sco_palma_r8 = GLM.lm( @formula( log(sco_palma_bhc) ~ year+snp ), out )

regtable( sco_gini_r1, sco_gini_r2, sco_gini_r3, sco_gini_r4; renderSettings=latexOutput("/home/graham_s/tmp/gini.tex" ))
regtable( sco_gini_r1, sco_gini_r2, sco_gini_r3, sco_gini_r4; renderSettings=asciiOutput("/home/graham_s/tmp/gini.txt" ))

# regtable( sco_gini_r5, sco_gini_r6, sco_gini_r7, sco_gini_r8 )
regtable( sco_palma_r1, sco_palma_r2, sco_palma_r3, sco_palma_r4; renderSettings=latexOutput( "/home/graham_s/tmp/palma.tex" ))
regtable( sco_palma_r1, sco_palma_r2, sco_palma_r3, sco_palma_r4; renderSettings=asciiOutput("/home/graham_s/tmp/palma.txt" ))
regtable( sco_palma_r5, sco_palma_r6, sco_palma_r7, sco_palma_r8 )


text:
using Parameters

abstract type AbstractThermoUncertainty end

struct EmptyThermoUncertainty <: AbstractThermoUncertainty end

@with_kw struct ConstantThermoUncertainty <: AbstractThermoUncertainty
    u::AbstractFloat = nothing
end
(c::ConstantThermoUncertainty)(T::AbstractFloat=nothing) = c.u


text:
# ---
# title: 304. Range Sum Query 2D - Immutable
# id: problem304
# author: Qling
# date: 2021-03-02
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/range-sum-query-2d-immutable/description/>
# hidden: true
# ---
#
# Given a 2D matrix _matrix_ , find the sum of the elements inside the rectangle
# defined by its upper left corner ( _row_ 1, _col_ 1) and lower right corner (
# _row_ 2, _col_ 2).
#
# ![Range Sum Query 2D](https://assets.leetcode.com/static/images/courses/range_sum_query_2d.png)
# The above rectangle (with the red border) is defined by (row1, col1) = **(2,
# 1)** and (row2, col2) = **(4, 3)** , which contains sum = **8**.
#
# **Example:**
#
#
#
#     Given matrix = [
#       [3, 0, 1, 4, 2],
#       [5, 6, 3, 2, 1],
#       [1, 2, 0, 1, 5],
#       [4, 1, 0, 1, 7],
#       [1, 0, 3, 0, 5]
#     ]
#
#     sumRegion(2, 1, 4, 3) -> 8
#     sumRegion(1, 1, 2, 2) -> 11
#     sumRegion(1, 2, 2, 4) -> 12
#
#
# **Note:**
#
#   1. You may assume that the matrix does not change.
#   2. There are many calls to _sumRegion_ function.
#   3. You may assume that _row_ 1 ≤ _row_ 2 and _col_ 1 ≤ _col_ 2.
#
#
## @lc code=start
using LeetCode

struct NumMatrix
    sums::Vector{Vector{Int}}
    function NumMatrix(matrix::Vector{Vector{Int}})
        m, n = length(matrix), length(matrix[1])
        sums = [fill(0, n + 1) for _ = 1:m]

        for i = 1:m, j = 1:n
            sums[i][j+1] = sums[i][j] + matrix[i][j]
        end

        new(sums)
    end
end

sum_region(matrix::NumMatrix, row1::Int, col1::Int, row2::Int, col2::Int) =
    sum(matrix.sums[i][col2+1] - matrix.sums[i][col1] for i = row1:row2)

## @lc code=end


text:
# Test ADL features in a grid flipping domain & assembly domain
@testset "action description language (adl)" begin

path = joinpath(dirname(pathof(PDDL)), "..", "test", "adl")

domain = load_domain(joinpath(path, "flip-domain.pddl"))
@test domain.name == Symbol("flip")
problem = load_problem(joinpath(path, "flip-problem.pddl"))
@test problem.name == Symbol("flip-problem")

state = initstate(domain, problem)
implementations = [
    "concrete interpreter" => (domain, state),
    "ground interpreter" => (ground(domain, state), state),
    "concrete compiler" => compiled(domain, state),
]

@testset "flip ($name)" for (name, (domain, state)) in implementations
    state = execute(domain, state, pddl"(flip_column c1)")
    state = execute(domain, state, pddl"(flip_column c3)")
    state = execute(domain, state, pddl"(flip_row r2)")

    @test satisfy(domain, state, problem.goal) == true
end

# Test all ADL features in assembly domain
path = joinpath(dirname(pathof(PDDL)), "..", "test", "adl")

domain = load_domain(joinpath(path, "assembly-domain.pddl"))
problem = load_problem(joinpath(path, "assembly-problem.pddl"))

# Test for static fluents
static_fluents = infer_static_fluents(domain)
@test :requires in static_fluents
@test length(static_fluents) == 6

state = initstate(domain, problem)

implementations = [
    "concrete interpreter" => (domain, state),
    "ground interpreter" => (ground(domain, state), state),
    "concrete compiler" => compiled(domain, state),
]

@testset "assembly ($name)" for (name, (domain, state)) in implementations
    # Execute plan to assemble a frob

    # Commit charger to assembly of frob
    state = execute(domain, state, pddl"(commit charger frob)")
    # Once commited, we can't commit again
    @test available(domain, state, pddl"(commit charger frob)") == false

    # We can't add a tube to the frob before adding the widget and fastener
    @test available(domain, state, pddl"(assemble tube frob)") == false
    state = execute(domain, state, pddl"(assemble widget frob)")
    @test available(domain, state, pddl"(assemble tube frob)") == false
    state = execute(domain, state, pddl"(assemble fastener frob)")

    # Having added both widget and fastener, now we can add the tube
    @test available(domain, state, pddl"(assemble tube frob)") == true
    state = execute(domain, state, pddl"(assemble tube frob)")

    # We've completely assembled a frob!
    @test satisfy(domain, state, problem.goal) == true
end

end # action description language (adl)


text:
#!/usr/bin/julia
function value(list)
    result = 0
    for v in list
        result = result * 2
        result = result + v
    end
    return result
end

input_file = ARGS[1]
open(input_file) do io
    lines = readlines(io)
    sums = sum(map.(x -> parse(Int64,x),map(collect, lines)))
    half = length(lines) / 2
    bits = map(v -> v > half ? 1 : 0, sums)
    gamma = value(bits)
    epsilon = value([1-v for v in bits])
    println(gamma * epsilon)
end