local L = AceLibrary("AceLocale-2.2"):new("GridAlert")
local media = LibStub("LibSharedMedia-3.0")

local player_name = UnitName("player")

media:Register("sound", "Aggro", "Interface\\Addons\\GridAlert\\sounds\\aggro.wav")
media:Register("sound", "Detox", "Sound\\interface\\AuctionWindowOpen.wav")

--[[
triggers = {
	<<status>> = {
		gain = {
			any = <<alert>>,
			player = <<alert>>,
			units = { ["<<name>>"] = <<alert>> },
		},
		lost = {
			all = <<alert>>,
			player = <<alert>>,
			units = { ["<<name>>"] = <<alert>> },
		},
	}
}

alerts = {
	[<<alert>>] = {
		sound = "<<media:sound>>",
		message = "<<string>>",
		min_pause = <<seconds>>, -- optional
	}
}
]]

GridAlert = Grid:NewModule("GridAlert", "AceModuleCore-2.0")
local GridAlert = GridAlert

GridAlert:SetModuleMixins("AceDebug-2.0")

GridAlert.defaultDB = {
	debug = false,
	alerts = {},
	triggers = {},
	modules = {},
	min_pause = 0.2,
	sinkOptions = {},
}

GridAlert.modulePrototype.core = GridAlert

function GridAlert.modulePrototype:OnInitialize()
	if not self.db then
		self.core.core:RegisterDefaults(self.name, "profile", self.defaultDB or {})
		self.db = self.core.core:AcquireDBNamespace(self.name)
	end
	self.debugging = self.db.profile.debug
	self.debugFrame = GridAlert.debugFrame
end

function GridAlert.modulePrototype:OnDisable()
	self.core:RemoveAlert(self.name)
end

function GridAlert.modulePrototype:Reset()
end

function GridAlert:UpdateMedia(_, type)
	if type == "sound" then
		local list = media:List("sound")
		for name in self:IterateModules() do
			self.options.args[name].args.sound.validate = list
		end
		for alert, options in pairs(self.options.args.alerts.args) do
			if options.args then
				options.args.sound.validate = list
			end
		end
	end
end

function GridAlert:OnInitialize()
	self.super.OnInitialize(self)
	
	LibStub("LibSink-2.0"):Embed(self)
	self:SetSinkStorage(self.db.profile.sinkOptions)
	
	self.alert_counts = {}
	self.notification_times = {}
	self.options.args.sink = {
		type = "group",
		name = L["Sink options"],
		desc = L["Options for the Sink library output."],
		args = self:GetSinkAce2OptionsDataTable(self),
	}
	media.RegisterCallback(self, "LibSharedMedia_Registered", "UpdateMedia")
end

local function validate_alert (alert)
	return GridAlert.db.profile.alerts[alert] ~= nil
end
local function validate_alert_or_empty (alert)
	return alert == "" or GridAlert.db.profile.alerts[alert] ~= nil
end

function GridAlert:RegisterModule(name, module)
	self:Debug("Registering "..name)

	if not module.db then
		self.core:RegisterDefaults(name, "profile", module.defaultDB or {})
		module.db = self.core:AcquireDBNamespace(name)
	end

	local p = self.db.profile
	if p.modules[name] == nil then
		p.modules[name] = module.defaultState
	end
	self.options.args[name] = {
		type = "group",
		name = name,
		desc = string.format(L["Options for module %s"], name),
		order = 100,
		args = {
			enable = {
				type = "toggle",
				name = L["Enable"],
				desc = string.format(L["Enable the alert module %s"], name),
				get = function () return self.db.profile.modules[name] end,
				set = function (v) self:SetModuleEnabled(name, v) end,
				order = 50,
			},
			s = {
				order = 90,
				type = "header",
			},
			sound = {
				type = "text",
				name = L["Sound"],
				desc = L["Sound to play when the alert is triggered."],
				usage = L["<sound>"],
				get = function () return module.db.profile.sound or "" end,
				set = function (v) module.db.profile.sound = (v ~= "" and v) end,
				validate = media:List("sound"),
			},
			message = {
				type = "text",
				name = L["Message to display"],
				desc = L["Message to display, set to off to disable."],
				usage = "<string>",
				get = function () return module.db.profile.message or L["off"] end,
				set = function (v) module.db.profile.message = (v ~= L["off"] and v) end,
			},
			min_pause = {
				type = "range",
				name = L["Minimum pause"],
				desc = L["Minimum pause between two of this kind of alert, in second."],
				min = 0,
				max = 2,
				step = 0.1,
				get = function () return module.db.profile.min_pause end,
				set = function (v) module.db.profile.min_pause = v end,
			},
		},
	}

	self.core:AddModuleDebugMenu(module)
end

function GridAlert:SetModuleEnabled(name, enable)
	enable = enable and true or false
	self.db.profile.modules[name] = enable
	self:ToggleModuleActive(name, enable)
end

function GridAlert:EnableModules()
	local p = self.db.profile
	for name, module in self:IterateModules() do
		if p.modules[name] then
			self:ToggleModuleActive(module, true)
		else
			self:ToggleModuleActive(module, false)
		end
	end
end

GridAlert.options = {
	type = "group",
	name = L["GridAlert"],
	desc = string.format(L["Options for %s."], GridAlert.name),
	args = {
		min_pause = {
			order = 80,
			type = "range",
			name = L["Minimum pause"],
			desc = L["Minimum pause between two alerts of any kind, in seconds."],
			min = 0,
			max = 2,
			step = 0.1,
			get = function () return GridAlert.db.profile.min_pause end,
			set = function (v) GridAlert.db.profile.min_pause = v end,
		},
		s_modules = {
			order = 90,
			type = "header",
		},
		s_triggers = {
			order = 110,
			type = "header",
		},
		triggers = {
			order = 120,
			type = "group",
			name = L["Triggers"],
			desc = L["Options to setup trigger from status"],
			args = {},
		},
		s_alerts = {
			order = 130,
			type = "header",
		},
		alerts = {
			order = 140,
			type = "group",
			name = L["Alerts"],
			desc = L["Options for the type of alert triggered"],
			args = {
				add_alert = {
					type = "text",
					name = L["Add an alert"],
					desc = L["Create a new alert"],
					usage = "<name>",
					get = false,
					set = function (v) GridAlert:CreateAlert(v) end,
					validate = function (v) return not validate_alert(v) end,
				},
				remove_alert = {
					type = "text",
					name = L["Remove an alert"],
					desc = L["Remove an alert"],
					usage = "<name>",
					get = false,
					set = function (v) GridAlert:RemoveAlert(v) end,
					validate = validate_alert,
				},
				s_alerts = {
					order = 150,
					type = "header",
				}
				
			},
		},
	},
}

function GridAlert:CreateAlert(name)
	self.db.profile.alerts[name] = { message = name, min_pause = 1 }
	self:AddAlertOptions(name)
end

function GridAlert:AddAlertOptions(name)
	self.options.args.alerts.args[name] = {
		type = "group",
		name = name,
		desc = string.format(L["Options for alert %s"], name),
		order = 200,
		args = {
			sound = {
				type = "text",
				name = L["Sound"],
				desc = L["Sound to play when the alert is triggered."],
				usage = L["<sound>"],
				get = function () return self.db.profile.alerts[name].sound or "" end,
				set = function (v) self.db.profile.alerts[name].sound = (v ~= "" and v) end,
				validate = media:List("sound"),
			},
			message = {
				type = "text",
				name = L["Message to display"],
				desc = L["Message to display, set to off to disable."],
				usage = "<string>",
				get = function () return self.db.profile.alerts[name].message or L["off"] end,
				set = function (v) self.db.profile.alerts[name].message = (v ~= L["off"] and v) end,
			},
			min_pause = {
				type = "range",
				name = L["Minimum pause"],
				desc = L["Minimum pause between two of this kind of alert, in second."],
				min = 0,
				max = 2,
				step = 0.1,
				get = function () return self.db.profile.alerts[name].min_pause end,
				set = function (v) self.db.profile.alerts[name].min_pause = v end,
			},
		}
	}
end

function GridAlert:RemoveAlert(name)
	-- check if alert is used and remove options that use it
	for _, settings in pairs(self.options.args.triggers.args) do
		if settings.gain then
			if settings.gain.any == name then settings.gain.any = nil end
			if settings.gain.player == name then settings.gain.player = nil end
			for k, v in pairs(settings.gain.units) do
				if v == name then settings.gain.units[k] = nil end
			end
		end
		if settings.lost then
			if settings.lost.all == name then settings.lost.all = nil end
			if settings.lost.player == name then settings.lost.player = nil end
			for k, v in pairs(settings.lost.units) do
				if v == name then settings.lost.units[k] = nil end
			end
		end
	end
	
	self.db.profile.alerts[name] = nil
	self.options.args.alerts.args[name] = nil
end

function GridAlert:HasAlert(name)
	return type(self.db.profile.alerts[name]) ~= "nil"
end

function GridAlert:GetAlertOptions(name)
	local options = self.db.profile.alerts[name]
	if not options then return nil end
	return options.sound, options.message, options.min_pause
end

function GridAlert:SetAlertOptions(name, sound, message, min_pause)
	local options = self.db.profile.alerts[name]
	if not options then
		options = {}
		self.db.profile.alerts[name] = options
	end
	options.sound = sound
	options.message = message
	options.min_pause = min_pause or 0
end


function GridAlert:AddStatusOptions(status, desc)
	local triggers = self.options.args.triggers.args
	if triggers[status] then return end
	if not self.db.profile.triggers[status] then
		self.db.profile.triggers[status] = {
			gain = { units = {} },
			lost = { units = {} }
		}
	end
	triggers[status] = {
		type = "group",
		name = desc,
		desc = string.format(L["Options for %s."], desc),
		args = {
			gain_any = {
				type = "text",
				name = L["Any unit gains"],
				desc = L["Alert to trigger when any unit gains Status"],
				usage = L["<alert>"],
				get = function () return self.db.profile.triggers[status].gain.any or "" end,
				set = function (v) self.db.profile.triggers[status].gain.any = (v ~= "" and v) end,
				validate = validate_alert_or_empty,
			},
			gain_player = {
				type = "text",
				name = L["Player gains"],
				desc = L["Alert to trigger when the player gains Status"],
				usage = L["<alert>"],
				get = function () return self.db.profile.triggers[status].gain.player or "" end,
				set = function (v) self.db.profile.triggers[status].gain.player = (v ~= "" and v) end,
				validate = validate_alert_or_empty,
			},
			gain_units = {
				type = "group",
				name = L["Other players gain"],
				desc = L["Alert when other players gain Status"],
				args = {},
			},
			lost_all = {
				type = "text",
				name = L["All units lose"],
				desc = L["Alert to trigger when all units have lost Status"],
				usage = L["<alert>"],
				get = function () return self.db.profile.triggers[status].lost.all or "" end,
				set = function (v) self.db.profile.triggers[status].lost.all = (v ~= "" and v) end,
				validate = validate_alert_or_empty,
			},
			lost_player = {
				type = "text",
				name = L["Player loses"],
				desc = L["Alert to trigger when the player loses Status"],
				usage = L["<alert>"],
				get = function () return self.db.profile.triggers[status].lost.player or "" end,
				set = function (v) self.db.profile.triggers[status].lost.player = (v ~= "" and v) end,
				validate = validate_alert_or_empty,
			},
			lost_units = {
				type = "group",
				name = L["Other players lose"],
				desc = L["Alert when other players lose Status"],
				args = {},
			},
		},
	}
end

function GridAlert:Grid_StatusRegistered(status, desc)
	self:AddStatusOptions(status, desc)
end

function GridAlert:Grid_StatusUnregistered(status)
	self.options.args.triggers.args[status] = nil
end

function GridAlert:OnEnable()
	local alerts = self.options.args.alerts.args
	for status, _, desc in GridStatus:RegisteredStatusIterator() do
		self:AddStatusOptions(status, desc)
	end
	
	for name in pairs(self.db.profile.alerts) do
		if not self:HasModule(name) then
			self:AddAlertOptions(name)
		end
	end
	
	self:RegisterEvent("Grid_StatusRegistered")
	self:RegisterEvent("Grid_StatusUnregistered")
	self:RegisterEvent("Grid_StatusGained")
	self:RegisterEvent("Grid_StatusLost")

	self:EnableModules()
end

function GridAlert:OnDisable()
	local p = self.db.profile
	for name, module in self:IterateModules() do
		self:ToggleModuleActive(module, false)
	end
end


function GridAlert:Grid_StatusGained(name, status, priority, range, color, text, value, maxValue, texture)
	local settings = self.db.profile.triggers[status]
	local count = self.alert_counts[status] or 0
	if settings and settings.gain then
		local alert = (count == 0 and settings.gain.any)
		if alert then
			self:TriggerAlert(alert, name)
		end

		self.alert_counts[status] = count + 1
		
		alert = settings.gain.player
		if not alert or name ~= player_name then
			alert = settings.gain.units and settings.gain.units[name]
		end
		if alert then
			self:TriggerAlert(alert, name)
		end
	end
end

function GridAlert:Grid_StatusLost(name, status)
	local settings = self.db.profile.triggers[status]
	local count = self.alert_counts[status] or 1
	if settings and settings.lost then
		local alert = (count == 1 and settings.lost.all)
		if alert then
			self:TriggerAlert(alert, name)
		end

		self.alert_counts[status] = count - 1

		alert = settings.lost.player
		if not alert or name ~= player_name then
			alert = settings.lost.units and settings.lost.units[name]
		end
		if alert then
			self:TriggerAlert(alert, name)
		end
	end
end

function GridAlert:TriggerAlert(alert, name)
	local p = self.db.profile
	local settings = p.alerts[alert]
	if not settings then
		self:Debug("Invalid alert played : \"%s\", I don't know it\n", alert)
		return
	end
	local cur_time = GetTime()
	
	if cur_time - (self.last_play_time or 0) < p.min_pause or 
	   cur_time - (self.notification_times[alert] or 0) < settings.min_pause then
		self:Debug("Skipping alert \"%s\"", alert)
	else
		self.last_play_time = cur_time
		self.notification_times[alert] = cur_time
		
		self:TriggerEvent("GridAlert_Notify", alert)
		if settings.sound then
			PlaySoundFile(media:Fetch("sound", settings.sound))
		end
        local message = settings.message
		if message then
            message = message:gsub("%%t", name)
			self:Pour(message, 1, 0, 0)
		end
	end
end
