local POINT = 1
local TARGET = 2
local REL_POINT = 3
local OFFS_X = 4
local OFFS_Y = 5
local CONSTRAINTS = 6

ZO_OPPOSING_ANCHOR_POINTS =
{
    [BOTTOM] = TOP,
    [BOTTOMLEFT] = TOPRIGHT,
    [BOTTOMRIGHT] = TOPLEFT,
    [CENTER] = CENTER,
    [LEFT] = RIGHT,
    [RIGHT] = LEFT,
    [TOP] = BOTTOM,
    [TOPLEFT] = BOTTOMRIGHT,
    [TOPRIGHT] = BOTTOMLEFT,
}

function ZO_GetOpposingAnchorPoint(anchorPoint)
    return ZO_OPPOSING_ANCHOR_POINTS[anchorPoint] or NONE
end

function ZO_GetAnchorPointNearestScreenCenter(controlCenterX, controlCenterY)
    local screenCenterX, screenCenterY = GuiRoot:GetCenter()
    local screenOffsetX = controlCenterX / screenCenterX
    local screenOffsetY = controlCenterY / screenCenterY

    local strongHorizontalAnchor, weakHorizontalAnchor
    if screenOffsetX < 0.5 then
        weakHorizontalAnchor = RIGHT
        strongHorizontalAnchor = screenOffsetX < 0.45 and RIGHT or NONE
    else
        weakHorizontalAnchor = LEFT
        strongHorizontalAnchor = screenOffsetX > 0.55 and LEFT or NONE
    end

    local strongVerticalAnchor, weakVerticalAnchor
    if screenOffsetY < 0.5 then
        weakVerticalAnchor = BOTTOM
        strongVerticalAnchor = screenOffsetY < 0.45 and BOTTOM or NONE
    else
        weakVerticalAnchor = TOP
        strongVerticalAnchor = screenOffsetY > 0.55 and TOP or NONE
    end

    local strongAnchor = strongHorizontalAnchor + strongVerticalAnchor
    if strongAnchor ~= NONE then
        return strongAnchor
    end

    local weakAnchor = weakHorizontalAnchor + weakVerticalAnchor
    return weakAnchor
end

ZO_Anchor = ZO_Object:Subclass ()

function ZO_Anchor:New(pointOnMe, target, pointOnTarget, offsetX, offsetY, constraints)
    local a = ZO_Object.New(self)
    
    if type(pointOnMe) == "table" then
        local copy = pointOnMe.data
        a.data = { copy[POINT], copy[TARGET], copy[REL_POINT], copy[OFFS_X], copy[OFFS_Y], copy[CONSTRAINTS] }
    else
        a.data = { pointOnMe or TOPLEFT, target, pointOnTarget or a[POINT], offsetX or 0, offsetY or 0, constraints or ANCHOR_CONSTRAINS_XY }
    end
    
    return a
end

function ZO_Anchor:ResetToAnchor(anchorObj)
    self.data[POINT]     = anchorObj.data[POINT] or TOPLEFT
    self.data[TARGET]    = anchorObj.data[TARGET]
    self.data[REL_POINT] = anchorObj.data[REL_POINT] or self.data[POINT]
    self.data[OFFS_X]    = anchorObj.data[OFFS_X] or 0
    self.data[OFFS_Y]    = anchorObj.data[OFFS_Y] or 0
    self.data[CONSTRAINTS] = anchorObj.data[CONSTRAINTS] or ANCHOR_CONSTRAINS_XY
end

function ZO_Anchor:SetFromControlAnchor(control, anchorIndex)
    local isValid, point, relTo, relPoint, offsX, offsY, constraints = control:GetAnchor(anchorIndex)
    
    if isValid then
        local data = self.data
        data[POINT] = point
        data[TARGET] = relTo
        data[REL_POINT] = relPoint
        data[OFFS_X] = offsX
        data[OFFS_Y] = offsY
        data[CONSTRAINTS] = constraints
    end
end

function ZO_Anchor:GetTarget()
    return self.data[TARGET]
end

function ZO_Anchor:SetTarget(control)
    self.data[TARGET] = control
end

function ZO_Anchor:GetMyPoint()
    return self.data[POINT]
end

function ZO_Anchor:SetMyPoint(myPoint)
    self.data[POINT] = myPoint
end

function ZO_Anchor:GetRelativePoint()
    return self.data[REL_POINT]
end

function ZO_Anchor:SetRelativePoint(relPoint)
    self.data[REL_POINT] = relPoint
end

function ZO_Anchor:GetOffsets()
    return self.data[OFFS_X], self.data[OFFS_Y]
end

function ZO_Anchor:GetOffsetX()
    return self.data[OFFS_X]
end

function ZO_Anchor:GetOffsetY()
    return self.data[OFFS_Y]
end

function ZO_Anchor:SetOffsets(offsetX, offsetY)
    self.data[OFFS_X] = offsetX or self.data[OFFS_X]
    self.data[OFFS_Y] = offsetY or self.data[OFFS_Y]
end

function ZO_Anchor:AddOffsets(offsetX, offsetY)
    if(offsetX)
    then
        self.data[OFFS_X] = offsetX + self.data[OFFS_X]
    end
    if(offsetY)
    then
        self.data[OFFS_Y] = offsetY + self.data[OFFS_Y]
    end
end

function ZO_Anchor:GetConstraints()
    return self.data[CONSTRAINTS]
end

function ZO_Anchor:SetConstraints(constraints)
    self.data[CONSTRAINTS] = constraints
end

function ZO_Anchor:Set(control)
    if(control)
    then
        control:ClearAnchors()
        local data = self.data
        control:SetAnchor(data[POINT], data[TARGET], data[REL_POINT], data[OFFS_X], data[OFFS_Y], data[CONSTRAINTS])
    end
end

function ZO_Anchor:AddToControl(control)
    if(control)
    then
        local data = self.data
        control:SetAnchor(data[POINT], data[TARGET], data[REL_POINT], data[OFFS_X], data[OFFS_Y], data[CONSTRAINTS])
    end
end

GROW_DIRECTION_UP_LEFT = 1
GROW_DIRECTION_UP_RIGHT = 2
GROW_DIRECTION_DOWN_LEFT = 3
GROW_DIRECTION_DOWN_RIGHT = 4

local verticalGrowthProduct =
{
    [GROW_DIRECTION_UP_LEFT] = -1,
    [GROW_DIRECTION_UP_RIGHT] = -1,
    [GROW_DIRECTION_DOWN_LEFT] = 1,
    [GROW_DIRECTION_DOWN_RIGHT] = 1,
}

local horizontalGrowthProduct =
{
    [GROW_DIRECTION_UP_LEFT] = -1,
    [GROW_DIRECTION_UP_RIGHT] = 1,
    [GROW_DIRECTION_DOWN_LEFT] = -1,
    [GROW_DIRECTION_DOWN_RIGHT] = 1,
}

function ZO_Anchor_BoxLayout(currentAnchor, control, controlIndex, containerStride, padX, padY, controlWidth, controlHeight, initialX, initialY, growDirection)
    growDirection = growDirection or GROW_DIRECTION_DOWN_RIGHT
    local row = zo_floor(controlIndex / containerStride)
    local col = controlIndex - (row * containerStride)
    
    padX = col * padX
    padY = row * padY

    if(padX < 0) then padX = 0 end
    if(padY < 0) then padY = 0 end

    local offsetX = horizontalGrowthProduct[growDirection] * ((col * controlWidth) + padX + initialX)
    local offsetY = verticalGrowthProduct[growDirection] * ((row * controlHeight) + padY + initialY)

    currentAnchor:SetOffsets(offsetX, offsetY)
    currentAnchor:Set(control)

    return row, col, offsetX, offsetY
end

--[[
    You would like to anchor "control" to "anchorTo".
    anchorTo will not move.
    This function determines the best fit for control so that anchorTo
    is not obscured and so that control fits in the window.
       
    NOTE: anchorTo can't be nil...it MUST be a valid control.
    
    The most common use of this function is to anchor a really big window, like a tooltip,
    to a really small window, like an actionbutton or inventory slot.
--]]
function ZO_Anchor_DynamicAnchorTo(control, anchorTo, offsetX, offsetY)
    offsetX = offsetX or 0
    offsetY = offsetY or 0

    local anchorToLeft, anchorToTop = anchorTo:GetLeft(), anchorTo:GetTop()
    local anchorToWidth, anchorToHeight = anchorTo:GetDimensions()
    local anchorToCenterX, anchorToCenterY = anchorToLeft + anchorToWidth * 0.5, anchorToTop + anchorToHeight * 0.5
    local UIWidth, UIHeight = GuiRoot:GetDimensions()
    local UICenterX, UICenterY = UIWidth * 0.5, UIHeight * 0.5

    control:ClearAnchors()

    if(anchorToCenterX < UICenterX) then
        if(anchorToCenterY < UICenterY) then
            --TOPLEFT
            control:SetAnchor(TOPLEFT, anchorTo, BOTTOMRIGHT, offsetX, offsetY)
        else
            --BOTTOMLEFT
            control:SetAnchor(BOTTOMLEFT, anchorTo, TOPRIGHT, offsetX, -offsetY)
        end
    else
        if(anchorToCenterY < UICenterY) then
            --TOPRIGHT
            control:SetAnchor(TOPRIGHT, anchorTo, BOTTOMLEFT, -offsetX, offsetY)
        else
            --BOTTOMRIGHT
            control:SetAnchor(BOTTOMRIGHT, anchorTo, TOPLEFT, -offsetX, -offsetY)
        end
    end
end

--anchors a control to the centered text in a larger label
function ZO_Anchor_ToCenteredLabel(control, anchor, labelWidth)
	local label = anchor:GetTarget()
	local textWidth, _ = label:GetTextDimensions()
	local labelWidth = labelWidth or label:GetWidth()
	local offX, offY = anchor:GetOffsets()
	
    control:SetAnchor(anchor:GetMyPoint(), label, anchor:GetRelativePoint(), labelWidth*0.5 - textWidth*0.5 + offX, offY)
end

function ZO_Anchor_OnRing(control, anchorToControl, x, y, radiusArg)
	local radius = radiusArg or anchorToControl:GetWidth() * 0.5
	local anchorCenterX = anchorToControl:GetLeft() + anchorToControl:GetWidth() * 0.5
	local anchorCenterY = anchorToControl:GetTop() + anchorToControl:GetHeight() * 0.5
	
	local vx = x - anchorCenterX
	local vy = y - anchorCenterY
	
	local vMagSq = vx*vx + vy*vy
	local rx, ry
	
	if(vMagSq > 0.001) then
		--scale the vector toward the origin of rotation
		local radiusSq = radius * radius
		local factor = math.sqrt(radiusSq / vMagSq)
		
		rx = vx * factor
		ry = vy * factor
	else
		--just put it at the top, we're so close to the center that it doesn't really matter what side we're on
		rx = 0
		ry = -radius
	end
	
	control:ClearAnchors()
	control:SetAnchor(CENTER, anchorToControl, CENTER, rx, ry)
end

function ZO_Anchor_ByAngle(control, anchorToControl, theta, radiusArg)
	local radius = radiusArg or anchorToControl:GetWidth() * 0.5	
	control:SetAnchor(CENTER, anchorToControl, CENTER, math.cos(theta) * radius, (-math.sin(theta)) * radius)
end

function ZO_Anchor_LineInContainer(line, container, startX, startY, endX, endY)
    line:ClearAnchors()
    line:SetAnchor(TOPLEFT, container, TOPLEFT, startX, startY)
    line:SetAnchor(BOTTOMRIGHT, container, TOPLEFT, endX, endY)
end
