--Pointer Box
-------------------

ZO_POINTER_BOX_ARROW_SIZE = 64
ZO_POINTER_BOX_ARROW_CENTER_TO_TIP_DISTANCE = 15
ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE = 2
ZO_POINTER_BOX_TRANSLATE_DISTANCE = 60
ZO_POINTER_BOX_ANIMATION_DURATION_MS = 350
ZO_POINTER_BOX_DEFAULT_PADDING = 20

ZO_PointerBox_Keyboard = ZO_Object:Subclass()

function ZO_PointerBox_Keyboard:New(...)
    local object = ZO_Object.New(self)
    object:Initialize(...)
    return object
end

function ZO_PointerBox_Keyboard:Initialize(control)
    self.control = control
    self.showAnimationTimeline = ANIMATION_MANAGER:CreateTimelineFromVirtual("ZO_PointerBox_KeyboardShow", self.control)
    self.showAnimationTimeline:SetHandler("OnStop", function(timeline, completedPlaying)
        if completedPlaying and timeline:IsPlayingBackward() then
            self.control:SetHidden(true)
            self.contentsControl:SetHidden(true)

            if self.onHiddenCallback then
                self:onHiddenCallback()
            end

            if self.releaseOnHidden then
                self:Release()
            end
        end
    end)
    
    control:SetHandler("OnMouseUp", function(control, button, upInside)
        if upInside and self.closeable then
            self:Hide()
        end
    end)

    self:Reset()
end

function ZO_PointerBox_Keyboard:GetPoolKey()
    return self.poolKey
end

function ZO_PointerBox_Keyboard:SetPoolKey(poolKey)
    self.poolKey = poolKey
end

-- Hiding can release the object (resetting it), so we should never rely on Reset to hide the object
function ZO_PointerBox_Keyboard:Reset()
    self.poolKey = nil
    self.tutorialIndex = nil
    self.tutorialTrigger = nil
    self.padding = ZO_POINTER_BOX_DEFAULT_PADDING
    self.closeable = true
    self.contentsControl = nil
    self.releaseOnHidden = false
    self.onHiddenCallback = nil
    if self.hideWithFragment then
        self.hideWithFragment:UnregisterCallback("StateChange", self.hideWithFragmentFunction)
        self.hideWithFragment = nil
        self.hideWithFragmentFunction = nil
    end
    self:ClearAnchors()
end

function ZO_PointerBox_Keyboard:SetCloseable(closeable)
    self.closeable = closeable
end

function ZO_PointerBox_Keyboard:SetContentsControl(contentsControl)
    self.contentsControl = contentsControl
    self.contentsControl:SetParent(self.control)
    local translateAnimation = self.showAnimationTimeline:GetAnimation(2)
    translateAnimation:SetAnimatedControl(contentsControl)
end

function ZO_PointerBox_Keyboard:SetParent(parent)
    self.control:SetParent(parent)
end

function ZO_PointerBox_Keyboard:SetHideWithFragment(fragment)
    local function OnFragmentStateChange(oldState, newState)
        if newState == SCENE_FRAGMENT_HIDING then
            self:Hide()
        end
    end
    fragment:RegisterCallback("StateChange", OnFragmentStateChange)

    self.hideWithFragment = fragment
    self.hideWithFragmentFunction = OnFragmentStateChange
end

function ZO_PointerBox_Keyboard:SetPadding(padding)
    self.padding = padding
end

--Anchored from the arrow tip
function ZO_PointerBox_Keyboard:SetAnchor(point, relativeTo, relativePoint, offsetX, offsetY)
    self.point = point
    self.relativeTo = relativeTo
    self.relativePoint = relativePoint
    self.offsetX = offsetX
    self.offsetY = offsetY
end

function ZO_PointerBox_Keyboard:ClearAnchors()
    self.point = nil
    self.relativeTo = nil
    self.relativePoint = nil
    self.offsetX = nil
    self.offsetY = nil
end

function ZO_PointerBox_Keyboard:SetReleaseOnHidden(releaseOnHidden)
    self.releaseOnHidden = releaseOnHidden
end

function ZO_PointerBox_Keyboard:SetOnHiddenCallback(callback)
    self.onHiddenCallback = callback
end

function ZO_PointerBox_Keyboard:GetTutorialIndex()
    return self.tutorialIndex
end

function ZO_PointerBox_Keyboard:GetTutorialTrigger()
    return self.tutorialTrigger
end

function ZO_PointerBox_Keyboard:SetTutorialIndex(tutorialIndex)
    self.tutorialIndex = tutorialIndex
    self.tutorialTrigger = GetTutorialTrigger(tutorialIndex)
end

function ZO_PointerBox_Keyboard:Commit()
    self:RefreshLayout()
    self:RefreshArrow()
end

function ZO_PointerBox_Keyboard:RefreshLayout()
    if self.contentsControl then
        self.control:ClearAnchors()
        self.control:SetAnchor(TOPLEFT, self.contentsControl, TOPLEFT, -self.padding, -self.padding)
        self.control:SetAnchor(BOTTOMRIGHT, self.contentsControl, BOTTOMRIGHT, self.padding, self.padding)
    end
end

function ZO_PointerBox_Keyboard:RefreshArrow()
    if self.point then
        local arrowTexture = self.control:GetNamedChild("Arrow")
        arrowTexture:ClearAnchors()

        if self.point == LEFT then
            arrowTexture:SetAnchor(CENTER, nil, LEFT, -ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE, 0)
            arrowTexture:SetTextureCoordsRotation(0)
        elseif self.point == RIGHT then
            arrowTexture:SetAnchor(CENTER, nil, RIGHT, ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE, 0)
            arrowTexture:SetTextureCoordsRotation(ZO_PI)
        elseif self.point == TOP then
            arrowTexture:SetAnchor(CENTER, nil, TOP, 0, -ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE)
            arrowTexture:SetTextureCoordsRotation(1.5 * ZO_PI)
        else
            arrowTexture:SetAnchor(CENTER, nil, BOTTOM, 0, ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE)
            arrowTexture:SetTextureCoordsRotation(ZO_HALF_PI)
        end
    end
end

function ZO_PointerBox_Keyboard:RefreshAnchor()
    if self.contentsControl and self.point then
        self.contentsControl:ClearAnchors()

        local anchorPoint, anchorOffsetX, anchorOffsetY
        if self.point == LEFT or self.point == RIGHT then
            local boxCenterY = self.control:GetHeight() * 0.5
            local contentsCenterY = self.contentsControl:GetHeight() * 0.5 + self.padding
            --The arrow is anchored to the box, but we anchor the contents control. This is the difference between the Y centers of the two to compensate for that
            local offsetY = contentsCenterY - boxCenterY

            if self.point == LEFT then
                --Assumes that relative point is one of the right side anchors
                anchorPoint = LEFT
                anchorOffsetX = self.offsetX + ZO_POINTER_BOX_ARROW_CENTER_TO_TIP_DISTANCE + ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE + self.padding
                anchorOffsetY = offsetY + self.offsetY
            else
                 --Assumes that relative point is one of the left side anchors
                anchorPoint = RIGHT
                anchorOffsetX = self.offsetX - (ZO_POINTER_BOX_ARROW_CENTER_TO_TIP_DISTANCE + ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE + self.padding)
                anchorOffsetY = offsetY + self.offsetY
            end
        elseif self.point == TOP or self.point == BOTTOM then
            local boxCenterX = self.control:GetWidth() * 0.5
            local contentsCenterX = self.contentsControl:GetWidth() * 0.5 + self.padding
            --The arrow is anchored to the box, but we anchor the contents control. This is the difference between the X centers of the two to compensate for that
            local offsetX = contentsCenterX - boxCenterX

            if self.point == TOP then
               --Assumes that relative point is one of the bottom side anchors
                anchorPoint = TOP
                anchorOffsetX = offsetX + self.offsetX
                anchorOffsetY = self.offsetY + ZO_POINTER_BOX_ARROW_CENTER_TO_TIP_DISTANCE + ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE + self.padding
            else
                --Assumes that relative point is one of the top side anchors
                anchorPoint = BOTTOM
                anchorOffsetX = offsetX + self.offsetX
                anchorOffsetY = self.offsetY - (ZO_POINTER_BOX_ARROW_CENTER_TO_TIP_DISTANCE + ZO_POINTER_BOX_ARROW_CENTER_OFFSET_FROM_BOX_SIDE + self.padding)
            end
        end

        if self.relativeTo and self.relativeTo:GetSpace() ~= self.contentsControl:GetSpace() then
            -- Anchoring to a relative control in non-interface space.
            -- Assumes self.contentsControl and self.relativeTo positions are static.
            if internalassert(self.contentsControl:GetSpace() == SPACE_INTERFACE, "Non-interface space PointerBox contents controls are not supported.") then
                local anchorToControl = self.relativeTo or self.contentsControl:GetParent()
                local anchorToX, anchorToY = anchorToControl:ProjectRectToScreenAndComputeAABBPoint(self.relativePoint)
                self.contentsControl:SetAnchor(anchorPoint, GuiRoot, TOPLEFT, anchorToX + anchorOffsetX, anchorToY + anchorOffsetY)
            end
        else
            -- Anchoring for interface space controls.
            self.contentsControl:SetAnchor(anchorPoint, self.relativeTo, self.relativePoint, anchorOffsetX, anchorOffsetY)
        end
    end
end

function ZO_PointerBox_Keyboard:Show(skipAnimation)
    if self.contentsControl then
        if self.contentsControl:IsHidden() == true then
            self.control:SetHidden(false)
            self.contentsControl:SetHidden(false)

            --Reset the anchor to the starting position on show
            self:RefreshAnchor()

            local translateAnimation = self.showAnimationTimeline:GetAnimation(2)
            local offsetX = 0
            local offsetY = 0
            if self.point == LEFT then
                offsetX = -ZO_POINTER_BOX_TRANSLATE_DISTANCE
            elseif self.point == RIGHT then
                offsetX = ZO_POINTER_BOX_TRANSLATE_DISTANCE
            elseif self.point == TOP then
                offsetY = -ZO_POINTER_BOX_TRANSLATE_DISTANCE
            else
                offsetY = ZO_POINTER_BOX_TRANSLATE_DISTANCE
            end
            translateAnimation:SetDeltaOffsetX(offsetX, TRANSLATE_ANIMATION_DELTA_TYPE_FROM_END)
            translateAnimation:SetDeltaOffsetY(offsetY, TRANSLATE_ANIMATION_DELTA_TYPE_FROM_END)
        end
        if skipAnimation then
            self.showAnimationTimeline:PlayInstantlyToEnd()
        else
            self.showAnimationTimeline:PlayForward()
        end
    end
end

function ZO_PointerBox_Keyboard:Hide(skipAnimation)
    if self.contentsControl then
        if skipAnimation then
            self.showAnimationTimeline:PlayInstantlyToStart()
        else
            self.showAnimationTimeline:PlayBackward()
        end
    end
end

function ZO_PointerBox_Keyboard:Release()
    POINTER_BOXES:Release(self)
end

--Pointer Box Manager
---------------------------

ZO_PointerBoxManager = ZO_Object:Subclass()

function ZO_PointerBoxManager:New(...)
    local object = ZO_Object.New(self)
    object:Initialize(...)
    return object
end

function ZO_PointerBoxManager:Initialize()
    local function Factory(pool)
        local control = ZO_ObjectPool_CreateNamedControl("ZO_PointerBox_KeyboardControl", "ZO_PointerBox_KeyboardControl", pool, GuiRoot)
        return ZO_PointerBox_Keyboard:New(control)
    end
    self.pool = ZO_ObjectPool:New(Factory, ZO_ObjectPool_DefaultResetObject)
end

function ZO_PointerBoxManager:Acquire()
    local pointerBox, poolKey = self.pool:AcquireObject()
    pointerBox:SetPoolKey(poolKey)
    return pointerBox
end

function ZO_PointerBoxManager:Release(pointerBox)
    self.pool:ReleaseObject(pointerBox:GetPoolKey())
end

function ZO_PointerBoxManager:GetActiveObjects()
    return self.pool:GetActiveObjects()
end

POINTER_BOXES = ZO_PointerBoxManager:New()