local AppName = "NDrums"
local VERSION = AppName .. "-r" .. ("$Revision: 2 $"):match("%d+")

local AceConfig = LibStub("AceConfig-3.0")
local AceDBOptions = LibStub("AceDBOptions-3.0")
local ACD = LibStub("AceConfigDialog-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale(AppName)
local NDrumsFrame = CreateFrame("Button", AppName, UIParent)
NDrumsFrame:SetScript("OnEvent", function(this, event, ...)
this[event](this, ...)
end)

-- internal vars

local db
local _ -- throwaway
local lastNotificationTime = 0
local cooldowns

-- cached stuff

local UnitInParty = UnitInParty
local GetTime = GetTime
local UnitName = UnitName

-- hard coded options

local PName = UnitName("player")
local SoundFileLow = "Interface\\Addons\\NDrums\\Sounds\\DrumsSoft75.mp3"
local SoundFileMedium = "Interface\\Addons\\NDrums\\Sounds\\DrumsSoft89.mp3"
local SoundFileHigh = "Interface\\Addons\\NDrums\\Sounds\\DrumsSoft105.mp3"
local LastNotificationTimeDelta = 5 -- interval in seconds where there can be only one notification to the player
local Lag = 0.5
local DrumsIcon
_, _, DrumsIcon = GetSpellInfo(35476)
local Party = "PARTY"
local Rotation = "Rotation: "
local CooldownLength = 120
---------------------------------

NDrums = LibStub("AceAddon-3.0"):NewAddon(AppName, "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0")
NDrums:SetDefaultModuleState(false)

NDrums.version = VERSION

-- Default DB stuff

local AudioVolumes = {
	["LOW"] = L["Low"],
	["MEDIUM"] = L["Medium"],
	["HIGH"] = L["High"],
}

local DrumsTypes = {
	["WAR"] = L["Drums of War"],
	["BATTLE"] = L["Drums of Battle"],
	["RESTORATION"] = L["Drums of Restoration"],
	["SPEED"] = L["Drums of Speed"],
}

local DrumsIds = {
	["WAR"] = 35475,
	["BATTLE"] = 35476,
	["RESTORATION"] = 35478,
	["SPEED"] = 35477,
}

local defaults = {
	profile = {
		enabled = true,
		drumsType = "BATTLE",
		audioWarning = true,
		audioVolume = "HIGH",
		bigWigs = true,
		dbm = true,
		previousDrummer = "",
		nextDrummer = "",
		partyStartMessage = L["Drums activated!"],
		partyEndMessage = L["Drums expired!"],
		whisperMessage = L["Drums, drums!"],
		whisperMatch = L["drums"],
		debug = false,
		cooldowns = true,
	},
}

local options = {
	type = "group",
	name = AppName,
	handler = NDrums,
	get = function(info) return db[info[#info]] end,
	set = "setOption",
	args = {
		enabled = {
			type = 'toggle',
			name = L["Enabled"],
			desc = L["Enable drums logic"],
			order = 1,
		},
		description1 = {
			type = 'description',
			name = '',
			order = 5,
		},
		audioWarning = {
			type = 'toggle',
			name = L["Audio Warning"],
			desc = L["Audio warns you when its time to drum"],
			order = 10,
		},
		audioVolume = {
			type = 'select',
			name = L["Audio Volume"],
			desc = L["Audio volume level"],
			values = AudioVolumes,
			order = 15,
			width = 'half'
		},
		description2 = {
			type = 'description',
			name = '',
			order = 20,
		},
		bigWigs = {
			type = 'toggle',
			name = L["BigWigs Integration"],
			desc = L["Uses BigWigs to track previous drummer"],
			order = 25,
		},
		dbm = {
			type = 'toggle',
			name = L["DBM Integration"],
			desc = L["Uses Deadly Bossmods to track previous drummer"],
			order = 30,
		},
		description3 = {
			type = 'description',
			name = '',
			order = 35,
		},
		drumsType = {
			type = 'select',
			name = L["Drums Type"],
			desc = L["Types of drums you are going to use"],
			values = DrumsTypes,
			order = 40,
		},
		cooldowns = {
			type = 'toggle',
			name = L["Cooldowns"],
			desc = L["Monitors cooldowns and notifies at the time next drummer has no drums cooldown"],
			order = 43,
		},
		description4 = {
			type = 'description',
			name = '',
			order = 45,
		},
		previousDrummer = {
			type = 'input',
			name = L["Previous Drummer"],
			desc = L["Player who drums before you"],
			order = 50,
		},
		setPreviousDrummer = {
			type = "execute",
			name = L["Target"],
			desc = L["Set the name of your current target"],
			func = function() NDrums:SetPreviousDrummerFromTarget() end,
			order = 55,
			width = 'half',
		},
		description5 = {
			type = 'description',
			name = '',
			order = 60,
		},
		nextDrummer = {
			type = 'input',
			name = L["Next Drummer"],
			desc = L["Next player to drum"],
			order = 65,
		},
		setNextDrummer = {
			type = "execute",
			name = L["Target"],
			desc = L["Set the name of your current target"],
			func = function() NDrums:SetNextDrummerFromTarget() end,
			order = 70,
			width = 'half',
		},
		description6 = {
			type = 'description',
			name = '',
			order = 75,
		},
		partyStartMessage = {
			type = 'input',
			name = L["Party Start Message"],
			desc = L["A message sent to party chat when you activate your drums"],
			order = 80,
		},
		partyEndMessage = {
			type = 'input',
			name = L["Party End Message"],
			desc = L["A message sent to party chat when your drums expire"],
			order = 85,
		},
		description7 = {
			type = 'description',
			name = '',
			order = 90,
		},
		whisperMessage = {
			type = 'input',
			name = L["Whisper Message"],
			desc = L["A message to be whispered to the next person"],
			order = 95,
		},
		whisperMatch = {
			type = 'input',
			name = L["Whisper Match"],
			desc = L["If a whisper message contains this, the drums notifications will be activated"],
			order = 100,
		},
		description8 = {
			type = 'description',
			name = '',
			order = 105,
		},
		debug = {
			type = 'toggle',
			name = L["Debug"],
			desc = L["Disable it or you can lose your harddrive"],
			order = 110,
		},
		defaultOptions = {
			type = "execute",
			name = L["Defaults"],
			desc = L["Resets all options to defaults"],
			func = function() NDrums:DefaultOptions() end,
			order = 120,
			width = 'half',
		},
		config = {
		    type = 'execute',
		    name = L["Configure"],
		    desc = L["Bring up GUI configure dialog"],
		    guiHidden = true,
		    order = 300,
		    func = function() NDrums:OpenConfigDialog() end,
		},
		rotation =  {
		    type = 'input',
		    name = L["Rotation"],
		    desc = L["Set up drums rotation for all party members"],
		    guiHidden = true,
		    order = 310,
		    set = "SetRotation",
		},
	},
}

-- config stuff

function NDrums:OpenConfigDialog()
	ACD:SetDefaultSize(AppName, 400, 500)
	ACD:Open(AppName, configFrame)
end

function NDrums:setOption(info, value)
	db[info[#info]] = value
	self:applySettings()
end

function NDrums:applySettings()
	self:FormatPlayerNames()
end

function NDrums:addConfigTab(key, group, order, isCmdInline)
	if (not self.configOptions) then
		self.configOptions = {
			type = "group",
			name = AppName,
			childGroups = "tab",
			args = {},
		}
	end
	self.configOptions.args[key] = group
	self.configOptions.args[key].order = order
	self.configOptions.args[key].cmdInline = isCmdInline
end

-- Event functions

function NDrums:OnInitialize()
	self:debugMsg("Initializing")

	self.db = LibStub("AceDB-3.0"):New("NDrumsDB1", defaults)
	self.db.RegisterCallback(self, "OnProfileChanged")
	db = self.db.profile
	self:addConfigTab('main', options, 10, true)
	self:addConfigTab('profiles', AceDBOptions:GetOptionsTable(self.db), 20, false)
	AceConfig:RegisterOptionsTable(AppName, self.configOptions, "ndrums")
	ACD:AddToBlizOptions(AppName, AppName)
end

function NDrums:OnEnable()
	if (db.debug) then self:debugMsg("OnEnable") end
	
	self:OnProfileChanged()
	self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
	self:RegisterEvent("CHAT_MSG_WHISPER")
	self:RegisterEvent("CHAT_MSG_ADDON")
	NDrumsFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

	cooldowns = {}
end

function NDrums:OnDisable()
	if (db.debug) then self:debugMsg("OnDisable") end

	self:UnregisterAllEvents()
	NDrumsFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	self:CancelAllTimers()

	cooldowns = {}
end

function NDrums:OnProfileChanged()
	if (db.debug) then self:debugMsg("OnProfileChanged") end

	db = self.db.profile
	self:applySettings()
end

function NDrums:MyDrumsExpired()
	if not db.enabled then return end

	SendChatMessage(self:FormatDrumsMessage(db.partyEndMessage), Party)
end

function NDrums:MyDrumsNotifyNext()
	if not db.enabled then return end

	if UnitInParty(db.nextDrummer) then
		SendChatMessage(self:FormatDrumsMessage(db.whisperMessage),"Whisper",nil,db.nextDrummer)
	end	
end

function NDrums:PreviousDrumsExpired()
	if not db.enabled then return end

	self:DrumsWarning()
end

function NDrums:CHAT_MSG_WHISPER(_, msg, author)
	if not db.enabled then return end

	if (db.debug) then self:debugMsg("CHAT_MSG_WHISPER: msg="..msg.." Author="..author) end

	if (UnitInParty(author)) then
		if (string.find(string.upper(msg), string.upper(db.whisperMatch))) then
			self:DrumsWarning()
		end
	end
end

function NDrums:UNIT_SPELLCAST_SUCCEEDED(_, unit, spellName)
	if not db.enabled then return end

	--if (db.debug) then self:debugMsg("UNIT_SPELLCAST_SUCCEEDED: Unit="..unit.." SpellName="..spellName) end

	if (unit == "player") then
		
	end
end

function NDrums:CHAT_MSG_ADDON(_, prefix, msg, distType, sender)
	if not db.enabled then return end

	if not (prefix == AppName) then return end

	if (db.debug) then self:debugMsg("CHAT_MSG_ADDON: " .. msg) end

	local startPos = string.find(msg, Rotation)
	
	if (db.debug) then self:debugMsg("startPos: " .. string.format("%d", startPos)) end

	if (startPos == 1) then
		local p1, p2, p3, p4, p5 = self:GetArgs(msg, 5, 11)
		local p = {}
		p[1] = p1
		p[2] = p2
		p[3] = p3
		p[4] = p4
		p[5] = p5

		if (db.debug) then 
			self:debugMsg("Players: " .. (p[1] or "") .. " : " .. (p[2] or "") .. " : " .. (p[3] or "") .. " : " .. (p[4] or "") .. " : " .. (p[5] or ""))
		end

		local i = 1
		local firstP
		local lastP

		while (p[i]) do
			if (i == 1) then firstP = p[i] end

			if (p[i] == PName) then
				if (p[i+1]) then
					db.nextDrummer = p[i+1]
				else
					db.nextDrummer = firstP
				end

				if (i == 1) then
					while (p[i]) do
						lastP = p[i]
						i = i + 1
					end
					db.previousDrummer = lastP
				else
					db.previousDrummer = p[i-1]
				end

				self:applySettings()

				self:AnnounceSelfRotation()
			end

			i = i + 1
		end
	end
end

function NDrumsFrame:COMBAT_LOG_EVENT_UNFILTERED(timestamp, event, sourceGUID, sourceName, sourceFLags, destGUID, destName, destFLags, ...)
	if not db.enabled then return end
	if not BigWigs then return end

	if event == nil then return end
	if sourceName == nil then return end

	if event == "SPELL_CAST_SUCCESS" then
		spellId, spellName, spellSchool = select(1, ...)
		
		--if (db.debug) then NDrums:debugMsg("SPELL_CAST_SUCCESS: SpellName="..spellName.." SpellId="..spellId) end

		if (spellId == DrumsIds["WAR"]) or (spellId == DrumsIds["RESTORATION"]) or (spellId == DrumsIds["BATTLE"]) or (spellId == DrumsIds["SPEED"]) or (spellId == DrumsIds["PANIC"]) then
			if (db.debug) then NDrums:debugMsg("Setting cooldown: Player="..sourceName) end

			NDrums:SetCooldown(sourceName, GetTime())
		end

		if (spellId == DrumsIds[db.drumsType]) then
			if (sourceName == PName) then
				if (db.debug) then NDrums:debugMsg("My Drums activated") end

				SendChatMessage(NDrums:FormatDrumsMessage(db.partyStartMessage), Party)
				local duration = NDrums:GetDrumsDuration(spellId)
				local expiredDuration = duration - Lag

				if (db.cooldowns) then
					local durDiff = GetTime() + duration - NDrums:GetCooldown(db.nextDrummer) - CooldownLength
					if (db.debug) then NDrums:debugMsg("UNIT_SPELLCAST_SUCCEEDED: durDiff=" .. durDiff) end
					if (durDiff < 0) then duration = duration - durDiff end
				end
				duration = duration - Lag
				
				if (db.debug) then NDrums:debugMsg("My drums activated") end

				NDrums:ScheduleTimer("MyDrumsExpired", expiredDuration)
				NDrums:ScheduleTimer("MyDrumsNotifyNext", duration)
			end

			if (UnitInParty(sourceName)) then
				if (sourceName == db.previousDrummer) then
					local duration = NDrums:GetDrumsDuration(spellId)
					if (db.cooldowns) then
						local durDiff = GetTime() + duration - NDrums:GetCooldown(PName) - CooldownLength
						if (db.debug) then NDrums:debugMsg("COMBAT_LOG_EVENT_UNFILTERED: durDiff=" .. durDiff) end
						if (durDiff < 0) then duration = duration - durDiff end
					end
					duration = duration - Lag

					NDrums:BigWigsBar(duration, L["Your time to drum"], L["Drums, drums!"])
					NDrums:DBMBar(duration, L["Your time to drum"], L["Drums, drums!"])
					NDrums:ScheduleTimer("PreviousDrumsExpired", duration)
				end
			end
		end
	end
end

-- Nonevent main functions
function NDrums:SetRotation(info, value)
	if (db.debug) then self:debugMsg("Value:" .. value) end

	local p1,p2,p3,p4,p5 = self:GetArgs(value, 5)

	if (db.debug) then self:debugMsg(p1) end
	if (db.debug) then self:debugMsg(p2) end

	local tP = {}
	local p = {}
	tP[1] = p1
	tP[2] = p2
	tP[3] = p3
	tP[4] = p4
	tP[5] = p5

	if (db.debug) then 
		self:debugMsg("Players: " .. (p1 or "") .. " : " .. (p2 or "") .. " : " .. (p3 or "") .. " : " .. (p4 or "") .. " : " .. (p5 or ""))
	end

	local i = 1
	local j = 0
	while (tP[i]) do
		if (UnitInParty(tP[i])) then
			j = j + 1
			p[j] = self:FormatPlayerName(tP[i])
		end
		i = i + 1
	end

	if (db.debug) then 
		self:debugMsg("Players: " .. (p[1] or "") .. " : " .. (p[2] or "") .. " : " .. (p[3] or "") .. " : " .. (p[4] or "") .. " : " .. (p[5] or ""))
	end

	local pMsg = "NDrums. "..L["Rotation: "]
	local aMsg = Rotation
	local m = "\""

	i = 1
	while (p[i]) do
		pMsg = pMsg..m..p[i]..m
		aMsg = aMsg .. p[i]
		i = i + 1 
		if (p[i]) then
			pMsg = pMsg .. " > "
			aMsg = aMsg .. " "
		end
	end

	SendChatMessage(pMsg, Party)
	SendAddonMessage(AppName, aMsg, Party)
end

-- Utility functions

function NDrums:GetDrumsDuration(drumsId)
	if (drumsId == DrumsIds["WAR"]) then return 30 end
	if (drumsId == DrumsIds["BATTLE"]) then return 30 end
	if (drumsId == DrumsIds["RESTORATION"]) then return 15 end
	if (drumsId == DrumsIds["SPEED"]) then return 30 end
end

function NDrums:BigWigsBar(seconds, barText, attentionText)
	if not db.bigWigs then return end

	if (db.debug) then self:debugMsg("Communicating with BigWigs: Seconds="..seconds.." BarText="..barText.." AttentionText="..attentionText) end

	BigWigs:ToggleActive(true)

	BigWigs:ScheduleEvent("bwcb"..barText, "BigWigs_Message", seconds, attentionText, "Attention", localOnly)
	BigWigs:TriggerEvent("BigWigs_StartBar", self, barText, seconds, DrumsIcon) --"Interface\\Icons\\INV_Misc_PocketWatch_01")
end

function NDrums:DBMBar(seconds, barText, attentionText)
	if not db.dbm then return end
	if not DBM then return end

	if (db.debug) then self:debugMsg("Communicating with Deadly Bossmods: Seconds="..seconds.." BarText="..barText.." AttentionText="..attentionText) end

	DBM.StartStatusBarTimer(seconds, barText, DrumsIcon, true) --"Interface\\Icons\\INV_Misc_PocketWatch_01", true) 
	DBM.Schedule(seconds, DBM.Announce, attentionText, true)
end

function NDrums:DrumsWarning()
	if (GetTime() < lastNotificationTime + LastNotificationTimeDelta) then return end

	lastNotificationTime = GetTime()

	if (db.audioWarning) then
		if (db.debug) then self:debugMsg("Playing audio: AudioVolume="..db.audioVolume) end
		if (db.audioVolume=="LOW") then
			PlaySoundFile(SoundFileLow)
		end
		if (db.audioVolume=="MEDIUM") then
			PlaySoundFile(SoundFileMedium)
		end
		if (db.audioVolume=="HIGH") then
			PlaySoundFile(SoundFileHigh)
		end
	end
end

function NDrums:SetNextDrummerFromTarget()
	local tName = UnitName("target")
	if (tName) then
		db.nextDrummer = tName
	else
		db.nextDrummer = ""
	end
	self:applySettings()
end

function NDrums:SetPreviousDrummerFromTarget()
	local tName = UnitName("target")
	if (tName) then
		db.previousDrummer = tName
	else
		db.previousDrummer = ""
	end
	self:applySettings()
end

function NDrums:DefaultOptions()
	db.enabled = defaults.profile.enabled
	db.drumsType = defaults.profile.drumsType
	db.audioWarning = defaults.profile.audioWarning
	db.audioVolume = defaults.profile.audioVolume
	db.bigWigs = defaults.profile.bigWigs
	db.dbm = defaults.profile.dbm
	db.previousDrummer = defaults.profile.previousDrummer
	db.nextDrummer = defaults.profile.nextDrummer
	db.partyStartMessage = defaults.profile.partyStartMessage
	db.partyEndMessage = defaults.profile.partyEndMessage
	db.whisperMessage = defaults.profile.whisperMessage
	db.whisperMatch = defaults.profile.whisperMatch
	db.debug = defaults.profile.debug
	db.cooldowns = defaults.profile.cooldowns

	self:applySettings()	
end

function NDrums:ToggleRotationFromTarget()
	local tName = UnitName("target")
	if (not tName) then tName = "" end

	db.previousDrummer = db.nextDrummer
	db.nextDrummer = tName

	self:applySettings()

	self:AnnounceSelfRotation()
end

function NDrums:GetCooldown(pName)
	if (cooldowns[pName] == nil) then cooldowns[pName] = 0 end		

	return cooldowns[pName]
end

function NDrums:SetCooldown(pName, time)
	if (cooldowns[pName] == nil) then cooldowns[pName] = 0 end		

	cooldowns[pName]= time
end

function NDrums:AnnounceSelfRotation()
	local m = "\""
	self:Print(L["Rotation: "]..m..db.previousDrummer..m.." > "..m..PName..m.." > "..m..db.nextDrummer..m)
end

function NDrums:FormatPlayerNames(pName)
	db.previousDrummer = self:FormatPlayerName(db.previousDrummer)
	db.nextDrummer = self:FormatPlayerName(db.nextDrummer)
end

function NDrums:FormatPlayerName(pName)
	if (pName == nil) then return nil end

	if (string.len(pName)<2) then return string.upper(pName) end

	return string.upper(string.sub(pName, 1, 1)) .. string.lower(string.sub(pName, 2))
end

function NDrums:FormatDrumsMessage(msg)
	if (msg == nil) then return nil end

	return string.gsub(msg, "%%d", DrumsTypes[db.drumsType])
end

function NDrums:debugMsg(msg)
	DEFAULT_CHAT_FRAME:AddMessage(AppName..": "..msg)
end
