-- Adapted from Blizzard's retail FrameXML NineSlice system for DragonUI.
local addon = select(2,...);
local C_Texture = addon.c_texture;

local function GetNineSlicePiece(container, pieceName)
	if container.GetNineSlicePiece then
		local piece = container:GetNineSlicePiece(pieceName)
		if piece then
			return piece, true
		end
	end
	
	local piece = container[pieceName];
	if piece then
		return piece, true;
	else
		piece = container:CreateTexture()
		container[pieceName] = piece;
		return piece, false;
	end

	-- return container[pieceName] or container:CreateTexture(), false
end

local function PropagateLayoutSettingsToPieceLayout(userLayout, pieceLayout)
	-- Only apply mirrorLayout if it wasn't explicitly defined
	if pieceLayout.mirrorLayout == nil then
		pieceLayout.mirrorLayout = userLayout.mirrorLayout
	end

	-- ... and other settings that apply to the whole nine-slice
end

local function SetupTextureCoordinates(piece, setupInfo, pieceLayout, userLayout)
	local left, right, top, bottom = 0, 1, 0, 1;

	local pieceMirrored = pieceLayout.mirrorLayout;
	if pieceMirrored == nil then
		pieceMirrored = userLayout and userLayout.mirrorLayout;
	end

	if pieceMirrored then
		if setupInfo.mirrorVertical then
			top, bottom = bottom, top;
		end

		if setupInfo.mirrorHorizontal then
			left, right = right, left;
		end
	end
	-- piece:SetHorizTile(setupInfo.tileHorizontal)
	-- piece:SetVertTile(setupInfo.tileVertical)
	piece:SetSubTexCoord(left, right, top, bottom);
end

local function SetupPieceVisuals(piece, setupInfo, pieceLayout, textureKit)
	-- Change texture coordinates before applying atlas.
	SetupTextureCoordinates(piece, setupInfo, pieceLayout)
	
	-- textureKit is optional, that's fine but if it's nil the caller should ensure that there are no format specifiers in .atlas
	local atlasName = C_Texture.GetFinalNameFromTextureKit(pieceLayout.atlas, textureKit)
	local info = C_Texture.GetAtlasInfo(atlasName)
	piece:SetHorizTile(info and info.tilesHorizontally or false)
	piece:SetVertTile(info and info.tilesVertically or false)
	piece:set_atlas(atlasName, true)
end

local function SetupCorner(container, piece, setupInfo, pieceLayout)
	piece:ClearAllPoints()
	piece:SetPoint(pieceLayout.point or setupInfo.point, container, pieceLayout.relativePoint or setupInfo.point, pieceLayout.x, pieceLayout.y)
end

local function SetupEdge(container, piece, setupInfo, pieceLayout)
	piece:ClearAllPoints();

	local userLayout = NineSliceUtils.GetLayout(container.layoutType);
	if userLayout and (userLayout.threeSliceVertical or userLayout.threeSliceHorizontal) then
		piece:SetPoint(setupInfo.point, container, setupInfo.relativePoint, pieceLayout.x, pieceLayout.y);
		piece:SetPoint(setupInfo.relativePoint, container, setupInfo.point, pieceLayout.x1, pieceLayout.y1);
	else
		piece:SetPoint(setupInfo.point, GetNineSlicePiece(container, setupInfo.relativePieces[1]), setupInfo.relativePoint, pieceLayout.x, pieceLayout.y);
		piece:SetPoint(setupInfo.relativePoint, GetNineSlicePiece(container, setupInfo.relativePieces[2]), setupInfo.point, pieceLayout.x1, pieceLayout.y1);
	end
end

local function SetupCenter(container, piece, setupInfo, pieceLayout)
	piece:ClearAllPoints();

	local userLayout = NineSliceUtils.GetLayout(container.layoutType);
	if userLayout and userLayout.threeSliceVertical then
		piece:SetPoint("TOPLEFT", GetNineSlicePiece(container, "TopEdge"), "BOTTOMLEFT", pieceLayout.x, pieceLayout.y);
		piece:SetPoint("BOTTOMRIGHT", GetNineSlicePiece(container, "BottomEdge"), "TOPRIGHT", pieceLayout.x1, pieceLayout.y1);
	elseif userLayout and userLayout.threeSliceHorizontal then
		piece:SetPoint("TOPLEFT", GetNineSlicePiece(container, "LeftEdge"), "TOPRIGHT", pieceLayout.x, pieceLayout.y);
		piece:SetPoint("BOTTOMRIGHT", GetNineSlicePiece(container, "RightEdge"), "BOTTOMLEFT", pieceLayout.x1, pieceLayout.y1);
	else
		piece:SetPoint("TOPLEFT", GetNineSlicePiece(container, "TopLeftCorner"), "BOTTOMRIGHT", pieceLayout.x, pieceLayout.y);
		piece:SetPoint("BOTTOMRIGHT", GetNineSlicePiece(container, "BottomRightCorner"), "TOPLEFT", pieceLayout.x1, pieceLayout.y1);
	end
end

-- Defines the order in which each piece should be set up, and how to do the setup.
--
-- Mirror types: As a texture memory and effort savings, many borders are assembled from a single topLeft corner, and top/left edges.
-- That's all that's required if everything is symmetrical (left edge is also superfluous, but allows for more detail variation)
-- The mirror flags specify which texture coords to flip relative to the piece that would use default texture coordinates: left = 0, top = 0, right = 1, bottom = 1
local nineSliceSetup =
{
	{ pieceName = "TopLeftCorner", point = "TOPLEFT", fn = SetupCorner, },
	{ pieceName = "TopRightCorner", point = "TOPRIGHT", mirrorHorizontal = true, fn = SetupCorner, },
	{ pieceName = "BottomLeftCorner", point = "BOTTOMLEFT", mirrorVertical = true, fn = SetupCorner, },
	{ pieceName = "BottomRightCorner", point = "BOTTOMRIGHT", mirrorHorizontal = true, mirrorVertical = true, fn = SetupCorner, },
	{ pieceName = "TopEdge", point = "TOPLEFT", relativePoint = "TOPRIGHT", relativePieces = { "TopLeftCorner", "TopRightCorner" }, fn = SetupEdge, tileHorizontal = true },
	{ pieceName = "BottomEdge", point = "BOTTOMLEFT", relativePoint = "BOTTOMRIGHT", relativePieces = { "BottomLeftCorner", "BottomRightCorner" }, mirrorVertical = true, tileHorizontal = true, fn = SetupEdge, },
	{ pieceName = "LeftEdge", point = "TOPLEFT", relativePoint = "BOTTOMLEFT", relativePieces = { "TopLeftCorner", "BottomLeftCorner" }, tileVertical = true, fn = SetupEdge, },
	{ pieceName = "RightEdge", point = "TOPRIGHT", relativePoint = "BOTTOMRIGHT", relativePieces = { "TopRightCorner", "BottomRightCorner" }, mirrorHorizontal = true, tileVertical = true, fn = SetupEdge, },
	{ pieceName = "Center", fn = SetupCenter, },
};

local layouts =
{
	UniqueCornersLayout = {
		TopRightCorner = {atlas = "%s-nineslice-cornertopright"},
		TopLeftCorner = {atlas = "%s-nineslice-cornertopleft"},
		BottomLeftCorner = {atlas = "%s-nineslice-cornerbottomleft"},
		BottomRightCorner = {atlas = "%s-nineslice-cornerbottomright"},
		TopEdge = {atlas = "_%s-nineslice-edgetop"},
		BottomEdge = {atlas = "_%s-nineslice-edgebottom"},
		LeftEdge = {atlas = "!%s-nineslice-edgeleft"},
		RightEdge = {atlas = "!%s-nineslice-edgeright"},
		Center = {atlas = "%s-nineslice-center"}
	},
}

--------------------------------------------------
-- NINE SLICE UTILS
NineSliceUtils = {}

function NineSliceUtils.ApplyUniqueCornersLayout(self, textureKit)
	NineSliceUtils.ApplyLayout(self, layouts.UniqueCornersLayout, textureKit);
end

function NineSliceUtils.ApplyIdenticalCornersLayout(self, textureKit)
	NineSliceUtils.ApplyLayout(self, layouts.IdenticalCornersLayout, textureKit);
end

function NineSliceUtils.ApplyLayout(container, userLayout, textureKit)
	for pieceIndex, setup in ipairs(nineSliceSetup) do
		local pieceName = setup.pieceName
		local pieceLayout = userLayout[pieceName]
		if pieceLayout then
			PropagateLayoutSettingsToPieceLayout(userLayout, pieceLayout)

			local piece, pieceAlreadyExisted = GetNineSlicePiece(container, pieceName)
			if not pieceAlreadyExisted then
				container[pieceName] = piece
				piece:SetDrawLayer(pieceLayout.layer or "BORDER", pieceLayout.subLevel)
			end

			-- Piece setup can change arbitrary properties, do it before changing the texture.
			setup.fn(container, piece, setup, pieceLayout)
			SetupPieceVisuals(piece, setup, pieceLayout, textureKit)
		end
	end
end

function NineSliceUtils.ApplyLayoutByName(container, userLayoutName, textureKit)
	return NineSliceUtils.ApplyLayout(container, NineSliceUtils.GetLayout(userLayoutName), textureKit)
end

function NineSliceUtils.GetLayout(layoutName)
	return layouts[layoutName]
end

function NineSliceUtils.AddLayout(layoutName, layout)
	layouts[layoutName] = layout
end

--------------------------------------------------
-- NINE SLICE PANEL MIXIN
 NineSlicePanelUiMixin = {};

function NineSlicePanelUiMixin:GetFrameLayoutType()
	return self:GetAttribute("layoutType") or self:GetParent():GetAttribute("layoutType")
end

function NineSlicePanelUiMixin:GetFrameLayoutTextureKit()
	return self:GetAttribute("layoutTextureKit") or self:GetParent():GetAttribute("layoutTextureKit")
end

function NineSlicePanelUiMixin:OnLoad()
	local layout = NineSliceUtils.GetLayout(self:GetFrameLayoutType())
	if layout then
		NineSliceUtils.ApplyLayout(self, layout, self:GetFrameLayoutTextureKit());
	end
end