local OVOICONSEARCH, addon = ...

local AddonData = addon.Data

local frame = CreateFrame(
    "Frame",
    "IconSearchFrame",
    UIParent,
    "BasicFrameTemplateWithInset"
)

frame:SetSize(700, 600)
frame:SetPoint("CENTER")
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")

frame:SetScript("OnDragStart", frame.StartMoving)
frame:SetScript("OnDragStop", frame.StopMovingOrSizing)


frame.title = frame:CreateFontString(
    nil,
    "OVERLAY",
    "GameFontHighlight"
)

frame.title:SetPoint("TOP", 0, -5)
frame.title:SetText("Icon Search")


------------------------------------------------------------
-- Configuration
------------------------------------------------------------

local MAX_RESULTS = 200

local COLUMNS = 4
local CELL_WIDTH = 160
local CELL_HEIGHT = 56


------------------------------------------------------------
-- Search box
------------------------------------------------------------

local searchBox = CreateFrame(
    "EditBox",
    nil,
    frame,
    "InputBoxTemplate"
)

searchBox:SetSize(620, 30)
searchBox:SetPoint("TOP", 0, -40)
searchBox:SetAutoFocus(false)
searchBox:SetTextInsets(8, 8, 0, 0)


------------------------------------------------------------
-- Results scroll frame
------------------------------------------------------------

local scrollFrame = CreateFrame(
    "ScrollFrame",
    nil,
    frame,
    "UIPanelScrollFrameTemplate"
)

scrollFrame:SetPoint("TOPLEFT", 20, -85)
scrollFrame:SetPoint("BOTTOMRIGHT", -40, 20)


local content = CreateFrame(
    "Frame",
    nil,
    scrollFrame
)

content:SetSize(620, 1)

scrollFrame:SetScrollChild(content)


------------------------------------------------------------
-- Result cell pool
------------------------------------------------------------

local resultCells = {}


local function GetResultCell(index)

    if resultCells[index] then
        return resultCells[index]
    end


    local cell = CreateFrame(
        "Button",
        nil,
        content
    )

    cell:SetSize(
        CELL_WIDTH,
        CELL_HEIGHT
    )

    cell:SetHighlightTexture(
        "Interface\\QuestFrame\\UI-QuestTitleHighlight"
    )


    --------------------------------------------------------
    -- Icon
    --------------------------------------------------------

    cell.icon = cell:CreateTexture(
        nil,
        "ARTWORK"
    )

    cell.icon:SetSize(48, 48)

    cell.icon:SetPoint(
        "LEFT",
        4,
        0
    )


    --------------------------------------------------------
    -- Text
    --------------------------------------------------------

    cell.name = cell:CreateFontString(
        nil,
        "OVERLAY",
        "GameFontNormal"
    )

    cell.name:SetPoint(
        "LEFT",
        cell.icon,
        "RIGHT",
        6,
        0
    )

    cell.name:SetWidth(
        CELL_WIDTH - 64
    )

    cell.name:SetJustifyH("LEFT")
    cell.name:SetWordWrap(true)


    resultCells[index] = cell

    return cell

end


------------------------------------------------------------
-- Hide existing cells
------------------------------------------------------------

local function HideAllCells()

    for _, cell in ipairs(resultCells) do
        cell:Hide()
    end

end


------------------------------------------------------------
-- Position a cell in the four-column grid
------------------------------------------------------------

local function PositionCell(cell, index)

    local column =
        (index - 1) % COLUMNS

    local row =
        math.floor((index - 1) / COLUMNS)


    cell:ClearAllPoints()

    cell:SetPoint(
        "TOPLEFT",
        column * CELL_WIDTH,
        -row * CELL_HEIGHT
    )

end


------------------------------------------------------------
-- Set the height of the result area
------------------------------------------------------------

local function SetContentHeight(count)

    local rows = math.ceil(
        count / COLUMNS
    )

    content:SetHeight(
        math.max(
            1,
            rows * CELL_HEIGHT
        )
    )

end


------------------------------------------------------------
-- Get icon IDs from a keyword
------------------------------------------------------------

local function GetIconsForKeyword(keyword)

    local entry =
        AddonData.Keywords[keyword]

    if not entry then
        return nil
    end


    local icons = {}

    for _, value in ipairs(entry) do

        if type(value) == "number" then

            icons[#icons + 1] = value

        end

    end


    return icons

end


------------------------------------------------------------
-- Get keyword suggestions
------------------------------------------------------------

local function GetSuggestionsForKeyword(keyword)

    local entry =
        AddonData.Keywords[keyword]

    if not entry then
        return nil
    end


    local suggestions = {}

    for _, value in ipairs(entry) do

        if type(value) == "string" then

            suggestions[#suggestions + 1] = value

        end

    end


    return suggestions

end


------------------------------------------------------------
-- Parse query
--
-- Examples:
--
--     orange
--
--     +orange +hair
--
--     orange & dragon - helmet
--
--     orange & dragon -helmet
--
-- The first bare term starts an OR group.
--
-- + means AND
-- & means OR
-- - means NOT
------------------------------------------------------------

local function ParseQuery(query)

    local terms = {}

    local currentOperator = nil
    local currentText = ""


    local function AddCurrentTerm()

        local text =
            currentText:match("^%s*(.-)%s*$")


        if text ~= "" then

            terms[#terms + 1] = {
                operator = currentOperator,
                keyword = text
            }

        end


        currentText = ""

    end


    local i = 1


    while i <= #query do

        local char =
            query:sub(i, i)


        if char == "+"
            or char == "&"
            or char == "-" then

            AddCurrentTerm()

            currentOperator = char

        else

            currentText =
                currentText .. char

        end


        i = i + 1

    end


    AddCurrentTerm()


    return terms

end


------------------------------------------------------------
-- Convert a keyword entry to a set of icon IDs
------------------------------------------------------------

local function MakeSet(keyword)

    local entry =
        AddonData.Keywords[keyword]

    if not entry then
        return nil
    end


    local result = {}


    for _, value in ipairs(entry) do

        if type(value) == "number" then

            result[value] = true

        end

    end


    return result

end


------------------------------------------------------------
-- Convert a set to a sorted list
------------------------------------------------------------

local function SetToSortedList(set)

    local result = {}


    for iconID in pairs(set) do

        result[#result + 1] =
            iconID

    end


    table.sort(result)


    return result

end


------------------------------------------------------------
-- Display a keyword suggestion
------------------------------------------------------------

local function DisplaySuggestion(
    index,
    suggestion
)

    local cell =
        GetResultCell(index)


    PositionCell(
        cell,
        index
    )


    cell.icon:SetTexture(nil)

    cell.name:SetPoint(
        "LEFT",
        cell.icon,
        "RIGHT",
        -40,
        0
    )

    cell.name:SetWidth(
        CELL_WIDTH - 10
    )

    cell.name:SetText(
        "|cff88ccff"
        .. suggestion
        .. "|r"
    )


    cell:SetScript(
        "OnClick",
        function()

            searchBox:SetText(
                suggestion
            )

            searchBox:SetCursorPosition(0)

            searchBox:SetFocus()

        end
    )


    cell:SetScript(
        "OnEnter",
        nil
    )

    cell:SetScript(
        "OnLeave",
        nil
    )


    cell:Show()

end


------------------------------------------------------------
-- Display an icon
------------------------------------------------------------

local function DisplayIcon(
    index,
    iconID
)

    local cell =
        GetResultCell(index)


    PositionCell(
        cell,
        index
    )


    local iconName =
        AddonData.Icons[iconID]


    --------------------------------------------------------
    -- Texture
    --------------------------------------------------------

    local texture =
        iconName


    if texture
        and not texture:find("[\\/]") then

        texture =
            "Interface\\Icons\\"
            .. texture

    end


    cell.icon:SetTexture(
        texture
    )


    --------------------------------------------------------
    -- Text
    --------------------------------------------------------

    cell.name:SetPoint(
        "LEFT",
        cell.icon,
        "RIGHT",
        6,
        0
    )

    cell.name:SetWidth(
        CELL_WIDTH - 64
    )

    cell.name:SetText(
        tostring(iconName)
    )


    --------------------------------------------------------
    -- Tooltip
    --------------------------------------------------------

    cell:SetScript(
        "OnEnter",
        function(self)

            GameTooltip:SetOwner(
                self,
                "ANCHOR_RIGHT"
            )


            GameTooltip:SetText(
                iconName or "Unknown"
            )


            GameTooltip:AddLine(
                "ID " .. iconID,
                1,
                1,
                1
            )


            GameTooltip:Show()

        end
    )


    cell:SetScript(
        "OnLeave",
        function()

            GameTooltip:Hide()

        end
    )


    --------------------------------------------------------
    -- Click
    --------------------------------------------------------

    cell:SetScript(
        "OnClick",
        function()

            searchBox:SetText(
                iconName or ""
            )

            searchBox:SetCursorPosition(0)

            searchBox:SetFocus()

        end
    )


    cell:Show()

end


------------------------------------------------------------
-- Search
------------------------------------------------------------

function Search()

    local query =
        searchBox:GetText():lower()


    HideAllCells()


    content:SetHeight(1)


    if query == "" then
        return
    end


    --------------------------------------------------------
    -- Exact keyword match
    --
    -- This is the common case and is extremely fast.
    --------------------------------------------------------

    local exactEntry =
        AddonData.Keywords[query]


    if exactEntry then

        local suggestions = {}
        local icons = {}


        for _, value in ipairs(exactEntry) do

            if type(value) == "string" then

                suggestions[#suggestions + 1] =
                    value

            elseif type(value) == "number" then

                icons[#icons + 1] =
                    value

            end

        end


        ----------------------------------------------------
        -- Suggestions first
        ----------------------------------------------------

        local resultIndex = 0


        for _, suggestion in ipairs(suggestions) do

            resultIndex =
                resultIndex + 1


            if resultIndex > MAX_RESULTS then
                break
            end


            DisplaySuggestion(
                resultIndex,
                suggestion
            )

        end


        ----------------------------------------------------
        -- Icons
        ----------------------------------------------------

        for _, iconID in ipairs(icons) do

            resultIndex =
                resultIndex + 1


            if resultIndex > MAX_RESULTS then
                break
            end


            DisplayIcon(
                resultIndex,
                iconID
            )

        end


        SetContentHeight(
            resultIndex
        )


        return

    end


    --------------------------------------------------------
    -- No exact keyword.
    --
    -- Parse explicit operators.
    --------------------------------------------------------

    local terms =
        ParseQuery(query)


    if #terms == 0 then
        return
    end


    --------------------------------------------------------
    -- Every keyword in a Boolean query must exist.
    --
    -- We deliberately don't scan the entire table looking
    -- for partial matches.
    --------------------------------------------------------

    for _, term in ipairs(terms) do

        if not AddonData.Keywords[
            term.keyword
        ] then

            return

        end

    end


    --------------------------------------------------------
    -- Separate operators.
    --------------------------------------------------------

    local requiredTerms = {}
    local orTerms = {}
    local excludedTerms = {}


    for index, term in ipairs(terms) do

        local keyword =
            term.keyword

        local operator =
            term.operator


        if operator == "-" then

            excludedTerms[#excludedTerms + 1] =
                keyword


        elseif operator == "+" then

            requiredTerms[#requiredTerms + 1] =
                keyword


        elseif operator == "&" then

            orTerms[#orTerms + 1] =
                keyword


        elseif index == 1 then

            orTerms[#orTerms + 1] =
                keyword


        else

            requiredTerms[#requiredTerms + 1] =
                keyword

        end

    end


    --------------------------------------------------------
    -- Construct initial result set.
    --------------------------------------------------------

    local resultSet = nil


    if #orTerms > 0 then

        resultSet = {}


        for _, keyword in ipairs(orTerms) do

            local set =
                MakeSet(keyword)


            for iconID in pairs(set) do

                resultSet[iconID] = true

            end

        end


    elseif #requiredTerms > 0 then

        resultSet =
            MakeSet(
                requiredTerms[1]
            )


        table.remove(
            requiredTerms,
            1
        )

    else

        return

    end


    --------------------------------------------------------
    -- AND
    --------------------------------------------------------

    for _, keyword in ipairs(requiredTerms) do

        local set =
            MakeSet(keyword)


        for iconID in pairs(resultSet) do

            if not set[iconID] then

                resultSet[iconID] =
                    nil

            end

        end

    end


    --------------------------------------------------------
    -- NOT
    --------------------------------------------------------

    for _, keyword in ipairs(excludedTerms) do

        local set =
            MakeSet(keyword)


        for iconID in pairs(set) do

            resultSet[iconID] =
                nil

        end

    end


    --------------------------------------------------------
    -- Sort final results
    --------------------------------------------------------

    local matchingIcons =
        SetToSortedList(resultSet)


    --------------------------------------------------------
    -- Display final results
    --------------------------------------------------------

    local count =
        math.min(
            #matchingIcons,
            MAX_RESULTS
        )


    for i = 1, count do

        DisplayIcon(
            i,
            matchingIcons[i]
        )

    end


    SetContentHeight(count)

end


------------------------------------------------------------
-- Search while typing
------------------------------------------------------------

searchBox:SetScript(
    "OnTextChanged",
    function()

        Search()

    end
)


------------------------------------------------------------
-- Enter
------------------------------------------------------------

searchBox:SetScript(
    "OnEnterPressed",
    function()

        Search()

    end
)


------------------------------------------------------------
-- Slash command
------------------------------------------------------------

SLASH_ICONSEARCH1 = "/iconsearch"

SlashCmdList["ICONSEARCH"] = function()

    frame:Show()

    searchBox:SetFocus()

end


------------------------------------------------------------
-- Escape closes the window
------------------------------------------------------------

table.insert(
    UISpecialFrames,
    "IconSearchFrame"
)


------------------------------------------------------------
-- Start hidden
------------------------------------------------------------

frame:Hide()
