local Blitbuffer = require("ffi/blitbuffer")
local Button = require("ui/widget/button")
local Device = require("device")
local FocusManager = require("ui/widget/focusmanager")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local LineWidget = require("ui/widget/linewidget")
local Size = require("ui/size")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local Geom = require("ui/geometry")
local Screen = Device.screen

local ButtonTable = FocusManager:extend{
    width = nil,
    -- If requested, allow ButtonTable to shrink itself if 'width' can
    -- be reduced without any truncation or font size getting smaller.
    shrink_unneeded_width = false,
    -- But we won't go below this: buttons are tapable, we want some
    -- minimal width so they are easy to tap (this is mostly needed
    -- for CJK languages where button text can be one or two glyphs).
    shrink_min_width = Screen:scaleBySize(100),

    buttons = {
        {
            {text="OK", enabled=true, callback=nil},
            {text="Cancel", enabled=false, callback=nil},
        },
    },
    sep_width = Size.line.medium,
    zero_sep = false,
}

function ButtonTable:init()
    self.width = self.width or math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.9)
    self.buttons_layout = {}
    self.button_by_id = {}
    self.container = VerticalGroup:new{ width = self.width }
    self[1] = self.container
    if self.zero_sep then
        -- If we're asked to add a first line, don't add a vspan before: caller
        -- must do its own padding before.
        self:addVerticalSeparator()
    end
    local row_cnt = #self.buttons
    local table_min_needed_width = -1
    for i = 1, row_cnt do
        self:addVerticalSpan()
        local buttons_layout_line = {}
        local horizontal_group = HorizontalGroup:new{}
        local row = self.buttons[i]
        local column_cnt = #row
        local available_width = self.width - self.sep_width * (column_cnt - 1)
        local unspecified_width_buttons = 0
        for j = 1, column_cnt do
            local btn_entry = row[j]
            if btn_entry.width then
                available_width = available_width - btn_entry.width
            else
                unspecified_width_buttons = unspecified_width_buttons + 1
            end
        end
        local default_button_width = math.floor(available_width / unspecified_width_buttons)
        local min_needed_button_width = -1
        for j = 1, column_cnt do
            local btn_entry = row[j]
            local button = Button:new{
                text = btn_entry.text,
                text_func = btn_entry.text_func,
                checked_func = btn_entry.checked_func,
                lang = btn_entry.lang,
                icon = btn_entry.icon,
                icon_width = btn_entry.icon_width,
                icon_height = btn_entry.icon_height,
                align = btn_entry.align,
                enabled = btn_entry.enabled,
                enabled_func = btn_entry.enabled_func,
                callback = function()
                    if self.show_parent and self.show_parent.movable then
                        self.show_parent.movable:resetEventState()
                    end
                    btn_entry.callback()
                end,
                hold_callback = btn_entry.hold_callback,
                allow_hold_when_disabled = btn_entry.allow_hold_when_disabled,
                vsync = btn_entry.vsync,
                width = btn_entry.width or default_button_width,
                bordersize = 0,
                margin = 0,
                padding = Size.padding.buttontable, -- a bit taller than standalone buttons, for easier tap
                padding_h = btn_entry.align == "left" and Size.padding.large or Size.padding.button,
                    -- allow text to take more of the horizontal space if centered
                avoid_text_truncation = btn_entry.avoid_text_truncation,
                text_font_face = btn_entry.font_face,
                text_font_size = btn_entry.font_size,
                text_font_bold = btn_entry.font_bold,
                show_parent = self.show_parent,
            }
            if self.shrink_unneeded_width and not btn_entry.width and min_needed_button_width ~= false then
                -- We gather the largest min width of all buttons without a specified width,
                -- and will see how it does when this largest min width is applied to all
                -- buttons (without a specified width): we still want to keep them the same
                -- size and balanced.
                local min_width = button:getMinNeededWidth()
                if min_width then
                    if min_needed_button_width < min_width then
                        min_needed_button_width = min_width
                    end
                else
                    -- If any one button in this row can't be made smaller, give up
                    min_needed_button_width = false
                end
            end
            if btn_entry.id then
                self.button_by_id[btn_entry.id] = button
            end
            local button_dim = button:getSize()
            local vertical_sep = LineWidget:new{
                background = Blitbuffer.COLOR_GRAY,
                dimen = Geom:new{
                    w = self.sep_width,
                    h = button_dim.h,
                }
            }
            buttons_layout_line[j] = button
            table.insert(horizontal_group, button)
            if j < column_cnt then
                table.insert(horizontal_group, vertical_sep)
            end
        end -- end for each button
        table.insert(self.container, horizontal_group)
        self:addVerticalSpan()
        if i < row_cnt then
            self:addVerticalSeparator()
        end
        if column_cnt > 0 then
            -- Only add lines that are not separator to the focusmanager
            table.insert(self.buttons_layout, buttons_layout_line)
        end
        if self.shrink_unneeded_width and table_min_needed_width ~= false then
            if min_needed_button_width then
                if min_needed_button_width >= 0 and min_needed_button_width < default_button_width then
                    local row_min_width = self.width - (default_button_width - min_needed_button_width)*unspecified_width_buttons
                    if table_min_needed_width < row_min_width then
                        table_min_needed_width = row_min_width
                    end
                end
            else
                -- If any one row can't be made smaller, give up
                table_min_needed_width = false
            end
        end
    end -- end for each button line
    if Device:hasDPad() then
        self.layout = self.buttons_layout
        self:refocusWidget()
    else
        self.key_events = {}  -- deregister all key press event listeners
    end
    if self.shrink_unneeded_width and table_min_needed_width ~= false
            and table_min_needed_width > 0 and table_min_needed_width < self.width then
        self.width = table_min_needed_width > self.shrink_min_width and table_min_needed_width or self.shrink_min_width
        self.shrink_unneeded_width = false
        self:free()
        self:init()
    end
end

function ButtonTable:addVerticalSpan()
    table.insert(self.container, VerticalSpan:new{
        width = Size.span.vertical_default,
    })
end

function ButtonTable:addVerticalSeparator(black_line)
    table.insert(self.container, LineWidget:new{
        background = black_line and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_GRAY,
        dimen = Geom:new{
            w = self.width,
            h = self.sep_width,
        },
    })
end

function ButtonTable:setupGridScrollBehaviour()
    -- So that the last row get the same height as all others,
    -- we add an invisible separator below it
    self.container:resetLayout()
    table.insert(self.container, VerticalSpan:new{
        width = self.sep_width,
    })
    self.container:getSize() -- have it recompute its offsets and size

    -- Generate self.step_scroll_grid (so that what we add next is not part of it)
    self:getStepScrollGrid()

    -- Insert 2 lines off-dimensions in VerticalGroup (that will show only when overflowing)
    table.insert(self.container, 1, LineWidget:new{
        background = Blitbuffer.COLOR_BLACK,
        dimen = Geom:new{
            w = self.width,
            h = self.sep_width,
        },
    })
    table.insert(self.container._offsets, 1, { x=self.width, y=-self.sep_width })
    table.insert(self.container, LineWidget:new{
        background = Blitbuffer.COLOR_BLACK,
        dimen = Geom:new{
            w = self.width,
            h = self.sep_width,
        },
    })
    table.insert(self.container._offsets, { x=self.width, y=self.container._size.h + self.sep_width })
end


function ButtonTable:getStepScrollGrid()
    if not self.step_scroll_grid then
        local step_rows = {}
        local offsets = self.container._offsets
        local idx = self.zero_sep and 2 or 1
        local row_num = 1
        while idx <= #self.container do
            local row = {
                row_num = row_num, -- (not used, but may help with debugging)
                top = offsets[idx].y, -- top of our vspan above text
                content_top = offsets[idx+1].y, -- top of our text widget
                content_bottom = offsets[idx+2].y - 1, -- bottom of our text widget
                bottom = idx+4 <= #self.container and offsets[idx+4].y -1 or self.container:getSize().h -1
                                                      -- bottom of our vspan + separator below text
                -- To implement when needed:
                -- columns = { array of similar info about each button in that row's HorizontalGroup }
                -- Its absence means free scrolling on the x-axis (if scroll ends up being needed)
            }
            table.insert(step_rows, row)
            idx = idx + 4
            row_num = row_num + 1
        end
        self.step_scroll_grid = step_rows
    end
    return self.step_scroll_grid
end

function ButtonTable:getButtonById(id)
    return self.button_by_id[id] -- nil if not found
end

return ButtonTable
