local _, addon = ...

local L = addon.L;
local Round = addon.API.Round;
local GetItemQualityColor = addon.API.GetItemQualityColor;

local SharedTooltip = CreateFrame("Frame");
addon.SharedTooltip = SharedTooltip;

SharedTooltip:Hide();
SharedTooltip:SetSize(16, 16);
SharedTooltip:SetIgnoreParentScale(true);
SharedTooltip:SetIgnoreParentAlpha(true);
SharedTooltip:SetFrameStrata("TOOLTIP");
SharedTooltip:SetFixedFrameStrata(true);
SharedTooltip:SetClampedToScreen(true);
SharedTooltip:SetClampRectInsets(-4, 4, 4, -4);

SharedTooltip.ShowFrame = SharedTooltip.Show;
SharedTooltip.HideFrame = SharedTooltip.Hide;

local LINE_TYPE_PRICE = Enum.TooltipDataLineType.SellPrice or 11;

local FONT_LARGE = "GameTooltipHeaderText";
local FONT_MEDIUM = "GameTooltipText";
local FONT_SMALL = "GameTooltipTextSmall";
local FONT_HEIGHT_MEDIUM = 12;
local FONTSTRING_MIN_GAP = 24;  --Betweeb the left and the right text of the same line
local SELL_PRICE_TEXT = (SELL_PRICE or "Sell Price").."  ";

do
	local _;
	_, FONT_HEIGHT_MEDIUM = _G[FONT_MEDIUM]:GetFont();
	FONT_HEIGHT_MEDIUM = Round(FONT_HEIGHT_MEDIUM);
end

local SPACING_NEW_LINE = 4;     --Between paragraphs
local SPACING_INTERNAL = 2;     --Within the same paragraph
local TOOLTIP_PADDING = 8;
local TOOLTIP_MAX_WIDTH = 320;
local FONTSTRING_MAX_WIDTH = TOOLTIP_MAX_WIDTH - 2*TOOLTIP_PADDING;


local C_TooltipInfo = C_TooltipInfo;
local unpack = unpack;
local pairs = pairs;
local floor = math.floor;


local function EvaluateWidth(fontString, lastMax)
	local width = fontString:GetWrappedWidth();
	return ((width > lastMax) and width) or lastMax
end

function SharedTooltip:Init()
	if not self.Background then
		self.Background = addon.CreateNineSliceFrame(self, "Tooltip_Brown");
		self.Background:CoverParent(0);
	end
	if not self.Content then
		self.Content = CreateFrame("Frame", nil, self);
		self.Content:SetPoint("TOPLEFT", self, "TOPLEFT", TOOLTIP_PADDING, -TOOLTIP_PADDING);
		self.Content:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -TOOLTIP_PADDING, TOOLTIP_PADDING);
	end

	if not self.fontStrings then
		self.fontStrings = {};
	end

	if not self.HotkeyIcon then
		self.HotkeyIcon = addon.CreateHotkeyIcon(self);
		local responsive = true;
		self.HotkeyIcon:SetKey("Alt", responsive);
		self.HotkeyIcon:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 2);
		self.HotkeyIcon:Hide();
	end
	self.Init = nil;
end

function SharedTooltip:ClearLines()
	if self.numLines == 0 then return end;

	self.numLines = 0;
	self.numCols = 1;
	self.numFontStrings = 0;
	self.dataInstanceID = nil;
	self.fontChanged = false;
	self.grid = {};
	self.useGridLayout = false;

	if self.fontStrings then
		for _, fontString in pairs(self.fontStrings) do
			fontString:Hide();
			fontString:SetText(nil);
		end
	end

	self:SetAlternateTooltipFunc(nil);
end

function SharedTooltip:SetOwner(owner, anchor, offsetX, offsetY)
	if self.Init then
		self:Init();
	end

	self.owner = owner;
	anchor = anchor or "ANCHOR_BOTTOM";
	offsetX = offsetX or 0;
	offsetY = offsetY or 0;

	self:ClearAllPoints();
	self:SetPoint("BOTTOMLEFT", owner, "TOPRIGHT", offsetX, offsetY);   --only need one layout for now
end

function SharedTooltip:SetLineFont(fontString, sizeIndex)
	if fontString.sizeIndex ~= sizeIndex then
		fontString.sizeIndex = sizeIndex;
		if sizeIndex == 1 then
			fontString:SetFontObject(FONT_LARGE);
		elseif sizeIndex == 2 then
			fontString:SetFontObject(FONT_MEDIUM);
		elseif sizeIndex == 3 then
			fontString:SetFontObject(FONT_SMALL);
		end
		return true
	end
end

function SharedTooltip:SetLineAlignment(fontString, alignIndex)
	if fontString.alignIndex ~= alignIndex then
		fontString.alignIndex = alignIndex;
		if alignIndex == 1 then
			fontString:SetJustifyH("LEFT");
		elseif alignIndex == 2 then
			fontString:SetJustifyH("CENTER");
		elseif alignIndex == 3 then
			fontString:SetJustifyH("RIGHT");
		end
		return true
	end
end

function SharedTooltip:AcquireFontString()
	local n = self.numFontStrings + 1;
	self.numFontStrings = n;

	if not self.fontStrings[n] then
		self.fontStrings[n] = self.Content:CreateFontString(nil, "ARTWORK", FONT_MEDIUM);
		self.fontStrings[n]:SetSpacing(SPACING_INTERNAL);
		self.fontStrings[n].sizeIndex = 2;
		self.fontStrings[n].alignIndex = 1;
		self.fontStrings[n]:SetJustifyV("MIDDLE");
	end

	return self.fontStrings[n]
end

function SharedTooltip:AddText(text, r, g, b, wrapText, offsetY, sizeIndex, alignIndex)
	r = r or 1;
	g = g or 1;
	b = b or 1;
	wrapText = (wrapText == true) or false;
	offsetY = offsetY or -SPACING_NEW_LINE;
	sizeIndex = sizeIndex or 2;
	alignIndex = alignIndex or 1;

	local fs = self:AcquireFontString();

	local fontChanged = self:SetLineFont(fs, sizeIndex);
	if fontChanged then
		self.fontChanged = true;
	end
	self:SetLineAlignment(fs, alignIndex);

	fs:ClearAllPoints();
	fs:SetPoint("TOPLEFT", self.Content, "TOPLEFT", 0, 0);

	fs:SetWidth(FONTSTRING_MAX_WIDTH);
	fs:SetText(text);
	fs:SetTextColor(r, g, b, 1);
	fs:Show();
	fs.inGrid = nil;

	return fs
end

function SharedTooltip:SetGridLine(row, col, text, r, g, b, sizeIndex, alignIndex)
	if not text then return end

	if self.grid[row] and self.grid[row][col] then
		self.grid[row][col]:SetText("(Occupied)")
		return
	end

	local fs = self:AddText(text, r, g, b, nil, nil, sizeIndex, alignIndex);
	fs.inGrid = true;

	if not self.grid[row] then
		self.grid[row] = {};
	end
	self.grid[row][col] = fs;

	if row > self.numLines then
		self.numLines = row;
	end

	if col > self.numCols then
		self.numCols = col;
	end

	self.useGridLayout = true;
end

function SharedTooltip:AddLeftLine(text, r, g, b, wrapText, offsetY, sizeIndex)
	--This will start a new line

	if not text then return end
	local n = self.numLines + 1;
	self.numLines = n;

	local alignIndex = 1;
	local fs = self:AddText(text, r, g, b, wrapText, offsetY, sizeIndex, alignIndex);

	if not self.grid[n] then
		self.grid[n] = {};
	end
	self.grid[n][1] = fs;
end

function SharedTooltip:AddCenterLine(text, r, g, b, wrapText, offsetY, sizeIndex)
	--This will also start a new line
	--Align to the center

	if not text then return end
	local n = self.numLines + 1;
	self.numLines = n;

	local alignIndex = 2;
	local fs = self:AddText(text, r, g, b, wrapText, offsetY, sizeIndex, alignIndex);

	if not self.grid[n] then
		self.grid[n] = {};
	end
	self.grid[n][1] = fs;
end

function SharedTooltip:AddRightLine(text, r, g, b, wrapText, offsetY, sizeIndex)
	--Right line must come in pairs with a LeftLine
	--This will NOT start a new line

	if not text then return end
	local alignIndex = 3;
	local fs = self:AddText(text, r, g, b, wrapText, offsetY, sizeIndex, alignIndex);
	local n = self.numLines;

	if not self.grid[n] then
		self.grid[n] = {};
	end
	self.grid[n][2] = fs;

	self.numCols = 2;
end

function SharedTooltip:AddBlankLine()
	local n = self.numLines + 1;
	self.numLines = n;
	self.grid[n] = {
		gap = SPACING_NEW_LINE,
	};
end

function SharedTooltip:ProcessInfo(info)
	self.lastInfo = info;

	if not info then
		return false
	end

	local tooltipData;
	if info.getterArgs then
		tooltipData = C_TooltipInfo[info.getterName](unpack(info.getterArgs));
	else
		tooltipData = C_TooltipInfo[info.getterName]();
	end

	self:ClearLines();
	self:SetScript("OnUpdate", nil);

	local success = self:ProcessTooltipData(tooltipData);

	if success then
		self:Show();
	else
		self:Hide();
	end
end

local OVERRIDE_COLORS = {
	["ffa335ee"] = 4,
	["ff0070dd"] = 3,
};

local function GenerateMoneyText(rawCopper)
	--Price
	local text;
	local gold = floor(rawCopper / 10000);
	local silver = floor((rawCopper - gold * 10000) / 100);
	local copper = floor(rawCopper - gold * 10000 - silver * 100);
	local iconOffsetX = 2;

	local goldText, silverText, copperText;

	if copper > 0 then
		copperText = string.format("%s|TInterface/AddOns/Plumber/Art/BackpackItemTracker/CoinSymbol:0:0:%s:-%s:128:32:64:96:0:32|t", copper, iconOffsetX, SPACING_INTERNAL);
		text = copperText;
	end

	if gold ~= 0 or silver ~= 0 then
		silverText = string.format("%s|TInterface/AddOns/Plumber/Art/BackpackItemTracker/CoinSymbol:0:0:%s:-%s:128:32:32:64:0:32|t", silver, iconOffsetX, SPACING_INTERNAL);
		if gold > 0 then
			goldText = string.format("%s|TInterface/AddOns/Plumber/Art/BackpackItemTracker/CoinSymbol:0:0:%s:-%s:128:32:0:32:0:32|t", gold, iconOffsetX, SPACING_INTERNAL);
			if text then
				text = goldText.." "..silverText.." "..text;
			elseif silver == 0 then
				text = goldText;
			else
				text = goldText.." "..silverText;
			end
		else
			if text then
				text = silverText.." "..text;
			else
				text = silverText;
			end
		end
	end

	return text
end

function SharedTooltip:ProcessTooltipData(tooltipData)
	if not (tooltipData and tooltipData.lines) then
		return false
	end

	self.dataInstanceID = tooltipData.dataInstanceID;
	self:RegisterEvent("TOOLTIP_DATA_UPDATE");

	local leftText, leftColor, wrapText, rightText, leftOffset;
	local r, g, b;

	for i, lineData in ipairs(tooltipData.lines) do
		leftText = lineData.leftText;
		leftColor = lineData.leftColor or NORMAL_FONT_COLOR;
		rightText = lineData.rightText;
		wrapText = lineData.wrapText or false;
		leftOffset = lineData.leftOffset;

		if leftText then
			if leftText == " "then
				--A whole blank line is too tall, so we change its height
				self:AddBlankLine();
			else
				if i == 1 then
					local hex = leftColor:GenerateHexColor();
					if OVERRIDE_COLORS[hex] then
						leftColor = GetItemQualityColor( OVERRIDE_COLORS[hex] );
					end
				end

				if lineData.type ~= LINE_TYPE_PRICE then
					r, g, b = leftColor:GetRGB();
					self:AddLeftLine(leftText, r, g, b, wrapText, nil, (i == 1 and 1) or 2);
				elseif lineData.price and lineData.price > 0 then
					local cointText = GenerateMoneyText(lineData.price);
					if cointText then
						leftText = SELL_PRICE_TEXT .. cointText;
						--self:AddBlankLine();
						self:AddLeftLine(leftText, 1, 1, 1, false, nil, 2);
					end
				end
			end
		end
	end

	return true
end

function SharedTooltip:Layout()
	local textWidth, textHeight, lineWidth;
	local totalHeight = 0;
	local maxLineWidth = 0;
	local maxLineHeight = 0;
	local grid = self.grid;
	local fs;
	local ref = self.Content;

	local colMaxWidths, rowOffsetYs;
	local numCols = self.numCols;
	local useGridLayout = self.useGridLayout;
	if useGridLayout then
		colMaxWidths = {};
		rowOffsetYs = {};
		for i = 1, numCols do
			colMaxWidths[i] = 0;
		end
	end

	for row = 1, self.numLines do
		if grid[row] then
			lineWidth = 0;
			maxLineHeight = 0;
			if grid[row].gap then
				--Positive value increase the distance between lines
				totalHeight = totalHeight + grid[row].gap;
			end
			for col = 1, numCols do
				fs = grid[row][col];
				if fs then
					textWidth = fs:GetWrappedWidth();
					textHeight = fs:GetHeight();

					if col == 1 then
						lineWidth = textWidth;
					else
						lineWidth = lineWidth + FONTSTRING_MIN_GAP + textWidth;
					end

					if lineWidth > maxLineWidth then
						maxLineWidth = lineWidth;
					end

					if textHeight > maxLineHeight then
						maxLineHeight = textHeight;
					end

					if useGridLayout and textWidth > colMaxWidths[col] and fs.inGrid then
						colMaxWidths[col] = textWidth;
					end
				end
			end

			if row ~= 1 then
				totalHeight = totalHeight + SPACING_NEW_LINE;
			end
			totalHeight = Round(totalHeight);

			if useGridLayout then
				rowOffsetYs[row] = -totalHeight;
			else
				for col = 1, numCols do
					fs = grid[row][col];
					if fs then
						fs:ClearAllPoints();
						if fs.alignIndex == 2 then
							fs:SetPoint("TOP", ref, "TOP", 0, -totalHeight);
						elseif fs.alignIndex == 3 then
							fs:SetPoint("TOPRIGHT", ref, "TOPRIGHT", 0, -totalHeight);
						else
							fs:SetPoint("TOPLEFT", ref, "TOPLEFT", 0, -totalHeight);
						end
					end
				end
			end

			totalHeight = totalHeight + maxLineHeight;
			totalHeight = Round(totalHeight);
		end
	end

	if useGridLayout then
		local offsetX, offsetY;
		for row = 1, self.numLines do
			offsetX = 0;
			offsetY = rowOffsetYs[row];
			for col = 1, numCols do
				fs = grid[row][col];
				textWidth = colMaxWidths[col] + 1;
				if fs then
					fs:ClearAllPoints();
					if fs.alignIndex == 2 then
						if fs.inGrid then
							fs:SetPoint("TOPLEFT", ref, "TOPLEFT", offsetX, offsetY);
							fs:SetWidth(textWidth);
						else
							fs:SetPoint("TOP", ref, "TOP", offsetX, offsetY);
						end
					elseif fs.alignIndex == 3 then
						if col == numCols then
							fs:SetPoint("TOPRIGHT", ref, "TOPRIGHT", 0, offsetY);
						else
							fs:SetPoint("TOPLEFT", ref, "TOPLEFT", offsetX, offsetY);
							fs:SetWidth(textWidth);
						end
					else
						fs:SetPoint("TOPLEFT", ref, "TOPLEFT", offsetX, offsetY);
					end
				end

				offsetX = offsetX + colMaxWidths[col] + FONTSTRING_MIN_GAP;
			end
		end

		maxLineWidth = 0;
		for col = 1, numCols do
			maxLineWidth = maxLineWidth + colMaxWidths[col];
			if col > 1 then
				maxLineWidth = maxLineWidth + FONTSTRING_MIN_GAP;
			end
		end
	end

	local fullWidth = Round(maxLineWidth) + 2*TOOLTIP_PADDING;
	local fullHeight = Round(totalHeight) + 2*TOOLTIP_PADDING;
	self:SetSize(fullWidth, fullHeight);
end

local function SharedTooltip_OnUpdate_Layout(self, elapsed)
	self:SetScript("OnUpdate", nil);
	self:Layout();
end

function SharedTooltip:LayoutNextUpdate()
	self:SetScript("OnUpdate", SharedTooltip_OnUpdate_Layout);
end

function SharedTooltip:Show()
	if self.fontChanged or self.useGridLayout then
		--fontString width will take one frame to change
		self:LayoutNextUpdate();
	else
		self:Layout();
	end

	self:ShowFrame();

	local scale = UIParent:GetEffectiveScale();
	self:SetScale(scale);
end

function SharedTooltip:Hide()
	self:HideFrame();
	self:ClearAllPoints();
	self:SetScript("OnUpdate", nil);
	self:ClearLines();
end

local function SharedTooltip_OnModifierStateChanged(key, down)
	--Show extra/alternate info when ALT pressed
	if key == "LALT" or key == "RALT" then
		if down == 1 then
			local newMode = not SharedTooltip.alternateMode;
			SharedTooltip.alternateMode = newMode;
			if SharedTooltip.alternateTooltipFunc then
				SharedTooltip.alternateTooltipFunc(SharedTooltip, newMode);
			end
		end
	end
end

function SharedTooltip:OnEvent(event, ...)
	if event == "TOOLTIP_DATA_UPDATE" then
		local dataInstanceID = ...
		if dataInstanceID and dataInstanceID == self.dataInstanceID then
			self:ProcessInfo(self.lastInfo);
		end
	elseif event == "MODIFIER_STATE_CHANGED" then
		SharedTooltip_OnModifierStateChanged(...)
	end
end

SharedTooltip:SetScript("OnEvent", SharedTooltip.OnEvent);

SharedTooltip:SetScript("OnHide", function(self)
	self:UnregisterEvent("TOOLTIP_DATA_UPDATE");
	self:UnregisterEvent("MODIFIER_STATE_CHANGED");
	self.lastInfo = nil;
end);


function SharedTooltip:SetAlternateTooltipFunc(setupFunc)
	SharedTooltip.alternateTooltipFunc = setupFunc;
	if setupFunc then
		self:RegisterEvent("MODIFIER_STATE_CHANGED");
		if self.HotkeyIcon then
			self.HotkeyIcon:Show();
		end
	else
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");
		if self.HotkeyIcon then
			self.HotkeyIcon:Hide();
		end
	end
end


do
	--Emulate the default GameTooltip
	--Code from Interface/SharedXML/Tooltip/TooltipDataHandler.lua

	local function AddTooltipDataAccessor(handler, accessor, getterName)
		handler[accessor] = function(self, ...)
			local tooltipInfo = {
				getterName = getterName,
				getterArgs = { ... };
			};
			return self:ProcessInfo(tooltipInfo);
		end
	end


	local accessors = {
		SetItemByID = "GetItemByID",
		SetCurrencyByID = "GetCurrencyByID",
	};

	local handler = SharedTooltip;
	for accessor, getterName in pairs(accessors) do
		AddTooltipDataAccessor(handler, accessor, getterName);
	end
end


do
	--Item Upgrade System (Crests)
	local IUC = addon.ItemUpgradeConstant;
	local GetCurrencyInfo = C_CurrencyInfo.GetCurrencyInfo;

	local CURRENCY_QUANTITY_ICON_FORMAT = "%s |T%s:10:10:0:-2:64:64:4:60:4:60|t";
	local DBKEY_MORE_INFO_CREST = "TooltipShowExtraInfoCrest";

	--local SEASON_CAP_LABEL = string.gsub(CURRENCY_SEASON_TOTAL_MAXIMUM or "Season Maximum: %s%s/%s", "%%s/%%s", "");

	function SharedTooltip:AppendCurrency(currencyID, addBlankLine, customName, hideWhenNotOwned)
		local info = GetCurrencyInfo(currencyID);
		if info then
			if hideWhenNotOwned and info.quantity <= 0 then
				return
			end
			local name = "|cffffd100"..(customName or info.name or "").."|r";
			local quantity = string.format(CURRENCY_QUANTITY_ICON_FORMAT, info.quantity, info.iconFileID);
			if addBlankLine then
				self:AddBlankLine();
			end
			self:AddCenterLine(name.."  "..quantity, 1, 1, 1, nil, nil, 2);
		end
	end

	function SharedTooltip:DisplayUpgradeCurrencies(showExtraInfo)
		if showExtraInfo == nil then
			showExtraInfo = addon.GetDBBool(DBKEY_MORE_INFO_CREST) or false;
		end
		addon.SetDBValue(DBKEY_MORE_INFO_CREST, showExtraInfo);

		self:ClearLines();

		local info, name, quantity, toEarn, totalEarned, maxQuantity;
		local seasonCap;
		local anyDiscovered = showExtraInfo;     --We make sure there is no skipped tier between the discovered ones
		local row = 1;

		local showIcon = true;

		for i, currencyID in ipairs(IUC.Crests) do
			info = GetCurrencyInfo(currencyID);
			if info and (info.discovered or anyDiscovered) then
				if info.discovered then
					anyDiscovered = true;
				end

				name = L["currency-"..currencyID] or info.name;
				quantity = info.quantity or 0;

				if showIcon then
					quantity = string.format(CURRENCY_QUANTITY_ICON_FORMAT, quantity, info.iconFileID);
				end

				totalEarned = info.totalEarned or 0;    --info.trackedQuantity
				maxQuantity = info.maxQuantity or 0;

				if showExtraInfo then
					--toEarn becomes number of Earned currency
					toEarn = totalEarned.."/"..maxQuantity;
				else
					--toEarn: how much left to earn
					if maxQuantity == 0 then
						toEarn = "-";
					else
						toEarn = maxQuantity - totalEarned;
						if toEarn <= 0 then
							toEarn = "|TInterface/AddOns/Plumber/Art/Button/Checkmark-Green:0:0|t";
						end
					end
				end


				if not seasonCap then
					seasonCap = maxQuantity;    --Wait until season starts to see how exactily it will work
				end

				row = row + 1;
				self:SetGridLine(row, 1, name, 1, 0.82, 0, nil, 1);
				self:SetGridLine(row, 2, quantity, 1, 1, 1, nil, 3);
				self:SetGridLine(row, 3, toEarn, 0.8, 0.8, 0.8, nil, 3);

				if showExtraInfo then
					self:SetGridLine(row, 4, IUC.CrestSources[i], 1, 0.82, 0, nil, 1);
				end
			end
		end

		if anyDiscovered then
			self:SetGridLine(1, 1, TYPE or "Type", 0.5, 0.5, 0.5, 3, 1);
			self:SetGridLine(1, 2, L["Own"], 0.5, 0.5, 0.5, 3, 2);

			if showExtraInfo then
				self:SetGridLine(1, 3, L["Numbers Of Earned"], 0.5, 0.5, 0.5, 3, 3);
				self:SetGridLine(1, 4, SOURCES or "Source", 0.5, 0.5, 0.5, 3, 1);
			else
				self:SetGridLine(1, 3, L["Numbers To Earn"], 0.5, 0.5, 0.5, 3, 3);
			end

			--Show Valorstone (Retired in Midnight)
			--self:AppendCurrency(IUC.BaseCurrencyID, true);

			--Catalyst Charges
			if showExtraInfo and IUC.CatalystCurrencyID then
				self:AppendCurrency(IUC.CatalystCurrencyID, false, L["Catalyst Charges"], true);
			end
		else
			self:AddLeftLine(ERR_ITEM_NOT_FOUND, 1.000, 0.282, 0.000);
		end

		self:SetAlternateTooltipFunc(self.DisplayUpgradeCurrencies);
		self.alternateMode = showExtraInfo;
		self:Show();
	end
end
