local _, addon = ...
local API = addon.API;
local L = addon.L;

local Round = API.Round;
local IsUncollectedTransmogByItemInfo = API.IsUncollectedTransmogByItemInfo;
local ipairs = ipairs;

local LootSlot = LootSlot;
local CloseLoot = CloseLoot;
local GetPhysicalScreenSize = GetPhysicalScreenSize;
local InCombatLockdown = InCombatLockdown;
local CreateFrame = CreateFrame;
local IsModifiedClick = IsModifiedClick;
local IsCosmeticItem = C_Item.IsCosmeticItem or API.Nop;
local GetItemCount = C_Item.GetItemCount;
local GetCursorPosition = GetCursorPosition;
local IsDressableItemByID = C_Item.IsDressableItemByID or API.Nop;
local QualityColorGetter = API.GetItemQualityColor;


-- User Settings
local SHOW_ITEM_COUNT = true;
local USE_HOTKEY = true;
local TAKE_ALL_KEY = "E";
local TAKE_ALL_MODIFIER_KEY = nil;  --"LALT"
local USE_MOG_MARKER = true;
local MERGE_JUNKS = true;
------------------


local MainFrame = CreateFrame("Frame", "PlumberLootWindow", UIParent);
addon.LootWindow = MainFrame;
MainFrame:Hide();
MainFrame:SetAlpha(0);
MainFrame:SetFrameStrata("DIALOG");
MainFrame:SetToplevel(true);
MainFrame:SetClampedToScreen(true);
MainFrame.HeaderWidgetContainer = CreateFrame("Frame", nil, MainFrame);
MainFrame.HeaderWidgetContainer:Hide();
MainFrame.HeaderWidgets = {};
MainFrame.isUISpecialFrame = false;


local P_Loot = {};
P_Loot.MainFrame = MainFrame;
addon.P_Loot = P_Loot;


local Defination = {
	SLOT_TYPE_CURRENCY = 3,
	SLOT_TYPE_MONEY = 10,       --Game value is 2, but we sort it to top
	SLOT_TYPE_REP = 9,          --Custom Value
	SLOT_TYPE_ITEM = 1,
	SLOT_TYPE_OVERFLOW = 128,   --Display overflown currency
	SLOT_TYPE_CUSTOM = -1,      --Display custom info like a notifaction sent by other module

	QUEST_TYPE_NEW = 2,
	QUEST_TYPE_ONGOING = 1,
};
P_Loot.Defination = Defination;

local IsRareItem;   --See Bottom

local Formatter = {};
P_Loot.Formatter = Formatter;
do
	Formatter.tostring = tostring;
	Formatter.strlen = string.len;

	function Formatter:Init()
		local fontSize = PlumberDB and PlumberDB.LootUI_FontSize;
		Formatter:CalculateDimensions(fontSize);

		if not self.DummyFontString then
			self.DummyFontString = MainFrame:CreateFontString(nil, "BACKGROUND", "PlumberLootUIFont");
			self.DummyFontString:Hide();
			self.DummyFontString:SetPoint("TOP", UIParent, "BOTTOM", 0, -64);
		end
	end

	function Formatter:CalculateDimensions(fontSize)
		if not (fontSize and fontSize >= 10 and fontSize <= 16) then
			fontSize = nil;
		end

		local baseFont = _G.ObjectiveTrackerFont14 or _G.GameTooltipHeader or _G.GameFontNormal;
		local fontFile, defaultFontSize = baseFont:GetFont();
		local normalizedFontSize;

		if not fontSize then
			fontSize = defaultFontSize;
		end

		local fontObject = PlumberLootUIFont;
		local fontFlag = "OUTLINE";
		fontObject:SetFont(fontFile, Round(fontSize), fontFlag);
		fontObject:SetShadowOffset(0, 0);

		local locale = GetLocale();
		if locale == "zhCN" or locale == "zhTW" then
			normalizedFontSize = Round(0.8 * fontSize);
		else
			normalizedFontSize = fontSize;
		end

		self.BASE_FONT_SIZE = fontSize;                      --GameFontNormal
		self.ICON_SIZE = Round(32/12 * normalizedFontSize);
		self.TEXT_BUTTON_HEIGHT = Round(16/12 * normalizedFontSize);
		self.ICON_BUTTON_HEIGHT = self.ICON_SIZE;
		self.ICON_TEXT_GAP = Round(self.ICON_SIZE / 4);
		self.DOT_SIZE = Round(1.5 * normalizedFontSize);
		self.COUNT_NAME_GAP = Round(0.5 * normalizedFontSize);
		self.NAME_WIDTH = Round(16 * fontSize);
		self.BUTTON_WIDTH = self.ICON_SIZE + self.ICON_TEXT_GAP + fontSize + self.COUNT_NAME_GAP + self.NAME_WIDTH;
		self.BUTTON_SPACING = 12;
		self.UI_BUTTON_HEIGHT = Round(fontSize + 2 * 12);

		self.numberWidths = {};

		if MainFrame.Header then
			if fontSize < defaultFontSize then
				MainFrame.Header:SetFont(fontFile, fontSize, "");
			else
				MainFrame.Header:SetFont(fontFile, defaultFontSize, "");
			end
		end
	end

	function Formatter:GetNumberWidth(number)
		number = number or 0;
		local digits = self.strlen(self.tostring(number));

		if not self.numberWidths[digits] then
			local text = "+";
			for i = 1, digits do
				text = text .. "8";
			end
			text = text.." ";
			self.DummyFontString:SetText(text);
			self.numberWidths[digits] = Round(self.DummyFontString:GetWidth());
		end

		return self.numberWidths[digits]
	end

	function Formatter:GetPixelPerfectScale()
		if not self.pixelPerfectScale then
			local SCREEN_WIDTH, SCREEN_HEIGHT = GetPhysicalScreenSize();
			self.pixelPerfectScale = 768/SCREEN_HEIGHT;
		end
		return self.pixelPerfectScale
	end

	function Formatter:PixelPerfectTextureSlice(object)
		object:SetScale(self:GetPixelPerfectScale());
	end

	function Formatter:PixelSizeForScale(pixelSize, objectScale)
		local scale0 = self:GetPixelPerfectScale();
		return pixelSize * scale0 / objectScale
	end
end


local SimilarItemGroups = {};
local MergedSimilarItemNames = {};
do  --Merge Similar Items
	local SimilarItemData = {
		{
			items = {242516, 246937, 242515, 242513, 242508, 242501,242510, 242503, 242505, 242502, 242514, 242512, 242506, 242507, 242504, 242509, 242511},    --Epoch
			name = L["Epoch Mementos"],
		},

		{
			items = {217605, 217606, 217607, 217608, 217730, 217731, 217901, 217928, 217929, 217956},    --Timeless Scroll
			name = L["Timeless Scrolls"],
		},
	};

	for groupID, v in ipairs(SimilarItemData) do
		MergedSimilarItemNames[groupID] = v.name;
		for _, itemID in ipairs(v.items) do
			SimilarItemGroups[itemID] = groupID;
		end
	end
	SimilarItemData = nil;

	local function MergeSimilarItems(d1, d2)
		if not (d1.id and d2.id) then return end;

		local group1 = SimilarItemGroups[d1.id];
		local group2 = SimilarItemGroups[d2.id];
		if (MERGE_JUNKS and d1.quality == 0 and d2.quality == 0) or (group1 and group1 == group2) then
			local idToData = {};
			local v;

			if d1.mergedData then
				for _, data in ipairs(d1.mergedData) do
					v = idToData[data.id];
					if v then
						v.quantity = v.quantity + data.quantity;
					else
						idToData[data.id] = data;
					end
				end
			else
				v = idToData[d1.id];
				if v then
					v.quantity = v.quantity + d1.quantity;
				else
					idToData[d1.id] = d1;
				end
			end

			if d2.mergedData then
				for _, data in ipairs(d2.mergedData) do
					v = idToData[data.id];
					if v then
						v.quantity = v.quantity + data.quantity;
					else
						idToData[data.id] = data;
					end
				end
			else
				v = idToData[d2.id];
				if v then
					v.quantity = v.quantity + d2.quantity;
				else
					idToData[d2.id] = d2;
				end
			end

			local n = 0;
			local mergedData = {};
			for id, data in pairs(idToData) do
				n = n + 1;
				mergedData[n] = data;
			end
			d1.mergedData = mergedData;
			d2.mergedData = nil;


			--[[
			if not d1.mergedData then
				d1.mergedData = {d1, d2};
			else
				local isNew = true;
				for _, data in ipairs(d1.mergedData) do
					if d2.id == data.id then
						isNew = false;
						data.quantity = data.quantity + d2.quantity;
						break
					end
				end

				if isNew then
					tinsert(d1.mergedData, d2);
				end
			end
			--]]

			return true
		else
			return false
		end
	end
	P_Loot.MergeSimilarItems = MergeSimilarItems;
end


local FocusSolver = API.CreateFocusSolver();
FocusSolver:SetUseModifierKeys(true);
FocusSolver:SetDelay(0.05);


local CreateItemFrame;
local ItemFrameMixin = {};
do  --UI ItemButton
	local ANIM_DURATION_BUTTON_HOVER = 0.25;
	local ANIM_OFFSET_H_BUTTON_HOVER = 8;
	local Esaing_OutQuart = addon.EasingFunctions.outQuart;

	local function Anim_ShiftButtonCentent_OnUpdate(self, elapsed)
		self.t = self.t + elapsed;
		if self.t < ANIM_DURATION_BUTTON_HOVER then
			self.offset = Esaing_OutQuart(self.t, 0, ANIM_OFFSET_H_BUTTON_HOVER, ANIM_DURATION_BUTTON_HOVER);
		else
			self.offset = ANIM_OFFSET_H_BUTTON_HOVER;
			self:SetScript("OnUpdate", nil);
		end
		self.Reference:SetPoint("LEFT", self, "LEFT", self.offset, 0);
	end

	local function Anim_ResetButtonCentent_OnUpdate(self, elapsed)
		self.t = self.t + elapsed;
		if self.t < ANIM_DURATION_BUTTON_HOVER then
			self.offset = Esaing_OutQuart(self.t, self.offset, 0, ANIM_DURATION_BUTTON_HOVER);
		else
			self.offset = 0;
			self:SetScript("OnUpdate", nil);
			self.hovered = nil;
			self.t = nil;
		end
		self.Reference:SetPoint("LEFT", self, "LEFT", self.offset, 0);
	end

	local function Anim_ShiftAndFadeOutButton_OnUpdate(self, elapsed)
		self.t = self.t + elapsed;
		if self.t > 0 then
			self.alpha = self.alpha - 5 * elapsed;
			if self.alpha < 0 then
				self.alpha = 0;
			end
			self:SetAlpha(self.alpha);

			self.offset = self.offset + 128 * elapsed;
			if self.t < ANIM_DURATION_BUTTON_HOVER then

			else
				self:SetScript("OnUpdate", nil);
			end
			self.Reference:SetPoint("LEFT", self, "LEFT", self.offset, 0);
		end
	end

	function ItemFrameMixin:ShowHoverVisual()
		self.hovered = true;
		self.t = 0;
		self:SetScript("OnUpdate", Anim_ShiftButtonCentent_OnUpdate);
	end

	function ItemFrameMixin:PlaySlideOutAnimation(delay)
		if false and self.hovered then
			self:Hide();
		else
			self.hovered = true;
			self.t = (delay and -delay) or 0;
			self.alpha = self:GetAlpha();
			if not self.offset then
				self.offset = 0;
			end
			self:SetScript("OnUpdate", Anim_ShiftAndFadeOutButton_OnUpdate);
		end
	end

	function ItemFrameMixin:ResetHoverVisual(instant)
		if self.hovered then
			self.t = 0;
			if instant then
				self.hovered = nil;
				self.offset = 0;
				self.Reference:SetPoint("LEFT", self, "LEFT", 0, 0);
				self:SetScript("OnUpdate", nil);
			else
				if not self.offset then
					self.offset = 0;
				end
				self:SetScript("OnUpdate", Anim_ResetButtonCentent_OnUpdate);
			end
		end
	end

	local IsMidnightCrafting = {};

	function ItemFrameMixin:SetIcon(texture, data)
		self.StackedIconContainer:Hide();
		self.showIcon = texture ~= nil;
		local f = self.IconFrame;
		if texture then
			self.hasIcon = true;
			local iconSize = Formatter.ICON_SIZE;
			f.Icon:SetTexture(texture);
			f:SetSize(iconSize, iconSize);
			f:SetPoint("LEFT", self.Reference, "LEFT", 0, 0);
			f.Count:SetText(nil);
			f.IconOverlay:Hide();
			f.IconOverlay:SetSize(2*iconSize, 2*iconSize);
			self:SetButtonHeight(Formatter.ICON_BUTTON_HEIGHT);

			if data then
				if data.locked then
					f.Icon:SetVertexColor(0.9, 0, 0);
				else
					f.Icon:SetVertexColor(1, 1, 1);
				end
				if data.slotType == Defination.SLOT_TYPE_ITEM then
					if data.questType ~= 0 then
						if data.questType == Defination.QUEST_TYPE_NEW then
							f.IconOverlay:SetTexCoord(0.625, 0.75, 0, 0.125);
						elseif data.questType == Defination.QUEST_TYPE_ONGOING then
							f.IconOverlay:SetTexCoord(0.75, 0.875, 0, 0.125);
						end
						f.IconOverlay:Show();
						self:SetBorderColor(1, 195/255, 41/255);
					elseif data.craftQuality and data.craftQuality ~= 0 then
						if (data.craftQuality == 1 or data.craftQuality == 2) then
							if IsMidnightCrafting[data.id] == nil then
								local info = C_TradeSkillUI.GetItemReagentQualityInfo(data.id);
								if info and info.icon and string.find(info.icon, "[Qq]uality%-12") then
									IsMidnightCrafting[data.id] = true;
								else
									IsMidnightCrafting[data.id] = false;
								end
							end
						end

						if IsMidnightCrafting[data.id] then
							f.IconOverlay:SetTexCoord((data.craftQuality + 1) * 0.125, (data.craftQuality + 2) * 0.125, 0.125, 0.25);
						else
							f.IconOverlay:SetTexCoord((data.craftQuality - 1) * 0.125, data.craftQuality * 0.125, 0, 0.125);
						end

						f.IconOverlay:Show();
					elseif data.id then
						if IsCosmeticItem(data.id) then
							f.IconOverlay:SetTexCoord(0, 0.125, 0.125, 0.25);
							f.IconOverlay:Show();
							self:SetBorderColor(1, 0, 1);
						elseif data.classID == 2 or data.classID == 4 then
							if data.link then
								if USE_MOG_MARKER and IsUncollectedTransmogByItemInfo(data.link) then
									f.IconOverlay:SetTexCoord(0.125, 0.25, 0.125, 0.25);
									f.IconOverlay:Show();
								end
							end
						end
					end

					if SHOW_ITEM_COUNT and data.id then
						local numOwned = GetItemCount(data.id);
						if numOwned > 0 then
							f.Count:SetText(numOwned);
						end
					end
				elseif data.slotType == Defination.SLOT_TYPE_CURRENCY then
					local overflow, numOwned = API.WillCurrencyRewardOverflow(data.id, data.quantity);

					if overflow then
						self:SetBorderColor(1, 0, 0);
						f.IconOverlay:SetTexCoord(0.875, 1, 0, 0.125);
						f.IconOverlay:Show();
					end

					if SHOW_ITEM_COUNT and numOwned > 9999 then
						f.Count:SetText(AbbreviateNumbers(numOwned));
					end
				end
			else
				f.Icon:SetVertexColor(1, 1, 1);
			end

			f:Show();
		else
			self.hasIcon = nil;
			f:Hide();
			self:SetHeight(Formatter.TEXT_BUTTON_HEIGHT);
		end
	end

	function ItemFrameMixin:ShowGlow(state)
		if state then
			if not self.glowFX then
				local f = MainFrame.glowFXPool:Acquire();
				f.glowFX = f;
				f:ClearAllPoints();
				f:SetPoint("CENTER", self.IconFrame, "CENTER", 0, 0);
				f:SetParent(self.IconFrame);
				f:SetFrameSize(Formatter.ICON_SIZE, Formatter.ICON_SIZE);
				f.AnimGlow:Play();
				f:SetQualityColor(self.quality);
				f:Show();
			end
		else
			if self.glowFX then
				self.glowFX:Release();
				self.glowFX = nil;
			end
		end
	end

	function ItemFrameMixin:SetButtonHeight(height)
		self:SetHeight(height);
		self.Reference:SetHeight(height);
	end

	function ItemFrameMixin:SetBorderColor(r, g, b)
		self.IconFrame.Border:SetVertexColor(r, g, b);
	end

	function ItemFrameMixin:SetNameByColor(name, color)
		color = color or QualityColorGetter(1);
		local r, g, b = color.r, color.g, color.b;
		self.Text:SetText(name);
		self.Text:SetTextColor(r, g, b);
		self:SetBorderColor(r, g, b);
	end

	function ItemFrameMixin:SetNameByQuality(name, quality)
		quality = quality or 1;
		self.quality = quality;
		local color = QualityColorGetter(quality);
		self:SetNameByColor(name, color);
	end

	function ItemFrameMixin:SetData(data)
		if self.data and self.data.quantity ~= 0 and not (self.data.toast ~= data.toast and self.data.quantity == data.quantity) then
			data.oldQuantity = self.data.quantity;
			data.quantity = self.data.quantity + data.quantity;
		end

		if data.slotType == Defination.SLOT_TYPE_ITEM then
			if data.mergedData then
				self:SetMergedItem(data);
			else
				self:SetItem(data);
			end
		elseif data.slotType == Defination.SLOT_TYPE_CURRENCY then
			self:SetCurrency(data);
		elseif data.slotType == Defination.SLOT_TYPE_REP then
			self:SetReputation(data);
		elseif data.slotType == Defination.SLOT_TYPE_MONEY then
			self:SetMoney(data);
		elseif data.slotType == Defination.SLOT_TYPE_OVERFLOW then
			self:SetOverflowCurrency(data);
		elseif data.slotType == Defination.SLOT_TYPE_CUSTOM then
			self:SetCustomInfo(data);
		end

		self.data = data;
	end

	function ItemFrameMixin:SetCount(data)
		if (not data) or (data.hideCount and data.quantity < 2) then
			--We don't show equipment count unless you loot multiple of the same item (Legacy Raid)
			self.countWidth = nil;
			self.Count:Hide();
		else
			local quantity = data.totalQuantity or data.quantity;
			local countWidth = Formatter:GetNumberWidth(quantity);
			self.countWidth = countWidth;
			if data.oldQuantity then
				self:AnimateItemCount(data.oldQuantity, quantity);
				data.oldQuantity = nil;
			else
				self.Count:SetText("+"..quantity);
			end
			self.Count:Show();
		end
	end

	function ItemFrameMixin:Layout()
		local offset;

		if self.hasIcon then
			offset = Formatter.ICON_SIZE + Formatter.ICON_TEXT_GAP;
		else
			offset = 0;
		end
		self.Count:ClearAllPoints();
		self.Count:SetPoint("LEFT", self.Reference, "LEFT", offset, 0);

		if self.countWidth then
			offset = offset + self.countWidth;
		end
		self.Text:ClearAllPoints();
		self.Text:SetPoint("LEFT", self.Reference, "LEFT", offset, 0);

		self.textOffset = offset;
	end

	function ItemFrameMixin:SetItem(data)
		self.singleItemID = data.id;

		local showGlow, subtitle = IsRareItem(data);

		if subtitle then
			self:SetNameByQuality(string.format("%s\n|cff19ff19%s|r", data.name, subtitle), data.quality);
		else
			self:SetNameByQuality(data.name, data.quality);
		end

		self:SetIcon(data.icon, data);
		self:SetCount(data);
		self:Layout();

		if showGlow then
			self:ShowGlow(true);
		else
			self:ShowGlow(false);
		end

		--[[
		if data.classID == 15 and data.subclassID == 4 then
			API.InquiryOpenableItem(data.id, function(bag, slot)
				self:ShowGlow(true);
			end);
		end
		--]]
	end

	local function CreateStackedIconPool(itemFrame)
		local function OnCreate()
			local f = CreateFrame("Frame", nil, itemFrame.StackedIconContainer, "PlumberLootUISharedIconTemplate");
			return f
		end
		return API.CreateObjectPool(OnCreate);
	end

	local function SortFunc_Quality(a, b)
		if a.quality ~= b.quality then
			return a.quality > b.quality
		end
		return a.id > b.id
	end

	function ItemFrameMixin:SetMergedItem(data)
		if not self.stackedIconPool then
			self.stackedIconPool = CreateStackedIconPool(self);
		end
		self.stackedIconPool:ReleaseAll();

		local maxQuality = -1;
		local totalQuantity = 0;
		local groupID, fallbackName, bestIcon;

		table.sort(data.mergedData, SortFunc_Quality);

		for _, v in ipairs(data.mergedData) do
			if v.quality > maxQuality then
				maxQuality = v.quality;
				fallbackName = v.name;
				bestIcon = v.icon;
			end
			totalQuantity = totalQuantity + v.quantity;
			if not groupID then
				groupID = SimilarItemGroups[data.mergedData[1].id];
			end
		end

		if data.totalQuantity then
			data.oldQuantity = data.totalQuantity;
		end
		data.totalQuantity = totalQuantity;
		data.hideCount = false;

		local name;
		if maxQuality == 0 then
			name = L["Junk Items"] or fallbackName;
		else
			name = MergedSimilarItemNames[groupID] or fallbackName;
		end

		self:SetNameByQuality(name, maxQuality);

		local numIcons = math.min(#data.mergedData, 4);
		if numIcons > 1 then
			local overlapRatio = 0.15;
			local iconSize = Formatter.ICON_SIZE / (1 + (numIcons - 1) * overlapRatio);
			local iconOffset = iconSize * overlapRatio;
			local baseFrameLevel = self.StackedIconContainer:GetFrameLevel() + numIcons + 1;
			local fromY = Formatter.ICON_SIZE * 0.5;
			for i = 1, numIcons do
				local f = self.stackedIconPool:Acquire();
				f:SetPoint("TOPLEFT", self.Reference, "LEFT", (i - 1)*iconOffset, fromY - (i - 1)*iconOffset);
				f:SetFrameLevel(baseFrameLevel - i);
				local v = data.mergedData[i];
				f.Icon:SetTexture(v.icon);
				local color = QualityColorGetter(v.quality);
				local r, g, b = color.r, color.g, color.b;
				--Make the icon below darker
				local a = 1 - (i - 1) * 0.2;
				f.Border:SetVertexColor(r * a, g * a, b * a);
				f.Icon:SetVertexColor(a, a, a);
				f:SetSize(iconSize, iconSize);
			end
			self.StackedIconContainer:Show();
			self.IconFrame:Hide();
			self.hasIcon = true;
		else
			self:SetIcon(bestIcon);
		end

		self:SetCount(data);
		self:Layout();
	end

	function ItemFrameMixin:SetCurrency(data)
		local extraTooltip = API.GetExtraTooltipForCurrency(data.id);
		local name = data.name;
		if extraTooltip then
			name = name.."\n"..extraTooltip;
		end
		self:SetNameByQuality(name, data.quality);
		self:SetIcon(data.icon, data);
		self:SetCount(data);
		self:Layout();
		self:ShowGlow(false);
	end

	function ItemFrameMixin:SetReputation(data)
		self:SetIcon(nil);
		if data.quantity then
			self:SetCount(data);
		else
			self:SetCount(nil);
		end
		self.Text:SetText(data.name);
		self.Text:SetTextColor(0, 0.8, 1);    --The default color (0.5, 0.5, 1) is too dark so we use INFLUENCE_COLOR
		self:Layout();
		self:ShowGlow(false);
	end

	function ItemFrameMixin:SetMoney(data)
		--For manual pick-up mode
		local name = string.gsub(data.name, "%c", ", ");
		self:SetIcon(data.icon);
		self:SetCount(nil);
		self:SetNameByQuality(name, 1);
		self:Layout();
		self:ShowGlow(false);
	end

	function ItemFrameMixin:SetOverflowCurrency(data)
		local currencyID = data.id;
		local info = C_CurrencyInfo.GetCurrencyInfo(currencyID);

		local quantity;
		local label;

		if info.useTotalEarnedForMaxQty then
			quantity = info.totalEarned;
			label = L["Total Maximum"];
		else
			quantity = info.quantity;
			label = "Total Cap: ";
		end

		local maxQuantity = info.maxQuantity or quantity;
		label = "|cffff4800"..quantity.."/"..maxQuantity.."|r";
		local name = info.name;
		local text = name.."\n"..label;

		self:SetNameByQuality(text, info.quality);
		self:SetIcon(info.iconFileID);
		self:SetBorderColor(1, 0, 0);
		self.IconFrame.IconOverlay:SetTexCoord(0.875, 1, 0, 0.125);
		self.IconFrame.IconOverlay:Show();
		self:SetCount(nil);
		self:Layout();
	end

	function ItemFrameMixin:SetCustomInfo(data)
		self:SetIcon(data.icon);
		self:SetCount(data);
		self:SetNameByQuality(data.name, data.quality or 1);
		self:ShowGlow(data.showGlow);
		self:Layout();
	end

	function ItemFrameMixin:IsSameItem(data)
		if self.data and (not self.data.mergedData) and (not data.mergedData) then
			if self.data.slotType == data.slotType and data.slotType ~= Defination.SLOT_TYPE_CUSTOM then
				if data.slotType == Defination.SLOT_TYPE_REP then
					return self.data.name == data.name
				else
					return self.data.id == data.id
				end
			end
		end
		return false
	end

	function ItemFrameMixin:UpdatePixel()
		Formatter:PixelPerfectTextureSlice(self.IconFrame.Border);
	end

	function ItemFrameMixin:OnRemoved()
		self.data = nil;
		self.items = nil;
		self.singleItemID = nil;
		self:StopAnimating();
		self:ResetHoverVisual(true);
		self.hasGlowFX = nil;
		self.hasItem = nil;
		self.oldQuantity = nil;
	end

	function ItemFrameMixin:AnimateItemCount(oldValue, newValue)
		self.AnimItemCount:Stop();
		if self.Count:IsShown() then
			self.Count:SetText("+"..newValue);
			self.DummyCount:SetText("+"..oldValue);
			self.DummyCount:Show();
			self.AnimItemCount:Play();
		end
	end

	function ItemFrameMixin:OnEnter()
		if self.enableState == 1 then
			MainFrame:HighlightItemFrame(self);
			self:ShowHoverVisual();
		elseif self.enableState == 2 then
			--MainFrame:HighlightItemFrame(self);
		end
		FocusSolver:SetFocus(self);
		MainFrame:SetFocused(true);
	end

	function ItemFrameMixin:OnLeave()
		--Effective during Manual Mode
		GameTooltip:Hide();
		MainFrame:HighlightItemFrame(nil);
		self:ResetHoverVisual();
		FocusSolver:SetFocus(nil);
		MainFrame:SetFocused(false);
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");
		self:SetScript("OnEvent", nil);
	end

	function ItemFrameMixin:ShowTooltip()
		--Effective during Manual Mode
		local tooltip = GameTooltip;
		if self.enableState == 1 then   --Manual Loot
			if self.data.slotType == Defination.SLOT_TYPE_ITEM then
				tooltip:SetOwner(self, "ANCHOR_RIGHT", -Formatter.BUTTON_SPACING, 0);
				tooltip.suppressAutomaticCompareItem = true;
				tooltip:SetLootItem(self.data.slotIndex);
			elseif self.data.slotType == Defination.SLOT_TYPE_CURRENCY then
				tooltip:SetOwner(self, "ANCHOR_RIGHT", -Formatter.BUTTON_SPACING, 0);
				tooltip:SetLootCurrency(self.data.slotIndex);
			end

			local comparisonTooltip = ShoppingTooltip1;
			if comparisonTooltip and comparisonTooltip:IsShown() then
				local left1 = tooltip:GetLeft();
				local left2 = comparisonTooltip:GetLeft();
				if API.Secret_CanAccessValues(left1, left2) then
					if left2 < left1 then
						tooltip:ClearAllPoints();
						tooltip:SetPoint("BOTTOMRIGHT", self, "TOPLEFT", 0, 0);
					end
				end
			end

		elseif self.enableState == 2 then   --Auto Loot
			local hyperLink = self.data.mergedData and self.data.mergedData[1].link or self.data.link;
			local width = self:GetWidth();
			local textWidth = self.Text:GetWrappedWidth();
			local offset = -(width - textWidth - (self.textOffset or 0));
			if hyperLink then
				tooltip:SetOwner(self, "ANCHOR_RIGHT", offset, 0);
				tooltip.suppressAutomaticCompareItem = true;
				tooltip:SetHyperlink(hyperLink);
			elseif self.data.tooltipMethod then
				tooltip:SetOwner(self, "ANCHOR_RIGHT", offset, 0);
				tooltip[self.data.tooltipMethod](tooltip, self.data.id);
			elseif self.data.tooltipFunc then
				tooltip:SetOwner(self, "ANCHOR_RIGHT", offset, 0);
				self.data.tooltipFunc(tooltip, self.data.id, self.data);
			end
		end

		self:RegisterEvent("MODIFIER_STATE_CHANGED");
		self:SetScript("OnEvent", self.OnEvent);
	end

	function ItemFrameMixin:OnFocused()
		self:ShowTooltip();
	end

	function ItemFrameMixin:OnMouseDown(button)
		if button == "LeftButton" and MainFrame.ButtonHighlight:IsShown() then
			MainFrame.ButtonHighlight:ShowMouseDownFeedback();
		end
	end

	function ItemFrameMixin:OnMouseUp(button)
		MainFrame.ButtonHighlight:ShowMouseUpFeedback();
	end

	function ItemFrameMixin:OnClick(button)
		if button == "LeftButton" or button == "EmulateLeftButton" then
			if IsModifiedClick("DRESSUP") and not InCombatLockdown() then
				local itemID = self.data.slotType == Defination.SLOT_TYPE_ITEM and self.data.id;
				if itemID then
					if DressUpVisual and IsDressableItemByID(itemID) then
						DressUpVisual(self.data.link);
						return
					end

					if C_Item.IsDecorItem and C_Item.IsDecorItem(itemID) then
						DressUpLink(self.data.link);
						return
					end
				end
			elseif IsModifiedClick("CHATLINK") then
				if self.data.link then
					if ChatEdit_InsertLink(self.data.link) then
						return
					elseif SocialPostFrame and Social_IsShown() then
						Social_InsertLink(self.data.link);
						return
					end
				end
			end
			if button == "EmulateLeftButton" then
				return
			end
			LootSlot(self.data.slotIndex);
			MainFrame:SetClickedFrameIndex(self.index);
		end
	end

	function ItemFrameMixin:EnableMouseScript(enableState)
		if enableState == 1 then
			--Manual Loot: Enable Clicks and Hover
			self:EnableMouse(true);
			self:EnableMouseMotion(true);
			self.enableState = 1;
		elseif enableState == 2 then
			--Auto Loot: Only enable Hover to display tooltip
			self:EnableMouse(false);
			self:EnableMouseMotion(true);
			self.enableState = 2;
		else
			--Auto Loot: Non-interactable
			self:EnableMouse(false);
			self:EnableMouseMotion(false);
			self.enableState = 0;
		end
	end

	function ItemFrameMixin:OnEvent(event, ...)
		if event == "MODIFIER_STATE_CHANGED" then
			local key, down = ...
			if self:IsMouseMotionFocus() then  --IsModifiedClick("COMPAREITEMS")
				if key == "LSHIFT" or key == "RSHIFT" then
					self:ShowTooltip();
				end
			else
				self:UnregisterEvent(event);
			end
		end
	end

	local function CreateIconFrame(itemFrame)
		local f = CreateFrame("Frame", nil, itemFrame, "PlumberLootUIIconTemplate");
		f.Border:SetIgnoreParentScale(true);
		f.IconOverlay:SetTexture("Interface/AddOns/Plumber/Art/LootUI/IconOverlay.png");
		itemFrame.IconFrame = f;
		return f
	end

	local function AnimItemCount_OnStop(self)
		self.DummyCount:Hide();
	end

	function CreateItemFrame()
		local f = CreateFrame("Button", nil, MainFrame, "PlumberLootUIItemFrameTemplate");
		API.Mixin(f, ItemFrameMixin);
		CreateIconFrame(f);
		f:UpdatePixel();

		f.AnimItemCount.DummyCount = f.DummyCount;
		f.AnimItemCount:SetScript("OnStop", AnimItemCount_OnStop);
		f.AnimItemCount:SetScript("OnFinished", AnimItemCount_OnStop);

		f:SetScript("OnEnter", f.OnEnter);
		f:SetScript("OnLeave", f.OnLeave);
		f:SetScript("OnMouseDown", f.OnMouseDown);
		f:SetScript("OnMouseUp", f.OnMouseUp);
		f:SetScript("OnClick", f.OnClick);

		f.scriptEnabled = true;
		f:EnableMouseScript();

		return f
	end
end


do  --UI Background
	local function OnUpdate_Background(self, elapsed)
		if self.toWidth then
			self.deltaValue = (self.toWidth - self.width) * 4 * elapsed;
			if self.deltaValue > -0.12 and self.deltaValue < 0.12 then
				if self.deltaValue < 0 then
					self.deltaValue = -0.12;
				else
					self.deltaValue = 0.12;
				end
			end
			self.width = self.width + self.deltaValue;
			if self.widthDelta > 0 then
				if self.width + 0.5 >= self.toWidth then
					self.width = self.toWidth;
					self.toWidth = nil;
				end
			else
				if self.width - 0.5 <= self.toWidth then
					self.width = self.toWidth;
					self.toWidth = nil;
				end
			end
		end

		if self.toHeight then
			self.deltaValue = (self.toHeight - self.height) * 4 * elapsed
			if self.deltaValue > -0.12 and self.deltaValue < 0.12 then
				if self.deltaValue < 0 then
					self.deltaValue = -0.12;
				else
					self.deltaValue = 0.12;
				end
			end
			self.height = self.height + self.deltaValue;
			if self.heightDelta > 0 then
				if self.height + 0.5 >= self.toHeight then
					self.height = self.toHeight;
					self.toHeight = nil;
				end
			else
				if self.height - 0.5 <= self.toHeight then
					self.height = self.toHeight;
					self.toHeight = nil;
				end
			end
		end

		if not (self.toWidth or self.toHeight) then
			self:SetScript("OnUpdate", nil);
		end

		self:SetBackgroundSize(self.width, self.height);
	end

	local BackgroundMixin = {};

	function BackgroundMixin:SetBackgroundSize(width, height)
		local lineLenth;

		lineLenth = height + self.lineShrink;
		self.LeftLine:SetSize(self.lineWeight, lineLenth);
		if lineLenth > self.maxLineSize then
			self.LeftLine:SetTexCoord(504/1024, 0.5, 0, 1);
		else
			self.LeftLine:SetTexCoord(504/1024, 0.5, 0, lineLenth/self.maxLineSize);
		end

		lineLenth = width + self.lineShrink;
		self.TopLine:SetSize(lineLenth, self.lineWeight);
		if lineLenth > self.maxLineSize then
			self.TopLine:SetTexCoord(0, 0.5, 504/512, 1);
		else
			self.TopLine:SetTexCoord(0, lineLenth/self.maxLineSize * 0.5, 504/512, 1);
		end

		local bgWidth = width + self.bgExtrude;
		local bgHeight = height + self.bgExtrude;

		local maxSize = (bgWidth > bgHeight and bgWidth) or bgHeight;

		if maxSize > self.maxBgSize then
			local bgScale = maxSize / self.maxBgSize;
			self.Background:SetTexCoord(0.5, 0.5 + 0.5*(bgWidth/bgScale/self.maxBgSize), 0, 1*(bgHeight/bgScale/self.maxBgSize));
			self.MaskRight:SetSize(self.bgMaskSize, maxSize + 2);
			self.MaskBottom:SetSize(maxSize + 2, self.bgMaskSize);
		else
			self.Background:SetTexCoord(0.5, 0.5 + 0.5*(bgWidth/self.maxBgSize), 0, 1*(bgHeight/self.maxBgSize));
			self.MaskRight:SetSize(self.bgMaskSize, self.maxBgSize + 2);
			self.MaskBottom:SetSize(self.maxBgSize + 2, self.bgMaskSize);
		end

		self.Background:SetSize(bgWidth, bgHeight);
		self.width = width;
		self.height = height;
	end

	function BackgroundMixin:SetBackgroundAlpha(alpha)
		self.Background:SetAlpha(alpha);
	end

	function BackgroundMixin:ShowBorderLine(state)
		self.LeftLine:SetShown(state);
		self.LeftLineEnd:SetShown(state);
		self.TopLine:SetShown(state);
		self.TopLineEnd:SetShown(state);
	end

	function BackgroundMixin:UpdatePixel()
		local scale = 1;
		local px = API.GetPixelForScale(scale, 1);

		local lineOffset = 16*px;
		local bgExtrude = 16*px;
		self.lineWeight = 8*px;
		self.lineShrink = -16*px;
		self.maxLineSize = 504*px;
		self.maxBgSize = 512*px;
		self.bgExtrude = bgExtrude;
		self.bgMaskSize = 64*px;

		self.LeftLine:ClearAllPoints();
		self.LeftLine:SetPoint("TOP", self, "TOPLEFT", 0, lineOffset);
		self.TopLine:ClearAllPoints();
		self.TopLine:SetPoint("LEFT", self, "TOPLEFT", -lineOffset, 0);

		self.LeftLineEnd:SetSize(self.lineWeight, 2*self.lineWeight);
		self.TopLineEnd:SetSize(2*self.lineWeight, self.lineWeight);

		self.Background:ClearAllPoints();
		self.Background:SetPoint("TOPLEFT", self, "TOPLEFT", -bgExtrude, bgExtrude);

		self.MaskRight:ClearAllPoints();
		self.MaskRight:SetPoint("TOPRIGHT", self.Background, "TOPRIGHT", 0, 0);
		self.MaskBottom:ClearAllPoints();
		self.MaskBottom:SetPoint("BOTTOMLEFT", self.Background, "BOTTOMLEFT", 0, 0);
	end

	function BackgroundMixin:AnimateSize(width, height)
		if width > self.width then
			self.widthDelta = 1;
			self.toWidth = width;
		elseif width < self.width then
			self.widthDelta = -1;
			self.toWidth = width;
		end

		if height > self.height then
			self.heightDelta = 1;
			self.toHeight = height;
		elseif height < self.height then
			self.heightDelta = -1;
			self.toHeight = height;
		end

		self:SetScript("OnUpdate", OnUpdate_Background);
	end

	function MainFrame:InitBackground()
		self.InitBackground = nil;

		local f = CreateFrame("Frame", nil, self, "PlumberLootUIBackgroundTemplate");
		self.BackgroundFrame = f;
		API.Mixin(f, BackgroundMixin);
		f:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0);

		f:UpdatePixel();
		f:SetBackgroundSize(256, 256);
		f:SetBackgroundAlpha(0.50);

		local file = "Interface/AddOns/Plumber/Art/LootUI/LootUI.png";
		f.Background:SetTexture(file);
		f.TopLine:SetTexture(file);
		f.TopLineEnd:SetTexture(file);
		f.LeftLine:SetTexture(file);
		f.LeftLineEnd:SetTexture(file);
		f.TopLineEnd:SetTexCoord(16/1024, 0, 504/512, 1);
		f.LeftLineEnd:SetTexCoord(504/1024, 0.5, 16/512, 0);

		--[[
		local tt = self:CreateTexture(nil, "BACKGROUND");
		tt:SetAllPoints(true);
		tt:SetColorTexture(1, 0, 0, 0.5);
		--]]
	end

	function MainFrame:SetBackgroundAlpha(alpha)
		if self.BackgroundFrame then
			self.BackgroundFrame:ShowBorderLine(alpha > 0);
			alpha = 0.9 * alpha;
			self.BackgroundFrame:SetBackgroundAlpha(alpha);
		end
	end

	function MainFrame:SetBackgroundSize(width, height)
		if self:IsShown() and not self.growUpwards then
			self.BackgroundFrame:AnimateSize(width, height);
		else
			self.BackgroundFrame:SetScript("OnUpdate", nil);
			self.BackgroundFrame.toWidth = nil;
			self.BackgroundFrame.toHeight = nil;
			self.BackgroundFrame:SetBackgroundSize(width, height);
		end
	end

	function MainFrame:SetClickedFrameIndex(index)
		self.clickedFrameIndex = index;
	end

	function MainFrame:SetBottomFrameIndex(index)
		self.bottomFrameIndex = index;
	end

	function MainFrame:UpdateBackgroundHeightAfterClicks()
		if self.clickedFrameIndex then
			self:SetClickedFrameIndex(nil);

			if self.activeFrames and self.bottomFrameIndex > 0 then
				local itemFrame;
				local bottomFrameIndex;
				for i = #self.activeFrames, 1, -1 do
					itemFrame = self.activeFrames[i];
					if itemFrame.hasItem then
						bottomFrameIndex = i;
						break
					end
				end
				if bottomFrameIndex and bottomFrameIndex > 0 and bottomFrameIndex ~= self.bottomFrameIndex then
					self:SetBottomFrameIndex(bottomFrameIndex);
					local frameHeight = bottomFrameIndex * (Formatter.ICON_BUTTON_HEIGHT + Formatter.BUTTON_SPACING) + Formatter.BUTTON_SPACING;
					self:SetHeight(frameHeight);
					local scale = self:GetEffectiveScale();
					self:SetBackgroundSize(self.BackgroundFrame.width, (frameHeight + Formatter.ICON_BUTTON_HEIGHT) * scale);
				end
			end
		end
	end
end


local CreateUIButton
do  --UI Generic Button (Hotkey Button)
	local UIButtonMixin = {};

	function UIButtonMixin:SetHotkeyVisual(key)
		if key then
			self.hotkeyName = key;
			if API.GetModifierKeyName(key) ~= nil then
				self.HotkeyFrame.Hotkey:SetText(API.GetModifierKeyName(key));
			else
				self.HotkeyFrame.Hotkey:SetText(key);
			end
			self.HotkeyFrame:Show();
		else
			self.hotkeyName = nil;
			self.HotkeyFrame:Hide();
		end
		self:Layout();
	end

	function UIButtonMixin:SetButtonText(text)
		self.Text:SetText(text);
		self:Layout();
	end

	function UIButtonMixin:Layout()
		local textWidth = self.Text:GetUnboundedStringWidth();
		local scale = Formatter:GetPixelPerfectScale();
		--self.Background:SetScale(scale);
		self.Text:ClearAllPoints();
		local padding = 12;  --Hotkey Padding
		local buttonHeight = Formatter.UI_BUTTON_HEIGHT;
		local buttonWidth;
		local minWidth = 2 * buttonHeight;
		if self.hotkeyName then
			local bgPadding = 4;
			local bgHeight = Formatter.BASE_FONT_SIZE + 2*bgPadding;
			local bgWidth;
			if string.len(self.hotkeyName) > 1 then
				bgWidth = self.HotkeyFrame.Hotkey:GetUnboundedStringWidth() + 2*bgPadding;
			else
				bgWidth = bgHeight;
			end

			self.HotkeyFrame:SetSize(bgWidth, bgHeight);
			self.HotkeyFrame:SetPoint("LEFT", self, "LEFT", padding, 0);
			self.HotkeyFrame.HotkeyBackdrop:SetScale(scale);
			self.Text:SetPoint("LEFT", self.HotkeyFrame, "RIGHT", bgPadding, 0);
			buttonWidth = Round(padding + bgWidth + bgPadding + textWidth + padding);
		else
			self.Text:SetPoint("LEFT", self, "LEFT", padding, 0)
			buttonWidth = Round(textWidth + 2*padding);
		end

		if buttonWidth < minWidth then
			buttonWidth = minWidth;
		end

		self:SetSize(buttonWidth, buttonHeight);
	end

	function UIButtonMixin:SetHighlighted(state)
		if state then
			self.Background:SetTexCoord(136/1024, 264/1024, 72/512, 104/512);
		else
			self.Background:SetTexCoord(0, 128/1024, 72/512, 104/512);
		end
	end

	function UIButtonMixin:OnEnter()
		self:SetHighlighted(true);
		MainFrame:SetFocused(true);
	end

	function UIButtonMixin:OnLeave()
		self:SetHighlighted(false);
		MainFrame:SetFocused(false);
	end

	function CreateUIButton(parent)
		local f = CreateFrame("Button", nil, parent, "PlumberLootUIGenericButtonTemplate");
		local file = "Interface/AddOns/Plumber/Art/LootUI/LootUI.png";
		f.HotkeyFrame.HotkeyBackdrop:SetTexture(file);
		f.HotkeyFrame.HotkeyBackdrop:SetTexCoord(16/1024, 32/1024, 40/512, 56/512);
		f.Background:SetTexture(file);
		f.Background:SetTexCoord(0, 128/1024, 72/512, 104/512);
		f.Highlight:SetTexture(file);
		f.Highlight:SetTexCoord(338/1024, 458/1024, 72/512, 104/512);
		API.Mixin(f, UIButtonMixin);
		f:SetScript("OnEnter", f.OnEnter);
		f:SetScript("OnLeave", f.OnLeave);
		return f
	end
end


local TakeAllButtonMixin = {};
do  --TakeAllButton
	function TakeAllButtonMixin:OnClick()
		MainFrame:LootAllItemsSorted();
		self.AnimClick:Stop();
		self.AnimClick:Play();
		self.Highlight:Show();
	end

	function TakeAllButtonMixin:OnKeyDown(key)
		local isValid;
		if key == self.hotkeyName then
			isValid = true;
			self:OnClick();
		end

		if not InCombatLockdown() then
			self:SetPropagateKeyboardInput(not isValid);
		end
	end

	function TakeAllButtonMixin:OnEvent(event, ...)
		if event == "PLAYER_REGEN_DISABLED" then
			self:SetPropagateKeyboardInput(true);
			self:UpdateHotKey();
		elseif event == "PLAYER_REGEN_ENABLED" then
			self:UpdateHotKey();
		elseif event == "MODIFIER_STATE_CHANGED" then
			local key, down = ...
			if down == 1 and key == TAKE_ALL_MODIFIER_KEY then
				self:OnClick();
			end
		end
	end

	function TakeAllButtonMixin:OnShow()
		if MainFrame.inEditMode then
			self:SetScript("OnKeyDown", nil);
			self:UnregisterEvent("MODIFIER_STATE_CHANGED");
			self:SetHotkeyVisual(TAKE_ALL_KEY);
			return;
		end

		self:RegisterEvent("PLAYER_REGEN_DISABLED");
		self:RegisterEvent("PLAYER_REGEN_ENABLED");
		self:UpdateHotKey();
	end

	function TakeAllButtonMixin:OnHide()
		self:SetScript("OnKeyDown", nil);
		self:UnregisterEvent("PLAYER_REGEN_DISABLED");
		self:UnregisterEvent("PLAYER_REGEN_ENABLED");
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");
		self.AnimClick:Stop();
		self.Highlight:Hide();
	end

	function TakeAllButtonMixin:UpdateHotKey(inCombat)
		self:SetScript("OnKeyDown", nil);
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");

		if USE_HOTKEY and TAKE_ALL_KEY and (TAKE_ALL_MODIFIER_KEY or (not (InCombatLockdown() or inCombat))) then
			self:SetHotkeyVisual(TAKE_ALL_KEY);
			if TAKE_ALL_MODIFIER_KEY then
				self:RegisterEvent("MODIFIER_STATE_CHANGED");
			else
				self:SetScript("OnKeyDown", self.OnKeyDown);
			end
		else
			self:SetHotkeyVisual(nil);
		end
	end

	function TakeAllButtonMixin:OnLoad()
		self:SetScript("OnEvent", self.OnEvent);
		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnHide", self.OnHide);
		self:SetScript("OnClick", self.OnClick);
	end
end


local CloseButtonMixin = {};
do  --CloseButton
	function CloseButtonMixin:OnLoad()
		local file = "Interface/AddOns/Plumber/Art/LootUI/LootUI.png";
		self.Icon:SetTexture(file);
		self.Icon:SetTexCoord(64/1024, 96/1024, 104/512, 136/512);
		self.Background:SetTexture(file);
		self:SetHighlighted(false);
		self:SetScript("OnEnter", self.OnEnter);
		self:SetScript("OnLeave", self.OnLeave);
		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnClick", self.OnClick);
		self:RegisterForClicks("LeftButtonUp", "RightButtonUp");
	end

	function CloseButtonMixin:SetHighlighted(state)
		if state then
			self.Background:SetTexCoord(32/1024, 64/1024, 104/512, 136/512);
		else
			self.Background:SetTexCoord(0, 32/1024, 104/512, 136/512);
		end
	end

	function CloseButtonMixin:OnEnter()
		self:SetHighlighted(true);
		MainFrame:SetFocused(true);
	end

	function CloseButtonMixin:OnLeave()
		self:SetHighlighted(false);
		MainFrame:SetFocused(false);
	end

	function CloseButtonMixin:OnShow()
		local a = Formatter.UI_BUTTON_HEIGHT;
		self:SetSize(a, a);
	end

	function CloseButtonMixin:OnClick()
		if not self.pauseUpdate then
			self.pauseUpdate = true;
			CloseLoot();
			C_Timer.After(0.5, function()
				self.pauseUpdate = nil;
			end);
		end
		MainFrame:TryHide(true);
	end
end


local CreateSpikeyGlowFrame;
do
	local GLOW_COLORS = {
		[1] = {0.8, 0.8, 0.8},
		[2] = {0, 1, 0},
		[3] = {0, 0.5, 1},
		[4] = {0.5, 0, 1},
		[5] = {1, 0.5, 0},
	};

	local SPIKE_COLORS = {
		[3] = {0.5, 0.8, 1},
		[4] = {0.83, 0.5, 1},
	};

	local SpikeyGlowMixin = {};

	function SpikeyGlowMixin:OnLoad()
		self.Glow:SetTexture("Interface/AddOns/Plumber/Art/LootUI/IconOverlay.png");
		self.Glow:SetTexCoord(0.875, 1, 0.875, 1);

		self.Spike:SetTexture("Interface/AddOns/Plumber/Art/LootUI/LootUI.png");
		self.Spike:SetTexCoord(422/1024, 494/1024, 0, 72/512);
	end

	function SpikeyGlowMixin:SetFrameSize(width, height)
		self:SetSize(width, height);
		local scale = 1.75;
		self.Spike:SetSize(width*scale, height*scale);
		self.SpikeMask:SetSize(width*scale, height*scale)
		self.Glow:SetSize(2*width, 2*height);
		self.Exclusion:SetSize(width, height);
	end

	function SpikeyGlowMixin:SetQualityColor(quality)
		if not GLOW_COLORS[quality] then
			quality = 1;
		end
		if GLOW_COLORS[quality] then
			local c = GLOW_COLORS[quality];
			self.Glow:SetVertexColor(c[1], c[2], c[3]);
			if SPIKE_COLORS[quality] then
				c = SPIKE_COLORS[quality];
			end
			--self.Spike:SetVertexColor(c[1], c[2], c[3]);
			self.Spike:SetVertexColor(1, 1, 1);
		else
			self:Hide();
		end
	end

	function CreateSpikeyGlowFrame(parent)
		local f = CreateFrame("Frame", nil, parent, "PlumberSpikeyGlowTemplate");
		API.Mixin(f, SpikeyGlowMixin);
		f:OnLoad();
		return f
	end
end


local ButtonHighlightMixin = {};
do
	function ButtonHighlightMixin:UpdatePixel()
		local scale = self:GetEffectiveScale();
		local textureHeight = Formatter:PixelSizeForScale(28, scale);
		local offsetY = Formatter:PixelSizeForScale(2, scale);
		local textureWidth = 4 * textureHeight;
		self.FeedbackFrame.TopTexture:SetSize(textureWidth, textureHeight);
		self.FeedbackFrame.BottomTexture:SetSize(textureWidth, textureHeight);
		self.FeedbackFrame.TopTexture:SetPoint("BOTTOM", self.FeedbackFrame, "TOP", 0.33*textureHeight, -offsetY);
		self.FeedbackFrame.BottomTexture:SetPoint("TOP", self.FeedbackFrame, "BOTTOM", -0.33*textureHeight, offsetY);

		Formatter:PixelPerfectTextureSlice(self.Texture);
	end

	local function OnUpdate_ClickFeedback(self, elapsed)
		self.offsetX = self.offsetX + 512 * elapsed;
		self.alpha = self.alpha - 5 * elapsed;
		if self.alpha < 0 then
			self.alpha = 0;
			self.offsetX = 0;
			self:Hide();
			self:SetScript("OnUpdate", nil);
		else
			self:SetPoint("LEFT", self.parent, "LEFT", self.offsetX, 0);
			self:SetAlpha(self.alpha);
		end
	end

	function ButtonHighlightMixin:ShowMouseDownFeedback()
		self.Texture:SetAlpha(1);
	end

	function ButtonHighlightMixin:ShowMouseUpFeedback()
		self.Texture:SetAlpha(0.8);
	end

	function ButtonHighlightMixin:ShowClickFeedback()
		local f = self.FeedbackFrame;
		f:SetHeight(self:GetHeight());
		f:SetAlpha(1);
		f.alpha = 1;
		f.offsetX = Formatter.ICON_BUTTON_HEIGHT * 2;
		f.parent = self;
		f:ClearAllPoints();
		f:SetScript("OnUpdate", OnUpdate_ClickFeedback);
		f:Show();
	end

	function ButtonHighlightMixin:StopClickFeedback()
		self.FeedbackFrame:Hide();
	end
end


do  --UI Basic
	local function OnUpdate_FadeOut(self, elapsed)
		self.alpha = self.alpha - 4*elapsed;
		if self.alpha <= 0 then
			self.alpha = 0;
			self:SetScript("OnUpdate", nil);
			self:Hide();
		end
		self:SetAlpha(self.alpha);
	end

	local function OnUpdate_FadeOut_IfNotFocused(self, elapsed)
		if self.isFocused then return end;
		self.t = self.t + elapsed;
		if self.t > 0.1 then
			self.t = 0;
			if self.timerFrame and self.timerFrame.t then
				return
			end
			if not self:IsMouseOver() then
				self:TryHide(true);
			end
		end
	end

	function MainFrame:TryHide(forceHide)
		if forceHide then
			self.lootQueue = nil;
			self.isUpdatingPage = nil;
			self.alpha = self:GetAlpha();
			self:SetScript("OnUpdate", OnUpdate_FadeOut);
			self:UnregisterEvent("GLOBAL_MOUSE_UP");
		else
			self.t = 0;
			self:SetScript("OnUpdate", OnUpdate_FadeOut_IfNotFocused);
		end
	end

	function MainFrame:Disable()
		if self.timerFrame then
			self.timerFrame:SetScript("OnUpdate", nil);
			self.timerFrame.t = nil;
		end

		self:Hide();
		self:SetScript("OnUpdate", nil);
	end

	function MainFrame:LoadPosition()
		self:ClearAllPoints();
		local DB = PlumberDB;
		local growUpwards = DB and DB.LootUI_GrowUpwards;
		self.growUpwards = growUpwards;
		local point = growUpwards and "BOTTOMLEFT" or "TOPLEFT";
		if DB and DB.LootUI_PositionX and DB.LootUI_PositionY then
			self:SetPoint(point, UIParent, "BOTTOMLEFT", DB.LootUI_PositionX, DB.LootUI_PositionY);
		else
			local viewportWidth, viewportHeight = WorldFrame:GetSize();
			viewportWidth = math.min(viewportWidth, viewportHeight * 16/9);
			local scale = UIParent:GetEffectiveScale();
			local offsetX = math.floor((0.5 - 0.3333) * viewportWidth /scale);
			self:SetPoint(point, nil, "CENTER", offsetX, 0);
		end
	end

	function MainFrame:PositionUnderMouse()
		local x, y = GetCursorPosition();
		local scale = self:GetEffectiveScale();
		x = x / (scale) - Formatter.ICON_SIZE;
		y = math.max((y / scale) + 24, 350);
		self:ClearAllPoints();
		self:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", x, y);
		self:Raise();
	end

	function MainFrame:LayoutActiveFrames(fixedFrameWidth)
		if not self.activeFrames then
			self:TryHide(true);
			return
		end

		local height = 0;
		local spacing = Formatter.BUTTON_SPACING;
		local iconSize = Formatter.ICON_SIZE;
		local textWidth;
		local maxTextWidth = 0;

		for i, itemFrame in ipairs(self.activeFrames) do
			if i == 1 then
				itemFrame:SetPoint("TOPLEFT", self, "TOPLEFT", spacing, -spacing);
			else
				itemFrame:SetPoint("TOPLEFT", self.activeFrames[i - 1], "BOTTOMLEFT", 0, -spacing);
			end

			height = height + itemFrame:GetHeight() + spacing;

			if itemFrame.Text then
				textWidth = itemFrame.Text:GetWrappedWidth();
			else
				textWidth = itemFrame:GetWidth() - 2*iconSize;
			end
			if textWidth > maxTextWidth then
				maxTextWidth = textWidth;
			end

			itemFrame.index = i;
		end

		local frameWidth = maxTextWidth + 2*iconSize + Formatter:GetNumberWidth(10) + spacing;
		local frameHeight = height + spacing;

		--return frameWidth, frameHeight

		local maxFrameWidth = Formatter.BUTTON_WIDTH + Formatter.BUTTON_SPACING * 2;
		if frameWidth > maxFrameWidth then
			frameWidth = maxFrameWidth;
		end

		local backgroundWidth;

		if fixedFrameWidth then
			frameWidth = Formatter.BUTTON_WIDTH;
			backgroundWidth = frameWidth + Formatter.BUTTON_SPACING * 2;
			self:SetBottomFrameIndex(#self.activeFrames);
		else
			backgroundWidth = frameWidth + Formatter.ICON_BUTTON_HEIGHT
		end

		self:SetSize(frameWidth, frameHeight);

		local scale = self:GetEffectiveScale();
		self:SetBackgroundSize(backgroundWidth * scale, (frameHeight + Formatter.ICON_BUTTON_HEIGHT) * scale);
	end

	function MainFrame:GetFocusedItemFrame()
		if self.activeFrames then
			for i, itemFrame in ipairs(self.activeFrames) do
				if itemFrame:IsMouseOver() then
					return itemFrame
				end
			end
		end
	end

	function MainFrame:EnableHeaderWidgets(state)
		for _, widget in ipairs(self.HeaderWidgets) do
			widget:SetEnabled(state);
			widget:EnableMouseMotion(state);
			widget:EnableMouse(state);
		end
	end

	function MainFrame:Init()
		self.Init = nil;

		local Header = self:CreateFontString(nil, "OVERLAY", "GameFontNormal");
		self.Header = Header;

		Formatter:Init()

		self.itemFramePool = API.CreateObjectPool(CreateItemFrame);

		local function CreatePagniation()
			local texture = self:CreateTexture(nil, "OVERLAY");
			texture:SetTexture("Interface/AddOns/Plumber/Art/LootUI/LootUI.png");
			texture:SetTexCoord(0, 32/1024, 0, 32/512);
			return texture
		end
		self.paginationPool = API.CreateObjectPool(CreatePagniation);

		self.glowFXPool = API.CreateObjectPool(CreateSpikeyGlowFrame);

		local MoneyFrame = addon.CreateMoneyDisplay(self, "PlumberLootUIFont");
		self.MoneyFrame = MoneyFrame;
		MoneyFrame:SetHeight(Formatter.TEXT_BUTTON_HEIGHT);
		MoneyFrame:Hide();
		MoneyFrame.EnableMouseScript = ItemFrameMixin.EnableMouseScript;
		MoneyFrame:SetScript("OnMouseDown", ItemFrameMixin.OnMouseDown);
		MoneyFrame:SetScript("OnEnter", ItemFrameMixin.OnEnter);
		MoneyFrame:SetScript("OnLeave", ItemFrameMixin.OnLeave);

		function MoneyFrame:SetData(data)
			if self:IsShown() then
				self:SetAmountByDelta(data.quantity, true);     --true: animate
			else
				self:SetAmount(data.quantity);
			end
		end

		function MoneyFrame:IsSameItem(data)
			return data.slotType == Defination.SLOT_TYPE_MONEY
		end

		function MoneyFrame:OnFocused()

		end

		function MoneyFrame:ResetHoverVisual()

		end

		self:InitBackground();

		Header:SetJustifyH("CENTER");
		Header:SetPoint("BOTTOM", self, "TOP", 0, Formatter.BUTTON_SPACING);
		Header:SetText(L["You Received"]);
		Header:SetTextColor(1, 1, 1, 0.5);
		Header:Hide();

		local ButtonHighlight = CreateFrame("Frame", nil, self, "PlumberLootUIButtonHighlightTemplate");
		self.ButtonHighlight = ButtonHighlight;
		ButtonHighlight.Texture:SetTexCoord(40/1024, 420/1024, 0, 64/512);
		ButtonHighlight.FeedbackFrame.TopTexture:SetTexCoord(272/1024, 336/1024, 74/512, 102/512);
		ButtonHighlight.FeedbackFrame.BottomTexture:SetTexCoord(272/1024, 336/1024, 102/512, 74/512);
		API.Mixin(ButtonHighlight, ButtonHighlightMixin);
		ButtonHighlight:UpdatePixel();
		ButtonHighlight:ShowMouseUpFeedback();

		local CloseButton = CreateFrame("Button", nil, self.HeaderWidgetContainer, "PlumberLootUISquareIconButtonTemplate");
		self.CloseButton = CloseButton;
		CloseButton:SetPoint("BOTTOMRIGHT", self, "TOPRIGHT", 0, Formatter.BUTTON_SPACING);
		API.Mixin(CloseButton, CloseButtonMixin);
		CloseButton:OnLoad();
		table.insert(self.HeaderWidgets, CloseButton);

		local TakeAllButton = CreateUIButton(self.HeaderWidgetContainer);
		self.TakeAllButton = TakeAllButton;
		TakeAllButton:SetButtonText(L["Take All"]);
		TakeAllButton:SetPoint("RIGHT", CloseButton, "LEFT", -Formatter.BUTTON_SPACING + 4, 0);
		API.Mixin(TakeAllButton, TakeAllButtonMixin);
		TakeAllButton:OnLoad();
		TakeAllButton:UpdateHotKey();
		table.insert(self.HeaderWidgets, TakeAllButton);

		self:LoadPosition();
	end

	function MainFrame:HighlightItemFrame(itemFrame)
		self.ButtonHighlight:Hide();
		self.ButtonHighlight:ClearAllPoints();
		if itemFrame then
			local spacing = Formatter.BUTTON_SPACING;
			self.ButtonHighlight:SetPoint("TOPLEFT", itemFrame, "TOPLEFT", -Formatter.ICON_BUTTON_HEIGHT, 0.5*spacing);
			self.ButtonHighlight:SetPoint("BOTTOMRIGHT", itemFrame, "BOTTOMRIGHT", 0, -0.5*spacing);
			self.ButtonHighlight:SetParent(itemFrame);
			self.ButtonHighlight:Show();
		end
	end

	function MainFrame:AcquireItemFrame()
		local f = self.itemFramePool:Acquire();
		f.Text:SetWidth(Formatter.NAME_WIDTH);
		f:SetWidth(Formatter.BUTTON_WIDTH);
		return f
	end

	function MainFrame:SetMaxPage(page)
		self.paginationPool:ReleaseAll();
		if page and page > 1 then
			local numDots = page - 1;
			local dot;
			local gap = 0;
			local dotSize = Formatter.DOT_SIZE;
			local offsetX = Formatter.ICON_TEXT_GAP;
			local fromOffsetY = 0.5*(numDots * (dotSize + gap) - gap);
			for i = 1, numDots do
				dot = self.paginationPool:Acquire();
				dot:SetSize(dotSize, dotSize);
				dot:SetPoint("TOPRIGHT", self, "LEFT", -offsetX, fromOffsetY - (i - 1) * (dotSize + gap));
			end
		end
	end

	function MainFrame:SetLootSlotCleared(slotIndex)
		if self.activeFrames then
			for i, itemFrame in ipairs(self.activeFrames) do
				if itemFrame.data.slotIndex == slotIndex then
					--itemFrame:Hide();
					itemFrame:EnableMouseScript();
					itemFrame:PlaySlideOutAnimation();
					itemFrame.hasItem = nil;
					self:UpdateBackgroundHeightAfterClicks();
					return true
				end
			end
		end
	end

	function MainFrame:UpdateLootSlotData(slotIndex, data)
		if self.activeFrames then
			for _, itemFrame in ipairs(self.activeFrames) do
				if itemFrame.data.slotIndex == slotIndex then
					itemFrame.data = nil;
					itemFrame:SetData(data);
					return true
				end
			end
		end
	end

	function MainFrame:UpdateItemCount()
		if SHOW_ITEM_COUNT and self.activeFrames then
			local numOwned;
			for i, itemFrame in ipairs(self.activeFrames) do
				if itemFrame.singleItemID then
					numOwned = GetItemCount(itemFrame.singleItemID);
					if numOwned > 0 then
						itemFrame.IconFrame.Count:SetText(numOwned);
					end
				end
			end
		end
	end

	function MainFrame:ReleaseAll()
		if self.activeFrames then
			self.activeFrames = nil;
			self.itemFramePool:ReleaseAll();
			self.glowFXPool:ReleaseAll();
			self.MoneyFrame:Hide();
			self.MoneyFrame:ClearAllPoints();
			self:SetMaxPage(nil);
			self:SetClickedFrameIndex(nil);
		end
	end

	function MainFrame:OnShow()
		self:UpdateFrameStrata();
		if SHOW_ITEM_COUNT then
			self:RegisterEvent("BAG_UPDATE_DELAYED");
		end
	end
	MainFrame:SetScript("OnShow", MainFrame.OnShow);

	function MainFrame:OnEvent(event, ...)
		if event == "GLOBAL_MOUSE_UP" then
			if self:IsMouseOver() then
				local button = ...
				if button == "RightButton" then
					CloseLoot();
					self:TryHide(true);
				elseif (not (self.manualMode or self.inEditMode)) and button == "LeftButton" and not InCombatLockdown() then
					local itemFrame = self:GetFocusedItemFrame();
					if itemFrame and itemFrame.OnClick then
						itemFrame:OnClick("EmulateLeftButton");
					end
				end
			end
		elseif event == "BAG_UPDATE_DELAYED" then
			self:UpdateItemCount();
		end
	end
	MainFrame:SetScript("OnEvent", MainFrame.OnEvent);

	function MainFrame:OnUIScaleChanged()
		if not self.uiScaleDirty then
			self.uiScaleDirty = true;
			C_Timer.After(0.33, function()
				Formatter.pixelPerfectScale = nil;
				self.uiScaleDirty = nil;
				self:LoadPosition();
				if self.itemFramePool then
					self.itemFramePool:CallAllObjects("UpdatePixel");
				end
				if self.BackgroundFrame then
					self.BackgroundFrame:UpdatePixel();
				end
				if self.ButtonHighlight then
					self.ButtonHighlight:UpdatePixel();
				end
			end);
		end
	end

	function MainFrame:EnableMouseScript(state)
		if state then
			self:EnableMouse(true);
			self:EnableMouseMotion(true);
		else
			self:EnableMouse(false);
			self:EnableMouseMotion(true);
		end
	end

	function MainFrame:IsFocused()
		return (self:IsShown() and (self:IsMouseOver() or self.TakeAllButton:IsMouseOver())) or (self.OptionFrame and self.OptionFrame:IsShown() and self.OptionFrame:IsMouseOver())
	end

	function MainFrame:SetFocused(state)
		--Mouse Motion will be propagated to frames below
		--If the user mouse down on our frames (e.g. move camera), the game triggers OnLeave so we do a IsMouseOver check
		--if (not state) and (not self:IsMouseOver()) then
		if not state then
			self.isFocused = false;
		else
			self.isFocused = true;
		end
	end

	function MainFrame:OnEnter()
		self:SetFocused(true);
	end
	MainFrame:SetScript("OnEnter", MainFrame.OnEnter);

	function MainFrame:OnLeave()
		self:SetFocused(false);
	end
	MainFrame:SetScript("OnLeave", MainFrame.OnLeave);

	function MainFrame:AddToUISpecialFrames(state)
		if state ~= self.isUISpecialFrame then
			self.isUISpecialFrame = state;
			local selfName = "PlumberLootWindow";
			if state then
				for i, name in ipairs(UISpecialFrames) do
					if name == selfName then
						return
					end
				end
				table.insert(UISpecialFrames, selfName);
			else
				for i, name in ipairs(UISpecialFrames) do
					if name == selfName then
						table.remove(UISpecialFrames, i);
						return
					end
				end
			end
		end
	end
end


do  --Rare Items
	local RareItems = {
		--[210796] = true,    --debug
		[224025] = true,    --Crackling Shard
	};

	local TertiaryStats = {
		"ITEM_MOD_CR_LIFESTEAL_SHORT",
		"ITEM_MOD_CR_AVOIDANCE_SHORT",
		"ITEM_MOD_CR_SPEED_SHORT",
		"ITEM_MOD_CR_STURDINESS_SHORT",
		--"ITEM_MOD_STAMINA_SHORT", -- debug
	};

	local ItemStatExclusion = {
		-- Items that come with tertiaries
		[251783] = "ITEM_MOD_CR_SPEED_SHORT",	-- Lost Idol of the Hash'ey
		[262754] = "ITEM_MOD_CR_SPEED_SHORT",	-- Void Pearl of Haste
	};

	function IsRareItem(data)
		if RareItems[data.id] then
			return true;
		elseif data.classID == 15 and (data.subclassID == 2 or data.subclassID == 5) then
			-- CompanionPet/Mount
			return true;
		elseif data.classID == 17 then
			-- Battlepet
			return true;
		elseif (data.classID == 2 or data.classID == 4) and data.link and C_Item.GetItemStats then
			-- Equipment with tertiary stats. Retail only.
			local stats = C_Item.GetItemStats(data.link);
			if stats then
				local excludeStat = ItemStatExclusion[data.id];
				for _, k in ipairs(TertiaryStats) do
					if stats[k] and k ~= excludeStat then
						local subtitle = _G[k];
						return true, subtitle;
					end
				end
			end
		end
	end
end


do  --Callback Registery
	local function SettingChanged_ShowItemCount(state, userInput)
		SHOW_ITEM_COUNT = state;
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_ShowItemCount", SettingChanged_ShowItemCount);

	local function SettingChanged_UseHotkey(state, userInput)
		USE_HOTKEY = state;
		if userInput then
			local button = MainFrame.TakeAllButton;
			if button then
				button:UpdateHotKey();
			end
		end
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_UseHotkey", SettingChanged_UseHotkey);

	local function SettingChanged_NewTransmogIcon(state, userInput)
		USE_MOG_MARKER = state;
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_NewTransmogIcon", SettingChanged_NewTransmogIcon);

	local function SettingChanged_HotkeyName(value, userInput)
		if not (value and type("value") == "string") then
			value = nil;
		end
		TAKE_ALL_KEY = value;
		if API.GetModifierKeyName(value) ~= nil then
			TAKE_ALL_MODIFIER_KEY = value;
		else
			TAKE_ALL_MODIFIER_KEY = nil;
		end

		if userInput then
			local button = MainFrame.TakeAllButton;
			if button then
				button:UpdateHotKey();
			end
		end
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_HotkeyName", SettingChanged_HotkeyName);

	local function SettingChanged_UseCustomColor(state, userInput)
		if state then
			if ColorManager.GetColorDataForItemQuality then
				QualityColorGetter = ColorManager.GetColorDataForItemQuality;
			else
				QualityColorGetter = API.GetItemQualityColor;
			end
		else
			QualityColorGetter = API.GetItemQualityColor;
		end
		if userInput then
			MainFrame:UpdateSampleItems();
		end
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_UseCustomColor", SettingChanged_UseCustomColor);

	local function SettingChanged_CombineItems(state, userInput)
		MERGE_JUNKS = state;
	end
	addon.CallbackRegistry:RegisterSettingCallback("LootUI_CombineItem", SettingChanged_CombineItems);
end


--[[    --Debug
C_Timer.After(0, function()
	local f = CreateSpikeyGlowFrame(UIParent);
	f:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
	f:SetQualityColor(2);
	f:SetFrameSize(Formatter.ICON_SIZE, Formatter.ICON_SIZE);
	f:Show();
	f.AnimGlow:Play();
end);
--]]
