local function StrSplit(inputString, separator)
	local outputTable = {}
	for str in string.gmatch(inputString, "([^" .. separator .. "]+)") do
		outputTable[#outputTable + 1] = str
	end
	return outputTable
end

local function PrintTable(table)
	for k, v in pairs(table) do
		print(k .. "    " .. v)
	end
end

aura_env.GetClassColor = function(class)
	if class == "Death Knight" then
		return "\124cFFC41E3A"
	elseif class == "Druid" then
		return "\124cFFFF7C0A"
	elseif class == "Hunter" then
		return "\124cFFAAD372"
	elseif class == "Mage" then
		return "\124cFF3FC7EB"
	elseif class == "Paladin" then
		return "\124cFFF48CBA"
	elseif class == "Priest" then
		return "\124cFFFFFFFF"
	elseif class == "Rogue" then
		return "\124cFFFFF468"
	elseif class == "Shaman" then
		return "\124cFF0070DD"
	elseif class == "Warlock" then
		return "\124cFF8788EE"
	elseif class == "Warrior" then
		return "\124cFFC69B6D"
	end
end

local Unit = {
	New = function(self, unit)
		o = {
			["unit"] = unit,
		}
		setmetatable(o, self)
		self.__index = self
		return o
	end,

	GetAuras = function(self, auraFunc, name, type) return nil end,
}
local BasicUnit = Unit:New("player")
function BasicUnit:GetAuras(auraFunc, name)
	local aura, _, _, stacks, _, duration, expirationTime, _, _, _, spellID = auraFunc(self.unit, name)
	if aura ~= nil then
		return {
			[UnitName(self.unit)] = {
				["duration"] = duration,
				["expirationTime"] = expirationTime,
				["spellID"] = spellID,
				["stacks"] = stacks or 1,
			},
		}
	else
		return {}
	end
end
-- Maybe implement some sort of throttle to group unit?
local GroupUnit = Unit:New("group")
function GroupUnit:GetAuras(auraFunc, name)
	local raidMem = GetNumRaidMembers()
	local num = GetNumPartyMembers()
	local unitPrefix = "party"
	if raidMem > num then
		unitPrefix = "raid"
		num = raidMem
	end
	auras = {}
	for i = 1, num do
		local aura, _, _, stacks, _, duration, expirationTime, _, _, _, spellID = auraFunc(unitPrefix .. i, name)
		if aura ~= nil then
			auras[UnitName(unitPrefix .. i)] = {
				["duration"] = duration,
				["expirationTime"] = expirationTime,
				["spellID"] = spellID,
				["class"] = UnitClass(unitPrefix .. i) or "Paladin",
				["stacks"] = stacks or 1,
			}
		end
	end
	if unitPrefix == "party" then
		local aura, _, _, stacks, _, duration, expirationTime, _, _, _, spellID = auraFunc("player", name)
		if aura ~= nil then
			auras[UnitName("player")] = {
				["duration"] = duration,
				["expirationTime"] = expirationTime,
				["spellID"] = spellID,
				["class"] = UnitClass(unitPrefix .. i) or "Paladin",
				["stacks"] = stacks or 1,
			}
		end
	end
	return auras
end
-- Nameplate does not work, find out why?
local NameplateUnit = Unit:New("nameplate")
function NameplateUnit:GetAuras(auraFunc, name)
	local unitPrefix = "nameplate"
	auras = {}
	for i = 1, 40 do
		local aura, _, _, stacks, _, duration, expirationTime, _, _, _, spellID = auraFunc(auraPrefix .. i, name)
		if aura ~= nil then
			auras[UnitName(unitPrefix .. i)] = {
				["duration"] = duration,
				["expirationTime"] = expirationTime,
				["spellID"] = spellID,
				["stacks"] = stacks or 1,
			}
		end
	end
	return auras
end

local UnitFactory = {
	New = function(self)
		o = {}
		setmetatable(o, self)
		self.__index = self
		return o
	end,

	CreateUnit = function(self, target)
		target = string.lower(target)
		if target == "player" or target == "target" or target == "focus" then
			return BasicUnit:New(target)
		elseif target == "group" then
			return GroupUnit:New(target)
		elseif target == "nameplate" then
			return NameplateUnit:New(target)
		end
	end,
}
local unitFactory = UnitFactory:New()

local Aura = {
	New = function(self, entry, index)
		o = {
			["name"] = entry.auraName,
			["unit"] = entry.target,
			["hasCooldown"] = entry.hasCooldown,
			["index"] = index,
			["GetAura"] = entry.GetAura,
		}
		setmetatable(o, self)
		self.__index = self
		return o
	end,

	IsActive = function(self)
		for k, v in pairs(self.unit:GetAuras(self.GetAura, self.name)) do
			for k2, v2 in pairs(v) do
			end
			return true
		end
		return false
	end,
	IsOnCooldown = function(self)
		if not self.hasCooldown then return false end
		return GetSpellCooldown(self.name) > 0
	end,

	AddAsAura = function(self, allstates)
		local auras = self.unit:GetAuras(self.GetAura, self.name)
		for k, v in pairs(auras) do
			duration = v.duration
			expirationTime = v.expirationTime
			icon = self:GetAuraIcon(v.spellID)
			allstates[self.name .. k] = {
				changed = true,
				show = true,
				resort = true,
				progressType = "timed",
				duration = duration,
				expirationTime = expirationTime,
				index = self.index,
				icon = icon,
				pname = k,
				stacks = v.stacks,
				pclass = v.class,
				IsOnCooldown = true,
				IsActive = true,
				IsBad = self.GetAura == UnitDebuff,
			}
		end
	end,
	AddAsCooldown = function(self, allstates)
		if not self.hasCooldown then return false end
		startTime, duration = GetSpellCooldown(self.name)
		icon = self:GetSpellIcon()
		allstates[self.name] = {
			changed = true,
			show = true,
			resort = true,
			progressType = "timed",
			duration = duration,
			expirationTime = startTime + duration,
			index = self.index,
			icon = icon,
			IsOnCooldown = true,
			IsActive = false,
			IsBad = self.GetAura == UnitDebuff,
		}
	end,
	AddAsIcon = function(self, allstates)
		if not self.hasCooldown then return false end
		icon = self:GetSpellIcon()
		allstates[self.name] = {
			changed = true,
			show = true,
			resort = true,
			progressType = "static",
			value = 1,
			total = 1,
			index = self.index,
			icon = icon,
			IsOnCooldown = false,
			IsActive = false,
			IsBad = self.GetAura == UnitDebuff,
		}
	end,

	GetSpellIcon = function(self) return select(3, GetSpellInfo(self.name)) end,
	GetAuraIcon = function(self, spellID) return select(3, GetSpellInfo(spellID)) end,
}

local Entry = {
	New = function(self, entry)
		entry = self:TrimWhitespace(entry)

		local entryData = StrSplit(entry, ",")
		local name = self:ReadEntryData(entryData, 1)
		local type = self:ReadEntryData(entryData, 2) or "Buff"
		local target = unitFactory:CreateUnit(self:ReadEntryData(entryData, 3) or "Player")
		local cooldown = (self:ReadEntryData(entryData, 4) == "0") or false
		cooldown = not cooldown
		local GetAura = UnitBuff

		if type == "Debuff" then GetAura = UnitDebuff end

		o = {
			["entry"] = entry,
			["auraName"] = name,
			["GetAura"] = GetAura,
			["target"] = target,
			["hasCooldown"] = cooldown,
		}
		setmetatable(o, self)
		self.__index = self
		return o
	end,

	ReadEntryData = function(self, entryData, index)
		local str = entryData[index]
		if str == nil then return nil end
		str = self:TrimWhitespace(str)
		return str
	end,

	TrimWhitespace = function(self, str)
		str = str:gsub("^[ ]+", "")
		str = str:gsub("\n$", "")
		str = str:gsub("[ ]+$", "")
		return str
	end,
}

aura_env.auras = {}
for entry in string.gmatch(aura_env.config.spellList, "([a-zA-Z,0-9 ]+)") do
	entry = Entry:New(entry)

	auraObj = Aura:New(entry, #aura_env.auras + 1)
	aura_env.auras[#aura_env.auras + 1] = auraObj
end

aura_env.HandleEvent = function(allstates)
	for k, v in ipairs(aura_env.auras) do
		if v:IsActive() then
			v:AddAsAura(allstates)
		elseif v:IsOnCooldown() and not v:IsActive() then
			v:AddAsCooldown(allstates)
		elseif not v:IsOnCooldown() and not v:IsActive() then
			v:AddAsIcon(allstates)
		end
	end
end
