text:
@testset "Redundancy removal" begin
    @testset "isredundant" begin
        @test isredundant(HyperPlane([0, 1], 0) ∩ HalfSpace([1, 1], 1), [0, 0], nl=0)
        @test !isredundant(HalfSpace([1, 1], 1) ∩ HyperPlane([0, 1], 0) ∩ HalfSpace([-1, -1], 1), [1, 0], nl=0)
        #@test !ishredundant(convexhull(SymPoint([1, 0])), HyperPlane([0, 1], 0), d=1)
        #@test isredundant(convexhull(SymPoint([1, 0])), HalfSpace([0, 1], 1), d=1)
    end
#    @testset "Non-redundant SymPoint" begin
#        vr = convexhull(SymPoint([-1, 0]), SymPoint([0, 1]))
#        hr = HalfSpace([-1, -1], 1) ∩ HalfSpace([1, -1], 1) ∩ HalfSpace([1, 1], 1) ∩ HalfSpace([-1, 1], 1)
#        vr = @inferred Polyhedra.removevredundancy(vr, hr)
#        @test vr isa Polyhedra.PointsHull{2,Int,Vector{Int}}
#        @test collect(sympoints(vr)) == [SymPoint([-1, 0]), SymPoint([0, 1])]
#        @test !haspoints(vr)
#        @test !hasallrays(vr)
#    end
    @testset "Point" begin
        vr = convexhull([-1, 0], [0, -1]) + conichull([1, 1], [-1, 1])
        hr = HalfSpace([-1, -1], 1) ∩ HalfSpace([1, -1], 1)
        vr = Polyhedra.removevredundancy(vr, hr)
        @test collect(points(vr)) == [[0, -1]]
        @test !haslines(vr)
        @test collect(rays(vr)) == [Ray([1, 1]), Ray([-1, 1])]
    end
#    @testset "Split SymPoint" begin
#        vr = convexhull(SymPoint([-1, 0]), SymPoint([0, 1])) + conichull([1, 1], [-1, 1])
#        hr = HalfSpace([-1, -1], 1) ∩ HalfSpace([1, -1], 1)
#        vr = Polyhedra.removevredundancy(vr, hr)
#        @test !hassympoints(vr)
#        @test collect(points(vr)) == [[0, -1]]
#        @test !haslines(vr)
#        @test collect(rays(vr)) == [Ray([1, 1]), Ray([-1, 1])]
#
#        vr = convexhull(SymPoint([1, 0]), SymPoint([0, 1])) + conichull([-1, 1])
#        hr = HalfSpace([-1, -1], 1) ∩ HalfSpace([1, -1], 1) ∩ HalfSpace([1, 1], 1)
#        vr = Polyhedra.removevredundancy(vr, hr)
#        @test !hassympoints(vr)
#        @test collect(points(vr)) == [[1, 0], [0, -1]]
#        @test !haslines(vr)
#        @test collect(rays(vr)) == [Ray([-1, 1])]
#    end
end
@testset "Duplicate removal" begin
    h = removeduplicates(hrep([1 1; -1 -1; 1 0; 0 -1], [1, -1, 1, 0]))
    @test nhyperplanes(h) == 1
    @test first(hyperplanes(h)) == HyperPlane([1, 1], 1)
    @test first(hyperplanes(h)) == HyperPlane([-1, -1], -1)
    @test nhalfspaces(h) == 1
    h = removeduplicates(hrep([1 1; -1 -1], [2, -1]))
    @test !hashyperplanes(h)
    @test nhalfspaces(h) == 2
#    v = removeduplicates(convexhull([1, 2], SymPoint([1, 2]), [-1, 1]))
#    @test typeof(v) == Polyhedra.PointsHull{2,Int,Vector{Int}}
#    @test collect(sympoints(v)) == [SymPoint([1, 2])]
#    @test collect(points(v)) == [[-1, 1]]
#    @test !hasallrays(v)
    for v in (#removeduplicates(convexhull([1, 2], [2, 1], SymPoint([1, 2]), [-1, 1]) + Line([0, 1])),
              #removeduplicates(Line([0, 1]) + convexhull([1, 2], [2, 1], SymPoint([1, 2]), [-1, 1])),
              removeduplicates(Line([0, 1]) + convexhull([-1, -2], [2, 1], [1, 2], [-1, 1], [-1, -2])),)
        @test typeof(v) == Polyhedra.Hull{Int, Vector{Int}, Int}
        #@test collect(sympoints(v)) == [SymPoint([1, 2])]
        @test collect(points(v)) == [[-1, -2], [2, 1], [1, 2]]
        @test !hasrays(v)
        @test collect(lines(v)) == [Line([0, 1])]
    end
end


text:
"""
    OffsetInteger{O, T}

OffsetInteger type mainly for indexing.
* `O` - The offset relative to Julia arrays. This helps reduce copying when
communicating with 0-indexed systems such as OpenGL.
"""
struct OffsetInteger{O,T<:Integer} <: Integer
    i::T
    OffsetInteger{O,T}(x::Integer) where {O,T<:Integer} = new{O,T}(T(x + O))
end

const ZeroIndex{T<:Integer} = OffsetInteger{-1,T}
const OneIndex{T<:Integer} = OffsetInteger{0,T}
const GLIndex = ZeroIndex{Cuint}

raw(x::OffsetInteger) = x.i
raw(x::Integer) = x
value(x::OffsetInteger{O,T}) where {O,T} = raw(x) - O
value(x::Integer) = x

function show(io::IO, oi::OffsetInteger)
    return print(io, "|$(raw(oi)) (indexes as $(value(oi))|")
end

Base.eltype(::Type{OffsetInteger{O,T}}) where {O,T} = T
Base.eltype(oi::OffsetInteger) = eltype(typeof(oi))

# constructors and conversion
function OffsetInteger{O1,T1}(x::OffsetInteger{O2,T2}) where {O1,O2,T1<:Integer,T2<:Integer}
    return OffsetInteger{O1,T1}(convert(T2, x))
end

OffsetInteger{O}(x::Integer) where {O} = OffsetInteger{O,eltype(x)}(x)
OffsetInteger{O}(x::OffsetInteger) where {O} = OffsetInteger{O,eltype(x)}(x)
# This constructor has a massive method invalidation as a consequence,
# and doesn't seem to be needed, so let's remove it!

Base.convert(::Type{IT}, x::OffsetInteger) where {IT<:Integer} = IT(value(x))

Base.promote_rule(::Type{IT}, ::Type{<:OffsetInteger}) where {IT<:Integer} = IT

function Base.promote_rule(::Type{OffsetInteger{O1,T1}},
                           ::Type{OffsetInteger{O2,T2}}) where {O1,O2,T1<:Integer,
                                                                T2<:Integer}
    return OffsetInteger{pure_max(O1, O2),promote_type(T1, T2)}
end

Base.@pure pure_max(x1, x2) = x1 > x2 ? x1 : x2

# Need to convert to Int here because of: https://github.com/JuliaLang/julia/issues/35038
Base.to_index(I::OffsetInteger) = convert(Int, raw(OneIndex(I)))
Base.to_index(I::OffsetInteger{0}) = convert(Int, raw(I))

# basic operators
for op in (:(-), :abs)
    @eval Base.$op(x::T) where {T<:OffsetInteger} = T($(op)(value(x)))
end

for op in (:(+), :(-), :(*), :(/), :div)
    @eval begin
        @inline function Base.$op(x::OffsetInteger{O}, y::OffsetInteger{O}) where {O}
            return OffsetInteger{O}($op(value(x), value(y)))
        end
    end
end

for op in (:(==), :(>=), :(<=), :(<), :(>), :sub_with_overflow)
    @eval begin
        @inline function Base.$op(x::OffsetInteger{O}, y::OffsetInteger{O}) where {O}
            return $op(x.i, y.i)
        end
    end
end

ext: jl


text:
# ---
# title: 1365. How Many Numbers Are Smaller Than the Current Number
# id: problem1365
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array, Hash Table
# link: <https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/description/>
# hidden: true
# ---
#
# Given the array `nums`, for each `nums[i]` find out how many numbers in the
# array are smaller than it. That is, for each `nums[i]` you have to count the
# number of valid `j's` such that `j != i` **and** `nums[j] < nums[i]`.
#
# Return the answer in an array.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [8,1,2,2,3]
#     Output: [4,0,1,1,3]
#     Explanation:
#     For nums[0]=8 there exist four smaller numbers than it (1, 2, 2 and 3).
#     For nums[1]=1 does not exist any smaller number than it.
#     For nums[2]=2 there exist one smaller number than it (1).
#     For nums[3]=2 there exist one smaller number than it (1).
#     For nums[4]=3 there exist three smaller numbers than it (1, 2 and 2).
#
#
# **Example 2:**
#
#
#
#     Input: nums = [6,5,4,8]
#     Output: [2,1,0,3]
#
#
# **Example 3:**
#
#
#
#     Input: nums = [7,7,7,7]
#     Output: [0,0,0,0]
#
#
#
#
# **Constraints:**
#
#   * `2 <= nums.length <= 500`
#   * `0 <= nums[i] <= 100`
#
#
## @lc code=start
using LeetCode

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


text:
"""
    load_gmsh_file(filename::AbstractString) -> NamedTuple

Loads `filename` into separete arrays according to the gmsh file format
style in ASCII mode.

# Arguments
- `filename::AbstractString`: name of the file to be loaded,

# Keywords

# Returns
- `NamedTuple`: gmsh file is separated into the arrays of substring:
    - msh_version - constains infomartion about gmsh file version,
    - msh_PhysicalNames - constains data about physical objects,
    - msh_Entities - constains data about entities,
    - msh_Nodes - constains data about nodes including coordinates,
    - msh_Elements - constains data about elements,

# Throws
- `Error`: file with name `filename` does not exists,
- `Error`: file with name `filename` is saved in binary mode.
"""
function load_gmsh_file(filename::AbstractString)
    if !isfile(filename)
        error("File $(filename) does not exist.")
    end
    msh = load_file_line_by_line(filename)

    iMeshFormat = findfirst(x -> x == "\$MeshFormat", msh)
    iEndMeshFormat = findfirst(x -> x == "\$EndMeshFormat", msh)
    raw_MeshFormat = msh[iMeshFormat + 1]
    msh_version, msh_filetype, msh_datasize = split(raw_MeshFormat)
    if msh_filetype == "1"
        error("Gmsh mesh file format saved in binary mode. Please convert to ASCII mode.")
    end

    iPhysicalNames = findfirst(x -> x == "\$PhysicalNames", msh)
    iEndPhysicalNames = findfirst(x -> x == "\$EndPhysicalNames", msh)
    raw_PhysicalNames = SubString{String}[]
    if iPhysicalNames != nothing
        raw_PhysicalNames = msh[(iPhysicalNames + 1):(iEndPhysicalNames - 1)]
        raw_PhysicalNames = [replace(v, "\"" => "") for v in raw_PhysicalNames]
    end

    iEntities = findfirst(x -> x == "\$Entities", msh)
    iEndEntities = findfirst(x -> x == "\$EndEntities", msh)
    raw_Entities = msh[(iEntities + 1):(iEndEntities - 1)]

    iNodes = findfirst(x -> x == "\$Nodes", msh)
    iEndNodes = findfirst(x -> x == "\$EndNodes", msh)
    raw_Nodes = msh[(iNodes + 1):(iEndNodes - 1)]

    iElements = findfirst(x -> x == "\$Elements", msh)
    iEndElements = findfirst(x -> x == "\$EndElements", msh)
    raw_Elements = msh[(iElements + 1):(iEndElements - 1)]

    return (
        msh_version=msh_version,
        raw_PhysicalNames=raw_PhysicalNames,
        raw_Entities=raw_Entities,
        raw_Nodes=raw_Nodes,
        raw_Elements=raw_Elements,
    )
end

"""
    gmsh_do_physicalnames(raw_PhysicalNames) -> NamedTuple

Split `raw_PhysicalNames` created by [`load_gmsh_file`](@ref) in to one `Dict` for each
dimension. No spaces in names allowed.

# Arguments
- `raw_PhysicalNames`: data about physical names in gmsh file,

# Keywords

# Returns
- `NamedTuple`: one `Dict{Int,String}` for each dimension, `physicalTag => Name`:
    - physicalTag0DtoName,
    - physicalTag1DtoName,
    - physicalTag2DtoName,
    - physicalTag3DtoName.

# Throws
"""
function gmsh_do_physicalnames(raw_PhysicalNames)
    n = parse(Int, raw_PhysicalNames[1])

    physicalTag0DtoName = Dict{Int,String}()
    physicalTag1DtoName = Dict{Int,String}()
    physicalTag2DtoName = Dict{Int,String}()
    physicalTag3DtoName = Dict{Int,String}()
    for i in 2:length(raw_PhysicalNames)
        s = split(raw_PhysicalNames[i])
        if s[1] == "0"
            physicalTag0DtoName[parse(Int, s[2])] = s[3]
        elseif s[1] == "1"
            physicalTag1DtoName[parse(Int, s[2])] = s[3]
        elseif s[1] == "2"
            physicalTag2DtoName[parse(Int, s[2])] = s[3]
        elseif s[1] == "3"
            physicalTag3DtoName[parse(Int, s[2])] = s[3]
        end
    end

    return (
        physicalTag0DtoName=physicalTag0DtoName,
        physicalTag1DtoName=physicalTag1DtoName,
        physicalTag2DtoName=physicalTag2DtoName,
        physicalTag3DtoName=physicalTag3DtoName,
    )
end

"""
    gmsh_do_entities(raw_Entities) -> NamedTuple

Split `raw_Entities` created by [`load_gmsh_file`](@ref) in to one `Dict` for each type of
entity. Operates with only one physicalTag per entity.

# Arguments
- `raw_Entities`: data about entities in gmsh file,

# Keywords

# Returns
- `NamedTuple`: one `Dict{Int,String}` for each dimension, `entityTag => physicalTag`:
    - pointTagtoPhysicalTag,
    - curveTagtoPhysicalTag,
    - surfaceTagtoPhysicalTag,
    - volumeTagtoPhysicalTag.

# Throws
"""
function gmsh_do_entities(raw_Entities)
    numPoints,
        numCurves,
        numSurfaces,
        numVolumes = (parse(Int, v) for v in split(raw_Entities[1]))

    pointTagtoPhysicalTag = Dict{Int,Int}()
    curveTagtoPhysicalTag = Dict{Int,Int}()
    surfaceTagtoPhysicalTag = Dict{Int,Int}()
    volumeTagtoPhysicalTag = Dict{Int,Int}()

    if numPoints != 0
        for i in 2:(1 + numPoints)
            s = split(raw_Entities[i])
            if parse(Int, s[5]) != 0
                pointTagtoPhysicalTag[parse(Int, s[1])] = parse(Int, s[6])
            end
        end
    end

    if numCurves != 0
        for i in (2 + numPoints):(1 + numPoints + numCurves)
            s = split(raw_Entities[i])
            if parse(Int, s[8]) != 0
                curveTagtoPhysicalTag[parse(Int, s[1])] = parse(Int, s[9])
            end
        end
    end

    if numSurfaces != 0
        for i in (2 + numPoints + numCurves):(1 + numPoints + numCurves + numSurfaces)
            s = split(raw_Entities[i])
            if parse(Int, s[8]) != 0
                surfaceTagtoPhysicalTag[parse(Int, s[1])] = parse(Int, s[9])
            end
        end
    end

    if numVolumes != 0
        i_start = (2 + numPoints + numCurves + numSurfaces)
        i_end = (1 + numPoints + numCurves + numSurfaces + numVolumes)
        for i in i_start:i_end
            s = split(raw_Entities[i])
            if parse(Int, s[8]) != 0
                volumeTagtoPhysicalTag[parse(Int, s[1])] = parse(Int, s[9])
            end
        end
    end

    return (
        pointTagtoPhysicalTag=pointTagtoPhysicalTag,
        curveTagtoPhysicalTag=curveTagtoPhysicalTag,
        surfaceTagtoPhysicalTag=surfaceTagtoPhysicalTag,
        volumeTagtoPhysicalTag=volumeTagtoPhysicalTag,
    )
end

"""
    gmsh_do_nodes(raw_Nodes) -> NamedTuple

Split `raw_Nodes` created by [`load_gmsh_file`](@ref). Operates with only non parametric
curves.

# Arguments
- `raw_Nodes`: data about nodes in gmsh file,

# Keywords

# Returns
- `NamedTuple`: one `Dict{Int,String}` for each dimension, `entityTag => physicalTag`:
    - nodeTag::Array{Int64,1},
    - vx::Array{Float64,1},
    - vy::Array{Float64,1},
    - vz::Array{Float64,1}.

# Throws
"""
function gmsh_do_nodes(raw_Nodes)
    numEntityBlocks,
        numNodes,
        minNodeTag,
        maxNodeTag = (parse(Int, v) for v in split(raw_Nodes[1]))

    nodeTag = Int[]
    sizehint!(nodeTag, length(raw_Nodes))
    vx = Float64[]
    sizehint!(vx, length(raw_Nodes))
    vy = Float64[]
    sizehint!(vy, length(raw_Nodes))
    vz = Float64[]
    sizehint!(vz, length(raw_Nodes))

    i = 2
    while i < length(raw_Nodes)
        entityDim,
            entityTag,
            parametric,
            numNodesInBlock = (parse(Int, v) for v in split(raw_Nodes[i]))
        i += 1

        for j in i:(i + numNodesInBlock - 1)
            push!(nodeTag, parse(Int, raw_Nodes[j]))
        end
        i += numNodesInBlock
        for j in i:(i + numNodesInBlock - 1)
            x, y, z = (parse(Float64, v) for v in split(raw_Nodes[j]))
            push!(vx, x)
            push!(vy, y)
            push!(vz, z)
        end
        i += numNodesInBlock
    end

    return (nodeTag=nodeTag, vx=vx, vy=vy, vz=vz)
end

"""
    gmsh_do_elements(raw_Elements) -> NamedTuple

Split `raw_Elements` created by [`load_gmsh_file`](@ref).

# Arguments
- `raw_Elements`: data about elements in gmsh file,

# Keywords

# Returns
- `NamedTuple`: one `Dict{Int,String}` for each dimension, `entityTag => physicalTag`:
    - entityTag1D   :: Array{Int64,1},
    - entityTag2D   :: Array{Int64,1},
    - entityTag3D   :: Array{Int64,1},
    - elementType1D :: Array{Int64,1},
    - elementType2D :: Array{Int64,1},
    - elementType3D :: Array{Int64,1},
    - elementTag1D  :: Array{Int64,1},
    - elementTag2D  :: Array{Int64,1},
    - elementTag3D  :: Array{Int64,1},
    - nodeTags1D    :: Array{Array{Int64,1},1},
    - nodeTags2D    :: Array{Array{Int64,1},1},
    - nodeTags3D    :: Array{Array{Int64,1},1}.

# Throws
"""
function gmsh_do_elements(raw_Elements)
    numEntityBlocks,
        numElements,
        minElementTag,
        maxElementTag = (parse(Int, v) for v in split(raw_Elements[1]))

    entityTag1D = Int[]
    entityTag2D = Int[]
    entityTag3D = Int[]

    elementType1D = Int[]
    elementType2D = Int[]
    elementType3D = Int[]

    elementTag1D = Int[]
    elementTag2D = Int[]
    elementTag3D = Int[]

    nodeTags1D = Array{Int,1}[]
    nodeTags2D = Array{Int,1}[]
    nodeTags3D = Array{Int,1}[]

    i = 2
    while i < length(raw_Elements)
        entityDim,
            entityTag,
            elementType,
            numElementsInBlock = (parse(Int, v) for v in split(raw_Elements[i]))
        i += 1

        for j in i:(i + numElementsInBlock - 1)
            if entityDim == 1
                push!(entityTag1D, entityTag)
                push!(elementType1D, elementType)
                s = [parse(Int, v) for v in split(raw_Elements[j])]
                push!(elementTag1D, s[1])
                push!(nodeTags1D, s[2:end])
            elseif entityDim == 2
                push!(entityTag2D, entityTag)
                push!(elementType2D, elementType)
                s = [parse(Int, v) for v in split(raw_Elements[j])]
                push!(elementTag2D, s[1])
                push!(nodeTags2D, s[2:end])
            elseif entityDim == 3
                push!(entityTag3D, entityTag)
                push!(elementType3D, elementType)
                s = [parse(Int, v) for v in split(raw_Elements[j])]
                push!(elementTag3D, s[1])
                push!(nodeTags3D, s[2:end])
            end
        end
        i += numElementsInBlock
    end

    return (
        entityTag1D=entityTag1D,
        entityTag2D=entityTag2D,
        entityTag3D=entityTag3D,
        elementType1D=elementType1D,
        elementType2D=elementType2D,
        elementType3D=elementType3D,
        elementTag1D=elementTag1D,
        elementTag2D=elementTag2D,
        elementTag3D=elementTag3D,
        nodeTags1D=nodeTags1D,
        nodeTags2D=nodeTags2D,
        nodeTags3D=nodeTags3D,
    )
end

"""
    load_gmsh(filename::AbstractString) -> NamedTuple

Loads `filename` into separete `Dict`s according to the gmsh file format
style in ASCII mode.

# Arguments
- `filename::AbstractString`: name of the file to be loaded,

# Keywords

# Returns
- `NamedTuple`: gmsh file is separated into the `Dict`s:
    - physicalTagtoName - see [`gmsh_do_physicalnames`](@ref),
    - tagToPhysicalTag - see [`gmsh_do_entities`](@ref),
    - nodes - see [`gmsh_do_nodes`](@ref),
    - elements - see [`gmsh_do_elements`](@ref).

# Throws
"""
function load_gmsh(filename::AbstractString)
    msh = load_gmsh_file(filename)
    physicalTagtoName = gmsh_do_physicalnames(msh[:raw_PhysicalNames])
    tagToPhysicalTag = gmsh_do_entities(msh[:raw_Entities])
    nodes = gmsh_do_nodes(msh[:raw_Nodes])
    elements = gmsh_do_elements(msh[:raw_Elements])

    return (
        physicalTagtoName=physicalTagtoName,
        tagToPhysicalTag=tagToPhysicalTag,
        nodes=nodes,
        elements=elements,
    )
end

"""
    import_gmsh(filename::AbstractString) -> ir(d,0)

Loads `filename` into incidence [`ir(d,0)`](https://github.com/PetrKryslUCSD/MeshCore.jl).

# Arguments
- `filename::AbstractString`: name of the file to be loaded,

# Keywords

# Returns
- `ir(d,0)`: MeshCore's incidence.

# Throws
"""
function import_gmsh(filename::AbstractString)
    msh = load_gmsh(filename)
    function get_vertices(nodes)
        idxs = [rand(1:size(nodes.vz, 1)) for i in 1:10]
        if isapprox(mean(nodes.vz[idxs]), nodes.vz[idxs[1]]; atol=1e-12)
            xyz = reshape([nodes.vx; nodes.vy], length(nodes.vx), 2)
            return xyz
        else
            xyz = reshape([nodes.vx; nodes.vy; nodes.vz], length(nodes.vx), 3)
            return xyz
        end
    end
    function get_shapes(elements)
        GMSH_DESC = Dict{Int,String}(
            1 => "L2", 2 => "T3", 3 => "Q4", 4 => "T4", 5 => "Q8")
        if length(elements.elementTag3D) == 0
            shape = SHAPE_DESC[GMSH_DESC[elements.elementType2D[1]]]
            shapes = ShapeColl(shape, size(elements.nodeTags2D, 1), "elements")
            return shapes, elements.nodeTags2D
        else
            shape = SHAPE_DESC[GMSH_DESC[elements.elementType3D[1]]]
            shapes = ShapeColl(shape, size(elements.nodeTags3D, 1), "elements")
            return shapes, elements.nodeTags3D
        end
    end

    xyz = get_vertices(msh.nodes)
    N, T = size(xyz, 2), eltype(xyz)
    locs = VecAttrib([SVector{N,T}(xyz[i, :]) for i in 1:size(xyz, 1)])
    vrts = ShapeColl(P1, length(locs), "vertices")
    vrts.attributes["geom"] = locs
    shapes, elements = get_shapes(msh.elements)

    return ird0 = IncRel(shapes, vrts, elements)
end


text:
using Test
using Transpiler

@testset "Unit tests - python" begin

end


text:
@testset "Test Quote" begin
    @testset "quote" begin
        @test isa(m.evaluate("quote()"), m.NullObj)

        for (input, expected) in [
            ("quote(5)", "5"),
            ("quote(5 + 8)", "(5 + 8)"),
            ("quote(foobar)", "foobar"),
            ("quote(foobar + barfoo)", "(foobar + barfoo)"),
        ]
            evaluated = m.evaluate(input)
            test_quote_object(evaluated, expected)
        end
    end

    @testset "quote and unquote" begin
        for (input, expected) in [
            ("quote(unquote(4))", "4"),
            ("quote(unquote(4 + 4))", "8"),
            ("quote(8 + unquote(4 + 4))", "(8 + 8)"),
            ("quote(unquote(4 + 4) + 8)", "(8 + 8)"),
            ("quote(unquote(null))", "null"),
            ("quote(unquote(true))", "true"),
            ("quote(unquote(true == false))", "false"),
            ("quote(unquote(\"hello\"))", "\"hello\""),
            ("quote(unquote([1, 2, 3]))", "[1, 2, 3]"),
            ("quote(unquote({2 + 3: \"4\" + \"5\"}))", "{5:\"45\"}"),
            ("quote(unquote(fn(x){x}))", "fn(x) { x }"),
            ("quote(unquote(quote(4 + 4)))", "(4 + 4)"),
            (
                "let quotedInfixExpression = quote(4 + 4); quote(unquote(4 + 4) + unquote(quotedInfixExpression))",
                "(8 + (4 + 4))",
            ),
        ]
            evaluated = m.evaluate(input)
            test_quote_object(evaluated, expected)
        end
    end
end


text:
export TabularQApproximator, update!

using StatsBase: sample

"""
    TabularQApproximator(table::Array{Float64, 2})
    TabularQApproximator(ns::Int, na::Int=1, init::Float64=0.)

Use a `table` of type `Array{Float64,2}` to store the action value of each state.
`ns` is the number of states. `na` is the number of actions. `init` is the initial value of `table`.
"""
struct TabularQApproximator <: AbstractQApproximator
    table::Array{Float64,2}
end

TabularQApproximator(; n_state::Int, n_action::Int = 1, init::Float64 = 0.0) =
    TabularQApproximator(fill(init, n_state, n_action))

(Q::TabularQApproximator)(s::Int) = @view(Q.table[s, :])

(Q::TabularQApproximator)(s::Int, a::Int) = Q.table[s, a]

function update!(Q::TabularQApproximator, correction::Pair{Int,Vector{Float64}})
    s, errors = correction
    for (i, e) in enumerate(errors)
        Q.table[s, i] += e
    end
end

function update!(Q::TabularQApproximator, correction::Pair{Tuple{Int,Int},Float64})
    (s, a), e = correction
    Q.table[s, a] += e
end


text:
# Pulls K2vec and sparsify_codes
include("utils.jl")

function reshape_C(C::Vector{Matrix{Float32}}, m::Integer, h::Integer, d::Integer)
    new_C = Array{Float32, 2}(zeros(m*h, d));
    for i = 1:m
      for j = 1:h
        temp = C[i];
        new_C[(i-1)*h+1:i*h, :] = temp';
      end
    end
    return new_C
  end

  function deReshape_C(C::Matrix{Float32}, m::Integer, h::Integer, d::Integer)
    new_C = Vector{Matrix{Float32}}(m);
    for i = 1:m
      for j = 1:h
        temp = C[(i-1)*h+1:i*h, :];
        new_C[i] = temp';
      end
    end
    return new_C
  end

  function update_C(X::Matrix{Float32}, B::Matrix{Int16}, h::Integer, C::Vector{Matrix{Float32}})
    lr = 1e-4

    d, n = size(X);
    m, _ = size(B);

    Codes = sparsify_codes(B, h);
    C = reshape_C(C, m, h, d);

    C_ = Codes' * (Codes * C - X');
    new_C = C - lr * C_;

    new_C = Array{Float32, 2}(new_C);
    C = deReshape_C(new_C, m, h, d);
    return C
  end

  function SGD_miniBatch(X::Matrix{Float32}, B::Matrix{Int16}, h::Integer, C::Vector{Matrix{Float32}}, lr::AbstractFloat, global_step::Integer, decay_rate::AbstractFloat=0.9, decay_step::Integer=10000, batch_size::Integer=64)
    # init_lr = 0.05
    # decay_rate = 0.9
    # decay_step = 10000
    # batch_size = 64
    # print_every = 256
    # global globalStep = 0

    d, n = size(X);
    m, _ = size(B);

    shuffle_idx = randperm(n)

    X_shuffle = X[:, shuffle_idx];
    B_shuffle = B[:, shuffle_idx];

    Codes = sparsify_codes(B_shuffle, h);
    C = reshape_C(C, m, h, d);

    num = Int(floor(n / batch_size))

    for i = 1:num
        bs = Codes[((i-1)*batch_size)+1:(i*batch_size), :];
        xs = X_shuffle[:, ((i-1)*batch_size)+1:(i*batch_size)];
        C_ = bs' * (bs * C - xs');
        C -= lr * C_;
        global_step += 1
        if global_step % decay_step == 0
            print("decay learning rate %.2e -> %.2e\n", lr, lr*decay_rate)
            lr *= decay_rate
        end
    end

    new_C = Array{Float32, 2}(C);
    C = deReshape_C(new_C, m, h, d);
    return C, lr, global_step
  end

  function Momentum_miniBatch(X::Matrix{Float32}, B::Matrix{Int16}, h::Integer, C::Vector{Matrix{Float32}}, lr::AbstractFloat, global_step::Integer, memory::Matrix{Float32}, momentum::AbstractFloat=0.9, decay_rate::AbstractFloat=0.9, decay_step::Integer=10000, batch_size::Integer=64)
    # init_lr = 0.05
    # decay_rate = 0.9
    # decay_step = 10000
    # batch_size = 64
    # print_every = 256
    # global globalStep = 0

    d, n = size(X);
    m, _ = size(B);

    shuffle_idx = randperm(n)

    X_shuffle = X[:, shuffle_idx];
    B_shuffle = B[:, shuffle_idx];

    Codes = sparsify_codes(B_shuffle, h);
    C = reshape_C(C, m, h, d);

    num = Int(floor(n / batch_size))

    for i = 1:num
        bs = Codes[((i-1)*batch_size)+1:(i*batch_size), :];
        xs = X_shuffle[:, ((i-1)*batch_size)+1:(i*batch_size)];
        C_ = bs' * (bs * C - xs');
        memory = momentum * memory + C_
        C -= lr * memory;
        global_step += 1
        if global_step % decay_step == 0
            @printf("decay learning rate %.2e -> %.2e\n", lr, lr*decay_rate)
            lr *= decay_rate
        end
    end

    new_C = Array{Float32, 2}(C);
    C = deReshape_C(new_C, m, h, d);
    memory = Array{Float32, 2}(memory);
    return C, lr, global_step, memory
  end

  function Adam_miniBatch(X::Matrix{Float32}, B::Matrix{Int16}, h::Integer, C::Vector{Matrix{Float32}}, lr::AbstractFloat, global_step::Integer, memory::Matrix{Float32}, momentum::AbstractFloat=0.9, decay_rate::AbstractFloat=0.9, decay_step::Integer=10000, batch_size::Integer=64)
    return X
  end

max_stars_repo_name: xiaosu-zhu/local-search-quantization

max_issues_repo_name: xiaosu-zhu/local-search-quantization

max_forks_repo_name: xiaosu-zhu/local-search-quantization


text:
using ForestBiometrics
using DelimitedFiles
using Test
using Plots

datapath = joinpath(@__DIR__, "data")
data = readdlm(joinpath(datapath,"StandExam_data.csv"),',',header=true)

tl = Tree[]

for i in eachrow(data[1])
    push!(tl,Tree(i[7], i[8], i[6], i[9]))
end

stand = Stand(tl)

@test isapprox(basal_area(stand), 90.58823529)

basal_area!(stand)

@test isapprox(stand.basal_area, 90.58823529)

@test isapprox(trees_per_area(stand), 147.8593658)

trees_per_area!(stand)

@test isapprox(stand.trees_per_area, 147.8593658)

qmd!(stand)

@test isapprox(stand.qmd, 10.5985824)

@test isapprox(sdi(stand),162.3198113)

@test typeof(gingrich_chart(400, 80)) == Plots.Plot{Plots.GRBackend}
@test typeof(gingrich_chart(stand)) == Plots.Plot{Plots.GRBackend}

@test typeof(reineke_chart(500, 8)) == Plots.Plot{Plots.GRBackend}
@test typeof(reineke_chart(stand))  == Plots.Plot{Plots.GRBackend}
@test typeof(reineke_chart(500,8;maxsdi=600)) == Plots.Plot{Plots.GRBackend}


text:
struct ParamModel{T <: Real} <: GAMModel

end


text:
@testset "deprecated" begin
    # Defining our missing datasets
    a = allowmissing(1.0:1.0:20.0)
    a[[2, 3, 7]] .= missing
    mask = map(!ismissing, a)

    # We call collect to not have a wrapper type that references the same data.
    m = collect(reshape(a, 5, 4))

    aa = AxisArray(
        deepcopy(m),
        Axis{:time}(DateTime(2017, 6, 5, 5):Hour(1):DateTime(2017, 6, 5, 9)),
        Axis{:id}(1:4)
    )

    table = DataFrame(
        :sin => allowmissing(sin.(1.0:1.0:20.0)),
        :cos => allowmissing(sin.(1.0:1.0:20.0)),
    )

    table.sin[[2, 3, 7, 12, 19]] .= missing

    @testset "Default colwise" begin
        msg = string(
            "Imputing on matrices will require specifying `dims=2` or `dims=:cols` in a ",
            "future release, to maintain the current behaviour."
        )
        @test_logs (:warn, msg) Impute.interp(m)
        @test_logs (:warn, msg) Impute.locf(m)
        @test_logs (:warn, msg) Impute.nocb(m)
        @test_logs (:warn, msg) Impute.srs(m)
    end

    @testset "Fill" begin
        @testset "Value" begin
            fill_val = -1.0
            result = @test_deprecated impute(a, Fill(; value=fill_val))
            expected = copy(a)
            expected[[2, 3, 7]] .= fill_val

            @test result == expected
            @test result == @test_deprecated Impute.fill(a; value=fill_val)
        end

        @testset "Mean" begin
            result = @test_deprecated impute(a, Fill(; value=mean))
            expected = copy(a)
            expected[[2, 3, 7]] .= mean(a[mask])

            @test result == expected
            @test result == @test_deprecated Impute.fill(a; value=mean)

            a2 = copy(a)
            @test_deprecated Impute.fill!(a2)
            @test a2 == result
        end

        @testset "Matrix" begin
            data = Matrix(Impute.dataset("test/table/neuro") |> DataFrame)

            result = @test_deprecated impute(data, Fill(; value=0.0); dims=:cols)
            @test size(result) == size(data)
            @test result == @test_deprecated Impute.fill(data; value=0.0, dims=:cols)

            data2 = copy(data)
            @test_deprecated Impute.fill!(data2; value=0.0, dims=:cols)
            @test data2 == result
        end
    end

    @testset "Drop" begin
        @testset "Equality" begin
            @testset "$T" for T in (DropObs, DropVars)
                @test @test_deprecated(T()) == @test_deprecated(T())
            end
        end

        @testset "DropObs" begin
            @testset "Vector" begin
                result = @test_deprecated impute(a, DropObs())
                expected = deleteat!(deepcopy(a), [2, 3, 7])

                @test result == expected
                @test result == @test_deprecated Impute.dropobs(a)

                a2 = deepcopy(a)
                @test_deprecated Impute.dropobs!(a2)
                @test a2 == expected
            end

            @testset "Matrix" begin
                result = @test_deprecated impute(m, DropObs())
                expected = m[[1, 4, 5], :]

                @test isequal(result, expected)
                @test isequal(result, @test_deprecated(Impute.dropobs(m)))
                @test isequal(
                    collect(result'),
                    @test_deprecated(Impute.dropobs(collect(m'); dims=2))
                )

                m_ = @test_deprecated Impute.dropobs!(m)
                # The mutating test is broken because we need to making a copy of
                # the original matrix
                @test_broken isequal(m, expected)
                @test isequal(m_, expected)
            end

            @testset "Tables" begin
                @testset "DataFrame" begin
                    df = deepcopy(table)
                    result = impute(df, @test_deprecated DropObs())
                    expected = dropmissing(df)

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated(Impute.dropobs(df)))

                    df_ = @test_deprecated Impute.dropobs!(df)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    @test_broken isequal(df, expected)
                    @test isequal(df_, expected)
                end

                @testset "Column Table" begin
                    coltab = Tables.columntable(table)

                    result = @test_deprecated impute(coltab, DropObs())
                    expected = Tables.columntable(dropmissing(table))

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated(Impute.dropobs(coltab)))

                    coltab_ = @test_deprecated Impute.dropobs!(coltab)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    @test_broken isequal(coltab, expected)
                    @test isequal(coltab_, expected)
                end

                @testset "Row Table" begin
                    rowtab = Tables.rowtable(table)
                    result = @test_deprecated impute(rowtab, DropObs())
                    expected = Tables.rowtable(dropmissing(table))

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated(Impute.dropobs(rowtab)))

                    rowtab_ = @test_deprecated Impute.dropobs!(rowtab)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    # @test_broken isequal(rowtab, expected)
                    @test isequal(rowtab_, expected)
                end
            end

            @testset "AxisArray" begin
                result = @test_deprecated impute(aa, DropObs())
                expected = aa[[1, 4, 5], :]

                @test isequal(result, expected)
                @test isequal(result, @test_deprecated(Impute.dropobs(aa)))

                aa_ = @test_deprecated Impute.dropobs!(aa)
                # The mutating test is broken because we need to making a copy of
                # the original matrix
                @test_broken isequal(aa, expected)
                @test isequal(aa_, expected)
            end
        end

        @testset "DropVars" begin
            @testset "Vector" begin
                @test_deprecated @test_throws MethodError Impute.dropvars(a)
            end

            @testset "Matrix" begin
                result = @test_deprecated impute(m, DropVars())
                expected = copy(m)[:, 3:4]

                @test isequal(result, expected)
                @test isequal(result, @test_deprecated(Impute.dropvars(m)))
                @test isequal(
                    collect(result'),
                    @test_deprecated(Impute.dropvars(collect(m'); dims=2))
                )

                m_ = @test_deprecated Impute.dropvars!(m)
                # The mutating test is broken because we need to making a copy of
                # the original matrix
                @test_broken isequal(m, expected)
                @test isequal(m_, expected)
            end

            @testset "Tables" begin
                @testset "DataFrame" begin
                    df = deepcopy(table)
                    result = @test_deprecated impute(df, DropVars())
                    expected = select(df, :cos)

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated Impute.dropvars(df))

                    @test_deprecated Impute.dropvars!(df)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    @test_broken isequal(df, expected)
                end

                @testset "Column Table" begin
                    coltab = Tables.columntable(table)

                    result = @test_deprecated impute(coltab, DropVars())
                    expected = Tables.columntable(TableOperations.select(coltab, :cos))

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated Impute.dropvars(coltab))

                    @test_deprecated Impute.dropvars!(coltab)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    @test_broken isequal(coltab, expected)
                end

                @testset "Row Table" begin
                    rowtab = Tables.rowtable(table)
                    result = @test_deprecated impute(rowtab, DropVars())
                    expected = Tables.rowtable(TableOperations.select(rowtab, :cos))

                    @test isequal(result, expected)
                    @test isequal(result, @test_deprecated Impute.dropvars(rowtab))

                    @test_deprecated Impute.dropvars!(rowtab)
                    # The mutating test is broken because we need to making a copy of
                    # the original table
                    @test_broken isequal(rowtab, expected)
                end
            end
            @testset "AxisArray" begin
                result = @test_deprecated impute(aa, DropVars())
                expected = copy(aa)[:, 3:4]

                @test isequal(result, expected)
                @test isequal(result, @test_deprecated Impute.dropvars(aa))

                aa_ = @test_deprecated Impute.dropvars!(aa)
                # The mutating test is broken because we need to making a copy of
                # the original matrix
                @test_broken isequal(aa, expected)
                @test isequal(aa_, expected)
            end
        end
    end

    @testset "Chain" begin
        orig = Impute.dataset("test/table/neuro") |> DataFrame

        # Less effecient, but a chain should produce the same results as manual
        # piping the functional outputs.
        result = Impute.interp(orig) |> Impute.locf! |> Impute.nocb!

        @test size(result) == size(orig)
        # Confirm that we don't have any more missing values
        @test all(!ismissing, Matrix(result))

        # We can also use the Chain type with explicit Imputor types
        result2 = @test_deprecated impute(
            orig,
            Impute.Chain(
                Impute.Interpolate(),
                Impute.LOCF(),
                Impute.NOCB()
            ),
        )

        # Test creating a Chain via Imputor composition
        C = Impute.Interpolate() ∘ Impute.LOCF() ∘ Impute.NOCB()
        result3 = @test_deprecated impute(orig, C)
        @test result == result2
        @test result == result3
    end

    @testset "utils" begin
        M = [1.0 2.0 3.0 4.0 5.0; 1.1 2.2 3.3 4.4 5.5]

        @testset "obswise" begin
            @test map(sum, Impute.obswise(M; dims=2)) == [2.1, 4.2, 6.3, 8.4, 10.5]
            @test map(sum, Impute.obswise(M; dims=1)) == [15, 16.5]
        end

        @testset "varwise" begin
            @test map(sum, Impute.varwise(M; dims=2)) == [15, 16.5]
            @test map(sum, Impute.varwise(M; dims=1)) == [2.1, 4.2, 6.3, 8.4, 10.5]
        end

        @testset "filterobs" begin
            @test Impute.filterobs(x -> sum(x) > 5.0, M; dims=2) == M[:, 3:5]
            @test Impute.filterobs(x -> sum(x) > 15.0, M; dims=1) == M[[false, true], :]
        end

        @testset "filtervars" begin
            @test Impute.filtervars(x -> sum(x) > 15.0, M; dims=2) == M[[false, true], :]
            @test Impute.filtervars(x -> sum(x) > 5.0, M; dims=1) == M[:, 3:5]
        end
    end
end


text:
# Autogenerated wrapper script for gmsh_jll for x86_64-apple-darwin
export gmsh_api, libgmsh

JLLWrappers.@generate_wrapper_header("gmsh")
JLLWrappers.@declare_file_product(gmsh_api)
JLLWrappers.@declare_library_product(libgmsh, "@rpath/libgmsh.4.8.dylib")
function __init__()
    JLLWrappers.@generate_init_header()
    JLLWrappers.@init_file_product(
        gmsh_api,
        "lib/gmsh.jl",
    )

    JLLWrappers.@init_library_product(
        libgmsh,
        "lib/libgmsh.4.8.4.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
### A Pluto.jl notebook ###
# v0.12.20

#
# This example assumes below packages are available in your environment
#

using Markdown
using InteractiveUtils

# ╔═╡ dcb4d418-5ec2-11eb-29d8-214f38b4d3ae
using Pkg, DrWatson

# ╔═╡ d20c24f8-5ec2-11eb-3d45-d97fedebee8e
begin
	@quickactivate "StatsModelComparisons"
	using StatsModelComparisons
	using StanSample, StatsFuns, StatsPlots
	using DataFrames, CSV
end

# ╔═╡ e3552750-5e9f-11eb-324b-8df36d671c79
begin
	ProjDir = joinpath(mc_path, "..", "examples", "roaches")
	df = CSV.read(joinpath(ProjDir, "roachdata.csv"), DataFrame)
	df.roach1 = df.roach1 / 100
end;

# ╔═╡ e3691972-5e9f-11eb-20b2-a766ef562598
roaches1_stan = "
data {
  int<lower=0> N;
  int<lower=0> K;
  vector[N] exposure2;
  vector[N] roach1;
  vector[N] senior;
  vector[N] treatment;
  int y[N];
}
transformed data {
  vector[N] log_expo;
  log_expo = log(exposure2);
}
parameters {
  vector[K] beta;
}
transformed parameters {
   vector[N] eta;
   eta = log_expo + beta[1] + beta[2] * roach1 + beta[3] * treatment
                + beta[4] * senior;
}
model {
  y ~ poisson_log(eta);
}
generated quantities {
  vector[N] log_lik;
  for (i in 1:N)
    log_lik[i] = poisson_log_lpmf(y[i] | eta[i]);
}
";

# ╔═╡ e371fde4-5e9f-11eb-39ad-8d25ac3034fc
begin
	data = (N = size(df, 1), K = 4, y = Int.(df.y), roach1=df.roach1,
 		exposure2=df.exposure2, senior=df.senior, treatment=df.treatment)
	n = size(df, 1)
	tmpdir=joinpath(ProjDir, "tmp")
	sm1 = SampleModel("roaches1", roaches1_stan; tmpdir)
	rc1 = stan_sample(sm1; data)
end;

# ╔═╡ e3a796f2-5e9f-11eb-20bf-9de765acb853
if success(rc1)
  read_summary(sm1, true)
  nt1 = read_samples(sm1)

  # Compute LOO and standard error
  log_lik1 = nt1.log_lik'
  loo1, loos1, pk1 = psisloo(log_lik1)
  elpd_loo1 = sum(loos1)
  se_elpd_loo1 = std(loos1) * sqrt(n)
  (elpd_loo = elpd_loo1, se_elpd_loo = se_elpd_loo1)
end

# ╔═╡ abc94b86-5ea9-11eb-1d5d-578926de3257
if success(rc1)
	# Check the shape parameter k of the generalized Pareto distribution
	pk_qualify(pk1) |> display
end

# ╔═╡ e3b82668-5e9f-11eb-1641-a9dfed9eb108
pk_plot(pk1, title="PSIS diagnostic plot for poisson-log model.")

# ╔═╡ e3b8fc58-5e9f-11eb-0607-e5424a04df9c
md" ##### Simple negative binomial regression example using the 2nd parametrization of the negative binomial distribution, see section 40.1-3 in the Stan reference guide."

# ╔═╡ 5824d2ae-5ea2-11eb-3545-dd03c4aa7a69
roaches2_stan = "
data {
  int<lower=0> N;
  int<lower=0> K;
  vector[N] exposure2;
  vector[N] roach1;
  vector[N] senior;
  vector[N] treatment;
  int y[N];
}
transformed data {
  vector[N] log_expo;
  log_expo = log(exposure2);
}
parameters {
  real phi;
  vector[K] beta;
}
transformed parameters {
   vector[N] eta;
   eta = log_expo + beta[1] + beta[2] * roach1 + beta[3] * treatment
                + beta[4] * senior;
}
model {
  phi ~ normal(0, 10);
  beta[1] ~ cauchy(0,10);   //prior for the intercept following Gelman 2008
  for(i in 2:K)
   beta[i] ~ cauchy(0,2.5); //prior for the slopes following Gelman 2008
  y ~ neg_binomial_2_log(eta, phi);
}
generated quantities {
 vector[N] log_lik;
 for(i in 1:N){
  log_lik[i] <- neg_binomial_2_log_lpmf(y[i] | eta[i], phi);
 }
}
";

# ╔═╡ e3cab9ca-5e9f-11eb-3b31-c34fabb9e8d1
begin
	sm2 = SampleModel("roaches2", roaches2_stan; tmpdir)
	rc2 = stan_sample(sm2; data)
end;

# ╔═╡ e3ddad0a-5e9f-11eb-0a7d-150f9f398fe0
if success(rc2)
  read_summary(sm2, true)
  nt2 = read_samples(sm2)

  # Compute LOO and standard error
  log_lik2 = nt2.log_lik'
  loo2, loos2, pk2 = psisloo(log_lik2)
  elpd_loo2 = sum(loos2)
  se_elpd_loo2 = std(loos2) * sqrt(n)
  (elpd_loo = elpd_loo2, se_elpd_loo = se_elpd_loo2)
end

# ╔═╡ ce509a0c-5ea8-11eb-3f2e-01023f29a1e3
if success(rc2)
	  # Check the shape parameter k of the generalized Pareto distribution
	pk_qualify(pk2)
end

# ╔═╡ e3f103f0-5e9f-11eb-159a-452961ed2619
pk_plot(pk2; title="PSIS diagnostic plot for neg-binomial model.", leg=:topright)

# ╔═╡ Cell order:
# ╠═dcb4d418-5ec2-11eb-29d8-214f38b4d3ae
# ╠═d20c24f8-5ec2-11eb-3d45-d97fedebee8e
# ╠═e3552750-5e9f-11eb-324b-8df36d671c79
# ╠═e3691972-5e9f-11eb-20b2-a766ef562598
# ╠═e371fde4-5e9f-11eb-39ad-8d25ac3034fc
# ╠═e3a796f2-5e9f-11eb-20bf-9de765acb853
# ╠═abc94b86-5ea9-11eb-1d5d-578926de3257
# ╠═e3b82668-5e9f-11eb-1641-a9dfed9eb108
# ╟─e3b8fc58-5e9f-11eb-0607-e5424a04df9c
# ╠═5824d2ae-5ea2-11eb-3545-dd03c4aa7a69
# ╠═e3cab9ca-5e9f-11eb-3b31-c34fabb9e8d1
# ╠═e3ddad0a-5e9f-11eb-0a7d-150f9f398fe0
# ╠═ce509a0c-5ea8-11eb-3f2e-01023f29a1e3
# ╠═e3f103f0-5e9f-11eb-159a-452961ed2619


text:
function draw_circle(x::Real, y::Real, r::Real; opts...)
    f(t) = r * cos(t) + x
    g(t) = r * sin(t) + y
    return plot!(f, g, 0, 2pi; opts...)
end

function line!((x1, y1), (x2, y2); opts...)
    return plot!([x1, x2], [y1, y2]; opts...)
end

function draw_grid(grid::Grid{Tria})
    p = plot(; aspectratio=1, legend=false, axis=false, grid=false, ticks=false)

    width = 1
    half_width = 0.5 * width
    height = sqrt(3) / 2
    half_height = 0.5 * height

    for (cond, color, linestyle) in [(true, :gray, :solid), (false, :black, :solid)]
        for row in 1:(grid.nrows)
            for col in 1:(grid.ncols)
                cell = grid[row, col]
                upright = iseven(cell.row + cell.col)
                cx = half_width + cell.col * half_width
                cy = half_height + cell.row * height
                west_x = (cx - half_width)
                mid_x = cx
                east_x = cx + half_width

                apex_y = upright ? -(cy - half_height) : -(cy + half_height)
                base_y = upright ? -(cy + half_height) : -(cy - half_height)

                if cond || !is_connected(cell, west(grid, cell))
                    line!((west_x, base_y), (mid_x, apex_y); color, linestyle)
                end
                if cond || !is_connected(cell, east(grid, cell))
                    line!((east_x, base_y), (mid_x, apex_y); color, linestyle)
                end

                ncond = (!upright && !is_connected(cell, north(grid, cell)))
                scond = (upright && !is_connected(cell, south(grid, cell)))
                if cond || ncond || scond
                    line!((east_x, base_y), (west_x, base_y); color, linestyle)
                end
            end
        end
    end

    return p
end

function draw_distance(::Type{Tria}, dists)
    @info "draw path"

    width = 1
    half_width = 0.5 * width
    height = sqrt(3) / 2
    half_height = 0.5 * height

    for (cell, d) in dists
        @info cell d
        cx = half_width + cell.col * half_width
        cy = half_height + cell.row * height

        x = cx
        y = -cy
        annotate!(x, y, "$(d)")
    end
end

function draw_solution(::Type{Tria}, path)
    @info "draw path"

    width = 1
    half_width = 0.5 * width
    height = sqrt(3) / 2
    half_height = 0.5 * height

    for (cell, d) in path
        @info cell d
        cx = half_width + cell.col * half_width
        cy = half_height + cell.row * height
        x = cx
        y = -cy
        draw_circle(x, y, 0.15; color=:red, seriestype=[:shape], fillalpha=1.0)
    end
end

function draw_grid(grid::Grid{Rect})
    p = plot(; aspectratio=1, legend=false, axis=false, grid=false, ticks=false)

    for row in 1:(grid.nrows)
        for col in 1:(grid.ncols)
            cell = grid[row, col]
            x1 = cell.col
            x2 = (1 + cell.col)
            # flip
            y1 = -cell.row + 1
            y2 = -(1 + cell.row) + 1
            if !is_connected(cell, north(grid, cell))
                line!((x1, y1), (x2, y1); color=:black)
            end
            if !is_connected(cell, west(grid, cell))
                line!((x1, y1), (x1, y2); color=:black)
            end
            if !is_connected(cell, east(grid, cell))
                line!((x2, y1), (x2, y2); color=:black)
            end
            if !is_connected(cell, south(grid, cell))
                line!((x1, y2), (x2, y2); color=:black)
            end
        end
    end

    return p
end

function draw_distance(::Type{Rect}, dists)
    @info "draw path"

    for (cell, d) in dists
        @info cell d
        x = cell.col + 0.5
        y = -(cell.row - 0.5)
        annotate!(x, y, "$(d)")
    end
end

function draw_solution(::Type{Rect}, path)
    @info "draw path"

    for (cell, d) in path
        @info cell d
        x = cell.col + 0.5
        y = -(cell.row - 0.5)
        draw_circle(x, y, 0.2; color=:red, seriestype=[:shape], fillalpha=1.0)
    end
end

function draw_grid(grid::Grid{Hexa})
    a = 0.5
    b = 0.5 * sqrt(3.0)
    height = b * 2

    p = plot(; aspectratio=1, legend=false, axis=false, grid=false, ticks=false)

    for (cond, color, linestyle) in [(true, :gray, :dash), (false, :black, :solid)]
        for row in 1:(grid.nrows)
            for col in 1:(grid.ncols)
                cell = grid[row, col]
                cx = 1 + 3 * cell.col * a
                cy = b + cell.row * height
                if isodd(cell.col)
                    cy += b
                end
                x_fw = (cx - 1)
                x_nw = (cx - a)
                x_ne = (cx + a)
                x_fe = (cx + 1)

                y_n = -(cy - b)
                y_m = -cy
                y_s = -(cy + b)

                if cond || !is_connected(cell, southwest(grid, cell))
                    line!((x_fw, y_m), (x_nw, y_s); color, linestyle)
                end

                if cond || !is_connected(cell, northwest(grid, cell))
                    line!((x_fw, y_m), (x_nw, y_n); color, linestyle)
                end
                if cond || !is_connected(cell, north(grid, cell))
                    line!((x_nw, y_n), (x_ne, y_n); color, linestyle)
                end
                if cond || !is_connected(cell, northeast(grid, cell))
                    line!((x_ne, y_n), (x_fe, y_m); color, linestyle)
                end
                if cond || !is_connected(cell, southeast(grid, cell))
                    line!((x_fe, y_m), (x_ne, y_s); color, linestyle)
                end
                if cond || !is_connected(cell, south(grid, cell))
                    line!((x_ne, y_s), (x_nw, y_s); color, linestyle)
                end
            end
        end
    end

    return p
end

function draw_distance(::Type{Hexa}, dists)
    @info "draw path"

    a = 0.5
    b = 0.5 * sqrt(3.0)
    height = b * 2

    for (cell, d) in dists
        @info cell d
        cx = 1 + 3 * cell.col * a
        cy = b + cell.row * height
        if isodd(cell.col)
            cy += b
        end
        x = cx
        y = -cy
        annotate!(x, y, "$(d)")
    end
end

function draw_solution(::Type{Hexa}, path)
    @info "draw path"

    a = 0.5
    b = 0.5 * sqrt(3.0)
    height = b * 2

    for (cell, d) in path
        @info cell d
        cx = 1 + 3 * cell.col * a
        cy = b + cell.row * height
        if isodd(cell.col)
            cy += b
        end
        x = cx
        y = -cy
        draw_circle(x, y, 0.3; color=:red, seriestype=[:shape], fillalpha=1.0)
    end
end