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


local GetInstanceInfo = GetInstanceInfo;
local IsLegacyDifficulty = IsLegacyDifficulty;
local IsEncounterComplete = C_RaidLocks.IsEncounterComplete;
local GetInstanceInfoForSelector = API.GetInstanceInfoForSelector;    --See ExpansionLandingPage\API_Encounter.lua
local GetInstanceEncounters = API.GetInstanceEncounters;


local DifficultyAnnouncer = CreateFrame("Frame", nil, UIParent, "PlumberPropagateMouseTemplate");    --Show difficulty when entering the instance
RaidCheck.DifficultyAnnouncer = DifficultyAnnouncer;
local SelectorUI = CreateFrame("Frame", nil, UIParent);             --Show difficulty selector at the entrance
RaidCheck.SelectorUI = SelectorUI;
DifficultyAnnouncer:Hide();
SelectorUI:Hide();
local DummyFrame;   --Edit Mode: For Reposition


local function SetupFont(fontString, fontHeight)
	local font = GameFontNormal:GetFont();
	fontString:SetFont(font, fontHeight or 14, "OUTLINE");
end


local ColorCodes = {
	"c6c6c6",   --Unfinished Instance
	"f55a4f",   --Red
	"5e5e5e",   --Grey
};


local MapThemes = {
	--1: Blue  2:Green
	[118] = 1,  --Icecrown
	--[646] = 2,  --Broken Shore
	--[885] = 2,  --Antoran Wastes
};


local Def = {
	ButtonMinWidth = 128,
	ButtonHeight = 32,
	ButtonGap = 4,
	BarTextureHeight = 48,
	BarSidePadding = 20,

	DefaultTopOffset = -40,
	SnapRangeX = 48,

	TextureFile = "Interface/AddOns/Plumber/Art/RaidCheck/DifficultySelector.png",
};


local function IsDisplayedRaidDifficultySolid()
	if IsInGroup() and (not IsInRaid()) and (not UnitIsGroupLeader("player")) then
		return false
	end
	return true
end


local function CanChangeDifficulty()
	if IsInInstance() then
		return false
	end

	if IsInGroup() and not UnitIsGroupLeader("player") then
		return false
	end

	if IsInGroup(2) then    --LE_PARTY_CATEGORY_INSTANCE
		return false
	end

	return true
end


local function ShowLockoutTooltip(self, instanceName, difficultyName, instanceID, showInstruction)
	local dead = BOSS_DEAD or "Defeated";
	local alive = BOSS_ALIVE or "Available";

	local tooltip = GameTooltip;
	tooltip:SetOwner(self, "ANCHOR_NONE");
	tooltip:SetPoint("TOP", self, "BOTTOM", 0, -6);
	tooltip:AddDoubleLine(instanceName, difficultyName);
	tooltip:SetCustomLineSpacing(4);

	for i, v in ipairs(self.encounters) do
		local hasDefeated = v.dungeonEncounterID and IsEncounterComplete(instanceID, v.dungeonEncounterID, v.difficultyID);
		if i % 5 == 1 then
			tooltip:AddLine(" ");
		end
		if hasDefeated then
			tooltip:AddDoubleLine(v.name, dead, 1, 1, 1, 1, 0.125, 0.125);
		else
			tooltip:AddDoubleLine(v.name, alive,  1, 1, 1, 0.1, 1, 0.1);
		end
	end

	if showInstruction then
		if not CanChangeDifficulty() then
			tooltip:AddLine(" ");
			tooltip:AddLine(L["Cannot Change Difficulty"], 1, 0.125, 0.125, true);
		end

		if SelectorUI.isRaid and API.HasActiveChatBox() and SelectorUI:CanLinkProgress(self.encounters[1].difficultyID) then
			tooltip:AddLine(" ");
			tooltip:AddLine(L["Instruction Link Progress In Chat"], 0.098, 1.000, 0.098, true);
		end
	end

	tooltip:Show();
end


local function GetSavedInstanceIndex(_instanceID, _difficultyID)
	local name, _, _, difficultyID, _, _, _, _, _, _, _, _, _, instanceID;
	local GetSavedInstanceInfo = GetSavedInstanceInfo;
	for i = 1, GetNumSavedInstances() do
		name, _, _, difficultyID, _, _, _, _, _, _, _, _, _, instanceID = GetSavedInstanceInfo(i);
		if difficultyID == _difficultyID and instanceID == _instanceID then
			return i
		end
	end
end


local function SetupFrameClamp(frame)
	frame:SetClampedToScreen(true);
	local offset = 8;
	frame:SetClampRectInsets(-offset, offset, offset, -offset);
end


local function LoadFramePosition()
	local setupFunc;
	local pos = PlumberDB and PlumberDB.InstanceDifficulty_Position;
	if pos and pos.x and pos.y then
		if math.abs(pos.x) < Def.SnapRangeX then
			pos.x = 0;
		end
		function setupFunc(f)
			f:SetPoint("TOP", UIParent, "TOP", pos.x, pos.y);
		end
	end

	if not setupFunc then
		function setupFunc(f)
			f:SetPoint("TOP", UIParent, "TOP", 0, Def.DefaultTopOffset);
		end
	end

	local frames = {DifficultyAnnouncer, SelectorUI, DummyFrame};
	for _, frame in ipairs(frames) do
		frame:ClearAllPoints();
		setupFunc(frame);
	end
end
RaidCheck.LoadFramePosition = LoadFramePosition;


local function SaveFramePosition()
	if not DummyFrame then return end;

	local x = DummyFrame:GetCenter();
	local y = DummyFrame:GetTop();

	if x and y then
		local x0 = UIParent:GetCenter();
		local y0 = UIParent:GetTop();
		x = x - x0;
		y = y - y0;
		if math.abs(x) < Def.SnapRangeX then
			x = 0;
		end
		if PlumberDB then
			PlumberDB.InstanceDifficulty_Position = {x = x, y = y};
		end
	else
		if PlumberDB then
			PlumberDB.InstanceDifficulty_Position = nil;
		end
	end

	LoadFramePosition();
end


local DifficultyButtonMixin = {};
do
	function DifficultyButtonMixin:OnEnter()
		self:UpdateVisual();
		SelectorUI:HighlightButton(self);
		SelectorUI.FocusSolver:SetFocus(self);
	end

	function DifficultyButtonMixin:OnLeave()
		self:UpdateVisual();
		SelectorUI:HighlightButton(nil);
		self:HideTooltip();
		SelectorUI.FocusSolver:SetFocus(nil);
	end

	function DifficultyButtonMixin:OnMouseDown()
		self.ButtonText:SetPoint("CENTER", self, "CENTER", 0, -1);
	end

	function DifficultyButtonMixin:OnMouseUp()
		self.ButtonText:SetPoint("CENTER", self, "CENTER", 0, 0);
	end

	function DifficultyButtonMixin:OnClick()
		if IsModifiedClick("CHATLINK") then
			SelectorUI:LinkProgressInChat(self.difficultyID);
			return
		end

		if SelectorUI:TrySelectDiffulty(self.difficultyID) then
			SelectorUI.LoadingIndicator:ClearAllPoints();
			SelectorUI.LoadingIndicator:SetPoint("CENTER", self, "CENTER", 0, 0);
			SelectorUI.LoadingIndicator:Show();
			PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
		end
	end

	function DifficultyButtonMixin:SetDifficulty(difficultyID, name)
		self.difficultyID = difficultyID;
		self.name = name;
		self.encounters = GetInstanceEncounters(SelectorUI.journalInstanceID, difficultyID);
		self:UpdateProgress();
		self.ButtonText:SetTextColor(1, 0.82, 0);
		return self.ButtonText:GetWrappedWidth();
	end

	function DifficultyButtonMixin:UpdateVisual()
		if self.selected then
			self.ButtonText:SetTextColor(1, 1, 1);
			self:SetAlpha(1);
		else
			if self:IsMouseMotionFocus() then
				self.ButtonText:SetTextColor(1, 1, 1);
			else
				self.ButtonText:SetTextColor(1, 0.82, 0);
			end
		end
	end

	function DifficultyButtonMixin:FormatButtonText(text, complete, total)
		local colorIndex;
		if complete < total then
			colorIndex = 1;
		else
			colorIndex = 2;
		end
		self.ButtonText:SetText(string.format("%s  |cff%s(%d/%d)|r", text, ColorCodes[colorIndex], complete, total));
	end

	function DifficultyButtonMixin:UpdateProgress()
		local total = self.encounters and #self.encounters or 0;
		local complete = 0;
		if total > 0 then
			for _, v in ipairs(self.encounters) do
				if v.dungeonEncounterID and IsEncounterComplete(SelectorUI.instanceID, v.dungeonEncounterID, self.difficultyID) then
					complete = complete + 1;
				end
			end
		end
		self:FormatButtonText(self.name, complete, total);
	end

	local function ButtonBackground_OnUpdate(self, elapsed)
		local complete;
		local diff = self.toX - self.x;
		local delta = elapsed * 12 * diff;

		if diff >= 0 and (diff < 1 or (self.x + delta >= self.toX)) then
			self.x = self.toX;
			complete = true;
		elseif diff <= 0 and (diff > -1 or (self.x + delta <= self.toX)) then
			self.x = self.toX;
			complete = true;
		else
			self.x = self.x + delta;
		end

		if complete then
			self:SetScript("OnUpdate", nil);
		end

		self:SetPoint("CENTER", self.owner, "CENTER", self.x, 0);
	end

	function DifficultyButtonMixin:UpdateSeleceted(playAnimation)
		self.selected = SelectorUI.selectedDifficulty and self.difficultyID == SelectorUI.selectedDifficulty;
		if self.selected then
			local ButtonBackground = SelectorUI.ButtonBackground;
			ButtonBackground.owner = self;
			if playAnimation then
				local x0 = self:GetCenter();
				local x1 = ButtonBackground:GetCenter();
				if x0 and x1 then
					ButtonBackground.toX = 0;
					ButtonBackground.x = x1 - x0;
					ButtonBackground:ClearAllPoints();
					ButtonBackground:SetScript("OnUpdate", ButtonBackground_OnUpdate);
					if math.abs(ButtonBackground.x) < 1 then
						playAnimation = false;
					end
				else
					playAnimation = false;
				end
			end
			if not playAnimation then
				ButtonBackground:ClearAllPoints();
				ButtonBackground:SetPoint("CENTER", self, "CENTER", 0, 0);
				ButtonBackground:SetScript("OnUpdate", nil);
			end
			ButtonBackground:Show();
			self:SetParent(SelectorUI.OpaqueFrame);
			ButtonBackground:SetParent(SelectorUI.OpaqueFrame);
		else
			self:SetParent(SelectorUI.Bar);
		end
		self:UpdateVisual();

		if self:IsMouseMotionFocus() then
			self:ShowTooltip();
		end
	end

	function DifficultyButtonMixin:ShowTooltip()
		ShowLockoutTooltip(self, SelectorUI.instanceName, self.name, SelectorUI.instanceID, true);
	end

	function DifficultyButtonMixin:HideTooltip()
		GameTooltip:Hide();
	end

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

	function DifficultyButtonMixin:OnLoad()
		self.ButtonText = self:CreateFontString(nil, "OVERLAY");
		SetupFont(self.ButtonText);
		self.ButtonText:SetJustifyH("CENTER");
		self.ButtonText:SetPoint("CENTER", self, "CENTER", 0, 0);

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


local TitleButtonMixin = {};
do
	local function CanResetInstances()
		if IsInInstance() then
			return false
		end

		if IsInGroup() and not UnitIsGroupLeader("player") then
			return false
		end

		if UnitPopupSharedUtil.HasLFGRestrictions() then
			return false
		end

		return true
	end

	function TitleButtonMixin:OnEnter()
		SelectorUI.Title:SetTextColor(1, 1, 1);

		local tooltip = GameTooltip;
		tooltip:SetOwner(self, "ANCHOR_RIGHT");
		tooltip:SetText(SelectorUI.instanceName, 1, 1, 1);
		tooltip:AddLine(L["Instruction Click To Open Adventure Guide"], 1, 0.82, 0, true);

		if CanResetInstances() then
			tooltip:AddLine(L["Instruction Alt Click To Reset Instance"], 1, 0.82, 0, true);
		else
			tooltip:AddLine(L["Cannot Reset Instance"], 0.5, 0.5, 0.5, true);
		end

		tooltip:Show();
	end

	function TitleButtonMixin:OnLeave()
		SelectorUI.Title:SetTextColor(0.9, 0.81, 0.67);
		GameTooltip:Hide();
	end

	function TitleButtonMixin:OnClick(button)
		if button == "LeftButton" then
			if IsModifiedClick("CHATLINK") then
				SelectorUI:LinkProgressInChat(self.difficultyID);
				return
			end
			SelectorUI:OpenEncounterJournal();
			GameTooltip:Hide();
		elseif button == "RightButton" and IsAltKeyDown() then
			if CanResetInstances() then
				StaticPopup_Show("CONFIRM_RESET_INSTANCES");
				GameTooltip:Hide();
			end
		end
	end

	function TitleButtonMixin:OnLoad()
		self:RegisterForClicks("LeftButtonUp", "RightButtonUp");
		self:SetScript("OnEnter", self.OnEnter);
		self:SetScript("OnLeave", self.OnLeave);
		self:SetScript("OnClick", self.OnClick);
	end
end


do  --SelectorUI
	local BAR_IDLE_ALPHA = 0.5;

	function SelectorUI:CreateBasicElements()
		local texture = Def.TextureFile;

		local OpaqueFrame = CreateFrame("Frame", nil, self);
		self.OpaqueFrame = OpaqueFrame;


		local Bar = CreateFrame("Frame", nil, self);
		self.Bar = Bar;
		Bar:SetSize(128, Def.BarTextureHeight);
		Bar:SetPoint("BOTTOM", self, "BOTTOM", 0, 0);


		local disableSharpenging = true;
		local BarBGs = API.CreateThreeSliceTextures(self.Bar, "BORDER", 20, Def.BarTextureHeight, 0, texture, disableSharpenging);
		self.BarBGs = BarBGs;
		BarBGs[1]:SetTexCoord(0/1024, 40/1024, 0/1024, 96/1024);
		BarBGs[2]:SetTexCoord(40/1024, 216/1024, 0/1024, 96/1024);
		BarBGs[3]:SetTexCoord(216/1024, 256/1024, 0/1024, 96/1024);


		local Title = self.OpaqueFrame:CreateFontString(nil, "OVERLAY");
		self.Title = Title;
		SetupFont(Title);
		Title:SetJustifyH("CENTER");
		Title:SetPoint("TOP", self, "TOP", 0, 0);
		Title:SetTextColor(0.9, 0.81, 0.67);    --AREA_NAME_FONT_COLOR
		--Title:SetShadowOffset(1, -1);


		local ButtonBackground = CreateFrame("Frame", nil, self.Bar);
		self.ButtonBackground = ButtonBackground;
		ButtonBackground:SetUsingParentLevel(true);
		ButtonBackground:SetSize(Def.ButtonMinWidth, Def.ButtonHeight);
		local ButtonBGs = API.CreateThreeSliceTextures(ButtonBackground, "ARTWORK", 16, Def.BarTextureHeight, 6, texture, disableSharpenging);
		self.ButtonBGs = ButtonBGs;
		ButtonBGs[1]:SetTexCoord(264/1024, 296/1024, 0/1024, 96/1024);
		ButtonBGs[2]:SetTexCoord(296/1024, 392/1024, 0/1024, 96/1024);
		ButtonBGs[3]:SetTexCoord(392/1024, 424/1024, 0/1024, 96/1024);
		ButtonBackground:SetPoint("CENTER", self.Bar, "CENTER", 0, 0);
	end

	function SelectorUI:Init()
		self.Init = nil;
		local texture = Def.TextureFile;


		self:SetSize(96, 64);
		self:SetPoint("TOP", UIParent, "TOP", 0, Def.DefaultTopOffset);
		self:SetAlpha(0);
		self:SetFrameStrata("HIGH");
		SetupFrameClamp(self);
		self:CreateBasicElements();
		self.Bar:SetAlpha(BAR_IDLE_ALPHA);


		local Shadow = self.Bar:CreateTexture(nil, "BACKGROUND");
		Shadow:SetTexture(texture);
		Shadow:SetTexCoord(768/1024, 1024/1024, 0/1024, 256/1024)
		Shadow:SetPoint("CENTER", self, "CENTER", 0, 0);
		Shadow:SetSize(1200, 400);
		Shadow:SetAlpha(0.65);


		local TitleButton = CreateFrame("Button", nil, self);
		self.TitleButton = TitleButton;
		Mixin(TitleButton, TitleButtonMixin);
		TitleButton:OnLoad();
		TitleButton:SetPoint("CENTER", self.Title, "CENTER", 0, 0);
		TitleButton:SetSize(32, 24);
		TitleButton:SetHitRectInsets(0, 0, -8, 0);


		local InaccuracyAlert = self.OpaqueFrame:CreateFontString(nil, "OVERLAY");
		self.InaccuracyAlert = InaccuracyAlert;
		SetupFont(InaccuracyAlert, 12);
		InaccuracyAlert:SetJustifyH("CENTER");
		InaccuracyAlert:SetPoint("TOP", self.Bar, "BOTTOM", 0, -4);
		InaccuracyAlert:SetSpacing(4);
		InaccuracyAlert:SetWidth(256);
		InaccuracyAlert:SetTextColor(1.000, 0.125, 0.125);
		InaccuracyAlert:SetText(L["Difficulty Not Accurate"]);
		InaccuracyAlert:Hide();


		local ButtonHighlight = self.Bar:CreateTexture(nil, "OVERLAY");
		self.ButtonHighlight = ButtonHighlight;
		ButtonHighlight:SetSize(Def.ButtonMinWidth, Def.BarTextureHeight);
		ButtonHighlight:SetTexture(texture);
		ButtonHighlight:SetTexCoord(432/1024, 560/1024, 0/1024, 96/1024);
		ButtonHighlight:SetBlendMode("ADD");
		ButtonHighlight:SetAlpha(0.5);
		ButtonHighlight:Hide();


		local function DifficultyButton_Create()
			local obj = CreateFrame("Button", nil, self.Bar);
			obj:SetSize(Def.ButtonMinWidth, Def.ButtonHeight);
			Mixin(obj, DifficultyButtonMixin);
			obj:OnLoad();
			return obj
		end

		local function DifficultyButton_OnAcquire(obj)
			obj:OnMouseUp();
		end

		local difficultyButtonPool = addon.LandingPageUtil.CreateObjectPool(DifficultyButton_Create, DifficultyButton_OnAcquire);
		self.difficultyButtonPool = difficultyButtonPool;


		local MotionFrame = CreateFrame("Frame", nil, self, "PlumberPropagateMouseTemplate");
		self.MotionFrame = MotionFrame;
		MotionFrame:SetSize(640, 128);
		MotionFrame:SetPoint("CENTER", self, "CENTER", 0, 0);
		MotionFrame:SetFrameLevel(10);
		MotionFrame:SetScript("OnEnter", function()
			self:BarFadeIn();
		end);
		MotionFrame:SetScript("OnLeave", function()
			self:BarFadeOut();
		end);


		local LoadingIndicator = CreateFrame("Frame", nil, self.OpaqueFrame, "PlumberLoadingIndicatorTemplate");
		self.LoadingIndicator = LoadingIndicator;
		LoadingIndicator.autoHide = true;
		LoadingIndicator:Hide();
		LoadingIndicator:SetFrameLevel(20);


		self.FocusSolver = API.CreateFocusSolver(self);
		self.FocusSolver:SetDelay(0.2);


		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnHide", self.OnHide);
		self:SetScript("OnEvent", self.OnEvent);

		LoadFramePosition();
	end

	function SelectorUI:OnShow()
		self:RegisterEvent("PLAYER_DIFFICULTY_CHANGED");
		self:RegisterEvent("UPDATE_INSTANCE_INFO");
		self:RegisterEvent("GROUP_ROSTER_UPDATE");
		self:RegisterEvent("PARTY_LEADER_CHANGED");
	end

	function SelectorUI:OnHide()
		self:UnregisterEvent("PLAYER_DIFFICULTY_CHANGED");
		self:UnregisterEvent("UPDATE_INSTANCE_INFO");
		self:UnregisterEvent("GROUP_ROSTER_UPDATE");
		self:UnregisterEvent("PARTY_LEADER_CHANGED");
		if self.LoadingIndicator then
			self.LoadingIndicator:Hide();
		end
		if self.OpaqueFrame then
			self.OpaqueFrame.t = 0;
			self.OpaqueFrame:SetScript("OnUpdate", nil);
		end
	end

	function SelectorUI:OnEvent(event, ...)
		if event == "PLAYER_DIFFICULTY_CHANGED" then
			self:UpdateDifficulty(true);
		elseif event == "UPDATE_INSTANCE_INFO" then
			self:UpdateInstanceProgress();
		elseif event == "GROUP_ROSTER_UPDATE" or event == "PARTY_LEADER_CHANGED" then
			self:RequestUpdate();
		end
	end

	local function FadeIn_OnUpdate(self, elapsed)
		self.alpha = self.alpha + 5 * elapsed;
		if self.alpha >= 1 then
			self:SetScript("OnUpdate", nil);
			self.alpha = 1;
		end
		self:SetAlpha(self.alpha);
	end

	local function FadeOut_OnUpdate(self, elapsed)
		self.alpha = self.alpha - 2 * elapsed;
		if self.alpha <= 0 then
			self:SetScript("OnUpdate", nil);
			self.alpha = 0;
			self:Hide();
		end
		self:SetAlpha(self.alpha);
	end

	local function BarFadeOut_OnUpdate(self, elapsed)
		self.alpha = self.alpha - 2 * elapsed;
		if self.alpha <= BAR_IDLE_ALPHA then
			self:SetScript("OnUpdate", nil);
			self.alpha = BAR_IDLE_ALPHA;
		end
		self:SetAlpha(self.alpha);
	end

	function SelectorUI:HideUI(instant)
		self.shown = false;
		if self:IsShown() then
			if instant then
				self:Hide();
				self:SetAlpha(0);
			else
				self.alpha = self:GetAlpha();
				self:SetScript("OnUpdate", FadeOut_OnUpdate);
			end
		else
			self:SetAlpha(0);
		end
	end

	function SelectorUI:BarFadeIn()
		self.Bar.alpha = self.Bar:GetAlpha();
		self.Bar:SetScript("OnUpdate", FadeIn_OnUpdate);
	end

	function SelectorUI:BarFadeOut()
		self.Bar.alpha = self.Bar:GetAlpha();
		self.Bar:SetScript("OnUpdate", BarFadeOut_OnUpdate);
	end

	function SelectorUI:ShowInstance(journalInstanceID, uiMapID)
		if self.inEditMode then
			return
		end

		if journalInstanceID ~= self.journalInstanceID then
			local themeID = uiMapID and MapThemes[uiMapID] or 0;

			self.journalInstanceID = journalInstanceID;
			local instanceInfo = GetInstanceInfoForSelector(journalInstanceID);
			self.isRaid = instanceInfo.isRaid;
			self.instanceID = instanceInfo.instanceID;
			local difficulties = instanceInfo and instanceInfo.difficulties;
			if difficulties and #difficulties > 0 then
				if self.Init then
					self:Init();
				end

				self:SetTheme(themeID);

				self.fallbackDifficultyID = difficulties[#difficulties].difficultyID;
				self.isValidDifficulty = {};
				self.instanceName = instanceInfo.name;
				self.Title:SetText(instanceInfo.name);
				self.TitleButton:SetWidth(math.max(API.Round(self.Title:GetWrappedWidth() + 8), Def.ButtonMinWidth));

				self.difficultyButtonPool:ReleaseAll();
				local textWidth = 0;
				local maxTextWidth = 0;

				for i, v in ipairs(difficulties) do
					local button = self.difficultyButtonPool:Acquire();
					textWidth = button:SetDifficulty(v.difficultyID, v.text);
					if textWidth > maxTextWidth then
						maxTextWidth = textWidth;
					end
					self.isValidDifficulty[v.difficultyID] = true;
				end

				local buttonWidth = math.max(Def.ButtonMinWidth, API.Round(maxTextWidth + Def.ButtonHeight));
				local barWidth = 2 * Def.BarSidePadding + #difficulties * (buttonWidth + Def.ButtonGap) - Def.ButtonGap;
				self.Bar:SetWidth(barWidth);
				self.ButtonBackground:SetWidth(buttonWidth);
				self.ButtonHighlight:SetWidth(buttonWidth);
				self.MotionFrame:SetWidth(barWidth + 64);

				for i, obj in self.difficultyButtonPool:EnumerateActive() do
					obj:SetPoint("LEFT", self.Bar, "LEFT", Def.BarSidePadding + (i - 1) * (buttonWidth + Def.ButtonGap), 0);
					obj:SetWidth(buttonWidth);
				end

				self.isLegacyRaid = instanceInfo.isLegacyRaid;

				self:UpdateDifficulty(false);
				self.hasInstanceData = true;
			else
				if self.difficultyButtonPool then
					self.difficultyButtonPool:ReleaseAll();
				end
				self:HideUI();
				self.hasInstanceData = nil;
				self.isValidDifficulty = {};
				return
			end
		end

		if (not self.shown) and self.hasInstanceData then
			self.shown = true;
			self.alpha = self:GetAlpha();
			self:SetScript("OnUpdate", FadeIn_OnUpdate);
			self:UpdateDifficulty(false);
			self:UpdateInstanceProgress();
			RequestRaidInfo();
			self:Show();
		end
	end

	function SelectorUI:SetTheme(themeID)
		if not themeID then themeID = 0 end;
		if themeID == self.themeID then return end;
		self.themeID = themeID;
		if self.Init then return end;

		local top = (themeID * 96)/1024;
		local bottom = ((themeID + 1) * 96)/1024;
		self.BarBGs[1]:SetTexCoord(0/1024, 40/1024, top, bottom);
		self.BarBGs[2]:SetTexCoord(40/1024, 216/1024, top, bottom);
		self.BarBGs[3]:SetTexCoord(216/1024, 256/1024, top, bottom);
		self.ButtonBGs[1]:SetTexCoord(264/1024, 296/1024, top, bottom);
		self.ButtonBGs[2]:SetTexCoord(296/1024, 392/1024, top, bottom);
		self.ButtonBGs[3]:SetTexCoord(392/1024, 424/1024, top, bottom);
		if self.ButtonHighlight then
			self.ButtonHighlight:SetTexCoord(432/1024, 560/1024, top, bottom);
		end
	end

	function SelectorUI:UpdateDifficulty(playAnimation)
		if self.difficultyButtonPool then
			self.ButtonBackground:Hide();
			self.ButtonBackground:SetParent(self.Bar);
			self.LoadingIndicator:Hide();
			self.InaccuracyAlert:Hide();

			local selectedDifficulty;
			if self.isRaid then
				if IsDisplayedRaidDifficultySolid() then
					local difficultyID1, difficultyID2 = DataProvider:GetRaidDifficultyID();
					if self.isLegacyRaid then
						selectedDifficulty = difficultyID2;
					else
						selectedDifficulty = difficultyID1;
					end
				else
					self.InaccuracyAlert:Show();
				end
			else
				selectedDifficulty = DataProvider:GetDungeonDifficultyID();
			end
			self.selectedDifficulty = selectedDifficulty;

			self.difficultyButtonPool:CallMethod("UpdateSeleceted", playAnimation);
		end
	end

	function SelectorUI:UpdateInstanceProgress()
		if self.difficultyButtonPool then
			self.difficultyButtonPool:CallMethod("UpdateProgress");
		end
	end

	local function UpdateFrame_OnUpdate(self, elapsed)
		self.t = self.t + elapsed;
		if self.t >= 0.5 then
			self.t = 0;
			self:SetScript("OnUpdate", nil);
			SelectorUI:UpdateDifficulty(true);
		end
	end

	function SelectorUI:RequestUpdate()
		self.OpaqueFrame.t = 0;
		self.OpaqueFrame:SetScript("OnUpdate", UpdateFrame_OnUpdate);
	end

	function SelectorUI:TrySelectDiffulty(difficultyID)
		if difficultyID == self.selectedDifficulty then return end;

		GameTooltip:Hide();

		local canChange = CanChangeDifficulty();

		if self.isRaid then
			if IsLegacyDifficulty(difficultyID) then
				SetLegacyRaidDifficultyID(difficultyID);
			else
				SetRaidDifficultyID(difficultyID);
			end
		else
			self.isLegacyRaid = nil;
			SetDungeonDifficultyID(difficultyID);
		end

		return true and canChange
	end

	function SelectorUI:HighlightButton(button)
		self.ButtonHighlight:Hide();
		self.ButtonHighlight:ClearAllPoints();
		if button then
			self.ButtonHighlight:SetPoint("CENTER", button, "CENTER", 0, 0);
			self.ButtonHighlight:Show();
		end
	end

	function SelectorUI:OpenEncounterJournal()
		if not API.CheckAndDisplayErrorIfInCombat() then
			if not EncounterJournal_OpenJournal then
				C_AddOns.LoadAddOn("Blizzard_EncounterJournal");
			end
			if EncounterJournal_OpenJournal then
				local difficultyID = self.selectedDifficulty;
				if not self.isValidDifficulty[difficultyID] then
					difficultyID = self.fallbackDifficultyID;
				end
				EncounterJournal_OpenJournal(difficultyID, self.journalInstanceID);
			end
		end
	end

	function SelectorUI:GetSavedInstanceIndex(difficultyID)
		if not difficultyID then
			difficultyID = self.selectedDifficulty;
		end

		if not self.isValidDifficulty[difficultyID] then
			difficultyID = self.fallbackDifficultyID;
		end

		return GetSavedInstanceIndex(self.instanceID, difficultyID);
	end

	function SelectorUI:CanLinkProgress(difficultyID)
		return self:GetSavedInstanceIndex(difficultyID) ~= nil
	end

	function SelectorUI:LinkProgressInChat(difficultyID)
		--Dungeon progress sent as plain text for some reason?

		local index = self:GetSavedInstanceIndex(difficultyID);
		if index then
			return API.ChatInsertLink(GetSavedInstanceChatLink(index));  --Dungeon progress sent as plain text for some reason?
		end
	end
end


do  --DifficultyAnnouncer
	DifficultyAnnouncer:SetFrameStrata("HIGH");
	local UpdateFrame;

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

		self:SetSize(128, 32);
		self:SetPoint("TOP", UIParent, "TOP", 0, Def.DefaultTopOffset);
		self:SetFrameStrata("HIGH");
		SetupFrameClamp(self);

		local Text1 = self:CreateFontString(nil, "OVERLAY");
		self.Text1 = Text1;
		SetupFont(Text1, 12);
		Text1:SetJustifyH("CENTER");
		Text1:SetPoint("TOP", self, "TOP", 0, 0);
		Text1:SetTextColor(0.9, 0.81, 0.67);

		local Text2 = self:CreateFontString(nil, "OVERLAY");
		self.Text2 = Text2;
		Text2:SetJustifyH("CENTER");
		SetupFont(Text2, 16);
		Text2:SetPoint("TOP", Text1, "BOTTOM", 0, -4);


		--[[
		local Banner = self:CreateTexture(nil, "ARTWORK");
		self.Banner = Banner;
		local bannerHeight = 48;
		local bannerRatio = 6;
		Banner:SetSize(bannerRatio * bannerHeight, 48);
		Banner:SetPoint("CENTER", self, "CENTER", 0, 0);
		Banner:SetTexCoord(0, 0.7617187, 0, 0.65625);

		local offsetH = 0.1;
		local coordV = (0.7617187 - 2 * offsetH)/bannerRatio/2;
		Banner:SetTexCoord(offsetH, 0.7617187 - offsetH, 0.65625/2 - coordV, 0.65625/2 + coordV)
		--]]


		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnHide", self.OnHide);
		self:SetScript("OnEnter", self.OnEnter);
		self:SetScript("OnLeave", self.OnLeave);
		self:SetScript("OnMouseDown", self.OnMouseDown);

		LoadFramePosition();
	end

	function DifficultyAnnouncer:Enable(state)
		if state then
			--self:RegisterEvent("RAID_INSTANCE_WELCOME");
			self:RegisterEvent("LOADING_SCREEN_DISABLED");
			self:SetScript("OnEvent", self.OnEvent);
			if not UpdateFrame then
				UpdateFrame = CreateFrame("Frame");
			end
		else
			self.instanceID = nil;
			self:Hide();
			--self:UnregisterEvent("RAID_INSTANCE_WELCOME");
			self:UnregisterEvent("LOADING_SCREEN_DISABLED");
			if UpdateFrame then
				UpdateFrame:SetScript("OnUpdate", nil);
			end
			self:SetScript("OnUpdate", nil);
		end
	end

	function DifficultyAnnouncer:OnEvent(event, ...)
		if event == "RAID_INSTANCE_WELCOME" then
			self:RequestUpdate();
		elseif event == "LOADING_SCREEN_DISABLED" then
			self:RequestUpdate();
		elseif event == "PLAYER_ENTERING_WORLD" then
			self:RequestUpdate();
		elseif event == "UPDATE_INSTANCE_INFO" then
			self:UpdateProgress();
		end
	end

	function DifficultyAnnouncer:OnShow()
		self:RegisterEvent("UPDATE_INSTANCE_INFO");
	end

	function DifficultyAnnouncer:OnHide()
		self:UnregisterEvent("UPDATE_INSTANCE_INFO");
		self.focused = false;
	end

	local function UpdateFrame_OnUpdate(self, elapsed)
		self.t = self.t + elapsed;
		if self.t > 1 then
			self.t = 0;
			self:SetScript("OnUpdate", nil);
			DifficultyAnnouncer:Refresh();
		end
	end

	function DifficultyAnnouncer:RequestUpdate()
		UpdateFrame.t = 0;
		UpdateFrame:SetScript("OnUpdate", UpdateFrame_OnUpdate);
	end

	local function FadeOut_OnUpdate(self, elapsed)
		self.alpha = self.alpha - 2 * elapsed;
		if self.alpha <= 0 then
			self:SetScript("OnUpdate", nil);
			self.alpha = 0;
			self:Hide();
		end
		self:SetAlpha(self.alpha);
	end

	function DifficultyAnnouncer:OnUpdate(elapsed)
		if not self.focused then
			self.t = self.t + elapsed;
		end
		if self.t > 4 then
			self.t = 0;
			self.alpha = self:GetAlpha();
			self:SetScript("OnUpdate", FadeOut_OnUpdate);
		end
	end

	function DifficultyAnnouncer:Refresh()
		local name, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceID, instanceGroupSize, LfgDungeonID = GetInstanceInfo();
		if instanceID == self.instanceID then
			return
		end

		if difficultyID and difficultyID > 0 and instanceID and instanceType and (instanceType == "raid" or instanceType == "party" or instanceType == "scenario") then
			--local difficultyName = API.GetRaidDifficultyString(difficultyID);
			self.instanceName = name;
			self.difficultyName = difficultyName;
			self.instanceID = instanceID;
			self.difficultyID = difficultyID;
			if self.Init then
				self:Init();
			end
			self.Text1:SetText(name);
			self.Text2:SetText(difficultyName);

			local journalInstanceID = C_EncounterJournal.GetInstanceForGameMap(instanceID);
			if journalInstanceID then
				--EJ_SelectInstance(journalInstanceID);
				--local instanceName, description, bgImage, _, loreImage, buttonImage, dungeonAreaMapID, _, _, mapID, covenantID = EJ_GetInstanceInfo();
				--self.Banner:SetTexture(loreImage);
				self.encounters = GetInstanceEncounters(journalInstanceID, difficultyID);
			else
				self.encounters = nil;
			end

			RequestRaidInfo();
			self:UpdateProgress();
			self:SetAlpha(1);
			self:Show();
			self:StartHideCountdown();
		else
			self.difficultyName = nil;
			self.instanceID = nil;
			self:Hide();
			self.encounters = nil;
		end
	end

	function DifficultyAnnouncer:UpdateProgress()
		if self.encounters and self.difficultyName then
			local complete = 0;
			local total = #self.encounters;
			for _, v in ipairs(self.encounters) do
				if v.dungeonEncounterID and IsEncounterComplete(self.instanceID, v.dungeonEncounterID, self.difficultyID) then
					complete = complete + 1;
				end
			end

			local colorIndex;
			if complete < total then
				colorIndex = 1;
			else
				colorIndex = 2;
			end
			self.Text2:SetText(string.format("%s  |cff%s(%d/%d)|r", self.difficultyName, ColorCodes[colorIndex], complete, total));
		end
	end

	function DifficultyAnnouncer:OnEnter()
		if self.encounters then
			ShowLockoutTooltip(self, self.instanceName, self.difficultyName, self.instanceID);
		end
		self.focused = true;
	end

	function DifficultyAnnouncer:OnLeave()
		GameTooltip:Hide();
		self.focused = false;
	end

	function DifficultyAnnouncer:OnMouseDown(button)
		if button == "RightButton" then
			self:Hide();
			if UpdateFrame then
				UpdateFrame:SetScript("OnUpdate", nil);
			end
		end
	end

	function DifficultyAnnouncer:StartHideCountdown()
		self.t = 0;
		self:SetScript("OnUpdate", self.OnUpdate);
	end

	addon.CallbackRegistry:RegisterSettingCallback("InstanceDifficulty", function(state, userInput)
		if state and userInput then
			DifficultyAnnouncer:Refresh();
		end
	end);
end


do  --Edit Mode Reposition
	local DummyFrameMixin = {};

	function RaidCheck.ExitEditMode()
		if DummyFrame then
			DummyFrame:ExitEditMode();
		end
		SelectorUI.inEditMode = nil;
	end

	function DummyFrameMixin:OnDragStart()
		local scale = self:GetEffectiveScale();
		local x0, y0 = self:GetCenter();
		local x1, y1 = GetCursorPosition();
		local deltaX = (x0 * scale) - x1;
		local deltaY = (y0 * scale) - y1;

		self.centerX = UIParent:GetCenter();
		self.snapLeft = self.centerX - 0.5*Def.SnapRangeX;
		self.snapRight = self.centerX + 0.5*Def.SnapRangeX;

		self.positionGetter = function()
			local x, y = GetCursorPosition();
			return (x + deltaX) / scale, (y + deltaY) / scale;
		end

		self.t = 0;
		self:ClearAllPoints();
		self:SetScript("OnUpdate", self.OnUpdate);
	end

	function DummyFrameMixin:OnDragStop()
		self.t = nil;
		self.centerX = nil;
		self.snapLeft = nil;
		self.snapRight = nil;
		self.positionGetter = nil;
		self:SetScript("OnUpdate", nil);
		if self:IsShown() then
			SaveFramePosition();
		end
	end

	function DummyFrameMixin:OnHide()
		self:Hide();
		self:OnDragStop();
		self.Selection:Hide();
	end

	function DummyFrameMixin:OnUpdate(elapsed)
		self.t = self.t + elapsed;
		if self.t > 0.016 then
			self.t = 0;
			local x, y = self.positionGetter();
			if x > self.snapLeft and x < self.snapRight then
				x = self.centerX;
			end
			self:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
			self:UpdateInstructionPosition();
		end
	end

	function DummyFrameMixin:IsFocused()
		return self:IsVisible() and self:IsMouseOver();
	end

	function DummyFrameMixin:ShowOptions(state)
		self.Instruction:SetShown(state);
	end

	function DummyFrameMixin:UpdateInstructionPosition()
		local bottom = self:GetBottom();
		local position;
		if bottom - self.instructionHeight < 0 then
			position = 1;
		else
			position = -1;
		end
		if position ~= self.instructionPos then
			self.instructionPos = position;
			self.Instruction:ClearAllPoints();
			if position > 0 then
				self.Instruction:SetPoint("BOTTOM", self, "TOP", 0, 12);
			else
				self.Instruction:SetPoint("TOP", self, "BOTTOM", 0, -12);
			end
		end
	end

	function DummyFrameMixin:ExitEditMode()
		self:Hide();
		SelectorUI.inEditMode = nil;
		addon.CallbackRegistry:Trigger("SettingsPanel.ModuleOptionClosed");
	end

	function DummyFrameMixin:OnRightButtonDown()
		self:ResetPosition();
	end

	function DummyFrameMixin:ResetPosition()
		self:SetPoint("TOP", UIParent, "TOP", 0, Def.DefaultTopOffset);
		if PlumberDB and PlumberDB.InstanceDifficulty_Position then
			PlumberDB.InstanceDifficulty_Position = nil;
		end
		LoadFramePosition();
	end

	local function DummyFrame_Init()
		if DummyFrame then return end;

		DummyFrame = CreateFrame("Frame", nil, UIParent);
		local self = DummyFrame;
		self:SetSize(96, 64);
		self:SetPoint("TOP", UIParent, "TOP", 0, Def.DefaultTopOffset);
		self:SetFrameStrata("HIGH");
		SetupFrameClamp(self);
		Mixin(self, DummyFrameMixin);

		SelectorUI.CreateBasicElements(self);
		SelectorUI.SetTheme(self);

		self.Title:SetText(L["Instance Name"]);

		local function CreateButton(text, selected)
			local f = CreateFrame("Frame", nil, self.Bar);
			f:SetSize(Def.ButtonMinWidth, Def.ButtonHeight);
			f.ButtonText = f:CreateFontString(nil, "OVERLAY", nil, 5);
			SetupFont(f.ButtonText);
			f.ButtonText:SetJustifyH("CENTER");
			f.ButtonText:SetPoint("CENTER", f, "CENTER", 0, 0);
			DifficultyButtonMixin.FormatButtonText(f, text, 0, 8);
			if selected then
				self.ButtonBackground:SetPoint("CENTER", f, "CENTER", 0, 0);
				f.ButtonText:SetTextColor(1, 1, 1);
			else
				f.ButtonText:SetTextColor(1, 0.82, 0);
			end
			return f
		end

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

		Instruction:Hide();
		Instruction:SetWidth(240);
		Instruction:SetPoint("TOP", self, "BOTTOM", 0, -12);
		Instruction:SetTextColor(1, 1, 1);
		Instruction:SetSpacing(2);
		Instruction:SetText(L["EditMode Instruction InstanceDifficulty"]);
		self.instructionHeight = math.ceil(Instruction:GetHeight() + 12);
		self:UpdateInstructionPosition();

		local buttonTexts = {PLAYER_DIFFICULTY1, PLAYER_DIFFICULTY2};
		local numButtons = #buttonTexts;
		local maxTextWidth = 0;
		local buttons = {};

		for i, text in ipairs(buttonTexts) do
			local button = CreateButton(text, i == 1);
			buttons[i] = button;
			local textWidth = button.ButtonText:GetWrappedWidth();
			if textWidth > maxTextWidth then
				maxTextWidth = textWidth;
			end
		end

		local buttonWidth = math.max(Def.ButtonMinWidth, API.Round(maxTextWidth + Def.ButtonHeight));
		local barWidth = 2 * Def.BarSidePadding + numButtons * (buttonWidth + Def.ButtonGap) - Def.ButtonGap;
		self.Bar:SetWidth(barWidth);
		self.ButtonBackground:SetWidth(buttonWidth);
		self:SetWidth(barWidth);

		for i, obj in ipairs(buttons) do
			obj:SetPoint("LEFT", self.Bar, "LEFT", Def.BarSidePadding + (i - 1) * (buttonWidth + Def.ButtonGap), 0);
			obj:SetWidth(buttonWidth);
		end


		self.selectionOffsetTop = 4;
		local uiName = L["ModuleName InstanceDifficulty"];
		local hideLabel = true;
		self.Selection = addon.CreateEditModeSelection(self, uiName, hideLabel);

		addon.AddModuleOptionExitMethod(self, RaidCheck.ExitEditMode);

		LoadFramePosition();
	end


	function RaidCheck.EnterEditMode()
		DummyFrame_Init();
		DummyFrame:Show();
		DummyFrame.Selection:Show();
		SelectorUI:HideUI(true);
		SelectorUI.inEditMode = true;
	end

	function RaidCheck.ToggleEditMode()
		local newState = not (DummyFrame and DummyFrame:IsVisible());
		if newState then
			RaidCheck.EnterEditMode();
		else
			RaidCheck.ExitEditMode();
		end
	end
end
