-- GridView.lua
-- @Author  : DengSir (tdaddon@163.com)
-- @Link    : https://dengsir.github.io
local MAJOR, MINOR = 'GridView', 7
local GUI = LibStub('tdGUI-1.0')
local GridView = GUI:NewClass(MAJOR, MINOR, 'Frame', 'Refresh', 'View', 'Select', 'Owner', 'Backdrop')
if not GridView then
    return
end

function GridView:Constructor()
    self._buttons = {}
    self:SetSelectMode('NONE')
    self:SetScript('OnShow', self.Refresh)
    self:SetScript('OnSizeChanged', self.OnSizeChanged)

    local scrollBar = CreateFrame('Slider', nil, self, 'UIPanelScrollBarTemplate')
    do
        self.scrollBar = scrollBar
        self.scrollUp = scrollBar.ScrollUpButton
        self.scrollDown = scrollBar.ScrollDownButton

        scrollBar.thumbTexture = scrollBar.ThumbTexture
        scrollBar:SetValueStep(1)
        scrollBar:SetStepsPerPage(1)
        scrollBar:SetPoint('TOPRIGHT', 0, -18)
        scrollBar:SetPoint('BOTTOMRIGHT', 0, 18)
        scrollBar:SetScript('OnValueChanged', function(scrollBar, value)
            HybridScrollFrame_UpdateButtonStates(self, value)
            self:SetOffset(value)
        end)
        scrollBar:SetScript('OnMinMaxChanged', function(scrollBar, minValue, maxValue)
            scrollBar:SetShown(minValue ~= maxValue)
        end)
        scrollBar:SetScript('OnShow', function(scrollBar)
            self:Refresh()
        end)
        scrollBar:SetScript('OnHide', function(scrollBar)
            self:Refresh()
        end)
    end

    self:EnableMouseWheel(true)
    self:SetScript('OnMouseWheel', self.OnMouseWheel)
end

function GridView:SetOffset(value)
    local offset = math.floor(value + 0.5)
    if self._offset ~= offset then
        self._offset = offset
        self:Refresh()
    end
end

function GridView:GetOffset()
    return self._offset or 1
end

function GridView:OnMouseWheel(delta)
    self.scrollBar:SetValue(self.scrollBar:GetValue() - self.scrollBar:GetValueStep() * delta)
end

function GridView:OnSizeChanged()
    self._rowCount = nil
    self:UpdateLayout()
    self:Refresh()
end

function GridView:Update()
    self:UpdateScrollBar()
    self:UpdateItems()
    self:UpdateLayout()
end

function GridView:GetMaxCount()
    return self:GetRowCount() * self:GetColumnCount()
end

function GridView:UpdateScrollBar()
    local maxCount = self:GetMaxCount()
    local itemCount = self:GetItemCount()
    local maxValue = itemCount <= maxCount and 1 or itemCount - maxCount + 1

    self.scrollBar:SetMinMaxValues(1, maxValue)

    if math.floor(self.scrollBar:GetValue() + 0.5) ~= self._offset then
        self.scrollBar:SetValue(self:GetOffset())
    end
end

function GridView:UpdateLayout()
    for i in ipairs(self._buttons) do
        self:UpdateItemPosition(i)
    end
end

function GridView:UpdateItemPosition(i)
    local button = self:GetButton(i)
    local itemSpacingV, itemSpacingH = self:GetItemSpacing()
    local itemHeight = self:GetItemHeight()
    local itemWidth = self:GetItemWidth()
    local lineCount = self:GetColumnCount()
    local left, right, top, bottom = self:GetPadding()

    right = right + self:GetScrollBarFixedWidth()

    button:ClearAllPoints()

    if lineCount == 1 then
        button:SetHeight(itemHeight)
        button:SetPoint('TOPLEFT', left, -top - (i - 1) * (itemHeight + itemSpacingV))
        button:SetPoint('TOPRIGHT', -right, -top - (i - 1) * (itemHeight + itemSpacingV))
    else
        local row = floor((i - 1) / lineCount)
        local col = (i - 1) % lineCount

        button:SetSize(itemWidth, itemHeight)
        button:SetPoint('TOPLEFT', left + col * (itemWidth + itemSpacingH), -top - row * (itemHeight + itemSpacingV))
    end
end

function GridView:UpdateItems()
    if not self:GetRight() then
        return
    end

    local offset = self:GetOffset()
    local maxCount = min(self:GetColumnCount() * self:GetRowCount(), self:GetItemCount())
    local autoWidth = self:GetItemWidth() or 1
    local maxRight = 0

    local column = self:GetColumnCount()
    offset = ceil((offset - 1) / column) * column + 1

    for i = 1, maxCount do
        local button = self:GetButton(i)
        local index = offset + i - 1

        if self:GetItem(index) then
            button:SetID(index)
            button:SetChecked(self:IsSelected(index))
            button:Show()
            button:FireFormat()

            autoWidth = max(autoWidth, button:GetAutoWidth() or 0)
            maxRight = max(maxRight, button:GetRight())
        else
            button:Hide()
        end
    end

    for i = maxCount + 1, #self._buttons do
        self:GetButton(i):Hide()
    end

    if maxCount > 0 and self:IsAutoSize() then
        local left, right, top, bottom = self:GetPadding()
        local height = self:GetTop() - self:GetButton(maxCount):GetBottom() + bottom
        local width = self:GetScrollBarFixedWidth()

        if self:GetColumnCount() == 1 then
            width = width + autoWidth + left + right
        else
            width = width + maxRight - self:GetLeft() + right
        end
        self:SetSize(width, height)
    end
end

function GridView:SetColumnCount(columnCount)
    self._columnCount = columnCount
    self:SetScrollStep(columnCount)
end

function GridView:GetColumnCount()
    return self._columnCount or 1
end

function GridView:SetRowCount(rowCount)
    self._rowCount = rowCount
    self:SetScript('OnSizeChanged', nil)
end

function GridView:GetRowCount()
    if not self._rowCount then
        if self._autoSize then
            return self:GetItemCount()
        else
            local itemHeight = self:GetItemHeight()
            local itemSpacing = self:GetItemSpacing()
            local top, bottom = select(2, self:GetPadding())
            local height = self:GetHeight() - top - bottom + itemSpacing

            self._rowCount = floor(height / (itemHeight + itemSpacing))
        end
    end
    return self._rowCount
end

function GridView:SetAutoSize(_autoSize)
    self._autoSize = _autoSize
    self:SetScript('OnSizeChanged', not autoSize and self.OnSizeChanged or nil)
    self:SetSize(1, 1)
end

function GridView:IsAutoSize()
    return self._autoSize
end

function GridView:GetItemWidth()
    if not self._autoSize then
        local left, right = self:GetPadding()
        local _, itemSpacingH = self:GetItemSpacing()
        local columnCount = self:GetColumnCount()
        local width = self:GetWidth() - left - right - (columnCount - 1) * itemSpacingH - self:GetScrollBarFixedWidth()

        self._itemWidth = width / columnCount

        return self._itemWidth
    end
    return self._itemWidth or 20
end

function GridView:SetItemSpacing(itemSpacingV, itemSpacingH)
    self.itemSpacingV, self.itemSpacingH = itemSpacingV, itemSpacingH
end

function GridView:GetItemSpacing()
    return self.itemSpacingV or 0, self.itemSpacingH or self.itemSpacingV or 0
end

function GridView:GetScrollBarFixedWidth()
    return self.scrollBar:IsShown() and self.scrollBar:GetWidth() or 0
end

function GridView:SetScrollStep(scrollStep)
    self.scrollBar.scrollStep = scrollStep
    self.scrollBar:SetValueStep(scrollStep)
    -- self.scrollBar:SetStepsPerPage(scrollStep)
end

function GridView:GetScrollStep()
    return self.scrollBar.scrollStep
end
