--[[---------------------------------------------------------------------------------
  Class declaration
------------------------------------------------------------------------------------]]

local elapsed = 0
local visible,frames,dirty,aura,dirtyStatus = {},{},{},{},{}
local marked,selected,sort,keys,registry= {},{},{},{},{}
local feign,soulstone,inrange,innervate,poweri = {},{},{},{},{}
local anyDirty,dirtyRoster
local force = 0

local debug = false

PerfectRaid = {
	name			= 'PerfectRaid',
	description		= "PerfectRaid is a very minimal set of configurable raid frames.",
	version			= '1.0',
	releaseDate		= '2006-02-12',
	author			= 'Cladhaire',
	email			= 'cladhaire@gmail.com',
	website			= 'http://cladhaire.wowinterface.com',

	Initialize = function(self) 
		self:Debug("Initializing PefectRaid.")
		self.poolsize = 0
		self.frames = frames
		self.visible = visible
		self.tooltip = PerfectRaidTooltip
		self.master = CreateFrame("Frame", "PerfectRaidFrame", UIParent)
		self.master:SetScript("OnEvent", self.EventHandler)
		self.master:SetScript("OnUpdate", self.OnUpdate)
		self.master:SetMovable(true)
		
		self:RegisterEvent("PLAYER_ENTERING_WORLD")
		self:RegisterEvent("RAID_ROSTER_UPDATE")
		self:RegisterEvent("ADDON_LOADED")
		
		self.defaults = { 	Align			= "left",
							Select			= "none",
							Sort			= "groupname",
							BackdropBar		= "bar 0 0 0 1",
							BackdropFrame	= "frame 0 0 0 0",
							Truncate		= true,
							LowMana			= 25,
							Scale			= 1,
							Voffset			= -5,
							Filter			= false,
							RangeCheck		= false,
						}
	end,
	
	Enable = function (self)	
		self.enabled = true
		self:RegisterEvent("UNIT_HEALTH")
		self:RegisterEvent("UNIT_AURA")
		self:RegisterEvent("PLAYER_TARGET_CHANGED")
		self:RegisterEvent("CHAT_MSG_SYSTEM")
		
		self:RangeHooks()
		
		if not self.virtualfont then
			self.virtualfont = self.master:CreateFontString(nil, "ARTWORK")
			self.virtualfont:SetFontObject(GameFontHighlightSmall)
		end

		-- Enable the OnUpdates
		self.master:Show()
		
		-- If we find WatchDog, enable click-casting
		if WatchDog_OnClick then
			PerfectRaidCustomClick = WatchDog_OnClick
		end
	end,

	Disable = function(self)
		self:UnhookAll()
		self.enabled = nil
		self.master:Hide()
	end,
	
	RangeHooks = function(self)
		if self.opt.RangeCheck and not self:IsHooked("CastSpell") then
			self:Hook("CastSpell")
			self:Hook("CastSpellByName")
			self:Hook("UseAction")
		else
			self:UnhookAll()
		end
	end,
	
	CheckVariables = function(self)
		PerfectRaidOpt = PerfectRaidOpt or {}
		self.opt = PerfectRaidOpt
		setmetatable(self.opt, {__index=self.defaults, __call=function(v) return self.opt[v] end})
		
		if not self.opt.PosX then
			self.master:ClearAllPoints()
			self.master:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
			self.master:SetWidth(100)
			self.master:SetHeight(100)
		else
			self.master:ClearAllPoints()
			self.master:SetPoint("TOPLEFT", UIParent, "TOPLEFT", self.opt.PosX, self.opt.PosY)
			self.master:SetWidth(100)
			self.master:SetHeight(100)
		end
	end,
	
	SetOpt = function(self, var, val)
		if self.defaults[var] == val then
			self.opt[var] = nil
		else
			self.opt[var] = val
		end
	end,
	
--[[---------------------------------------------------------------------------------
  Event handlers
------------------------------------------------------------------------------------]]

	ADDON_LOADED = function(self)
		self:Debug("ADDON_LOADED")	
		AceHookLib:Embed(PerfectRaid, "1.4")
		
		if string.lower(arg1) == "perfectraid" then
			self:UnregisterEvent("ADDON_LOADED")
			self:CheckVariables()
			self:RestorePosition()

			if not self.cmd then
				self:Debug("Registering chat commands.")
				self.cmd = self.AceChatCmd:new(PERFECTRAID.CHAT_COMMANDS, PERFECTRAID.CHAT_OPTIONS)
				self.cmd.app = self
				self.cmd:Register(self)
			end
		end
	end,

	PLAYER_ENTERING_WORLD = function(self)
		self:Debug("PLAYER_ENTERING_WORLD")	
		self:RAID_ROSTER_UPDATE()
	end,

	RAID_ROSTER_UPDATE = function(self)
		self:Debug("RAID_ROSTER_UPDATE")
		if GetNumRaidMembers() > 0 then
			if not self.enabled then
				self:Debug("You've joined a raid-- enabling PerfectRaid.")
			end
			self:Enable()
			dirtyRoster = true
		elseif self.enabled then
			self:Disable()
		end
	end, 
	
	UNIT_HEALTH = function(self)
		--self:Debug("UNIT_HEALTH for %s.",arg1)
		if visible[arg1] then
			anyDirty = true
			dirty[arg1] = true
		end
	end,
	
	UNIT_AURA = function(self)
		--self:Debug("UNIT_AURA for %s.",arg1)
		if visible[arg1] then
			anyDirty = true
			dirtyStatus[arg1] = true
		end
	end,

	PLAYER_TARGET_CHANGED = function(self)
		self:Debug("PLAYER_TARGET_CHANGED")
				
		for k,v in pairs(visible) do
			if UnitIsUnit(k, "target") then
				local f = frames[k]
				marked[k] = true
				f.Prefix:SetText(self.mark)
				f.Prefix:SetTextColor(1,1,0)
--[[
			elseif UnitIsUnit(k, "targettarget") then
				marked[k] = true
				f.Prefix:SetText(self.ttmark)
				f.Prefix:SetTextColor(1,1,0)
--]]
			elseif marked[k] then 
				local f = frames[k]
				marked[k] = nil
				if selected[k] then
					f.Prefix:SetTextColor(1,1,1)
					if self.opt.Select == "number" then 
						f.Prefix:SetText(f.group)
					else
						f.Prefix:SetText(">")
					end
				else
					f.Prefix:SetText()
				end
			end
		end
	end,
	
	CHAT_MSG_SYSTEM = function(self)
		if string.find(arg1, ERR_RAID_YOU_LEFT) then
			self:Disable()
		end
	end,
	
	CastSpell = function(self,a1,a2,a3) 
		rangecheck = true
		self.Hooks.CastSpell.orig(a1,a2,a3)
		
		if SpellIsTargeting() then
			for k,v in visible do
				if SpellCanTargetUnit(k) then
					frames[k]:SetAlpha(1.0)
				else
					frames[k]:SetAlpha(0.5)
				end
			end
		end
	end,
	
	CastSpellByName = function(self,a1,a2,a3) 
		rangecheck = true
		self.Hooks.CastSpellByName.orig(a1,a2,a3)
		
		if SpellIsTargeting() then
			for k,v in visible do
				if SpellCanTargetUnit(k) then
					frames[k]:SetAlpha(1.0)
				else
					frames[k]:SetAlpha(0.5)
				end
			end
		end
	end,
	
	UseAction = function(self,a1,a2,a3) 
		rangecheck = true
		self.Hooks.UseAction.orig(a1,a2,a3)
		
		if SpellIsTargeting() then
			for k,v in visible do
				if SpellCanTargetUnit(k) then
					frames[k]:SetAlpha(1.0)
				else
					frames[k]:SetAlpha(0.5)
				end
			end
		end
	end,
	
--[[---------------------------------------------------------------------------------
  Main Addon
------------------------------------------------------------------------------------]]	

	OnUpdate = function()
		elapsed = elapsed + arg1
		force = force + arg1
				
		if elapsed > 0.20 and (anyDirty or dirtyRoster) then
			local self = PerfectRaid
			elapsed = 0
			anyDirty = nil
			
			if dirtyRoster then
				self:UpdateVisibility()		
				for k,v in pairs(visible) do
					self:UpdateUnit(k)
					self:UpdateStatus(k)
				end
				dirtyRoster = nil
			end
			
			for k,v in pairs(dirty) do
				dirty[k] = nil
				self:UpdateUnit(k)
			end

			for k,v in pairs(dirtyStatus) do
				dirtyStatus[k] = nil
				self:UpdateStatus(k)
			end
		elseif force > 1.0 then
			force = 0
			elapsed = 0
			anyDirty = nil
			
			for k,v in pairs(visible) do
				dirty[k] = nil
				PerfectRaid:UpdateUnit(k)
			end
		end
	end,
	
	UpdateUnit = function(self, unit)
		if not UnitExists(unit) then return end
		
		local f = frames[unit]
		
		if UnitClass(unit) then 	
			local _,class = UnitClass(unit)
			f.Name:SetText(PERFECTRAID.CLASSES[class]..UnitName(unit).."|r")
		else
			f.Name:SetText(UnitName(unit) or "Unknown")
		end

		local hp = UnitIsConnected(unit) and UnitHealth(unit) or 0
		local hpmax = UnitHealthMax(unit)
		local hpp = (hpmax ~= 0) and floor((hp / hpmax) * 100) or 0

		if UnitPowerType(unit) == 0 then
			
			local mana = UnitMana(unit) or 0
			local max = UnitManaMax(unit) or 1
			local mpp = floor((mana / max) * 100)
			
			if max > 0 and mpp <= self.opt.LowMana and not f.lowmana then 
				dirtyStatus[unit] = true
				f.lowmana = true
				self:UpdateStatus(unit)
			elseif f.lowmana and max > 0 and mpp > self.opt.LowMana then
				f.lowmana = nil
				self:UpdateStatus(unit)
			end
		end

		if not feign[unit] then
			f.Bar:SetValue(hpp)
			f.Bar:SetStatusBarColor(self:GetHPSeverity(hp/hpmax,1))
		else
			f.Bar:SetValue(100)
			f.Bar:SetStatusBarColor(0, 0.9, 0.78)
		end
		
	end,
	
	UpdateVisibility = function(self)
		local num = GetNumRaidMembers()
		if num == 0 then return end
		
		if num >= self.poolsize then self:CreateFrame(num) end
		
		frames[1]:Show()
		visible["raid1"] = true
		
		for i=2,num do
			frames[i]:Show()
			local id = "raid"..i
			visible[id] = true			
		end
		
		for i=(num+1),self.poolsize do
			visible["raid"..i] = nil
			if not frames[i] then break end
			frames[i]:Hide()
		end
		
		for k,v in visible do
			if not UnitName(k) then
				dirtyRoster = true
				return
			end
		end
		
		self:Sort(nil, true)
		self:Align(nil, true)
		self:Select(nil, true)
		self:SetBackdrop(nil, true)
		self:SetScale(nil, true)
	end,

	UpdateStatus = function(self, unit)
	
		soulstone[unit] = nil
		feign[unit] = nil
		innervate[unit] = nil
		poweri[unit] = nil
		
		if UnitIsDead(unit) then 
			for i=1,32 do
				local texture = UnitBuff(unit, i)
				if texture == "Interface\\Icons\\Ability_Rogue_FeignDeath" then
					feign[unit] = true
					break
				end
			end
			if not feign[unit] then 
				self.frames[unit].Status:SetText("|cffff0000Dead|r")
				return
			end
		end

		if UnitIsGhost(unit) then
			self.frames[unit].Status:SetText("|cff9d9d9dGhost|r")
			return
		elseif not UnitIsConnected(unit) then
			self.frames[unit].Status:SetText("|cffff8000Offline|r")
			return
		else
			-- Clear Feign Death when it comes off
			if feign[unit] and not UnitIsDead(unit) then feign[unit] = nil end
			
			for k,v in pairs(aura) do aura[k] = nil end
	
			if UnitPowerType(unit) == 0 and not feign[unit] then
				
				local mana = UnitMana(unit) or 0
				local max = UnitManaMax(unit) or 1
				local mpp = floor((mana / max) * 100)
				
				if max > 0 and mpp <= self.opt.LowMana then 
					aura.LowMana = true
				end
			end

			-- No easy way out, so we need to scan the debuffs
				for i=1,16 do
				if UnitDebuff(unit, i) then
					PerfectRaidTooltipTextRight1:SetText("")
					PerfectRaidTooltip:SetUnitDebuff(unit, i, self.opt.Filter)
					local type = PerfectRaidTooltipTextRight1:GetText()
					if PERFECTRAID.COLORS[type] then 
						aura[type] = true
					end
				end
				for i=1,32 do
					local texture = UnitBuff(unit, i)
					if not texture then break end
					if texture == "Interface\\Icons\\Spell_Shadow_SoulGem" then
						soulstone[unit] = true
					elseif texture == "Interface\\Icons\\Spell_Nature_Lightning" then
						innervate[unit] = true
					elseif texture == "Interface\\Icons\\Spell_Holy_PowerInfusion" then
						poweri[unit] = true
					end
				end
			end
			
			local output = string.format("%s%s%s%s%s%s%s%s%s",
										(soulstone[unit] and PERFECTRAID.COLORS.Soulstone) or "",
										(innervate[unit] and PERFECTRAID.COLORS.Innervate) or "",
										(poweri[unit] and PERFECTRAID.COLORS.PowerInfusion) or "",
										(feign[unit] and PERFECTRAID.COLORS.FeignDeath) or "",
										(aura[PERFECTRAID.CURSE] and PERFECTRAID.COLORS.Curse) or "",
										(aura[PERFECTRAID.DISEASE] and PERFECTRAID.COLORS.Disease) or "",
										(aura[PERFECTRAID.MAGIC] and PERFECTRAID.COLORS.Magic) or "",
										(aura[PERFECTRAID.POISON] and PERFECTRAID.COLORS.Poison) or "",
										(aura.LowMana and PERFECTRAID.COLORS.LowMana) or "")
			self.frames[unit].Status:SetText(output)
		end
	end,
	
	OnClick = function(self)
		local unit = this.unit
		if UnitIsUnit(unit, "player") then unit = "player" end
		
		local button = arg1

		-- To enable click-casting on this frame, all anyone has to do is
		-- set PerfectRaidCustomClick
		
		if PerfectRaidCustomClick then
			PerfectRaidCustomClick(arg1, unit) 
		else
			if not UnitExists(unit) then return end
	
			if SpellIsTargeting() then
				if button == "LeftButton" then SpellTargetUnit(unit)
				elseif button == "RightButton" then	SpellStopTargeting() end
				return
			end
		
			if CursorHasItem() then
				if button == "LeftButton" then
					if unit=="player" then AutoEquipCursorItem()
					else DropItemOnUnit(unit) end
				else PutItemInBackpack() end
				return
			end
			
			TargetUnit(unit)
		end
	end,

--[[---------------------------------------------------------------------------------
  Options handlers
------------------------------------------------------------------------------------]]

	-- Type is optional and defaults to the current sort type
	Sort = function(self, msg, force)
		if GetNumRaidMembers() == 0 then return end

		-- Clear the sort table
		for k,v in pairs(sort) do sort[k] = nil table.setn(sort, 0) end
		
		-- Insert all the active raid ids in the table
		for k in visible do 
			table.insert(sort, k)
			if not UnitName(k) then return end
		end		
		
		msg = msg or self.opt.Sort
		msg = string.lower(msg)
		
		-- Sort the table based on criteria
		if msg == "class" then 
			if not force then self:Msg("Sorting by Class.") end
			table.sort(sort, function(a,b)
								keys[a] = UnitClass(a)
								keys[b] = UnitClass(b)
								if UnitClass(a) == UnitClass(b) then 
									return UnitName(a) < UnitName(b) 
								else
									return UnitClass(a) < UnitClass(b) 
								end end)

		elseif msg == "raid" then
			if not force then self:Msg("Sorting by Raid.") end
			-- We don't need to sort if we're sorting raid, since we're in order
		
		elseif msg == "name" then 
			if not force then self:Msg("Sorting by Name.") end
			table.sort(sort, function(a,b) return UnitName(a) < UnitName(b) end)
		
		elseif msg == "group" then
			if not force then self:Msg("Sorting by Group.") end
			local sortGroup = function(a,b) 
				local _,_,groupA = GetRaidRosterInfo(self.frames[a].id) 
				local _,_,groupB = GetRaidRosterInfo(self.frames[b].id) 
				keys[a] = groupA
				keys[b] = groupB
				return groupA < groupB 
			end
			table.sort(sort, sortGroup)
		
		elseif msg == "groupclass" then 
			if not force then self:Msg("Sorting by Group/Class.") end
			local sortGroupName = function(a,b) 
				local _,_,groupA = GetRaidRosterInfo(self.frames[a].id) 
				local _,_,groupB = GetRaidRosterInfo(self.frames[b].id) 
				keys[a] = groupA
				keys[b] = groupB
				if groupA == groupB then 
					return UnitClass(a) < UnitClass(b)
				else
					return groupA < groupB 
				end
			end
			table.sort(sort, sortGroupName)
		end
		
		self:SetOpt("Sort", msg)
		
		-- We need to ensure that all the frames we need exist at this point
		local f = self.frames[sort[1]]
		f:ClearAllPoints()
		f:SetPoint("TOPLEFT", PerfectRaidFrame, "TOPLEFT", 0, 0)
		local last = f
		
		local vfont = self.virtualfont
		local truncate = self.opt.Truncate
		local length
		
		if truncate then
			vfont:SetText("Spartikus")
			length = vfont:GetStringWidth() 
		else
			length = f.Name:GetStringWidth()
		end
		
		local seperator = self.opt.Voffset
		local seperate, voffset = nil, 0
		if msg == "group" or msg == "groupclass" or msg == "class" then
			seperate = true
		end
		
		for i=2,table.getn(sort) do
			local v = sort[i]
			local f = frames[v]
		
			if seperate then
				if keys[v] ~= keys[last.unit] then
					voffset = seperator
				else
					voffset = 0
				end
			else
				voffset = 0
			end
			
			if not truncate then
				vfont:SetText(UnitName(v))
				local len = vfont:GetStringWidth()
				if len > length then length = len end
			end
			
			f:ClearAllPoints()
			f:SetPoint("TOPLEFT", last, "BOTTOMLEFT", 0, voffset)
			last = self.frames[v]
		end

		for k,v in visible do
			local f = frames[k]
			f.Name:SetWidth(length + 5)
		end
	end,
	
	Select = function(self, msg, force)
		if GetNumRaidMembers() == 0 then return end

		local _,type,sub

		if msg then 
			_,_,type,sub = string.find(msg, "(%w+)%s*(%w*)")
		else
			type = self.opt.Select
			sub = self.opt.SelectSub
		end
		
		if sub then sub = string.upper(sub) end
		local num = tonumber(sub)
		
		if self.opt.Align == "right" then 
			self.mark = "<"
			self.ttmark = "<<"
		else
			self.mark = ">"
			self.ttmark = ">>"
		end

		if type == "group" and num then 
			if not force then self:Msg("Selecting group #" .. num.. ".") end
			for k,v in pairs(visible) do
				local _,_,g = GetRaidRosterInfo(self.frames[k].id)
				selected[k] = (g == num)
			end
		elseif type == "class" and PERFECTRAID.CLASSES[sub] then
			if not force then self:Msg("Selecting by class: " .. string.upper(string.sub(sub,1,1)) .. string.lower(string.sub(sub,2,-1)) .. ".") end
			for k,v in pairs(visible) do
				local _,class = UnitClass(k)
				selected[k] = (class == sub)
			end
		elseif type == "all" then 
			if not force then self:Msg("Selecting all raid members.") end
			for k,v in pairs(visible) do
				selected[k] = true
			end
		elseif type == "none" then 
			if not force then self:Msg("Selecting nothing.") end
			for k,v in pairs(visible) do
				selected[k] = false
			end
		elseif type == "number" then
			if not force then self:Msg("Showing group numbers.") end
			for k,v in pairs(visible) do
				local num = self.frames[k].id
				local _,_,g = GetRaidRosterInfo(num)
				self.frames[k].group = g
				self.frames[k].Prefix:SetText(g)
				selected[k] = true
				self:SetOpt("Select", type)
			end
			return
		end
		
		self:SetOpt("Select", type)
		if sub then self:SetOpt("SelectSub", sub) end
		
		for k,v in pairs(selected) do
			local f = self.frames[k]
			f.marked = v
			
			-- Change the direction of the mark if we're facing right			
			if v then 
				f.Prefix:SetText(self.mark) 
			else 
				f.Prefix:SetText("")
			end
		end
	end,
	
	Show = function(self) 
		if self.master then self.master:Show() end
	end,
	
	Hide = function(self)
		if self.master then self.master:Hide() end
	end,
	
	Reset = function(self)
		self:Msg("Resetting all settings to defaults.")
		
		local f = self.master
		
		f:ClearAllPoints()
		f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
		f:Show()
		
		for k,v in pairs(self.opt) do
			self.opt[k] = nil
		end
	
		self:UpdateVisibility()
		self:SavePosition()
	end,
				
	Align = function(self, msg, force)
		msg = msg or self.opt.Align
		msg = string.lower(msg)
		local justify, point, relative, offset
		
		if msg == "left" then
			if not force then self:Msg("Aligning frames to the left.") end
			justify = "RIGHT"
			point = "LEFT"
			relative = "RIGHT"
			offset = 5

		elseif msg  == "right" then
			if not force then self:Msg("Aligning frames to the right.") end
			justify = "LEFT"
			point = "RIGHT"
			relative = "LEFT"
			offset = -5
		end
		
		for i,f in ipairs(self.frames) do
			f.Prefix:ClearAllPoints()
			f.Name:ClearAllPoints()
			f.Bar:ClearAllPoints()
			f.Status:ClearAllPoints()
			
			-- Prefix gets "LEFT, LEFT" or "RIGHT, RIGHT" since its always on the edge
			f.Prefix:SetPoint(point, f, point, 0, 0)
			f.Name:SetPoint(point, f.Prefix, relative, 0, 0)
			f.Name:SetJustifyH(justify)
			f.Bar:SetPoint(point, f.Name, relative, offset, 0)
			f.Status:SetPoint(point, f.Bar, relative, offset, 0)
			f.Status:SetJustifyH(justify)
		end
		
		-- Refresh the selection
		self:SetOpt("Align", msg)
		self:Select(nil, true)
	end,
	
	SetBackdrop = function(self, msg, force)
		if not msg then 
			self:SetBackdrop(self.opt.BackdropBar, true) 
			self:SetBackdrop(self.opt.BackdropFrame, true)
			return
		end
		
		msg = string.lower(msg)
		local _,_,type,r,g,b,a = string.find(msg, "(%a+)%s*(%d*%.?%d*)%s*(%d*%.?%d*)%s*(%d*%.?%d*)%s*(%d*%.?%d*)")
		r,g,b,a = r or 0, g or 0, b or 0, a or 0
		
		if type == "bar" then
			self:SetOpt("BackdropBar", msg)
			for k,v in visible do
				local f = frames[k]
				f.Bar:SetBackdrop({bgFile = "Interface/Tooltips/UI-Tooltip-Background", 
												tile = true, tileSize = 32,
												insets = { left = 0, right = 0, top = 0, bottom = 0 }});
				f.Bar:SetBackdropColor(r,g,b,a)	
			end
		elseif type == "frame" then
			self:SetOpt("BackdropFrame", msg)
			for k,v in visible do
				local f = frames[k]
				f:SetBackdrop({bgFile = "Interface/Tooltips/UI-Tooltip-Background", 
												tile = true, tileSize = 32,
												insets = { left = 0, right = 0, top = 0, bottom = 0 }});
				f:SetBackdropColor(r,g,b,a)	
			end
		else
			self:Msg("Usage is /praid [bar|frame] r g b a.")
			return
		end
	
		if not force then self:Msg("Setting backdrop for %s to %s, %s, %s, %s.", type, r, g, b, a) end
	end,
	
	Truncate = function(self)
		if self.opt.Truncate then
			self:Msg("Truncate names is |cFF00FF00Off|r.")
			self:SetOpt("Truncate", false)
		else
			self:Msg("Truncate names is |cFF00FF00On|r.")
			self:SetOpt("Truncate", true)
		end
		self:UpdateVisibility()
	end,
	
	SetLowMana = function(self, msg)
		if not msg then 
			self:Msg("/praid manathreshold takes a number 0-100 as its only option.")
			return
		end
		
		local num = tonumber(msg)
		if num and (num >= 0 and num <= 100) then
			self:SetOpt("LowMana", num)
			self:Msg("Setting the low mana threshold to %d", num)
		end
	end,

	SetScale = function(self, msg, force)
		if force then msg = self.opt.Scale end
		
		if not msg then 
			self:Msg("/praid scale takes a number from 0-2.")
			return
		end
		
		local num = tonumber(msg)
		if num and (num > 0 and num < 2) then
			self:SetOpt("Scale", num)
			self.master:SetScale(num)
			if not force then self:Msg("Setting the scale to %d", num) end
		end
	end,

	SetVoffset = function(self, msg)
		if not msg then msg = self.opt.Voffset end
		
		if not msg then 
			self:Msg("/praid scale takes any number greater than 0.")
			return
		end
		
		self:Msg("Setting the vertical offset to %s.", msg)
		
		local num = tonumber(msg)
		self:SetOpt("Voffset", num)
		
		self:UpdateVisibility()
	end,
	
	SortNeed = function(self, msg)

	end,
	
	ToggleFilter = function(self)
		if self.opt.Filter then
			self:Msg("Debuff filter is |cFF00FF00Off|r.")
			self:SetOpt("Filter", false)
		else
			self:Msg("Debuff filter is |cFF00FF00On|r.")
			self:SetOpt("Filter", true)
		end
		self:UpdateVisibility()
	end,		

	ToggleRangeCheck = function(self)
		if self.opt.RangeCheck then
			self:Msg("Range checking is |cFF00FF00Off|r.")
			self:SetOpt("RangeCheck", false)
			for k,v in visible do
				frames[k]:SetAlpha(1.0)
			end
		else
			self:Msg("Range checking is |cFF00FF00On|r.")
			self:SetOpt("RangeCheck", true)
		end
		
		self:RangeHooks()
	end,		

--[[---------------------------------------------------------------------------------
  Utility functions
------------------------------------------------------------------------------------]]
	
	-- Type is stored in TextRight1
	--local type = PerfectRaidTooltipTextRight1:GetText()
	
	CreateFrame = function(self, num)
		-- We need to allocate up to num frames
		
		if self.poolsize >= num then return end

--[[
		local mem,thr = gcinfo()
		self:Msg("Memory Usage Before: %s [%s].", mem, thr)
--]]
		local side = self.opt.Align
		
		local justify,point,relative,offset
		
		if side == "left" then
			justify = "RIGHT"
			point = "LEFT"
			relative = "RIGHT"
			offset = 5
		elseif side == "right" then
			justify = "LEFT"
			point = "RIGHT"
			relative = "LEFT"
			offset = -5
		end
		
		for i=(self.poolsize + 1),num do
			local frame = CreateFrame("Button", nil, PerfectRaidFrame)
			frame:EnableMouse(true)
			frame.unit = "raid"..i
			frame.id = i
			frame:SetWidth(225)
			frame:SetHeight(13)
			frame:SetMovable(true)
			frame:RegisterForDrag("LeftButton")
			frame:SetScript("OnDragStart", function() self["master"]:StartMoving() end)
			frame:SetScript("OnDragStop", function() self["master"]:StopMovingOrSizing() self:SavePosition() end)
			frame:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up")
			frame:SetScript("OnClick", self.OnClick)
			frame:SetParent(self.master)

			local font = frame:CreateFontString(nil, "ARTWORK")
			font:SetFontObject(GameFontHighlightSmall)
			font:SetText("WW")
			font:SetJustifyH("CENTER")
			font:SetWidth(font:GetStringWidth())
			font:SetHeight(14)
			font:Show()
			font:ClearAllPoints()
			font:SetPoint(point, frame, relative,0, 0)
			-- Add this font string to the frame
			frame.Prefix = font
			
			font = frame:CreateFontString(nil, "ARTWORK")
			font:SetFontObject(GameFontHighlightSmall)
			font:SetText()
			font:SetJustifyH(justify)
			font:SetWidth(55)
			font:SetHeight(12)
			font:Show()
			font:ClearAllPoints()
			font:SetPoint(point, frame.Prefix, relative, offset, 0)
			-- Add this font string to the frame
			frame.Name = font
			
			local bar = CreateFrame("StatusBar", nil, frame)
			bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
			bar:SetMinMaxValues(0,100)
			bar:ClearAllPoints()
			bar:SetPoint(point, frame.Name, relative, offset, 0)
			bar:SetWidth(60)
			bar:SetHeight(7)
			bar:Show()
			-- Add this status bar to the frame
			frame.Bar = bar
			
			font = frame:CreateFontString(nil, "ARTWORK")
			font:SetFontObject(GameFontHighlightSmall)
			font:SetText("")
			font:SetJustifyH(justify)
			font:SetWidth(font:GetStringWidth())
			font:SetHeight(12)
			font:Show()
			font:ClearAllPoints()
			font:SetPoint(point, frame.Bar, relative, offset, 0)
			-- Add this font string to the frame
			frame.Status = font
			
			-- Lets set the frame in the indexed array
			frames[i] = frame
			frames["raid"..i] = frame
			self.poolsize = i
		end
		
		mem2,thr2 = gcinfo()
--[[
		self:Msg("Memory Usage After: %s [%s].", mem2, thr2)
		self:Msg("Frame creation change: %s [%s].", mem2 - mem, thr2 - thr)
--]]
	end,
	
	GetHPSeverity = function(self, percent,smooth)
		if (percent<=0 or percent>1.0) then return 0.35,0.35,0.35 end
	
			if smooth then	
			if (percent >= 0.5) then
				return (1.0-percent)*2, 1.0, 0.0
			else
				return 1.0, percent*2, 0.0
			end
		else
			return 0,1,0
		end
	end,
	
	SavePosition = function(self)	
		local f = self.master
		local x,y = f:GetLeft(), f:GetTop()
		local s = f:GetEffectiveScale()
		
		x,y = x*s,y*s
		
		self:SetOpt("PosX", x)
		self:SetOpt("PosY", y)
	end,

	RestorePosition = function(self)
		local x = self.opt.PosX
		local y = self.opt.PosY
		
		if not x or not y then return end
				
		local f = PerfectRaidFrame
		local s = f:GetEffectiveScale()
	
		x,y = x/s,y/s
	
		f:ClearAllPoints()
		f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y)
	end,
	
	RegisterEvent = function(self, event)
		registry[event] = event
		self.master:RegisterEvent(event)
	end,
	
	UnregisterEvent = function(self, event)
		registry[event] = nil
		self.master:UnregisterEvent(event)
	end,
	
	UnregisterAllEvents = function(self)
		for k,v in pairs(registry) do
			registry[k] = nil
			self.master:UnregisterEvent(k)
		end
	end,
	
	EventHandler = function()
		local self = PerfectRaid
		if registry[event] then
			if self[event] then
				self[event](self)
			end
		end
	end,	
	
	Msg = function(self,msg,a1,a2,a3,a4,a5,a6,a7,a8,a9)
		if not msg then return end
		local output
		if a1 then output = string.format(msg,a1,a2,a3,a4,a5,a6,a7,a8,a9) end
		DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffffff78PerfectRaid:|r %s", output or msg))
	end,
	
	Debug = function(self,msg,a1,a2,a3,a4,a5,a6,a7,a8,a9)
		if not msg or not debug then return end
		local output
		if a1 then output = string.format(msg,a1,a2,a3,a4,a5,a6,a7,a8,a9) end
		DEFAULT_CHAT_FRAME:AddMessage(string.format("|cFF00E6C6PerfectRaid:|r %s", output or msg))
	end,
	
	Test = function(self, command, args)
		self:Msg("Test Command: %s Args: %s.", tostring(command), tostring(args))
	end,

	SortNeed = function(self, command, args)
		self:Msg("SortNeed Command: %s Args: %s.", tostring(command), tostring(args))
	end,	
}

PerfectRaid:Initialize()
p = PerfectRaid