local ADDON_NAME, BigBrother = ...

local module = BigBrother:NewModule("BuffCheck")
if not module then return end

local L = LibStub("AceLocale-3.0"):GetLocale("BigBrother")
local SpellData = BigBrother.SpellData
local SpellCache = SpellData.SpellCache

local _G = _G
local bit_lshift = bit.lshift
local bit_band = bit.band
local NUM_LE_RAID_BUFF_TYPES = NUM_LE_RAID_BUFF_TYPES

local DEFAULT_ROWS = 10
local DEFAULT_ROWS_MIN = 5
local DEFAULT_ROWS_MAX = 25

local BUFF_SIZE = 16
local BUFF_SPACING = 18
local TOTAL_BUFFS = 3 + NUM_LE_RAID_BUFF_TYPES -- rune, food, flask, and buffs
local BAR_HEIGHT = 18
local BAR_WIDTH = BUFF_SPACING * TOTAL_BUFFS + 130
local WINDOW_WIDTH = BAR_WIDTH + 16 + 24

local ResizeBuffWindow, UpdateRosterBuffs, UpdateBuffWindow

local BuffWindow = CreateFrame("Frame", "BigBrotherBuffsFrame", UIParent)
local NumPlayersShown, NumRowsCreated = nil, nil
local PlayerList = {}
local DirtyBuffs = nil

local ClassColors = {}
for k,v in pairs(CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS) do ClassColors[k] = v end

tinsert(UISpecialFrames, "BigBrotherBuffsFrame")

local AvailableRaidBuffs = {}
local function UpdateAvailableRaidBuffs()
	local buffMask = GetRaidBuffInfo()
	local mask = 1
	for i=1,NUM_LE_RAID_BUFF_TYPES do
		AvailableRaidBuffs[i] = bit_band(buffMask, mask) > 0
		mask = bit_lshift(mask, 1)
	end
end

local BuffTable = {
	SpellData.runes,
	SpellData.flasks,
	SpellData.foods,
}
for _, buffTypes in ipairs(SpellData.raidBuffs) do
	local buffs = {}
	for _, spellId in next, buffTypes do
		buffs[#buffs + 1] = SpellCache[spellId].name
	end
	BuffTable[#BuffTable + 1] = buffs
end

local DefaultRaidBuffs = {
	SpellCache[1126].texture, --Mark of the Wild
	SpellCache[21562].texture, -- PW:Fortitude
	SpellCache[6673].texture, -- Battle Shout
	SpellCache[49868].texture, -- Mind Quickening
	SpellCache[1459].texture, -- Arcane Brilliance
	SpellCache[17007].texture, -- Leader of the Pack
	SpellCache[19740].texture, -- Blessing of Might
	SpellCache[166916].texture, -- Windflurry
	SpellCache[167187].texture, -- Sanctity Aura
}	


local function GetHeightFromRows(rows)
	return 4 + BUFF_SPACING + 4 + ((BAR_HEIGHT + 2) * rows) + 4 + 0.5
end

local function GetRowsFromHeight(height)
	return math.floor( (height - (BUFF_SPACING + 4) - 8) / (BUFF_SPACING + 2) )
end

local function SetDirty()
	DirtyBuffs = true
end

local function BuffWindow_OnEnter(frame)
	GameTooltip:SetOwner(frame, "ANCHOR_TOPLEFT")
	if frame.name then
		GameTooltip:SetUnitBuff(frame:GetParent().unit, frame.name)
	elseif frame.tooltip then
		GameTooltip:AddLine(frame.tooltip)
	end
	GameTooltip:Show()
end

local function BuffWindow_OnLeave()
	GameTooltip:Hide()
end

local BuffWindow_OnUpdate
do
	local total = 0
	function BuffWindow_OnUpdate(self, elapsed)
		total = total + elapsed
		if total > 0.5 and DirtyBuffs then
			total = 0
			UpdateRosterBuffs()
			DirtyBuffs = nil
		end
	end
end

local function CreateBuffRow(parent, index, xOffset, yOffset)
	local row = CreateFrame("Frame", parent:GetName().."Row"..index, parent)
	row:SetPoint("TOPLEFT", xOffset, yOffset)
	row:SetHeight(BAR_HEIGHT)
	row:SetWidth(BAR_WIDTH)

	local bg = row:CreateTexture(nil, "BACKGROUND")
	bg:SetAllPoints()
	bg:SetTexture("Interface\\Buttons\\WHITE8X8")
	bg:SetGradientAlpha("HORIZONTAL", 0.5, 0, 0, 0.8, 0.5, 0, 0, 0)
	row.background = bg

	row.buff = {}
	local last
	for i = TOTAL_BUFFS, 1, -1 do
		local buff = CreateFrame("Frame", row:GetName().."Buff"..i, row)
		buff:SetSize(BUFF_SIZE, BUFF_SIZE)
		if last then
			buff:SetPoint("RIGHT", last, "LEFT", -2, 0)
		else
			buff:SetPoint("RIGHT", -2, 0)
		end

		local icon = buff:CreateTexture(nil, "OVERLAY")
		icon:SetAllPoints()
		icon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
		icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
		buff.icon = icon

		buff:SetScript("OnEnter", BuffWindow_OnEnter)
		buff:SetScript("OnLeave", BuffWindow_OnLeave)
		buff:EnableMouse(true)

		last = buff
		row.buff[i] = buff
	end

	local name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	name:SetPoint("LEFT", 2, 0)
	name:SetPoint("RIGHT", last, "LEFT", -2, 0)
	name:SetJustifyH("LEFT")
	name:SetTextColor(1, 1, 1)
	name:SetText(_G.UNKNOWN)
	row.name = name

	return row
end

local function CreateBuffWindow()
	local frame = BuffWindow
	frame:Hide()

	--set on top of most ui elements
	frame:SetFrameStrata("HIGH")
	frame:SetFrameLevel(10)

	frame:ClearAllPoints()
	if module.db.profile.x and module.db.profile.y then
		frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", module.db.profile.x, module.db.profile.y)
	else
		frame:SetPoint("CENTER")
	end
	local height = module.db.profile.height or GetHeightFromRows(DEFAULT_ROWS)

	frame:SetBackdrop({
		bgFile = "Interface/Tooltips/UI-Tooltip-Background",
		edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
		tile = true, tileSize = 16, edgeSize = 16,
		insets = { left = 4, right = 4, top = 4, bottom = 4 }
	})
	frame:SetBackdropColor(0, 0, 0, 0.5)

	frame:EnableMouse(true)
	frame:SetMovable(true)
	frame:SetClampedToScreen(true)
	frame:SetResizable(true)
	frame:SetMinResize(WINDOW_WIDTH, GetHeightFromRows(DEFAULT_ROWS_MIN))
	frame:SetMaxResize(WINDOW_WIDTH, GetHeightFromRows(DEFAULT_ROWS_MAX))
	frame:SetWidth(WINDOW_WIDTH)
	frame:SetHeight(height)

	frame:SetScript("OnMouseDown", function(self, button)
		if button == "LeftButton" then
			self:StartMoving()
			self.isMoving = true
		end
	end)
	frame:SetScript("OnMouseUp", function(self)
		if self.isMoving then
			self:StopMovingOrSizing()
			self.isMoving = false
			module.db.profile.x = self:GetLeft()
			module.db.profile.y = self:GetTop()
		end
	end)
	frame:SetScript("OnHide", function(self)
		if self.isMoving then
			self:StopMovingOrSizing()
			self.isMoving = false
		end
	end)
	frame:SetScript("OnShow", UpdateRosterBuffs)
	frame:SetScript("OnUpdate", BuffWindow_OnUpdate)
	frame:SetScript("OnSizeChanged", function(self)
		if self.isResizing then
			ResizeBuffWindow()
		end
	end)

	local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	title:SetPoint("TOP", frame, "TOP", 0, -8)
	title:SetTextColor(1, 1, 1)
	title:SetText("BigBrother")

	local closeButton = CreateFrame("Button", frame:GetName().."CloseButton", frame)
	closeButton:SetNormalTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up.blp")
	closeButton:SetPushedTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Down.blp")
	closeButton:SetHighlightTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Highlight.blp")
	closeButton:SetWidth(22)
	closeButton:SetHeight(22)
	closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
	closeButton:SetScript("OnClick", function(self) self:GetParent():Hide() end)

	local readyButton = CreateFrame("Button", frame:GetName().."ReadyCheckButton", frame)
	readyButton:SetNormalTexture("Interface\\RAIDFRAME\\ReadyCheck-Waiting")
	readyButton:SetWidth(12)
	readyButton:SetHeight(12)
	readyButton:SetPoint("TOPLEFT", frame, "TOPLEFT", 8, -8)
	readyButton:SetScript("OnClick", function() DoReadyCheck() end)
	readyButton:SetScript("OnEnter", BuffWindow_OnEnter)
	readyButton:SetScript("OnLeave", BuffWindow_OnLeave)
	readyButton.tooltip = _G.READY_CHECK

	local checkButton = CreateFrame("Button", frame:GetName().."CheckButton", frame)
	checkButton:SetNormalTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready")
	checkButton:SetWidth(12)
	checkButton:SetHeight(12)
	checkButton:SetPoint("LEFT", readyButton, "RIGHT", 2, 0)
	checkButton:SetScript("OnClick", function() BigBrother:BuffCheck() end)
	checkButton:SetScript("OnEnter", BuffWindow_OnEnter)
	checkButton:SetScript("OnLeave", BuffWindow_OnLeave)
	checkButton.tooltip = L["Buff Check"]

	local rows = {}
	for i = 1, GetRowsFromHeight(height) do
		rows[i] = CreateBuffRow(frame, i, 8, -4 - i * (BUFF_SPACING + 2))
	end
	frame.rows = rows
	NumRowsCreated = #rows
	NumPlayersShown = #rows

	local scrollBar = CreateFrame("ScrollFrame", frame:GetName().."ScrollFrame", frame, "FauxScrollFrameTemplate")
	scrollBar:SetScript("OnVerticalScroll", function(self, offset)
		FauxScrollFrame_OnVerticalScroll(self, offset, BAR_HEIGHT + 2, UpdateBuffWindow)
	end)
	scrollBar:SetPoint("TOPLEFT", frame, "TOPLEFT", 8, -24)
	scrollBar:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -8 - 24, 8)
	frame.scrollBar = scrollBar

	local dragHandle = CreateFrame("Frame", nil, frame)
	dragHandle:Show()
	dragHandle:SetFrameLevel(frame:GetFrameLevel() + 10) -- place this above everything
	dragHandle:SetWidth(WINDOW_WIDTH)
	dragHandle:SetHeight(8)
	dragHandle:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
	dragHandle:EnableMouse(true)
	dragHandle:SetBackdrop({
		bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
		tile = true, tileSize = 16, edgeSize = 0,
		insets = { left = 6, right = 6, top = 3, bottom = 3 }
	})
	dragHandle:SetBackdropColor(1, 1, 1, 0)
	dragHandle:SetScript("OnEnter", function(self)
		self:SetBackdropColor(1, 1, 1, 0.8)
	end)
	dragHandle:SetScript("OnLeave", function(self)
		self:SetBackdropColor(1, 1, 1, 0)
	end)
	dragHandle:SetScript("OnMouseDown", function(self, button)
		if button == "LeftButton" then
			frame.isResizing = true
			frame:StartSizing("BOTTOMRIGHT")
		end
	end)
	dragHandle:SetScript("OnMouseUp", function(self, button)
		if button == "LeftButton" then
			frame:StopMovingOrSizing()
			frame.isResizing = false
			frame:SetHeight(GetHeightFromRows(NumPlayersShown))
		elseif button == "RightButton" then
			if IsShiftKeyDown() then
				frame:SetHeight(math.max(GetHeightFromRows(DEFAULT_ROWS_MIN), GetHeightFromRows(#PlayerList)))
			else
				frame:SetHeight(GetHeightFromRows(DEFAULT_ROWS))
			end
		end
		ResizeBuffWindow()
	end)

	return frame
end

--update the player buff table
do --UpdateRosterBuffs
	local function buffSortFunc(a, b)
		if a.buffs < b.buffs then
			return true
		elseif a.buffs > b.buffs then
			return false
		elseif a.name < b.name then
			return true
		end
		return false
	end

	function UpdateRosterBuffs()
		local groups = BigBrother:GetCheckedGroups()
		local currentZone = GetRealZoneText()
		local inRaid = IsInRaid()

		local index = 1
		for unit in BigBrother:IterateGroup() do
			local name, subgroup, class, online, dead, _
			if inRaid then
				name, _, subgroup, _, _, class, _, online, dead = GetRaidRosterInfo(index)
			else
				name = BigBrother:UnitName(unit)
				subgroup = 1
				class = select(2, UnitClass(unit)) or false --cross-realm players can take a sec to load
				online = UnitIsConnected(unit)
				dead = UnitIsDeadOrGhost(unit)
			end
			name = name:gsub("%-.*", "*")

			if groups[subgroup] then
				local player = PlayerList[index]
				if not player then
					player = { buff = {} }
					PlayerList[index] = player
				end

				player.name = name
				player.class = class
				player.status = not online or dead or not UnitIsVisible(unit) or not UnitInPhase(unit)
				player.unit = unit
				player.buffs = 0

				for column, group in ipairs(BuffTable) do
					player.buff[column] = nil
					for _, buff in ipairs(group) do
						local spellId = buff and select(11, UnitBuff(player.unit, buff))
						if spellId then
							player.buff[column] = spellId
							player.buffs = player.buffs + 1
							break
						end
					end
				end

				index = index + 1
			end
		end

		-- clear the rest of the table so we dont get nil holes that lead to ill-defined behavior in sort
		while PlayerList[index] do
			PlayerList[index] = nil
			index = index + 1
		end
		sort(PlayerList, buffSortFunc)

		UpdateBuffWindow()
	end
end

--update the display
function UpdateBuffWindow()
	FauxScrollFrame_Update(BuffWindow.scrollBar, #PlayerList, NumPlayersShown, 20)
	local offset = FauxScrollFrame_GetOffset(BuffWindow.scrollBar)
	UpdateAvailableRaidBuffs()
	local raidBuffOffset = TOTAL_BUFFS - NUM_LE_RAID_BUFF_TYPES

	for i = 1, NumPlayersShown do
		local row = BuffWindow.rows[i]
		if PlayerList[i + offset] then
			local player = PlayerList[i + offset]
			row.unit = player.unit
			local color = not player.status and _G.HIGHLIGHT_FONT_COLOR or _G.RED_FONT_COLOR
			local bgColor = ClassColors[player.class] or _G.GRAY_FONT_COLOR
			row.name:SetText(player.name)
			row.name:SetTextColor(color.r, color.g, color.b)
			row.background:SetGradientAlpha(
				"HORIZONTAL",
				bgColor.r/1.5, bgColor.g/1.5, bgColor.b/1.5, 0.8,
				bgColor.r/1.5, bgColor.g/2, bgColor.b/1.5, 0
			)

			for column = 1, TOTAL_BUFFS do
				local buff = row.buff[column]
				local spellId = player.buff[column]
				if spellId then
					local info = SpellCache[spellId]
					buff.name = info.name
					buff.tooltip = nil
					buff.icon:SetTexture(info.texture)
					if info.texture == "Interface\\Icons\\Spell_Misc_Food" and not SpellData.foodsMax[spellId] then
						buff.icon:SetVertexColor(1, 0.5, 0.5, 1) --red tint
					else
						buff.icon:SetVertexColor(1, 1, 1, 1)
					end
					buff.icon:SetDesaturated(nil)
					buff:Show()
				else
					local raidBuffIndex = column - raidBuffOffset
					if raidBuffIndex > 0 then
						buff.name = nil
						buff.tooltip = ("|cff808080%s|r"):format(_G["RAID_BUFF_"..raidBuffIndex])
						buff.icon:SetTexture(DefaultRaidBuffs[raidBuffIndex])
						--dimmed icons for missing available raid buffs, hide non-available
						buff.icon:SetVertexColor(1, 1, 1, AvailableRaidBuffs[raidBuffIndex] and 0.3 or 0)
						buff.icon:SetDesaturated(true)
						buff:Show()
					else
						buff:Hide()
					end
				end
			end
			row:Show()
		else
			row:Hide()
		end
	end
end

function ResizeBuffWindow()
	local numVisibleRows = GetRowsFromHeight(BuffWindow:GetHeight())

	if numVisibleRows > NumRowsCreated then
		for i = (1 +  NumRowsCreated), numVisibleRows do
			BuffWindow.rows[i] = CreateBuffRow(BuffWindow, i, 8, -4 - i * (BUFF_SPACING + 2))
			BuffWindow.rows[i]:Hide()
		end
		NumRowsCreated = numVisibleRows
	end

	for i = (1 + numVisibleRows), NumPlayersShown do
		BuffWindow.rows[i]:Hide()
	end
	NumPlayersShown = numVisibleRows

	module.db.profile.height = BuffWindow:GetHeight()
	UpdateBuffWindow()
end


function BigBrother:ToggleBuffWindow()
	if BuffWindow:IsShown() then
		BuffWindow:Hide()
	else
		BuffWindow:Show()
	end
end

function module:READY_CHECK()
	if IsInGroup() and not IsPartyLFG() and self.db.profile.openOnReadyCheck then
		BuffWindow:Show()
	end
end

function module:PLAYER_REGEN_DISABLED()
	if BuffWindow:IsShown() and self.db.profile.closeOnCombat then
		BuffWindow:Hide()
	end
end


---------------------------------------
-- Init

function module:OnInitialize()
	self.db = BigBrother.db:RegisterNamespace("BuffCheck", {
		profile = {
			openOnReadyCheck = false,
			closeOnCombat = false,
		}
	})

	local function OnProfileChanged()
		BuffWindow:ClearAllPoints()
		if self.db.profile.x and self.db.profile.y then
			BuffWindow:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", self.db.profile.x, self.db.profile.y)
		else
			BuffWindow:SetPoint("CENTER")
		end
	end
	self.db.RegisterCallback(self, "OnProfileChanged", OnProfileChanged)
	self.db.RegisterCallback(self, "OnProfileCopied", OnProfileChanged)
	self.db.RegisterCallback(self, "OnProfileReset", OnProfileChanged)
end

function module:OnEnable()
	self:RegisterEvent("UNIT_AURA", SetDirty)
	self:RegisterEvent("GROUP_ROSTER_UPDATE", SetDirty)
	self:RegisterEvent("READY_CHECK")
	self:RegisterEvent("PLAYER_REGEN_DISABLED")

	if _G.CUSTOM_CLASS_COLORS then
		local function updateClassColors()
			for k,v in next, _G.CUSTOM_CLASS_COLORS do
				ClassColors[k] = v
			end
		end
		_G.CUSTOM_CLASS_COLORS:RegisterCallback(updateClassColors)
		updateClassColors()
	end

	CreateBuffWindow()
end


---------------------------------------
-- Options

function module:AddOptions(options)
	options.args.window_button = {
		type = "execute",
		name = L["Buff Window"],
		desc = L["Opens the buff window (drag the bottom to resize)."],
		func = function() BigBrother:ToggleBuffWindow() end,
		order = 6,
	}

	local db = self.db.profile
	options.args.buffcheck = {
		name = L["Buff Window"],
		--desc = L["Buff Window"],
		type = "group",
		get = function(info) return db[info[#info]] end,
		set = function(info, value) db[info[#info]] = value end,
		order = 30,
		args = {
			openOnReadyCheck = {
				type = "toggle",
				name = L["Open on Ready Check"],
				desc = L["Open the buff window on a ready check when you're in a non-LFG instance group."],
				order = 1,
			},
			closeOnCombat = {
				type = "toggle",
				name = L["Close on Combat"],
				desc = L["Close the buff window when entering combat."],
				order = 2,
			},
		},
	}
end

