
if ( not DivineBlessing ) then DivineBlessing = {} end

-- State variables
DivineBlessing.CastingState					= nil
DivineBlessing.CursorSpellId				= nil
DivineBlessing.PlayerBuffs					= nil
DivineBlessing.PlayerClass					= nil
DivineBlessing.State						= {}

-- Hooks
DivineBlessing.SavedPickupSpell				= nil

-- Constants	
DivineBlessing.ADDON_NAME					= "DivineBlessing"
DivineBlessing.SETTYPE_PARTY				= "party"
DivineBlessing.SETTYPE_CLASS				= "class"
DivineBlessing.SETTYPE_RAID					= "raid"
DivineBlessing.TARGET_CLASS					= "class"
DivineBlessing.TARGET_GROUP					= "group"
DivineBlessing.SET_INFO						= {}
DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_PARTY ]	= { size = 6 }
DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_CLASS ]	= { size = 2 }
DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_RAID ]	= { size = 2, isRaid = true }
DivineBlessing.SPELL_TARGET_LEVEL_OFFSET	= 10
DivineBlessing.FONT_COLOR_BANNER			= { r = 1.00, g = 0.82, b = 0.00 }
DivineBlessing.FONT_COLOR_MESSAGE			= { r = 0.95, g = 0.95, b = 0.95 }
DivineBlessing.FONT_COLOR_ENABLED_BUTTON	= { r = 1.00, g = 1.00, b = 1.00 }
DivineBlessing.FONT_COLOR_DISABLED_BUTTON	= { r = 0.40, g = 0.40, b = 0.40 }
DivineBlessing.FONT_SIZE_LABEL				= 10
DivineBlessing.MENU_BUTTON_HEIGHT       	= 16
DivineBlessing.MENU_BUTTON_PADDING      	= 20
DivineBlessing.MENU_HORIZ_PADDING       	= 18
DivineBlessing.MENU_MAX_BUTTONS         	= 32
DivineBlessing.MENU_VERTICAL_PADDING    	= 20
DivineBlessing.FONT_COLOR_UNKNOWN_NAME		= { r = 1.00, g = 1.00, b = 1.00 }
DivineBlessing.FONT_COLOR_UNKNOWN_MESSAGE	= { r = 1.00, g = 0.30, b = 0.30 }
DivineBlessing.SYSMSG_SPELL_ACTION			=
	{
		-- This table controls how to react to various system messages (wait / not wait)
		-- false = wait, true = use set's normal wait rule
		[ DIVINEBLESSING_SPELLFAIL_NOTENOUGHMANA ]	= false,
		[ DIVINEBLESSING_SPELLFAIL_INVALIDTARGET ]	= false,
		[ DIVINEBLESSING_SPELLFAIL_LINEOFSIGHT ]	= true,
		[ DIVINEBLESSING_SPELLFAIL_OUTOFRANGE ]		= true,
	}
DivineBlessing.TEXTURE_SET_ALL				= "Interface\\Icons\\Ability_Creature_Cursed_04"
DivineBlessing.TEXTURE_SET_ALL_COLOR		= { r = 0.05, g = 1.00, b = 1.00, a = 0.60 }
DivineBlessing.TEXTURE_UNKNOWN_SPELL		= "Interface\\Icons\\INV_Misc_QuestionMark"
DivineBlessing.TARGET_INDICES				= {}
DivineBlessing.TARGET_INDICES[ DivineBlessing.SETTYPE_PARTY ] =
	{
		"player", "party1", "party2", "party3", "party4",
		"pet", "party1pet", "party2pet", "party3pet", "party4pet"
	}
DivineBlessing.TARGET_INDICES[ DivineBlessing.SETTYPE_CLASS ] =
	{
		"DRUID", "HUNTER", "MAGE", "PALADIN", "PRIEST", "ROGUE", "SHAMAN", "WARLOCK", "WARRIOR",
		"IMP", "VOIDWALKER", "SUCCUBUS", "FELHUNTER", "BEAST"
	}
DivineBlessing.TARGET_INDICES[ DivineBlessing.SETTYPE_RAID ] = DivineBlessing.TARGET_INDICES[ DivineBlessing.SETTYPE_CLASS ]

-- Options checkboxes
DivineBlessing.Options						=
	{
		[1]	= { label = DIVINEBLESSING_DIALOG_BUTTON_COMPLETION, option = "announceSetCompletion" },
		[2]	= { label = DIVINEBLESSING_DIALOG_BUTTON_SUCCESS, option = "announceSuccess" },
		[3]	= { label = DIVINEBLESSING_DIALOG_BUTTON_FAILURE, option = "announceFailure" },
		[4]	= { label = DIVINEBLESSING_DIALOG_BUTTON_REMAINING, option = "announceRemaining" },
		[5]	= { label = DIVINEBLESSING_DIALOG_BUTTON_BANNER, option = "bannerAnnounce" },
		[6] = { label = DIVINEBLESSING_DIALOG_BUTTON_MISSED, option = "announceMissedTargets" },
		[7] = { label = DIVINEBLESSING_DIALOG_BUTTON_REBUFF_MISSED, option = "rebuffMissedTargets" },
		[8] = { label = DIVINEBLESSING_DIALOG_BUTTON_GROUPORDER, option = "useGroupOrder" },
		[9] = { label = DIVINEBLESSING_DIALOG_BUTTON_FIND_TARGET, option = "findAlternateTargets" },
	}

-- Default config
DivineBlessing_UserConfig					= {}
DivineBlessing_UserConfig.Sets				= {}
DivineBlessing_UserConfig.Options			=
	{
		announceSetCompletion				= 1,
		announceSuccess						= 1,
		announceFailure						= 1,
		announceRemaining					= 1,
		bannerAnnounce						= 1,
		useGroupOrder						= 1,
		announceMissedTargets				= 1,
		rebuffMissedTargets					= nil,
		findAlternateTargets				= 1,
	}

-- Panel registration
UIPanelWindows[ "DivineBlessingFrame" ] = { area = "left", pushable = 999, whileDead = 1 }

----------------------------------------------------------------------
-- Library Functions
--	Divine Blessing has no Library functions -- all functionality
--	should be accessed either from the configuration frame, the
--	slash commands, or the keybindings.
----------------------------------------------------------------------

----------------------------------------------------------------------
-- Normal Functions
----------------------------------------------------------------------

--[[ Event Handlers	]]--

--[[
--	DivineBlessing.OnLoad()
--		DivineBlessingFrame OnLoad event
--]]

function DivineBlessing.OnLoad()
	this:RegisterEvent( "ADDON_LOADED" )
	this:RegisterEvent( "VARIABLES_LOADED" )
	this:RegisterEvent( "UNIT_SPELLCAST_STOP" )
	this:RegisterEvent( "UNIT_SPELLCAST_FAILED" )
	this:RegisterEvent( "UNIT_SPELLCAST_INTERRUPTED" )
	this:RegisterEvent( "UNIT_SPELLCAST_SUCCEEDED" )
	this:RegisterEvent( "SYSMSG" )
	
	-- Hook the spellbook being opened so we can drag spells from it
	if ( ( Sea ) and ( Sea.util ) and ( Sea.util.hook ) ) then
		Sea.util.hook( "PickupSpell", "DivineBlessing.PickupSpell", "before" )
	else
		DivineBlessing.SavedPickupSpell = PickupSpell
		PickupSpell = DivineBlessing.PickupSpell
	end

	DivineBlessing.SetScrollFrameInit( DivineBlessingFrameParty, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_PARTY ].size )
	DivineBlessing.SetScrollFrameInit( DivineBlessingFrameClass, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_CLASS ].size )
	DivineBlessing.SetScrollFrameInit( DivineBlessingFrameRaid, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_RAID ].size )

	-- Initialize the dialog tabs
	PanelTemplates_SetNumTabs( this, 5 )
	this.selectedTab = 1
	PanelTemplates_UpdateTabs( this )	
	
	-- Addon manager registration
	DivineBlessing.RegisterCosmos()
	DivineBlessing.RegisterKhaos()
end

--[[
--	DivineBlessing.RegisterCosmos()
--		Adds the DivineBlessing button to the Cosmos frame
--]]

function DivineBlessing.RegisterCosmos()
	if ( not Cosmos_RegisterButton ) then return end

	Cosmos_RegisterButton(
		DIVINEBLESSING_BUTTON_NAME,
		DIVINEBLESSING_BUTTON_SUBTITLE,
		DIVINEBLESSING_BUTTON_DESCRIPTION,
		DIVINEBLESSING_BUTTON_ICON,
		DivineBlessing.ToggleFrame,
		function() return true end
	)
end

--[[
--	DivineBlessing.RegisterKhaos()
--		Adds the DivineBlessing button to the Earth Feature Frame
--]]

function DivineBlessing.RegisterKhaos()
	if ( not EarthFeature_AddButton ) then return end

	EarthFeature_AddButton(
		{
			id = DIVINEBLESSING_KHAOS_ID,
			name = DIVINEBLESSING_BUTTON_NAME,
			subtext = DIVINEBLESSING_BUTTON_SUBTITLE,
			tooltip = DIVINEBLESSING_BUTTON_DESCRIPTION,
			icon = DIVINEBLESSING_BUTTON_ICON,
			callback = DivineBlessing.ToggleFrame,
			test = nil,
		}
	)
end

--[[
--	DivineBlessing.ResolveStateName( string stateId )
--		Resolve a DivineBlessing 2.0 or earlier blessing set name into a DB3 name
--
--	Args:
--		string stateId - the input set / state id to resolve
--
--	Returns:
--		string stateId - the resolved blessing state id
--]]

function DivineBlessing.ResolveStateName( stateId )
	stateId = string.lower( stateId )

	if ( DivineBlessing.State[ stateId ] ) then return stateId end

	-- Backwards-compatibility state names
	if ( ( stateId == "a" ) or ( stateId == "1" ) ) then		stateId = "party1"
	elseif ( ( stateId == "b" ) or ( stateId == "2" ) ) then	stateId = "party2"
	elseif ( ( stateId == "c" ) or ( stateId == "3" ) ) then	stateId = "party3"
	elseif ( ( stateId == "d" ) or ( stateId == "4" ) ) then	stateId = "party4"
	elseif ( ( stateId == "e" ) or ( stateId == "5" ) ) then	stateId = "party5"
	elseif ( ( stateId == "f" ) or ( stateId == "6" ) ) then	stateId = "party6"
	elseif ( stateId == "class" ) then							stateId = "class1"
	elseif ( stateId == "raid" ) then							stateId = "raid1"
	end
	
	return stateId
end

--[[
--	DivineBlessing.OnBless( string stateId )
--		Chat command handler for the /bless command
--
--	Args:
--		string stateId - the state id of the set to cast
--
--	Returns:
--		NO RETURN
--]]
-- Doesn't work anymore, due to 2.0 changes
--function DivineBlessing.OnBless( stateId )
--	stateId = DivineBlessing.ResolveStateName( stateId )
--	if ( DivineBlessing.State[ stateId ] ) then
--		DivineBlessing.Bless( stateId )
--	else
--		DivineBlessing.MultilineMessage( DIVINEBLESSING_USAGE_MESSAGE )
--	end
--end
--SlashCmdList["BLESS"] = DivineBlessing.OnBless

--[[
--	DivineBlessing.OnChatCommand( string message )
--		Chat command handler for the /divineblessing command
--
--	Args:
--		string stateId - the state id of the set to cast
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.OnChatCommand( message )
	local s, e, command, param = string.find( message, DIVINEBLESSING_REGEX_CHAT_COMMAND )
	if ( not s ) then command = message end
	command = string.lower( command )

	if ( ( not command ) or ( string.len( command ) == 0 ) ) then
		DivineBlessing.ToggleFrame()
	elseif ( command == DIVINEBLESSING_CMD_RESET ) then
		if ( not param ) then
			DivineBlessing.MultilineMessage( DIVINEBLESSING_USAGE_MESSAGE )
		else
			local stateId = DivineBlessing.ResolveStateName( param )
			local state = DivineBlessing.State[ stateId ]
			if ( not state ) then
				DivineBlessing.MultilineMessage( DIVINEBLESSING_USAGE_MESSAGE )
			else
				state:Reset()
				DivineBlessing.Message( string.format( DIVINEBLESSING_RESET_MESSAGE, state:GetName() ), false )
			end
		end
	elseif ( command == DIVINEBLESSING_CMD_RESETALL ) then
		for stateId, state in pairs( DivineBlessing.State ) do
			state:Reset()
		end
		DivineBlessing.Message( DIVINEBLESSING_RESETALL_MESSAGE, false )
	else
		-- Display usage help
		DivineBlessing.MultilineMessage( DIVINEBLESSING_USAGE_MESSAGE )
	end
end
SlashCmdList["DIVINEBLESSING"] = DivineBlessing.OnChatCommand

--[[
--	DivineBlessing.PickupSpell( number spellId, string bookType )
--		Hook function for PickupSpell
--
--	Args:
--		number spellId - the spellid of the spell to pick up
--		string bookType - the spellbook to pick the spell up from
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.PickupSpell( spellId, bookType )
	DivineBlessing.CursorSpellId = nil
	if ( spellId ) then
		if ( ( spellId < 1 ) or ( spellId >= MAX_SPELLS ) ) then return end
		if ( bookType == BOOKTYPE_SPELL ) then DivineBlessing.CursorSpellId = spellId end
	end
	if ( DivineBlessing.SavedPickupSpell ) then
		return DivineBlessing.SavedPickupSpell( spellId, bookType )
	end
end

--[[
--	DivineBlessing.OnEvent( string event, string arg1 )
--		OnEvent handler for DivineBlessingFrame
--
--	Args:
--		string event	= the event name
--		string arg1		= the first argument of the event
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.OnEvent( event, arg1 )
	if ( event == "ADDON_LOADED" and arg1 == DivineBlessing.ADDON_NAME ) then
		local _, unitClass = UnitClass( "player" )
		DivineBlessing.PlayerClass = unitClass
		DivineBlessing.PlayerBuffs = DivineBlessing.Buffs[ DivineBlessing.PlayerClass ]
		DivineBlessing.Message( DIVINEBLESSING_LOADED_FORMAT_STRING, false, true )
	elseif ( event == "VARIABLES_LOADED" ) then
		DivineBlessing.InitializeSettings()
	elseif ( event =="UNIT_SPELLCAST_SUCCEEDED" ) then
		DivineBlessing.OnSpellCast( true )
	elseif ( ( event == "UNIT_SPELLCAST_FAILED" ) or
			 ( event == "UNIT_SPELLCAST_INTERRUPTED" ) or
			 ( event == "UNIT_SPELLCAST_STOP" ) ) then
		DivineBlessing.OnSpellCast( false )
	elseif ( event == "SYSMSG" ) then
		local action = DivineBlessing.SYSMSG_SPELL_ACTION[ arg1 ]
		if ( action ~= nil ) then
			DivineBlessing.OnSpellCast( false, action )
		end
	end
end

--[[
--	DivineBlessing.OnSpellCast( bool success, bool forceWait )
--		This function does processing for the SPELLCAST_* and SYSMSG events
--
--	Args:
--		bool success - true if the spell casting succeeded, false otherwise
--		bool forceWait - true if a failure should always be waited on, false otherwise
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.OnSpellCast( success )
	if ( not DivineBlessing.CastingState ) then return end

	local setState = DivineBlessing.CastingState
	local targetId = setState.lastTargetId
	DivineBlessing.CastingState = nil

	local spellName = GetSpellName( setState.lastSpellId or MAX_SPELLS, BOOKTYPE_SPELL ) or DIVINEBLESSING_UNKNOWN_SPELL
	local targetName = setState:GetCurrentTargetName() or DIVINEBLESSING_UNKNOWN_TARGET
	
	if ( success ) then
		-- Display "successful cast" message
		if ( DivineBlessing_UserConfig.Options.announceSuccess ) then
			if ( DivineBlessing_UserConfig.Options.announceRemaining ) then
				local remaining = setState:GetRemainingCasts() - 1
				if ( remaining <= 0 ) then
					DivineBlessing.Message( string.format( DIVINEBLESSING_FMR_CAST_SUCCESS_REM_0, spellName, targetName ) )
				else
					DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_SUCCESS_REM, spellName, targetName, remaining ) )
				end
			else
				DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_SUCCESS, spellName, targetName ) )
			end
		end

		local missedTargets = setState:SpellCastSuccess()
		
		if ( ( DivineBlessing_UserConfig.announceMissedTargets ) and 
			 ( missedTargets ) and ( table.getn( missedTargets ) > 0 ) ) then
			-- Output the list of missed targets for this spell
			DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_MISSED_MESSAGE, spellName ), false )
			local targetNames = ""
			local k, unitId
			for k, unitId in pairs( missedTargets ) do
				if ( k > 1 ) then targetNames = targetNames..", " end
				targetNames = targetNames..UnitName( unitId )
			end
			DivineBlessing.Message( targetNames, false )
		end
		
	else
-- 2.0 changes prevent this from working
--		SpellStopTargeting()

		if ( forceWait or setState.set.options.wait ) then
			-- Display "failed, but waiting" message
			if ( DivineBlessing_UserConfig.Options.announceFailure ) then
				DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_FAILURE, spellName, targetName ) )
			end
			return
		end

		-- Display "failed, but continuing anyway" message
		if ( DivineBlessing_UserConfig.Options.announceFailure ) then
			if ( DivineBlessing_UserConfig.Options.announceRemaining ) then
				local remaining = setState:GetRemainingCasts() - 1
				if ( remaining <= 0 ) then
					DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_FAILURE_REM_0, spellName, targetName ) )
				else
					DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_FAILURE_REM, spellName, targetName, remaining ) )
				end
			else
				DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_CAST_FAILURE, spellName, targetName ) )
			end
		end

		setState:SpellCastFailure()
	end
	
	if ( setState:atBeginning() ) then
		-- Display "set completed" message
		if ( DivineBlessing_UserConfig.Options.announceSetCompletion ) then
			DivineBlessing.Message( string.format( DIVINEBLESSING_FMT_SET_COMPLETED, setState:GetName() ) )
		end
	end
end

--[[ General Functions ]]--

--[[
--	DivineBlessing.Message( string message, bool bannerOverride )
--		Display a message to the chatbox or banner
--
--	Args:
--		string message - the message to display
--		bool bannerOverride - if provided, overrides the user's bannerAnnounce option setting
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.Message( message, bannerOverride, noPrefix )
	if ( ( not message ) or ( string.len( message ) == 0 ) ) then return end

	local isBanner = DivineBlessing_UserConfig.Options.bannerAnnounce
	if ( bannerOverride ~= nil ) then isBanner = bannerOverride end

	if ( isBanner ) then
		local color = DivineBlessing.FONT_COLOR_BANNER
		if ( ( Sea ) and ( Sea.IO ) and ( Sea.IO.bannerc ) ) then
			Sea.IO.bannerc( color, message )
		else
			UIErrorsFrame:AddMessage( message, color.r, color.g, color.b, 1.0, UIERRORS_HOLD_TIME )
		end
	else
		if ( not noPrefix ) then message = DIVINEBLESSING_MESSAGE_PREFIX..message end
		local color = DivineBlessing.FONT_COLOR_MESSAGE
		if ( ( Sea ) and ( Sea.IO ) and ( Sea.IO.printc ) ) then
			Sea.IO.printc( color, message )
		else
			DEFAULT_CHAT_FRAME:AddMessage( message, color.r, color.g, color.b )
		end
	end
end

--[[
--	DivineBlessing.MultilineMessage( table messageTable )
--		Multi-line DivineBlessing.Message function
--
--	Args:
--		table messageTable - a table of strings to display sequentially
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.MultilineMessage( messageTable )
	if ( messageTable ) then
		for k, message in pairs( messageTable ) do
			DivineBlessing.Message( message, false, true )
		end
	end
end

--[[
--	DivineBlessing.InitSetState( table set )
--		Initializes the state object for the specified set
--
--	Args:
--		table set - the set to init state for
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.InitSetState( set )
	if ( not set ) then return end
	local stateId = set.type..set.id
	local setState = DivineBlessing.State[ stateId ]
	if ( not setState ) then
		DivineBlessing.State[ stateId ] = DivineBlessing.SetState:New( set )
		setState = DivineBlessing.State[ stateId ]
	end
end

--[[
--	DivineBlessing.InitializeSets( string setType )
--		Makes sure the sets of the specified type are properly initialized
--
--	Args:
--		string setType - the type of the set
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.InitializeSets( setType )
	local setInfo = DivineBlessing.SET_INFO[ setType ]
	if ( not setInfo ) then return end
	
	local setNum
	for setNum = 1, setInfo.size, 1 do
		if ( not DivineBlessing_UserConfig.Sets[ setType ][ setNum ] ) then
			DivineBlessing_UserConfig.Sets[ setType ][ setNum ] = {}
		end
	end
	for setNum, set in pairs( DivineBlessing_UserConfig.Sets[ setType ] ) do
		set.type = setType
		set.id = setNum
		if ( not set.spells ) then set.spells = {} end
		if ( setInfo.isRaid ) then
			if ( not set.skipGroups ) then set.skipGroups = {} end
		else
			if ( set.skipGroups ) then set.skipGroups = nil end
		end
		if ( not set.options ) then
			set.options = { wait = 1, overrideFriendlyTarget = nil }
		end
		if ( setType == DivineBlessing.SETTYPE_PARTY ) then
			-- Slight change, party sets cannot use the Override Friendly Target rule,
			-- it never really made sense for party sets.  If you want that functionality,
			-- use a class set for drive-by buffing.
			set.options.overrideFriendlyTarget = nil
		end
		DivineBlessing.InitSetState( set )
	end
end

--[[
--	DivineBlessing.InitializeSettings()
--		Makes sure the user config is properly initialized
--]]

function DivineBlessing.InitializeSettings()
	if ( not DivineBlessing_UserConfig ) then DivineBlessing_UserConfig = {} end
	if ( not DivineBlessing_UserConfig.Sets ) then DivineBlessing_UserConfig.Sets = {} end
	if ( not DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_PARTY ] ) then DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_PARTY ] = {} end
	if ( not DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_CLASS ] ) then DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_CLASS ] = {} end
	if ( not DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_RAID ] ) then DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_RAID ] = {} end
	
	DivineBlessing.InitializeSets( DivineBlessing.SETTYPE_PARTY )
	DivineBlessing.InitializeSets( DivineBlessing.SETTYPE_CLASS )
	DivineBlessing.InitializeSets( DivineBlessing.SETTYPE_RAID )

	if ( not DivineBlessing_UserConfig.Options ) then DivineBlessing_UserConfig.Options = {} end
end

--[[
--	DivineBlessing.SetSpellId( table set, string targetIndex, number spellId, bool noPickup )
--		Sets the spell in a specific slot in the set to the specified spell
--
--	Args:
--		table set - the set to place the spell in
--		string targetIndex - the target index to place the spell in
--		number spellId - the id of the spell to place
--		bool noPickup - true if the current spell, if any, should not be placed on the cursor
--
--	Returns:
--		NO RETURN
--]]

function DivineBlessing.SetSpellId( set, targetIndex, spellId, noPickup )
	if ( not set ) then return nil end

	PickupSpell( MAX_SPELLS, BOOKTYPE_SPELL )
	
	local spellName = DivineBlessing.GetSpellName( spellId )

	if ( targetIndex == "ALL" ) then
		local id
		for id, targetIndex in pairs( DivineBlessing.TARGET_INDICES[ set.type ] ) do
			set.spells[ targetIndex ] = spellName
		end
	else
		local oldSpellName = set.spells[ targetIndex ]
		set.spells[ targetIndex ] = spellName

		if ( noPickup ) then return nil end

		if ( oldSpellName ) then
			local oldSpellId = DivineBlessing.GetSpellId( oldSpellName )
			if ( oldSpellId ) then PickupSpell( oldSpellId, BOOKTYPE_SPELL ) end
			return oldSpellId
		end
	end

	return nil
end

--[[ Spell Name / Id Functions ]]--

--[[
--	DivineBlessing.GetSpellName( number spellId )
--		Gets the name of a spell with a specific id (cleanly, no errors on invalid ids)
--
--	Args:
--		number spellId - the id of the spell
--
--	Returns:
--		string spellName - the name of the spell
--		string spellRank - the rank of the spell
--]]

function DivineBlessing.GetSpellName( spellId )
	if ( ( not spellId ) or ( spellId < 1 ) ) then return nil, nil end
	return GetSpellName( spellId, BOOKTYPE_SPELL )
end

--[[
--	DivineBlessing.GetSpellId( string spellName, string spellRank )
--		Gets the spellId of the specified spell
--
--	Args:
--		string spellName - the name of the spell
--		string spellRank - the rank of the spell (if not provided,
--			the highest rank of the spell will be returned)
--
--	Returns:
--		number spellId - the id of the spell, or nil if not found
--]]

function DivineBlessing.GetSpellId( spellName, spellRank )
	if ( not spellName ) then return nil end
	
	local id = 1
	local name, rank = GetSpellName( id, BOOKTYPE_SPELL )
	while ( name ) do
		while ( name == spellName ) do
			if ( ( spellRank ) and ( spellRank == rank ) ) then return id end
			id = id + 1
			name, rank = GetSpellName( id, BOOKTYPE_SPELL )
			if ( name ~= spellName ) then return id - 1 end
		end
		id = id + 1
		name, rank = GetSpellName( id, BOOKTYPE_SPELL )
	end

	return nil
end

--[[
--	DivineBlessing.GetSpellIdFromTexture( string spellTexture )
--		Gets the spellId of the specified spell
--
--	Args:
--		string spellTexture - the texture of the spell's icon
--
--	Returns:
--		number spellId - the id of the spell, or nil if not found
--]]

function DivineBlessing.GetSpellIdFromTexture( spellTexture )
	if ( not spellTexture ) then return nil end

	local id = 1
	local texture = GetSpellTexture( id, BOOKTYPE_SPELL )
	while ( texture ) do
		while ( texture == spellTexture ) do
			id = id + 1
			texture = GetSpellTexture( id, BOOKTYPE_SPELL )
			if ( texture ~= spellTexture ) then return id - 1 end
		end
		id = id + 1
		texture = GetSpellTexture( id, BOOKTYPE_SPELL )
	end

	return nil
end

--[[
--	DivineBlessing.GetBestSpellId( string targetId, string spellName, number maxRank )
--		Gets the spellId of the specified spell, capped based on the target's level
--
--	Args:
--		string targetId - the id of the target unit
--		string spellName - the name of the spell
--		number maxRank - the highest rank to check
--
--	Returns:
--		number spellId - the id of the spell, or nil if not found
--]]

function DivineBlessing.GetBestSpellId( targetId, spellName, maxRank )
	if ( not UnitExists( targetId ) ) then return nil end
	if ( ( not DivineBlessing.PlayerBuffs ) or ( not DivineBlessing.PlayerBuffs[ spellName ] ) ) then
		-- This isn't a spell we know details about, so just get the highest rank
		return DivineBlessing.GetSpellId( spellName )
	end

	local buff = DivineBlessing.PlayerBuffs[ spellName ]
	if ( not buff ) then return nil end

	local level = UnitLevel( targetId ) + DivineBlessing.SPELL_TARGET_LEVEL_OFFSET
	maxRank = math.min( maxRank or 99, table.getn( buff.ranks ) )

	local rank = maxRank	
	while ( ( buff.ranks[ rank ] > level ) and ( rank > 1 ) ) do
		rank = rank - 1
	end
	if ( rank == 0 ) then return nil end

	-- Get the best spell for this target	
	return DivineBlessing.GetSpellId( spellName, string.format( DIVINEBLESSING_FMT_SPELLRANK, rank ) )
end

--[[ Target Functions ]]--

--[[
--	DivineBlessing.GetTargetId( number index, bool isPet, bool isRaid )
--		Gets the targetId at the specified group index
--
--	Args:
--		number index - the 1-based group / raid index of the target
--		bool isPet - if true, the targetId is for the unit's pet
--		bool isRaid - if true, return a raid id, otherwise a party id
--
--	Returns:
--		string targetId - the targetId of the specified unit
--]]

function DivineBlessing.GetTargetId( index, isPet, isRaid )
	local targetId
	if ( isRaid ) then
		if ( DivineBlessing_UserConfig.Options.useGroupOrder ) then
			-- If we're targeting the raid in sequential order vs raid order,
			-- get the sequential target index instead
			index = DivineBlessing.GetSequentialRaidIndex( index )
		end
		if ( not index ) then return nil end
		targetId = "raid"
		if ( isPet ) then targetId = targetId.."pet" end
		targetId = targetId..tonumber( index )
	else
		if ( index == 1 ) then
			if ( isPet ) then
				targetId = "pet"
			else
				targetId = "player"
			end
		else
			targetId = "party"
			if ( isPet ) then targetId = targetId.."pet" end
			targetId = targetId..( tonumber( index ) - 1 )
		end
	end
	return targetId
end

--[[
--	DivineBlessing.ParseTargetId( string targetId )
--		Parses a targetId for information
--
--	Args:
--		string targetId - the targetId of the specified unit
--
--	Returns:
--		number index - the 1-based group / raid index of the target
--		bool isPet - if true, the targetId is for the unit's pet
--		bool isRaid - if true, return a raid id, otherwise a party id
--]]

function DivineBlessing.ParseTargetId( targetId )
	local index, isPet, isRaid = nil, nil, nil
	targetId = string.lower( targetId )
	
	if ( string.sub( targetId, 0, 4 ) == "raid" ) then
		isRaid = true
		targetId = string.sub( targetId, 5 )
	else
		isRaid = false
		targetId = string.sub( targetId, 6 )
	end

	if ( string.sub( targetId, 0, 3 ) == "pet" ) then
		isPet = true
		index = tonumber( string.sub( targetId, 4 ) )
	else
		isPet = false
		index = tonumber( targetId )
	end
	
	if ( not index ) then return nil, nil, nil end
	return index, isPet, isRaid
end

--[[
--	DivineBlessing.GetSequentialRaidIndex( number index )
--		Gets the sequentual target index of the player at the specified raid index
--		Sequential indices are in group order
--
--	Args:
--		number index - the 1-based raid index
--
--	Returns:
--		number sequentialIndex - the 1-based sequential target index
--]]

function DivineBlessing.GetSequentialRaidIndex( index )
	if ( not UnitInRaid( "player" ) ) then return nil end

	local sequentialIndex = 0

	local raidIndex
	local raidSize = GetNumRaidMembers()

	local currentGroup
	for currentGroup = 1, 8, 1 do
		for raidIndex = 1, raidSize, 1 do
			local _, _, subGroup = GetRaidRosterInfo( raidIndex )
			if ( subGroup == currentGroup ) then
				sequentialIndex = sequentialIndex + 1
				if ( sequentialIndex == index ) then
					return raidIndex
				end
			end
		end
	end

	return nil
end

--[[
--	DivineBlessing.GetRaidRosterInfo( number index )
--		A version of GetRaidRosterInfo that will obey the useGroupOrder rule
--
--	Args:
--		number index - the 1-based raid index
--
--	Returns:
--		GetRaidRosterInfo() - normal return values for the GetRaidRosterInfo() api call
--]]

function DivineBlessing.GetRaidRosterInfo( index )
	if ( DivineBlessing_UserConfig.Options.useGroupOrder ) then
		-- If we're targeting the raid in sequential order vs raid order,
		-- get the sequential target index instead
		index = DivineBlessing.GetSequentialRaidIndex( index )
	end
	if ( not index ) then return nil, 0, 1, 1, nil, nil, nil, nil, nil end
	return GetRaidRosterInfo( index )
end

--[[
--	DivineBlessing.UnitClass( string targetId )
--		An expanded UnitClass() method that returns a single localized
--		unit class for both players and pets
--
--	Args:
--		string targetId - the targetId of the unit to find the class for
--
--	Returns:
--		string unitClass - the localized class of the target unit
--]]

function DivineBlessing.UnitClass( targetId )
	if ( not targetId ) then return nil end

	local _, class = UnitClass( targetId )

	local creatureType = UnitCreatureType( targetId )
	if ( ( creatureType == DIVINEBLESSING_CREATURE_TYPE_BEAST ) and
		 ( class ~= "DRUID" ) ) then	-- Shapeshifted druids are not pets!
		return "BEAST"
	elseif ( creatureType == DIVINEBLESSING_CREATURE_TYPE_DEMON ) then
		local family = UnitCreatureFamily( targetId )
		if ( family ) then
			local type = DIVINEBLESSING_CREATURE_FAMILIES_DEMONS[ family ]
			if ( type ) then return type end
		end
	end
		
	-- If we're here, just return the normal unit class
	return class
end

--[[
--	DivineBlessing.UnitGroup( string targetId )
--		Returns the group number of the specified target
--
--	Args:
--		string targetId - the targetId of the unit to find the group for
--
--	Returns:
--		number unitGroup - the group number of the specified target
--]]

function DivineBlessing.UnitGroup( targetId )
	local index, isPet, isRaid = DivineBlessing.ParseTargetId( targetId )
	if ( not isRaid ) then
		return 1
	else
		local _, _, subGroup = GetRaidRosterInfo( index )
		return subGroup
	end
end

--[[
--	DivineBlessing.FindAlternateTarget( number spellId, string targetId, number setSize, bool isRaid )
--		Attempts to find an alternate valid target to cast a group/class buff on
--
--	Args:
--		number spellId - the spellId to check range on
--		string targetId - the target to check range to
--		number setSize - the size of the blessing set
--		bool isRaid - true if this is a raid set, false otherwise
--
--	Returns:
--		string targetId - a valid alternate targetId, the input targetId if none was found
--]]

function DivineBlessing.FindAlternateTarget( spellId, targetId, setSize, isRaid )
	if ( not DivineBlessing.PlayerBuffs ) then return targetId end

	local spellName = GetSpellName( spellId, BOOKTYPE_SPELL )
	local buff = DivineBlessing.PlayerBuffs[ spellName ]
	if ( ( not buff ) or ( not buff.target ) ) then return targetId end
	
	local compare = nil
	if ( buff.target == DivineBlessing.TARGET_CLASS ) then
		compare = DivineBlessing.UnitClass( targetId )
	elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
		compare = DivineBlessing.UnitGroup( targetId )
	end

	local index
	for index = 1, setSize, 1 do
		local currentTargetId = DivineBlessing.GetTargetId( index, false, isRaid )
		local value = nil
		if ( buff.target == DivineBlessing.TARGET_CLASS ) then
			value = DivineBlessing.UnitClass( currentTargetId )
		elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
			local _, _, subGroup = DivineBlessing.GetRaidRosterInfo( index )
			value = subGroup
		end
		
		if ( value == compare ) then
			if ( DivineBlessing.SpellCanTargetUnit( currentTargetId ) ) then
				return currentTargetId
			end
		end
	end
	
	return targetId
end

--[[
--	DivineBlessing.SpellCanTargetUnit( string targetId )
--		Returns true if the specified target is valid for the active spell
--		(Wraps SpellCanTargetUnit to add in a UnitIsDead check for some 1.10 strangeness)
--
--	Args:
--		string targetId - the targetId to check
--
--	Returns:
--		bool canTarget - true if the target is valid, false otherwise
--]]

function DivineBlessing.SpellCanTargetUnit( targetId )
	return ( ( SpellCanTargetUnit( targetId ) ) and ( not UnitIsDead( targetId ) ) )
end

--[[
--	DivineBlessing.TargetHasBuff( string targetId, string buffName )
--		Checks to see if the target with the specified id has a buff with the specified name
--
--	Args:
--		string targetId - the targetId to check
--		string buffName - the name of the buff to look for
--
--	Returns:
--		bool hasBuff - true if the target has the buff, false otherwise
--]]

function DivineBlessing.TargetHasBuff( targetId, buffName )
	local buffId = 1
	while ( UnitBuff( targetId, buffId ) ) do
		DivineBlessingTooltip:SetUnitBuff( targetId, buffId )
		local currentBuffName = DivineBlessingTooltipTextLeft1:GetText()
		if ( currentBuffName == buffName ) then return true end
		buffId = buffId + 1
	end
	return false
end

--[[
--	DivineBlessing.Bless( string stateId )
--		Casts the next spell in the set associated with the provided stateId
--
--	Args:
--		string stateId - the id of the state to cast from
--
--	Returns:
--		NO RETURN
--]]
-- Doesn't work anymore, due to 2.0 changes
--function DivineBlessing.Bless( stateId )
--	if ( UnitOnTaxi( "player" ) ) then return end
--	if ( not stateId ) then return end
--
--	local setState = DivineBlessing.State[ stateId ]
--	if ( not setState ) then return end
--
--	local targetId = setState:CastNextSpell()
--	if ( targetId ) then
--		DivineBlessing.CastingState = setState
--	else
--		DivineBlessing.CastingState = nil
--	end
--end

----------------------------------------------------------------------
-- User Interface Methods
----------------------------------------------------------------------

--[[
--	DivineBlessing.ToggleFrame()
--		Toggles the display of DivineBlessingFrame
--]]

function DivineBlessing.ToggleFrame()
	if ( DivineBlessingFrame:IsVisible() ) then
		HideUIPanel( DivineBlessingFrame )
	else
		ShowUIPanel( DivineBlessingFrame )
	end
end

function DivineBlessing.KeyBindOnLoad( self )
	self:RegisterForClicks( "AnyUp" )

	local bindings = {}

	for set = 1, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_PARTY ].size do
		table.insert( bindings, { "Bless Party "..set, "CLICK DivineBlessingFrameParty"..set.."CastButton:LeftButton" }	)
	end

	for set = 1, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_CLASS ].size do
		table.insert( bindings, { "Bless Class "..set, "CLICK DivineBlessingFrameClass"..set.."CastButton:LeftButton" } )
	end

	for set = 1, DivineBlessing.SET_INFO[ DivineBlessing.SETTYPE_RAID ].size do
		table.insert( bindings, { "Bless Raid "..set, "CLICK DivineBlessingFrameRaid"..set.."CastButton:LeftButton" } )
	end
	
	self.bindingInfo = bindings
end

function DivineBlessing.KeyBindOnClick( self )
	ClickBinder.Toggle( "Divine Blessing", self.bindingInfo )
end

function DivineBlessing.CastButtonOnLoad( self )
	self:EnableMouse( true )
	self:RegisterForClicks( "LeftButtonUp", "RightButtonUp" )
	self:SetAttribute( "type", "spell" )
end

function DivineBlessing.CastButtonPreClick( self )
	if ( InCombatLockdown() ) then
		DivineBlessing.Message( "In combat -- autocasting functionality blocked." )
		return
	end
	
	if ( UnitOnTaxi( "player" ) ) then return end
	
	local parent = self:GetParent()
	local stateId = parent.type..parent:GetID()
	
	local setState = DivineBlessing.State[ stateId ]
	if ( not setState ) then return end

	local spellId = setState:GetCurrentSpellId()
	if ( not spellId ) then
		local spellId = setState:GetNextSpellId()
		if ( not spellId ) then return end
	end

	local spellName = GetSpellName( spellId, BOOKTYPE_SPELL )
	local targetId = setState:GetCurrentTargetId()
		
	if ( targetId ) then
		self:SetAttribute( "spell", spellName )
		self:SetAttribute( "unit", targetId )
		setState:SetLastSpell( spellId, targetId, retarget )
		DivineBlessing.CastingState = setState
	else
		DivineBlessing.CastingState = nil
	end
end

function DivineBlessing.CastButtonPostClick( self )
	self:SetAttribute( "spell", nil )
	self:SetAttribute( "unit", nil)
end

--[[
--	DivineBlessing.TabOnClick( table tab )
--		Generic tab OnClick handler
--]]

function DivineBlessing.TabOnClick( tab )
	if ( ( not tab ) or ( not tab.tabFrame ) ) then return end

	local frames = { tab:GetParent():GetChildren() }
	local index
	for index = 1, table.getn( frames ), 1 do
		local frame = frames[ index ].tabFrame
		if ( frame ) then
			if ( frame == tab.tabFrame ) then
				frame:Show()
				PanelTemplates_SetTab( tab:GetParent(), tab:GetID() )
			else
				frame:Hide()
			end
		end
	end
end

--[[
--	DivineBlessing.PartySetOnLoad( table frame )
--		OnLoad handler for the party set
--]]

function DivineBlessing.PartySetOnLoad( frame )
	frame.type = DivineBlessing.SETTYPE_PARTY
end

--[[
--	DivineBlessing.PartySetOnShow( table frame )
--		OnShow handler for the party set
--]]

function DivineBlessing.PartySetOnShow( frame )
	getglobal( frame:GetName().."Label" ):SetText( frame:GetID() )
end

--[[
--	DivineBlessing.ClassSetOnLoad( table frame )
--		OnLoad handler for the class set frame
--]]

function DivineBlessing.ClassSetOnLoad( frame )
	frame.type = DivineBlessing.SETTYPE_CLASS
	local name = frame:GetName()
	
	for raw, localized in pairs( DIVINEBLESSING_DIALOG_CLASS_BUTTON_LABELS ) do
		local label = getglobal( name..raw.."Label" )
		if ( label ) then label:SetText( localized ) end
	end
	
--	if ( UnitFactionGroup("player") == "Horde" ) then
--		getglobal( name.."PALADIN" ):Hide()
--		getglobal( name.."SHAMAN" ):Show()
--	end
end

--[[
--	DivineBlessing.ClassSetOnLoad( table frame )
--		OnShow handler for the class set frame
--]]

function DivineBlessing.ClassSetOnShow( frame )
	local name = DIVINEBLESSING_DIALOG_CLASS_BLESSING
	if ( frame:GetParent():GetID() > 1 ) then
		name = name..string.format( " #%d", frame:GetID() )
	end
	getglobal( frame:GetName().."BlessingLabel" ):SetText( name )
end

--[[
--	DivineBlessing.RaidSetOnLoad( table frame )
--		OnLoad handler for the raid set frame
--]]

function DivineBlessing.RaidSetOnLoad( frame )
	DivineBlessing.ClassSetOnLoad( frame )
	frame.type = DivineBlessing.SETTYPE_RAID
	getglobal( frame:GetName().."BlessingLabel" ):SetText( DIVINEBLESSING_DIALOG_RAID_BLESSING )
end

--[[
--	DivineBlessing.RaidSetOnShow( table frame )
--		OnShow handler for the raid set frame
--]]

function DivineBlessing.RaidSetOnShow( frame )
	local name = DIVINEBLESSING_DIALOG_RAID_BLESSING
	local count = frame:GetParent():GetID() or 1
	if ( frame:GetParent():GetID() > 1 ) then
		name = name..string.format( " #%d", frame:GetID() )
	end
	getglobal( frame:GetName().."BlessingLabel" ):SetText( name )
end

--[[
--	DivineBlessing.GroupTargetCheckButtonOnLoad( table button )
--		OnLoad handler for group target buttons
--]]

function DivineBlessing.GroupTargetCheckButtonOnLoad( button )
	local text = getglobal( button:GetName().."Text" )
	text:SetText( string.format( DIVINEBLESSING_DIALOG_FMT_GROUP, button:GetID() ) )
	text:SetTextColor( 1.0, 1.0, 1.0 )
end

--[[
--	DivineBlessing.GroupTargetCheckButtonOnClick( table button )
--		OnClick handler for group target buttons
--]]

function DivineBlessing.GroupTargetCheckButtonOnClick( button )
	local setNum = button:GetParent():GetID()
	if ( not setNum ) then return end
	local set = DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_RAID ][ setNum ]	
	if ( not set ) then return end
	local groupNum = button:GetID()
	if ( not groupNum ) then return end

	if ( not set.skipGroups ) then set.skipGroups = {} end
	set.skipGroups[ groupNum ] = button:GetChecked()
end

--[[
--	DivineBlessing.GroupTargetCheckButtonOnShow( table button )
--		OnShow handler for group target buttons
--]]

function DivineBlessing.GroupTargetCheckButtonOnShow( button )
	local setNum = button:GetParent():GetID()
	if ( not setNum ) then return end
	local set = DivineBlessing_UserConfig.Sets[ DivineBlessing.SETTYPE_RAID ][ setNum ]	
	if ( not set ) then return end
	local groupNum = button:GetID()
	if ( not groupNum ) then return end

	if ( not set.skipGroups ) then set.skipGroups = {} end
	button:SetChecked( not set.skipGroups[ groupNum ] )
end

--[[
--	DivineBlessing.MenuButtonOnLoad( table menu )
--		OnLoad handler for the menu
--]]

function DivineBlessing.MenuOnLoad( menu )
	menu:SetBackdropBorderColor( 0.8, 0.8, 0.8 )
	menu:SetBackdropColor( 0.09, 0.09, 0.19, 0.95 )
end

--[[
--	DivineBlessing.MenuButtonOnEnter( table button )
--		OnEnter handler for the menu buttons
--]]

function DivineBlessing.MenuButtonOnEnter( button )
	getglobal( button:GetName().."Highlight" ):Show()
end

--[[
--	DivineBlessing.MenuButtonOnLeave( table button )
--		OnLeave handler for the menu buttons
--]]

function DivineBlessing.MenuButtonOnLeave( button )
	getglobal( button:GetName().."Highlight" ):Hide()
end

--[[
--	DivineBlessing.MenuButtonOnClick( table button )
--		OnClick handler for the menu buttons
--]]

function DivineBlessing.MenuButtonOnClick( button )
	button.menu.callback( button.value or button:GetText(), button.menu.arguments )
	DivineBlessing.CloseMenu()
end

--[[
--	DivineBlessing.DisplayMenu( string anchor, function callback, object arguments, table items, number offsetX, number offsetY, bool right )
--		Displays the menu, with the specified settings
--
--	Args:
--		string anchor - the name of the frame to anchor the menu to
--		function callback - OnClick callback function for menuitems, of prototype:
--								function( string text, object arguments ) ... end
--		object arguments - arguments to pass to the OnClick callback function
--		table items - a table of the items to display in the menu, in the format of:
--			{ string name, string type, string value }
--				name - the text to display in the menu
--				type - menu item type: "normal", "heading", "disabled"
--				value - the value to pass to the callback (otherwise, name is returned)
--		number offsetX - the x offset for aligning the menu to the anchor
--		number offsetY - the y offset for aligning the menu to the anchor
--		bool right - if true, the menu will align its right side to the anchor, otherwise
--						it will align its left side to it
--	Return:
--		NO RETURN
--]]

function DivineBlessing.DisplayMenu( anchor, callback, arguments, items, offsetX, offsetY, right )
	local menu, count, widest = DivineBlessingMenu, table.getn( items ), 0
	for i = 1, count, 1 do
		local button, item, icon = getglobal( menu:GetName().."Button"..i ), items[ i ], getglobal( menu:GetName().."Button"..i.."IconTexture" )
		button.menu = menu
		button:SetText( item[ 1 ] )
		icon:Hide()
		if ( item[ 2 ] == "normal" ) then
			button.value = item[ 3 ]
			local spellId = DivineBlessing.GetSpellId( item[ 1 ] )
			if ( spellId ) then
				icon:Show()
				icon:SetTexture( GetSpellTexture( spellId, BOOKTYPE_SPELL ) )
			end
			button:SetTextColor( 1.0, 0.82, 0.0 )
			button:Enable()
		elseif ( item[ 2 ] == "heading" ) then
			button:Disable()
			button:SetDisabledTextColor( 1.0, 1.0, 1.0 )
		elseif ( item[ 2 ] == "disabled" ) then
			button:Disable()
			button:SetDisabledTextColor( 0.5, 0.5, 0.5 )
		end
		button:Show()
		widest = math.max( widest, button:GetTextWidth() )
	end

	-- Ensure all displayed buttons are of the proper width
	local width = widest + DivineBlessing.MENU_BUTTON_PADDING
	for i = 1, count, 1 do
		getglobal( menu:GetName().."Button"..i ):SetWidth( width )
		getglobal( menu:GetName().."Button"..i.."Highlight" ):SetWidth( width )
	end

	-- Hide the remaining buttons
	for i = count + 1, DivineBlessing.MENU_MAX_BUTTONS, 1 do
	    local button = getglobal( menu:GetName().."Button"..i )
	    button:Hide()
	end

	menu.callback, menu.arguments, menu.anchor = callback, arguments, anchor
	menu:SetHeight( ( count * DivineBlessing.MENU_BUTTON_HEIGHT ) + DivineBlessing.MENU_VERTICAL_PADDING )
	menu:SetWidth( width + DivineBlessing.MENU_HORIZ_PADDING )

	menu:ClearAllPoints()
	menu:SetPoint( right and "TOPRIGHT" or "TOPLEFT", anchor, "TOPLEFT", offsetX or 0, offsetY or 0 )
	menu:Show()
end

--[[
--	DivineBlessing.ToggleMenu( string anchor, function callback, object arguments, table items, number offsetX, number offsetY, bool right )
--		As DivineBlessing.DisplayMenu, but also hides the menu if it is visible
--
--	Args:
--		string anchor - the name of the frame to anchor the menu to
--		function callback - OnClick callback function for menuitems, of prototype:
--								function( string text, object arguments ) ... end
--		object arguments - arguments to pass to the OnClick callback function
--		table items - a table of the items to display in the menu, in the format of:
--			{ string name, string type, string value }
--				name - the text to display in the menu
--				type - menu item type: "normal", "heading", "disabled"
--				value - the value to pass to the callback (otherwise, name is returned)
--		number offsetX - the x offset for aligning the menu to the anchor
--		number offsetY - the y offset for aligning the menu to the anchor
--		bool right - if true, the menu will align its right side to the anchor, otherwise
--						it will align its left side to it
--	Return:
--		NO RETURN
--]]

function DivineBlessing.ToggleMenu( anchor, callback, arguments, items, offsetX, offsetY, right )
    local menu = getglobal( "DivineBlessingMenu" )
    if menu:IsVisible() and menu.anchor == anchor then
        DivineBlessing.CloseMenu()
    else
        DivineBlessing.CloseMenu()
        DivineBlessing.DisplayMenu( anchor, callback, arguments, items, offsetX, offsetY, right )
    end
end

--[[
--	DivineBlessing.CloseMenu()
--		Hide the menu
--]]

function DivineBlessing.CloseMenu()
	DivineBlessingMenu:Hide()
end

--[[
--	DivineBlessing.SetScrollFrameOnLoad( table frame, number numSets )
--		OnLoad handler for the party/class/raid set scroll frame template
--]]

function DivineBlessing.SetScrollFrameInit( frame, numSets )
	if ( not frame ) then return end
	frame:SetID( numSets or 1 )
	if ( ( not numSets ) or ( numSets <= 1 ) ) then return end
	
	local children = { frame:GetChildren() }
	if ( not children ) then return end

	if ( children[ table.getn( children ) ]:GetID() == numSets ) then return end
	local button
	button = getglobal( frame:GetName().."ScrollUpButton" )
	button:Show()
	button:Disable()
	
	button = getglobal( frame:GetName().."ScrollDownButton" )
	button:Show()
	button:Enable()
end

--[[
--	DivineBlessing.SetScrollButtonOnLoad( table button )
--		OnLoad handler for the party/class/raid set scroll frame template scroll buttons
--]]

function DivineBlessing.SetScrollButtonOnLoad( button )
	button:SetFrameLevel( button:GetParent():GetFrameLevel() + 10 )
	button:Disable()
	button:Hide()
end

--[[
--	DivineBlessing.SetScrollUpOnClick( table button )
--		OnClick handler for the party/class/raid set scroll frame template ScrollUp button
--]]

function DivineBlessing.SetScrollUpOnClick( button )
	if ( not button ) then return end
	
	local frame = button:GetParent()
	local children = { frame:GetChildren() }
	if ( not children ) then return end

	-- The first two child frames are the scroll up / down buttons
	local index
	for index = 3, table.getn( children ), 1 do
		local child = children[ index ]
		local id = child:GetID()
		if ( id > 1 ) then
			id = id - 1
			child:SetID( id )
			child:Hide()
			child:Show()
		end
		if ( id == 1 ) then button:Disable() end
	end

	getglobal( frame:GetName().."ScrollDownButton" ):Enable()
end

--[[
--	DivineBlessing.SetScrollDownOnClick( table button )
--		OnClick handler for the party/class/raid set scroll frame template ScrollDown button
--]]

function DivineBlessing.SetScrollDownOnClick( button )
	if ( not button ) then return end
	
	local frame = button:GetParent()
	local numSets = frame:GetID()
	if ( not numSets ) then return end

	local children = { frame:GetChildren() }
	if ( not children ) then return end

	-- The first two child frames are the scroll up / down buttons
	local index
	for index = 3, table.getn( children ), 1 do
		local child = children[ index ]
		local id = child:GetID()
		if ( id < numSets ) then
			id = id + 1
			child:SetID( id )
			child:Hide()
			child:Show()
		end
		if ( id == numSets ) then button:Disable() end
	end

	getglobal( frame:GetName().."ScrollUpButton" ):Enable()
end

--[[
--	DivineBlessing.SpellButtonGetInfo( table button )
--		Returns identifying information about the specified spellbutton
--
--	Args:
--		table button - the spellbutton to query
--
--	Return:
--		table set - the set this button is associated with
--		string targetIndex - the targetIndex this button is associated with
--		string setType - the set type this button is associated with
--		number setNum - the set number for this set
--]]

function DivineBlessing.SpellButtonGetInfo( button )
	if ( not button ) then return nil, nil, nil, nil end
	local setType = button:GetParent().type
	local setNum = button:GetParent():GetID()
	local set = DivineBlessing_UserConfig.Sets[ setType ][ setNum ]
	local targetIndex = string.sub( button:GetName(), string.len( button:GetParent():GetName() ) + 1 )
	return set, targetIndex, setType, setNum
end

--[[
--	DivineBlessing.SpellButtonSetSpellId( table button, number spellId )
--		Put a specified spell into the specified button
--
--	Args:
--		table button - the spellbutton to place the spell into
--		number spellId - the spellId to place in the button
--
--	Return:
--		NO RETURN
--]]

function DivineBlessing.SpellButtonSetSpellId( button, spellId )
	local set, targetIndex = DivineBlessing.SpellButtonGetInfo( button )
	if ( ( not set ) or ( not targetIndex ) ) then return end
	DivineBlessing.CursorSpellId = DivineBlessing.SetSpellId( set, targetIndex, spellId )
end

--[[
--	DivineBlessing.SpellButtonOnLoad( table button )
--		OnLoad handler for spell icon buttons
--]]

function DivineBlessing.SpellButtonOnLoad( button )
	if ( not button ) then return end
	button:RegisterForClicks( "LeftButtonUp", "RightButtonUp" )
end

--[[
--	DivineBlessing.SpellButtonOnShow( table button )
--		OnShow handler for spell icon buttons
--]]

function DivineBlessing.SpellButtonOnShow( button )
	if ( not button ) then return end
	getglobal( button:GetName().."Label" ):SetWidth( 50 )
end

--[[
--	DivineBlessing.SpellButtonOnUpdate( table button )
--		OnUpdate handler for spell icon buttons
--]]

function DivineBlessing.SpellButtonOnUpdate( button )
	local set, targetIndex, setType, setNum = DivineBlessing.SpellButtonGetInfo( button )
	if ( ( not set ) or ( not targetIndex ) ) then return end

	button:SetChecked( false )

	if ( targetIndex == "ALL" ) then
		-- Special case for the "Set All" buttons
		local icon = getglobal( button:GetName().."Icon" )
		icon:SetTexture( DivineBlessing.TEXTURE_SET_ALL )
		
		local color = DivineBlessing.TEXTURE_SET_ALL_COLOR
		icon:SetVertexColor( color.r, color.g, color.b )
		icon:SetAlpha( color.a )
		return
	end

	if ( setType == DivineBlessing.SETTYPE_PARTY ) then
		local setState = DivineBlessing.State[ setType..setNum ]
		if ( setState ) then
			if ( UnitIsUnit( setState:GetCurrentTargetId(), targetIndex ) ) then
				button:SetChecked( true )
			end
		end
	end

	local spellId = nil
	local targetSpell = set.spells[ targetIndex ]
	if ( targetSpell ) then spellId = DivineBlessing.GetSpellId( targetSpell ) end

	-- Set the spell texture	
	if ( spellId ) then
		local color = DivineBlessing.FONT_COLOR_ENABLED_BUTTON
		if ( ( setType == DivineBlessing.SETTYPE_PARTY ) and ( not UnitExists( targetIndex ) ) ) then
			color = DivineBlessing.FONT_COLOR_DISABLED_BUTTON
		end
	
		local icon = getglobal( button:GetName().."Icon" )
		local texture = GetSpellTexture( spellId, BOOKTYPE_SPELL )
		if ( texture ) then
			icon:Show()
			icon:SetTexture( texture )
		else
			icon:Hide()
		end
		icon:SetVertexColor( color.r, color.g, color.b )
	elseif ( targetSpell ) then
		local icon = getglobal( button:GetName().."Icon" )
		icon:Show()
		icon:SetTexture( DivineBlessing.TEXTURE_UNKNOWN_SPELL )
	else
		getglobal( button:GetName().."Icon" ):Hide()
	end

	-- If this is a party set, set the target name
	if ( set.type == DivineBlessing.SETTYPE_PARTY ) then
		local unitName = UnitName( targetIndex )
		local color = DivineBlessing.FONT_COLOR_ENABLED_BUTTON
		if ( not UnitExists( targetIndex ) ) then
			color = DivineBlessing.FONT_COLOR_DISABLED_BUTTON
		end

		local label = getglobal( button:GetName().."Label" )
		label:SetText( unitName or DIVINEBLESSING_DIALOG_TARGET_NAMES[ targetIndex ] )
		label:SetTextColor( color.r, color.g, color.b )
	end
end

--[[
--	DivineBlessing.SpellMenuClick( string spellName, table args )
--		OnClick handler for the spell selection menu
--
--	Args:
--		string spellName - the name of the spell to set
--		table args - a table identifying where to set the spell:
--			{ table set, string targetIndex }
--				set - the set to place the spell into
--				targetIndex - the target index to place the spell into
--
--	Return:
--		NO RETURN
--]]

function DivineBlessing.SpellMenuClick( spellName, args )
	if ( ( not spellName ) or ( not args ) ) then return end
	if ( spellName == DIVINEBLESSING_MENU_CLOSE_MENU ) then return end

	local spellId = nil
	if ( spellName ~= DIVINEBLESSING_MENU_NO_SPELL ) then
		spellId = DivineBlessing.GetSpellId( spellName )
	end
	DivineBlessing.SetSpellId( args.set, args.targetIndex, spellId, true )
end

--[[
--	DivineBlessing.SpellButtonOnClick( table button, string mouseButton )
--		OnClick handler for the spell icon buttons
--]]

function DivineBlessing.SpellButtonOnClick( button, mouseButton )
	local set, targetIndex = DivineBlessing.SpellButtonGetInfo( button )
	if ( ( not set ) or ( not targetIndex ) ) then return end
	
	if ( not CursorHasSpell() ) then DivineBlessing.CursorSpellId = nil end

	if ( mouseButton == "RightButton" ) then
		DivineBlessing.CursorSpellId = nil

		-- Display the buff menu
		local spellNames = {}
		if ( DivineBlessing.PlayerBuffs ) then	
			for spellName in pairs( DivineBlessing.PlayerBuffs ) do
				local spellId = DivineBlessing.GetSpellId( spellName )
				if ( spellId ) then table.insert( spellNames, spellName ) end
			end
			table.sort( spellNames )
		end
		
		local menu = {}
		table.insert( menu, { DIVINEBLESSING_MENU_CLOSE_MENU, "normal" } )
		if ( table.getn( spellNames ) > 0 ) then
			table.insert( menu, { "", "heading" } )
			for key, spellName in pairs( spellNames ) do
				table.insert( menu, { spellName, "normal" } )
			end
			table.insert( menu, { "", "heading" } )
		end
		table.insert( menu, { DIVINEBLESSING_MENU_NO_SPELL, "normal" } )
		
		local args = { set = set, targetIndex = targetIndex }		
		DivineBlessing.ToggleMenu( this:GetName(), DivineBlessing.SpellMenuClick, args, menu, 36, 3 )
		return
	end

	DivineBlessing.CloseMenu()

	if ( IsAltKeyDown() ) then
		-- Set next index
	else
		-- Pick up this spell, possibly swapping it with the cursor's spell
		DivineBlessing.CursorSpellId = DivineBlessing.SetSpellId( set, targetIndex, DivineBlessing.CursorSpellId )
	end
end

--[[
--	DivineBlessing.SpellButtonOnReceiveDrag( table button )
--		OnReceiveDrag handler for the spell icon buttons
--]]

function DivineBlessing.SpellButtonOnReceiveDrag( button )
	-- Drop the carried spell into the button, and pick up whatever is there now
	DivineBlessing.SpellButtonSetSpellId( button, DivineBlessing.CursorSpellId )
end

--[[
--	DivineBlessing.SpellButtonOnEnter( table button )
--		OnEnter handler for the spell icon buttons
--]]

function DivineBlessing.SpellButtonOnEnter( button )
	local set, targetIndex = DivineBlessing.SpellButtonGetInfo( button )
	if ( ( not set ) or ( not targetIndex ) ) then return end

	local spellName = set.spells[ targetIndex ]
	local spellId = DivineBlessing.GetSpellId( spellName )
	if ( spellId ) then
		GameTooltip:SetOwner( button, "ANCHOR_RIGHT" )
		GameTooltip:SetSpell( spellId, BOOKTYPE_SPELL )
	elseif ( spellName ) then
		GameTooltip:ClearLines()
		GameTooltip:SetOwner( button, "ANCHOR_RIGHT" )
		
		local color = DivineBlessing.FONT_COLOR_UNKNOWN_NAME
		GameTooltip:AddLine( spellName, color.r, color.g, color.b )
		color = DivineBlessing.FONT_COLOR_UNKNOWN_MESSAGE
		GameTooltip:AddLine( DIVINEBLESSING_DIALOG_UNKNOWN_SPELL, color.r, color.g, color.b )
		GameTooltip:Show()
	end
end

--[[
--	DivineBlessing.SpellButtonOnLeave( table button )
--		OnLeave handler for the spell icon buttons
--]]

function DivineBlessing.SpellButtonOnLeave( button )
	GameTooltip:Hide()
end

--[[
--	DivineBlessing.ClearButtonOnClick( table button )
--		OnClick handler for the clear button
--]]

function DivineBlessing.ClearButtonOnClick( button )
	local set = DivineBlessing.SpellButtonGetInfo( button )
	if not set then return end

	set.spells = {}	
	if ( set.type == DivineBlessing.SETTYPE_RAID ) then
		set.skipGroups = {}
	else
		set.skipGroups = nil
	end
	set.options = {}

	button:GetParent():Hide()
	button:GetParent():Show()
end

--[[
--	DivineBlessing.ResetButtonOnClick( table button )
--		OnClick handler for the reset button
--]]

function DivineBlessing.ResetButtonOnClick( button )
	if ( not button ) then return end

	local setType = button:GetParent().type
	if ( not setType ) then return end
	local setNum = button:GetParent():GetID()
	if ( not setNum ) then return end

	local setState = DivineBlessing.State[ setType..setNum ]
	if ( not setState ) then return end
	
	setState:Reset()
end

--[[
--	DivineBlessing.OverrideCheckButtonOnLoad( table button )
--		OnLoad handler for the override check button
--]]

function DivineBlessing.OverrideCheckButtonOnLoad( button )
	getglobal( this:GetName().."Text" ):SetText( DIVINEBLESSING_DIALOG_BUTTON_OVERRIDE )
end

--[[
--	DivineBlessing.OverrideCheckButtonOnClick( table button )
--		OnClick handler for the override check button
--]]

function DivineBlessing.OverrideCheckButtonOnClick( button )
	local set = DivineBlessing.SpellButtonGetInfo( button )
	if not set then return end

	set.options.overrideFriendlyTarget = button:GetChecked()	
end

--[[
--	DivineBlessing.OverrideCheckButtonOnShow( table button )
--		OnShow handler for the override check button
--]]

function DivineBlessing.OverrideCheckButtonOnShow( button )
	local set = DivineBlessing.SpellButtonGetInfo( button )
	if not set then return end

	button:SetChecked( set.options.overrideFriendlyTarget )	
end

--[[
--	DivineBlessing.WaitCheckButtonOnLoad( table button )
--		OnLoad handler for the wait check button
--]]

function DivineBlessing.WaitCheckButtonOnLoad( button )
	getglobal( this:GetName().."Text" ):SetText( DIVINEBLESSING_DIALOG_BUTTON_WAIT )
end

--[[
--	DivineBlessing.WaitCheckButtonOnClick( table button )
--		OnClick handler for the wait check button
--]]

function DivineBlessing.WaitCheckButtonOnClick( button )
	local set = DivineBlessing.SpellButtonGetInfo( button )
	if not set then return end

	set.options.wait = button:GetChecked()	
end

--[[
--	DivineBlessing.WaitCheckButtonOnShow( table button )
--		OnShow handler for the wait check button
--]]

function DivineBlessing.WaitCheckButtonOnShow( button )
	local set = DivineBlessing.SpellButtonGetInfo( button )
	if not set then return end

	button:SetChecked( set.options.wait )
end

--[[
--	DivineBlessing.OptionsCheckButtonOnLoad( table button )
--		OnLoad handler for the option check buttons
--]]

function DivineBlessing.OptionsCheckButtonOnLoad( button )
	local optionData = DivineBlessing.Options[ button:GetID() ]
	if ( not optionData ) then return end
	getglobal( button:GetName().."Text" ):SetText( optionData.label )
end

--[[
--	DivineBlessing.OptionsCheckButtonOnShow( table button )
--		OnShow handler for the option check buttons
--]]

function DivineBlessing.OptionsCheckButtonOnShow( button )
	local optionData = DivineBlessing.Options[ button:GetID() ]
	if ( not optionData ) then return end
	button:SetChecked( DivineBlessing_UserConfig.Options[ optionData.option ] )
end

--[[
--	DivineBlessing.OptionsCheckButtonOnClick( table button )
--		OnClick handler for the option check buttons
--]]

function DivineBlessing.OptionsCheckButtonOnClick( button )
	local optionData = DivineBlessing.Options[ button:GetID() ]
	if ( not optionData ) then return end
	DivineBlessing_UserConfig.Options[ optionData.option ] = button:GetChecked()
end
