--[[---------------------------------------------------------------------------

Copyright (c) 2008 by K. Scott Piel 
All Rights Reserved

E-mail: < kscottpiel@gmail.com >
Web:    < http://www.scottpiel.com >

This file is part of nUI.

    nUI is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    nUI is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with nUI.  If not, see <http://www.gnu.org/licenses/>.
	
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

--]]---------------------------------------------------------------------------

if not nUI then nUI = {}; end
if not nUI_Unit then nUI_Unit = {}; end
if not nUI_UnitOptions then nUI_UnitOptions = {}; end
if not nUI_DefaultConfig then nUI_DefaultConfig = {}; end

local CancelItemTempEnchantment = CancelItemTempEnchantment;
local CancelPlayerBuff          = CancelPlayerBuff;
local CreateFrame               = CreateFrame;
local GetInventoryItemTexture   = GetInventoryItemTexture;
local GetPlayerBuff             = GetPlayerBuff;
local GetPlayerBuffApplications = GetPlayerBuffApplications;
local GetPlayerBuffTimeLeft     = GetPlayerBuffTimeLeft;
local GetPlayerBuffName         = GetPlayerBuffName;
local GetTime                   = GetTime;
local GetWeaponEnchantInfo      = GetWeaponEnchantInfo;
local UnitBuff                  = UnitBuff;
local UnitDebuff                = UnitDebuff;
local UnitIsDeadOrGhost         = UnitIsDeadOrGhost;

-------------------------------------------------------------------------------
-- default options for the unit aura colors

nUI_DefaultConfig.AuraColors =
{
	["none"]          = nil,
	["Magic"]         = DebuffTypeColor["Magic"],
	["Curse"]         = DebuffTypeColor["Curse"],
	["Disease"]       = DebuffTypeColor["Disease"],
	["Poison"]        = DebuffTypeColor["Poison"],
	["count"]         = { r = 1, g = 1, b = 1 },
	["remains"]       = { r = 1, g = 1, b = 1 },
	["expiring"]      = { r = 1, g = 0.5, b = 0.5 },
	["player_buff"]   = { r = 0, g = 1, b = 0, a = 0.25 },
	["player_debuff"] = { r = 1, g = 0, b = 0, a = 0.25 },
};

-------------------------------------------------------------------------------
-- simple utility method to convert a time value to a string

local function TimeLeftToString( value )

	local fmt;
	
	if value >= 3600 then 
		fmt   = nUI_L["TimeLeftInHours"];
		value = value / 3600;
	elseif value >= 60 then 
		fmt   = nUI_L["TimeLeftInMinutes"];
		value = value / 60;
	elseif value >= 2 then 
		fmt = nUI_L["TimeLeftInSeconds"];
	else
		fmt = nUI_L["TimeLeftInTenths"];
	end
	
	return fmt:format( value );
	
end

-------------------------------------------------------------------------------
-- unit aura event management

if not nUI_Unit.Drivers then 
	nUI_Unit.Drivers = CreateFrame( "Frame", "nUI_UnitDrivers", WorldFrame ); 
end

local frame = CreateFrame( "Frame", "$parent_Aura", nUI_Unit.Drivers )

local AuraCallbacks     = {};
local AuraUnits         = {};
local FlashAuras        = {};
local TimerAuras        = {};
local flash_alpha       = 1;
local flash_timer       = 1 / nUI_DEFAULT_FRAME_RATE;
local count_timer       = 0;
local aura_timer        = 0;
local new_weapon_buff   = false;
local PlayerWeaponBuffs;

nUI_Unit.Drivers.Aura  = frame;

-------------------------------------------------------------------------------

local function onAuraEvent()
	
	if event == "VARIABLES_LOADED" then
		
		nUI:patchConfig();
		nUI_Unit:configAura();
		
	-- for these events, we don't know which units are affected, so
	-- we span the list of all known interested units to see who is watching
		
	elseif event == "PLAYER_ENTERING_WORLD" then

		nUI_Unit:refreshAuraCallbacks();

	-- aura changes on a single unit
	
	else

		if event == "SPELLS_CHANGED" 
		or event == "PLAYER_AURAS_CHANGED"
		then arg1 = "player"; 
		end
		
		frame.newUnitInfo( arg1, nUI_Unit:getUnitInfo( arg1 ) );

	end
end

frame:SetScript( "OnEvent", onAuraEvent );
frame:RegisterEvent( "VARIABLES_LOADED" );
frame:RegisterEvent( "PLAYER_ENTERING_WORLD" );
frame:RegisterEvent( "UNIT_AURA" );
frame:RegisterEvent( "UNIT_AURA_STATE" );
frame:RegisterEvent( "SPELLS_CHANGED" );
frame:RegisterEvent( "PLAYER_AURAS_CHANGED" );

-------------------------------------------------------------------------------
-- aura update event management

local flash_rate = 0;

local function onAuraUpdate( who, elapsed )

	local update_list = {};
	local proc_time   = GetTime();

	-- check player's temporary weapon enchants... I'd love to know why
	-- Bliz doesn't create and fire and event for this... seems mighty
	-- expensive to me to be hammering it in the frame update loop, but
	-- that's where they do it, so I follow suit
	
	local weapon_buffs = {};
	local proc_time    = GetTime();
	
	local has_mainhand, mainhand_exp, mainhand_count, 
	 	  has_offhand,  offhand_exp,  offhand_count = GetWeaponEnchantInfo();			

	if has_mainhand then
		
		weapon_buffs[1] =
		{
			id         = 16,
			count      = mainhand_count and mainhand_count ~= 0 and mainhand_count or nil,
			cur_time   = mainhand_exp / 1000,
			max_time   = mainhand_exp / 1000,
			pct_time   = 1,
			start_time = proc_time,
			end_time   = proc_time + (mainhand_exp / 1000),
			name       = nUI_L["Main Hand Weapon:"].." "..GetItemInfo( nUI_GetItemIdFromLink( GetInventoryItemLink( "player", 16 ) ) ),
			icon       = GetInventoryItemTexture( "player", 16 ),
			
			SetTooltip = function( frame, unit_id )
				
				frame:SetScript( "OnEnter",
					function()	

						frame:EnableMouse( true );
						
						frame:SetScript( "OnUpdate",
							function()
								if GameTooltip:IsOwned( frame ) then
									GameTooltip:SetInventoryItem( unit_id, 16 );
								end
							end
						);
						
						frame:SetID( 16 );
						frame.id = 16;
						frame.unit_id = unit_id;
						GameTooltip:SetOwner( frame );
						GameTooltip:SetInventoryItem( unit_id, 16 );
						GameTooltip:Show();
					end
				);				
				
				frame:SetScript( "OnLeave",
					function()							
						GameTooltip:Hide();
						frame:SetScript( "OnUpdate", nil );
					end
				);
			end,
			
			SetClick = function( frame )
				
				frame:EnableMouse( true );
				frame:RegisterForClicks( "RightButtonUp" );
				frame:SetScript( "OnClick",
					function()
						CancelItemTempEnchantment( 1 );
					end
				);
			end,			
		};
		
	end
	
	if has_offhand then
		
		weapon_buffs[2] =
		{
			id         = 17,
			count      = offhand_count and offhand_count ~= 0 and offhand_count or nil,
			cur_time   = offhand_exp / 1000,
			max_time   = offhand_exp / 1000,
			pct_time   = 1,
			start_time = proc_time,
			end_time   = proc_time + (offhand_exp / 1000),
			name       = nUI_L["Off Hand Weapon:"].." "..GetItemInfo( nUI_GetItemIdFromLink( GetInventoryItemLink( "player", 17 ) ) ),
			icon       = GetInventoryItemTexture( "player", 17 ),
			
			SetTooltip = function( frame, unit_id )
				
				frame:SetScript( "OnEnter",
					function()							

						frame:EnableMouse( true );
						
						frame:SetScript( "OnUpdate",
							function()
								if GameTooltip:IsOwned( frame ) then
									GameTooltip:SetInventoryItem( unit_id, 17 );
								end
							end
						);
						
						frame:SetID( 17 );
						frame.unit_id = unit_id;
						GameTooltip:SetOwner( frame );
						GameTooltip:SetInventoryItem( unit_id, 17 );
						GameTooltip:Show();
					end
				);
									
				frame:SetScript( "OnLeave",
					function()							
						GameTooltip:Hide();
						frame:SetScript( "OnUpdate", nil );
					end
				);
			end,
			
			SetClick = function( frame )
				
				frame:RegisterForClicks( "RightButtonUp" );
				frame:EnableMouse( true );
				frame:SetScript( "OnClick",
					function()
						CancelItemTempEnchantment( 2 );
					end
				);
			end,
		};
		
	end

	-- have the weapon buffs changed?
	
	if  not weapon_buffs[1] 
	and not weapon_buffs[2] 
	then
		
		new_weapon_buff = PlayerWeaponBuffs ~= nil;
		weapon_buffs    = nil;
		
	else
		
		for i=1,2 do
			
			local old = PlayWeaponBuffs and PlayerWeaponBuffs[i];
			local new = weapon_buffs[i];
			
			if (old and not new)
			or (new and not old)
			then
				
				new_weapon_buff   = true;
				
			elseif new then
				
				if old.count   ~= new.count
				or old.expires ~= new.expires
				or old.icon    ~= new.icon
				then 
					
					new_weapon_buff   = true;
					
				end
			end
		end		
	end

	-- if something changed, update the player auras
	
	if new_weapon_buff then
		
		PlayerWeaponBuffs = weapon_buffs;
		frame.newUnitInfo( "player", nUI_Unit.PlayerInfo );
		
	end
	
	-- flash the auras that are expiring
	
	if #FlashAuras > 0 then
		
		flash_timer = flash_timer - elapsed;
		
		if flash_timer <= 0 then -- update at the user selected frame rate
		
			-- calculate an alpha for the auras that are flashing

			flash_rate  = (flash_rate + (nUI_Unit.frame_rate - flash_timer)) % 2;
			flash_timer = nUI_Unit.frame_rate;
			
			-- for the first 1/2 second, we fade out
			
			if flash_rate < 0.5 then
				
				flash_alpha = 1 - (flash_rate * 2);
				
			-- for the next 1/4 second we hold the button invisible
			
			elseif flash_rate < 0.75 then
				
				flash_alpha = 0;
				
			-- for the next 1/2 second we fade in
			
			elseif flash_rate < 1.25 then
				
				flash_alpha = (flash_rate - 0.75) * 2;
				
			-- and for the last 3/4 of a second, we hold the button at full alpha
			
			else
				
				flash_alpha = 1;
				
			end
			
			-- span the list of auras
			
			for i=#FlashAuras, 1, -1  do
				
				local button = FlashAuras[i];
				local aura   = button.aura;
				
				-- if the aura has expired, stop flashing it
				
				if proc_time > aura.end_time then
					
					button.is_flashing = false;
					aura.expired       = true;

					nUI:TableRemoveByValue( FlashAuras, button );
					nUI:TableInsertByValue( update_list, button.parent );
					
--					nUI:debug( "nUI_UnitAura: expired flashing button: "..button:GetName(), 1 );
					
				-- otherwise, set the alpha for the aura
				
				elseif aura.expires_at and proc_time >= aura.expires_at then
						
					local color = nUI_UnitOptions.AuraColors["expiring"];
					
					if button.remains.r ~= color.r
					or button.remains.g ~= color.g
					or button.remains.b ~= color.b
					then
						
						button.remains.r = color.r;
						button.remains.g = color.g;
						button.remains.b = color.b;
						
						button.remains:SetTextColor( color.r, color.g, color.b );
						
					end
					
					button.icon:SetAlpha( flash_alpha );
					
					if button.highlight.r then 
						button.highlight:SetAlpha( flash_alpha ); 
					end
				end
			end
		end
	end
	
	-- update time remaining counters on those auras that require it
	
	if #TimerAuras > 0 then

		count_timer = count_timer + elapsed;
		
		if count_timer > 0.1 then -- update at 10fps
		
			count_timer = 0;
			
			for i=#TimerAuras, 1, -1 do
				
				local button  = TimerAuras[i];
				local aura    = button.aura;
				local remains = aura.end_time - proc_time;
				
				if remains <= 0 then

					button.is_timing = false;
					button.active    = false;
					aura.expired     = true;
					
					nUI:TableRemoveByValue( TimerAuras, button );
					nUI:TableInsertByValue( update_list, button.parent );
					
--					nUI:debug( "nUI_UnitAura: expired timed button: "..button:GetName(), 1 );
					
				else
					
					local text = TimeLeftToString( remains );

--					nUI:debug( "nUI_UnitAura: timed button: "..button:GetName().." remains = "..text, 1 );
					
					if button.remains.value ~= text then
						
						button.remains.value = text;
						button.remains:SetText( text );
					end
				end
			end
		end
	end
	
	-- if we expired any buttons, make sure their parent frames get updated
	
	for i,frame in ipairs( update_list ) do
		nUI_Unit:updateAuraFrame( frame );
	end
	
end

frame:SetScript( "OnUpdate", onAuraUpdate );

-------------------------------------------------------------------------------
-- this callback method is called when one of the unit IDs we are monitoring
-- for unit aura changes GUID

frame.newUnitInfo = function( list_unit, unit_info )

	local new_data  = nUI_Unit:updateAuraInfo( list_unit, unit_info );
	local callbacks = AuraCallbacks;
	local unitlist  = AuraUnits;
	
	nUI_Unit:notifyCallbacks( nUI_L["unit aura"], callbacks, unitlist, unit_info, list_unit, new_data );
	
end

-------------------------------------------------------------------------------
-- helper function to copy one aura structure to another as a duplicate

local function CopyAura( target, source )
		
	target.id = source.id;
	target.count = source.count;
	target.player_idx = source.player_idx;
	target.max_time = source.max_time;
	target.cur_time = source.cur_time;
	target.pct_time = source.pct_time;
	target.start_time = source.start_time;
	target.end_time = source.end_time;
	target.name = source.name;
	target.rank = source.rank;
	target.icon = source.icon;
	target.type = source.type;
	target.expired = source.expired;
	target.is_player = source.is_player;
	target.until_cancel = source.unit_cancel;
	target.SetTooltip = source.SetTooltip;
	target.SetClick = source.SetClick;
		
	target.buff_color =
	{
		r = source.color and source.color.r or 1,
		g = source.color and source.color.g or 1,
		b = source.color and source.color.b or 1,
	};
	
end

-------------------------------------------------------------------------------
-- disable all mouseover and click abilities for a button and hide it

local function DisableAuraButton( button )
		
--	nUI:debug( "nUI_UnitAura: disabling button: "..button:GetName(), 1 );					
		
	button.is_timing   = false;
	button.is_flashing = false;
	
	nUI:TableRemoveByValue( FlashAuras, button );
	nUI:TableRemoveByValue( TimerAuras, button );
	
	button.active = false;
	
	button:SetAlpha( 0 );
	button:EnableMouse( false );
	button:RegisterForClicks();
	button:SetScript( "OnEnter", nil );
	button:SetScript( "OnLeave", nil );
	button:SetScript( "OnClick", nil );
	
	if button.count.value then 
		button.count.value = nil;
		button.count:SetText( "" );
	end
	
	if button.remains.value then
		button.remains.value = nil;
		button.remains:SetText( "" );
	end
	
	if button.label.value then
		button.label.value = nil;
		button.label:SetText( "" );
	end

end
		
-------------------------------------------------------------------------------
-- apply the current set of aura frame options to a button

local function ApplyAuraFrameOptions( frame, button )

	button:SetHeight( frame.size );
	button:SetWidth( frame.size );
	button:SetFrameStrata( frame:GetFrameStrata() );
	button:SetFrameLevel( frame:GetFrameLevel()+1 );

	button.label.enabled = frame.options.label and true or false;
	button.label:SetFont( nUI_L["font1"], frame.label.fontsize * frame.scale, "OUTLINE" );
	button.label:SetJustifyH( frame.label.justifyH );
	button.label:SetJustifyV( frame.label.justifyV );
	button.label:SetPoint( frame.label.anchor_pt, button, frame.label.relative_pt, frame.label.xOfs, frame.label.yOfs );
	button.label:SetTextColor( 1, 0.83, 0, 1 );
	
	button.remains.enabled = frame.options.timer and true or false;
	button.remains:SetFont( nUI_L["font1"], frame.timer.fontsize * frame.scale, "OUTLINE" );
	button.remains:SetJustifyH( frame.timer.justifyH );
	button.remains:SetJustifyV( frame.timer.justifyV );
	button.remains:SetPoint( frame.timer.anchor_pt, button, frame.timer.relative_pt, frame.timer.xOfs, frame.timer.yOfs );
	
	button.count.enabled = frame.options.count and true or false;
	button.count:SetFont( nUI_L["font1"], frame.count.fontsize * frame.scale, "OUTLINE" );
	button.count:SetJustifyH( frame.count.justifyH );
	button.count:SetJustifyV( frame.count.justifyV );
	button.count:SetPoint( frame.count.anchor_pt, button, frame.count.relative_pt, frame.count.xOfs, frame.count.yOfs );
	button.count:SetTextColor( 1, 1, 1, 1 );
		
end

-------------------------------------------------------------------------------
-- configure a button for a given aura

local function SetButtonAura( frame, button, aura, aura_type )
	
--	if button:GetName():match( "nUI_HUDUnit_PlayerTarget_Target_Aura2" ) then
--		local aura_copy = {};
--		CopyAura( aura_copy, aura );
--		aura_copy.button = button:GetName();
--		table.insert( nUI_DebugLog[nUI.realmName][nUI.playerName], aura_copy );
--	end
	
--	nUI:debug( "nUI_UnitAura: setting aura for "..button:GetName().." to ["..aura.name.."]", 1 );
	
	local proc_time = GetTime();
	local new_aura  = false;
	
	-- update the button's OnClick and Tooltip functionality

	aura.SetClick( button );
	aura.SetTooltip( button, frame.unit );				
	
	button.aura      = aura;
	button.aura_type = aura_type;

	-- do we need to update the icon for this button (basically, a new button at this point)
	
	if button.icon.path ~= aura.icon then
		
		button.icon.path = aura.icon;
		button.icon:SetTexture( aura.icon );
		button.icon:SetAlpha( 1 );

		button.new_aura = true;
		
	end

	-- do we need to update the spell name label for this button
	
	if button.label.enabled then
		
		local label = frame.label.enabled and (aura.name..(aura.rank and aura.rank ~= "" and " ("..aura.rank..")" or "")) or "";
	
		if button.label.value ~= label 
		then
			
			button.label.value = label;
			button.label:SetText( label );
		
		end
	end

	-- do we need to update the count for this button
	
	if button.count.enabled
	then
			
		if button.count.value ~= aura.count 
		then
			
			button.count.value = aura.count;
			button.count:SetText( aura.count and aura.count > 0 and aura.count or "" );
			
		end
		
		local count_color = nUI_UnitOptions.AuraColors["count"];
		
		if button.count.r ~= count_color.r
		or button.count.g ~= count_color.g
		or button.count.b ~= count_color.b
		then
			
			button.count.r = count_color.r;
			button.count.g = count_color.g;
			button.count.b = count_color.b;
			
			button.count:SetTextColor( count_color.r, count_color.g, count_color.b );
			
		end
	end
	
	-- highlight the aura icon's button face if this is a player cast
	-- and player highlighting is enabled
	
	if aura.is_player and frame.options.highlight_player then
		
		local color = nUI_UnitOptions.AuraColors[(aura_type == "buff" and "player_buff" or "player_debuff")] or {};
		
		if new_aura
		or button.highlight.r ~= color.r
		or button.highlight.g ~= color.g
		or button.highlight.b ~= color.b
		or button.highlight.a ~= color.a
		then

			button.highlight.r = color.r;
			button.highlight.g = color.g;
			button.highlight.b = color.b;
			button.highlight.a = color.a;
			
			button.highlight:SetTexture( color.r, color.g, color.b, color.a );
			button.highlight:SetAlpha( 1 );
			
		end
		
	elseif button.highlight.r
	or button.highlight.g
	or button.highlight.b
	or button.highlight.a
	then
		
		button.highlight.r = nil;
		button.highlight.g = nil;
		button.highlight.b = nil;
		button.highlight.a = nil;
		
		button.highlight:SetAlpha( 0 );
		
	end
	
	-- highlight the aura's border if aura type highlights are enabled
	
	if frame.options.aura_types then
		
		local color = aura.buff_color or aura.debuff_color;
		
		if not color or not aura.type then
			
			if button.class.r
			or button.class.g
			or button.class.b
			then
				
				button.class.r = nil;
				button.class.g = nil;
				button.class.b = nil;
				button.class:SetAlpha( 0 );
			
			end
		
		elseif new_aura
		or button.class.r ~= color.r
		or button.class.g ~= color.g
		or button.class.b ~= color.b
		then
			
			button.class.r = color.r;
			button.class.g = color.g;
			button.class.b = color.b;
			button.class:SetAlpha( 1 );
			button.class:SetVertexColor( color.r, color.g, color.b );
			
		end
		
	elseif button.class.r
	or button.class.g
	or button.class.b
	then
		button.class.r = nil;
		button.class.g = nil;
		button.class.b = nil;
		button.class:SetAlpha( 0 );
	end

	-- if the aura has a time remaining, at what point do we consider it
	-- to be expiring?

	local timer_color = nUI_UnitOptions.AuraColors["remains"];
	
	if aura.cur_time and aura.end_time then
		
		aura.expires_at = aura.end_time - frame.options.expire_time;
		
		if proc_time >= aura.expires_at then
			timer_color = nUI_UnitOptions.AuraColors["expiring"];
		end			
		
	else
		aura.expires_at = nil;
	end
	
	-- do we need to add the button to the list of buttons with timers
	
	if button.remains.enabled then
			
		if aura.end_time
		then
			
			if not button.is_timing 
			and aura.end_time > proc_time then

--				nUI:debug( "nUI_UnitAura: adding button "..button:GetName().." ("..aura.name..") to timed aura list", 1 );
				
				button.is_timing = true;
				nUI:TableInsertByValue( TimerAuras, button );
				
			end
	
			if button.remains.r ~= timer_color.r
			or button.remains.g ~= timer_color.g
			or button.remains.b ~= timer_color.b
			then
				
				button.remains.r = timer_color.r;
				button.remains.g = timer_color.g;
				button.remains.b = timer_color.b;
				
				button.remains:SetTextColor( timer_color.r, timer_color.g, timer_color.b );
				
			end
			
		elseif button.is_timing
		then
			
--			nUI:debug( "nUI_UnitAura: button "..button:GetName().." ("..aura.name..")  is not being timed", 1 );			
			
			button.is_timing = false;
			nUI:TableRemoveByValue( TimerAuras, button );
		
			if button.remains.value then
				button.remains.value = nil;
				button.remains:SetText( "" );
			end
			
		elseif button.remains.value
		then
			
--			nUI:debug( "nUI_UnitAura: clearing value on button "..button:GetName().." ("..aura.name..")", 1 );			
			
			button.remains.value = nil;
			button.remains:SetText( "" );
		end
	end
	
	-- do we need to flash an expiring aura?
	
	if frame.options.flash_expire then
	
		if aura.cur_time
		then
	
--			nUI:debug( "nUI_UnitAura: adding button "..button:GetName().." to flashing button list", 1 );
					
			if not button.is_flashing and aura.end_time > proc_time then
	
				button.is_flashing = true;
				nUI:TableInsertByValue( FlashAuras, button );
				
			end
			
		elseif button.is_flashing 
		then
			
--			nUI:debug( "nUI_UnitAura: button "..button:GetName().." is not set for flashing", 1 );
			
			button.is_flashing = false;			
			nUI:TableRemoveByValue( FlashAuras, button );
			
		end		
	end
	
	-- do we need to show the Blizzard cooldown animation?
--[[	
	if frame.options.cooldown_anim
	and aura.start_time
	and aura.duration
	then
		
		if button.cooldown.time1 ~= aura.start_time
		or button.cooldown.time2 ~= aura.duration
		then
			
			button.cooldown.time1 = aura.start_time;
			button.cooldown.time2 = aura.duration;
			
			CooldownFrame_SetTimer( button.cooldown, aura.start_time, aura.duration, 1 );
		end
	end			
]]--
end	

-------------------------------------------------------------------------------
-- construct a single buff/debuff aura button

local function CreateAuraButton( frame, i )
	
	local options = frame.options;

--	nUI:debug( "nUI_UnitAura: creating new button for "..frame:GetName().." and index "..i, 1 );
	
	button              = CreateFrame( "Button", "$parent_Button"..i, frame );
	button.remains      = button:CreateFontString( "$parentRemains", "OVERLAY" );
	button.count        = button:CreateFontString( "$parentCount", "OVERLAY" );
	button.label        = button:CreateFontString( "$parentLabel", "OVERLAY" );
	button.icon         = button:CreateTexture( "$parentIcon", "BACKGROUND" );
	button.class        = button:CreateTexture( "$parentBorder", "ARTWORK" );
	button.highlight    = button:CreateTexture( "$parentHighight", "BORDER" );
	button.cooldown     = button:CreateTexture( "$parentCooldown", "ARTWORK" );
	button.parent       = frame;
	button.active       = true;
	button.class.active = true;

	button.icon:SetAllPoints( button );
	button.class:SetAllPoints( button );
	button.cooldown:SetAllPoints( button );
	button.highlight:SetAllPoints( button );

	button.class:SetAlpha( 0 );
	button.class:SetTexture( "Interface\\AddOns\\nUI\\Units\\Art\\nUI_BuffBorder" );
	button.class:SetTexCoord( 0, 0, 0, 1, 1, 0, 1, 1 );
	button.highlight:SetAlpha( 0 );

	return button;
	
end

-------------------------------------------------------------------------------
-- this method is used to sort a table of auras using the following rules
--
-- spells that are valid until cancelled come last in the table and are
-- sorted by spell name and rank
--
-- spells that are not valid until cancelled but have no known duration
-- or time remaining come second and are sorted by name and rank
--
-- spells with a known time remaining are sorted in asccending order by the
-- time remaining and then by name and rank if the time remaining is equal
--
-- Nothing fancy... just a simple modified bubble sort

local function SortAuras( table )
	
	for i=1,#table do
		
		for j=i+1,#table do
			
			local left  = table[i];
			local right = table[j];
			local name_sort = false;
			local swap;
			
			-- any aura with an end time comes before any
			-- other aura without an end time
			
			if right.end_time and not left.end_time then
				swap = true;
			elseif left.end_time and not right.end_time then
				swap = false;
				
			-- two auras, both with end times, are sorted
			-- with the earliest expiration first
			
			elseif left.end_time and right.end_time then
				
				name_sort = left.end_time == right.end_time;
				swap      = left.end_time > right.end_time;
				
			-- otherwise the spell does not have an expiration
			-- time either (a) because it's good until the player
			-- cancels it, or, (b) because it isn't a player spell
			-- and we can't see how much time remains. We sort
			-- good until canceled spells to the end of the list
			-- and non-player spells after the timed spells
			
			elseif left.until_cancel and not right.until_cancel then
				
				swap = true;
				
			elseif not left.until_cancel and right.until_cancel then
				
				swap = false;
				
			-- otherwise, we sort by spell name and rank
			
			else
				
				name_sort = true;
				
			end
			
			-- if we need a name sort, then sort spells ascending by
			-- spenn name and rank
			
			if name_sort then
				
				if left.name > right.name then
					swap = true;
				elseif left.name < right.name then
					swap = false;
				elseif left.rank and not right.rank then
					swap = true;
				elseif not left.rank and right.rank then
					swap = false;
				elseif left.rank and right.rank then
					swap = left.rank > right.rank;
				else
					swap = false;
				end
				
			end
			
			-- if we need to, then swap the two spells
			
			if swap then
				
				local temp = table[i];
				table[i]   = table[j];
				table[j]   = temp;
				
			end
		end
	end
end

-------------------------------------------------------------------------------
-- this method scans the unit for all buffs present and builds a list accordingly

local function GetBuffList( unit_id, is_self )
	
	local buff_list = {};
	
	-- spin the list of possible buffs and see what's there
	
	for i=1,40 do

		-- check for a buff on this index
		
		local buff = {};
		
		buff.name, buff.rank, buff.icon, buff.count, 
		buff.max_time, buff.cur_time = UnitBuff( unit_id, i );

		if buff.name then

			buff.id         = i;
			buff.is_player  = buff.max_time ~= nil and buff.cur_time ~= nil;
			buff.type       = GetPlayerBuffDispelType( buff.name, buff.rank );
			buff.buff_color = nUI_UnitOptions.AuraColors[buff.type or "none"];

			-- if this is the player, then try to collect additional information

			if is_self then
				
				for j=1,40 do
					
					local index, until_cancel = GetPlayerBuff( j, "HELPFUL|PASSIVE" );
						
					if index > 0 then
						
						local name, rank = GetPlayerBuffName( index );
	
						if name == buff.name and rank == buff.rank then
								
							buff.count        = GetPlayerBuffApplications( index );
							buff.cur_time     = GetPlayerBuffTimeLeft( index );
							buff.until_cancel = until_cancel; 
							buff.player_idx   = index;
					
							buff.SetTooltip = function( frame, unit_id )
								
								frame:EnableMouse( true );
								
								frame:SetScript( "OnEnter",
									function()

										frame:SetScript( "OnUpdate",
											function()
												if GameTooltip:IsOwned( frame ) then
													GameTooltip:SetPlayerBuff( index );
												end
											end
										);
										
										frame.unit_id = unit_id;
										GameTooltip:SetOwner( frame );
										GameTooltip:SetPlayerBuff( index );
										GameTooltip:Show();
									end
								);					
					
								frame:SetScript( "OnLeave",
									function()
										GameTooltip:Hide();
										frame:SetScript( "OnUpdate", nil );
									end
								);
							end
								
							buff.SetClick = function( frame )
								
								frame:EnableMouse( true );
								frame:RegisterForClicks( "RightButtonUp" );
								frame:SetScript( "OnClick",
									function()
										CancelPlayerBuff( index );
									end
								);
							end
							
							break;
							
						end					
					end
				end
			end
--[[		
			if unit_id == "target" then
				local log_buff = {};
				CopyAura( log_buff, buff );
				log_buff.unit_id = unit_id;
				log_buff.aura_type = "buff";				
				table.insert( nUI_DebugLog[nUI.realmName][nUI.playerName], log_buff );
			end
]]--			
			buff.count = buff.count and buff.count > 0 and buff.count or nil;
			
			if buff.max_time and buff.max_time > 0 then

				buff.end_time   = GetTime() + buff.cur_time;
				buff.start_time = buff.end_time - buff.max_time;
				buff.pct_time   = buff.cur_time / buff.max_time;
				
			elseif buff.cur_time and buff.cur_time > 0 then
				
				buff.max_time   = buff.cur_time;
				buff.start_time = GetTime();
				buff.end_time   = buff.start_time + buff.cur_time;
				buff.pct_time   = 1;
				
			else
				
				buff.cur_time   = nil;
				buff.max_time   = nil;
				buff.start_time = nil;
				buff.end_time   = nil;
				buff.pct_time   = nil;
				
			end
			
			-- set up non-player buff tooltip and disable click-to-cancel
			
			if not buff.SetTooltip then

				buff.SetTooltip = function( frame, unit_id )

					frame:EnableMouse( true );
					
					frame:SetScript( "OnEnter",
						function()

							frame:SetScript( "OnUpdate",
								function()
									if GameTooltip:IsOwned( frame ) then
										GameTooltip:SetUnitBuff( unit_id, buff.id );
									end
								end
							);
							
							frame:SetID( buff.id );
							frame.unit_id = unit_id;
							GameTooltip:SetOwner( frame );
							GameTooltip:SetUnitBuff( unit_id, buff.id );
							GameTooltip:Show();
						end
					);					
					
					frame:SetScript( "OnLeave",
						function()
							GameTooltip:Hide();
							frame:SetScript( "OnUpdate", nil );
						end
					);
				end
				
				buff.SetClick = function( frame )
					
					frame:RegisterForClicks();
					frame:SetScript( "OnClick", nil );
					
				end
			end
			
			nUI:TableInsertByValue( buff_list, buff );

		end
	end
	
	-- sort the table
	
	if #buff_list > 0 then SortAuras( buff_list ); end;
	
	-- return the result
	
	return #buff_list > 0 and buff_list or nil;
	
end

-------------------------------------------------------------------------------
-- this method scans the unit for all debuffs present and builds a list accordingly

local function GetDebuffList( unit_id, is_self )
	
	local debuff_list = {};
	
	-- spin the list of possible buffs and see what's there
	
	for i=1,40 do

		-- check for a buff on this index
		
		local debuff = {};
		
		debuff.name, debuff.rank, debuff.icon, debuff.count, 
		debuff.type, debuff.max_time, debuff.cur_time = UnitDebuff( unit_id, i );

		if debuff.name then

			debuff.can_dispell  = UnitDebuff( unit_id, i, SHOW_DISPELLABLE_DEBUFFS ) ~= nil;
			debuff.id           = i;
			debuff.now          = GetTime();
			debuff.is_player    = debuff.max_time ~= nil and debuff.cur_time ~= nil;
			debuff.debuff_color = nUI_UnitOptions.AuraColors[debuff.type or "none"];
			
			-- if this is the player, then try to collect additional information

			if is_self then
				
				for j=1,40 do
					
					local index, until_cancel = GetPlayerBuff( j, "HARMFUL" );
						
					if index > 0 then
						
						local name, rank = GetPlayerBuffName( index );
	
						if name == debuff.name and rank == debuff.rank then
								
							debuff.count        = GetPlayerBuffApplications( index );
							debuff.cur_time     = GetPlayerBuffTimeLeft( index );
							debuff.until_cancel = until_cancel; 
							debuff.player_idx   = index;
					
							debuff.SetTooltip = function( frame, unit_id )
								
								frame:EnableMouse( true );
								
								frame:SetScript( "OnEnter",
									function()							

										frame:SetScript( "OnUpdate",
											function()
												if GameTooltip:IsOwned( frame ) then
													GameTooltip:SetPlayerBuff( index );
												end
											end
										);
										
										frame:SetID( debuff.id );
										frame.unit_id = unit_id;
										GameTooltip:SetOwner( frame );
										GameTooltip:SetPlayerBuff( index );
										GameTooltip:Show();
									end
								);					
					
								frame:SetScript( "OnLeave",
									function()
										GameTooltip:Hide();
										frame:SetScript( "OnUpdate", nil );
									end
								);
							end
								
							debuff.SetClick = function( frame )
								
								frame:EnableMouse( true );
								frame:RegisterForClicks( "RightButtonUp" );
								frame:SetScript( "OnClick",
									function()
										CancelPlayerBuff( index );
									end
								);
							end
							
							break;
						end					
					end
				end
			end
--[[		
			if unit_id == "target" then
				local log_buff = {};
				CopyAura( log_buff, debuff );
				log_buff.unit_id = unit_id;
				log_buff.aura_type = "buff";				
				table.insert( nUI_DebugLog[nUI.realmName][nUI.playerName], log_buff );
			end
]]--				
			debuff.count = debuff.count and debuff.count ~= 0 and debuff.count or nil;
			
			if debuff.max_time and debuff.max_time > 0 then

				debuff.end_time   = GetTime() + debuff.cur_time;
				debuff.start_time = debuff.end_time - debuff.max_time;
				debuff.pct_time   = debuff.cur_time / debuff.max_time;
				
			elseif debuff.cur_time and debuff.cur_time > 0 then
				
				debuff.max_time   = debuff.cur_time;
				debuff.start_time = GetTime();
				debuff.end_time   = debuff.start_time + debuff.cur_time;
				debuff.pct_time   = 1;
				
			else
				
				debuff.cur_time   = nil;
				debuff.max_time   = nil;
				debuff.start_time = nil;
				debuff.end_time   = nil;
				debuff.pct_time   = nil;
				
			end
			
			-- set up non-player buff tooltip and disable click-to-cancel
			
			if not debuff.SetTooltip then

				debuff.SetTooltip = function( frame, unit_id )

					frame:EnableMouse( true );
					
					frame:SetScript( "OnEnter",
						function()							

							frame:SetScript( "OnUpdate",
								function()
									if GameTooltip:IsOwned( frame ) then
										GameTooltip:SetUnitDebuff( unit_id, debuff.id );
									end
								end
							);
							
							frame:SetID( debuff.id );
							frame.unit_id = unit_id;
							GameTooltip:SetOwner( frame );
							GameTooltip:SetUnitDebuff( unit_id, debuff.id );
							GameTooltip:Show();
						end
					);					
					
					frame:SetScript( "OnLeave",
						function()
							GameTooltip:Hide();
							frame:SetScript( "OnUpdate", nil );
						end
					);
					
				end
				
				debuff.SetClick = function( frame )
					
					frame:RegisterForClicks();
					frame:SetScript( "OnClick", nil );
					
				end
			end
			
			nUI:TableInsertByValue( debuff_list, debuff );

		end
	end
	
	-- sort the table
	
	if #debuff_list > 0 then SortAuras( debuff_list ); end;
	
	-- return the result
	
	return #debuff_list > 0 and debuff_list or nil;
	
end

			
-------------------------------------------------------------------------------
-- initialize configuration for the unit aura color indicators
-- 
-- this method is called when the mod's saved variables have been loaded by Bliz and
-- may be called again whenever the unit aura configuration has been changed
-- by the player or programmatically. Passing true or a non-nil value for "use_default"
-- will cause the player's current aura color configuration to be replaced with
-- the default settings defined at the top of this file (which cannot be undone!)

function nUI_Unit:configAura( use_default )
	
	if not nUI_UnitOptions then nUI_UnitOptions = {}; end
	if not nUI_UnitOptions.AuraColors then nUI_UnitOptions.AuraColors = {}; end
	
	for aura in pairs( nUI_DefaultConfig.AuraColors ) do
		nUI_Unit:configAuraColor( aura, use_default );
	end
end

function nUI_Unit:configAuraColor( aura, use_default )
	
	local config  = nUI_UnitOptions.AuraColors[aura] or {};
	local default = nUI_DefaultConfig.AuraColors[aura] or {};
	
	if use_default then
			
		config.r = default.r;
		config.g = default.g;
		config.b = default.b;
		config.a = default.a or 1;

	else
			
		config.r = tonumber( config.r or default.r );
		config.g = tonumber( config.g or default.g );
		config.b = tonumber( config.b or default.b );
		config.a = tonumber( config.a or default.a or 1 );

	end
	
	nUI_UnitOptions.AuraColors[aura] = config;
	nUI_Unit:refreshAuraCallbacks();
	
end

-------------------------------------------------------------------------------
-- add and remove callbacks from the list of unit aura listeners we manage
--
-- calling this method will return the current unit_info structure for this 
-- unit if it exists or nil if the unit does not exist at this time
--
-- Note: these callbacks will be notified both when the underlying GUID for the
--		 unit changes or when the aura info of the underlying GUID to the
--		 player changes. If the underlying unit does not exist, the callback
--		 will be passed a nil unit_info structure

function nUI_Unit:registerAuraCallback( unit_id, callback )
	
	local unit_info = nil;
	
	if unit_id and callback then
		
		-- get the list of callbacks for this unit id and add this callback
		
		local list = AuraCallbacks[unit_id] or {};
		
		nUI:TableInsertByValue( list, callback );
		
		-- if this is a new unit id, add it to the callback list
		
		if not AuraCallbacks[unit_id] then
			AuraCallbacks[unit_id] = list;
		end
		
		-- if this is the first callback for the unit id, then register our
		-- event driver to receive notice when the GUID changes on this id
		
		if #list == 1 then
			nUI_Unit:registerStatusCallback( unit_id, nUI_Unit.Drivers.Aura );
		end
		
		-- collect aura information for this unit as we know it at this time
	
		unit_info = nUI_Unit:getUnitInfo( unit_id );
		
		if unit_info then
			nUI_Unit:updateAuraInfo( unit_id, unit_info );
		end
	end
	
	return unit_info;
	
end

function nUI_Unit:unregisterAuraCallback( unit_id, callback )
	
	if unit_id and callback then
		
		-- get the list of current callbacks for this unit ud and remove this callback
		
		local list = AuraCallbacks[unit_id] or {};
		
		nUI:TableRemoveByValue( list, callback );
		
		-- if that's the last callback in the list, then remove our event handler of
		-- the list of unit change callbacks for that unit it
		
		if #list == 0 then
			nUI_Unit:unregisterStatusCallback( unit_id, nUI_Unit.Drivers.Aura );
		end
	end
end

-------------------------------------------------------------------------------
-- update the aura information for this unit
--
-- note: it is the caller's responsibility to ensure that the unit_info being
--       passed belongs to the unit_id that is passed. Generally third party
--       consumers of unit_info should not call this method, rather they 
--       should use the callback registration system to get change notices
--       and let the nUI unit driver engine do the updating. If you MUST call
--       this method, you should first test that the following condition 
--       evaluates as true: UnitGUID( unit_id ) == unit_info.guid
--
-- returns the updated unit information structure for the current GUID
-- if the data has changed, otherwise returns nil if nothing changed

function nUI_Unit:updateAuraInfo( unit_id, unit_info )

	local modified  = false;
	
	if unit_info then

		local aura_info;
		local status_info = unit_info.status_info;
		local alive       = status_info and (not status_info.is_dead and not status_info.is_ghost and true or false) or UnitIsDeadOrGhost( unit_id );
		
		-- a dead unit never has auras
		
		if not alive then
		
			modified = true;
			
		-- otherwise, see what auras we do have
		
		else
		
			local is_self   = unit_info.is_self;
			local cached    = unit_info.aura_info or {};

			aura_info =
			{
				weapon_buffs   = nil,
				buff_list      = GetBuffList( unit_id, is_self ),
				debuff_list    = GetDebuffList( unit_id, is_self ),
			};

			-- if this is the player, see if their weapon buffs changed
			
			if unit_info.is_self then
				
				modified               = new_weapon_buff;
				aura_info.weapon_buffs = PlayerWeaponBuffs;
				new_weapon_buff        = false;
			end
			
			-- if we don't already know we're modified by weapon buffs, 
			-- see if the buff auras changed

			if not modified then
					
				if (aura_info.buff_list and not cached.buff_list)
				or (cached.buff_list and not aura_info.buff_list)
				then
					
					modified = true;
					
				elseif aura_info.buff_list then
					
					if #aura_info.buff_list ~= #cached.buff_list then
						
						modified = true;
						
					elseif #aura_info.buff_list > 0 then
						
						for i=1,#aura_info.buff_list do
							
							local old = cached.buff_list[i];
							local new = aura_info.buff_list[i];
							
							if old.cur_time     ~= new.cur_time
							or old.max_time     ~= new.max_time
							or old.start_time   ~= new.start_time
							or old.end_time     ~= new.end_time
							or old.is_player    ~= new.is_player
							or old.until_cancel ~= new.until_cancel
							or old.id           ~= new.id
							or old.player_idx   ~= new.player_idx
							or old.count        ~= new.count
							or old.name         ~= new.name
							or old.rank         ~= new.rank
							or old.icon         ~= new.icon
							or old.type         ~= new.type
							or old.buff_color   ~= new.buff_color
							then
								
								modified = true;
								break;
								
							end	
							
							if old.buff_color
							and new.buff_color 
							then
								
								if old.buff_color.r ~= new.buff_color.r
								or old.buff_color.g ~= new.buff_color.g
								or old.buff_color.b ~= new.buff_color.b
								then
									modified = true;
									break;
								end
							end
						end						
					end
				end
			end
			
			-- if we don't know if we're modified yet, then see if the debuffs changed
			
			if not modified then
				
				if (aura_info.debuff_list and not cached.debuff_list)
				or (cached.debuff_list and not aura_info.debuff_list)
				then
					
					modified = true;
					
				elseif aura_info.debuff_list then
					
					if #aura_info.debuff_list ~= #cached.debuff_list then
						
						modified = true;
						
					elseif #aura_info.debuff_list > 0 then
						
						for i=1,#aura_info.debuff_list do
							
							local old = cached.debuff_list[i];
							local new = aura_info.debuff_list[i];
							
							if old.cur_time       ~= new.cur_time
							or old.max_time       ~= new.max_time
							or old.start_time     ~= new.start_time
							or old.end_time       ~= new.end_time
							or old.is_player      ~= new.is_player
							or old.until_cancel   ~= new.until_cancel
							or old.can_dispell    ~= new.can_dispell
							or old.id             ~= new.id
							or old.player_idx     ~= new.player_idx
							or old.count          ~= new.count
							or old.debuff_color   ~= new.debuff_color
							or old.name           ~= new.name
							or old.rank           ~= new.rank
							or old.icon           ~= new.icon
							or old.type           ~= new.type
							then
								
								modified = true;
								break;
								
							end						
							
							if old.debuff_color
							and new.debuff_color 
							then
								
								if old.debuff_color.r ~= new.debuff_color.r
								or old.debuff_color.g ~= new.debuff_color.g
								or old.debuff_color.b ~= new.debuff_color.b
								then
									modified = true;
									break;
								end
							end
						end						
					end
				end
			end	
		end
		
		-- if we've found something new in the unit auras, update the cache
		
		if modified then
			
			unit_info.modified    = true;
			unit_info.last_change = GetTime();
			unit_info.aura_info   = aura_info;
			
		end
	end
	
	return modified and unit_info or nil;
	
end


-------------------------------------------------------------------------------
-- update all of the registered unit aura listeners, even if there's no 
-- change in data... typically used when the aura color options change
-- or entering the world

function nUI_Unit:refreshAuraCallbacks()

	nUI_Unit:refreshCallbacks( 
	
		nUI_L["unit aura"], AuraCallbacks, AuraUnits, 
	
		function( list_unit, unit_info ) 
			nUI_Unit:updateAuraInfo( list_unit, unit_info ); 
		end 
	);
	
end

-------------------------------------------------------------------------------
-- create a new unit aura frame

function nUI_Unit:createAuraFrame( parent, unit_id, id, options )

	local frame = nUI_Unit:createFrame( "$parent_Aura"..(id or ""), parent, unit_id, false );
	frame.Super = {};

	-- create the aura buttons for this frame
	
	frame.ButtonList = {};

	-- called when the underlying GUID for the unit changes or when the
	-- content of the GUID is updated
	
	frame.Super.newUnitInfo = frame.newUnitInfo;	
	frame.newUnitInfo       = function( list_unit, unit_info )
		
		frame.Super.newUnitInfo( list_unit, unit_info );
		
		if frame.enabled then
			nUI_Unit:updateAuraFrame( frame );
		end
		
	end;
	
	-- setting enabled to false will prevent the frame from updating when new
	-- unit information is received (saves framerate). Setting enabled true will
	-- call the frame to immediately update if its content has changed since it
	-- was disabled
	
	frame.Super.setEnabled = frame.setEnabled;	
	frame.setEnabled       = function( enabled )
		
		local prior_state = frame.enabled;
		
		frame.Super.setEnabled( enabled );
		
		if frame.enabled ~= prior_state then
		
			if frame.enabled then
				frame.unit_info = nUI_Unit:registerAuraCallback( frame.unit, frame );
				nUI_Unit:updateAuraFrame( frame );
			else
				nUI_Unit:unregisterAuraCallback( frame.unit, frame );
			end
		end
	end
	
	-- used to change the scale of the frame... rather than the Bliz widget frame:SetScale()
	-- this method actually recalculates the size of the frame and uses frame:SetHeight()
	-- and frame:SetWidth() to reflect the actual size of the frame.
	
	frame.Super.applyScale = frame.applyScale;
	frame.applyScale       = function( scale )

		frame.Super.applyScale( scale );

		local anchor      = scale and frame.anchor or nil;
		local scale       = scale or frame.scale or 1;
		local size        = frame.options.size * scale * nUI.scale;
		local hGap        = frame.options.hGap * scale * nUI.scale;
		local vGap        = frame.options.vGap * scale * nUI.scale;
		
		frame.scale = scale;
		
		if frame.size        ~= size 
		or frame.hGap        ~= hGap
		or frame.vGap        ~= vGap
		then
			
			local rows      = frame.options.rows;
			local cols      = frame.options.cols;
			
			frame.size      = size;
			frame.hGap      = hGap;
			frame.vGap      = vGap;
			
			frame:SetHeight( rows * size + (rows-1) * vGap );
			frame:SetWidth( cols * size + (cols-1) * hGap );
			
			for i=1,#frame.ButtonList do
				
				local button = frame.ButtonList[i];
				local anchor_pt, relative_to, relative_pt, xOfs, yOfs = button:GetPoint( 1 );

				ApplyAuraFrameOptions( frame, button );
				
				if anchor_pt then
						
					if xOfs < 0 then xOfs = -hGap;
					elseif xOfs > 0 then xOfs = hGap;
					end
					
					if yOfs < 0 then yOfs = -vGap;
					elseif yOfs > 0 then yOfs = vGap;
					end
					
					button:ClearAllPoints();
					button:SetPoint( anchor_pt, relative_to, relative_pt, xOfs, yOfs );
					
				end				
			end			
		end		
		
		if anchor then frame.applyAnchor(); end
		
	end
	
	-- this method applies the anchor point of the frame. As with all else, the
	-- frame's anchor is only moved if the point defined is different than the
	-- point that is already known
	
	frame.Super.applyAnchor = frame.applyAnchor;
	frame.applyAnchor       = function( anchor )
		
		frame.Super.applyAnchor( anchor );

		if frame.options then
			for i=1,#frame.ButtonList do
				ApplyAuraFrameOptions( frame, frame.ButtonList[i] );
			end
		end			
	end
	
	-- applies the set of frame options to this frame. Typically called when the frame 
	-- is first created or when the user changes options via config.
	
	frame.Super.applyOptions = frame.applyOptions;
	frame.applyOptions       = function( options )

		frame.Super.applyOptions( options );
		
		-- set up the spell name label;
		
		local name_label = options.label or {};
		
		name_label.enabled     = options.label ~= nil;
		name_label.anchor_pt   = name_label.anchor_pt or "CENTER";
		name_label.relative_to = name_label.relative_to or "CENTER";
		name_label.xOfs        = name_label.xOfs or 0;
		name_label.yOfs        = name_label.yOfs or 0;
		name_label.justifyH    = name_label.justifyH or "CENTER";
		name_label.justifyV    = name_label.justifyV or "MIDDLE";
		name_label.fontsize    = name_label.fontsize or 12;
		
		-- set up the countdown timer label;
		
		local timer_label = options.timer or {};
		
		timer_label.enabled     = options.timer ~= nil;
		timer_label.anchor_pt   = timer_label.anchor_pt or "CENTER";
		timer_label.relative_to = timer_label.relative_to or "CENTER";
		timer_label.xOfs        = timer_label.xOfs or 0;
		timer_label.yOfs        = timer_label.yOfs or 0;
		timer_label.justifyH    = timer_label.justifyH or "CENTER";
		timer_label.justifyV    = timer_label.justifyV or "MIDDLE";
		timer_label.fontsize    = timer_label.fontsize or 12;
		
		-- set up the application count label;
		
		local count_label = options.count or {};
		
		count_label.enabled     = options.count ~= nil;
		count_label.anchor_pt   = count_label.anchor_pt or "CENTER";
		count_label.relative_to = count_label.relative_to or "CENTER";
		count_label.xOfs        = count_label.xOfs or 0;
		count_label.yOfs        = count_label.yOfs or 0;
		count_label.justifyH    = count_label.justifyH or "CENTER";
		count_label.justifyV    = count_label.justifyV or "MIDDLE";
		count_label.fontsize    = count_label.fontsize or 12;
			
		-- if we're changing options after having already created buttons,
		-- then update the buttons that already exist
		
		if frame.label then
			
			frame.label = name_label;
			frame.timer = timer_label;
			frame.count = count_label;
			
			for i=1,#frame.ButtonList do
				ApplyAuraFrameOptions( frame, frame.ButtonList[i] );
			end
			
		else
			
			frame.label = name_label;
			frame.timer = timer_label;
			frame.count = count_label;
			
		end
		
		-- update the buttons
		
		frame.last_id = min( 40, (options.rows or 1) * (options.cols or 1) );
		
		for i=1, frame.last_id do
			frame.ButtonList[i] = frame.ButtonList[i] or CreateAuraButton( frame, i );
			ApplyAuraFrameOptions( frame, frame.ButtonList[i] );
		end

		-- anchor all active aura buttons

		local origin     = options.origin;
		local left       = origin == "TOPLEFT" or origin == "BOTTOMLEFT";
		local top        = origin == "TOPLEFT" or origin == "TOPRIGHT";
		local horizontal = options.horizontal;
		local rows       = horizontal and options.rows or options.cols;
		local cols       = horizontal and options.cols or options.rows;
		local row        = 0;
		local col        = 0;
		local hGap       = frame.hGap;
		local vGap       = frame.vGap;
		local last_button;
		local row_start;
		local next_button;
		local next_line;
		
		-- builing auras starting from the top left
		
		if top and left then
		
			if horizontal then
				next_button = { pt1 = "LEFT", pt2 = "RIGHT", xOfs = hGap, yOfs = 0 };
				next_line = { pt1 = "TOPLEFT", pt2 = "BOTTOMLEFT", xOfs = 0, yOfs = -vGap };
			else
				next_button = { pt1 = "TOP", pt2 = "BOTTOM", xOfs = 0, yOfs = -vGap };
				next_line = { pt1 = "TOPLEFT", pt2 = "TOPRIGHT", xOfs = hGap, yOfs = 0 };
			end

		-- building auras starting from the top right
		
		elseif top then
			
			if horizontal then
				next_button = { pt1 = "RIGHT", pt2 = "LEFT", xOfs = -hGap, yOfs = 0 };
				next_line = { pt1 = "TOPRIGHT", pt2 = "BOTTOMRIGHT", xOfs = 0, yOfs = -vGap };
			else
				next_button = { pt1 = "TOP", pt2 = "BOTTOM", xOfs = 0, yOfs = -vGap };
				next_line = { pt1 = "TOPRIGHT", pt2 = "TOPLEFT", xOfs = -hGap, yOfs = 0 };
			end

		-- building auras starting from the bottom left
		
		elseif left then			
		
			if horizontal then
				next_button = { pt1 = "LEFT", pt2 = "RIGHT", xOfs = hGap, yOfs = 0 };
				next_line = { pt1 = "BOTTOMLEFT", pt2 = "TOPLEFT", xOfs = 0, yOfs = vGap };
			else
				next_button = { pt1 = "BOTTOM", pt2 = "TOP", xOfs = 0, yOfs = vGap };
				next_line = { pt1 = "BOTTOMLEFT", pt2 = "BOTTOMRIGHT", xOfs = hGap, yOfs = 0 };
			end
			
		-- building auras starting from the bottom right
		
		else
		
			if horizontal then
				next_button = { pt1 = "RIGHT", pt2 = "LEFT", xOfs = -hGap, yOfs = 0 };
				next_line = { pt1 = "BOTTOMRIGHT", pt2 = "TOPRIGHT", xOfs = 0, yOfs = vGap };
			else
				next_button = { pt1 = "BOTTOM", pt2 = "TOP", xOfs = 0, yOfs = vGap };
				next_line = { pt1 = "BOTTOMRIGHT", pt2 = "BOTTOMLEFT", xOfs = -hGap, yOfs = 0 };
			end
			
		end

		-- span the list of active aura buttons and anchor them
		
		for i=1, frame.last_id do

			local button = frame.ButtonList[i];
			local anchor;
			local anchor_frame;
						
			col = col+1;
			
			-- first button on the frame
			
			if i == 1 then
				
				button:ClearAllPoints();				
				button:SetPoint( origin, frame, origin, 0, 0 );
				
				row         = 1;
				row_start   = button;

			-- start a new line of aura buttons 
			
			elseif col > cols then 
				
				anchor_frame = row_start;
				anchor       = next_line;
				row_start    = button;
				row          = row+1;
				col          = 1;

				button:ClearAllPoints();
				button:SetPoint( anchor.pt1, anchor_frame, anchor.pt2, anchor.xOfs, anchor.yOfs );

			-- otherwise, just place this button after the last one on the line
			
			else
				
				anchor_frame = last_button;
				anchor       = next_button;
			
				button:ClearAllPoints();
				button:SetPoint( anchor.pt1, anchor_frame, anchor.pt2, anchor.xOfs, anchor.yOfs );

			end
				
			last_button = button;
			
		end
		
		-- refresh the frame
		
		nUI_Unit:updateAuraFrame( frame );
		
	end

	-- register the frame for scaling
	
	nUI:registerScalableFrame( frame );
	
	-- initiate the frame
	
	frame.unit_info = nUI_Unit:registerAuraCallback( frame.unit, frame );
	
	frame.applyOptions( options );
	
	return frame;
	
end

-------------------------------------------------------------------------------
-- remove a unit aura frame

function nUI_Unit:deleteAuraFrame( frame )

	nUI:unregisterScalableFrame( frame );
	nUI_Unit:unregisterAuraCallback( frame.unit, frame );
	nUI_Unit:deleteFrame( frame );
	
end

-------------------------------------------------------------------------------
-- display the appropriate icons for the unit's auras
--
-- note: this method expends extra energy in state management... as in knowing
--       exactly what state it is currently in and only updating the frame text,
--       content, colors, alphas, etc. when a change in state occurs. The extra
--       effort is spent on state management in order to reduce impact to the
--       graphis engine so as to preserve frame rate. It costs far less to check
--		 a memory value that and burn through the graphics geometry. It does not
--       matter how many times the unit changes GUID or how many times this 
--       method will call, it will only alter the graphics elements when its
--       relative state changes.

function nUI_Unit:updateAuraFrame( frame )
	
	local aura_type  = frame.options.aura_type;
	local unit_info  = frame.unit_info;
	local aura_info  = unit_info and unit_info.aura_info;
	local aura_count = 0;
	local aura_list;
	
	-- otherwise determine the type of auras to show and where or not there are any

	if aura_info then
		
		if aura_type == "help" then 
			aura_type = unit_info.attackable and "debuff" or "buff";
		elseif aura_type == "harm" then
			aura_type = unit_info.attackable and "buff" or "debuff";
		end

		if aura_type == "buff" then
			aura_list = aura_info.buff_list;
		else
			aura_list = aura_info.debuff_list;
		end
		
		if aura_list then 
			aura_count = #aura_list;
		end
		
		if aura_type == "buff" and aura_info.weapon_buffs then
			if aura_info.weapon_buffs[1] then aura_count = aura_count+1; end
			if aura_info.weapon_buffs[2] then aura_count = aura_count+1; end
		end
		
	end

	-- if there's no unit or no auras, hide the frame and disable
	-- all of its buttons so there's no mouseover, etc.

	if aura_count == 0 then
		
		if frame.active then
			
			for i=1,40 do
				
				local button = frame.ButtonList[i];
				
				if button and button.active then
					DisableAuraButton( button );
				else
					break;
				end
			end
			
			frame.active = false;
			frame:SetAlpha( 0 );
		end

	-- otherwise we have work to do
		
	else

--		nUI:debug( "nUI_UnitAura: processing aura update for "..frame:GetName(), 1 );
		
		-- make sure the frame is visible
		
		if not frame.active then
			frame.active = true;
			frame:SetAlpha( 1 );
		end

		-- add weapon buffs if they apply... weapon buffs always appear first
		
		local last_id  = 0;
		local button;
		
		if aura_info.weapon_buffs then

			for i=1,2 do
					
				local aura = aura_info.weapon_buffs[i];
				
				if aura and not aura.expired then
					
					last_id  = last_id+1;
					button   = frame.ButtonList[last_id];
					
--					nUI:debug( "nUI_UnitAura: preparing to set weapon aura on button #"..last_id..": "..button:GetName() );
					
					SetButtonAura( frame, button, aura, "buff" );
					
				end
			end
		end
		
		-- span the list of active auras
		
		local player_auras = frame.options.player_auras;
		local dispellable  = frame.options.dispellable;
		
		if aura_list then
				
			for i=1, #aura_list do
				
				local aura = aura_list[i];
	
				if aura and not aura.expired then
						
					-- if the we only want to see auras the player cast, ignore all else
						
					if player_auras and not aura.is_player then
						aura = nil;
						
					-- if we only want to see auras the player can dispell, ignore all else
						
					elseif dispellable and not aura.can_dispell then
						aura = nil;
						
					end
					
					if aura then
						
						last_id = last_id+1;
						button  = frame.ButtonList[last_id];
						
--						nUI:debug( "nUI_UnitAura: preparing to set "..aura_type.." aura on button #"..last_id..": "..button:GetName() );
											
						SetButtonAura( frame, button, aura, aura_type );
						
						if last_id == frame.last_id then
							break;
						end										
					end
				end			
			end
	
			-- make sure all of the active auras are visible
			
			for i=1,last_id do
	
				local button = frame.ButtonList[i];
				
				if not button.active then
				
--					nUI:debug( "nUI_UnitAura: enabling "..button:GetName().." for aura "..button.aura.name, 1 );
					button.active = true;
					button:SetAlpha( 1 );
					
				end			
			end
		end
		
		-- make sure any unused aura buttons are deactivated
		
		for i=last_id+1,40 do
			
			local button = frame.ButtonList[i];
			
			if button and button.active then
				DisableAuraButton( button );
			else
				break;
			end
			
		end
		
		-- if we're dynamically sizing the frame, then calculate and set its
		-- size accordingly
		
		if frame.options.dynamic_size then
			
			local rows, cols;
			
			if frame.options.horizontal then
				cols = min( frame.options.cols, last_id );
				rows = math.ceil( last_id / frame.options.cols );
			else
				cols = math.ceil( last_id / frame.options.rows );
				rows = min( frame.options.rows, last_id );
			end
			
			local width  = cols * frame.size + (cols-1) * frame.hGap;
			local height = rows * frame.size + (rows-1) * frame.vGap;
			
			if frame.dynamic_width  ~= width
			or frame.dynamic_height ~= height
			then
				
				frame.dynamic_width  = width;
				frame.dynamic_height = height;
				
				frame:SetWidth( width );
				frame:SetHeight( height );
				
			end
		end
	end
end
