local XLoot = select(2, ...)
local lib = {
	skins = {},
	masque_tweaks = {}
}
XLoot.Skin = lib
local L = XLoot.L
local print = print

-- Custom skins
-- To add your own, simply make XLoot a dependency then call XLoot:RegisterSkin(name, skin_table)
-- skin_table being a table with these keys:
-- [required] texture - Path to texture
-- [optional] name - Shown in UI, name provided to RegisterSkin used by default
-- [optional] size, size_highlight
-- [optional] row_spacing
-- [optional] frame_color_*, loot_color_* -- See above
-- [optional] color_mod - Multiplier for border quality colors
-- [optional] padding, padding_highlight - Padding refers to how far outward (or inward) the border should be offset from the frame

-- Tweaking Masque skins
-- To keep from having to change this file, call XLoot:RegisterMasqueTweak(skin_name, skin_table)
-- following the same rules as custom skins
-- Quick or temporary tweaks can be added to SKIN_TWEAKS.lua

-- Base skin template
lib.base = {
	bar_texture = [[Interface\AddOns\XLoot\Textures\bar]],
	color_mod = .75,
	row_spacing = 2,
}

-- Skin registration
function XLoot:RegisterSkin(skin_name, skin_table)
	setmetatable(skin_table, { __index = lib.base })
	skin_table.key = skin_name
	lib.skins[skin_name] = skin_table
end

-- Masque tweaks
function XLoot:RegisterMasqueTweak(masque_name, tweak_table)
	lib.masque_tweaks[masque_name] = tweak_table
	-- Apply to existing skins
	if lib.skins[masque_name] then
		for k, v in pairs(tweak_table) do
			lib.skins[masque_name][k] = v
		end
	end
end

-- Skinning
local function subtable_insert(t, k, v)
	if not t[k] then
		t[k] = {}
	end
	table.insert(t[k], v)
end

do 
	local base = {
		padding = 2,
		size = 16,
		layer = 'ARTWORK',
		mode = 'BLEND',
		r = 1,
		g = 1,
		b = 1,
		a = 1
	}
	local types = {
		highlight = {
			mode = 'ADD',
			layer = 'HIGHLIGHT',
			texture = [[Interface\Buttons\ButtonHilight-Square]],
			size = 8,
			padding = 0,
			r = .8,
			g = .8,
			b = .8,
			a = .8
		}
	}
	
	local mt = {
		__index = function(t, k)
			local ttype = rawget(t, 'type')
			if ttype and types[ttype] and types[ttype][k] then
				return types[ttype][k]
			end
			return base[k] or nil
		end
	}
	
	local function meta(t)
		return setmetatable(t, mt)
	end
	
	local function update_borders(frame, options, borders, r, g, b, a)
		local padding = options.padding
		for pos, tex in ipairs(borders) do
			-- Set texture options
			tex:SetDrawLayer(options.layer)
			tex:SetTexture(options.texture)
			tex:SetBlendMode(options.mode)
			tex:SetWidth(options.size)
			tex:SetHeight(options.size)
			tex:SetVertexColor(r or options.r, g or options.g, b or options.b, a or options.a)

			-- Position texture
			tex:ClearAllPoints()
			if pos == 1 then
				tex:SetTexCoord(0, 1/3, 0, 1/3)
				tex:SetPoint("TOPLEFT", frame, "TOPLEFT", -padding, padding)
			elseif pos == 2 then
				tex:SetTexCoord(2/3, 1, 0, 1/3)
				tex:SetPoint("TOPRIGHT", frame, "TOPRIGHT", padding, padding)
			elseif pos == 3 then
				tex:SetTexCoord(0, 1/3, 2/3, 1)
				tex:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", -padding, -padding)
			elseif pos == 4 then
				tex:SetTexCoord(2/3, 1, 2/3, 1)
				tex:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", padding, -padding)
			elseif pos == 5 then
				tex:SetTexCoord(1/3, 2/3, 0, 1/3)
				tex:SetPoint("TOPLEFT", borders[1], "TOPRIGHT")
				tex:SetPoint("TOPRIGHT", borders[2], "TOPLEFT")
			elseif pos == 6 then
				tex:SetTexCoord(1/3, 2/3, 2/3, 1)
				tex:SetPoint("BOTTOMLEFT", borders[3], "BOTTOMRIGHT")
				tex:SetPoint("BOTTOMRIGHT", borders[4], "BOTTOMLEFT")
			elseif pos == 7 then
				tex:SetTexCoord(0, 1/3, 1/3, 2/3)
				tex:SetPoint("TOPLEFT", borders[1], "BOTTOMLEFT")
				tex:SetPoint("BOTTOMLEFT", borders[3], "TOPLEFT")
			elseif pos == 8 then
				tex:SetTexCoord(2/3, 1, 1/3, 2/3)
				tex:SetPoint("TOPRIGHT", borders[2], "BOTTOMRIGHT")
				tex:SetPoint("BOTTOMRIGHT", borders[4], "TOPRIGHT")
			end
		end
		return tex
	end

	local function create_borders(frame, options)
		local borders = { }
		for i = 1, 8 do
			borders[i] = frame:CreateTexture()
		end
		update_borders(frame, options, borders)
		return borders
	end

	local backdrop = {
		bgFile = [[Interface\ChatFrame\ChatFrameBackground]], tile = true, tileSize = 16,
		insets = {left = 3, right = 3, top = 3, bottom = 3},
	}

	local backdrop_empty = {
		bgFile = ''
	}

	local bd_color = { 0, 0, 0, .9 }
	local g_color = { .5, .5, .5, .6 }

	-- Frame methods
	local function SetBorderColor(self, r, g, b)
		for i, x in pairs(self._skin_borders) do
			x:SetVertexColor(r, g, b, 1)
		end
	end

	local function GetBorderColor(self)
		return self._skin_borders[1]:GetVertexColor()
	end
	
	local function SetGradientColor(self, r, g, b, a)
		self.gradient:SetGradientAlpha('VERTICAL', .1, .1, .1, 0, r, g, b, a)
	end
	
	function lib:Gradient(frame)
		local g = frame:CreateTexture(nil, 'BORDER')
		frame.gradient = g
		g:SetTexture[[Interface\ChatFrame\ChatFrameBackground]]
		g:SetPoint('TOPLEFT', 3, -3)
		g:SetPoint('BOTTOMRIGHT', -3, 3)
		-- g:SetBlendMode'ADD'
		frame.SetGradientColor = SetGradientColor
		frame:SetGradientColor(unpack(g_color))		
	end
	
	function lib:Backdrop(frame, opt_backdrop)
		frame:SetBackdrop(opt_backdrop or backdrop)
		frame:SetBackdropColor(unpack(bd_color))
	end
	
	-- Lib methods
	-- Basic skin
	function lib:Skin(frame, options)
		-- Store options
		frame._skin_options = meta(options)
		
		-- Apply backdrop
		if options.backdrop ~= false then
			self:Backdrop(frame, type(options.backdrop) == 'table' and options.backdrop or nil)
		end
		
		-- Gradient
		if options.gradient ~= false then
			self:Gradient(frame)
		end
		
		-- Borders
		frame._skin_borders = create_borders(frame, options)
		frame.SetBorderColor = SetBorderColor
		frame.GetBorderColor = GetBorderColor
	end
	
	function lib:SkinRaw(frame, options)
		return create_borders(frame, meta(options)), SetBorderColor
	end
	
	function lib:UpdateSkin(frame, options, r, g, b, a)
		frame._skin_options = meta(options)
		update_borders(frame, options, frame._skin_borders, r, g, b, a)
	end
	
	-- Highlights
	local function ShowHighlight(self, status)
		for _, tex in ipairs(self._highlights) do
			tex:Show()
		end
	end
	
	local function HideHighlight(self)
		for _, tex in ipairs(self._highlights) do
			tex:Hide()
		end
	end

	local function SetHighlightColor(self, r, g, b, a)
		for _, tex in ipairs(self._highlights) do
			tex:SetVertexColor(r, g, b, a)
		end
	end

	local function GetHighlightColor(self)
		return self._highlights[1]:GetVertexColor()
	end
	
	local highlight = { type = 'highlight' }
	
	-- Add highlight borders to a frame
	function lib:Highlight(frame, options)
		-- Default options
		options = meta(options or highlight)
		
		frame._highlights = create_borders(frame, options)
		frame._higlight_options = options

		frame.ShowHighlight = ShowHighlight
		frame.HideHighlight = HideHighlight
		frame.SetHighlightColor = SetHighlightColor
		frame.GetHighlightColor = GetHighlightColor
		if options.layer ~= 'HIGHLIGHT' then
			frame:HideHighlight()
		end
	end
	
	function lib:UpdateHighlight(frame, options, r, g, b, a)
		frame._higlight_options = meta(options)
		update_borders(frame, options, frame._highlights, r, g, b, a)
	end
end


-- Create a subset of skins to be applied
do
	-- Merge current skin with set options
	local function compile(data, name)
		assert(data.sets[name], "Bad set name given to XLoot.Skin")
		-- Return cached
		if not data.compiled[name] then
			data.compiled[name] = {}
		elseif next(data.compiled[name]) then
			return data.compiled[name]
		end
		-- Generate to cache
		local out = data.compiled[name]
		local skin = (data.SKIN and lib.skins[data.SKIN]) and lib.skins[data.SKIN] or lib.current
		local set = data.sets[name]
		-- Copy defaults
		for k,v in pairs(lib.base) do
			out[k] = v
		end
		-- Copy skin data
		for k,v in pairs(skin) do
			out[k] = v
		end
		-- Extract data for highlights
		if skin.highlight
			and set.type
			and set.type == 'highlight'
			and type(skin.highlight) == 'table' then
			for k,v in pairs(skin.highlight) do
				out[k] = v
			end
			-- Highlights shouldn't inherit default texture
			if not skin.highlight.texture then
				out.texture = nil
			end
		end
		-- Apply set overrides
		for k,v in pairs(set) do
			out[k] = v
		end
		-- Apply metatable
		setmetatable(out, getmetatable(skin))
		return out
	end

	-- Re-compile and Re-apply all 
	local function Reskin(self)
		local data = self._skin_data
		-- Clear cache
		for k,v in pairs(data.compiled) do
			wipe(v)
		end
		-- Update skins
		for set_name,frames in pairs(data.skinned) do
			for i,frame in ipairs(frames) do
				lib:UpdateSkin(frame, compile(data, set_name), frame:GetBorderColor())
			end
		end
		-- Update highlights
		for set_name,frames in pairs(data.highlighted) do
			for i,frame in ipairs(frames) do
				lib:UpdateHighlight(frame, compile(data, set_name), frame:GetHighlightColor())
			end
		end
		return compile(data, data.default or next(data.sets))
	end
	
	local function Skin(self, frame, set_name)
		local data = self._skin_data
		set_name = set_name or data.default
		local skin = compile(data, set_name)
		lib:Skin(frame, skin)
		subtable_insert(data.skinned, set_name, frame)
		return skin
	end
	
	local function Highlight(self, frame, set_name)
		local data = self._skin_data
		set_name = set_name or data.default
		lib:Highlight(frame, compile(data, set_name))
		subtable_insert(data.highlighted, set_name, frame)
	end

	-- Embed required functions and create data set to skin multiple similar frames
	XLoot.skinners = {}
	function XLoot:MakeSkinner(target, sets, default_set)
		if not default_set and not sets.default then
			sets.default = {}
		end
		target._skin_data = {
			skinned = {},
			highlighted = {},
			compiled = {},
			sets = sets,
			default = default_set or "default"
		}
		target.Reskin = Reskin
		target.Skin = Skin
		target.Highlight = Highlight
		table.insert(XLoot.skinners, target)
		return target
	end
end

-------------------------------------------------------------------------------
-- Default skins
local svelte = {
	name = ('|c2244dd22%s|r'):format(L.skin_svelte),
	texture = [[Interface\AddOns\XLoot\Textures\border_svelte]],
}
local legacy = {
	name = ('|c2244dd22%s|r'):format(L.skin_legacy),
	row_spacing = 3,	
	texture = [[Interface\AddOns\XLoot\Textures\border_legacy]],
	size = 14,
	highlight = {
		size = 12
	},
	color_mod = .85,
}
local smooth = {
	name = ('|c2244dd22%s|r'):format(L.skin_smooth),
	row_spacing = 3,
	texture = [[Interface\AddOns\XLoot\Textures\border_smooth]],
	size = 14,
	padding = 1,
	highlight = {
		size = 6,
		padding = -1
	},
	color_mod = .9,
}

-- Register default skins
XLoot:RegisterSkin('svelte', svelte)
XLoot:RegisterSkin('legacy', legacy)
XLoot:RegisterSkin('smooth', smooth)

-------------------------------------------------------------------------------
-- Index Masque skins later so we definitely catch all of them

function XLoot:SkinsOnInitialize()
	-- Masque skins
	local Masque = LibStub('Masque', true) or LibStub('LibButtonFacade', true)
	if Masque and Masque.GetSkins then
		-- Add available skins
		local Masque_Skins = Masque:GetSkins()
		if type(Masque_Skins) == 'table' then
			for k, v in pairs(Masque_Skins) do
				if k ~= 'Blizzard' and v.Normal.Texture then
					local skin = { }
					-- Guess at dimensions
					if v.Icon.Height and v.Normal.Height then
						skin.padding = (v.Normal.Height - v.Icon.Height) / 4
						skin.row_spacing = v.Icon.Height / 16
						skin.size = v.Icon.Height / 2
					end
					skin.texture = v.Normal.Texture
					skin.name = ('|c22dddd22Masque:|r %s'):format(k)
					-- Apply existing tweak
					if lib.masque_tweaks[k] then
						for mk,mv in pairs(lib.masque_tweaks[k]) do
							skin[mk] = mv
						end
					end
					-- Register
					XLoot:RegisterSkin(k, skin)
				end
			end
		else -- Warn about outdated Masque
			print("XLoot: Use of masque skins requires the beta version of Masque.")
		end
	end

	XLoot:ApplySkinTweaks()

	-- Activate current skin
	self:SetSkin(self.db.profile.skin)
end

-------------------------------------------------------------------------------
-- Skin access

function XLoot:SetSkin(name)
	lib.current = lib.skins[lib.skins[name] and name or 'smooth']
end


