using Luxor
using UUIDs

##
# No padding: Padding can be achived using margins of the inner element
##

##
# Everything visual is an aggregation of 
# Label, grid lines, and Cell primitives.
##

##
# Grid is a simpler structure than Table.
# - All rows sizes are equal to all column sizes.
# - Row/col size is predetermined (i.e., doesn't depend on any cell content).
# - The entire grid size is predetermined (base on the aggregate of the cell size, plus the margins).
##

struct Grid <: Drawable
    cell_fn::Function
    num_rows::Integer
    num_cols::Integer
    cell_size::Integer
    line_color::Color
    line_thickness::Integer
    title::Union{Nothing,Label}
    title_right::Union{Nothing,Label}
    header::Union{Nothing,Label}
    header_right::Union{Nothing,Label}
    margins::Margins
    font_size::Integer
end

# @todo make "cell_fn" optional (i.e.), Grid(3,3)
function Grid(    
    num_rows::Integer,
    num_cols::Integer,
    cell_fn::Function;
    cell_size=50,
    line_thickness=1,
    line_color="#333",
    title=nothing,
    title_right=nothing,
    header=nothing,
    header_right=nothing,
    margin=nothing,
    margin_top=nothing,
    margin_bottom=nothing,
    margin_left=nothing,
    margin_right=nothing,
    font_size=22,
    )
    if !isnothing(margin)
        if isnothing(margin_top)
            margin_top = margin
        end
        if isnothing(margin_bottom)
            margin_bottom = margin
        end
        if isnothing(margin_left)
            margin_left = margin
        end
        if isnothing(margin_right)
            margin_right = margin
        end
    end
    if isnothing(margin_top)
        if !isnothing(title) || !isnothing(title_right)
            margin_top = cell_size
        end
    end
    if isnothing(margin_bottom)
        if !isnothing(header) || !isnothing(header_right)
            margin_bottom = cell_size
        end
    end
    Grid(
        cell_fn,
        num_rows,
        num_cols,
        cell_size,
        Color(line_color),
        line_thickness,
        Label(title),
        Label(title_right),
        Label(header),
        Label(header_right),
        Margins(
            top=margin_top,
            bottom=margin_bottom,
            left=margin_left,
            right=margin_right,
        ),
        font_size
    )
end

function Grid(grids::Vector{<:Drawable}; kwargs...)
    Grid(grids, :, 3; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, num_rows::Integer, ::Colon; kwargs...)
    num_cols = ((length(grids)-1) ÷ num_rows) + 1
    Grid(grids, num_rows, num_cols; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, ::Colon, num_cols::Integer; kwargs...)
    num_rows = ((length(grids)-1) ÷ num_cols) + 1
    Grid(grids, num_rows, num_cols; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, num_rows::Integer, num_cols::Integer; kwargs...)
    function cellfn(row,col)
        grids_idx = num_cols*(row-1) + col
        if grids_idx > length(grids)
            return nothing
        end
        grids[grids_idx]
    end
    Table(num_rows, num_cols, cellfn; kwargs...)
end


getcellsize(g::Grid) = (g.cell_size, g.cell_size)
getnumrows(g::Grid) = g.num_rows
getnumcols(g::Grid) = g.num_cols
getlinecolor(g::Grid) = string(g.line_color.name)

function getwidth(g::Grid)
    sum([
        (g.cell_size * g.num_cols),
        g.margins.left,
        g.margins.right,
    ])
end

function getheight(g::Grid)
    sum([
        (g.cell_size * g.num_rows),
        g.margins.top, 
        g.margins.bottom,
    ])
end

usetitlerow(g::Grid) = !isnothing(g.title) || !isnothing(g.title_right)
useheaderrow(g::Grid) = !isnothing(g.header) || !isnothing(g.header_right)

getdrawable(x::Drawable; kwargs...) = x
getdrawable(x::Label; kwargs...) = Cell(Label(x); kwargs...)
getdrawable(::Nothing; kwargs...) = Cell(nothing; kwargs...)
getdrawable(x; kwargs...) = Cell(Label(x); kwargs...)

draw(::Nothing, pos, cell_size; halign=nothing) = nothing

function drawtitle(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (-1*(g.cell_size/2), 0),
        #title offset
    ])
    draw(
        g.title,
        pos,
        g.cell_size;
        halign=:left
    )
end
function drawtitleright(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (g.cell_size/2, 0),
        #titleright offset
    ])
    draw(
        g.title_right,
        pos,
        g.cell_size;
        halign=:right
    )
end
function drawheader(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (-1*(g.cell_size/2), 0),
        #header offset
    ])
    draw(
        g.header,
        pos,
        g.cell_size;
        halign=:left
    )
end

function drawheaderright(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (g.cell_size/2, 0),
        #headerright offset
    ])
    draw(
        g.header_right,
        pos,
        g.cell_size;
        halign=:right
    )
end

function draw(g::Grid)
    sethue("#fff")
    box(O, getwidth(g), getheight(g), :fill)
    num_rows = getnumrows(g) + 2
    num_cols = getnumcols(g)
    A = rand(1:99, num_rows, num_cols)
    #@show "Grid.draw.translate"
    #translate(0, (g.margins.top - g.margins.bottom) / 2.0)
    sethue("black")
    fontsize(30)
    setline(0.5)
    sethue("white")
    table = Luxor.Table(size(A)..., getcellsize(g)...)
    after_effects = []
    for r in 1:num_rows, c in 1:num_cols
        cell_center = table[r, c]
        if r == 1 
            if c == 1
                drawtitle(g, cell_center)
            elseif c == num_cols
                drawtitleright(g, cell_center)
            end
            continue
        end
        if r == num_rows
            if c == 1
                drawheader(g, cell_center)
            elseif c == num_cols
                drawheaderright(g, cell_center)
            end
            continue
        end
        grid_row = r - 1
        grid_col = c
        sethue("#333")
        fontsize(g.font_size)
        x = g.cell_fn(grid_row,grid_col)
        d = getdrawable(x; line_thickness=g.line_thickness)
        draw(
            d, 
            cell_center,
            g.cell_size
        )
        if hasaftereffect(d)
            push!(after_effects, (d,cell_center))
        end
    end
    for after_effect in after_effects
        (drawable, position) = after_effect
        drawaftereffect(
            drawable, 
            position,
            g.cell_size
        )
    end
end

function savesvg(g::Grid, path::String)
    ensurepathtoexists(path)
    Drawing(
        getwidth(g), 
        getheight(g), 
        path
    )
    origin()
    draw(g)
    finish()
    preview()
end

function Base.show(io::IO, m::MIME"image/svg+xml", g::Grid)
    path = "./output/grid.svg"
    savesvg(g, path)
    print(io, read(path, String))
end