local _, BigBrother = ...

local module = BigBrother:NewModule("Checks", "AceTimer-3.0")
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_band = bit.lshift, bit.band
local tsort, tconcat = table.sort, table.concat
local NUM_LE_RAID_BUFF_TYPES = NUM_LE_RAID_BUFF_TYPES

local chatTypes = {
	SELF = true,
	SAY = true,
	YELL = true,
	GROUP = true,
	PARTY = true,
	GUILD = true,
	OFFICIER = true,
	RAID = true,
	RAID_WARNING = true,
	WHISPER = true,
}

do -- BigBrother:SendChatMessageList(title, list, output)
	-- pick the right output or fallback to self
	local function getOutput(output)
		output = output or module.db.profile.outputDefault
		output = output:upper()

		if not chatTypes[output] then
			local index = GetChannelName(output)
			if index > 0 then
				return "CHANNEL", index
			else
				return "SELF"
			end
		elseif output == "WHISPER" then
			if UnitCanCooperate("player", "target") then
				return output, BigBrother:UnitName("target")
			else
				return "SELF"
			end
		elseif output == "GROUP" then
			if IsInRaid() then
				output = "RAID"
			elseif IsInGroup() then
				output = "PARTY"
			else
				return "SELF"
			end
		end
		if output == "RAID" or output == "PARTY" then
			if IsPartyLFG() then
				return "INSTANCE_CHAT"
			elseif not IsInGroup() then
				return "SELF"
			end
		end

		return output
	end

	function module:SendChatMessage(text, output)
		if type(text) ~= "string" or text == "" then return end

		local output, target = getOutput(output)
		if not output then return end

		if output == "SELF" then
			print(("|Hbigbrother:%s|h|cff33ff99BigBrother|r|h: %s"):format(text, text))
		else
			SendChatMessage(text, output, nil, target)
		end
	end

	function module:SendChatMessageList(title, list, output)
		if type(title) ~= "string" or title == "" then return end
		if type(list) ~= "table" or #list == 0 then return end

		local output, target = getOutput(output)
		if not output then return end

		tsort(list)
		for _, name in next, list do name = name:gsub("%-.*", "") end
		local names = tconcat(list, ", ")
		local text = ("%s (%d): %s"):format(title, #list, names)

		if output == "SELF" then
			local coloredText = ("|cffff8040%s (%d)|r: %s"):format(title, #list, names)
			print(("|Hbigbrother:%s|h|cff33ff99BigBrother|r|h: %s"):format(text, coloredText))
		else
			SendChatMessage(text, output, nil, target)
		end
	end
end

do
	ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", function(self, event, msg)
		if msg:sub(1, 11) == "BigBrother>" then return true end
	end)

	local function whisper(name, text)
		if not module.db.profile.blame then return end
		ChatThrottleLib:SendChatMessage("BULK", "", ("BigBrother> %s"):format(text), "WHISPER", nil, name)
	end

	local missingFlasks, missingFood, missingBuffs, missingRunes = {}, {}, {}, {}
	local warnings = {}
	local availableBuffs, auraBuffs = {}, {}

	function module:BuffCheck(output)
		local groups = BigBrother:GetCheckedGroups()
		local inRaid = IsInRaid()
		if self.db.profile.checkBuffs then
			local buffMask = GetRaidBuffInfo()
			local mask = 1
			for i=1, NUM_LE_RAID_BUFF_TYPES do
				availableBuffs[i] = bit_band(buffMask, mask) > 0
				auraBuffs[i] = nil
				mask = bit_lshift(mask, 1)
			end
		end

		wipe(missingFlasks)
		wipe(missingFood)
		wipe(missingRunes)
		wipe(missingBuffs)

		for unit, index in BigBrother:IterateGroup() do
			local name = BigBrother:UnitName(unit)
			local group = inRaid and select(3, GetRaidRosterInfo(index)) or 1
			if UnitIsConnected(unit) and not UnitIsDeadOrGhost(unit) and UnitIsVisible(unit) and UnitInPhase(unit) and groups[group] then

				wipe(warnings)

				-- check flasks/elixirs
				if self.db.profile.checkFlasks then
					local found = nil
					for _, spellName in next, SpellData.flasks do
						local _, _, _, _, _, _, expires = UnitBuff(unit, spellName)
						if expires then
							found = expires
							break
						end
					end
					if not found then
						warnings[#warnings + 1] = L["No Flask"]
						missingFlasks[#missingFlasks + 1] = name
					elseif found > 0 and found-GetTime() < 600 then
						whisper(name, L["Your flask is expiring in less than 10 minutes!"])
					end
				end

				-- check food
				if self.db.profile.checkFood then
					local found = nil
					for _, spellName in next, SpellData.foods do
						local id = select(11, UnitBuff(unit, spellName))
						if id then
							found = true
							break
						end
					end
					if not found then
						warnings[#warnings + 1] = L["Not Well Fed"]
						missingFood[#missingFood + 1] = name
					end
				end

				if self.db.profile.checkRunes then
					local found = nil
					for _, spellName in next, SpellData.runes do
						local name = UnitBuff(unit, spellName)
						if name then
							found = true
							break
						end
					end
					if not found then
						warnings[#warnings + 1] = L["No Augment Rune"]
						missingRunes[#missingRunes + 1] = name
					end
				end

				if #warnings > 0 then
					whisper(name, tconcat(warnings, ", "))
				end

				-- check raid buffs
				if self.db.profile.checkBuffs then
					for i=1, NUM_LE_RAID_BUFF_TYPES do
						if availableBuffs[i] then
							local found = nil
							for _, spellId in next, SpellData.raidBuffs[i] do
								local _, _, _, _, _, duration, _, _, _, _, id = UnitBuff(unit, SpellCache[spellId].name)
								if id then
									found = true
									if duration == 0 then
										-- don't complain about people that will have the buff when they aren't too far away
										auraBuffs[i] = true
									end
									break
								end
							end
							if not found then
								missingBuffs[i] = true
							end
						end
					end
				end

			end
		end

		self:SendChatMessageList(L["No Flask"], missingFlasks, output)
		self:SendChatMessageList(L["Not Well Fed"], missingFood, output)
		self:SendChatMessageList(L["No Augment Rune"], missingRunes, output)

		if next(missingBuffs) then
			local list = {}
			for i=1, NUM_LE_RAID_BUFF_TYPES do
				if missingBuffs[i] and not auraBuffs[i] then
					list[#list+1] = _G["RAID_BUFF_" .. i]
				end
			end
			self:SendChatMessageList(L["Missing Buffs"], list, output)
		end
	end
end

function BigBrother:BuffCheck(output)
	module:BuffCheck(output)
end

function module:READY_CHECK(_, sender)
	-- unchecked (0), checked (1), greyed (2)
	if not IsInGroup() or IsPartyLFG() or self.db.profile.checkReadyCheck == 0 then return end

	if self.db.profile.checkReadyCheck == 2 or (self.db.profile.checkReadyCheck == 1 and UnitIsUnit(sender, "player")) then
		self:BuffCheck()
	end
end


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

function module:OnInitialize()
	self.db = BigBrother.db:RegisterNamespace("Checks", {
		profile = {
			checkFlasks = true,
			checkFood = true,
			checkRunes = false,
			checkBuffs = true,
			outputDefault = "raid",
			blame = false,
			checkReadyCheck = 1, -- started by you
			maxOnly = false,
		}
	})
end

function module:OnEnable()
	self:RegisterEvent("READY_CHECK")
end


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


function module:AddOptions(options)
	options.args.check_button = {
		name = L["Buff Check"],
		desc = L["Report who is missing consumables and/or buffs."],
		type = "execute",
		func = function() BigBrother:BuffCheck() end,
		order = 5,
	}

	local db = self.db.profile
	options.args.checks = {
		name = L["Checks"],
		--desc = L["Checks"],
		type = "group",
		get = function(info) return db[info[#info]] end,
		set = function(info, value) db[info[#info]] = value end,
		order = 10,
		args = {
			checks = {
				name = L["Checks"],
				--desc = L["Set what is included in a buff check."],
				type = "group",
				inline = true,
				order = 10,
				args = {
					checkFlasks = {
						name = L["Flasks"],
						desc = L["Include flasks in checks."],
						type = "toggle",
						order = 5,
					},
					checkFood = {
						name = L["Food Buffs"],
						desc = L["Include food buffs in checks."],
						type = "toggle",
						order = 15,
					},
					checkRunes = {
						name = L["Augment Runes"],
						desc = L["Include augment runes in checks."],
						type = "toggle",
						order = 16,
					},
					checkBuffs = {
						name = L["Raid Buffs"],
						desc = L["Include raid buffs in checks."],
						type = "toggle",
						order = 20,
					},
				},
			}, -- checks

			output = {
				name = L["Default Output"],
				--desc = L["Set the default check output."],
				type = "group",
				inline = true,
				get = function(info)
					local key = info[#info]
					return db.outputDefault:lower() == key or (not chatTypes[db.outputDefault:upper()] and key == "channel")
				end,
				set = function(info, value) db.outputDefault = info[#info] end,
				order = 20,
				args = {
					self = {
						name = L["Self"],
						desc = L["Print to your chat window."],
						type = "toggle",
						order = 1,
					},
					say = {
						name = _G.SAY,
						desc = L["Report to say."],
						type = "toggle",
						order = 5,
					},
					group = {
						name = _G.GROUP,
						desc = L["Report to party or raid chat."],
						type = "toggle",
						order = 10,
					},
					party = {
						name = _G.PARTY,
						desc = L["Report to party chat."],
						type = "toggle",
						order = 11,
					},
					raid = {
						name = _G.RAID,
						desc = L["Report to raid chat."],
						type = "toggle",
						order = 12,
					},
					raid_warning = {
						name = _G.RAID_WARNING,
						desc = L["Report as a raid warning."],
						type = "toggle",
						order = 13,
					},
					guild = {
						name = _G.GUILD,
						desc = L["Report to guild chat."],
						type = "toggle",
						order = 15,
					},
					officer = {
						name = _G.OFFICER,
						desc = L["Report to officer chat."],
						type = "toggle",
						order = 16,
					},
					channel = {
						name = _G.CHANNEL,
						desc = L["Report to a custom channel."],
						type = "toggle",
						order = 20,
					},
					extra = {
						name = _G.CHANNEL,
						type = "select",
						values = function() return BigBrother:GetChatChannels() end,
						get = function() return GetChannelName(db.outputDefault) > 0 and db.outputDefault or "" end,
						set = function(info, value) db.outputDefault = value end,
						hidden = function() return chatTypes[db.outputDefault:upper()] end,
						order = 21,
					},
				},
			},

			blame = {
				name = L["Whisper Missing"],
				desc = L["Whisper players that are missing consumables on checks."],
				type = "toggle",
				order = 25,
			},
			checkReadyCheck = {
				name = L["Check on Ready Check"],
				desc = L["Buff check on a ready check when in a non-LFG instance group.\n\n|cffffff33Checked|r: Report if started by you.\n|cffccccccGray|r: Report always."],
				type = "toggle",
				tristate = true,
				-- unchecked (false/0), checked (true/1), greyed (nil/2)
				get = function()
					local value = db.checkReadyCheck
					if value == 0 then
						return false
					elseif value == 1 then
						return true
					elseif value == 2 then
						return nil
					end
				end,
				set = function(info, value)
					if value == false then
						value = 0
					elseif value == true then
						value = 1
					elseif value == nil then
						value = 2
					end
					db.checkReadyCheck = value
				end,
				order = 30,
			},
		},
	}
end

