--## Author: FHeerdink ## Version: 1.0.0
    
    do
	local _M = { Version = 1.4 };

	if Krutilities and Krutilities.Version >= _M.Version then
		-- Newer/equal version already loaded.
		return;
	end

	-- [[ Optimization ]] --
	local type = type;
	local pairs = pairs;
	local CreateFrame = CreateFrame;

	-- [[ Local Functions ]] --
	local Shared_ProcessPoints = function(target, points, parent)
		if points then
			if type(points) == "string" then
				target:SetPoint(points, parent, points, 0, 0);
			else
				if #points == 0 then
					-- Single point.
					points.point = points.point or "CENTER";
					target:SetPoint(points.point, points.relativeTo or parent, points.relativePoint or points.point, points.x or 0, points.y or 0);
				else
					-- Many points
					for i = 1, #points do
						local point = points[i];
						point.point = point.point or "CENTER";
						target:SetPoint(point.point, point.relativeTo or parent, point.relativePoint or point.point, point.x or 0, point.y or 0);
					end
				end
			end
		end
	end

	local Shared_Sizing = function(target, node)
		local width = node.width or nil;
		local height = node.height or nil;

		if node.size then
			if type(node.size) == "table" then
				width = node.size[1];
				height = node.size[2];
			else
				width = node.size;
				height = node.size;
			end
		end

		if width then target:SetWidth(width); end
		if height then target:SetHeight(height); end
	end

	local Shared_Mixin = function(target, mixin)
		if mixin then
			for key, value in pairs(mixin) do
				target[key] = value;
			end
		end
	end

	local Shared_Inject = function(target, parent, injectSelf)
		if injectSelf then
			parent[injectSelf] = target;
		end
	end

	local Shared_CreateChild = function(createFunc, frame, node)
		local new = createFunc(frame, node);
		
		if node.buttonTex then
			if node.buttonTex == "PUSHED" then
				frame:SetPushedTexture(new);
			elseif node.buttonTex == "HIGHLIGHT" then
				frame:SetHighlightTexture(new);
			end
		end

		if node.scrollChild then
			frame:SetScrollChild(new);
		end
	end

	local Shared_HandleChildren = function(frame, childFunc, node)
		if node == nil then
			return;
		end

		local nodeCount = #node;
		if nodeCount > 0 then
			-- Node contains children, spawn them all.
			for i = 1, nodeCount do
				Shared_CreateChild(childFunc, frame, node[i]);
			end
		else
			-- No children, treat as a single object.
			Shared_CreateChild(childFunc, frame, node);
		end
	end

	-- [[ Global Utility ]] --

	-- [[ Clone a table, shallow or deep ]] --
	_M.CloneTable = function(input, deep)
		local inputType = type(input);
		local output;

		if inputType == "table" then
			output = {};

			if deep then -- Deep copy (copy-by-value)
				for key, value in next, input, nil do
					output[_M.CloneTable(key, true)] = _M.CloneTable(value, true);
				end
			else -- Shallow copy (copy-by-reference)
				for key, value in pairs(input) do
					output[key] = value;
				end
			end
		else
			output = input;
		end

		return output;
	end

	-- [[ Dump an object using Blizzard's debugging tool ]] --
	_M.Dump = function(input)
		if type(input) ~= "string" then
			_M._TEMP = input;
			input = "Krutilities._TEMP";
		end
		SlashCmdList["DUMP"](input);
		_M._TEMP = nil;
	end

	-- [[ Event handler creation utility ]] --
	_M.EventHandler = function(addon, events)
		local eventFrame = CreateFrame("FRAME");

		for eventName, funcName in pairs(events) do
			eventFrame:RegisterEvent(eventName);
		end

		eventFrame:SetScript("OnEvent", function(self, event, ...)
			addon[events[event]](...);
		end);

		return eventFrame;
	end

	_M.Frame = function(self, node)
		assert(type(node) == "table", "Krutilities:Frame called with invalid constructor table.");
		Shared_Mixin(node, node.mixin);

		if self ~= _M then
			node.parent = self;
		end

		if node.parentName then node.name = "$parent" .. node.parentName; end
		if node.parent then
			-- Parent cannot be string, attempt a global lookup.
			if type(node.parent) == "string" then
				node.parent = _G[node.parent];
			end
		else
			-- Default to UIParent.
			node.parent = UIParent;
		end

		local frame = CreateFrame(node.type or "FRAME", node.name, node.parent, node.inherit);

		if node.hidden then frame:Hide(); end
		if node.enableMouse then frame:EnableMouse(); end

		if node.strata then
			frame:SetFrameStrata(node.strata);
		end

		-- Generic stuff.
		Shared_Sizing(frame, node);
		Shared_Inject(frame, node.parent, node.injectSelf);

		-- Anchor points
		if node.setAllPoints then frame:SetAllPoints(true); end
		if node.points == nil then node.points = { point = "CENTER" }; end
		Shared_ProcessPoints(frame, node.points, node.parent);

		-- Backdrop
		if node.backdrop then frame:SetBackdrop(node.backdrop); end

		-- Data
		if node.data then
			for key, value in pairs(node.data) do
				frame[key] = value;
			end
		end

		-- Editbox Stuff
		if node.type == "EDITBOX" then
			if node.multiLine then frame:SetMultiLine(true); else frame:SetMultiLine(false); end
			if node.autoFocus then frame:SetAutoFocus(true); else frame:SetAutoFocus(false); end
		end

		-- Children
		Shared_HandleChildren(frame, _M.Texture, node.textures);
		Shared_HandleChildren(frame, _M.Frame, node.frames);
		Shared_HandleChildren(frame, _M.Text, node.texts);

		-- Scripts
		if node.scripts then
			for scriptEvent, scriptFunc in pairs(node.scripts) do
				if scriptEvent == "OnLoad" then
					scriptFunc(frame);
				else
					frame:SetScript(scriptEvent, scriptFunc);

					if not node.hidden and scriptEvent == "OnShow" then
						scriptFunc(frame);
					end
				end
			end
		end

		-- Inject shortcut functions.
		frame.SpawnTexture = _M.Texture;
		frame.SpawnText = _M.Text;
		frame.SpawnFrame = _M.Frame;

		return frame;
	end

	_M.Texture = function(frame, node)
		assert(type(node) == "table", "Krutilities:Texture called with invalid constructor table.");
		Shared_Mixin(node, node.mixin);

		if not node.parent then
			node.parent = frame ~= _M and frame or UIParent;
		end

		if node.parentName then node.name = "$parent" .. node.parentName; end
		local tex = node.parent:CreateTexture(node.name, node.layer, node.inherit, node.subLevel or 0);

		-- Generic stuff
		Shared_Sizing(tex, node);
		Shared_Inject(tex, frame, node.injectSelf);

		-- Tiling
		local tileX = node.tile or node.tileX;
		local tileY = node.tile or node.tileY;

		tex:SetHorizTile(tileX);
		tex:SetVertTile(tileY);
		tex:SetTexture(node.texture, tileX, tileY);

		-- Anchor points
		if node.points == nil and node.setAllPoints ~= false then
			node.setAllPoints = true;
		end
		
		if node.setAllPoints then tex:SetAllPoints(true); end
		Shared_ProcessPoints(tex, node.points, frame);

		-- Colour filter
		if node.color then
			local r = node.color.r or node.color[1] or 0;
			local g = node.color.g or node.color[2] or 0;
			local b = node.color.b or node.color[3] or 0;
			local a = node.color.a or node.color[4] or 1;

			tex:SetVertexColor(r, g, b, a);
		end

		-- Tex coords.
		if node.texCoord then
			tex:SetTexCoord(node.texCoord[1], node.texCoord[2], node.texCoord[3], node.texCoord[4]);
		end

		return tex;
	end

	_M.Text = function(frame, node)
		assert(type(node) == "table", "Krutilities:Text called with invalid constructor table.");
		Shared_Mixin(node, node.mixin);

		if not node.parent then
			node.parent = frame ~= _M and frame or UIParent;
		end

		if node.parentName then node.name = "$parent" .. node.parentName; end
		local text = frame:CreateFontString(node.name, node.layer, node.inherit);

		-- Generic Stuff
		Shared_Sizing(text, node);
		Shared_Inject(text, frame, node.injectSelf);

		-- Text / Alignment
		if node.text then text:SetText(node.text); end
		if node.justifyH then text:SetJustifyH(node.justifyH); end
		if node.justifyV then text:SetJustifyV(node.justifyV); end
		if node.maxLines then text:SetMaxLines(node.maxLines); end

		-- Colouring
		if node.color then
			local r = node.color.r or node.color[1] or 0;
			local g = node.color.g or node.color[2] or 0;
			local b = node.color.b or node.color[3] or 0;
			local a = node.color.a or node.color[4] or 1;

			text:SetTextColor(r, g, b, a);
		end

		-- Anchor points
		if node.points == nil then node.points = { point = "CENTER" }; end
		Shared_ProcessPoints(text, node.points, frame);

		return text;
	end

	-- Expose module to global scope.
	Krutilities = _M;
end

--[[
	EasyDisenchantRevamped (C) FHeerdink <frankeheerdink@gmail.com>
	Forked from EasyDisenchant with Approval from Kruithne <kruithne@gmail.com>
	Licensed under GNU General Public Licence version 3.BlacklistItem

	https://github.com/FHeerdink/EasyDisenchantRevamped

	EasyDisenchantRevamped.lua - Contains the core functionality of the addon.
]]--

do
	-- [[ Globals ]] --
	local PlaySound = PlaySound;
	local HideUIPanel = HideUIPanel;
	local math_floor = math.floor;
	local string_match = string.match;
	local tonumber = tonumber;
	local strlower = strlower;
	local pairs = pairs;

	local _K = Krutilities;
	local _M = {
		ADDON_NAME = "EasyDisenchantRevamped",
		chatFormat = "Easy Disenchant Revamped: %s",
		eventFrame = CreateFrame("FRAME"),
		itemButtons = {},
		eventMap = {}, -- Used for internal mapping of events.
		isTradeSkillFrameHooked = false,
		tradeSkillID = 333, -- CHARACTER_PROFESSION_ENCHANTING
		maxButtons = 89,
		buttonRenderingCache = {}
	};

	BINDING_HEADER_EASY_DISENCHANT = _M.ADDON_NAME;
	BINDING_NAME_EASY_DISENCHANT_OPEN = SHOW;

	_M.GetItemIDFromLink = function(itemLink)
		return tonumber(string_match(itemLink, "Hitem:(%d+)"));
	end

	_M.IsBlacklisted = function(self, itemID)
		return self.blacklist[itemID];
	end

	_M.BlacklistItem = function(self, itemID, itemLink)
		self.blacklist[itemID] = true;
		self.lastBlacklistedItem = itemID;
		self.lastBlacklistedItemLink = itemLink;

		self:Print(itemLink.."已加入不分解清单.");
		self:Print("使用指令'/de undo'恢复,或 '/de reset' 清除所有不分解清单。");		
	end

	_M.ResetBlacklist = function(self)
		self.blacklist = {};
		ShiGuangDB.EasyDisenchantBlacklist = self.blacklist;

		self:Print("不分解物品清单已重置");
		self.InvokeWindowOpen(); -- Refresh display.
	end

	_M.UndoBlacklist = function(self)
		if self.lastBlacklistedItem and self:IsBlacklisted(self.lastBlacklistedItem) then
			self.blacklist[self.lastBlacklistedItem] = nil;
			self:Print("将"..self.lastBlacklistedItemLink.."%s从不分解物品清单移除");

			self.lastBlacklistedItem = nil;
			self.lastBlacklistedItemLink = nil;

			self.InvokeWindowOpen(); -- Refresh display.
		end
	end

	_M.Print = function(self, message)
		DEFAULT_CHAT_FRAME:AddMessage(self.chatFormat:format(message));
	end

	_M.SetEventHandler = function(self, event, func)
		self.eventMap[event] = func;
		self.eventFrame:RegisterEvent(event);
	end

	_M.RemoveEventHandler = function(self, event)
		self.eventMap[event] = nil;
		self.eventFrame:UnregisterEvent(event);
	end

	_M.HookTradeSkillFrame = function(self)
		self:SetEventHandler("TRADE_SKILL_DATA_SOURCE_CHANGED", _M.CheckTradeSkillButton);
		self.isTradeSkillFrameHooked = true;
	end

	_M.CreateTradeSkillButton = function(self)
		self.tradeSkillButton = _K:Frame({
			name = "EasyDisenchantRevampedTradeSkillButton",
			parent = TradeSkillFrame,
			enableMouse = true,
			size = {256, 78},
			points = {
				point = "TOPRIGHT",
				relativePoint = "BOTTOMRIGHT",
			},
			textures = {
				{
					texture = [[Interface\Transmogrify\TransmogToast]],
					texCoord = {0, 1, 0, 0.609375}
				},
				{
					texture = [[Interface\ICONS\INV_Enchant_Disenchant]],
					subLevel = -1,
					size = 45,
					points = {
						point = "LEFT", x = 15,
					}
				}
			},
			texts = {
				text = "EasyDisenchantRevamped",
				inherit = "GameFontHighlight",
				points = {
					point = "LEFT", x = 72
				}
			},
			scripts = {
				OnMouseUp = self.InvokeWindowOpen
			}
		});
	end

	_M.CheckTradeSkillButton = function(self)
		if C_TradeSkillUI.GetTradeSkillLine() == self.tradeSkillID then
			if not self.tradeSkillButton then
				self:CreateTradeSkillButton();
			end

			self.tradeSkillButton:Show();
		else
			if self.tradeSkillButton then
				self.tradeSkillButton:Hide();
			end
		end
	end

	_M.GetItemButtonRenderingCache = function(self)
		local cache = self.buttonRenderingCache;
		if not cache.hasCreated then
			local frame = self.disenchantFrame;

			-- Called when an item button is clicked, as a post-event.
			cache.func_clickHook = function(self, key)					
				if InCombatLockdown() then
					frame.header:SetText(ERR_NOT_IN_COMBAT);
					frame.header:SetTextColor(1, 0, 0);
				else
					if key == "RightButton" then
						_M:BlacklistItem(self.itemID, self.link);				
					end									
					self:Hide();
				end
			end

			-- Called when the player's cursor leaves an item button.
			cache.func_mouseLeave = function(self)
				frame.glow:Hide();
				GameTooltip:Hide();
			end

			-- Called when the player's cursor enters an item button.
			cache.func_mouseEnter = function(self)
				frame.glow:SetPoint("CENTER", self);
				frame.glow:Show();

				GameTooltip:SetOwner(self, "ANCHOR_LEFT");
				GameTooltip:SetHyperlink(self.link);
				GameTooltip:Show();
			end

			cache.factory = function(index)			
				return {
					type = "ItemButton",				
					parent = self.disenchantFrame,
					parentName = "ItemButton" .. index,
					inherit = "SecureActionButtonTemplate",
					textures = {
						injectSelf = "backdrop",
						layer = "BACKGROUND",
						texture = [[Interface\Buttons\UI-EmptySlot-Disabled]],
						size = 54,
					},
					points = {
						point = "TOPLEFT",
						x = 38 + (38 * (index % 9)),
						y = -73 + (math_floor(index / 9) * -38)
					},
					scripts = {
						OnEnter = cache.func_mouseEnter,
						OnLeave = cache.func_mouseLeave
					},
				};
			end

			-- Prevent this scope being run again.
			cache.hasCreated = true;
		end
		return cache;
	end

	_M.GetItemButton = function(self, index)
		local buttons = self.itemButtons;
		if buttons[index + 1] then
			return buttons[index + 1];
		end

		local cache = self:GetItemButtonRenderingCache();
		local button = _K:Frame(cache.factory(index));
		
		button:HookScript("OnClick", cache.func_clickHook);
		button:RegisterForClicks("LeftButtonDown", "RightButtonDown");
		
		buttons[#buttons + 1] = button;
		return button;
	end

	_M.ScanEquipmentManager = function(self)
		local numOutfits = C_EquipmentSet.GetNumEquipmentSets();
		local equipmentCache = {};

		-- Equipment sets appear to be zero-index since 8.0?
		for index = 0, numOutfits - 1 do
			local _, _, setID = C_EquipmentSet.GetEquipmentSetInfo(index);
			if setID ~= nil then
				local itemLocations = C_EquipmentSet.GetItemLocations(setID);		
				for slotIndex, itemLocation in pairs(itemLocations) do
					local isInBags = (bit.band(itemLocation, ITEM_INVENTORY_LOCATION_BAGS) ~= 0);
					if isInBags then
						local _, _, _, _, itemSlotIndex, itemBagIndex = EquipmentManager_UnpackLocation(itemLocation);

						if itemSlotIndex ~= nil and itemBagIndex ~= nil then						
							local itemID = C_Container.GetContainerItemID(itemBagIndex, itemSlotIndex);
							equipmentCache[table.concat({itemBagIndex, itemSlotIndex, itemID}, "-")] = true;
						end
					end
				end
			else
				self:Print("您的其中一个物品集已损坏，无法进行筛选。请删除并重新创建");
			end
		end

		self.equipmentCache = equipmentCache;
	end

	_M.ResetEquipmentManagerCache = function(self)
		self.equipmentCache = nil;
	end

	_M.IsItemInOutfit = function(self, bagID, slotID, itemID)
		-- Check Outfitter if installed.
		if Outfitter then
			local inventoryCache = Outfitter:GetInventoryCache();

			-- Call this method to mark items with UsedInOutfit
			inventoryCache:CompiledUnusedItemsList();

			local vItems = inventoryCache.ItemsByCode[itemID];
			if vItems ~= nil then
				for _, vItemInfo in ipairs(vItems) do
					-- We found our item now check if its used in an outfit.
					if vItemInfo.UsedInOutfit == true then
						return true
					end
				end
			end
		else
			if self.equipmentCache == nil then
				-- Only process outfit data once per window open.
				self:ScanEquipmentManager();
			end

			return self.equipmentCache[table.concat({bagID, slotID, itemID}, "-")] or false;
		end

		return false;
	end


	_M.UpdateItems = function(self)
		-- Hide buttons.
		local buttons = _M.itemButtons;
		local nButtons = #buttons;

		for i = 1, nButtons do
			buttons[i]:Hide();
		end

		local disenchantName = C_Spell.GetSpellName(13262);		
		local macroFormat = "/stopmacro [combat][btn:2]\n/stopcasting\n/cast %s\n/cast %s %s";	
		self:ResetEquipmentManagerCache();

		local useButton = 0;
		for bagID = 0, NUM_BAG_SLOTS do						
			for slotID = 1, C_Container.GetContainerNumSlots(bagID) do
				local item = C_Container.GetContainerItemInfo(bagID, slotID);
	
				-- Skip non-existant items or legendary+.
				if item ~= nul and item.hyperlink ~= nil and (item.quality ~= nil and item.quality < 5 and item.quality > 1) then
					local itemName, _, _, _, _, itemClass, itemSubClass = GetItemInfo(item.hyperlink);
					--print("item name: " .. itemName)

					-- Avoid breaking on M+ keys
					if itemSubClass ~= nil then
						-- Check Blacklist
						local itemID = item.itemID;
						
						if not self:IsBlacklisted(itemID) and self:IsItemInOutfit(bagID, slotID, itemID) == false then
							-- Only disenchant weapons and armour.
							if itemClass == WEAPON or itemClass == ARMOR or itemSubClass:find(ITEM_QUALITY6_DESC) then
								-- print("item name: " .. itemName)
								local button = self:GetItemButton(useButton);
							
								SetItemButtonTexture(button, item.iconFileID);
								SetItemButtonQuality(button, item.quality, item.hyperlink);
							
								button:SetAttribute("type", "macro");
								-- button:SetAttribute("macrotext", string.format('/cast %s\n/cast %s %s', disenchantName, bagID, slotID));
								-- print("disenchant name: " .. disenchantName)
								button:SetAttribute("macrotext", format(macroFormat, disenchantName, bagID, slotID));
						
								button.link = item.hyperlink;
								button.itemID = itemID;
								-- button.bagID = bagID;
								-- button.slotID = slotID;
								
								button:Show();
								if useButton == self.maxButtons then
									return;
								end

								useButton = useButton + 1;
							end
						end
					end
				end
			end
		end
	end

	_M.CreateDisenchantFrame = function(self)
		local bgAnchor = {
			{ point = "TOPLEFT", x = 8, y = -8 },
			{ point = "BOTTOMRIGHT", x = -8, y = 8 }
		};

		local cornerMixin = {
			layer = "BACKGROUND",
			size = 64,
			subLevel = -2,
			texture = [[Interface\Transmogrify\Textures]]
		};

		local edgeXMixin = {
			tileX = true,
			subLevel = -3,
			size = {64, 23},
			layer = "BACKGROUND",
			texture = [[Interface\Transmogrify\HorizontalTiles]]
		};

		local edgeYMixin = {
			tileY = true,
			subLevel = -3,
			size = {23, 64},
			layer = "BACKGROUND",
			texture = [[Interface\Transmogrify\VerticalTiles]]
		};

		self.disenchantFrame = _K:Frame({
			name = "Easy Disenchant Revamped Frame",
			size = {418, 472},
			frames = {
				{ -- Close Button
					type = "BUTTON",
					parentName = "CloseButton",
					inherit = "UIPanelCloseButton",
					points = { point = "TOPRIGHT", x = -20, y = -25 }
				},
				{ -- Glow used by the item buttons.
					size = 37,
					hidden = true,
					injectSelf = "glow",
					textures = {
						{
							parentName = "InnerGlow", injectSelf = "innerGlow",
							texture = [[Interface\SpellActivationOverlay\IconAlert]],
							size = 53, points = { point = "CENTER" },
							texCoord = { 0.00781250, 0.50781250, 0.27734375, 0.52734375 }
						},
						{
							layer = "OVERLAY", parentName = "Ants", injectSelf = "ants",
							texture = [[Interface\SpellActivationOverlay\IconAlertAnts]],
							size = 44, points = { point = "CENTER" }
						}
					},
					scripts = {
						OnUpdate = function(self, elapsed) AnimateTexCoords(self.ants, 256, 256, 48, 48, 22, elapsed, 0.01); end
					}
				}
			},
			texts = {
				{
					inherit = "GameFontHighlightMedium",
					text = "Easy Disenchant Revamped",
					injectSelf = "header",
					points = { point = "TOPLEFT", x = 35, y = -40 }
				},
				{
					inherit = "GameFontHighlightMedium",
					text = "左键分解，右键加入不分解清单",
					justifyH = "CENTER",
					points = { point = "BOTTOM", y = 30 }
				}
			},
			textures = {
				{ -- Background.
					layer = "BACKGROUND",
					subLevel = -6,
					texture = [[Interface\FrameGeneral\UI-Background-Marble]],
					tile = true,
					points = bgAnchor,
					color = {0.302, 0.102, 0.204, 0.8}
				},
				{ -- Corner: Top Left
					parentName = "CornerTL",
					mixin = cornerMixin,
					points = "TOPLEFT",
					texCoord = {0.00781250, 0.50781250, 0.00195313, 0.12695313}
				},
				{ -- Corner: Top Right
					parentName = "CornerTR",
					mixin = cornerMixin,
					points = "TOPRIGHT",
					texCoord = {0.00781250, 0.50781250, 0.38476563, 0.50781250}
				},
				{ -- Corner: Bottom Left
					parentName = "CornerBL",
					mixin = cornerMixin,
					points = "BOTTOMLEFT",
					texCoord = {0.0078125, 0.5078125, 0.2578125, 0.38085938}
				},
				{ -- Corner: Bottom Right
					parentName = "CornerBR",
					mixin = cornerMixin,
					points = "BOTTOMRIGHT",
					texCoord = {0.0078125, 0.5078125, 0.13085938, 0.25390625}
				},
				{ -- Edge: Top
					parentName = "TopEdge",
					mixin = edgeXMixin,
					points = {
						{ point = "TOPLEFT", relativeTo = "$parentCornerTL", relativePoint = "TOPRIGHT", x = -30, y = -5 },
						{ point = "TOPRIGHT", relativeTo = "$parentCornerTR", relativePoint = "TOPLEFT", x = 30, y = -5 }
					},
					texCoord = {0, 1, 0.40625, 0.765625}
				},
				{ -- Edge: Bottom
					parentName = "BottomEdge",
					mixin = edgeXMixin,
					points = {
						{ point = "BOTTOMLEFT", relativeTo = "$parentCornerBL", relativePoint = "BOTTOMRIGHT", x = -30, y = 4 },
						{ point = "BOTTOMRIGHT", relativeTo = "$parentCornerBR", relativePoint = "BOTTOMLEFT", x = 30, y = 4 }
					},
					texCoord = {0, 1, 0.015625, 0.375}
				},
				{ -- Edge: Left
					parentName = "LeftEdge",
					mixin = edgeYMixin,
					points = {
						{ point = "TOPLEFT", relativeTo = "$parentCornerTL", relativePoint = "BOTTOMLEFT", x = 4, y = 16 },
						{ point = "BOTTOMLEFT", relativeTo = "$parentCornerBL", relativePoint = "TOPLEFT", x = 4, y = -16 }
					},
					texCoord = {0.40625, 0.765625, 0, 1}
				},
				{ -- Edge: Right
					parentName = "RightEdge",
					mixin = edgeYMixin,
					points = {
						{ point = "TOPRIGHT", relativeTo = "$parentCornerTR", relativePoint = "BOTTOMRIGHT", x = -4, y = 16 },
						{ point = "BOTTOMRIGHT", relativeTo = "$parentCornerBR", relativePoint = "TOPRIGHT", x = -4, y = -16 }
					},
					texCoord = {0.015625, 0.375, 0, 1}
				}
			},
			scripts = {
				OnHide = function() PlaySound(SOUNDKIT.UI_ETHEREAL_WINDOW_CLOSE); end
			}
		});
	end

	_M.OpenWindow = function(self)
		HideUIPanel(TradeSkillFrame);

		if not self.disenchantFrame then
			self:CreateDisenchantFrame();
		end

		self:UpdateItems();
		self.disenchantFrame:Show();
		PlaySound(SOUNDKIT.UI_ETHEREAL_WINDOW_CLOSE);
	end

	_M.OnCommand = function(msg)
		msg = strlower(msg);

		if msg == "reset" then
			-- Reset the entire blacklist.
			_M:ResetBlacklist();
		elseif msg == "undo" then
			-- Revert last addition to the blacklist.
			_M:UndoBlacklist();
		else
			-- Everything else just opens the window.
			_M:InvokeWindowOpen();
		end
	end

	_M.OnLoad = function(self)
		-- Register command.
		SLASH_DISENCHANT1, SLASH_DISENCHANT2 = "/disenchant", "/de";
		SlashCmdList["DISENCHANT"] = _M.OnCommand;

		-- Created store blacklist table if it doesn't exist.
		if ShiGuangDB.EasyDisenchantBlacklist == nil then ShiGuangDB.EasyDisenchantBlacklist = {}; end

		-- Store local reference to our stored table.
		self.blacklist = ShiGuangDB.EasyDisenchantBlacklist;

		-- Hook to TradeSkillFrame.
		if not self.isTradeSkillFrameHooked and C_AddOns.IsAddOnLoaded("Blizzard_TradeSkillUI") then
			self:HookTradeSkillFrame();
		end
	end

	_M.OnEvent = function(self, event, ...)
		-- Note: self is not _M in this instance, it's eventFrame.
		local handler = _M.eventMap[event];
		if handler then
			handler(_M, ...);
		end
	end

	_M.InvokeWindowOpen = function()
		_M:OpenWindow();
	end

	_M.OnAddonLoaded = function(self, addonName)
		if addonName == "OrzUI" then
			self:OnLoad();
		elseif addonName == "Blizzard_TradeSkillUI" then
			_M:HookTradeSkillFrame();
		end
	end

	_M.eventFrame:SetScript("OnEvent", _M.OnEvent);
	_M:SetEventHandler("ADDON_LOADED", _M.OnAddonLoaded);

	EasyDisenchantShowWindow = _M.InvokeWindowOpen; -- Expose window open function.
	EasyDisenchant = _M; -- Expose addon container.
end
