text:
# module Border

using OffsetArrays, CatIndices

abstract type AbstractBorder end

struct NoPad{T} <: AbstractBorder
    border::T
end
NoPad() = NoPad(nothing)

"""
    NoPad()
    NoPad(border)

Indicates that no padding should be applied to the input array, or that you have already pre-padded the input image. Passing a `border` object allows you to preserve "memory" of a border choice; it can be retrieved by indexing with `[]`.

# Example
The commands

    np = NoPad(Pad(:replicate))
    imfilter!(out, img, kernel, np)

run filtering directly, skipping any padding steps.  Every entry of
`out` must be computable using in-bounds operations on `img` and
`kernel`.
"""
NoPad

Base.getindex(np::NoPad) = np.border

"""
```julia
    struct Pad{N} <: AbstractBorder
        style::Symbol
        lo::Dims{N}    # number to extend by on the lower edge for each dimension
        hi::Dims{N}    # number to extend by on the upper edge for each dimension
    end
```

`Pad` is a type that designates the form of padding which should be used to
extrapolate pixels beyond the boundary of an image. Instances must set `style`,
a Symbol specifying the boundary conditions of the image.

# Output

The type `Pad` specifying how the boundary of an image should be padded.

# Details
When representing a spatial two-dimensional image filtering operation as a
discrete convolution between the image and a ``D \\times D `` filter, the
results are undefined for pixels closer than ``D`` pixels from the border of the
image. To define the operation near and at the border, one needs a scheme for
extrapolating pixels beyond the edge. The `Pad` type allows one to specify the
necessary extrapolation scheme.

The type facilitates the padding of one, two or multi-dimensional images.

You can specify a different amount of padding at the lower and upper borders of
each dimension of the image (top, left, bottom and right in two dimensions).

## Options
Some valid `style` options are described below. As an indicative example of each
option the results of the padding are illustrated on an image consisting of a
row of six pixels which are specified alphabetically: ``\\boxed{a \\, b \\, c
\\,d \\, e \\, f}``. We show the effects of padding only on the left and right
border, but analogous consequences hold for the top and bottom border.

### `:replicate` (Default)

The border pixels extend beyond the image boundaries.
```math
\\boxed{
\\begin{array}{l|c|r}
  a\\, a\\, a\\, a  &  a \\, b \\, c \\, d \\, e \\, f & f \\, f \\, f \\, f
\\end{array}
}
```
See also: [`Fill`](@ref), [`padarray`](@ref), [`Inner`](@ref) and
[`NoPad`](@ref)

### `:circular`

The border pixels wrap around. For instance, indexing beyond the left border
returns values starting from the right border.
```math
\\boxed{
\\begin{array}{l|c|r}
  c\\, d\\, e\\, f  &  a \\, b \\, c \\, d \\, e \\, f & a \\, b \\, c \\, d
\\end{array}
}
```
See also: [`Fill`](@ref), [`padarray`](@ref), [`Inner`](@ref) and
[`NoPad`](@ref)

### `:symmetric`
The border pixels reflect relative to a position between pixels. That is, the
border pixel is omitted when mirroring.
```math
\\boxed{
\\begin{array}{l|c|r}
  e\\, d\\, c\\, b  &  a \\, b \\, c \\, d \\, e \\, f & e \\, d \\, c \\, b
\\end{array}
}
```
See also: [`Fill`](@ref),[`padarray`](@ref), [`Inner`](@ref) and
[`NoPad`](@ref)

### `:reflect`
The border pixels reflect relative to the edge itself.
```math
\\boxed{
\\begin{array}{l|c|r}
  d\\, c\\, b\\, a  &  a \\, b \\, c \\, d \\, e \\, f & f \\, e \\, d \\, c
\\end{array}
}
```
See also: [`Fill`](@ref),[`padarray`](@ref), [`Inner`](@ref) and
[`NoPad`](@ref)

---

"""
struct Pad{N} <: AbstractBorder
    style::Symbol
    lo::Dims{N}    # number to extend by on the lower edge for each dimension
    hi::Dims{N}    # number to extend by on the upper edge for each dimension
end

Pad{N}(style, lo::AbstractVector, hi::AbstractVector) where {N} =
    Pad{N}(style, (lo...,), (hi...,))

const valid_borders = ("replicate", "circular", "reflect", "symmetric")

function borderinstance(border::AbstractString)
    if border ∈ valid_borders
        return Pad(Symbol(border))
    elseif border == "inner"
        throw(ArgumentError("specifying Inner as a string is deprecated, use `imfilter(img, kern, Inner())` instead"))
    else
        throw(ArgumentError("$border not a recognized border"))
    end
end
borderinstance(b::AbstractBorder) = b

"""
```julia
    Pad(style::Symbol, m, n, ...)
    Pad(style::Symbol, (m,n))
```

Construct an instance of [`Pad`](@ref) such that the image is prepended and appended symmetrically with `m` pixels at the lower and upper edge of dimension 1, `n` pixels for dimension 2, and so forth.

#### Usage illustration
Use `Pad(:replicate,2,4)` to designate that the top and bottom border should be
replicated by two pixels, and the left and right border by four pixels.

Use `Pad(:circular,(0,3))` to designate that the top and bottom border should
not be padded, and that the left and right border should wrap around by three
pixels.

---

"""
Pad(style::Symbol, both::Int...) = Pad(style, both, both)
Pad(both::Int...) = Pad(:replicate, both, both)
Pad(style::Symbol, both::Dims) = Pad(style, both, both)

"""
```julia
    Pad(both::Dims)
```
Construct an instance of [`Pad`](@ref) with default `:replicate` extrapolation, where the tuple `both` specifies the number of pixels which will be prepended and appended for each dimension.

#### Usage illustration
Use `Pad((5,5))` to designate that the top, bottom, left and right border should
be replicated by five pixels.

---

"""
Pad(both::Dims) = Pad(:replicate, both, both)

"""
```julia
    Pad(style::Symbol, lo::Dims, hi::Dims)
```
Construct an instance of [`Pad`](@ref) such that the image is prepended by `lo` pixels  and appended by `hi` pixels  in each dimension.

#### Usage illustration

Use `Pad(:replicate,(1,2),(3,4))` to designate that the top and bottom border
should be replicated by one and two pixels, and that the left and right border
should be replicated by three and four pixels.

---

"""
Pad(lo::Dims, hi::Dims) = Pad(:replicate, lo, hi)
Pad(style::Symbol, lo::Tuple{}, hi::Tuple{}) = Pad{0}(style, lo, hi)
Pad(style::Symbol, lo::Dims{N}, hi::Tuple{}) where {N} = Pad(style, lo, ntuple(d->0,Val(N)))
Pad(style::Symbol, lo::Tuple{}, hi::Dims{N}) where {N} = Pad(style, ntuple(d->0,Val(N)), hi)
Pad(style::Symbol, lo::AbstractVector{Int}, hi::AbstractVector{Int}) = Pad(style, (lo...,), (hi...,))

Pad(style::Symbol, ::Tuple{}) = Pad(style, (), ())    # ambiguity resolution
Pad(style::Symbol, inds::Indices) = Pad(style, map(lo,inds), map(hi,inds))

"""
```julia
    Pad(style, kernel)
    Pad(style)(kernel)
```
Construct an instance of [`Pad`](@ref) by designating the value `val` and a filter array `kernel` which will be used to determine the amount of padding from the `axes` of `kernel`.

#### Usage illustration

Use `Pad(:circular,Kernel.sobel())` to specify a `:circular` border style and
the minimal amount of padding necessary to ensure that convolution with
[`Kernel.sobel`](@ref) will be defined at the borders of an image.

---

"""
(p::Pad{0})(kernel) = Pad(p.style, calculate_padding(kernel))
(p::Pad{0})(kernel, img, ::Alg) = p(kernel)

# Padding for FFT: round up to next size expressible as 2^m*3^n
function (p::Pad{0})(kernel, img, ::FFT)
    inds = calculate_padding(kernel)
    newinds = map(padfft, inds, map(length, axes(img)))
    Pad(p.style, newinds)
end
function padfft(indk::AbstractUnitRange, l::Integer)
    lk = length(indk)
    range(first(indk), length=nextprod([2,3], l+lk)-l+1)
end

function error_bad_padding_size(inner, border)
    ArgumentError("$border lacks the proper padding sizes for an array with $(ndims(inner)) dimensions")
end

function padindices(img::AbstractArray{<:Any,N}, border::Pad) where N
    throw(error_bad_padding_size(img, border))
end
function padindices(img::AbstractArray{<:Any,N}, border::Pad{N}) where N
    _padindices(border, border.lo, axes(img), border.hi)
end
function padindices(img::AbstractArray, ::Type{P}) where P<:Pad
    throw(ArgumentError("must supply padding sizes to $P"))
end

# The 3-argument map is not inferrable, so do it manually
@inline _padindices(border, lo, inds, hi) =
    (padindex(border, lo[1], inds[1], hi[1]),
     _padindices(border, tail(lo), tail(inds), tail(hi))...)
_padindices(border, ::Tuple{}, ::Tuple{}, ::Tuple{}) = ()

"""
```julia
    padarray([T], img, border) --> imgpadded
```
Generate a padded image from an array `img` and a specification
`border` of the boundary conditions and amount of padding to
add.

# Output

An expansion of the input image in which additional pixels are derived
from the border of the input image using the extrapolation scheme specified by
`border`.

# Details

The function supports one, two or multi-dimensional images. You can specify the
element type `T` of the output image.

## Options
Valid `border` options are described below.

### `Pad`
The type `Pad` designates the form of padding which should be used to
extrapolate pixels beyond the boundary of an image. Instances must set `style`,
a Symbol specifying the boundary conditions of the image.

Symbol must be on one of:
- `:replicate` (repeat edge values to infinity),
- `:circular` (image edges "wrap around"),
- `:symmetric` (the image reflects relative to a position between pixels),
- `:reflect` (the image reflects relative to the edge itself).

Refer to the documentation of [`Pad`](@ref) for more details and examples for
each option.

### `Fill`
The type `Fill` designates a particular value which will be used to
extrapolate pixels beyond the boundary of an image. Refer to the documentation
of [`Fill`](@ref) for more details and illustrations.

# 2D Examples
Each example is based on the input array
```math
\\mathbf{A} =
\\boxed{
\\begin{matrix}
 1  & 2  &  3  &  4 & 5  & 6 \\\\
 2  & 4  &  6  &  8 & 10 & 12 \\\\
 3  & 6  &  9  & 12 & 15 & 18 \\\\
 4  & 8  & 12  & 16 & 20 & 24 \\\\
 5  & 10 & 15  & 20 & 25 & 30 \\\\
 6  & 12 & 18  & 24 & 30 & 36
 \\end{matrix}}.
```
## Examples with `Pad`
The command `padarray(A, Pad(:replicate,4,4))` yields
```math
\\boxed{
\\begin{array}{ccccccccccccc}
1 & 1 & 1 & 1 &         1   &          2   &          3   &          4   &          5   &          6   &  6  &  6  &  6  &  6 \\\\
1 & 1 & 1 & 1 &         1   &          2   &          3   &          4   &          5   &          6   &  6  &  6  &  6  &  6 \\\\
1 & 1 & 1 & 1 &         1   &          2   &          3   &          4   &          5   &          6   &  6  &  6  &  6  &  6 \\\\
1 & 1 & 1 & 1 &         1   &          2   &          3   &          4   &          5   &          6   &  6  &  6  &  6  &  6 \\\\
1 & 1 & 1 & 1 &  \\boxed{1}  &   \\boxed{2}  &   \\boxed{3}  &   \\boxed{4}  &   \\boxed{5}  &   \\boxed{6}  &  6  &  6  &  6  &  6 \\\\
2 & 2 & 2 & 2 &  \\boxed{2}  &   \\boxed{4}  &   \\boxed{6}  &   \\boxed{8}  &  \\boxed{10}  &  \\boxed{12}  & 12  & 12  & 12  & 12 \\\\
3 & 3 & 3 & 3 &  \\boxed{3}  &   \\boxed{6}  &   \\boxed{9}  &  \\boxed{12}  &  \\boxed{15}  &  \\boxed{18}  & 18  & 18  & 18  & 18 \\\\
4 & 4 & 4 & 4 &  \\boxed{4}  &   \\boxed{8}  &  \\boxed{12}  &  \\boxed{16}  &  \\boxed{20}  &  \\boxed{24}  & 24  & 24  & 24  & 24 \\\\
5 & 5 & 5 & 5 &  \\boxed{5}  &  \\boxed{10}  &  \\boxed{15}  &  \\boxed{20}  &  \\boxed{25}  &  \\boxed{30}  & 30  & 30  & 30  & 30 \\\\
6 & 6 & 6 & 6 &  \\boxed{6}  &  \\boxed{12}  &  \\boxed{18}  &  \\boxed{24}  &  \\boxed{30}  &  \\boxed{36}  & 36  & 36  & 36  & 36 \\\\
6 & 6 & 6 & 6 &         6   &         12   &         18   &         24   &         30   &         36   & 36  & 36  & 36  & 36 \\\\
6 & 6 & 6 & 6 &         6   &         12   &         18   &         24   &         30   &         36   & 36  & 36  & 36  & 36 \\\\
6 & 6 & 6 & 6 &         6   &         12   &         18   &         24   &         30   &         36   & 36  & 36  & 36  & 36 \\\\
6 & 6 & 6 & 6 &         6   &         12   &         18   &         24   &         30   &         36   & 36  & 36  & 36  & 36
 \\end{array}
}.
```

The command `padarray(A, Pad(:circular,4,4))` yields
```math
\\boxed{
\\begin{array}{ccccccccccccc}
9  & 12 & 15 & 18 &         3  &         6   &         9   &         12  &          15  &         18  & 3 &  6 &  9 & 12 \\\\
12 & 16 & 20 & 24 &         4  &         8   &        12   &         16  &          20  &         24  & 4 &  8 & 12 & 16 \\\\
15 & 20 & 25 & 30 &         5  &        10   &        15   &         20  &          25  &         30  & 5 & 10 & 15 & 20 \\\\
18 & 24 & 30 & 36 &         6  &        12   &        18   &         24  &          30  &         36  & 6 & 12 & 18 & 24 \\\\
3  &  4 &  5 &  6 &  \\boxed{1} &  \\boxed{2}  &  \\boxed{3}  &  \\boxed{4}  &  \\boxed{5}   &  \\boxed{6}  & 1 &  2 &  3 &  4 \\\\
6  &  8 & 10 & 12 &  \\boxed{2} &  \\boxed{4}  &  \\boxed{6}  &  \\boxed{8}  &  \\boxed{10}  &  \\boxed{12} & 2 &  4 &  6 &  8 \\\\
9  & 12 & 15 & 18 &  \\boxed{3} &  \\boxed{6}  &  \\boxed{9}  &  \\boxed{12} &  \\boxed{15}  &  \\boxed{18} & 3 &  6 &  9 & 12 \\\\
12 & 16 & 20 & 24 &  \\boxed{4} &  \\boxed{8}  &  \\boxed{12} &  \\boxed{16} &  \\boxed{20}  &  \\boxed{24} & 4 &  8 & 12 & 16 \\\\
15 & 20 & 25 & 30 &  \\boxed{5} &  \\boxed{10} &  \\boxed{15} &  \\boxed{20} &  \\boxed{25}  &  \\boxed{30} & 5 & 10 & 15 & 20 \\\\
18 & 24 & 30 & 36 &  \\boxed{6} &  \\boxed{12} &  \\boxed{18} &  \\boxed{24} &  \\boxed{30}  &  \\boxed{36} & 6 & 12 & 18 & 24 \\\\
3  &  4 &  5 &  6 &         1  &          2  &          3  &          4  &           5  &          6  & 1 &  2 &  3 &  4 \\\\
6  &  8 & 10 & 12 &         2  &          4  &          6  &          8  &          10  &         12  & 2 &  4 &  6 &  8 \\\\
9  & 12 & 15 & 18 &         3  &          6  &          9  &         12  &          15  &         18  & 3 &  6 &  9 & 12 \\\\
12 & 16 & 20 & 24 &         4  &          8  &         12  &         16  &          20  &         24  & 4 &  8 & 12 & 16
\\end{array}
}.
```
The command `padarray(A, Pad(:symmetric,4,4))` yields
```math
\\boxed{
\\begin{array}{ccccccccccccc}
16 & 12 &  8 & 4 &         4  &          8  &         12  &          16 &          20 &         24  & 24 & 20 & 16 & 12 \\\\
12 &  9 &  6 & 3 &         3  &          6  &         9   &          12 &          15 &         18  & 18 & 15 & 12 &  9 \\\\
 8 &  6 &  4 & 2 &         2  &          4  &         6   &          8  &          10 &         12  & 12 & 10 &  8 &  6 \\\\
 4 &  3 &  2 & 1 &         1  &          2  &         3   &          4  &          5  &         6   &  6 &  5 &  4 &  3 \\\\
 4 &  3 &  2 & 1 &  \\boxed{1} &   \\boxed{2} &  \\boxed{3}  &   \\boxed{4} &  \\boxed{5}  &  \\boxed{6}  &  6 &  5 &  4 &  3 \\\\
 8 &  6 &  4 & 2 &  \\boxed{2} &   \\boxed{4} &  \\boxed{6}  &   \\boxed{8} &  \\boxed{10} &  \\boxed{12} & 12 & 10 &  8 &  6 \\\\
12 &  9 &  6 & 3 &  \\boxed{3} &   \\boxed{6} &  \\boxed{9}  &  \\boxed{12} &  \\boxed{15} &  \\boxed{18} & 18 & 15 & 12 &  9 \\\\
16 & 12 &  8 & 4 &  \\boxed{4} &   \\boxed{8} &  \\boxed{12} &  \\boxed{16} &  \\boxed{20} &  \\boxed{24} & 24 & 20 & 16 & 12 \\\\
20 & 15 & 10 & 5 &  \\boxed{5} &  \\boxed{10} &  \\boxed{15} &  \\boxed{20} &  \\boxed{25} &  \\boxed{30} & 30 & 25 & 20 & 15 \\\\
24 & 18 & 12 & 6 &  \\boxed{6} &  \\boxed{12} &  \\boxed{18} &  \\boxed{24} &  \\boxed{30} &  \\boxed{36} & 36 & 30 & 24 & 18 \\\\
24 & 18 & 12 & 6 &         6  &         12  &         18  &         24  &         30  &         36  & 36 & 30 & 24 & 18 \\\\
20 & 15 & 10 & 5 &         5  &         10  &         15  &         20  &         25  &         30  & 30 & 25 & 20 & 15 \\\\
16 & 12 &  8 & 4 &         4  &          8  &         12  &         16  &         20  &         24  & 24 & 20 & 16 & 12 \\\\
12 &  9 &  6 & 3 &         3  &          6  &          9  &         12  &         15  &         18  & 18 & 15 & 12 &  9
\\end{array}
}.
```

The command `padarray(A, Pad(:reflect,4,4))` yields
```math
\\boxed{
\\begin{array}{ccccccccccccc}
25 & 20 & 15 & 10 &         5  &         10  &         15   &         20  &          25  &         30  & 25 & 20 & 15 & 10 \\\\
20 & 16 & 12 &  8 &         4  &         8   &         12   &         16  &          20  &         24  & 20 & 16 & 12 &  8 \\\\
15 & 12 &  9 &  6 &         3  &         6   &          9   &         12  &          15  &         18  & 15 & 12 &  9 &  6 \\\\
10 &  8 &  6 &  4 &         2  &         4   &          6   &         8   &          10  &         12  & 10 &  8 &  6 &  4 \\\\
5  &  4 &  3 &  2 &  \\boxed{1} &  \\boxed{2}  &   \\boxed{3}  &  \\boxed{4}  &   \\boxed{5}  &  \\boxed{6}  &  5 &  4 &  3 &  2 \\\\
10 &  8 &  6 &  4 &  \\boxed{2} &  \\boxed{4}  &   \\boxed{6}  &  \\boxed{8}  &   \\boxed{10} &  \\boxed{12} & 10 &  8 &  6 &  4 \\\\
15 & 12 &  9 &  6 &  \\boxed{3} &  \\boxed{6}  &   \\boxed{9}  &  \\boxed{12} &   \\boxed{15} &  \\boxed{18} & 15 & 12 &  9 &  6 \\\\
20 & 16 & 12 &  8 &  \\boxed{4} &  \\boxed{8}  &   \\boxed{12} &  \\boxed{16} &   \\boxed{20} &  \\boxed{24} & 20 & 16 & 12 &  8 \\\\
25 & 20 & 15 & 10 &  \\boxed{5} &  \\boxed{10} &   \\boxed{15} &  \\boxed{20} &   \\boxed{25} &  \\boxed{30} & 25 & 20 & 15 & 10 \\\\
30 & 24 & 18 & 12 &  \\boxed{6} &  \\boxed{12} &   \\boxed{18} &  \\boxed{24} &   \\boxed{30} &  \\boxed{36} & 30 & 24 & 18 & 12 \\\\
25 & 20 & 15 & 10 &         5  &         10  &          15  &         20  &          25  &         30  & 25 & 20 & 15 & 10 \\\\
20 & 16 & 12 &  8 &         4  &         8   &          12  &         16  &          20  &         24  & 20 & 16 & 12 &  8 \\\\
15 & 12 &  9 &  6 &         3  &         6   &           9  &         12  &          15  &         18  & 15 & 12 &  9 &  6 \\\\
10 &  8 &  6 &  4 &         2  &         4   &           6  &          8  &          10  &         12  & 10 &  8 &  6 &  4
\\end{array}
}.
```
## Examples with `Fill`

The command `padarray(A, Fill(0,(4,4),(4,4)))` yields
```math
\\boxed{
\\begin{array}{ccccccccccccc}
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{1} &  \\boxed{2}  &  \\boxed{3}  &  \\boxed{4}  &  \\boxed{5}  &   \\boxed{6}  & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{2} &  \\boxed{4}  &  \\boxed{6}  &  \\boxed{8}  &  \\boxed{10} &   \\boxed{12} & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{3} &  \\boxed{6}  &  \\boxed{9}  &  \\boxed{12} &  \\boxed{15} &   \\boxed{18} & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{4} &  \\boxed{8}  &  \\boxed{12} &  \\boxed{16} &  \\boxed{20} &   \\boxed{24} & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{5} &  \\boxed{10} &  \\boxed{15} &  \\boxed{20} &  \\boxed{25} &   \\boxed{30} & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &  \\boxed{6} &  \\boxed{12} &  \\boxed{18} &  \\boxed{24} &  \\boxed{30} &   \\boxed{36} & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 &         0  &         0   &         0   &         0   &         0   &          0   & 0 & 0 & 0 & 0
\\end{array}
}.
```
# 3D Examples
Each example is based on a multi-dimensional array ``\\mathsf{A} \\in\\mathbb{R}^{2 \\times 2 \\times 2}`` given by

```math
\\mathsf{A}(:,:,1) =
\\boxed{
\\begin{array}{cc}
1 & 2 \\\\
3 & 4
\\end{array}}
\\quad
\\text{and}
\\quad
\\mathsf{A}(:,:,2) =
\\boxed{
\\begin{array}{cc}
5 & 6 \\\\
7 & 8
\\end{array}}.
```
Note that each example will yield a new multi-dimensional array ``\\mathsf{A}'
\\in \\mathbb{R}^{4 \\times 4 \\times 4}`` of type `OffsetArray`, where prepended
dimensions may be negative or start from zero.

## Examples with `Pad`

The command `padarray(A,Pad(:replicate,1,1,1))` yields
```math
\\begin{aligned}
\\mathsf{A}'(:,:,0) & =
\\boxed{
\\begin{array}{cccc}
1 & 1 & 2 & 2 \\\\
1 & 1 & 2 & 2 \\\\
3 & 3 & 4 & 4 \\\\
3 & 3 & 4 & 4
\\end{array}}
&
\\mathsf{A}'(:,:,1) & =
\\boxed{
\\begin{array}{cccc}
1 &         1  &         2  & 2 \\\\
1 &  \\boxed{1} &  \\boxed{2} & 2 \\\\
3 &  \\boxed{3} &  \\boxed{4} & 4 \\\\
3 &         3  &         4  & 4
\\end{array}} \\\\
\\mathsf{A}'(:,:,2) & =
\\boxed{
\\begin{array}{cccc}
5 &         5  &         6  & 6 \\\\
5 &  \\boxed{5} &  \\boxed{6} & 6 \\\\
7 &  \\boxed{7} &  \\boxed{8} & 8 \\\\
7 &         7  &         8  & 8
\\end{array}}
&
\\mathsf{A}'(:,:,3) & =
\\boxed{
\\begin{array}{cccc}
5 & 5 & 6 & 6 \\\\
5 & 5 & 6 & 6 \\\\
7 & 7 & 8 & 8 \\\\
7 & 7 & 8 & 8
\\end{array}}
\\end{aligned}
.
```

The command `padarray(A,Pad(:circular,1,1,1))` yields
```math
\\begin{aligned}
\\mathsf{A}'(:,:,0) & =
\\boxed{
\\begin{array}{cccc}
8 & 7 & 8 & 7 \\\\
6 & 5 & 6 & 5 \\\\
8 & 7 & 8 & 7 \\\\
6 & 5 & 6 & 5
\\end{array}}
&
\\mathsf{A}'(:,:,1) & =
\\boxed{
\\begin{array}{cccc}
4 &         3  &         4  & 3 \\\\
2 &  \\boxed{1} &  \\boxed{2} & 1 \\\\
4 &  \\boxed{3} &  \\boxed{4} & 3 \\\\
2 &         1  &         2  & 1
\\end{array}} \\\\
\\mathsf{A}'(:,:,2) & =
\\boxed{
\\begin{array}{cccc}
8 &         7  &         8  & 7 \\\\
6 &  \\boxed{5} &  \\boxed{6} & 5 \\\\
8 &  \\boxed{7} &  \\boxed{8} & 7 \\\\
6 &         5  &         6  & 5
\\end{array}}
&
\\mathsf{A}'(:,:,3) & =
\\boxed{
\\begin{array}{cccc}
4 & 3 & 4 & 3 \\\\
2 & 1 & 2 & 1 \\\\
4 & 3 & 4 & 3 \\\\
2 & 1 & 2 & 1
\\end{array}}
\\end{aligned}
.
```

The command `padarray(A,Pad(:symmetric,1,1,1))` yields
```math
\\begin{aligned}
\\mathsf{A}'(:,:,0) & =
\\boxed{
\\begin{array}{cccc}
1 & 1 & 2 & 2 \\\\
1 & 1 & 2 & 2 \\\\
3 & 3 & 4 & 4 \\\\
3 & 3 & 4 & 4
\\end{array}}
&
\\mathsf{A}'(:,:,1) & =
\\boxed{
\\begin{array}{cccc}
1 &         1  &         2  & 2 \\\\
1 &  \\boxed{1} &  \\boxed{2} & 2 \\\\
2 &  \\boxed{3} &  \\boxed{4} & 4 \\\\
2 &         3  &         4  & 4
\\end{array}} \\\\
\\mathsf{A}'(:,:,2) & =
\\boxed{
\\begin{array}{cccc}
5 &         5  &         6  & 6 \\\\
5 &  \\boxed{5} &  \\boxed{6} & 6 \\\\
7 &  \\boxed{7} &  \\boxed{8} & 8 \\\\
7 &         7  &         8  & 8
\\end{array}}
&
\\mathsf{A}'(:,:,3) & =
\\boxed{
\\begin{array}{cccc}
5 & 5 & 6 & 6 \\\\
5 & 5 & 6 & 6 \\\\
7 & 7 & 8 & 8 \\\\
7 & 7 & 8 & 8
\\end{array}}
\\end{aligned}
.
```

The command `padarray(A,Pad(:reflect,1,1,1))` yields
```math
\\begin{aligned}
\\mathsf{A}'(:,:,0) & =
\\boxed{
\\begin{array}{cccc}
8 & 7 & 8 & 7 \\\\
6 & 5 & 6 & 5 \\\\
8 & 7 & 8 & 7 \\\\
6 & 5 & 6 & 5
\\end{array}}
&
\\mathsf{A}'(:,:,1) & =
\\boxed{
\\begin{array}{cccc}
4 &         3  &         4  & 3 \\\\
2 &  \\boxed{1} &  \\boxed{2} & 1 \\\\
4 &  \\boxed{3} &  \\boxed{4} & 3 \\\\
2 &         1  &         2  & 1
\\end{array}} \\\\
\\mathsf{A}'(:,:,2) & =
\\boxed{
\\begin{array}{cccc}
8 &         7  &         8  & 7 \\\\
6 &  \\boxed{5} &  \\boxed{6} & 5 \\\\
8 &  \\boxed{7} &  \\boxed{8} & 7 \\\\
6 &         5  &         6  & 5
\\end{array}}
&
\\mathsf{A}'(:,:,3) & =
\\boxed{
\\begin{array}{cccc}
4 & 3 & 4 & 3 \\\\
2 & 1 & 2 & 1 \\\\
4 & 3 & 4 & 3 \\\\
2 & 1 & 2 & 1
\\end{array}}
\\end{aligned}
.
```
## Examples with `Fill`

The command `padarray(A,Fill(0,(1,1,1)))` yields
```math
\\begin{aligned}
\\mathsf{A}'(:,:,0) & =
\\boxed{
\\begin{array}{cccc}
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0
\\end{array}}
&
\\mathsf{A}'(:,:,1) & =
\\boxed{
\\begin{array}{cccc}
0 &         0  &         0  & 0 \\\\
0 &  \\boxed{1} &  \\boxed{2} & 0 \\\\
0 &  \\boxed{3} &  \\boxed{4} & 0 \\\\
0 &         0  &         0  & 0
\\end{array}} \\\\
\\mathsf{A}'(:,:,2) & =
\\boxed{
\\begin{array}{cccc}
0 &         0  &         0  & 0 \\\\
0 &  \\boxed{5} &  \\boxed{6} & 0 \\\\
0 &  \\boxed{7} &  \\boxed{8} & 0 \\\\
0 &         0  &         0  & 0
\\end{array}}
&
\\mathsf{A}'(:,:,3) & =
\\boxed{
\\begin{array}{cccc}
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0 \\\\
0 & 0 & 0 & 0
\\end{array}}
\\end{aligned}
.
```

---

"""
padarray(img::AbstractArray, border::AbstractBorder)  = padarray(eltype(img), img, border)
function padarray(::Type{T}, img::AbstractArray, border) where {T}
    ba = BorderArray(img, border)
    out = similar(ba, T, axes(ba))
    copy!(out, ba)
end

padarray(img, ::Type{P}) where {P} = img[padindices(img, P)...]      # just to throw the nice error

function copydata!(dest, img, inds)
    isempty(inds) && return dest
    idest = axes(dest)
    # Work around julia #9080
    i1, itail = idest[1], tail(idest)
    inds1, indstail = inds[1], tail(inds)
    @inbounds for I in CartesianIndices(itail)
        J = CartesianIndex(map((i,x)->x[i], Tuple(I), indstail))
        for i in i1
            j = inds1[i]
            dest[i,I] = img[j,J]
        end
    end
    dest
end

function copydata!(dest::OffsetArray, img, inds::Tuple{Vararg{OffsetArray}})
    copydata!(parent(dest), img, map(parent, inds))
    dest
end

Base.ndims(b::AbstractBorder) = ndims(typeof(b))
Base.ndims(::Type{Pad{N}}) where {N} = N

# Make these separate types because the dispatch almost surely needs to be different
"""
    Inner()
    Inner(lo, hi)

Indicate that edges are to be discarded in filtering, only the interior of the result is to be returned.

# Example:

    imfilter(img, kernel, Inner())
"""
struct Inner{N} <: AbstractBorder
    lo::Dims{N}
    hi::Dims{N}
end

Inner(both::Int...) = Inner(both, both)
Inner(::Tuple{}) = Inner((), ())   # ambiguity resolution
Inner(both::Dims{N}) where {N} = Inner(both, both)
Inner(lo::Tuple{}, hi::Tuple{}) = Inner{0}(lo, hi)
Inner(lo::Dims{N}, hi::Tuple{}) where {N} = Inner{N}(lo, ntuple(d->0,Val(N)))
Inner(lo::Tuple{}, hi::Dims{N}) where {N} = Inner{N}(ntuple(d->0,Val(N)), hi)
Inner(inds::Indices{N}) where {N} = Inner{N}(map(lo,inds), map(hi,inds))
Inner{N}(lo::AbstractVector, hi::AbstractVector) where {N} = Inner{N}((lo...,), (hi...,))
Inner(lo::AbstractVector, hi::AbstractVector) = Inner((lo...,), (hi...,)) # not inferrable

(p::Inner{0})(kernel, img, ::Alg) = p(kernel)
(p::Inner{0})(kernel) = Inner(calculate_padding(kernel))

Base.ndims(::Type{Inner{N}}) where N = N

"""
    NA(na=isnan)

Choose filtering using "NA" (Not Available) boundary conditions. This
is most appropriate for filters that have only positive weights, such
as blurring filters. Effectively, the output value is normalized in the
following way:

              filtered array with Fill(0) boundary conditions
    output =  -----------------------------------------------
              filtered 1     with Fill(0) boundary conditions

Array elements for which `na` returns `true` are also considered outside
array boundaries.
"""
struct NA{na} <: AbstractBorder
    NA(na=isnan) = new{na}()
end

"""
```julia
    struct Fill{T,N} <: AbstractBorder
        value::T
        lo::Dims{N}
        hi::Dims{N}
    end
```

`Fill` is a type that designates a particular value which will be used to
extrapolate pixels beyond the boundary of an image.

# Output

The type `Fill` specifying the value with which the boundary of the image should
be padded.

# Details
When representing a two-dimensional spatial image filtering operation as a
discrete convolution between an image and a ``D \\times D `` filter, the
results are undefined for pixels closer than ``D`` pixels from the border of the
image. To define the operation near and at the border, one needs a scheme for
extrapolating pixels beyond the edge. The `Fill` type allows one to specify a
particular value which will be used in the extrapolation. For more elaborate
extrapolation schemes refer to the documentation of  [`Pad`](@ref).

The type facilitates the padding of one, two or multi-dimensional images.

You can specify a different amount of padding at the lower and upper borders of
each dimension of the image (top, left, bottom and right in two dimensions).

# Example
As an indicative illustration consider an image consisting of a
row of six pixels which are specified alphabetically: ``\\boxed{a \\, b \\, c \\,
d \\, e \\, f}``. We show the effects of padding with a constant value
``m`` only on the left and right
border, but analogous consequences hold for the top and bottom border.

```math
\\boxed{
\\begin{array}{l|c|r}
  m\\, m\\, m\\, m  &  a \\, b \\, c \\, d \\, e \\, f & m \\, m \\, m \\, m
\\end{array}
}
```
See also: [`Pad`](@ref), [`padarray`](@ref), [`Inner`](@ref) and
[`NoPad`](@ref)

---

"""
struct Fill{T,N} <: AbstractBorder
    value::T
    lo::Dims{N}
    hi::Dims{N}

    Fill{T,N}(value::T) where {T,N} = new{T,N}(value)
    Fill{T,N}(value::T, lo::Dims{N}, hi::Dims{N}) where {T,N} = new{T,N}(value, lo, hi)
end
Base.ndims(::Type{Fill{T,N}}) where {T,N} = N

"""
```julia
Fill(value::T)
```

Construct an instance of [`Fill`](@ref) designating a `value` and zero padding (i.e. no padding).

---

"""
Fill(value::T) where {T} = Fill{T,0}(value)

"""
```julia
    Fill(value::T, lo::Dims{N}, hi::Dims{N})
    Fill(value, lo::AbstractVector, hi::AbstractVector)
```
Construct an instance of [`Fill`](@ref) designating a `value` such that the image is prepended by `lo` pixels  and appended by `hi` pixels  in each dimension.

#### Usage illustration
Use `Fill(5,(2,2),(2,2))` to specify a padding of two pixels for the top,
bottom, left and right edge with the value five.

Use `Fill(zero(eltype(img))(1,2),(3,4))` to specify a padding of one, two, three
and four pixels for the top, left, bottom and right edge respectively using a
value of zero with the same type as `img`.

Use `Fill(0,[1,2],[3,4]` to specify a padding of one, two, three and four pixels
for the top, left, bottom and right edge respectively with the value zero.

---

"""
Fill(value::T, lo::Dims{N}, hi::Dims{N}) where {T,N} = Fill{T,N}(value, lo, hi)

"""
```julia
    Fill(value::T, both::Dims{N})
```
Construct an instance of [`Fill`](@ref) designating a `value`  and a tuple
`both` which stipulates the number of row and columns which will be prepended
and appended to the image.

#### Usage illustration
Use `Fill(0,(5,10))` to stipulate a padding of five pixels for the top and left
edge, and a padding of ten pixels for the bottom and right edge with a value of
zero.

---

"""
Fill(value::T, both::Dims{N}) where {T,N} = Fill{T,N}(value, both, both)

Fill(value, lo::AbstractVector, hi::AbstractVector) = Fill(value, (lo...,), (hi...,))
Fill(value::T, ::Tuple{}) where {T} = Fill{T,0}(value, (), ())   # ambiguity resolution
Fill(value::T, inds::Base.Indices{N}) where {T,N} = Fill{T,N}(value, map(lo,inds), map(hi,inds))

"""
```julia
    Fill(value, kernel)
```
Construct an instance of [`Fill`](@ref) by designating a `value` and a
`kernel` which will be used to infer an appropriate padding.

A minimal amount of padding is added which ensures that a convolution between
the image and the kernel is defined at the boundary.

#### Usage illustration
Use `Fill(0,Kernel.sobel())` to specify a value of zero and the minimal amount
of padding necessary to ensure that convolution with [`Kernel.sobel`](@ref) will
be defined at the borders of an image.

---

"""
Fill(value, kernel) = Fill(value, calculate_padding(kernel))

(p::Fill)(kernel) = Fill(p.value, kernel)
(p::Fill)(kernel, img, ::Alg) = Fill(p.value, kernel)
function (p::Fill)(kernel, img, ::FFT)
    inds = calculate_padding(kernel)
    newinds = map(padfft, inds, map(length, axes(img)))
    Fill(p.value, newinds)
end

# There are other ways to define these, but using `mod` makes it safe
# for cases where the padding is bigger than length(inds)
"""
    padindex(border::Pad, lo::Integer, inds::AbstractUnitRange, hi::Integer)

Generate an index-vector to be used for padding. `inds` specifies the image axes along a particular axis; `lo` and `hi` are the amount to pad on the lower and upper, respectively, sides of this axis. `border` specifying the style of padding.
"""
function padindex(border::Pad, lo::Int, inds::UnitRange{Int}, hi::Int)
    if border.style == :replicate
        indsnew = Int[fill(first(inds), lo); inds; fill(last(inds), hi)]
        OffsetArray(indsnew, first(inds)-lo:last(inds)+hi)
    elseif border.style == :circular
        return modrange(extend(lo, inds, hi), inds)
    elseif border.style == :symmetric
        I = OffsetArray(Int[inds; reverse(inds)], (0:2*length(inds)-1) .+ first(inds))
        r = modrange(extend(lo, inds, hi), axes(I, 1))
        return I[r]
    elseif border.style == :reflect
        I = OffsetArray(Int[inds; last(inds)-1:-1:first(inds)+1], (0:2*length(inds)-3) .+ first(inds))
        return I[modrange(extend(lo, inds, hi), axes(I, 1))]
    else
        error("border style $(border.style) unrecognized")
    end
end
padindex(border::Pad, lo::Integer, inds::AbstractUnitRange, hi::Integer) = padindex(border, Int(lo)::Int, UnitRange{Int}(inds)::UnitRange{Int}, Int(hi)::Int)
function padindex(border::Pad, inner::AbstractUnitRange, outer::AbstractUnitRange)
    lo = max(0, first(inner)-first(outer))
    hi = max(0, last(outer)-last(inner))
    padindex(border, lo, inner, hi)
end

function inner(lo::Integer, inds::AbstractUnitRange, hi::Integer)
    first(inds)+lo:last(inds)-hi
end

lo(o::Integer) = max(-o, zero(o))
lo(r::AbstractUnitRange) = lo(first(r))

hi(o::Integer) = max(o, zero(o))
hi(r::AbstractUnitRange) = hi(last(r))

# extend(lo::Integer, inds::AbstractUnitRange, hi::Integer) = CatIndices.URange(first(inds)-lo, last(inds)+hi)
function extend(lo::Integer, inds::AbstractUnitRange, hi::Integer)
    newind = first(inds)-lo:last(inds)+hi
    OffsetArray(newind, newind)
end

calculate_padding(kernel) = axes(kernel)
@inline function calculate_padding(kernel::Tuple{Any, Vararg{Any}})
    inds = accumulate_padding(axes(kernel[1]), tail(kernel)...)
    if hasiir(kernel) && hasfir(kernel)
        inds = map(doublepadding, inds)
    end
    inds
end

hasiir(kernel) = _hasiir(false, kernel...)
_hasiir(ret) = ret
_hasiir(ret, kern, kernel...) = _hasiir(ret, kernel...)
_hasiir(ret, kern::AnyIIR, kernel...) = true

hasfir(kernel) = _hasfir(false, kernel...)
_hasfir(ret) = ret
_hasfir(ret, kern, kernel...) = true
_hasfir(ret, kern::AnyIIR, kernel...) = _hasfir(ret, kernel...)

function doublepadding(ind::AbstractUnitRange)
    f, l = first(ind), last(ind)
    f = f < 0 ? 2f : f
    l = l > 0 ? 2l : l
    f:l
end

accumulate_padding(inds::Indices, kernel1, kernels...) =
    accumulate_padding(expand(inds, axes(kernel1)), kernels...)
accumulate_padding(inds::Indices) = inds

modrange(x, r::AbstractUnitRange) = mod(x-first(r), length(r))+first(r)
modrange(A::AbstractArray, r::AbstractUnitRange) = map(x->modrange(x, r), A)

arraytype(A::AbstractArray, ::Type{T}) where {T} = Array{T}  # fallback
arraytype(A::BitArray, ::Type{Bool}) = BitArray

interior(A, kernel) = _interior(axes(A), axes(kernel))
interior(A, factkernel::Tuple) = _interior(axes(A), accumulate_padding(axes(factkernel[1]), tail(factkernel)...))
function _interior(indsA::NTuple{N}, indsk) where N
    indskN = fill_to_length(indsk, 0:0, Val(N))
    map(intersect, indsA, shrink(indsA, indsk))
end

next_shrink(inds::Indices, ::Tuple{}) = inds
function next_shrink(inds::Indices, kernel::Tuple)
    kern = first(kernel)
    iscopy(kern) && return next_shrink(inds, tail(kernel))
    shrink(inds, kern)
end

"""
    expand(inds::Indices, kernel)
    expand(inds::Indices, indskernel::Indices)

Expand an image region `inds` to account for necessary padding by `kernel`.
"""
expand(inds::Indices, kernel) = expand(inds, calculate_padding(kernel))
expand(inds::Indices, pad::Indices) = firsttype(map_copytail(expand, inds, pad))
expand(ind::AbstractUnitRange, pad::AbstractUnitRange) = typeof(ind)(first(ind)+first(pad):last(ind)+last(pad))
expand(ind::Base.OneTo, pad::AbstractUnitRange) = expand(UnitRange(ind), pad)

"""
    shrink(inds::Indices, kernel)
    shrink(inds::Indices, indskernel)

Remove edges from an image region `inds` that correspond to padding needed for `kernel`.
"""
shrink(inds::Indices, kernel) = shrink(inds, calculate_padding(kernel))
shrink(inds::Indices, pad::Indices) = firsttype(map_copytail(shrink, inds, pad))
shrink(ind::AbstractUnitRange, pad::AbstractUnitRange) = typeof(ind)(first(ind)-first(pad):last(ind)-last(pad))
shrink(ind::Base.OneTo, pad::AbstractUnitRange) = shrink(UnitRange(ind), pad)

allocate_output(::Type{T}, img, kernel, border) where {T} = similar(img, T)
function allocate_output(::Type{T}, img, kernel, ::Inner{0}) where T
    inds = interior(img, kernel)
    similar(img, T, inds)
end
function allocate_output(::Type{T}, img, kernel, inr::Inner) where T
    ndims(img) == ndims(inr) || throw(DimensionMismatch("dimensionality of img and the border must agree, got $(ndims(img)) and $(ndims(inr))"))
    inds = inner.(inr.lo, axes(img), inr.hi)
    similar(img, T, inds)
end
allocate_output(img, kernel, border) = allocate_output(filter_type(img, kernel), img, kernel, border)

"""
    map_copytail(f, a::Tuple, b::Tuple)

Apply `f` to paired elements of `a` and `b`, copying any tail elements
when the lengths of `a` and `b` are not equal.
"""
@inline map_copytail(f, a::Tuple, b::Tuple) = (f(a[1], b[1]), map_copytail(f, tail(a), tail(b))...)
map_copytail(f, a::Tuple{}, b::Tuple{}) = ()
map_copytail(f, a::Tuple, b::Tuple{}) = a
map_copytail(f, a::Tuple{}, b::Tuple) = b

function firsttype(t::Tuple)
    T = typeof(t[1])
    map(T, t)
end
firsttype(::Tuple{}) = ()

# end


text:
using Revise
using Dice

# Convert (value, weight) tuples to input for dice discrete distribution
# Example: unpack_sparse([(0, 0.2), (3, 0.8)]) => [0.2, 0, 0, 0.8]
function unpack_sparse(sparse::Vector{Tuple{Int,Float64}})
    dice_disc = zeros(maximum(val for (val, _) in sparse) + 1)
    for (val, weight) in sparse
        dice_disc[val + 1] = weight
    end
    return dice_disc
end

# Naively calculate P[gcd(X, Y) = res] given distributions of X and Y
function gcd_enumeration(
    sparse1::Vector{Tuple{Int,Float64}},
    sparse2::Vector{Tuple{Int,Float64}},
    res::Int
)
    total_weight = 0
    for (val1, weight1) in sparse1
        for (val2, weight2) in sparse2
            if gcd(val1, val2) == res
                total_weight += weight1 * weight2
            end
        end
    end
    return total_weight
end

# Generate Dice code for P[gcd(X, Y) = res] given distributions of X and Y
function code_gcd(
    sparse1::Vector{Tuple{Int,Float64}},
    sparse2::Vector{Tuple{Int,Float64}},
    res::Int
)
    discrete_dist1 = unpack_sparse(sparse1)
    discrete_dist2 = unpack_sparse(sparse2)
    @dice begin
        function discrete(p::Vector{Float64})
            mb = length(p)
            v = Vector(undef, mb)
            sum = 1
            for i=1:mb
                v[i] = p[i]/sum
                sum = sum - p[i]
            end

            ans = DistInt(dicecontext(), mb-1)
            for i=mb-1:-1:1
                ans = if flip(v[i]) DistInt(dicecontext(), i-1) else ans end
            end
            return ans
        end

        # Subtraction-based GCD
        # https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations
        function dice_gcd(a::Tuple{DistInt, DistBool}, b::Tuple{DistInt, DistBool})
            # Swap if a < b to handle cases where a=0 and b!=0
            lt = b[1] > a[1]
            t = a
            a = Dice.ifelse(lt, b[1], a[1]), Dice.ifelse(lt, b[2], a[2])
            b = Dice.ifelse(lt, t[1], b[1]), Dice.ifelse(lt, t[2], b[2])

            for _ = 0:100  # TODO: better bound
                gt = a[1] > b[1]
                lt = b[1] > a[1]
                amb = a[1] - b[1]
                bma = b[1] - a[1]
                a = Dice.ifelse(gt, amb[1], a[1]), a[2] | (amb[2] & gt)
                b = Dice.ifelse(lt, bma[1], b[1]), b[2] | (bma[2] & lt)
            end
            return a[1], a[2]
        end

        a = discrete(discrete_dist1), flip(0)
        b = discrete(discrete_dist2), flip(0)
        g = dice_gcd(a, b)
        prob_equals(g[1], res) & !g[2]
    end
end

all_pass = true
function test(sparse::Vector{Tuple{Int,Float64}})
    for res = 0:10
        code = code_gcd(sparse, sparse, res)
        bdd = compile(code)
        dice_p = infer(code, :bdd)
        naive_p = gcd_enumeration(sparse, sparse, res)
        if !isapprox(dice_p, naive_p)
            global all_pass = false
            println("FAIL. ", sparse, " ", res, " Expected: ", naive_p, " Got: ", dice_p)
        end
    end
end

sparse1 = [(4, 0.3), (6, 0.2), (9, 0.5)] # 4 has probability 0.3, etc...
sparse2 = [(0, 0.2), (4, 0.3), (6, 0.5)]
test(sparse1)
test(sparse2)
if all_pass
    println("ALL TESTS PASSED")
end

ext: jl


text:
using Plots
import Plots: plot

function plot(tdf::TimeDataFrame; label = [], title = "")
    x = [string(p) for p in periods(tdf)]
    if TimeDataFrames.ncol(tdf) > 1 && isempty(label)
        label = names(tdf)
    end
    for (i,c) in enumerate(eachcol(tdf))
        if i == 1
            Plots.plot(x, c, legend=false)
        else
            Plots.plot!(x, c, label = label[i])
        end
    end

    if TimeDataFrames.ncol(tdf) == 1 && isempty(title)
            title = names(tdf)[1]
    end
    Plots.plot!(label = label,
                title = title)
end


text:
using RoadRunner
using Test
using Plots

rr = RoadRunner.createRRInstance()

ant_str = """
    const Xo, X1
     Xo -> S1; k1*Xo - k2*S1
     S1 -> S2; k3*S2
     S2 -> X1; k4*S2

     Xo = 1;   X1 = 0
     S1 = 0;   S2 = 0
     k1 = 0.1; k2 = 0.56
     k3 = 1.2; k4 = 0.9
"""

rr = loada(ant_str)
data = simulate(rr, 0, 10, 51)
plotSimulation(data, rr)

# speciesName = getFloatingSpeciesIds(rr)
# time = data[:, 1]
# plot(time, data[:, 2:end], label = speciesName)
# RoadRunner.freeRRInstance(rr)

@testset "RoadRunner.jl" begin
    ssValues = RoadRunner.computeSteadyStateValues(rr)
    sol = [0.178571429, -4.38678616e-27]
    diff = ssValues - sol
    @test abs(diff[1]) < 1e-7 && abs(diff[2]) < 1e-7
end

max_stars_repo_name: Lukez-pi/RoadRunnerJulia

max_issues_repo_name: Lukez-pi/RoadRunnerJulia


text:
module TC
abstract TypeClass
abstract Arrow{A,R}
abstract Value{T}
abstract State{T}

export Arrow, Value, State, TypeClass, is_of_type

deref{T}( ::Type{T} ) = T.parameters[1]

totype{T}( t::Type{T} ) = T
totype{T}( t::Type{Value{T}}) = retval(next, Tuple{T,totype(State{T})}).parameters[1]
totype{T}( t::Type{State{T}}) = retval(start, Tuple{T} )

function retval{A}(func::Function, ::Type{A})
  ct = code_typed( func, A )
  (length( ct ) < 1 || length( ct[1].args ) < 3) && return Any
  return ct[1].args[3].typ
end

function norm{T<:Tuple}( ::Type{T})
  converted = map(totype, T.parameters )
  return eval(current_module(), parse( "Tuple{$(join(converted, ","))}" ))
end

function norm{T}( ::Type{T})
  return totype( T )
end

# Recursive definition for inherited properties
function check_sig{T<:Tuple}( t::Type{T}, f::Symbol )
  contains( (typ,comp) -> comp == contains( (f,comp)->comp == check_sig( fieldtype( typ, f ) ,f ), fieldnames( typ ), false), t.parameters, false )
end

function check_sig{A,R}( t::Type{Arrow{A,R}}, f::Symbol )
  func = eval( current_module(), f )
  an = norm(A)
  rn = norm(R)
  ret = method_exists( func, an ) && rn == retval( func, an )
  !ret && warn( "checking function '$f' mismatch signature $A -> $R ")
  return ret
end

_is_of_type{T}( ::Type{T}) = false
function _is_of_type{TTC<:TypeClass}( typ::Type{TTC} )
  return !contains( (f,comp)->comp == check_sig( fieldtype( typ, f ) ,f ), fieldnames( typ ), false)
end

@generated function is_of_type{TTC<:TypeClass}( typ::Type{TTC} )
  _is_of_type(deref( typ ))?:(true) : :(false)
end

end


text:
export @test_equal, @assert_equal,  @test_almostequal

function test_equal(a, b, doassert = false)
    if isequal(a,b) && isequal(typeof(a), typeof(b)) return end
    local sizea = ()
    try sizea = siz(a); catch end
    local sizeb = ()
    try sizeb = siz(b); catch end
    local sizesEqual = isequal(sizea,sizeb)
    local contentEqual = true
    if sizesEqual
        if sizea == sizeb == ()
            contentEqual = a == b
        else
            for i = 1:length(a)
                if a[i] != b[i]
                    println("typeof a[i] is $(typeof(a[i]))")
                    println("typeof b[i] is $(typeof(b[i]))")
                    contentEqual = false
                    break
                end
            end
        end
    end
    if !sizesEqual || !contentEqual
        println("########### Failed test: $(!sizesEqual ? "size mismatch, " : "")$(!contentEqual ? "content mismatch" : "")")
        # println(a)
        println("\nShould have been: $b")
        #println($(esc(b)))
        println("of size $(sizeb') \nof type $(typeof(b))")
        println("\nwas $a")
        #println($(esc(a)))
        println("of size $(sizea') \nof type $(typeof(a))\n")
        doassert && error("@assert_equal: test failed")
        return false
    else
        return true
    end
end

macro test_equal(A,B)
    quote
        local a = $(esc(A))
        local b = $(esc(B))
        test_equal(a,b)
    end
end

macro assert_equal(A,B)
    quote
        local a = $(esc(A))
        local b = $(esc(B))
        test_equal(a,b,true)
    end
end

macro test_almostequal(A,B,MAXDIFF)
    #println(A)
    quote
        local a = $(esc(A))
        #if isequal(a,Void)   # FIXME this check does not actually work
        #    error("test_equal: the following function does not return any output! \n\n $A \n\n")
        #end
        local b = $(esc(B))
        local maxdiff = $(esc(MAXDIFF))
        if isequal(a,b) && isequal(typeof(a), typeof(b)) return end
        local sizea = siz(a)
        local sizeb = siz(b)
        local sizesEqual = isequal(sizea,sizeb)
        local contentEqual = true
        if sizesEqual
            for i = 1:length(a)
                if abs(a[i]-b[i]) > maxdiff
                    println("typeof a[i] is $(typeof(a[i]))")
                    println("typeof b[i] is $(typeof(b[i]))")
                    contentEqual = false
                    break
                end
            end
        end
        if ~sizesEqual || ~contentEqual
            println("########### Failed test:")
            println($(string(A)))
            println("\nShould have been: $b")
            #println($(esc(b)))
            println("of size $(sizeb') \nof type $(typeof(b))")
            println("\nwas $a")
            #println($(esc(a)))
            println("of size $(sizea') \nof type $(typeof(b))\n")
        end
    end
end


text:
# Autogenerated wrapper script for ERFA_jll for aarch64-apple-darwin
export liberfa

JLLWrappers.@generate_wrapper_header("ERFA")
JLLWrappers.@declare_library_product(liberfa, "@rpath/liberfa.1.dylib")
function __init__()
    JLLWrappers.@generate_init_header()
    JLLWrappers.@init_library_product(
        liberfa,
        "lib/liberfa.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
"""
    Stats(attack, defense, hitpoints)

Stores the attack, defense, and hitpoints values (factoring in base and IV
stats) of a Pokemon in integers. This means that attack and defense are 100
times larger than their real value.
"""
struct Stats
    attack::UInt16
    defense::UInt16
    hitpoints::Int16
end

"""
    StatBuffs(val)
Because the stat buffs can only be between -4 and 4 inclusive, they are
collectively stored as one 8-bit integer. Use of this constructor is not
recommended. Also, use get_atk and get_def functions for extracting individual
values.
"""
struct StatBuffs
    val::UInt8
end

"""
    StatBuffs(atk, def)
Friendlier constructor for StatBuffs where attack and defense buffs can be
passed in
"""
function StatBuffs(atk::Int8, def::Int8)
    StatBuffs((clamp(atk, Int8(-4), Int8(4)) + Int8(8)) +
        (clamp(def, Int8(-4), Int8(4)) + Int16(8))<<Int16(4))
end

"""
    get_atk(x::StatBuffs)
Get the attack value of the StatBuffs passed in
"""
get_atk(x::StatBuffs) = Int8(x.val & 0x0F) - Int8(8)

"""
    get_def(x::StatBuffs)
Get the defense value of the StatBuffs passed in
"""
get_def(x::StatBuffs) = Int8(x.val >> 4) - Int8(8)

"""
    +(x::StatBuffs, y::StatBuffs)
Adding two StatBuffs together adds the attack and defense values, and clamps
the result between -4 and 4 inclusive.
"""
Base.:+(x::StatBuffs, y::StatBuffs) = StatBuffs(get_atk(x) + get_atk(y),
    get_def(x) + get_def(y))

# Value for all buffs being zero, useful for construction or after switches
const defaultBuff = StatBuffs(Int8(0), Int8(0))


text:
module Utils

include("util.jl")
include("math.jl")
include("rand.jl")
include("io.jl")
include("lalg.jl")
include("blockinv.jl")

end # module


text:
mutable struct ComposeFactorCurve{F<:Function, IRA<:AbstractIRCurve, IRB<:AbstractIRCurve} <: AbstractIRCurve{NullMethod}
    name::String
    date::Date
    op::F
    curve_a::IRA
    curve_b::IRB
    daycount::DayCountConvention
    compounding::CompoundingType

    function ComposeFactorCurve(
                name::AbstractString,
                date::Date,
                op::F,
                curve_a::IRA,
                curve_b::IRB,
                daycount::DayCountConvention,
                compounding::CompoundingType
            ) where {F<:Function, IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

        @assert curve_get_date(curve_a) == date && curve_get_date(curve_b) == date "curve_a and curve_b should have the same dates"
        return new{F, IRA, IRB}(name, date, op, curve_a, curve_b, daycount, compounding)
    end
end

function ComposeFactorCurve(
                date::Date,
                op::F,
                curve_a::IRA,
                curve_b::IRB,
                daycount::DayCountConvention,
                compounding::CompoundingType
            ) where {F<:Function, IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

    ComposeFactorCurve("", date, op, curve_a, curve_b, daycount, compounding)
end

"""
    ComposeProdFactorCurve(
            [name],
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

Creates a curve that is composed by the discount factors of `curve_a * curve_b`.
The resulting curve will produce zero rates based on `daycount` and `compounding` conventions.
"""
function ComposeProdFactorCurve(
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

    @nospecialize daycount compounding
    return ComposeFactorCurve(curve_get_date(curve_a), *, curve_a, curve_b, daycount, compounding)
end

function ComposeProdFactorCurve(
            name::AbstractString,
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

    @nospecialize daycount compounding
    return ComposeFactorCurve(name, curve_get_date(curve_a), *, curve_a, curve_b, daycount, compounding)
end

"""
    ComposeDivFactorCurve(
            [name],
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

Creates a curve that is composed by the discount factors of `curve_a / curve_b`.
The resulting curve will produce zero rates based on `daycount` and `compounding` conventions.
"""
function ComposeDivFactorCurve(
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

    @nospecialize daycount compounding
    return ComposeFactorCurve(curve_get_date(curve_a), /, curve_a, curve_b, daycount, compounding)
end

function ComposeDivFactorCurve(
            name::AbstractString,
            curve_a::IRA,
            curve_b::IRB,
            daycount::DayCountConvention,
            compounding::CompoundingType
        ) where {IRA<:AbstractIRCurve, IRB<:AbstractIRCurve}

    @nospecialize daycount compounding
    return ComposeFactorCurve(name, curve_get_date(curve_a), /, curve_a, curve_b, daycount, compounding)
end

function curve_get_name(c::ComposeFactorCurve)
    @nospecialize c
    return c.name
end

function curve_get_date(c::ComposeFactorCurve)
    @nospecialize c
    return c.date
end

function curve_get_daycount(c::ComposeFactorCurve)
    @nospecialize c
    return c.daycount
end

function curve_get_compounding(c::ComposeFactorCurve)
    @nospecialize c
    return c.compounding
end

# using @eval instead of Union{Date, YearFraction} to avoid method ambiguity
for T in (:Date, :YearFraction)
    @eval begin
        function ERF(c::ComposeFactorCurve, maturity::$T)
            return c.op( ERF(c.curve_a, maturity), ERF(c.curve_b, maturity) )
        end

        function zero_rate(c::ComposeFactorCurve, maturity::$T)
            erf = ERF(c, maturity)
            ERF_to_rate(c, erf, maturity)
        end
    end
end

# Unoptimized vector function for ERF
function ERF(curve::ComposeFactorCurve, maturity_vec::Vector{Date})
    @nospecialize curve

    len = length(maturity_vec)
    result = Vector{Float64}(undef, len)
    for i in 1:len
        @inbounds result[i] = ERF(curve, maturity_vec[i])
    end
    return result
end

# Unoptimized vector function for discountfactor
function discountfactor(curve::ComposeFactorCurve, maturity_vec::Vector{Date})
    @nospecialize curve

    len = length(maturity_vec)
    result = Vector{Float64}(undef, len)
    for i in 1:len
        @inbounds result[i] = discountfactor(curve, maturity_vec[i])
    end
    return result
end


text:
module XCompiler

# overloads
# ---------

import Core.Compiler:
    # interfaces.jl
    InferenceParams,
    OptimizationParams,
    get_world_counter,
    get_inference_cache,
    lock_mi_inference,
    unlock_mi_inference,
    add_remark!,
    may_optimize,
    may_compress,
    may_discard_trees,
    # cicache.jl
    code_cache,
    get_inference_cache,
    cache_lookup,
    # inferencestate.jl
    InferenceState,
    method_table,
    # tfuncs.jl
    builtin_tfunction,
    return_type_tfunc,
    # abstractinterpretation.jl
    abstract_call_gf_by_type,
    bail_out_toplevel_call,
    bail_out_call,
    bail_out_apply,
    add_call_backedges!,
    abstract_call_method_with_const_args,
    maybe_get_const_prop_profitable,
    const_prop_entry_heuristic,
    const_prop_argument_heuristic,
    const_prop_rettype_heuristic,
    force_const_prop,
    const_prop_function_heuristic,
    const_prop_methodinstance_heuristic,
    abstract_call_method,
    precise_container_type,
    abstract_iteration,
    abstract_apply,
    bail_out_apply,
    abstract_call_builtin,
    abstract_invoke,
    abstract_call_known,
    abstract_call,
    abstract_eval_cfunction,
    abstract_eval_value_expr,
    abstract_eval_special_value,
    abstract_eval_value,
    abstract_eval_statement,
    typeinf_local,
    typeinf_nocycle,
    # optimize.jl
    IRCode,
    OptimizationState,
    finish,
    optimize,
    # typeinfer.jl
    typeinf,
    _typeinf,
    finish!,
    maybe_compress_codeinfo,
    transform_result_for_cache,
    cache_result!,
    finish,
    is_same_frame,
    resolve_call_cycle!,
    typeinf_edge

# imports
# -------

import Core:
    CodeInfo,
    MethodInstance,
    CodeInstance,
    MethodMatch,
    LineInfoNode,
    SimpleVector,
    Builtin,
    Typeof,
    svec

import Core.Compiler:
    AbstractInterpreter,
    NativeInterpreter,
    InferenceResult,
    WorldRange,
    WorldView,
    MethodCallResult,
    VarTable,
    to_tuple_type,
    _methods_by_ftype,
    specialize_method,
    inlining_enabled

import Base:
    unwrap_unionall,
    rewrap_unionall,
    destructure_callex

import Base.Meta:
    isexpr,
    lower

using InteractiveUtils

const CC = Core.Compiler

function __init__()
    @eval begin
        using Revise

        files = normpath.(@__DIR__, (readdir(@__DIR__)))
        Revise.add_callback(files) do
            __clear_cache!()
        end
    end

    foreach(f->f(), __init_hooks__)
end
const __init_hooks__ = []

# utilties
# --------

"""
    @invoke f(arg::T, ...; kwargs...)

Provides a convenient way to call [`invoke`](@ref);
`@invoke f(arg1::T1, arg2::T2; kwargs...)` will be expanded into `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`.
When an argument's type annotation is omitted, it's specified as `Any` argument, e.g.
`@invoke f(arg1::T, arg2)` will be expanded into `invoke(f, Tuple{T,Any}, arg1, arg2)`.

This could be used to call down to `NativeInterpreter`'s abstract interpretation method
while passing `XInterpreter` so that subsequent calls of abstract interpretation methods
are dispatched to those overloaded against `XInterpreter`.

E.g. call down to `abstract_call_gf_by_type(::NativeInterpreter, ...)` within `abstract_call_gf_by_type(::XInterpreter, ...)`:
```julia
function Core.Compiler.abstract_call_gf_by_type(interp::XInterpreter, ...)
    ...
    @invoke Core.Compiler.abstract_call_gf_by_type(interp::AbstractInterpreter, ...) # within this call, methods overloaded on `XInterpreter` will still be dispatched.
    ...
end
```
"""
macro invoke(ex)
    f, args, kwargs = destructure_callex(ex)
    arg2typs = map(args) do x
        if isexpr(x, :macrocall) && first(x.args) === Symbol("@nospecialize")
            x = last(x.args)
        end
        isexpr(x, :(::)) ? (x.args...,) : (x, GlobalRef(Core, :Any))
    end
    args, argtypes = first.(arg2typs), last.(arg2typs)
    return esc(:($(GlobalRef(Core, :invoke))($(f), Tuple{$(argtypes...)}, $(args...); $(kwargs...))))
end

# for inspection
macro lwr(ex) QuoteNode(lower(__module__, ex)) end
macro src(ex) QuoteNode(first(lower(__module__, ex).args)) end

# includes
# --------

include("types.jl")
include("inferencestate.jl")
include("tfuncs.jl")
include("abstractinterpretation.jl")
include("optimize.jl")
include("typeinfer.jl")
include("xcache.jl")

# entry
# -----

struct XResult
    interp::XInterpreter
    frame::InferenceState
end
function Base.show(io::IO, res::XResult)
    (; interp, frame) = res
    print(io, "XResult(XInterpreter($(interp.cache_key)), InferenceFrame($(frame.result)))")
end

macro enter_call(ex0...)
    return InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :enter_call, ex0)
end

function enter_call(@nospecialize(f), @nospecialize(types=Tuple{});
                    kwargs...)
    ft = Typeof(f)
    tt = if isa(types, Type)
        u = unwrap_unionall(types)
        rewrap_unionall(Tuple{ft, u.parameters...}, types)
    else
        Tuple{ft, types...}
    end
    interp = XInterpreter(; kwargs...)
    return enter_gf_by_type!(interp, tt)
end

# TODO `enter_call_builtin!` ?
function enter_gf_by_type!(interp::XInterpreter,
                           @nospecialize(tt::Type{<:Tuple}),
                           world::UInt = get_world_counter(interp),
                           )
    mms = _methods_by_ftype(tt, InferenceParams(interp).MAX_METHODS, world)
    @assert mms !== false "unable to find matching method for $(tt)"

    filter!(mm->mm.spec_types===tt, mms)
    @assert length(mms) == 1 "unable to find single target method for $(tt)"

    mm = first(mms)::MethodMatch

    return enter_method_signature!(interp, mm.method, mm.spec_types, mm.sparams)
end

function enter_method_signature!(interp::XInterpreter,
                                 m::Method,
                                 @nospecialize(atype),
                                 sparams::SimpleVector,
                                 world::UInt = get_world_counter(interp),
                                 )
    mi = specialize_method(m, atype, sparams)

    result = InferenceResult(mi)

    frame = InferenceState(result, #= cached =# true, interp)

    typeinf(interp, frame)

    return XResult(interp, frame)
end

function enter_method!(interp::XInterpreter,
                       m::Method,
                       world::UInt = get_world_counter(interp),
                       )
    return enter_method_signature!(interp, m, m.sig, sparams_from_method_signature(m), world)
end

function sparams_from_method_signature(m)
    s = TypeVar[]
    sig = m.sig
    while isa(sig, UnionAll)
        push!(s, sig.var)
        sig = sig.body
    end
    return svec(s...)
end

# exports

export
    @enter_call,
    enter_call

end # module XCompiler


text:
module API

using Stipple, StippleUI, Colors
import Genie.Renderer.Html: HTMLString, normal_element

export attributes, quasar, vue, quasar_pure, vue_pure, xelem, xelem_pure, csscolors

const ATTRIBUTES_MAPPINGS = Dict{String,String}(
  "autoclose" => "auto-close",
  "bgcolor" => "bg-color",
  "bottomslots" => "bottom-slots",
  "checkedicon" => "checked-icon",
  "clearicon" => "clear-icon",
  "contentclass" => "content-class",
  "contentstyle" => "content-style",
  "contextmenu" => "context-menu",
  "darkpercentage" => "dark-percentage",
  "defaultyearmonth" => "default-year-month",
  "defaultview" => "default-view",
  "definitions" => ":definitions",
  "dropnative" => "@drop.native",
  "dragonlyrange" => "drag-only-range",
  "dragrange" => "drag-range",
  "emitimmd" => "emit-immediately",
  "errormessage" => "error-message",
  "eventcolor" => "event-color",
  "falsevalue" => "false-value",
  "fieldname" => "v-model",
  "fillmask" => "fill-mask",
  "firstdayofweek" => "first-day-of-week",
  "fullheight" => "full-height",
  "fullwidth" => "full-width",
  "hidebottom" => "hide-bottom",
  "hidebottomspace" => "hide-bottom-space",
  "hideheader" => "hide-header",
  "hidehint" => "hide-hint",
  "iconcolor" => "icon-color",
  "iconremove" => "icon-remove",
  "iconright" => "icon-right",
  "indeterminateicon" => "indeterminate-icon",
  "indeterminatevalue" => "indeterminate-value",
  "inputclass" => "input-class",
  "inputstyle" => "input-style",
  "insetlevel" => "inset-level",
  "itemaligned" => "item-aligned",
  "keepcolor" => "keep-color",
  "labelalways" => "label-always",
  "labelcolor" => "label-color",
  "labelslot" => "label-slot",
  "labelvalue" => "label-value",
  "labelvalueleft" => "left-label-value",
  "labelvalueright" => "right-label-value",
  "lazyrules" => "lazy-rules",
  "leftlabel" => "left-label",
  "manualfocus" => "manual-focus",
  "minheight" => "min-height",
  "maxheight" => "max-height",
  "multiline" => "multi-line",
  "navmaxyearmonth" => "navigation-max-year-month",
  "navminyearmonth" => "navigation-min-year-month",
  "nobackdrop" => "no-backdrop-dismiss",
  "nocaps" => "no-caps",
  "noerrorfocus" => "no-error-focus",
  "noerroricon" => "no-error-icon",
  "noesc" => "no-esc-dismiss",
  "nofocus" => "no-focus",
  "noparentevent" => "no-parent-event",
  "norefocus" => "no-refocus",
  "noresetfocus" => "no-reset-focus",
  "nowrap" => "no-wrap",
  "nounset" => "no-unset",
  "pagination" => ":pagination.sync",
  "reactiverules" => "reactive-rules",
  "reversefillmask" => "reverse-fill-mask",
  "rules" => ":rules",
  "selected" => ":selected.sync",
  "shadowtext" => "shadow-text",
  "showifabove" => "show-if-above",
  "stacked" => "stack",
  "stacklabel" => "stack-label",
  "textcolor" => "text-color",
  "todaybtn" => "today-btn",
  "toggleindeterminate" => "toggle-indeterminate",
  "toggleorder" => "toggle-order",
  "toolbar" => ":toolbar",
  "toolbartextcolor" => "toolbar-text-color",
  "toolbartogglecolor" => "toolbar-toggle-color",
  "toolbarbg" => "toolbar-bg",
  "pastenative" => "@paste.native",
  "transitionhide" => "transition-hide",
  "transitionshow" => "transition-show",
  "truevalue" => "true-value",
  "uncheckedicon" => "unchecked-icon",
  "unmaskedvalue" => "unmasked-value",
  "vripple" => "v-ripple",
  "yearsinmonthview" => "years-in-month-view"
);

function attributes(kwargs::Vector{X},
                    mappings::Dict{String,String} = Dict{String,String}())::NamedTuple where {X}

  attrs = Dict()
  mapped = false

  for (k,v) in Dict(kwargs)
    v === nothing && continue
    mapped = false

    if haskey(mappings, string(k))
      k = mappings[string(k)]
    end

    attr_key = string((isa(v, Symbol) && ! startswith(string(k), ":") &&
      ! ( startswith(string(k), "v-") || startswith(string(k), "v" * Genie.config.html_parser_char_dash) ) ? ":" : ""), "$k") |> Symbol
    attr_val = isa(v, Symbol) && ! startswith(string(k), ":") ? Stipple.julia_to_vue(v) : v

    attrs[attr_key] = attr_val
  end

  NamedTuple(attrs)
end

function q__elem(f::Function, elem::Symbol, args...; attrs...) :: HTMLString
  normal_element(f, string(elem), [args...], Pair{Symbol,Any}[attrs...])
end

function q__elem(elem::Symbol, children::Union{String,Vector{String}} = "", args...; attrs...) :: HTMLString
  normal_element(children, string(elem), [args...], Pair{Symbol,Any}[attrs...])
end

function q__elem(elem::Symbol, children::Any, args...; attrs...) :: HTMLString
  normal_element(string(children), string(elem), [args...], Pair{Symbol,Any}[attrs...])
end

function xelem(elem::Symbol, args...;
              wrap::Function = StippleUI.DEFAULT_WRAPPER,
              attributesmappings::Dict{String, String} = Dict{String, String}(),
              mergemappings = true,
              kwargs...)
  wrap() do
    q__elem(elem, args...; attributes([kwargs...], mergemappings ? merge(ATTRIBUTES_MAPPINGS, attributesmappings) : attributesmappings)...)
  end
end

function quasar(elem::Symbol, args...;
               wrap::Function = StippleUI.DEFAULT_WRAPPER,
               kwargs...)
  xelem(elem, :q, args...; wrap, kwargs...)
end

function vue(elem::Symbol, args...;
            wrap::Function = StippleUI.DEFAULT_WRAPPER,
            kwargs...)
  xelem(elem, :vue, args...; wrap, kwargs...)
end

xelem(elem::Symbol, prefix::Symbol, args...; kwargs...) = xelem(Symbol("$prefix-$elem"), args...; kwargs...)
xelem_pure(elem::Symbol, args...; kwargs...) = xelem(elem, args...; wrap = StippleUI.NO_WRAPPER, kwargs...)
quasar_pure(elem::Symbol, args...; kwargs...) = quasar(elem, args...; wrap = StippleUI.NO_WRAPPER, kwargs...)
vue_pure(elem::Symbol, args...; kwargs...) = vue(elem, args...; wrap = StippleUI.NO_WRAPPER, kwargs...)

"""
    `csscolors(name, color)`
    `csscolors(names, colors)`
    `csscolors(prefix, colors)`

Construct a css string that defines colors to be used for styling quasar elements.
# Usage
css = styles(csscolors(:stipple, [RGB(0.2, 0.4, 0.8), "#123456", RGBA(0.1, 0.2, 0.3, 0.5)]))

ui() = css * dashboard(vm(model), class="container", [
  btn("Hit me", @click(:pressed), color="stipple-3")
])
"""
function csscolors(name, color::AbstractString)
  ".text-$name { color: $color }\n.bg-$name   { background: $color !important}"
end

csscolors(name, color::Colorant) = csscolors(name, "#" * hex(color, :auto))

csscolors(names::Vector, colors::Vector) = join([csscolors(n, c) for (n, c) in zip(names, colors)], "\n")

csscolors(prefix::Union{Symbol, AbstractString}, colors::Vector) = csscolors("$prefix-" .* string.(1:length(colors)), colors)

function __init__() :: Nothing
  Stipple.rendering_mappings(ATTRIBUTES_MAPPINGS)

  nothing
end

end


text:
# Julia wrapper for header: /usr/local/include/libavfilter/buffersrc.h
# Automatically generated using Clang.jl wrap_c, version 0.0.0

export
    av_buffersrc_get_nb_failed_requests,
    av_buffersrc_write_frame,
    av_buffersrc_add_frame,
    av_buffersrc_add_frame_flags

function av_buffersrc_get_nb_failed_requests(buffer_src)
    ccall((:av_buffersrc_get_nb_failed_requests,libavfilter),UInt32,(Ptr{AVFilterContext},),buffer_src)
end

function av_buffersrc_write_frame(ctx,frame)
    ccall((:av_buffersrc_write_frame,libavfilter),Cint,(Ptr{AVFilterContext},Ptr{AVFrame}),ctx,frame)
end

function av_buffersrc_add_frame(ctx,frame)
    ccall((:av_buffersrc_add_frame,libavfilter),Cint,(Ptr{AVFilterContext},Ptr{AVFrame}),ctx,frame)
end

function av_buffersrc_add_frame_flags(buffer_src,frame,flags::Integer)
    ccall((:av_buffersrc_add_frame_flags,libavfilter),Cint,(Ptr{AVFilterContext},Ptr{AVFrame},Cint),buffer_src,frame,flags)
end

max_stars_repo_name: asafasban/AAVideoIO

max_issues_repo_name: asafasban/AAVideoIO

max_forks_repo_name: asafasban/AAVideoIO


text:
mutable struct Agent <: AbstractAgent
    v_::Float64
    ω_::Float64
    function Agent(v::Float64, ω::Float64)
        new(v, ω)
    end
end

function decision(agent::Agent, observation::Nothing)
    return agent.v_, agent.ω_
end

function decision(agent::Agent, observation::Vector{Vector{Float64}})
    return agent.v_, agent.ω_
end

function draw(agent::Agent, p::Plot{T}) where {T} end

mutable struct EstimatorAgent <: AbstractAgent
    v_::Float64
    ω_::Float64
    dt::Float64
    prev_v_::Float64
    prev_ω_::Float64
    estimator_::AbstractEstimator
    function EstimatorAgent(
        v::Float64,
        ω::Float64,
        dt::Float64,
        estimator::AbstractEstimator,
    )
        new(v, ω, dt, 0.0, 0.0, estimator)
    end
end

function decision(agent::EstimatorAgent, observation::Nothing)
    estimator = agent.estimator_
    motion_update(estimator, agent.prev_v_, agent.prev_ω_, agent.dt)
    agent.prev_v_, agent.prev_ω_ = agent.v_, agent.ω_
    return agent.v_, agent.ω_
end

function decision(
    agent::EstimatorAgent,
    observation::Vector{Vector{Float64}},
    envmap::Map;
    kwargs...,
)
    estimator = agent.estimator_

    motion_update(
        estimator,
        agent.prev_v_,
        agent.prev_ω_,
        agent.dt;
        kwargs...,
        observation = observation,
    )

    agent.prev_v_, agent.prev_ω_ = agent.v_, agent.ω_

    observation_update(agent.estimator_, observation, envmap; kwargs...)

    return agent.v_, agent.ω_
end

function draw(agent::EstimatorAgent, p::Plot{T}) where {T}
    draw(agent.estimator_, p)
end

mutable struct LoggerAgent <: AbstractAgent
    v_::Float64
    ω_::Float64
    dt::Float64
    pose_::Vector{Float64}
    step_::Int64
    log_::IOStream
    function LoggerAgent(
        v::Float64,
        ω::Float64,
        dt::Float64,
        init_pose::Vector{Float64},
        fname = "log.txt",
    )
        fd = open(fname, "w")
        write(fd, "delta $(dt)\n")
        new(v, ω, dt, copy(init_pose), 0, fd)
    end
end

function decision(agent::LoggerAgent, observation::Vector{Vector{Float64}})
    write(agent.log_, "u $(agent.step_) $(agent.v_) $(agent.ω_)\n")
    pose = agent.pose_
    write(agent.log_, "x $(agent.step_) $(pose[1]) $(pose[2]) $(pose[3])\n")
    N = size(observation)[1]
    for i = 1:N
        obsv = observation[i]
        # obsv = [idx, d, ϕ, ψ]
        body = "z $(agent.step_) $(convert(Int64, obsv[4])) $(obsv[1]) $(obsv[2]) $(obsv[3])\n"
        write(agent.log_, body)
    end

    agent.step_ += 1
    flush(agent.log_)
    agent.pose_ = state_transition(agent.pose_, agent.v_, agent.ω_, agent.dt)
    return agent.v_, agent.ω_
end

function draw(agent::LoggerAgent, p::Plot{T}) where {T} end

max_stars_repo_name: soblin/JuliaProbo

max_issues_repo_name: soblin/JuliaProbo

max_forks_repo_name: soblin/JuliaProbo


text:
#########################################################################
#
#    Graph simplification
#      Walks the tree to
#           - fuse duplicates
#           - apply simplification rules
#
#########################################################################

function simplify!(g::ExGraph, emod = Main)

	i = 1
	evalsort!(g)
	markalloc!(g)
	while i <= length(g.nodes)
		restart = false
		n = g.nodes[i]

		for n2 in g.nodes[i+1:end]
			restart = identical(n, n2, g)
			restart && break
		end

		restart = restart || evalmoduleref(n, g, emod) ||
			evalconstants(n, g, emod) ||
			rule1(n, g) ||
			rule2(n, g) ||
			rule3(n, g) ||
			rule4(n, g) ||
			rule5(n, g) ||
			rule6(n, g) ||
			rule7(n, g) ||
			rule8(n, g) ||
			rule9(n, g) ||
			rule10(n, g) ||
			rule11(n, g)

		if restart
			markalloc!(g)
			i = 1
		else
			i += 1
		end
	end

	# separate pass on subgraphs
	for n in filter(n->isa(n, NFor), g.nodes)
		simplify!(n.main[2], emod)
	end

	g
end

# function fuseidentical!(g::ExGraph)
# 	ns = filter(n -> !n.alloc, g.nodes)
# 	nl = collect( zip(ns, map(n -> (n.main, vcat(n.parents, n.precedence)), ns)) )
#     sort!(nl, lt= (a,b) -> length(a[2]) < length(b[2]))
#     sort!(ns, lt= (a,b) -> b[1] in a[2] & !(a[1] in b[2]))
# 	for n in g.nodes
# 		any(n2 -> identical(n, n2, g), g.nodes[i+1:end])
# 	end
# end

## mark nodes that can't be fused because they are modified by a setindex/setfield or a for loop
function markalloc!(g::ExGraph)
	for n in g.nodes
		if isa(n, Union{NSRef, NSDot})
			n.parents[1].alloc = true

		elseif isa(n, NFor)
			g2 = n.main[2]  # subgraph
			for (n2, s2) in g2.exto
				hassym(g2.seto, s2) || continue
				n2.alloc = true
			end
		else
			n.alloc = false
		end
	end
end

# TODO : propagate to grand-parent graph etc...
function constequiv(n, g)
	isa(n, NConst)          && return n.main
	!isa(n, NExt)           && return nothing

	!hasnode(g.exti, n)     && return nothing
	sym = g.exti.kv[n]
	!hassym(g.exto, sym)    && return nothing
	 hassym(g.seti, sym)    && return nothing

	pn = getnode(g.exto, sym)
	!isa(pn, NConst)  && return nothing
	pn.main
end

## fusion of identical nodes
function identical(n,n2,g)
	!is(n.main, n2.main)    && return false
	n.parents != n2.parents && return false
	n.alloc	                && return false
	n2.alloc	            && return false

	fusenodes(g, n, n2)
	true
end

### calculate constant nodes
#  only if they reduce to a real (zeros(..), etc. should not be converted)
function evalconstants(n, g, emod)
	!isa(n, NCall)                       && return false
	vals = similar(n.parents, Any)
	for (i,p) in enumerate(n.parents)
		vals[i] = constequiv(p,g)
		vals[i] == nothing && return false
	end

	# calculate value
	res = 0.
	try
		res = vals[1](vals[2:end]...)
	catch e
		println("error $e while evaluating $(vals[1])($(vals[2:end]...))")
		rethrow(e)
	end

	!isa(res, Real) && return false

	# create a new constant node and replace n with it
	nn = addnode!(g, NConst(res) )
	fusenodes(g, nn, n)
	true
end

### calculate Module.xxx constants nodes
function evalmoduleref(n, g, emod)
	!isa(n, NDot)                    && return false
	!isa(n.parents[1], NConst)       && return false
	!isa(n.parents[1].main, Module)  && return false

	# calculate value
	res = 0.
	ex  = :()
	try
		ex = Expr(:., n.parents[1].main, n.main )
		res = emod.eval( ex )
	catch e
		println("error $e \n while evaluating $ex")
		rethrow(e)
	end

	nn = addnode!(g, NConst( res ) )
	fusenodes(g, nn, n)
	true
end

## right neutral element
function rule1(n, g)
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops
	val = constequiv(n.parents[3], g)
	(val == nothing)           && return false

	op = n.parents[1].main
	if val == 0 && in(op, [+, -, .+, .-])
		fusenodes(g, n.parents[2], n)
		return true

	elseif val == 1 && in(op, [*, /, ^, .*, ./, .^])
		fusenodes(g, n.parents[2], n)
		return true

	else
		return false
	end
end

## right zero element
# FIXME : incorrect if left term is an array ?
function rule2(n, g)
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops
	val = constequiv(n.parents[3], g)
	(val == nothing)           && return false

	op = n.parents[1].main
	if val == 0 && in(op, [*, .*])
		nn = addnode!(g, NConst(0.0) )
		fusenodes(g, nn, n)
		return true

	else
		return false
	end
end

## left neutral element
function rule3(n, g)
	# n.alloc	                   && return false
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops
	val = constequiv(n.parents[2], g)
	(val == nothing)           && return false

	op = n.parents[1].main
	if val == 0 && in(op, [+, .+])
		fusenodes(g, n.parents[3], n)
		return true

	elseif val == 1 && in(op, [*, .*])
		fusenodes(g, n.parents[3], n)
		return true

	else
		return false
	end
end

## left zero element
# FIXME : incorrect if right term is an array ?
function rule4(n, g)
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops
	val = constequiv(n.parents[2], g)
	(val == nothing)           && return false

	op = n.parents[1].main
	if val == 0 && in(op, [*, /, ^, .*, ./, .^])
		nn = addnode!(g, NConst(0.0) )
		fusenodes(g, nn, n)
		return true

	else
		return false
	end
end

## setindex on same getindex
function rule5(n, g)
	!isa(n, NSRef)                              && return false
	!isa(n.parents[2], NRef)                    && return false
	n2 = n.parents[2]
	(n.parents[1] != n2.parents[1])             && return false
	# check that indexing is the same
	(length(n.parents)-1 != length(n2.parents))   && return false
	!all( n.parents[3:end] .== n2.parents[2:end]) && return false

	fusenodes(g, n.parents[1], n)
	true
end

## setfield on same getfield
function rule6(n, g)
	!isa(n, NSDot)                            && return false
	!isa(n.parents[2], NDot)                  && return false
	(n.main != n.parents[2].main)             && return false
	(n.parents[1] != n.parents[2].parents[1]) && return false

	fusenodes(g, n.parents[1], n)
	true
end

## getindex on zeros()
# FIXME : incorrect if index is a range
function rule7(n, g)
	!isa(n, NRef)                               && return false
	p = n.parents[1]
	!isa(p, NCall)                              && return false
	p.parents[1].main != zeros                  && return false
	# any(x -> !isa(x, NConst), n.parents[2:end]) && return false

	nn = addnode!(g, NConst(0.0) )
	fusenodes(g, nn, n)
	true
end

## change (-1 * x)  to  (-x)
function rule8(n, g)
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops

	op = n.parents[1].main
	!in(op, [*, .*])     && return false
	!isa(n.parents[2], NConst) && return false
	(n.parents[2].main != -1)  && return false

	nm = addnode!(g, NConst(-))
	nn = addnode!(g, NCall(:call, [nm, n.parents[3]]) )
	fusenodes(g, nn, n)
	true
end

## change (x * -1)  to  (-x)
function rule9(n, g)
	!isa(n, NCall)             && return false
	(length(n.parents) != 3)   && return false # restricted to binary ops

	op = n.parents[1].main
	!in(op, [*, .*])           && return false
	!isa(n.parents[3], NConst) && return false
	(n.parents[3].main != -1)  && return false

	nm = addnode!(g, NConst(-))
	nn = addnode!(g, NCall(:call, [nm, n.parents[2]]) )
	fusenodes(g, nn, n)
	true
end

## getindex on fill()
# FIXME : incorrect if index is a range
function rule10(n, g)
	!isa(n, NRef)                               && return false

	p = n.parents[1]
	!isa(p, NCall)                              && return false
	p.parents[1].main != fill                   && return false
	val = constequiv(p.parents[2], g)
	(val == nothing)                            && return false

	# any(x -> !isa(x, NConst), n.parents[3:end]) && return false

	nn = addnode!(g, NConst(val) )
	fusenodes(g, nn, n)
	true
end

## getindex on ones()
# FIXME : incorrect if index is a range
function rule11(n, g)
	!isa(n, NRef)                               && return false
	p = n.parents[1]
	!isa(p, NCall)                              && return false
	p.parents[1].main != ones                   && return false
	# any(x -> !isa(x, NConst), n.parents[2:end]) && return false

	nn = addnode!(g, NConst(1.0) )
	fusenodes(g, nn, n)
	true
end