-- @docclass
--[[
  TODO:
    * Make table headers more robust.
    * Get dynamic row heights working with text wrapping.
]] TABLE_SORTING_ASC = 0
TABLE_SORTING_DESC = 1

UITable = extends(UIWidget, 'UITable')

-- Initialize default values
function UITable.create()
    local table = UITable.internalCreate()
    table.headerRow = nil
    table.headerColumns = {}
    table.dataSpace = nil
    table.rows = {}
    table.rowBaseStyle = nil
    table.columns = {}
    table.columnWidth = {}
    table.columBaseStyle = nil
    table.headerRowBaseStyle = nil
    table.headerColumnBaseStyle = nil
    table.selectedRow = nil
    table.defaultColumnWidth = 80
    table.sortColumn = -1
    table.sortType = TABLE_SORTING_ASC
    table.autoSort = false

    return table
end

-- Clear table values
function UITable:onDestroy()
    for _, row in pairs(self.rows) do
        row.onClick = nil
    end
    self.rows = {}
    self.columns = {}
    self.headerRow = nil
    self.headerColumns = {}
    self.columnWidth = {}
    self.selectedRow = nil

    if self.dataSpace then
        self.dataSpace:destroyChildren()
        self.dataSpace = nil
    end
end

-- Detect if a header is already defined
function UITable:onSetup()
    local header = self:getChildById('header')
    if header then
        self:setHeader(header)
    end
end

-- Parse table related styles
function UITable:onStyleApply(styleName, styleNode)
    for name, value in pairs(styleNode) do
        if value ~= false then
            if name == 'table-data' then
                addEvent(function()
                    self:setTableData(self:getParent():getChildById(value))
                end)
            elseif name == 'column-style' then
                addEvent(function()
                    self:setColumnStyle(value)
                end)
            elseif name == 'row-style' then
                addEvent(function()
                    self:setRowStyle(value)
                end)
            elseif name == 'header-column-style' then
                addEvent(function()
                    self:setHeaderColumnStyle(value)
                end)
            elseif name == 'header-row-style' then
                addEvent(function()
                    self:setHeaderRowStyle(value)
                end)
            end
        end
    end
end

function UITable:setColumnWidth(width)
    if self:hasHeader() then
        return
    end
    self.columnWidth = width
end

function UITable:setDefaultColumnWidth(width)
    self.defaultColumnWidth = width
end

-- Check if the table has a header
function UITable:hasHeader()
    return self.headerRow ~= nil
end

-- Clear all rows
function UITable:clearData()
    if not self.dataSpace then
        return
    end
    self.dataSpace:destroyChildren()
    self.selectedRow = nil
    self.columns = {}
    self.rows = {}
end

-- Set existing child as header
function UITable:setHeader(headerWidget)
    self:removeHeader()

    if self.dataSpace then
        local newHeight = self.dataSpace:getHeight() - headerRow:getHeight() - self.dataSpace:getMarginTop()
        self.dataSpace:applyStyle({
            height = newHeight
        })
    end

    self.headerColumns = {}
    self.columnWidth = {}
    for colId, column in pairs(headerWidget:getChildren()) do
        column.colId = colId
        column.table = self
        table.insert(self.columnWidth, column:getWidth())
        table.insert(self.headerColumns, column)
    end

    self.headerRow = headerWidget
end

-- Create and add header from table data
function UITable:addHeader(data)
    if not data or type(data) ~= 'table' then
        g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
        return
    end

    self:removeHeader()

    -- build header columns
    local columns = {}
    for colId, column in pairs(data) do
        local col = g_ui.createWidget(self.headerColumnBaseStyle)
        col.colId = colId
        col.table = self
        for type, value in pairs(column) do
            if type == 'width' then
                col:setWidth(value)
            elseif type == 'height' then
                col:setHeight(value)
            elseif type == 'text' then
                col:setText(value)
            elseif type == 'onClick' then
                col.onClick = value
            end
        end
        table.insert(columns, col)
    end

    -- create a new header
    local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
    local newHeight = self.dataSpace:getHeight() - headerRow:getHeight() - self.dataSpace:getMarginTop()
    self.dataSpace:applyStyle({
        height = newHeight
    })

    headerRow:setId('header')
    self.headerColumns = {}
    self.columnWidth = {}
    for _, column in pairs(columns) do
        headerRow:addChild(column)
        table.insert(self.columnWidth, column:getWidth())
        table.insert(self.headerColumns, column)
    end

    self.headerRow = headerRow
    return headerRow
end

-- Remove header
function UITable:removeHeader()
    if self:hasHeader() then
        if self.dataSpace then
            local newHeight = self.dataSpace:getHeight() + self.headerRow:getHeight() + self.dataSpace:getMarginTop()
            self.dataSpace:applyStyle({
                height = newHeight
            })
        end
        self.headerColumns = {}
        self.columnWidth = {}
        self.headerRow:destroy()
        self.headerRow = nil
    end
end

function UITable:addRow(data, height)
    if not self.dataSpace then
        g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
        return
    end
    if not data or type(data) ~= 'table' then
        g_logger.error('UITable:addRow - table columns must be provided in a table.')
        return
    end

    local row = g_ui.createWidget(self.rowBaseStyle)
    row.table = self
    if height then
        row:setHeight(height)
    end

    local rowId = #self.rows + 1
    row.rowId = rowId
    row:setId('row' .. rowId)
    row:updateBackgroundColor()

    self.columns[rowId] = {}
    for colId, column in pairs(data) do
        local col = g_ui.createWidget(column.style or self.columBaseStyle, row)
        if column.id then
          col:setId(column.id)
        end
        if column.width then
            col:setWidth(column.width)
        else
            col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
        end
        if column.height then
            col:setHeight(column.height)
        end
        if column.text then
            col:setText(column.text)
        end
        if column.color then
            col:setColor(column.color)
        end
        if column.coloredText then
            col:parseColoredText(column.coloredText.text, column.coloredText.color)
        end
        if column.sortvalue then
            col.sortvalue = column.sortvalue
        else
            col.sortvalue = column.text or 0
        end
        if column.marginTop then
            col:setMarginTop(column.marginTop)
        end
        if column.marginBottom then
            col:setMarginBottom(column.marginBottom)
        end
        if column.comboBox then
            for _, comboValue in ipairs(column.comboBox) do
                col:addOption(comboValue[1], comboValue[2])
            end
        end

        table.insert(self.columns[rowId], col)
    end

    self.dataSpace:addChild(row)
    table.insert(self.rows, row)

    if self.autoSort then
        self:sort()
    end

    return row
end

-- Update row indices and background color
function UITable:updateRows()
    for rowId = 1, #self.rows do
        local row = self.rows[rowId]
        row.rowId = rowId
        row:setId('row' .. rowId)
        row:updateBackgroundColor()
    end
end

-- Removes the given row widget from the table
function UITable:removeRow(row)
    if self.selectedRow == row then
        self:selectRow(nil)
    end
    row.onClick = nil
    row.table = nil
    table.remove(self.columns, row.rowId)
    table.remove(self.rows, row.rowId)
    self.dataSpace:removeChild(row)
    self:updateRows()
end

function UITable:toggleSorting(enabled)
    self.autoSort = enabled
end

function UITable:setSorting(colId, sortType)
    self.headerColumns[colId]:focus()

    if sortType then
        self.sortType = sortType
    elseif self.sortColumn == colId then
        if self.sortType == TABLE_SORTING_ASC then
            self.sortType = TABLE_SORTING_DESC
        else
            self.sortType = TABLE_SORTING_ASC
        end
    else
        self.sortType = TABLE_SORTING_ASC
    end
    self.sortColumn = colId
end

function UITable:sort()
    if self.sortColumn <= 0 then
        return
    end

    if self.sortType == TABLE_SORTING_ASC then
        table.sort(self.rows, function(rowA, b)
            return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
        end)
    else
        table.sort(self.rows, function(rowA, b)
            return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
        end)
    end

    if self.dataSpace then
        for _, child in pairs(self.dataSpace:getChildren()) do
            self.dataSpace:removeChild(child)
        end
    end

    self:updateRows()
    self.columns = {}
    for _, row in pairs(self.rows) do
        if self.dataSpace then
            self.dataSpace:addChild(row)
        end

        self.columns[row.rowId] = {}
        for _, column in pairs(row:getChildren()) do
            table.insert(self.columns[row.rowId], column)
        end
    end
end

function UITable:selectRow(selectedRow)
    if selectedRow == self.selectedRow then
        return
    end

    local previousSelectedRow = self.selectedRow
    self.selectedRow = selectedRow

    if previousSelectedRow then
        previousSelectedRow:setChecked(false)
    end

    if selectedRow then
        selectedRow:setChecked(true)
    end

    signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow)
end

function UITable:setTableData(tableData)
    local headerHeight = 0
    if self.headerRow then
        headerHeight = self.headerRow:getHeight()
    end

    self.dataSpace = tableData
    self.dataSpace:applyStyle({
        height = self:getHeight() - headerHeight - self:getMarginTop()
    })
end

function UITable:setRowStyle(style, dontUpdate)
    self.rowBaseStyle = style

    if not dontUpdate then
        for _, row in pairs(self.rows) do
            row:setStyle(style)
        end
    end
end

function UITable:setColumnStyle(style, dontUpdate)
    self.columBaseStyle = style

    if not dontUpdate then
        for _, columns in pairs(self.columns) do
            for _, col in pairs(columns) do
                col:setStyle(style)
            end
        end
    end
end

function UITable:setHeaderRowStyle(style)
    self.headerRowBaseStyle = style
    if self.headerRow then
        self.headerRow:setStyle(style)
    end
end

function UITable:setHeaderColumnStyle(style)
    self.headerColumnBaseStyle = style
    for _, col in pairs(self.headerColumns) do
        col:setStyle(style)
    end
end

UITableRow = extends(UIWidget, 'UITableRow')

function UITableRow:onFocusChange(focused)
    if focused then
        if self.table then
            self.table:selectRow(self)
        end
    end
end

function UITableRow:onStyleApply(styleName, styleNode)
    for name, value in pairs(styleNode) do
        if name == 'even-background-color' then
            self.evenBackgroundColor = value
        elseif name == 'odd-background-color' then
            self.oddBackgroundColor = value
        end
    end
end

function UITableRow:updateBackgroundColor()
    self.backgroundColor = nil

    local isEven = (self.rowId % 2 == 0)
    if isEven and self.evenBackgroundColor then
        self.backgroundColor = self.evenBackgroundColor
    elseif not isEven and self.oddBackgroundColor then
        self.backgroundColor = self.oddBackgroundColor
    end

    if self.backgroundColor then
        self:mergeStyle({
            ['background-color'] = self.backgroundColor
        })
    end
end

UITableHeaderColumn = extends(UIButton, 'UITableHeaderColumn')

function UITableHeaderColumn:onClick()
    if self.table then
        self.table:setSorting(self.colId)
        self.table:sort()
    end
end
