
---@type detailsframework
local detailsFramework = _G["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
	return
end

local _
local APIImageFunctions = false

do
	local metaPrototype = {
		WidgetType = "image",
		dversion = detailsFramework.dversion,
	}

	--check if there's a metaPrototype already existing
	if (_G[detailsFramework.GlobalWidgetControlNames["image"]]) then
		--get the already existing metaPrototype
		local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames["image"]]
		--check if is older
		if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
			--the version is older them the currently loading one
			--copy the new values into the old metatable
			for funcName, _ in pairs(metaPrototype) do
				oldMetaPrototype[funcName] = metaPrototype[funcName]
			end
		end
	else
		--first time loading the framework
		_G[detailsFramework.GlobalWidgetControlNames["image"]] = metaPrototype
	end
end

local ImageMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["image"]]

detailsFramework:Mixin(ImageMetaFunctions, detailsFramework.SetPointMixin)
detailsFramework:Mixin(ImageMetaFunctions, detailsFramework.ScriptHookMixin)

------------------------------------------------------------------------------------------------------------
--metatables

	ImageMetaFunctions.__call = function(object, value)
		return object.image:SetTexture(value)
	end

------------------------------------------------------------------------------------------------------------
--members

	--frame width
	local gmember_width = function(object)
		return object.image:GetWidth()
	end

	--frame height
	local gmember_height = function(object)
		return object.image:GetHeight()
	end

	--texture
	local gmember_texture = function(object)
		return object.image:GetTexture()
	end

	--alpha
	local gmember_alpha = function(object)
		return object.image:GetAlpha()
	end

	--saturation
	local gmember_saturation = function(object)
		return object.image:GetDesaturated()
	end

	--atlas
	local gmember_atlas = function(object)
		return object.image:GetAtlas()
	end

	--texcoords
	local gmember_texcoord = function(object)
		return object.image:GetTexCoord()
	end

	ImageMetaFunctions.GetMembers = ImageMetaFunctions.GetMembers or {}
	detailsFramework:Mixin(ImageMetaFunctions.GetMembers, detailsFramework.DefaultMetaFunctionsGet)
	detailsFramework:Mixin(ImageMetaFunctions.GetMembers, detailsFramework.LayeredRegionMetaFunctionsGet)

	ImageMetaFunctions.GetMembers["alpha"] = gmember_alpha
	ImageMetaFunctions.GetMembers["width"] = gmember_width
	ImageMetaFunctions.GetMembers["height"] = gmember_height
	ImageMetaFunctions.GetMembers["texture"] = gmember_texture
	ImageMetaFunctions.GetMembers["blackwhite"] = gmember_saturation
	ImageMetaFunctions.GetMembers["desaturated"] = gmember_saturation
	ImageMetaFunctions.GetMembers["atlas"] = gmember_atlas
	ImageMetaFunctions.GetMembers["texcoord"] = gmember_texcoord

	ImageMetaFunctions.__index = function(object, key)
		local func = ImageMetaFunctions.GetMembers[key]
		if (func) then
			return func(object, key)
		end

		local fromMe = rawget(object, key)
		if (fromMe) then
			return fromMe
		end

		return ImageMetaFunctions[key]
	end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

	--texture
	local smember_texture = function(object, value)
		if (type(value) == "table") then
			local red, green, blue, alpha = detailsFramework:ParseColors(value)
			object.image:SetTexture(red, green, blue, alpha)
		else
			if (detailsFramework:IsHtmlColor(value)) then
				local red, green, blue, alpha = detailsFramework:ParseColors(value)
				object.image:SetTexture(red, green, blue, alpha)
			else
				object.image:SetTexture(value)
			end
		end
	end

	--width
	local smember_width = function(object, value)
		return object.image:SetWidth(value)
	end

	--height
	local smember_height = function(object, value)
		return object.image:SetHeight(value)
	end

	--alpha
	local smember_alpha = function(object, value)
		return object.image:SetAlpha(value)
	end

	--color
	local smember_color = function(object, value)
		local red, green, blue, alpha = detailsFramework:ParseColors(value)
		object.image:SetTexture(red, green, blue, alpha)
	end

	--vertex color
	local smember_vertexcolor = function(object, value)
		local red, green, blue, alpha = detailsFramework:ParseColors(value)
		object.image:SetVertexColor(red, green, blue, alpha)
	end

	--desaturated
	local smember_desaturated = function(object, value)
		if (value) then
			object:SetDesaturated(true)
		else
			object:SetDesaturated(false)
		end
	end

	--texcoords
	local smember_texcoord = function(object, value)
		if (value) then
			object:SetTexCoord(unpack(value))
		else
			object:SetTexCoord(0, 1, 0, 1)
		end
	end

	--atlas
	local smember_atlas = function(object, value)
		if (value) then
			object:SetAtlas(value)
		end
	end

	--gradient
	local smember_gradient = function(object, value)
		if (type(value) == "table" and value.gradient and value.fromColor and value.toColor) then
			object.image:SetTexture(1, 1, 1, 1)
			local fromColor = detailsFramework:FormatColor("tablemembers", value.fromColor)
			local toColor = detailsFramework:FormatColor("tablemembers", value.toColor)
			object.image:SetGradient(value.gradient, fromColor, toColor)
		else
			error("texture.gradient expect a table{gradient = 'gradient type', fromColor = 'color', toColor = 'color'}")
		end
	end

	ImageMetaFunctions.SetMembers = ImageMetaFunctions.SetMembers or {}
	detailsFramework:Mixin(ImageMetaFunctions.SetMembers, detailsFramework.DefaultMetaFunctionsSet)
	detailsFramework:Mixin(ImageMetaFunctions.SetMembers, detailsFramework.LayeredRegionMetaFunctionsSet)

	ImageMetaFunctions.SetMembers["alpha"] = smember_alpha
	ImageMetaFunctions.SetMembers["width"] = smember_width
	ImageMetaFunctions.SetMembers["height"] = smember_height
	ImageMetaFunctions.SetMembers["texture"] = smember_texture
	ImageMetaFunctions.SetMembers["texcoord"] = smember_texcoord
	ImageMetaFunctions.SetMembers["color"] = smember_color
	ImageMetaFunctions.SetMembers["vertexcolor"] = smember_vertexcolor
	ImageMetaFunctions.SetMembers["blackwhite"] = smember_desaturated
	ImageMetaFunctions.SetMembers["desaturated"] = smember_desaturated
	ImageMetaFunctions.SetMembers["atlas"] = smember_atlas
	ImageMetaFunctions.SetMembers["gradient"] = smember_gradient

	ImageMetaFunctions.__newindex = function(object, key, value)
		local func = ImageMetaFunctions.SetMembers[key]
		if (func) then
			return func(object, value)
		else
			return rawset(object, key, value)
		end
	end

------------------------------------------------------------------------------------------------------------
--methods
	--size
	function ImageMetaFunctions:SetSize(width, height)
		if (width) then
			self.image:SetWidth(width)
		end
		if (height) then
			return self.image:SetHeight(height)
		end
	end

	function ImageMetaFunctions:SetGradient(gradientType, fromColor, toColor, bInvert)
		fromColor = detailsFramework:FormatColor("tablemembers", fromColor)
		toColor = detailsFramework:FormatColor("tablemembers", toColor)

		if (bInvert) then
			local temp = fromColor
			fromColor = toColor
			toColor = temp
		end

		self.image:SetGradient(gradientType, fromColor, toColor)
	end

------------------------------------------------------------------------------------------------------------
--object constructor

	---@class df_image : texture, df_widgets
	---@field SetGradient fun(gradientType: "vertical"|"horizontal", fromColor: table, toColor: table)
	---@field image texture

	---@class df_gradienttable : table
	---@field gradient "vertical"|"horizontal"
	---@field fromColor table|string
	---@field toColor table|string
	---@field invert boolean?

	---create an object that encapsulates a texture and add additional methods to it
	---this function is an alias of NewImage() with a different name and parameters in different order
	---@param parent frame
	---@param texture texturepath|textureid|df_gradienttable|nil
	---@param width number?
	---@param height number?
	---@param layer drawlayer?
	---@param coords {key1: number, key2: number, key3: number, key4: number}?
	---@param member string?
	---@param name string?
	---@return df_image
	function detailsFramework:CreateTexture(parent, texture, width, height, layer, coords, member, name)
		return detailsFramework:NewImage(parent, texture, width, height, layer, coords, member, name)
	end

	---create an object that encapsulates a texture and add additional methods to it
	---this function is an alias of NewImage() with a different name and parameters in different order
	---@param parent frame
	---@param texture texturepath|textureid|df_gradienttable|nil
	---@param width number?
	---@param height number?
	---@param layer drawlayer?
	---@param coords {key1: number, key2: number, key3: number, key4: number}?
	---@param member string?
	---@param name string?
	---@return df_image
	function detailsFramework:CreateImage(parent, texture, width, height, layer, coords, member, name)
		return detailsFramework:NewImage(parent, texture, width, height, layer, coords, member, name)
	end

	---create an object that encapsulates a texture and add additional methods to it
	---@param parent frame
	---@param texture texturepath|textureid|df_gradienttable|nil
	---@param width number?
	---@param height number?
	---@param layer drawlayer?
	---@param texCoord {key1: number, key2: number, key3: number, key4: number}?
	---@param member string?
	---@param name string?
	---@return df_image
	function detailsFramework:NewImage(parent, texture, width, height, layer, texCoord, member, name)
		if (not parent) then
			return error("DetailsFrameWork: NewImage() parent not found.", 2)
		end

		if (not name) then
			name = "DetailsFrameworkPictureNumber" .. detailsFramework.PictureNameCounter
			detailsFramework.PictureNameCounter = detailsFramework.PictureNameCounter + 1
		end

		if (name:find("$parent")) then
			local parentName = detailsFramework:GetParentName(parent)
			name = name:gsub("$parent", parentName)
		end

		local ImageObject = {type = "image", dframework = true}

		if (member) then
			parent[member] = ImageObject
		end

		if (parent.dframework) then
			parent = parent.widget
		end

		texture = texture or ""

		ImageObject.image = parent:CreateTexture(name, layer or "overlay")
		ImageObject.widget = ImageObject.image

		detailsFramework:Mixin(ImageObject.image, detailsFramework.WidgetFunctions)

		if (not APIImageFunctions) then
			APIImageFunctions = true
			local idx = getmetatable(ImageObject.image).__index
			for funcName, funcAddress in pairs(idx) do
				if (not ImageMetaFunctions[funcName]) then
					ImageMetaFunctions[funcName] = function(object, ...)
						local x = loadstring( "return _G['" .. object.image:GetName() .. "']:" .. funcName .. "(...)")
						return x(...)
					end
				end
			end
		end

		ImageObject.image.MyObject = ImageObject

		if (texture) then
			if (type(texture) == "table") then
				if (texture.gradient) then
					---@type df_gradienttable
					local gradientTable = texture

					local fromR, fromG, fromB, fromA = detailsFramework:ParseColors(gradientTable.fromColor)
						local toR, toG, toB, toA = detailsFramework:ParseColors(gradientTable.toColor)

						if (gradientTable.invert) then
							local temp = fromR
							fromR = toR
							toR = temp
							temp = fromG
							fromG = toG
							toG = temp
							temp = fromB
							fromB = toB
							toB = temp
							temp = fromA
							fromA = toA
							toA = temp
						end

						ImageObject.image:SetTexture(1, 1, 1, 1)
						ImageObject.image:SetGradientAlpha(gradientTable.gradient, fromR, fromG, fromB, fromA, toR, toG, toB, toA)
				else
					local r, g, b, a = detailsFramework:ParseColors(texture)
					ImageObject.image:SetTexture(r, g, b, a)
				end

			elseif (type(texture) == "string") then
				local isAtlas = AtlasUtil:AtlasExists(texture)
				if (isAtlas) then
					ImageObject.image:SetAtlas(texture)
				else
					if (detailsFramework:IsHtmlColor(texture)) then
						local r, g, b = detailsFramework:ParseColors(texture)
						ImageObject.image:SetTexture(r, g, b)
					else
						ImageObject.image:SetTexture(texture)
					end
				end
			else
				local textureType = type(texture)
				if (textureType == "string" or textureType == "number") then
					ImageObject.image:SetTexture(texture)
				end
			end
		end

		if (texCoord and type(texCoord) == "table" and texCoord[4]) then
			ImageObject.image:SetTexCoord(unpack(texCoord))
		end

		if (width) then
			ImageObject.image:SetWidth(width)
		end
		if (height) then
			ImageObject.image:SetHeight(height)
		end

		ImageObject.HookList = {
		}

		setmetatable(ImageObject, ImageMetaFunctions)

		return ImageObject
	end

function detailsFramework:CreateHighlightTexture(parent, parentKey, alpha, name)
	if (not name) then
		name = "DetailsFrameworkPictureNumber" .. detailsFramework.PictureNameCounter
		detailsFramework.PictureNameCounter = detailsFramework.PictureNameCounter + 1
	end

	local highlightTexture = parent:CreateTexture(name, "highlight")
	highlightTexture:SetTexture([[Interface\Buttons\WHITE8X8]])
	highlightTexture:SetAlpha(alpha or 0.1)
	highlightTexture:SetBlendMode("ADD")
	highlightTexture:SetAllPoints()

	if (parentKey) then
		parent[parentKey] = highlightTexture
	end

	return highlightTexture
end

---Set an atlas to a texture object
---Accpets a string (atlasname) or a table (atlasinfo)
---@param self table
---@param textureObject texture
---@param atlas atlasinfo|atlasname
---@param useAtlasSize boolean?
---@param filterMode texturefilter?
---@param resetTexCoords boolean?
function detailsFramework:SetAtlas(textureObject, atlas, useAtlasSize, filterMode, resetTexCoords)
	local isAtlas = AtlasUtil:AtlasExists(type(atlas) == "string" and atlas or "--")
	if (isAtlas and type(atlas) == "string") then
		textureObject:SetAtlas(atlas, useAtlasSize, filterMode, resetTexCoords)
		return
	end

	if (type(atlas) == "table") then
		---@cast atlas df_atlasinfo
		local atlasInfo = atlas

		local atlasName = atlas.atlas
		if (atlasName) then
			isAtlas = AtlasUtil:AtlasExists(atlasName)
			if (isAtlas) then
				textureObject:SetAtlas(atlasName, useAtlasSize, filterMode, resetTexCoords)
				return
			end
		end

		if (useAtlasSize) then
			if (atlasInfo.width) then
				textureObject:SetWidth(atlasInfo.width)
			end
			if (atlasInfo.height) then
				textureObject:SetHeight(atlasInfo.height)
			end
		end

		textureObject:SetHorizTile(atlasInfo.tilesHorizontally or false)
		textureObject:SetVertTile(atlasInfo.tilesVertically or false)

		textureObject:SetTexture(atlasInfo.file, atlasInfo.tilesHorizontally and "REPEAT" or "CLAMP", atlasInfo.tilesVertically and "REPEAT" or "CLAMP", filterMode or "LINEAR")
		textureObject:SetTexCoord(atlasInfo.leftTexCoord or 0, atlasInfo.rightTexCoord or 1, atlasInfo.topTexCoord or 0, atlasInfo.bottomTexCoord or 1)

		if (atlasInfo.desaturated) then
			textureObject:SetDesaturated(true)
		else
			textureObject:SetDesaturated(false)
		end

		if (atlasInfo.colorName) then
			textureObject:SetVertexColor(detailsFramework:ParseColors(atlasInfo.colorName))
		else
			if (atlasInfo.vertexRed or atlasInfo.vertexGreen or atlasInfo.vertexBlue or atlasInfo.vertexAlpha) then
				textureObject:SetVertexColor(atlasInfo.vertexRed or 1, atlasInfo.vertexGreen or 1, atlasInfo.vertexBlue or 1, atlasInfo.vertexAlpha or 1)
			end
		end

	elseif (type(atlas) == "string" or type(atlas) == "number") then
		---@cast atlas string
		textureObject:SetTexture(atlas)
	end
end

---get the passed atlas, convert it to string with a texture escape sequence, and return it
---textureHeight overrides the height of the atlas
---textureWidth overrides the width of the atlas
---@param self table
---@param atlas atlasinfo|atlasname
---@param textureHeight number?
---@param textureWidth number?
---@return string
function detailsFramework:CreateAtlasString(atlas, textureHeight, textureWidth)
	local file, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, r, g, b, a, nativeWidth, nativeHeight = detailsFramework:ParseTexture(atlas)

	nativeWidth = nativeWidth or width or textureWidth
	nativeHeight = nativeHeight or height or textureHeight

	if (not height) then
		return "|T" .. file .. "|t"
	elseif (not width) then
		return "|T" .. file .. ":" .. height .. "|t"
	elseif (not leftTexCoord) then
		return "|T" .. file .. ":" .. height .. ":" .. width .. "|t"
	elseif (not r) then
		--the two zeros are the x and y offset
		--texCoords are multiplied by the heigh and width to get the actual pixel position
		local str = "|T" .. file .. ":" .. (textureHeight or height) .. ":" .. (textureWidth or width) .. ":0:0:" .. nativeWidth .. ":" .. nativeHeight .. ":" .. math.floor(leftTexCoord*nativeWidth) .. ":" .. math.floor(rightTexCoord*nativeWidth) .. ":" .. math.floor(topTexCoord*nativeHeight) .. ":" .. math.floor(bottomTexCoord*nativeHeight) .. "|t"
		return str
	else
		return "|T" .. file .. ":" .. (textureHeight or height) .. ":" .. (textureWidth or width) .. ":0:0:" .. nativeWidth .. ":" .. nativeHeight .. ":" .. math.floor(leftTexCoord*nativeWidth) .. ":" .. math.floor(rightTexCoord*nativeWidth) .. ":" .. math.floor(topTexCoord*nativeHeight) .. ":" .. math.floor(bottomTexCoord*nativeHeight) .. ":" .. r .. ":" .. g .. ":" .. b .. "|t"
	end
end

---Receives a texturepath or a textureid or an atlasname or an atlasinfo.
---Parse the data received and return the texture path or id, width, height and texcoords, what is available.
---nativeWidth and nativeHeight are the dimentions of the texture file in pixels.
---@param self table
---@param texture texturepath|textureid|atlasname|atlasinfo
---@param width number?
---@param height number?
---@param leftTexCoord number?
---@param rightTexCoord number?
---@param topTexCoord number?
---@param bottomTexCoord number?
---@param vertexRed number?
---@param vertexGreen number?
---@param vertexBlue number?
---@param vertexAlpha number?
---@return any texture
---@return number? width
---@return number? height
---@return number? leftTexCoord
---@return number? rightTexCoord
---@return number? topTexCoord
---@return number? bottomTexCoord
---@return number? red
---@return number? green
---@return number? blue
---@return number? alpha
---@return number? nativeWidth
---@return number? nativeHeight
function detailsFramework:ParseTexture(texture, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, vertexRed, vertexGreen, vertexBlue, vertexAlpha)
	local isAtlas
	if (type(texture) == "string") then
		isAtlas = AtlasUtil:AtlasExists(texture)
	end

	if (isAtlas) then
		--ui atlasinfo
		---@type atlasinfo
		local atlasInfo = isAtlas
		local textureId = atlasInfo.file
		local texturePath = atlasInfo.filename
		return textureId or texturePath, width or atlasInfo.width, height or atlasInfo.height, atlasInfo.leftTexCoord, atlasInfo.rightTexCoord, atlasInfo.topTexCoord, atlasInfo.bottomTexCoord
	end

	if (type(texture) == "table") then
		---@type df_atlasinfo
		local atlasInfo = texture

		local r, g, b, a
		if (type(atlasInfo.colorName) == "string") then
			r, g, b, a = detailsFramework:ParseColors(atlasInfo.colorName)
		else
			r, g, b, a = atlasInfo.vertexRed or vertexRed, atlasInfo.vertexGreen or vertexGreen, atlasInfo.vertexBlue or vertexBlue, atlasInfo.vertexAlpha or vertexAlpha
		end

		local nativeWidth, nativeHeight = atlasInfo.nativeWidth, atlasInfo.nativeHeight
		return atlasInfo.file or atlasInfo.filename, width or atlasInfo.width, height or atlasInfo.height, atlasInfo.leftTexCoord or 0, atlasInfo.rightTexCoord or 1, atlasInfo.topTexCoord or 0, atlasInfo.bottomTexCoord or 1, r, g, b, a, nativeWidth, nativeHeight
	end

	if (type(vertexRed) == "string" or type(vertexRed) == "table") then
		--the color passed is a colorName or a colorTable
		vertexRed, vertexGreen, vertexBlue, vertexAlpha = detailsFramework:ParseColors(vertexRed)
	end

	return texture, width, height, leftTexCoord or 0, rightTexCoord or 1, topTexCoord or 0, bottomTexCoord or 1, vertexRed, vertexGreen, vertexBlue, vertexAlpha
end

---Use the passed arguments to create a table imitate an atlasinfo
---@param self table
---@param file any
---@param width number? width of the texture
---@param height number? height of the texture
---@param leftTexCoord number? left texture coordinate to use with SetTexCoord as firt parameter
---@param rightTexCoord number? right texture coordinate to use with SetTexCoord as second parameter
---@param topTexCoord number? top texture coordinate to use with SetTexCoord as third parameter
---@param bottomTexCoord number? bottom texture coordinate to use with SetTexCoord as fourth parameter
---@param tilesHorizontally boolean? if the texture should tile horizontally, used with texture:SetHorizTile(value)
---@param tilesVertically boolean? if the texture should tile vertically, used with texture:SetVertTile(value)
---@param vertexRed number|string? red color to use with SetVertexColor or a color name to be parsed with ParseColors
---@param vertexGreen number? green color to use with SetVertexColor
---@param vertexBlue number? blue color to use with SetVertexColor
---@param vertexAlpha number? alpha color to use with SetVertexColor
---@param desaturated boolean? if the texture should be desaturated
---@param desaturation number? the amount of desaturation to use with SetDesaturation
---@param alpha number? the alpha to use with SetAlpha
---@return df_atlasinfo
function detailsFramework:CreateAtlas(file, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, tilesHorizontally, tilesVertically, vertexRed, vertexGreen, vertexBlue, vertexAlpha, desaturated, desaturation, alpha)
	---@type df_atlasinfo
	local atlasInfo = {
		file = file,
		width = width or 64,
		height = height or 64,
		leftTexCoord = leftTexCoord or 0,
		rightTexCoord = rightTexCoord or 1,
		topTexCoord = topTexCoord or 0,
		bottomTexCoord = bottomTexCoord or 1,
		tilesHorizontally = tilesHorizontally or false,
		tilesVertically = tilesVertically or false,
		desaturated = desaturated,
		desaturation = desaturation,
		alpha = alpha,
	}

	--parse the colors passed
	if (vertexRed) then
		if (type(vertexRed) == "string") then
			atlasInfo.colorName = vertexRed
		else
			atlasInfo.vertexRed = vertexRed
			atlasInfo.vertexGreen = vertexGreen
			atlasInfo.vertexBlue = vertexBlue
			atlasInfo.vertexAlpha = vertexAlpha
		end
	end

	return atlasInfo
end

---Return the texture passed can be parsed as a texture
---@param self table
---@param texture any
---@param bCheckTextureObject boolean?
---@return boolean
function detailsFramework:IsTexture(texture, bCheckTextureObject)
	--if is a string, can be a path or an atlasname, so can be parsed
	if (type(texture) == "string") then
		return true
	end

	--if is a number, can be parsed
	if (type(texture) == "number") then
		return true
	end

	if (type(texture) == "table") then
		--gradient texture
		if (texture.gradient) then
			return true
		end

		--part of an atlasinfo
		if (texture.file or texture.filename) then
			return true
		end

		if (bCheckTextureObject) then
			--check if is a texture object
			if (texture.GetTexture and texture.GetObjectType and texture:GetObjectType() == "Texture") then
				return true
			end
		end
	end

	return false
end

---Return if the table passed has the structure of an atlasinfo
---@param self table
---@param atlasTale table
---@return boolean
function detailsFramework:TableIsAtlas(atlasTale)
	if (type(atlasTale) == "table") then
		if (atlasTale.file or atlasTale.filename) then
			return true
		end
	end
	return false
end
