PTButtonEditor = PTGuiComponent:Extend("puppeteer_button_editor")
local util = PTUtil
local RotateTexture = util.RotateTexture

function PTButtonEditor:New()
    local obj = setmetatable({}, self)
    local frame = CreateFrame("Frame", self:GenerateName())
    obj:SetHandle(frame)
    obj:SetSimpleBackground(PTGuiComponent.BACKGROUND_DIALOG)

    local title = PTGuiLib.Get("title", frame)
        :SetSize(192, 36)
        :SetPoint("CENTER", frame, "TOP")
        :SetText("Edit Buttons")
    obj:AddComponent("Title", title)

    local scrollFrame = PTGuiLib.Get("scroll_frame", frame)
        :SetPoint("TOPLEFT", frame, "TOPLEFT", 5, -50)
        :SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -5, 100)
        :SetSimpleBackground()
    obj:AddComponent("ScrollFrame", scrollFrame)

    local controlsLabel = PTGuiLib.Get("text", frame)
        :SetText("Controls")
        :SetWidth(66)
        :SetHeight(30)
        :SetPoint("TOPLEFT", scrollFrame:GetHandle(), "TOPLEFT", 5, 33)
        :SetFontSize(14)
        :SetFontFlags("OUTLINE")
        :SetJustifyV("BOTTOM")
    local buttonIDLabel = PTGuiLib.Get("text", frame)
        :SetText("Button ID")
        :SetWidth(100)
        :SetHeight(30)
        :SetPoint("LEFT", controlsLabel, "RIGHT", 0, 0)
        :SetFontSize(14)
        :SetFontFlags("OUTLINE")
        :SetJustifyV("BOTTOM")
    local customNameLabel = PTGuiLib.Get("text", frame)
        :SetText("Custom Name")
        :SetWidth(130)
        :SetHeight(30)
        :SetPoint("LEFT", buttonIDLabel, "RIGHT", 0, 0)
        :SetFontSize(14)
        :SetFontFlags("OUTLINE")
        :SetJustifyV("BOTTOM")
    local showUnboundLabel = PTGuiLib.Get("text", frame)
        :SetText("Show Unbound")
        :SetWidth(82)
        :SetHeight(30)
        :SetPoint("LEFT", customNameLabel, "RIGHT", 0, 0)
        :SetFontSize(14)
        :SetFontFlags("OUTLINE")
        :SetJustifyV("BOTTOM")

    local done = PTGuiLib.Get("button", frame)
        :SetPoint("BOTTOM", frame, "BOTTOM", 0, 5)
        :SetText("Done")
        :SetSize(200, 20)
        :OnClick(function()
            local Buttons = PTOptions.Buttons
            local buttonInfo = PTOptions.ButtonInfo
            PTUtil.ClearTable(Buttons)
            for _, line in ipairs(obj.Lines) do
                local button = line.Button
                table.insert(Buttons, button)
                if not buttonInfo[button] then
                    buttonInfo[button] = {}
                end
                local info = buttonInfo[button]
                info.Name = line:GetCustomButtonName()
                info.ShowUnbound = line:IsUnboundShown()
            end
            PTSettingsGui.ReloadBindingLines()
            PTSettingsGui.UpdateBindingsInterface()
            Puppeteer.InitBindingDisplayCache()
            Puppeteer.InitOverrideBindingsMapping()
            obj:Dispose()
            PTSettingsGui.PopOverlayFrame()
        end)

    local addButton = PTGuiLib.Get("button", frame)
        :SetPoint("BOTTOM", done, "TOP", 0, 10)
        :SetSize(200, 50)
        :SetText("Add a Button")
        :OnClick(function(self)
            if not obj.AddMode then
                obj:SetAddMode(true)
            end
        end)

    addButton:HookScript("OnMouseUp", function(self)
        if not obj.AddMode then
            return
        end
        if not obj:ContainsButton(arg1) then
            obj:AddButtonLine(arg1, arg1)
            PlaySound("igSpellBookOpen")
        end
        obj:SetAddMode(false)
        local onClick = addButton:GetScript("OnClick")
        addButton:SetScript("OnClick", nil)
        PTUtil.RunLater(function()
            addButton:SetScript("OnClick", onClick, true)
        end)
    end)
    addButton:SetScript("OnKeyUp", function(self)
        if not obj.AddMode then
            return
        end
        if arg1 == "ESCAPE" then
            obj:SetAddMode(false)
            return
        end
        if not obj:ContainsButton(arg1) then
            if not obj:AreBindingsFull() then
                obj:AddButtonLine(arg1, arg1)
                PlaySound("igSpellBookOpen")
            else
                DEFAULT_CHAT_FRAME:AddMessage("You cannot add any more non-mouse buttons!")
            end
        end
        obj:SetAddMode(false)
    end)
    addButton:SetScript("OnMouseWheel", function(self)
        if not obj.AddMode then
            return
        end
        if arg1 > 0 then
            if not obj:ContainsButton("MOUSEWHEELUP") then
                if not obj:AreBindingsFull() then
                    obj:AddButtonLine("MOUSEWHEELUP", "MOUSEWHEELUP")
                    PlaySound("igSpellBookOpen")
                else
                    DEFAULT_CHAT_FRAME:AddMessage("You cannot add any more non-mouse buttons!")
                end
            end
        elseif arg1 < 0 then
            if not obj:ContainsButton("MOUSEWHEELDOWN") then
                if not obj:AreBindingsFull() then
                    obj:AddButtonLine("MOUSEWHEELDOWN", "MOUSEWHEELDOWN")
                    PlaySound("igSpellBookOpen")
                else
                    DEFAULT_CHAT_FRAME:AddMessage("You cannot add any more non-mouse buttons!")
                end
            end
        end
        obj:SetAddMode(false)
    end)
    obj:AddComponent("AddButton", addButton)
    obj:SetAddMode(false)

    obj.Lines = {}
    obj.LineMap = {}
    return obj
end

function PTButtonEditor:OnAcquire()
    self.super.OnAcquire(self)
    self:SetSize(425, 400)
    self:GetScrollFrame():FixNextUpdate()
    for _, button in ipairs(PTOptions.Buttons) do
        self:AddButtonLine(button, button)
    end
end

function PTButtonEditor:OnDispose()
    self.super.OnDispose(self)
    self:ClearButtonLines()
    self:SetAddMode(false)
end

PTButtonEditor:CreateGetter("ScrollFrame")
PTButtonEditor:CreateGetter("AddButton")

function PTButtonEditor:SetAddMode(addMode)
    self.AddMode = addMode
    self:GetAddButton():GetHandle():EnableKeyboard(self.AddMode)
    self:GetAddButton():GetHandle():EnableMouseWheel(self.AddMode)
    self:GetAddButton():SetText(self.AddMode and "Press any button, key,\nor mouse wheel to bind\nPress Esc to Cancel" 
        or "Left click to start\nbinding a button")
end

function PTButtonEditor:ContainsButton(button)
    return self.LineMap[button] ~= nil
end

function PTButtonEditor:GetNumUsedBindings()
    local nonBindingButtons = util.GetAllButtonsSet()
    local used = 0
    for button, _ in pairs(self.LineMap) do
        if not nonBindingButtons[button] then
            used = used + 1
        end
    end
    return used
end

function PTButtonEditor:AreBindingsFull()
    return self:GetNumUsedBindings() >= 24
end

function PTButtonEditor:SwapIndexes(index1, index2)
    self.Lines[index1], self.Lines[index2] = self.Lines[index2], self.Lines[index1]
    self:UpdateLinePositions()
end

function PTButtonEditor:AddButtonLine(id, button)
    local line = PTGuiLib.Get("puppeteer_button_line", self:GetScrollFrame())
            :SetSize(380, 25)
            :SetButton(button)
            :SetControlHandler(self)

    table.insert(self.Lines, line)
    if id then
        self.LineMap[id] = line
    end

    self:UpdateLinePositions()
    return line
end

function PTButtonEditor:RemoveButtonLine(line)
    for id, l in pairs(self.LineMap) do
        if line == l then
            self.LineMap[id] = nil
        end
    end
    PTUtil.RemoveElement(self.Lines, line)
    line:Dispose()

    self:UpdateLinePositions()
end

function PTButtonEditor:ClearButtonLines()
    for _, line in ipairs(self.Lines) do
        line:Dispose()
    end
    PTUtil.ClearTable(self.Lines)
    PTUtil.ClearTable(self.LineMap)
end

function PTButtonEditor:UpdateLinePositions()
    local INTERVAL = 26
    for i, line in ipairs(self.Lines) do
        line:SetPoint("TOPLEFT", self:GetScrollFrame(), "TOPLEFT", 5, -5 - (INTERVAL * (i - 1)))
    end
    self:GetScrollFrame():UpdateScrollChildRect()
end

-- Control handler functions

function PTButtonEditor:OnLineDelete(line)
    self:RemoveButtonLine(line)
end

function PTButtonEditor:OnLineUp(line)
    local index = PTUtil.IndexOf(self.Lines, line)
    if IsShiftKeyDown() then
        for i = index, 2, -1 do
            self:SwapIndexes(i, i - 1)
        end
    elseif index > 1 and table.getn(self.Lines) > 1 then
        self:SwapIndexes(index, index - 1)
    end
end

function PTButtonEditor:OnLineDown(line)
    local index = PTUtil.IndexOf(self.Lines, line)
    local lineCount = table.getn(self.Lines)
    if IsShiftKeyDown() then
        for i = index, lineCount - 1 do
            self:SwapIndexes(i, i + 1)
        end
    elseif index < table.getn(self.Lines) then
        self:SwapIndexes(index, index + 1)
    end
end

-- End of control handler functions

PTGuiLib.RegisterComponent(PTButtonEditor)

-- Button line component

PTButtonLine = PTGuiComponent:Extend("puppeteer_button_line")

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

    local delete = PTGuiLib.Get("button", frame)
        :SetPoint("LEFT", frame, "LEFT", 0, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Delete this button")
        :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 up = PTGuiLib.Get("button", frame)
        :SetPoint("LEFT", delete, "RIGHT", 6, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Move this button 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 down = PTGuiLib.Get("button", frame)
        :SetPoint("LEFT", up, "RIGHT", 1, 0)
        :SetSize(20, 20)
        :ApplyTooltip("Move this button down", "Hold shift to move to bottom")
        :OnClick(function()
            if obj.ControlHandler then
                obj.ControlHandler:OnLineDown(obj)
            end
        end, true)
    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 buttonNameLabel = PTGuiLib.Get("text", frame)
        :SetPoint("LEFT", down, "RIGHT", 5, 0)
        :SetText("Left")
        :SetWidth(90)
        :SetHeight(30)
        :SetJustifyH("RIGHT")
        :SetNonSpaceWrap(true)
    obj:AddComponent("ButtonNameLabel", buttonNameLabel)

    local nameEditbox = PTGuiLib.Get("editbox", frame)
        :SetPoint("LEFT", buttonNameLabel, "RIGHT", 5, 0)
        :SetSize(130, 20)
        :SetScript("OnEnterPressed", function(self)
            self:ClearFocus()
        end)
        :ApplyTooltip("This name will be shown on the binding tooltip")
    obj:AddComponent("NameEditbox", nameEditbox)
    
    local showUnboundCheckbox = PTGuiLib.Get("checkbox", frame)
        :SetPoint("LEFT", nameEditbox, "RIGHT", 30, 0)
        :SetSize(20, 20)
        :ApplyTooltip("If enabled, the binding tooltip will show this key when unbound")
    obj:AddComponent("ShowUnboundCheckbox", showUnboundCheckbox)


    return obj
end

function PTButtonLine:OnAcquire()
    self.super.OnAcquire(self)
    self:SetSize(380, 25)
end

function PTButtonLine:OnDispose()
    self.super.OnDispose(self)
    self.Button = nil
end

PTButtonLine:CreateGetter("ButtonNameLabel")
PTButtonLine:CreateGetter("NameEditbox")
PTButtonLine:CreateGetter("ShowUnboundCheckbox")

function PTButtonLine:SetButton(button)
    self.Button = button
    self:GetButtonNameLabel():SetText(util.GetButtonName(button))
    local buttonInfo = PTOptions.ButtonInfo
    if not buttonInfo[button] then
        buttonInfo[button] = {Name = util.GetButtonName(button), ShowUnbound = true}
    end
    self:GetNameEditbox():SetText(buttonInfo[button].Name or util.GetButtonName(button))
    self:GetShowUnboundCheckbox():SetChecked(buttonInfo[button].ShowUnbound)
    return self
end

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

function PTButtonLine:GetCustomButtonName()
    local text = self:GetNameEditbox():GetText()
    if text == "" then
        text = util.GetButtonName(self.Button)
    end
    return text
end

function PTButtonLine:IsUnboundShown()
    return self:GetShowUnboundCheckbox():GetChecked() == 1
end

PTGuiLib.RegisterComponent(PTButtonLine)