PTSpellLine = PTGuiComponent:Extend("puppeteer_spell_line")
local compost = AceLibrary("Compost-2.0")
local util = PTUtil
local RotateTexture = util.RotateTexture
local colorize = util.Colorize
local SearchSpells = util.SearchSpells
local SearchMacros = util.SearchMacros
local SearchItems = util.SearchItems

local bindHelper = PTGuiLib.Get("dropdown", UIParent)

function PTSpellLine:New()
    local obj = setmetatable({}, self)
    local frame = CreateFrame("Frame", self:GenerateName())
    obj:SetHandle(frame)

    local initFunc = function(self, gui)
        self.checked = self.text == gui:GetText()
    end
    local func = function(self, gui)
        gui:SetText(self.text)
        obj:SetBindType(self.type)
    end
    local createTypeEntry = function(type, text, tooltip)
        return {
            type = type,
            text = text,
            tooltipTitle = text,
            tooltipText = tooltip,
            initFunc = initFunc,
            func = func
        }
    end
    local typeDropdown = PTGuiLib.Get("dropdown", frame)
        :SetShowShadow(false)
        :SetPoint("LEFT", frame, "LEFT", 70, 0)
        :SetWidth(70)
        :SetText("Spell")
        :SetOptions({
            createTypeEntry("SPELL", "Spell", "Bind a spell from your spellbook"),
            createTypeEntry("ACTION", "Action", "Bind a special action"),
            createTypeEntry("ITEM", "Item", "Bind an item from your inventory"),
            createTypeEntry("MACRO", "Macro", "Bind one of your macros"),
            createTypeEntry("SCRIPT", "Script", "Run a custom Lua script"),
            createTypeEntry("MULTI", "Multi", "Open an interface with multiple bindings")
        })

    local buttonText = PTGuiLib.Get("text", frame)
        :SetPoint("RIGHT", typeDropdown, "LEFT", -5, 0)
        :SetText("Left")
        :SetWidth(65)
        :SetHeight(30)
        :SetJustifyH("RIGHT")
        :SetNonSpaceWrap(true)

    buttonText:Hide()
    
    local down = PTGuiLib.Get("button", frame)
        :SetPoint("RIGHT", typeDropdown, "LEFT", -3, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Move this binding down", "Hold shift to move to bottom")
        :OnClick(function()
            if obj.ControlHandler then
                obj.ControlHandler:OnLineDown(obj)
            end
        end)
    down.NormalTexture:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
    RotateTexture(down.NormalTexture, 90, 0.75)
    down.PushedTexture:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Down")
    RotateTexture(down.PushedTexture, 90, 0.75)
    down.HighlightTexture:SetTexture("Interface\\Buttons\\UI-Common-MouseHilight")
    down.HighlightTexture:SetTexCoord(0, 1, 0, 1)
    down:SetUseDefaultTextures(true)

    local up = PTGuiLib.Get("button", frame)
        :SetPoint("RIGHT", down, "LEFT", -1, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Move this binding up", "Hold shift to move to top")
        :OnClick(function()
            if obj.ControlHandler then
                obj.ControlHandler:OnLineUp(obj)
            end
        end)
    up.NormalTexture:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
    RotateTexture(up.NormalTexture, -90, 0.75)
    up.PushedTexture:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Down")
    RotateTexture(up.PushedTexture, -90, 0.75)
    up.HighlightTexture:SetTexture("Interface\\Buttons\\UI-Common-MouseHilight")
    up.HighlightTexture:SetTexCoord(0, 1, 0, 1)
    up:SetUseDefaultTextures(true)

    local delete = PTGuiLib.Get("button", frame)
        :SetPoint("RIGHT", up, "LEFT", -6, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Delete this binding")
        :OnClick(function()
            if obj.ControlHandler then
                obj.ControlHandler:OnLineDelete(obj)
            end
        end)
    delete.NormalTexture:SetTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up")
    RotateTexture(delete.NormalTexture, 180, 0.7)
    delete.PushedTexture:SetTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Down")
    RotateTexture(delete.PushedTexture, 180, 0.7)
    delete:SetUseDefaultTextures(true)

    local options = PTGuiLib.Get("button", frame)
        :SetPoint("RIGHT", frame, "RIGHT")
        :SetSize(20, 20)
        :ApplyTooltip("More options for this binding")
        :OnClick(function()
            local TabFrame = PTSettingsGui.TabFrame
            local editor = PTGuiLib.Get("puppeteer_binding_options", TabFrame)
                :SetPoint("CENTER", TabFrame, "CENTER")
            editor:SetBinding(obj.Binding)
            PTSettingsGui.AddOverlayFrame(editor)
            editor:SetDisposeHandler(function()
                PTSettingsGui.PopOverlayFrame()
                obj:Update()
            end)
        end)
    options:SetClickSound(PlaySound, "igMainMenuOption")
    options.NormalTexture:SetTexture("Interface\\ICONS\\INV_Misc_Gear_01")
    options.NormalTexture:SetTexCoord(0, 1, 0, 1)
    options.PushedTexture:SetTexture("Interface\\ICONS\\INV_Misc_Gear_01")
    options.PushedTexture:SetTexCoord(0.1, 0.9, 0.1, 0.9)
    options.HighlightTexture:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
    RotateTexture(options.HighlightTexture, 0, 0.5)
    options:SetUseDefaultTextures(true)

    -- Depending on the selected type, one of these three components is displayed
    local editbox = PTGuiLib.Get("editbox", frame)
        :SetJustifyH("CENTER")
        :SetScript("OnEnterPressed", function(self)
            self:ClearFocus()
        end)
    local dropdown = PTGuiLib.Get("dropdown", frame)
        :SetShowShadow(false)
    local t = getglobal(dropdown:GetName().."Text")
    t:ClearAllPoints()
    t:SetPoint("CENTER", dropdown:GetHandle(), "CENTER")
    t:SetJustifyH("CENTER")
    t:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
    local button = PTGuiLib.Get("button", frame)
    for _, f in ipairs({editbox, dropdown, button}) do
        f:SetPoint("LEFT", typeDropdown, "RIGHT", 5, 0)
        f:SetPoint("RIGHT", options, "LEFT", -5, 0)
        f:SetHeight(20)
        f:Hide()
    end

    obj:AddComponent("Label", buttonText)
    obj:AddComponent("UpButton", up)
    obj:AddComponent("DownButton", down)
    obj:AddComponent("DeleteButton", delete)
    obj:AddComponent("TypeDropdown", typeDropdown)
    obj:AddComponent("OptionsButton", options)
    obj:AddComponent("ContentEditbox", editbox)
    obj:AddComponent("ContentDropdown", dropdown)
    obj:AddComponent("ContentButton", button)
    return obj
end

function PTSpellLine:OnAcquire()
    self.super.OnAcquire(self)

    self:SetMode(PTSpellLine.MODE_LABELED)
end

function PTSpellLine:OnDispose()
    self.super.OnDispose(self)

    self.ControlHandler = nil
end

PTSpellLine:CreateGetter("Label")
PTSpellLine:CreateGetter("UpButton")
PTSpellLine:CreateGetter("DownButton")
PTSpellLine:CreateGetter("DeleteButton")
PTSpellLine:CreateGetter("TypeDropdown")
PTSpellLine:CreateGetter("OptionsButton")
PTSpellLine:CreateGetter("ContentEditbox")
PTSpellLine:CreateGetter("ContentDropdown")
PTSpellLine:CreateGetter("ContentButton")

function PTSpellLine:SetLabel(text)
    self:GetLabel():SetText(text):ApplyTooltip(text)
    return self
end

PTSpellLine.MODE_LABELED = 1
PTSpellLine.MODE_ORDERED = 2
function PTSpellLine:SetMode(mode)
    if mode == PTSpellLine.MODE_LABELED then
        self:GetLabel():Show()
        self:GetUpButton():Hide()
        self:GetDownButton():Hide()
        self:GetDeleteButton():Hide()
    elseif mode == PTSpellLine.MODE_ORDERED then
        self:GetLabel():Hide()
        self:GetUpButton():Show()
        self:GetDownButton():Show()
        self:GetDeleteButton():Show()
    end
    return self
end

function PTSpellLine:SetBindType(bindType)
    self:GetTypeDropdown():SetText(bindType)
    util.ClearTable(self.Binding)
    self.Binding.Type = bindType
    self:Update()
    return self
end

function PTSpellLine:SetBinding(binding)
    self.Binding = binding
    Puppeteer.ExpandBinding(binding)
    self:Update()
    return self
end

-- The handler is expected to have functions OnLineDelete, OnLineUp, OnLineDown
function PTSpellLine:SetControlHandler(handler)
    self.ControlHandler = handler
    return self
end

function PTSpellLine:SetEnabledContent(contentType)
    self:GetContentEditbox():Hide()
    self:GetContentDropdown():Hide()
    self:GetContentButton():Hide()
    if contentType == "editbox" then
        self:GetContentEditbox():Show()
    elseif contentType == "dropdown" then
        self:GetContentDropdown():Show()
    elseif contentType == "button" then
        self:GetContentButton():Show()
    end
end

function PTSpellLine:ApplySearchableEditbox(bindType, searchFunc, searchAtLength)
    local binding = self.Binding
    self:GetContentEditbox():SetScript("OnTextChanged", function(editbox)
        if not editbox.hasFocus then
            return
        end
        binding.Data = self:GetContentEditbox():GetText()
        if not binding.Type then
            binding.Type = bindType
        end
        if string.len(binding.Data) < (searchAtLength or 2) then
            return
        end
        local searchResults = searchFunc(binding.Data)
        if table.getn(searchResults) == 1 and searchResults[1] == binding.Data then
            bindHelper:SetToggleState(false)
            return
        end
        bindHelper:SetSimpleOptions(searchResults, function(option)
            return {
                text = option,
                notCheckable = true,
                func = function(option)
                    binding.Data = option.text
                    self:Update()
                end
            }
        end)
        bindHelper:SetToggleState(false)
        bindHelper:SetToggleState(true, self:GetContentEditbox():GetHandle(), 0, 0)
        bindHelper:SetKeepOpen(true)
    end)
    self:GetContentEditbox():SetScript("OnEditFocusGained", function(self)
        self:HighlightText()
        self.hasFocus = true
    end)
    self:GetContentEditbox():SetScript("OnEditFocusLost", function(self)
        bindHelper:SetToggleState(false)
        self:HighlightText(0, 0)
        self.hasFocus = nil
    end)
    self:GetContentEditbox():SetScript("OnTabPressed", function(editbox)
        if string.len(binding.Data) < (searchAtLength or 2) then
            return
        end
        local searchResults = searchFunc(binding.Data)
        if table.getn(searchResults) > 0 then
            editbox:SetText(searchResults[1])
        end
    end)
end

function PTSpellLine:Update()
    local binding = self.Binding
    if binding.Type == "SPELL" or not binding.Type then
        self:SetEnabledContent("editbox")
        self:GetTypeDropdown():SetText("Spell")
        self:GetContentEditbox():SetText(binding.Data or "")
        self:GetContentEditbox():ApplyTooltip("Enter the name of a spell", "To downrank, suffix '(Rank #)'", "For example, 'Heal(Rank 2)'")
        self:ApplySearchableEditbox("SPELL", SearchSpells, 2)
    elseif binding.Type == "ACTION" then
        self:SetEnabledContent("dropdown")
        self:GetTypeDropdown():SetText("Action")
        self:GetContentDropdown():ApplyTooltip("Choose from a list of special actions")
        self:GetContentDropdown():SetText(binding.Data or "")

        self:GetContentDropdown():SetDynamicOptions(function(addOption, level, args)
            for _, action in ipairs(Puppeteer.ActionBinds) do
                addOption("text", action.Name,
                    "checked", binding.Data == action.Name,
                    "func", args.func,
                    "tooltipTitle", action.Name,
                    "tooltipText", action.Description)
            end
        end, {
            func = function(self, gui)
                binding.Data = self.text
                gui:SetText(self.text)
            end
        })
    elseif binding.Type == "MACRO" then
        self:SetEnabledContent("editbox")
        self:GetTypeDropdown():SetText("Macro")
        self:GetContentEditbox():SetText(binding.Data or "")
        self:GetContentEditbox():ApplyTooltip("Enter the name of one of your macros", 
            "Note that macro binds won't target unless you enable", "\"Target While Casting\" in this binding's settings or globally")
        self:ApplySearchableEditbox("MACRO", SearchMacros, 1)
    elseif binding.Type == "ITEM" then
        self:SetEnabledContent("editbox")
        self:GetTypeDropdown():SetText("Item")
        self:GetContentEditbox():SetText(binding.Data or "")
        self:GetContentEditbox():ApplyTooltip("Enter the name of an item in your bags")
        self:ApplySearchableEditbox("ITEM", SearchItems, 1)
    elseif binding.Type == "SCRIPT" then
        self:SetEnabledContent("button")
        self:GetTypeDropdown():SetText("Script")
        local script = binding.Data or ""
        local info
        local _, lines = nil, 0
        if script ~= "" then
            _, lines = string.gsub(script, "\n", "")
            lines = lines + 1
            info = lines.." Line"..(lines ~= 1 and "s" or "")
        else
            info = "Empty"
        end
        self:GetContentButton():SetText("Edit Script ("..info..")")
        local maxLines = 16
        if lines > maxLines then
            local lineCount = 0
            local pos = 0
            local nl
            while lineCount < maxLines do
                nl = string.find(script, "\n", pos, true)
                if not nl then
                    break
                end
                pos = nl + 1
                lineCount = lineCount + 1
            end
            script = string.sub(script, 1, nl - 1).."\n"..colorize((lines - maxLines).." More...", 0.7, 0.7, 0.7)
        end
        if lines > 0 then
            self:GetContentButton():ApplyTooltip("Script Preview", script)
        else
            self:GetContentButton():ApplyTooltip("Blank Script")
        end
        self:GetContentButton():OnClick(function()
            local TabFrame = PTSettingsGui.TabFrame
            local editor
            editor = PTGuiLib.Get("puppeteer_binding_script_editor", TabFrame)
                :SetPoint("CENTER", TabFrame, "CENTER")
                :SetBinding(binding)
                :SetCallback(function(save, scriptData, nameData)
                    if save then
                        binding.Data = scriptData
                    end
                    editor:Dispose()

                    self:Update()
                end)
            PTSettingsGui.AddOverlayFrame(editor)
            editor:SetDisposeHandler(PTSettingsGui.PopOverlayFrame)

            editor:GetEditbox():SetFocus()
        end, true)
    elseif binding.Type == "MULTI" then
        self:SetEnabledContent("button")
        self:GetTypeDropdown():SetText("Multi")
        local count = (binding.Data and binding.Data.Bindings and table.getn(binding.Data.Bindings)) or 0
        self:GetContentButton():SetText("Edit Bindings ("..count..")")
        local tooltip = {}
        for i = 1, count do
            local subBinding = binding.Data.Bindings[i]
            tooltip[i] = subBinding.Type
            if subBinding.Type ~= "SCRIPT" and subBinding.Type ~= "MULTI" and subBinding.Data then
                tooltip[i] = tooltip[i].." - "..subBinding.Data
            end
        end
        self:GetContentButton():ApplyTooltip("Contents", tooltip)
        self:GetContentButton():OnClick(function()
            local TabFrame = PTSettingsGui.TabFrame
            local editor = PTGuiLib.Get("puppeteer_multi_editor", TabFrame)
                :SetPoint("CENTER", TabFrame, "CENTER")
            editor:SetBinding(binding)
            PTSettingsGui.AddOverlayFrame(editor)
            editor:SetDisposeHandler(function()
                PTSettingsGui.PopOverlayFrame()
                self:Update()
            end)
        end, true)
    end
end

PTGuiLib.RegisterComponent(PTSpellLine)