PTGuiComponent = {}
PTUtil.SetEnvironment(PTGuiComponent)
local _G = getfenv(0)
PTGuiComponent.__index = PTGuiComponent

-- A static flag to verify if this is a gui component
PTGuiComponent.IsPuppeteerGui = true

PTGuiComponent.TypeName = "BASE_COMPONENT"

-- The primary frame associated with the set of components
PTGuiComponent.Primary = nil
-- The raw frame associated with the component
PTGuiComponent.Frame = nil
-- All gui component instances associated with the component, including itself(named "Frame")
PTGuiComponent.Components = {}

-- Permanent frames cannot be recycled
PTGuiComponent.Permanent = false

PTGuiComponent.FrameCount = 0

function PTGuiComponent:Extend(typeName)
    local obj = {}
    setmetatable(obj, self)
    obj.TypeName = typeName
    obj.super = self
    obj.__index = obj
    return obj
end

-- Called when a component is acquired
function PTGuiComponent:OnAcquire()
    self:Show()
end

-- Responsible for cleaning up a component, putting it in a default state
function PTGuiComponent:OnDispose()
    self:Hide()
    self:ClearAllPoints()
    self:SetSize(0, 0)
    self:SetParent(nil)
    if self.HasScript and self:HasScript("OnClick") then
        self:OnClick(nil)
    end

    if self.DisposeHandler then
        self:DisposeHandler()
        self.DisposeHandler = nil
    end
end

function PTGuiComponent:Dispose()
    if not self:IsPermanent() then -- Permanent components aren't recycled, but can try to hide themselves
        PTGuiLib.Recycle(self)
    end

    self:OnDispose()
end

-- Set a special handler that is called when this component is being disposed.
-- Should be used for cleaning up things components don't normally clean up.
function PTGuiComponent:SetDisposeHandler(handler)
    self.DisposeHandler = handler
    return self
end

function PTGuiComponent:SetPermanent()
    self.Permanent = true
    return self
end

function PTGuiComponent:IsPermanent()
    return self.Permanent
end

function PTGuiComponent:Type()
    return self.TypeName
end

function PTGuiComponent:GenerateName()
    self.FrameCount = self.FrameCount + 1
    return "PTGui_"..self:Type()..self.FrameCount
end

function PTGuiComponent:SetHandle(frame)
    self.Frame = frame
    self:AddComponent("Frame", self)
    frame.GuiInstance = self
    self:SetPrimary()
    return self
end

function PTGuiComponent:GetHandle()
    return self.Frame
end

function PTGuiComponent:IsInstance()
    return self.Frame ~= nil
end

function PTGuiComponent:IsPrimary()
    return self.Primary == self.Frame
end

function PTGuiComponent:GetPrimary()
    return self.Primary
end

-- Sets this component as the primary for all associated components
function PTGuiComponent:SetPrimary()
    for _, component in pairs(self.Components) do
        component.Primary = self
    end
end

function PTGuiComponent:Import(returnSelf, ...)
    self:ImportComponent(nil, returnSelf, arg)
end

function PTGuiComponent:ImportComponent(frameName, returnSelf, ...)
    frameName = frameName or "Frame"
    for _, import in ipairs(arg) do
        if type(import) == "table" then
            for _, import in ipairs(import) do
                self:DoImport(frameName, returnSelf, import)
            end
        else
            self:DoImport(frameName, returnSelf, import)
        end
    end
end

-- Instance imports are faster and static imports consume less memory.
-- Instance importing must be done after the component is added.
function PTGuiComponent:DoImport(frameName, returnSelf, import)
    if self:IsInstance() then -- Prefer an instance import
        local frame = self:GetComponent(frameName):GetHandle()
        local func = frame[import]
        if returnSelf then
            self[import] = function(self, a1, a2, a3, a4, a5)
                func(frame, a1, a2, a3, a4, a5)
                return self
            end
        else
            self[import] = function(self, a1, a2, a3, a4, a5)
                return func(frame, a1, a2, a3, a4, a5)
            end
        end
    elseif frameName == "Frame" then -- Optimized static import for the handle
        if returnSelf then
            self[import] = function(self, a1, a2, a3, a4, a5)
                local frame = self.Frame
                frame[import](frame, a1, a2, a3, a4, a5)
                return self
            end
        else
            self[import] = function(self, a1, a2, a3, a4, a5)
                local frame = self.Frame
                return frame[import](frame, a1, a2, a3, a4, a5)
            end
        end
    else -- Static import for other components
        if returnSelf then
            self[import] = function(self, a1, a2, a3, a4, a5)
                local frame = self.Components[frameName]:GetHandle()
                frame[import](frame, a1, a2, a3, a4, a5)
                return self
            end
        else
            self[import] = function(self, a1, a2, a3, a4, a5)
                local frame = self.Components[frameName]:GetHandle()
                return frame[import](frame, a1, a2, a3, a4, a5)
            end
        end
    end
end

function PTGuiComponent:CreateGetter(componentName, funcName)
    self[funcName or ("Get"..componentName)] = function(self)
        return self:GetComponent(componentName)
    end
end

function PTGuiComponent:GetContainer()
    return self.Frame
end

function PTGuiComponent:GetAnchor()
    return self.Frame
end

function PTGuiComponent:AddComponent(name, component)
    if self.Components == PTGuiComponent.Components then
        self.Components = {}
    end
    self.Components[name] = component
end

function PTGuiComponent:GetComponent(name)
    return self.Components[name]
end

function PTGuiComponent:GetComponents()
    return self.Components
end

function PTGuiComponent:SetParent(obj)
    if obj and obj.IsPuppeteerGui then
        obj = obj:GetContainer()
    end
    self:GetHandle():SetParent(obj)
    PTUtil.FixFrameLevels(self:GetHandle())
    return self
end

function PTGuiComponent:SetSize(width, height)
    self:SetWidth(width)
    self:SetHeight(height)
    return self
end

function PTGuiComponent:SetScript(scriptName, script, noSelf)
    self:GetHandle():SetScript(scriptName, script and (noSelf and script or function() script(self) end) or nil)
    return self
end

function PTGuiComponent:HookScript(scriptName, script)
    local prevScript = self:GetScript(scriptName)
    self:GetHandle():SetScript(scriptName, function()
        if prevScript then
            prevScript(self)
        end
        script(self)
    end)
    return self
end

function PTGuiComponent:OnClick(script, noSelf)
    return self:SetScript("OnClick", script, noSelf)
end

function PTGuiComponent:SetPoint(point, relativeFrame, relativePoint, xOffset, yOffset)
    if type(relativeFrame) == "table" and relativeFrame.IsPuppeteerGui then
        relativeFrame = relativeFrame:GetAnchor()
    end
    self:GetHandle():SetPoint(point, relativeFrame, relativePoint, xOffset, yOffset)
    return self
end

PTGuiComponent.BACKGROUND_TOOLTIP = 1
PTGuiComponent.BACKGROUND_DIALOG = 2
function PTGuiComponent:SetSimpleBackground(type, diff)
    type = type or PTGuiComponent.BACKGROUND_TOOLTIP
    local background
    if type == PTGuiComponent.BACKGROUND_TOOLTIP then
        background = {
            frameBackdrop = {
                bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
                edgeSize = 11,
                tile = true
            },
            frameBackdropColor = {0.09, 0.09, 0.19},
            frameBorderColor = nil,
            borderBackdrop = {
                edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
                edgeSize = 11,
                tileSize = 11,
                tile = true
            },
            borderBackdropColor = nil,
            borderBorderColor = {0.8, 0.8, 0.8},
            borderPadding = 2
        }
    elseif type == PTGuiComponent.BACKGROUND_DIALOG then
        background = {
            frameBackdrop = {
                bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
                edgeSize = 32,
                tile = true,
                tileSize = 32
            },
            borderBackdrop = {
                edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", 
                edgeSize = 32,
                tile = true,
                tileSize = 32
            },
            borderPadding = 11
        }
    end
    if diff then
        for k, v in pairs(diff) do
            background[k] = v
        end
    end
    return self:SetBackground(background)
end

function PTGuiComponent:SetBackground(params)
    local frame = self:GetHandle()

    frame:SetBackdrop(nil)
    frame:SetBackdropColor()
    frame:SetBackdropBorderColor()
    if frame.guiBorder then
        frame.guiBorder:Hide()
        frame.guiBorder:SetBackdrop(nil)
        frame.guiBorder:SetBackdropColor()
        frame.guiBorder:SetBackdropBorderColor()
    end

    if not params then
        return self
    end

    if params.frameBackdrop then
        frame:SetBackdrop(params.frameBackdrop)
        if params.frameBackdropColor then
            frame:SetBackdropColor(unpack(params.frameBackdropColor))
        end
        if params.frameBorderColor then
            frame:SetBackdropColor(unpack(params.frameBorderColor))
        end
    end

    if params.borderBackdrop then
        local border = frame.guiBorder or CreateFrame("Frame", nil, frame)
        frame.guiBorder = border
        frame.guiBorder:Show()
        local padding = params.borderPadding or 0
        border:SetPoint("TOPLEFT", frame, "TOPLEFT", -padding, padding)
        border:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", padding, -padding)
        border:SetBackdrop(params.borderBackdrop)
        border:SetFrameLevel(frame:GetFrameLevel())
        if params.borderBackdropColor then
            border:SetBackdropColor(unpack(params.borderBackdropColor))
        end
        if params.borderBorderColor then
            border:SetBackdropBorderColor(unpack(params.borderBorderColor))
        end
    end
    return self
end

-- TODO: Handle cleanup of tooltips for disposed components
local infoTooltip = CreateFrame("GameTooltip", "PTGuiInfoTooltip", UIParent, "GameTooltipTemplate")

local singleTextArray = {}
local function ShowTooltip(attachTo, texts)
    infoTooltip:SetOwner(attachTo, "ANCHOR_RIGHT")
    infoTooltip:SetPoint("RIGHT", attachTo, "LEFT", 0, 0)

    if type(texts) == "string" then
        singleTextArray[1] = texts
        texts = singleTextArray
    end

    for i, text in ipairs(texts) do
        if i ~= 1 then
            text = PTUtil.Colorize(text, 1.0, 0.88, 0.3)
        end
        infoTooltip:AddLine(text)
        _G["PTGuiInfoTooltipTextLeft"..i]:SetFont("Fonts\\FRIZQT__.TTF", i == 1 and 14 or 12, "OUTLINE")
    end
    
    infoTooltip:Show()
end

function PTGuiComponent:ApplyTooltip(...)
    self:ApplyTooltipToAll(arg)
    return self
end

function PTGuiComponent:ApplyTooltipTo(component, texts)
    local primary = component:GetPrimary():GetHandle()
    local frame = component:GetHandle()
    frame:EnableMouse(true)
    frame:SetScript("OnEnter", function()
        ShowTooltip(primary, texts)
    end)
    frame:SetScript("OnLeave", function()
        infoTooltip:Hide()
    end)
end

local function flattenTexts(element, t)
    if type(element) == "table" then
        for _, e in ipairs(element) do
            flattenTexts(e, t)
        end
    elseif type(element) == "string" then
        table.insert(t, element)
    end
    return t
end

function PTGuiComponent:ApplyTooltipToAll(texts)
    local primary = self:GetPrimary():GetHandle()
    local texts = flattenTexts(texts, {})
    local onEnter = function()
        ShowTooltip(primary, texts)
    end
    local onLeave = function()
        infoTooltip:Hide()
    end
    for _, component in pairs(self:GetComponents()) do
        local frame = component:GetHandle()
        if frame.EnableMouse and frame.HasScript and frame:HasScript("OnEnter") then
            frame:EnableMouse(true)
            frame:SetScript("OnEnter", onEnter)
            frame:SetScript("OnLeave", onLeave)
        end
    end
end


PTGuiComponent:Import(true, "ClearAllPoints", "SetAllPoints", "SetWidth", "SetHeight", "Show", "Hide", "SetFrameLevel")
PTGuiComponent:Import(false, "GetWidth", "GetHeight", "GetName", "HasScript", "GetScript", "GetParent", "GetFrameLevel", "IsVisible", "IsShown")