text:
##############################################################################
##
## Missing values support
##
##############################################################################

"""
    allowmissing!(ds::Dataset, cols=:)

Convert columns `cols` of data set `ds` from element type `T` to
`Union{T, Missing}` to support missing values.

`cols` can be any column selector ($COLUMNINDEX_STR; $MULTICOLUMNINDEX_STR).

If `cols` is omitted all columns in the data set are converted.
"""
function allowmissing! end
#
# # Modify Dataset
function allowmissing!(ds::Dataset, col::ColumnIndex)
    colidx = index(ds)[col]
    # use _columns to avoid reseting format or grouping info
    _columns(ds)[colidx] = allowmissing(_columns(ds)[colidx])
    _modified(_attributes(ds))
    return ds
end
#
# # Modify Dataset
function allowmissing!(ds::Dataset, cols::AbstractVector{<:ColumnIndex})
    for col in cols
        allowmissing!(ds, col)
    end
    return ds
end
#
# # Modify Dataset
function allowmissing!(ds::Dataset, cols::AbstractVector{Bool})
    length(cols) == size(ds, 2) || throw(BoundsError(ds, (!, cols)))
    for (col, cond) in enumerate(cols)
        cond && allowmissing!(ds, col)
    end
    return ds
end
#
# # Modify Dataset
allowmissing!(ds::Dataset, cols::MultiColumnIndex) =
    allowmissing!(ds, index(ds)[cols])

# Modify Dataset
allowmissing!(ds::Dataset, cols::Colon=:) =
    allowmissing!(ds, axes(ds, 2))

"""
    disallowmissing!(ds::Dataset, cols=:; error::Bool=false)

Convert columns `cols` of data set `ds` from element type `Union{T, Missing}` to
`T` to drop support for missing values.

`cols` can be any column selector ($COLUMNINDEX_STR; $MULTICOLUMNINDEX_STR).

If `cols` is omitted all columns in the data set are converted.

If `error=false` then columns containing a `missing` value will be skipped instead
of throwing an error.
"""
function disallowmissing! end

# Modify Dataset
function disallowmissing!(ds::Dataset, col::ColumnIndex; error::Bool=false)
    x = _columns(ds)[col]
    colidx = index(ds)[col]
    if !(!error && Missing <: eltype(x) && any(ismissing, x))
        # use _columns to avoid reseting attributes
         _columns(ds)[colidx] = disallowmissing(x)
    end
    _modified(_attributes(ds))
    return ds
end
#
# # Modify Dataset
function disallowmissing!(ds::Dataset, cols::AbstractVector{<:ColumnIndex};
                          error::Bool=false)
    for col in cols
        disallowmissing!(ds, col, error=error)
    end
    return ds
end
#
# Modify Dataset
function disallowmissing!(ds::Dataset, cols::AbstractVector{Bool}; error::Bool=false)
    length(cols) == size(ds, 2) || throw(BoundsError(ds, (!, cols)))
    for (col, cond) in enumerate(cols)
        cond && disallowmissing!(ds, col, error=error)
    end
    return ds
end
#
# Modify Dataset
disallowmissing!(ds::Dataset, cols::MultiColumnIndex; error::Bool=false) =
    disallowmissing!(ds, index(ds)[cols], error=error)

# Modify Dataset
disallowmissing!(ds::Dataset, cols::Colon=:; error::Bool=false) =
    disallowmissing!(ds, axes(ds, 2), error=error)

"""
    repeat!(ds::Dataset; inner::Integer = 1, outer::Integer = 1, freq = nothing)
    repeat!(ds::Dataset, count) = repeat!(ds, outer = count)

Update a data set `ds` in-place by repeating its rows.

* `inner` specifies how many times each row is repeated,
* `outer` specifies how many times the full set of rows is repeated, and
* `freq` allow user to pass a vector of integers or a column name or index which indicate how many times the corresponding row should be repeated.
When `freq` is passed, the values must be positive integer values or zero (zero means the corresponding row should be dropped).

# Example
```jldoctest
julia> ds = Dataset(a = 1:2, b = 3:4)
2×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        2         4

julia> repeat!(ds, inner = 2, outer = 3);

julia> ds
12×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        1         3
   3 │        2         4
   4 │        2         4
   5 │        1         3
   6 │        1         3
   7 │        2         4
   8 │        2         4
   9 │        1         3
  10 │        1         3
  11 │        2         4
  12 │        2         4

julia> ds = Dataset(a = 1:2, b = 3:4)
2×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        2         4

julia> repeat!(ds, freq = :a)
3×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        2         4
   3 │        2         4
```
"""
function repeat!(ds::Dataset; inner::Integer = 1, outer::Integer = 1, freq::Union{AbstractVector, DatasetColumn, SubDatasetColumn, ColumnIndex, Nothing} = nothing)
    T = nrow(ds) < typemax(Int8) ? Int8 : nrow(ds) < typemax(Int32) ? Int32 : Int64
# Modify Dataset
    if freq === nothing
        inner <= 0 && throw(ArgumentError("inner keyword argument must be greater than zero"))
        outer <= 0 && throw(ArgumentError("outer keyword argument must be greater than zero"))
        r_indx = repeat(T(1):T(nrow(ds)), inner = inner, outer = outer)
        _permute_ds_after_sort!(ds, r_indx, check = false)
        _reset_grouping_info!(ds)

        _modified(_attributes(ds))
        ds
    else
        if freq isa SubDatasetColumn || freq isa DatasetColumn
            lengths = __!(freq)
        elseif freq isa ColumnIndex
            lengths = _columns(ds)[index(ds)[freq]]
        elseif freq isa AbstractVector
            lengths = freq
        end
        if !(eltype(lengths) <: Union{Missing, Integer}) || any(ismissing, lengths) || any(x->isless(x, 0), lengths)
            throw(ArgumentError("The column selected for repeating must be an integer column with all values greater than or equal to zero and with no missing values"))
        end
        if length(lengths) != nrow(ds)
            throw(ArgumentError("The length of frequencies must match the number of rows in passed data set"))
        end
        r_index = _create_index_for_repeat(lengths, Val(T))
        _permute_ds_after_sort!(ds, r_index, check = false)
        _reset_grouping_info!(ds)
        _modified(_attributes(ds))
        ds
    end
end
function _fill_index_for_repeat!(res, w)
    counter = 1
    for i in 1:length(w)
        for j in 1:w[i]
            res[counter] = i
            counter += 1
        end
    end
end
# use this to create index for new data set
# and then use getindex with the result of this function for repeating
# This should be better for repeating large data set/ since getindex is threaded
function _create_index_for_repeat(w, ::Val{T}) where T
    res = Vector{T}(undef, sum(w))
    _fill_index_for_repeat!(res, w)
    res
end

function repeat!(ds::Dataset, count::Integer)

# Modify Dataset
    count <= 0 && throw(ArgumentError("count must be greater than zero"))
    repeat!(ds, inner = 1, outer = count)
    ds
end

"""
    repeat(ds::AbstractDataset; inner::Integer = 1, outer::Integer = 1, freq = nothing, view = false)
    repeat(ds::AbstractDataset, count) = repeat!(ds, outer = count, view = false)

Variant of `repeat!` which returns a fresh copy of passed data set. If `view = true` a view of the result will be returned.

# Example
```jldoctest
julia> ds = Dataset(a = 1:2, b = 3:4)
2×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        2         4

julia> repeat(ds, inner = 2, outer = 3)
12×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        1         3
   3 │        2         4
   4 │        2         4
   5 │        1         3
   6 │        1         3
   7 │        2         4
   8 │        2         4
   9 │        1         3
  10 │        1         3
  11 │        2         4
  12 │        2         4

julia> repeat(ds, freq = :a)
3×2 Dataset
 Row │ a         b
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         3
   2 │        2         4
   3 │        2         4
```
"""
Base.repeat(ds::AbstractDataset, count::Integer; view = false) = repeat(ds, outer = count, view = view)
function Base.repeat(ds::AbstractDataset; inner::Integer = 1, outer::Integer = 1, freq = nothing, view = false)
    T = nrow(ds) < typemax(Int8) ? Int8 : nrow(ds) < typemax(Int32) ? Int32 : Int64

    if freq === nothing
        if view
            inner <= 0 && throw(ArgumentError("inner keyword argument must be greater than zero"))
            outer <= 0 && throw(ArgumentError("outer keyword argument must be greater than zero"))
            r_indx = repeat(T(1):T(nrow(ds)), inner = inner, outer = outer)
            Base.view(ds, r_indx, :)
        else
            repeat!(copy(ds), inner = inner, outer = outer)
        end
    else
        if view
            if freq isa SubDatasetColumn || freq isa DatasetColumn
                lengths = __!(freq)
            elseif freq isa ColumnIndex
                lengths = _columns(ds)[index(ds)[freq]]
            elseif freq isa AbstractVector
                lengths = freq
            end
            if !(eltype(lengths) <: Union{Missing, Integer}) || any(ismissing, lengths) || any(x->isless(x, 0), lengths)
                throw(ArgumentError("The column selected for repeating must be an integer column with all values greater than or equal to zero and with no missing values"))
            end
            if length(lengths) != nrow(ds)
                throw(ArgumentError("The length of frequencies must match the number of rows in passed data set"))
            end
            r_index = _create_index_for_repeat(lengths, Val(T))
            Base.view(ds, r_index, :)
        else
            repeat!(copy(ds), freq = freq)
        end
    end
end

# This is not exactly copy! as in general we allow axes to be different

# Modify Dataset
function _replace_columns!(ds::Dataset, newds::Dataset)
    copy!(_columns(ds), _columns(newds))
    copy!(_names(index(ds)), _names(newds))
    copy!(index(ds).lookup, index(newds).lookup)
    copy!(index(ds).format, index(newds).format)
    _copy_grouping_info!(ds, newds)
    _modified(_attributes(ds))
    _attributes(ds).meta.info[] = _attributes(newds).meta.info[]
    # created date cannot be modified
    return ds
end

"""
    map(ds::AbstractDataset, f::Function, cols; [threads = true])
    map(ds::AbstractDataset, f::Vector{Function}, cols; [threads = true])

Return a copy of `ds` where cols of the new `Dataset` is the result of calling `f` on each observation. The order of columns for the new data set is the same as `ds`.
Note that `map` guarantees not to reuse the columns from `ds` in the returned
`Dataset`. If `f` returns its argument then it gets copied before being stored.
The number of functions and the number of cols must match when multiple functions is used.

See [`map!`](@ref)

# Examples
```jldoctest
julia> ds = Dataset(x=1:4, y=11:14)
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1        11
   2 │        2        12
   3 │        3        13
   4 │        4        14

julia> map(ds, x -> x^2, :)
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1       121
   2 │        4       144
   3 │        9       169
   4 │       16       196
```
"""
function Base.map(ds::AbstractDataset, f::Function, cols::MultiColumnIndex; threads = true)
    colsidx = index(ds)[cols]
    fs = repeat([f], length(colsidx))
    map(ds, fs, cols; threads = threads)
end
Base.map(ds::AbstractDataset, f::Function, col::ColumnIndex; threads = true) = map(ds, f, [col]; threads = threads)
Base.map(ds::AbstractDataset, f::Vector{Function}; threads = true) = throw(ArgumentError("the `cols` argument cannot be left blank"))

function Base.map(ds::AbstractDataset, f::Vector{<:Function}, cols::MultiColumnIndex; threads = true)
    # Create Dataset
    ncol(ds) == 0 && return ds # skip if no columns
    colsidx = index(ds)[cols]
    @assert length(f) ==length(colsidx) "The number of functions and the number of columns must match"
    if ds isa SubDataset
        transfer_grouping_info = false
    else
        transfer_grouping_info = !any(colsidx .∈ Ref(index(ds).sortedcols))
    end
    sort_perm_colsidx = sortperm(colsidx)
    # sorted_colsidx = sort(colsidx)
    counter_f = 1
    vs = AbstractVector[]
    for j in 1:ncol(ds)
        if insorted(j, view(colsidx, sort_perm_colsidx))
            _f = f[sort_perm_colsidx[counter_f]]
            counter_f += 1
        else
            _f = identity
        end
        v = _columns(ds)[j]

        if threads
            T = Core.Compiler.return_type(_f, Tuple{eltype(v)})
            fv = _our_vect_alloc(T, length(v))
            _hp_map_a_function!(fv, _f, v)
        else
            fv = map(_f, v)
        end
        push!(vs, fv === v ? copy(fv) : fv)
    end
    if transfer_grouping_info
        newds_index = copy(index(ds))
    else
        newds_index = copy(index(ds))
        _reset_grouping_info!(newds_index)
    end
    # formats don't need to be transferred
    newds = Dataset(vs, newds_index, copycols=false)
    removeformat!(newds, cols)
    setinfo!(newds, _attributes(ds).meta.info[])
    return newds

end

"""
    map!(ds::AbstractDataset, f::Function, cols; [threads = true])
    map!(ds::AbstractDataset, f::Vector{Function}, cols; [threads = true])

Update each row of each `col` in `ds[!, cols]` in-place when `map!` return a result, and skip when it is not possible.

If `f` cannot be applied in place, use `map` for creating a copy of `ds`.

See [`map`](@ref)

# Examples

```jldoctest
julia> ds = Dataset(x=1:4, y=11:14)
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1        11
   2 │        2        12
   3 │        3        13
   4 │        4        14

julia> map!(ds, x -> x^2, :);

julia> ds
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1       121
   2 │        4       144
   3 │        9       169
   4 │       16       196

julia> ds = Dataset(x=1:4, y=11:14)
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1        11
   2 │        2        12
   3 │        3        13
   4 │        4        14

julia> map!(ds, [x -> x^2, x -> x-1], :);

julia> ds
4×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Int64     Int64
─────┼────────────────────
   1 │        1        10
   2 │        4        11
   3 │        9        12
   4 │       16        13
```
"""
function Base.map!(ds::AbstractDataset, f::Vector{<:Function}, cols::MultiColumnIndex; threads = true)
    # Create Dataset
    ncol(ds) == 0 && return ds # skip if no columns
    colsidx = index(ds)[cols]
    @assert length(f) == length(colsidx) "The number of functions and the number of columns must match"
    _reset_group = false
    # TODO needs function barrier
    number_of_warnnings = 0
    for j in 1:length(colsidx)
        CT = eltype(_columns(ds)[colsidx[j]])
        # Core.Compiler.return_type cannot handle the situations like x->ismissing(x) ? 0 : x when x is missing and float, since the output of Core.Compiler.return_type is Union{Missing, Float64, Int64}
        # we remove missing and then check the result,
        # TODO is there any problem with this?
        T = Core.Compiler.return_type(f[j], Tuple{nonmissingtype(CT)})
        T = Union{Missing, T}
        if promote_type(T, CT) <: CT
            if threads && DataAPI.refpool(_columns(ds)[colsidx[j]]) === nothing
                _hp_map!_a_function!(_columns(ds)[colsidx[j]], f[j])
            else
                map!(f[j], _columns(ds)[colsidx[j]], _columns(ds)[colsidx[j]])
            end
            # removeformat!(ds, colsidx[j])
            _modified(_attributes(ds))
            if !_reset_group && parentcols(index(ds), colsidx[j]) ∈ index(parent(ds)).sortedcols
                _reset_grouping_info!(ds)
                _reset_group = true
            end
        else
            if number_of_warnnings < 10
                @warn "cannot map `f` on ds[!, :$(_names(ds)[colsidx[j]])] in-place, the selected column is $(CT) and the result of calculation is $(T)"
                number_of_warnnings += 1
            elseif number_of_warnnings == 10
                @warn "more than 5 columns can not be replaced ...."
                number_of_warnnings += 1
            end
            continue
        end
    end
    return ds
end

function Base.map!(ds::AbstractDataset, f::Function, cols::MultiColumnIndex; threads = true)
    colsidx = index(ds)[cols]
    fs = repeat([f], length(colsidx))
    map!(ds, fs, colsidx; threads = threads)
end

Base.map!(ds::AbstractDataset, f::Union{Function}, col::ColumnIndex; threads = true) = map!(ds, f, [col]; threads = threads)
Base.map!(ds::AbstractDataset, f::Vector{<:Function}; threads = true) = throw(ArgumentError("the `cols` argument cannot be left blank"))

# the order of argument in `mask` is based on map, should we make it mask(ds, cols, fun)

"""
    mask(ds::AbstractDataset, f::Function, cols; mapformats = false, threads = true)
    mask(ds::AbstractDataset, f::Vector{Function}, cols; mapformats = false, threads = true)

Map `f` on each observation and return a `Bool` data set. When multiple `f` is provided, each one map to its corresponding column.

# Examples

```jldoctest
julia> ds = Dataset(x = [1, -1, -1],
                    y = [2, 4, 1],
                    z = [1, 2, 3])
3×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64     Int64     Int64
─────┼──────────────────────────────
   1 │        1         2         1
   2 │       -1         4         2
   3 │       -1         1         3

julia> mask(ds, [>(0), iseven], 1:2)
3×2 Dataset
 Row │ x         y
     │ identity  identity
     │ Bool      Bool
─────┼────────────────────
   1 │     true      true
   2 │    false      true
   3 │    false     false

julia> mask(ds, isodd, 1:3)
3×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Bool      Bool      Bool
─────┼──────────────────────────────
   1 │     true     false      true
   2 │     true     false     false
   3 │     true      true      true
```
"""
mask(ds::AbstractDataset, f::Function, col::ColumnIndex; mapformats = true, threads = true, missings = false) = mask(ds, f, [col]; mapformats = mapformats, threads = threads, missings = missings)
function mask(ds::AbstractDataset, f::Function, cols::MultiColumnIndex; mapformats = true, threads = true, missings = false)
  colsidx = index(ds)[cols]
  v_f = Vector{Function}(undef, length(colsidx))
  fill!(v_f, f)
  mask(ds, v_f, cols; mapformats = mapformats, threads = threads, missings = missings)
end

function mask(ds::AbstractDataset, f::Vector{<:Function}, cols::MultiColumnIndex; mapformats = true, threads = true, missings = false)
    # Create Dataset
    ncol(ds) == 0 && return ds # skip if no columns
    colsidx = index(ds)[cols]
    @assert length(f) == length(colsidx) "the number of functions and number of cols must match"
    vs = AbstractVector[]
    for j in 1:length(colsidx)
        v = _columns(ds)[colsidx[j]]
        _col_f = getformat(ds, colsidx[j])
        fv = _our_vect_alloc(Union{Missing, Bool}, nrow(ds))
        if mapformats
          _fill_mask!(fv, v, _col_f, f[j], threads, missings)
        else
          _fill_mask!(fv, v, f[j], threads, missings)
        end
        push!(vs, fv)
    end
    Dataset(vs, _names(ds)[colsidx], copycols=false)
end

function _fill_mask!(fv, v, format, fj, threads, missings)
  if threads
    Threads.@threads for i in 1:length(fv)
      fv[i] = _bool_mask(fj)(format(v[i]))
    end
    if !ismissing(missings)
        Threads.@threads for i in 1:length(fv)
          ismissing(fv[i]) ? fv[i] = missings : nothing
        end
    end
  else
    map!(_bool_mask(fj∘format), fv, v)
    if !ismissing(missings)
        map!(x->ismissing(x) ? x = missings : x, fv, fv)
    end
  end
end
# not using formats
function _fill_mask!(fv, v, fj, threads, missings)
  if threads
    Threads.@threads for i in 1:length(fv)
      fv[i] = _bool_mask(fj)(v[i])
    end
    if !ismissing(missings)
        Threads.@threads for i in 1:length(fv)
          ismissing(fv[i]) ? fv[i] = missings : nothing
        end
    end
  else
    map!(_bool_mask(fj), fv, v)
    if !ismissing(missings)
        map!(x->ismissing(x) ? x = missings : x, fv, fv)
    end
  end
end
_bool_mask(f) = x->f(x)::Union{Bool, Missing}

# TODO is it faster to use this function for unique(..., keep = :none) case?
function _unique_none_case(ds::Dataset, cols; mapformats = false)
    if ncol(ds) == 0
        throw(ArgumentError("finding duplicate rows in data set with no " *
                            "columns is not allowed"))
    end
    groups, gslots, ngroups = _gather_groups(ds, cols, nrow(ds) < typemax(Int32) ? Val(Int32) : Val(Int64), mapformats = mapformats, stable = true)
    _find_groups_with_more_than_one_observation(groups, ngroups)
end

# Modify Dataset
function Base.unique!(ds::Dataset; mapformats = false, keep = :first, threads = true)
    !(keep in (:first, :last, :none, :only, :random)) && throw(ArgumentError( "The `keep` keyword argument must be one of :first, :last, :only, :none, or :random"))
    if keep == :none
        rowidx = duplicates(ds, mapformats = mapformats, leave = :or, threads = threads)
    elseif keep == :only
        rowidx = duplicates(ds, mapformats = mapformats, leave = :none, threads = threads)
    elseif keep == :random
        rowidx = duplicates(ds, mapformats = mapformats, leave = :random, threads = threads)
    else
        rowidx = duplicates(ds, mapformats = mapformats, leave = keep, threads = threads)
    end
    deleteat!(ds, rowidx)
end
# this is for fixing ambiguity
function Base.unique!(ds::Dataset, cols::AbstractVector; mapformats = false, keep = :first, threads = true)
    !(keep in (:first, :last, :none, :only, :random)) && throw(ArgumentError( "The `keep` keyword argument must be one of :first, :last, :only, :none, or :random"))
    if keep == :none
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :or, threads = threads)
    elseif keep == :only
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :none, threads = threads)
    elseif keep == :random
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :random, threads = threads)
    else
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = keep, threads = threads)
    end
    deleteat!(ds, rowidx)
end
function Base.unique!(ds::Dataset, cols; mapformats = false, keep = :first, threads = true)
    !(keep in (:first, :last, :none, :only, :random)) && throw(ArgumentError( "The `keep` keyword argument must be one of :first, :last, :only, :none, or :random"))
    if keep == :none
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :or, threads = threads)
    elseif keep == :only
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :none, threads = threads)
    elseif keep == :random
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = :random, threads = threads)
    else
        rowidx = duplicates(ds, cols, mapformats = mapformats, leave = keep, threads = threads)
    end
    deleteat!(ds, rowidx)
end

# Unique rows of an Dataset.
@inline function Base.unique(ds::AbstractDataset; view::Bool=false, mapformats = false, keep = :first, threads = true)
    !(keep in (:first, :last, :none, :only, :random)) && throw(ArgumentError( "The `keep` keyword argument must be one of :first, :last, :only, :none, or :random"))
    if keep == :none
        rowidxs = duplicates(ds, mapformats = mapformats, leave = :none, threads = threads)
    elseif keep == :only
        rowidxs = duplicates(ds, mapformats = mapformats, leave = :or, threads = threads)
    elseif keep == :random
        rowidxs = .!duplicates(ds, mapformats = mapformats, leave = :random, threads = threads)
    else
        rowidxs = (!).(duplicates(ds, mapformats = mapformats, leave = keep, threads = threads))
    end
    return view ? Base.view(ds, rowidxs, :) : ds[rowidxs, :]
end

@inline function Base.unique(ds::AbstractDataset, cols; view::Bool=false, mapformats = false, keep = :first, threads = true)
    !(keep in (:first, :last, :none, :only, :random)) && throw(ArgumentError( "The `keep` keyword argument must be one of :first, :last, :only, :none, or :random"))
    if keep == :none
        rowidxs = duplicates(ds, cols, mapformats = mapformats, leave = :none, threads = threads)
    elseif keep == :only
        rowidxs = duplicates(ds, cols, mapformats = mapformats, leave = :or, threads = threads)
    elseif keep == :random
        rowidxs = .!duplicates(ds, cols, mapformats = mapformats, leave = :random, threads = threads)
    else
        rowidxs = (!).(duplicates(ds, cols, mapformats = mapformats, leave = keep, threads = threads))
    end
    return view ? Base.view(ds, rowidxs, :) : ds[rowidxs, :]
end

"""
    unique(ds::AbstractDataset, cols = : ; [mapformats = false, keep = :first, view::Bool=false, threads])
    unique!(ds::Dataset, cols = : ; [mapformats = false, keep = :first, threads])

Filter a data set based on uniqueness of its rows, and the `keep` keyword argument determines which occurance of duplicated rows should be retained. `keep` can be one of the following values: `:first`, `:last`, `:none`, `:only`, or `:random`.

* `keep = :first` the first occurrence of the duplicated rows are kept
* `keep = :last` the last occurrence are kept
* `keep = :none` all duplicates are dropped from the result
* `keep = :only` only duplicated rows are kept,
* `keep = :random` a random occurance of duplicates rows are kept

When `cols` is specified, the unique occurrence is detemined by given combination of values
in selected columns. `cols` can be any column selector.

For `unique`, if `view=false` a freshly allocated `Dataset` is returned,
and if `view=true` a `SubDataset` view into `ds` is returned.

`unique!` updates `ds` in-place and does not support the `view` keyword argument.

See also [`duplicates`](@ref).

# Arguments
- `ds` :  a data set
- `cols` :  column indicator (Symbol, Int, Vector{Symbol}, Regex, etc.) specifying the column(s) to compare.

## keyword arguments
- `mapformats` : if `true` then uniqueness of values is determined by their formatted values.
- `keep` : indicates which occurrence of the duplicate rows should be kept
- `view` : available only for `unique`, and indicates the result be a view of the input data set
- `threads` : determine if mulit-threading should be disabled

# Examples
```jldoctest
julia> ds = Dataset(i = 1:4, x = [1, 2, 1, 2])
4×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         1
   2 │        2         2
   3 │        3         1
   4 │        4         2

julia> ds = vcat(ds, ds)
8×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         1
   2 │        2         2
   3 │        3         1
   4 │        4         2
   5 │        1         1
   6 │        2         2
   7 │        3         1
   8 │        4         2

julia> unique(ds)   # doesn't modify ds
4×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         1
   2 │        2         2
   3 │        3         1
   4 │        4         2

julia> unique(ds, 2)
2×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         1
   2 │        2         2

julia> unique(ds, 2, keep = :last)
2×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        3         1
   2 │        4         2

julia> unique!(ds)  # modifies ds
4×2 Dataset
 Row │ i         x
     │ identity  identity
     │ Int64?    Int64?
─────┼────────────────────
   1 │        1         1
   2 │        2         2
   3 │        3         1
   4 │        4         2
```
"""
(unique, unique!)

"""
    dropmissing!(ds::Dataset, cols=:; mapformats = false, threads)

Remove rows with missing values from data set `ds` and return it.

If `cols` is provided, only missing values in the corresponding columns are considered.
`cols` can be any column selector ($COLUMNINDEX_STR; $MULTICOLUMNINDEX_STR).

See also: [`dropmissing`](@ref) and [`completecases`](@ref).

```jldoctest
julia> ds = Dataset(i = 1:5,
                             x = [missing, 4, missing, 2, 1],
                             y = [missing, missing, "c", "d", "e"])
5×3 Dataset
 Row │ i         x         y
     │ identity  identity  identity
     │ Int64?    Int64?    String?
─────┼──────────────────────────────
   1 │        1   missing  missing
   2 │        2         4  missing
   3 │        3   missing  c
   4 │        4         2  d
   5 │        5         1  e

julia> dropmissing!(copy(ds))
2×3 Dataset
 Row │ i         x         y
     │ identity  identity  identity
     │ Int64?    Int64?    String?
─────┼──────────────────────────────
   1 │        4         2  d
   2 │        5         1  e

julia> dropmissing!(copy(ds), :x)
3×3 Dataset
 Row │ i         x         y
     │ identity  identity  identity
     │ Int64?    Int64?    String?
─────┼──────────────────────────────
   1 │        2         4  missing
   2 │        4         2  d
   3 │        5         1  e

julia> dropmissing!(ds, [:x, :y])
2×3 Dataset
 Row │ i         x         y
     │ identity  identity  identity
     │ Int64?    Int64?    String?
─────┼──────────────────────────────
   1 │        4         2  d
   2 │        5         1  e

```
"""
function dropmissing!(ds::Dataset,
                      cols::Union{ColumnIndex, MultiColumnIndex}=:; mapformats = false, threads = nrow(ds)>Threads.nthreads() *10)
    inds = completecases(ds, cols; mapformats = mapformats, threads = threads)
    inds .= .!(inds)
    deleteat!(ds, inds)
    ds
end

function _compare_barrier_function_threaded!(_res, xl, xr, fl, fr, eq_fun)
    Threads.@threads for i in 1:length(xl)
        _res[i] = eq_fun(fl(xl[i]), fr(xr[i]))
    end
    _res
end
function _compare_barrier_function!(_res, xl, xr, fl, fr, eq_fun)
    for i in 1:length(xl)
        _res[i] = eq_fun(fl(xl[i]), fr(xr[i]))
    end
    _res
end

"""
    compare(ds1::AbstractDataset, ds2::AbstractDataset; [on = nothing, eq = isequal, mapformats = false, threads = true])

Compare values of two data sets column by column. It returns a boolean data set which is the result of calling  `eq` on each value of
corresponding columns. The `on` keyword can be used to specifiy the pair of columns which is needed to be compared. The `mapformats` keyword
controls whether the actual values or the formatted values should be compared.

```julia
julia> ds1 = Dataset(x = 1:9, y = 9:-1:1);
julia> ds2 = Dataset(x = 1:9, y2 = 9:-1:1, y3 = 1:9);
julia> compare(ds1, ds2, on = [:x=>:x, :y=>:y2])
9×2 Dataset
 Row │ x=>x      y=>y2
     │ identity  identity
     │ Bool?     Bool?
─────┼────────────────────
   1 │     true      true
   2 │     true      true
   3 │     true      true
   4 │     true      true
   5 │     true      true
   6 │     true      true
   7 │     true      true
   8 │     true      true
   9 │     true      true

julia> compare(ds1, ds2, on = [:x=>:x, :y=>:y3])
9×2 Dataset
 Row │ x=>x      y=>y3
     │ identity  identity
     │ Bool?     Bool?
─────┼────────────────────
   1 │     true     false
   2 │     true     false
   3 │     true     false
   4 │     true     false
   5 │     true      true
   6 │     true     false
   7 │     true     false
   8 │     true     false
   9 │     true     false

```
"""
function compare(ds1::AbstractDataset, ds2::AbstractDataset; on = nothing, eq = isequal, mapformats = false, threads = true)
    if !(mapformats isa AbstractVector)
        mapformats = repeat([mapformats], 2)
    else
        length(mapformats) !== 2 && throw(ArgumentError("`mapformats` must be a Bool or a vector of Bool with size two"))
    end
    if on === nothing
        left_col_idx = 1:ncol(ds1)
        right_col_idx = index(ds2)[names(ds1)]
    elseif typeof(on) <: AbstractVector{<:Union{AbstractString, Symbol}}
        left_col_idx = index(ds1)[on]
        right_col_idx = index(ds2)[names(ds1)[left_col_idx]]
    elseif (typeof(on) <: AbstractVector{<:Pair{<:ColumnIndex, <:ColumnIndex}}) || (typeof(on) <: AbstractVector{<:Pair{<:AbstractString, <:AbstractString}})
        left_col_idx = index(ds1)[map(x->x.first, on)]
        right_col_idx = index(ds2)[map(x->x.second, on)]
    else
        throw(ArgumentError("`on` keyword must be a vector of column names or a vector of pairs of column names"))
    end

    nrow(ds1) != nrow(ds2) && throw(ArgumentError("the number of rows for both data sets should be the same"))
    res = Dataset()
    for j in 1:length(left_col_idx)
        _res = allocatecol(Union{Bool, Missing}, nrow(ds1))
        fl = identity
        if mapformats[1]
            fl = getformat(ds1, left_col_idx[j])
        end
        fr = identity
        if mapformats[2]
            fr = getformat(ds2, right_col_idx[j])
        end
        if threads
            _compare_barrier_function_threaded!(_res, _columns(ds1)[left_col_idx[j]], _columns(ds2)[right_col_idx[j]], fl, fr, eq)
        else
            _compare_barrier_function!(_res, _columns(ds1)[left_col_idx[j]], _columns(ds2)[right_col_idx[j]], fl, fr, eq)
        end
        push!(_columns(res), _res)
        push!(index(res),  Symbol(names(ds1)[left_col_idx[j]]* "=>" * names(ds2)[right_col_idx[j]]))
    end
    res
end

"""
    describe(ds::AbstractDataset; cols=:, threads = true)
    describe(ds::AbstractDataset, fun...; cols=:, threads = true)

Return descriptive statistics for a data set as a new `Dataset`
where each row represents a variable and each column a summary statistic.

# Arguments
- `ds` : the `AbstractDataset`
- `fun...` : list of functions for summarising each column.
- `cols` : a keyword argument allowing to select only a subset
  of columns from `ds` to describe.

# Details
`IMD.n` and `IMD.nmissing` are two special functions for reporting number of nonmissing and number of missing observations, respectively.

# Examples
```jldoctest
julia> ds = Dataset(i=1:10, x=0.1:0.1:1.0, y='a':'j');

julia> describe(ds)
3×7 Dataset
 Row │ column    n         nmissing  mean      std       minimum   maximum
     │ identity  identity  identity  identity  identity  identity  identity
     │ String?   Any       Any       Any       Any       Any       Any
─────┼──────────────────────────────────────────────────────────────────────
   1 │ i         10        0         5.5       3.02765   1         10
   2 │ x         10        0         0.55      0.302765  0.1       1.0
   3 │ y         10        0         nothing   nothing   a         j

julia> describe(ds, minimum, maximum, IMD.n)
3×4 Dataset
 Row │ column    minimum   maximum   n
     │ identity  identity  identity  identity
     │ String?   Any       Any       Any
─────┼────────────────────────────────────────
   1 │ i         1         10        10
   2 │ x         0.1       1.0       10
   3 │ y         a         j         10

julia> describe(ds, sum, cols=:x)
1×2 Dataset
 Row │ column    sum
     │ identity  identity
     │ String?   Union…?
─────┼────────────────────
   1 │ x         5.5
```
"""
function DataAPI.describe(ds::Dataset,
                 stats::Base.Callable...;
                 cols=:, threads = true)
        colsidx = index(ds)[cols]

        n_stats = Vector{Base.Callable}(undef, length(stats))
        for j in 1:length(n_stats)
            n_stats[j] = x->try stats[j](x) catch  end
        end
        res = [Dataset() for _ in 1:length(colsidx)]
        newds = Dataset()

        @_threadsfor threads for j in 1:length(colsidx)
            res[j] = combine_ds(ds, colsidx[j] .=> n_stats .=> Symbol.([stats...]))
        end

        for j in 1:length(colsidx)
            append!(newds, res[j], promote = true)
        end
        varnames = (names(ds)[colsidx])
        # varnames = repeat(varnames, inner = _ngroups(ds))
        insertcols!(newds, 1, :column => varnames)
        newds
end
DataAPI.describe(ds::Dataset; cols = :) = describe(ds, n, nmissing, mean, std, minimum, maximum; cols = cols)
nmissing(x) = count(ismissing, x)
n(x) = count(!ismissing, x)

"""
    filter(ds::AbstractDataset, cols; [type = all,...])

A convenient shortcut for `ds[byrow(ds, type, cols; ...), :]`.

`type` can be any function supported by `byrow` which returns a Vector{Bool} or BitVector.

See [`byrow`](@ref), [`filter!`](@ref)

# Examples

```jldoctest
julia> ds = Dataset(x = [1,2,3,4,5], y = [1.5,2.3,-1,0,2.0], z = Bool[1,0,1,0,1])
5×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        1       1.5      true
   2 │        2       2.3     false
   3 │        3      -1.0      true
   4 │        4       0.0     false
   5 │        5       2.0      true

julia> filter(ds, :z)
3×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        1       1.5      true
   2 │        3      -1.0      true
   3 │        5       2.0      true

julia> filter(ds, 1:2, by = [iseven, >(2.0)])
1×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        2       2.3     false

julia> filter(ds, 1:2, type = any, by = [iseven, >(2.0)])
2×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        2       2.3     false
   2 │        4       0.0     false

julia> filter(ds, 1:3, type = issorted, rev = true)
2×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        4       0.0     false
   2 │        5       2.0      true

julia> filter(ds, 2:3, type = isless, with = :x)
3×3 Dataset
 Row │ x         y         z
     │ identity  identity  identity
     │ Int64?    Float64?  Bool?
─────┼──────────────────────────────
   1 │        3      -1.0      true
   2 │        4       0.0     false
   3 │        5       2.0      true
```
"""
function Base.filter(ds::AbstractDataset, cols::Union{ColumnIndex, MultiColumnIndex}; view = false, type= all, kwargs...)
    if view
        Base.view(ds, byrow(ds, type, cols; kwargs...), :)
    else
        ds[byrow(ds, type, cols; kwargs...), :]
    end
end
"""
    filter!(ds::AbstractDataset, cols; [type = all, ...])

Variant of `filter` which replaces the passed data set with the filtered one.

It is a convenient shortcut for `deleteat![ds, .!byrow(ds, type, cols; ...)]`.

`type` can be any function supported by `byrow` which returns a Vector{Bool} or BitVector.

Refer to [`filter`](@ref) for exmaples.

See [`byrow`](@ref), [`filter`](@ref)
"""
Base.filter!(ds::Dataset, cols::Union{ColumnIndex, MultiColumnIndex}; type = all, kwargs...) = deleteat!(ds, .!byrow(ds, type, cols; kwargs...))

"""
    mapcols(ds::AbstractDataset, f, cols)
    mapcols(ds::AbstractDataset, f::Vector, cols)

Return a `Dataset` where each column in `cols` of `ds` is transformed using function `f`.
`f` must return `AbstractVector` objects all with the same length or scalars
(all values other than `AbstractVector` are considered to be a scalar).

To apply different functions on different columns pass a vector of functions.

Note that `mapcols` guarantees not to reuse the columns from `ds` in the returned
`Dataset`. If `f` returns its argument then it gets copied before being stored.
"""
function mapcols(ds::AbstractDataset, f::Union{Function, Type}, cols = :)
    # note: `f` must return a consistent length
    vs = AbstractVector[]
    seenscalar = false
    seenvector = false
    colsidx = index(ds)[cols]
    for j in 1:length(colsidx)
        fv = f(_columns(ds)[colsidx[j]])
        if fv isa AbstractVector
            if seenscalar
                throw(ArgumentError("mixing scalars and vectors in mapcols not allowed"))
            end
            seenvector = true
            push!(vs, fv === _columns(ds)[colsidx[j]] ? copy(fv) : fv)
        else
            if seenvector
                throw(ArgumentError("mixing scalars and vectors in mapcols not allowed"))
            end
            seenscalar = true
            push!(vs, [fv])
        end
    end
    return Dataset(vs, names(ds, colsidx), copycols=false)
end
function mapcols(ds::AbstractDataset, f::Vector{T}, cols = :) where T <: Union{Function, Type}
    # note: `f` must return a consistent length
    vs = AbstractVector[]
    seenscalar = false
    seenvector = false
    colsidx = index(ds)[cols]
    @assert length(f) == length(colsidx) "Number of functions must match number of columns."
    for j in 1:length(colsidx)
        fv = f[j](_columns(ds)[colsidx[j]])
        if fv isa AbstractVector
            if seenscalar
                throw(ArgumentError("mixing scalars and vectors in mapcols not allowed"))
            end
            seenvector = true
            push!(vs, fv === _columns(ds)[colsidx[j]] ? copy(fv) : fv)
        else
            if seenvector
                throw(ArgumentError("mixing scalars and vectors in mapcols not allowed"))
            end
            seenscalar = true
            push!(vs, [fv])
        end
    end
    return Dataset(vs, names(ds, colsidx), copycols=false)
end

function _permute_ds_after_sort!(ds, perm; check = true, cols = :, threads = true)
    if check
        @assert nrow(ds) == length(perm) "the length of perm and the nrow of the data set must match"

        if issorted(perm)
            return ds
        end
    end
    colsidx = index(ds)[cols]
    for j in 1:length(colsidx)
        if DataAPI.refpool(_columns(ds)[colsidx[j]]) !== nothing
            # if _columns(ds)[colsidx[j] isa PooledArray
            #     pa = _columns(ds)[colsidx[j]
            #     _columns(ds)[colsidx[j] = PooledArray(PooledArrays.RefArray(_threaded_permute(pa.refs, perm)), DataAPI.invrefpool(pa), DataAPI.refpool(pa), PooledArrays.refcount(pa))
            # else
            #     # TODO must be optimised
            #     _columns(ds)[colsidx[j] = _columns(ds)[colsidx[j][perm]
            # end
            # since we don't support copycols for external usage it is safe to only permute refs
            _columns(ds)[colsidx[j]].refs = _threaded_permute(_columns(ds)[colsidx[j]].refs, perm; threads = threads)
        else
            _columns(ds)[colsidx[j]] = _threaded_permute(_columns(ds)[colsidx[j]], perm; threads = threads)
        end
    end
    _modified(_attributes(ds))
end


text:
"""Implementation of RRT-connect

ref:
- Kuffner, J. J., & LaValle, S. M. (2000).
  RRT-connect: An efficient approach to single-query path planning.
  In ICRA.
"""
module LibRRT_connect
export RRT_connect

import Printf: @sprintf, @printf
import ...MRMP: AbsState, Node, now, elapsed_sec, gen_uniform_sampling, get_mid_status, dist

"""
    RRT_connect(
        config_init::Vector{State},
        config_goal::Vector{State},
        connect::Function,
        collide::Function,
        check_goal::Function;
        steering_depth::Int64 = 4,
        epsilon::Union{Float64,Nothing} = 0.2,
        TIME_LIMIT::Union{Nothing,Real} = nothing,
        VERBOSE::Int64 = 0,
    )::Tuple{
        Union{Nothing,Vector{Vector{Node{State}}}},  # solution
        Vector{Vector{Node{State}}},  # roadmaps
    } where {State<:AbsState}

implementation of RRT-connect
"""
function RRT_connect(
    config_init::Vector{State},
    config_goal::Vector{State},
    connect::Function,
    collide::Function,
    check_goal::Function;
    steering_depth::Int64 = 4,
    epsilon::Union{Float64,Nothing} = 0.2,
    TIME_LIMIT::Union{Nothing,Real} = nothing,
    VERBOSE::Int64 = 0,
)::Tuple{
    Union{Nothing,Vector{Vector{Node{State}}}},  # solution
    Vector{Vector{Node{State}}},  # roadmaps
} where {State<:AbsState}

    # utilities for timeout
    t_s = now()
    elapsed() = elapsed_sec(t_s)
    timeover() = TIME_LIMIT != nothing && elapsed() > TIME_LIMIT

    # number of agents
    N = length(config_init)

    # define sampler
    _sampler = gen_uniform_sampling(config_init[1])
    sampler() = map(i -> _sampler(), 1:N)

    # re-define connect function
    connect_C(C_from::Vector{State}, C_to::Vector{State})::Bool = begin
        # check distance
        !(isnothing(epsilon) || dist(C_from, C_to) <= epsilon) && return false

        # check collision with obstacles
        any(i -> !connect(C_from[i], C_to[i], i), 1:N) && return false

        # check inter-agent collisions
        for i = 1:N, j = i+1:N
            collide(C_from[i], C_to[i], C_from[j], C_to[j], i, j) && return false
        end

        return true
    end

    connect_C(C::Vector{State})::Bool = all(i -> connect(C[i], i), 1:N)

    # store all samples
    V1 = [config_init]
    V2 = [config_goal]
    # parents index
    P1 = [-1]
    P2 = [-1]

    V = V1
    P = P1

    # special case
    if connect_C(config_init, config_goal)
        return (
            [
                map(q -> Node{State}(q, 1, []), config_init),
                map(q -> Node{State}(q, 2, []), config_goal),
            ],
            get_roadmaps(V1, P1, V2, P2),
        )
    end

    # main loop
    iter = 0
    from_start = false
    while !timeover()
        iter += 1
        from_start = !from_start

        VERBOSE > 1 &&
            iter % 100 == 0 &&
            @printf("\r\t%6.4f sec, iteration: %02d", elapsed(), iter)

        C_new = extend!(connect_C, sampler(), V1, P1, from_start, steering_depth)[end]
        if extend!(connect_C, C_new, V2, P2, !from_start, steering_depth)[1] == :reached
            # solved
            VERBOSE > 1 && @info @sprintf("\r\t%6.4f sec, iteration: %02d", elapsed(), iter)
            VERBOSE > 0 && @info @sprintf("\t%6.4f sec: found solution\n", elapsed())
            return (
                from_start ? (backtrack(V1, P1, V2, P2), get_roadmaps(V1, P1, V2, P2)) :
                (backtrack(V2, P2, V1, P1), get_roadmaps(V2, P2, V1, P1))
            )
        end

        # swap tree
        V1, V2 = V2, V1
        P1, P2 = P2, P1
    end

    VERBOSE > 0 && @info @sprintf("\t%6.4f sec: failed to find solution\n", elapsed())

    return (
        nothing,
        from_start ? get_roadmaps(V2, P2, V1, P1) : get_roadmaps(V1, P1, V2, P2),
    )
end

# define expand function
function extend!(
    connect::Function,
    C_rand::Vector{State},  # sampled point
    V::Vector{Vector{State}},  # vertices
    P::Vector{Int64},  # parent indexes
    from_start::Bool,  # true -> tree rooted C_init, false -> tree rooted C_goal
    steering_depth::Int64,
)::Tuple{Symbol,Vector{State}} where {State<:AbsState}

    # find nearest existing vertex
    ind_near = (
        from_start ? findmin(C -> dist(C, C_rand), V)[end] :
        findmin(C -> dist(C_rand, C), V)[end]
    )
    C_near = V[ind_near]
    flg = :reached

    C_l = C_near  # safe sample
    C_h = C_rand  # probably unsafe sample -> eventually safe

    # steering, binary search
    if from_start ? !connect(C_near, C_h) : !(connect(C_h) && connect(C_h, C_near))
        flg = :advanced
        for _ = 1:steering_depth
            C = map(e -> get_mid_status(e...), from_start ? zip(C_l, C_h) : zip(C_h, C_l))
            if from_start ? connect(C_near, C) : (connect(C) && connect(C, C_near))
                C_l = C
            else
                C_h = C
            end
        end
        C_h = C_l
    end
    push!(V, C_h)
    push!(P, ind_near)

    return (flg, C_h)
end

function backtrack(
    V1::Vector{Vector{State}},  # tree rooted config_init
    P1::Vector{Int64},
    V2::Vector{Vector{State}},  # tree rooted config_goal
    P2::Vector{Int64},
)::Vector{Vector{Node{State}}} where {State<:AbsState}
    """create solution"""

    solution = Vector{Vector{Node{State}}}()
    ind = length(V1)
    while ind != -1
        pushfirst!(solution, map(q -> Node{State}(q, ind, []), V1[ind]))
        ind = P1[ind]
    end
    solution = solution[1:end-1]
    ind = length(V2)
    while ind != -1
        push!(solution, map(q -> Node{State}(q, ind, []), V2[ind]))
        ind = P2[ind]
    end
    solution
end

function get_roadmaps(
    V1::Vector{Vector{State}},  # tree rooted config_init
    P1::Vector{Int64},
    V2::Vector{Vector{State}},  # tree rooted config_goal
    P2::Vector{Int64},
)::Vector{Vector{Node{State}}} where {State<:AbsState}
    """create roadmaps for visualization"""

    roadmaps = map(i -> Vector{Node{State}}(), 1:length(V1[1]))
    for (k, C) in enumerate(V1)
        for (i, q) in enumerate(C)
            push!(roadmaps[i], Node{State}(q, k, []))
            P1[k] > 0 && push!(roadmaps[i][P1[k]].neighbors, k)
        end
    end
    K = length(V1)
    for (k, C) in enumerate(V2)
        for (i, q) in enumerate(C)
            push!(roadmaps[i], Node{State}(q, k, P2[k] > 0 ? [P2[k] + K] : []))
        end
    end
    roadmaps
end

end

ext: jl


text:
@testset "trace" begin

@testset "TraceState" begin
    ts = OpenTelemetryAPI.TraceState(
        "1a-2f@foo"    => "bar1",
        "1a-_*/2b@foo" => "bar2",
        "foo"          => "bar3",
        "foo-_*/bar"   => "bar4",
    )
    @test haskey(ts, "foo")
    @test ts["foo"] == "bar3"
end

@testset "TracerProvider" begin
    tracer = get_tracer("test")
    with_span(tracer, "foo") do
        with_span(tracer, "bar") do
            with_span(tracer, "baz") do
                println("hello")
            end
        end
    end
end

end


text:
using SimpleParticleInCell
using Test

@testset "SimpleParticleInCell" begin
    @testset "World" begin
        world = SimpleParticleInCell.World([0.0,0.0,0.0],
                                           [1.0,1.0,1.0],
                                           (4,4,4),
                                           0.1,
                                           10
                                           )
        @test world.mesh_origin == [0,0,0]
        @test world.cell_spacing == [0.25, 0.25, 0.25]
        @test all(world.electric_potential .== 0)
        @test all(world.electric_field .== 0)
        @test all(world.charge_density .== 0)

        @test all(size(world.charge_density) .== world.number_cells)
        @test all(size(world.electric_potential) .== world.number_cells)
        @test all(size(world.electric_field) .== (world.number_cells..., 3))
        @test world.timestep == 0.1
        @test world.number_timesteps == 10

        @test sum(world.node_volumes) == (3/4)^3 # TODO generalize?
        @test length(unique(world.node_volumes)) == 4
    end

    @testset "Particles" begin
        world = SimpleParticleInCell.World([0.0,0.0,0.0],
                                           [1.0,1.0,1.0],
                                           (4,4,4),
                                           0.1,
                                           10
                                           )
        electrons = SimpleParticleInCell.Species("e-", 1.0, -1.0, world)
        @test size(electrons.density) == world.number_cells
        @test all(electrons.density .== 0)
        @test length(electrons.particles) == 0

        electron = SimpleParticleInCell.Particle([0., 0., 0.], [1., -1., 2.], 1.)
        push!(electrons.particles, electron)
        @test length(electrons.particles) == 1

        SimpleParticleInCell.compute_number_density(electrons, world)
        @test all(electrons.density .== 0)

        electron_shifted = SimpleParticleInCell.Particle([0.5, 0.25, 0.375], [1., -1., 2.], 1.)
        @test SimpleParticleInCell.X_to_L(electron_shifted, world) == [2.0, 1.0, 1.5]

        electron_shifted = SimpleParticleInCell.Particle([0.5, 0.5, 0.5], [0., 0., 0.], 1.)
        push!(electrons.particles, electron_shifted)
        SimpleParticleInCell.compute_number_density(electrons, world)
        @test sum(electrons.density .> 0) == 1
        @test sum(electrons.density .* world.node_volumes) == 1
        electron_shifted2 = SimpleParticleInCell.Particle([0.625, 0.625, 0.625], [0., 0., 0.], 1.)
        push!(electrons.particles, electron_shifted2)
        SimpleParticleInCell.compute_number_density(electrons, world)
        @test sum(electrons.density .> 0) == 8
        @test sum(electrons.density .* world.node_volumes) == 2
        # @show electrons.density

        # @test SimpleParticleInCell.solve_potential(world)
        # @test SimpleParticleInCell.compute_electric_field(world)
    end
end

max_stars_repo_name: StanczakDominik/SimpleParticleInCell

max_issues_repo_name: StanczakDominik/SimpleParticleInCell

max_forks_repo_name: StanczakDominik/SimpleParticleInCell


text:
using Test, YaoArrayRegister, BitBasis, YaoBase

function naive_focus!(reg::AbstractArrayReg{D}, bits) where {D}
    B = YaoArrayRegister._asint(nbatch(reg))
    nbits = nqubits(reg)
    norder = vcat(bits, setdiff(1:nbits, bits), nbits + 1)
    @views reg.state = reshape(
        permutedims(reshape(reg.state, fill(D, nbits)..., B), norder),
        :,
        (D ^ (nbits - length(bits))) * B,
    )
    return reg
end

function naive_relax!(reg::AbstractArrayReg{D}, bits) where {D}
    B = YaoArrayRegister._asint(nbatch(reg))
    nbit = nqubits(reg)
    norder = vcat(bits, setdiff(1:nbit, bits), nbit + 1) |> invperm
    @views reg.state =
        reshape(permutedims(reshape(reg.state, fill(D, nbit)..., B), norder), :, B)
    return reg
end

@testset "Focus 1" begin
    reg0 = rand_state(5; nbatch = 3)
    @test focus!(copy(reg0), [1, 4, 2]) == naive_focus!(copy(reg0), [1, 4, 2])
    @test copy(reg0) |>
          focus!(1, 4, 2) |>
          focus!(2) |>
          relax!(2, to_nactive = 3) |>
          relax!(1, 4, 2) == reg0
    reg = focus!(copy(reg0), 2:3)
    @test reg |> probs ≈
          hcat([sum(abs2.(reshape(reg.state[i, :], :, 3)), dims = 1)[1, :] for i = 1:4]...)'
    @test size(state(reg)) == (2^2, 2^3 * 3)
    @test nactive(reg) == 2
    @test nremain(reg) == 3
    @test relax!(reg, 2:3) == reg0
end

@testset "Focus 3" begin
    # conanical shape
    reg = rand_state(3; nbatch = 5)
    @test oneto(reg, 2) |> nactive == 2
    @test reg |> nactive == 3
    @test copy(reg) |> addbits!(2) |> nactive == 5
    reg2 = copy(reg) |> addbits!(2) |> focus!(4, 5)
    @test (measure!(RemoveMeasured(), reg2); reg2) |> relax!(to_nactive = nqubits(reg2)) ≈
          reg

    @test insert_qudits!(copy(reg), 2; nqudits = 2) |> nactive == 5
    @test insert_qubits!(copy(reg), 2; nqubits = 2) |> nactive == 5
    @test insert_qudits!(2; nqudits = 2)(copy(reg)) |> nactive == 5
    @test insert_qubits!(2; nqubits = 2)(copy(reg)) |> nactive == 5
end

@testset "Focus 2" begin
    reg0 = rand_state(8)
    reg = focus!(copy(reg0), 7)
    @test reg |> probs ≈ sum(abs2.(reg.state), dims = 2)[:, 1]
    @test nactive(reg) == 1

    reg0 = rand_state(10)
    reg = focus!(copy(reg0), 1:8)
    @test hypercubic(reg) == reshape(reg0.state, fill(2, 8)..., 4)
    @test nactive(reg) == 8
    @test reg0 == relax!(reg, 1:8)
end

@testset "focus do ... end" begin
    reg = rand_state(5)
    focus(reg, (1, 2, 3)) do reg
        @test nactive(reg) == 3
    end
    @test nactive(reg) == 5

    reg = rand_state(5)
    focus(reg, 1, 2, 3) do reg
        @test nactive(reg) == 3
    end
    @test nactive(reg) == 5

    reg = rand_state(5)
    focus(reg, 1) do reg
        @test nactive(reg) == 1
    end
    @test nactive(reg) == 5
end

@testset "partial trace" begin
    r = join(arrayreg(bit"111"), zero_state(1))
    @test partial_tr(r, 1) ≈ arrayreg(bit"111")
end


text:
struct CreditCard
    name :: AbstractString
    prefixes :: AbstractString
    length :: Int64
    security_code :: AbstractString
    security_code_length :: Int64
end

    prefix_mastercard = ["51", "52", "53", "54", "55"]
    prefix_visa = ["4"]
    prefix_amex = ["34", "37"]
    prefix_discover = ["6011"]
    prefix_diners = ["300", "301", "302", "303", "304", "305"]
    prefix_jcb16 = ["3088", "3096", "3112", "3158", "3337", "3528"]
    prefix_jcb15 = ["2100", "1800"]
    prefix_voyager = ["8699"]

    credit_card_types = Dict(
        "mastercard"=>    CreditCard("Mastercard",      rand(prefix_mastercard), 16, "CVV",3)
        ,"visa16"=>       CreditCard("VISA 16 digit",   rand(prefix_visa), 16, "CVC",3)
        ,"visa13"=>       CreditCard("VISA 13 digit",   rand(prefix_visa) , 13, "CVC",3)
        ,"amex"=>         CreditCard("American Express",rand(prefix_amex), 15, "CID",4)
        ,"discover"=>     CreditCard("Discover",        rand(prefix_discover), 16, "CVC",3)
        ,"diners"=>       CreditCard("Diners Club / Carte Blanche", rand(prefix_diners), 14, "CVC",3)
        ,"jcb15"=>        CreditCard("JCB 15 digit",    rand(prefix_jcb15), 15, "CVC",3)
        ,"jcb16"=>        CreditCard("JCB 16 digit",    rand(prefix_jcb16), 16, "CVC",3)
        ,"voyager"=>      CreditCard("Voyager",         rand(prefix_voyager) , 15, "CVC",3)
    )
    credit_card_types["visa"] = credit_card_types["visa16"]
    credit_card_types["jcb"] = credit_card_types["jcb16"]

    credit_card_provider()::String  = select_random(data["faker"]["business"]["credit_card_types"])

    function getcard(card::String="None")::CreditCard
        if card == "None"
            return collect(rand(credit_card_types))[2]
        else
            return credit_card_types[card]
        end
    end

    function credit_card_number(card::String="None")::String
        card::CreditCard = getcard(card)
        return generate_card_number(card.prefixes, card.length)
    end

    #=function credit_card_expire(start="now", end="+10y", date_format="%m/%y")

        expire_date = DateTime.date_time_between(start, end)
        return expire_date.strftime(date_format)
    end=#

    function credit_card_full(card::String="None")::String
        card::CreditCard = getcard(card)
        return string(card.name," ",first_name()," ",last_name()," ",generate_card_number(card.prefixes,card.length)," ",card.security_code," ",credit_card_security_code(card.security_code_length))
    end

    credit_card_security_code(code_length::Int=3)::String  = numerify("#"^(code_length))

    function generate_card_number(prefix::String="52", len::Int=16)::String
        """
        "prefix" is the start of the CC number as a AbstractString, any number of digits.
        "len" is the len of the CC number to generate. Typically 13 or 16
        """
        # Generate random char digits
        number::String = numerify(string(prefix,"#"^(len - length(prefix) - 1)))

        result::Array{Int64,1} =  map( x -> parse(Int,x), collect(number))
        odds::Array{Int64,1}  = map( x ->(z=x*2;div(z,10)+rem(z,10)), result[end:-2:1])
        # Calculate sum
        tot::Int = reduce(+, append!(result[end-1:-2:1],odds))
        # Calculate check digit
        check_digit::Int = (10 - (tot % 10)) % 10
        return string(number,check_digit)
    end


text:
installer = ""
if Sys.islinux()
    installer = "Miniconda3-py37_4.8.3-Linux-x86_64.sh"
elseif Sys.isapple()
    installer = "Miniconda3-py37_4.8.3-MacOSX-x86_64.sh"
else
    error("Currently installing miniconda on Windows does not work.")
end

if isdir("~/.julia/conda/")
    mkpath("~/.julia/conda/")
end

PWD = pwd()
cd("~/.julia/conda/")
@info "Downloading miniconda installer..."
download("https://repo.anaconda.com/miniconda/"*installer, installer)
if isdir("3")
    mv("3", "trash")
end
@info "Installing miniconda..."
run(`bash $installer -b -p 3`)
cd(PWD)


text:
using Makie
using MakieLayout
using Makie: px
using KernelDensity
using StatsBase
using GLFW; GLFW.WindowHint(GLFW.FLOATING, 1)
using StatsMakie
using DataFrames
using Colors

struct Infrastructure{T<:NamedTuple}
    content::T
end

function Base.getproperty(infra::Infrastructure, sym::Symbol)
    if sym in fieldnames(Infrastructure)
        getfield(infra, sym)
    else
        infra.content[sym]
    end
end

function create_infrastructure(T::Type, scene::Scene, args...; kwargs...)
    error("create_infrastructure not defined for type $T.")
end

function myplot(T::Type, args...; kwargs...)
    dkwargs = Dict(kwargs)
    scenekw = pop!(dkwargs, :scene, NamedTuple())
    layoutkw = pop!(dkwargs, :layout, NamedTuple())

    scene, layout = layoutscene(; scenekw..., layoutkw...)
    infrastructure, plots = myplot(T, layout[1, 1], args...; dkwargs...)
    (scene = scene, layout = layout, infra = infrastructure, plots = plots)
end

function myplot(T::Type, scene::Scene, args...; kwargs...)
    dkwargs = Dict(kwargs)
    scenekw = pop!(dkwargs, :scene, NamedTuple())
    layoutkw = pop!(dkwargs, :layout, NamedTuple())

    layout = GridLayout(scene; alignmode = Outside(30), scenekw..., layoutkw...)
    infrastructure, plots = myplot(T, layout[1, 1], args...; dkwargs...)
    (layout = layout, infra = infrastructure, plots = plots)
end

function get_layout_top_scene(layout::GridLayout)
    if isnothing(layout.layoutobservables.gridcontent)
        return layout.parentscene
    else
        parentgrid = layout.layoutobservables.gridcontent.parent
        if isnothing(parentgrid)
            return layout.parentscene
        else
            return get_layout_top_scene(parentgrid)
        end
    end
end

abstract type InfrastructureType end
struct SingleAxis <: InfrastructureType end
struct FacetAxes <: InfrastructureType end

infrastructure_type(any::Type) = any # if nothing is set, just use the type directly
infrastructure_type(::Type{Scatter}) = SingleAxis
infrastructure_type(::Type{Lines}) = SingleAxis

function myplot(T::Type, gp::GridPosition, args...; kwargs...)
    scene = get_layout_top_scene(gp.layout)
    if isnothing(scene)
        error("Could not retrieve a parent scene from the layout's tree. Can't plot using a GridLayout without top level parent scene.")
    end

    dkwargs = Dict(kwargs)
    infrakw = pop!(dkwargs, :infra, NamedTuple())

    infrastructure = create_infrastructure(
        infrastructure_type(T),
        scene, args...; infrakw...)

    gp[] = infrastructure.layout
    plots = myplot(T, infrastructure, args...; dkwargs...)
    infrastructure, plots
end

function myplot(T::Type, infra::Infrastructure, args...; kwargs...)
    error("myplot not defined for type $T")
end

function create_infrastructure(::Type{SingleAxis}, scene::Scene, args...; kwargs...)
    dkwargs = Dict(kwargs)
    axiskw = pop!(dkwargs, :axis, NamedTuple())
    layoutkw = pop!(dkwargs, :layout, NamedTuple())

    layout = GridLayout(;layoutkw...)
    axis = layout[1, 1] = Axis(scene; axiskw...)
    Infrastructure((axis = axis, layout = layout))
end

function create_infrastructure(::Union{Type{Heatmap}, Type{Image}}, scene::Scene, args...; kwargs...)
    infra = create_infrastructure(SingleAxis, scene, args...; kwargs...)
    tightlimits!(infra.axis)
    infra
end

function Base.display(nt::NamedTuple{(:scene, :layout, :infra, :plots), Tuple{S,G,I,P}}) where {S<:SceneLike, G, I, P}
    display(nt.scene)
end

function myplot(::Type{Scatter}, infra::Infrastructure, args...; kwargs...)
    scat = scatter!(infra.axis, args...; kwargs...)
end

function myplot(::Type{Lines}, infra::Infrastructure, args...; kwargs...)
    lin = lines!(infra.axis, args...; kwargs...)
end

function myplot(::Type{Heatmap}, infra::Infrastructure, args...; kwargs...)
    hm = heatmap!(infra.axis, args...; kwargs...)
end

function myplot(::Type{Image}, infra::Infrastructure, args...; kwargs...)
    hm = image!(infra.axis, args...; kwargs...)
end

##
scene, layout, _ = myplot(Heatmap, rand(100, 100), infra = (;axis = (;aspect = DataAspect(), xlabel = "helloo"))); scene
myplot(Lines, layout[1, 2], 1:10, sin.(1:10))
scene
##

myplot(Scatter, Group(color = rand(1:3, 100)), rand(100), rand(100), markersize = 20px)

struct MarginDensityScatter end

function create_infrastructure(::Type{MarginDensityScatter}, scene::Scene, args...; kwargs...)

    dkwargs = Dict(kwargs)

    mainax = Axis(scene, xlabel = get(dkwargs, :xlabel, " "), ylabel = get(dkwargs, :ylabel, " "))
    topax = Axis(scene; xlabelvisible = false, xticklabelsvisible = false, xticksvisible = false)
    rightax = Axis(scene; ylabelvisible = false, yticklabelsvisible = false, yticksvisible = false)

    linkxaxes!(mainax, topax)
    linkyaxes!(mainax, rightax)

    tightlimits!(topax, Bottom())
    tightlimits!(rightax, Left())

    marginfraction = get(dkwargs, :marginfraction, 1/4)
    gap = get(dkwargs, :gap, Fixed(20))

    layout = GridLayout(2, 2; rowsizes = [Relative(marginfraction), Auto()],
        colsizes = [Auto(), Relative(marginfraction)], addedcolgaps = gap, addedrowgaps = gap)

    layout[2, 1] = mainax
    layout[1, 1] = topax
    layout[2, 2] = rightax

    Infrastructure((axes = (main = mainax, top = topax, right = rightax), layout = layout))
end

function myplot(::Type{MarginDensityScatter}, infra::Infrastructure, x, y; kwargs...)

    dkwargs = Dict(kwargs)

    xkde = kde(x; npoints = 300)
    ykde = kde(y; npoints = 300)

    color = get(dkwargs, :color, :red)

    scat = scatter!(infra.axes.main, x, y; markersize = get(dkwargs, :markersize, 10px), color = color, strokecolor = get(dkwargs, :strokecolor, :black), strokewidth = get(dkwargs, :strokewidth, 1))
    kde1 = poly!(infra.axes.top, Point2f0.(xkde.x, xkde.density), color = color)
    kde2 = poly!(infra.axes.right, Point2f0.(ykde.density, ykde.x), color = color)

    autolimits!(infra.axes.main)

    (scatter = scat, xkde = kde1, ykde = kde2)
end

##
scene, layout = layoutscene()

for i in 1:3, j in 1:3
    myplot(MarginDensityScatter, layout[i, j], randn(100), randn(100);
        color = rand(RGBAf0),
        infra = (
            marginfraction = 0.2,
            xlabel = "$i & $j",
            gap = Fixed(10)
        ))
end

scene
##

##
scene, layout = layoutscene()
myplot(Scatter, layout[1, 1], rand(100), rand(100), markersize = 20px, color = :green, strokecolor = :black, strokewidth = 1)
myplot(Scatter, layout[2, 1], rand(100), rand(100), markersize = 20px, color = :red, strokecolor = :black, strokewidth = 1)
myplot(Scatter, layout[1:2, 2], rand(100), rand(100), markersize = 20px, color = :blue, strokecolor = :black, strokewidth = 1)
myplot(MarginDensityScatter, layout[1:2, 3:4], randn(100), randn(100))
scene
##

struct FacetPlot end

function create_infrastructure(::Type{FacetPlot}, scene::Scene, plottype, rows, cols, args...; kwargs...)

    dkwargs = Dict(kwargs)

    axiskw = pop!(dkwargs, :axes, NamedTuple())
    layoutkw = pop!(dkwargs, :layout, NamedTuple())

    nrows = length(unique(rows))
    ncols = length(unique(cols))

    layout = GridLayout(; layoutkw...)

    axs = [Axis(scene; axiskw...) for x in CartesianIndices((nrows, ncols))]

    layout[] = axs

    for ax in axs[:, 2:end]
        ax.ylabelvisible = false
        ax.yticklabelsvisible = false
        ax.yticksvisible[] = false
    end

    for ax in axs[1:end-1, :]
        ax.xlabelvisible[] = false
        ax.xticklabelsvisible = false
        ax.xticksvisible[] = false
    end

    linkxaxes!(axs...)
    linkyaxes!(axs...)

    Infrastructure((axes = axs, layout = layout))
end

function myplot(::Type{FacetPlot}, infra::Infrastructure, plottype, rows, cols, x, y; kwargs...)

    dkwargs = Dict(kwargs)

    df = DataFrame(rows = rows, cols = cols, x = x, y = y)

    for (ax, sdf) in zip(infra.axes, groupby(df, [:rows, :cols]))
        myplot(plottype, Infrastructure((axis = ax,)), sdf.x, sdf.y; kwargs...)
    end

    autolimits!(infra.axes[1])
end

##

msize = Node(10px)
scene, layout, infra = myplot(FacetPlot, Scatter,
    rand(1:3, 1000), rand(1:4, 1000), randn(1000), randn(1000), markersize = msize, color = :orange, strokewidth = 1, strokecolor = :black, infra = (; axes = (;backgroundcolor = Gray(0.9))))
myplot(FacetPlot, infra, Lines,
    rand(1:3, 1000), rand(1:4, 1000), randn(1000), randn(1000); color = :red)

for c in 1:infra.layout.ncols
    infra.layout[1, c, Top()] = Box(scene, color = Gray(0.8))
    infra.layout[1, c, Top()] = Label(scene, "Column $c", padding = (0, 0, 10, 10))
end

for r in 1:infra.layout.nrows
    infra.layout[r, end, Right()] = Box(scene, color = Gray(0.8))
    infra.layout[r, end, Right()] = Label(scene, "Row $r", rotation = -pi/2, padding = (10, 10, 0, 0))
end

layout[0, :] = Box(scene, color = Gray(0.7))
layout[1, :] = Label(scene, "Columns", padding = (0, 0, 10, 10))
layout.content[end].content.tellwidth = false

foreach(Axis, layout) do ax; tight_ticklabel_spacing!(ax); end


text:
# by lazarusA # HIDE
# using GLMakie # HIDE
using GLMakie
GLMakie.activate!() # HIDE
let
    u = LinRange(0, 1, 50)
    v = LinRange(0, 2π, 50)
    X1 = [u for u in u, v in v]
    Y1 = [(u^4 - u^2)*cos(v) for u in u, v in v]
    Z1 = [(u^4 - u^2)*sin(v)  for u in u, v in v]
    fig, _ = surface(X1, Y1, Z1, shading = true,  ambient = Vec3f0(0.65, 0.65, 0.65), backlight = 0.95f0,
        color = sqrt.(X1.^2 .+ Y1.^2 .+ Z1.^2), colormap = :plasma)
    fig
    save(joinpath(@__DIR__, "output", "revolutionSurf.png"), fig, px_per_unit = 2.0) # HIDE
end
using Pkg # HIDE
Pkg.status("GLMakie") # HIDE

max_stars_repo_name: MisterBiggs/BeautifulMakie

max_issues_repo_name: MisterBiggs/BeautifulMakie

max_forks_repo_name: MisterBiggs/BeautifulMakie


text:
function versioninfo(io::IO=stdout)
    # get a hold of Pkg without adding a dependency on the package
    Pkg = let
        id = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
        Base.loaded_modules[id]
    end
    deps = Pkg.dependencies()
    versions = Dict(map(uuid->deps[uuid].name => deps[uuid].version, collect(keys(deps))))

    println(io, "Binary dependencies:")
    for pkg in ["NEO_jll", "libigc_jll", "gmmlib_jll", "SPIRV_LLVM_Translator_unified_jll", "SPIRV_Tools_jll"]
        println(io, "- $pkg: $(versions[pkg])")
    end
    println(io)

    println(io, "Toolchain:")
    println(io, "- Julia: $VERSION")
    println(io, "- LLVM: $(LLVM.version())")
    println(io)

    env = filter(var->startswith(var, "JULIA_ONEAPI"), keys(ENV))
    if !isempty(env)
        println(io, "Environment:")
        for var in env
            println(io, "- $var: $(ENV[var])")
        end
        println(io)
    end

    drvs = drivers()
    if isempty(drvs)
        println(io, "No oneAPI-capable drivers.")
    elseif length(drvs) == 1
        println(io, "1 driver:")
    else
        println(io, length(drvs), " drivers:")
    end
    for drv in drivers()
        props = properties(drv)
        println(io, "- $(props.uuid) (v$(props.driverVersion), API v$(api_version(drv)))")
    end
    println(io)

    devs = [dev for drv in drivers() for dev in devices(drv)]
    if isempty(devs)
        println(io, "No oneAPI-capable devices.")
    elseif length(devs) == 1
        println(io, "1 device:")
    else
        println(io, length(devs), " devices:")
    end
    for dev in devs
        props = properties(dev)
        println(io, "- $(props.name)")
    end
end

"""
    @sync ex

Run expression `ex` and synchronize the GPU afterwards.

See also: [`synchronize`](@ref).
"""
macro sync(ex)
    quote
        local ret = $(esc(ex))
        synchronize()
        ret
    end
end


text:
try
	fileName = ARGS[1]
	if occursin(r"^\s*$", fileName)
		println("error handler from \"cokey\":\n  exception    : File\n  error caller : $fileName\n  message      : void name.")
	else
		try
			json = "{"

			file = readlines(fileName)
			for (index, line) in enumerate(file)
				if occursin(r"^(.*)\n*{", line)
					if occursin(r"^(.*)\n*\{(.*)\{$|^(.*)\n*\{[^\s]+$", line)
						println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $line\n  message      : syntax error.")
					else

						struct_name = strip(match(r"^(.*)\{\s*$", line).match, [' ', '{'])
						json *= "\n\t\"$struct_name\": {"

						curlyBrackets_start_pos = index
						curlyBrackets_end_pos = 0
						for (index2,line2) in enumerate(file[curlyBrackets_start_pos:end])
							if occursin(r"(.*)\}", line2)
								if occursin(r"^\}\s*", line2)
									curlyBrackets_end_pos = curlyBrackets_start_pos+index2-1
									break
								else
									println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $line2\n  message      : invalid values on close curly brackets.")
								end
							end
						end

						keys = file[curlyBrackets_start_pos+1:curlyBrackets_end_pos]
						registered_keys = []
						for (keyIndex, key) in enumerate(keys)
							if keyIndex == length(keys)
								break
							end

							key = strip(key, ['\t', ' '])
							if occursin(r"^\s*$", key)
								continue
							end

							if occursin(r"^(.*)", key)
								if occursin(r"^(.*)+\:", key)
									if occursin(r"^(.*)+\:(.*)+$", key)
										keySplit = split(key, r"\s*\:\s*")
										keyName = keySplit[1]
										keyValue = keySplit[2]

										if keyName in registered_keys
											println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : this key is repeated.")
										elseif occursin(r"^\s*$", keyName)
											println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : unfound key name.")
										elseif occursin(r"^\s*$", keyValue)
											println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : unfound key value.")
										else
											push!(registered_keys, keyName)
											json *= "\n\t\t\"$keyName\": \"$keyValue\""
										end

									elseif occursin(r"^(.*)\:\s*$", key)
										println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : unfound key value.")
									else
										println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : unfound key value.")
									end
								else
									println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $key\n  message      : unfound colon.")
								end
							end
						end

						if curlyBrackets_end_pos != 0
							file = deleteat!(file,curlyBrackets_start_pos:curlyBrackets_end_pos)
						else
							println("error handler from \"cokey\":\n  exception    : Syntax\n  error caller : $line\n  message      : unclosed curly brackets.")
						end

					end
				end

				json *= "\n\t},"
			end
			json *= "\n}"
			println(json)
		catch
			println("error handler from \"cokey\":\n  exception    : File\n  error caller : $fileName\n  message      : void name.")
		end
	end
catch
end

max_stars_repo_name: endlishemmet/cokey

max_issues_repo_name: endlishemmet/cokey

max_forks_repo_name: endlishemmet/cokey


text:
# ---
# title: 1464. Maximum Product of Two Elements in an Array
# id: problem1464
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/maximum-product-of-two-elements-in-an-array/description/>
# hidden: true
# ---
#
# Given the array of integers `nums`, you will choose two different indices `i`
# and `j` of that array. _Return the maximum value of_
# `(nums[i]-1)*(nums[j]-1)`.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [3,4,5,2]
#     Output: 12
#     Explanation: If you choose the indices i=1 and j=2 (indexed from 0), you will get the maximum value, that is, (nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [1,5,4,5]
#     Output: 16
#     Explanation: Choosing the indices i=1 and j=3 (indexed from 0), you will get the maximum value of (5-1)*(5-1) = 16.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [3,7]
#     Output: 12
#
#
#
#
# **Constraints:**
#
#   * `2 <= nums.length <= 500`
#   * `1 <= nums[i] <= 10^3`
#
#
## @lc code=start
using LeetCode

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