if ( not DivineBlessing ) then DivineBlessing = {} end

-- Default set state
DivineBlessing.SetState =
	{
		index = 1,
		pet = nil,
		set = nil,
		lastSpellId = nil,
		lastTargetId = nil,
		retarget = nil,
		history = {},
	}
local SetState = DivineBlessing.SetState

--[[
--	SetState:New( table set )
--		Creates a new SetState around the provided set
--
--	Args:
--		table set - the set to store state for
--
--	Return:
--		table newObj - the new SetState
--]]

function SetState:New( set )
	local newObj = {}
	setmetatable( newObj, { __index = self } )
	newObj.set = set
	return newObj
end

--[[
--	SetState:Reset()
--		Resets the index of this state to the beginning
--]]

function SetState:Reset()
	self.index = 1
	self.pet = nil
	self.history = {}
	self:SetLastSpell( nil, nil, nil )
end

--[[
--	SetState:atBeginning()
--		Is this state at the beginning?
--
--	Args:
--		NO ARGS
--
--	Return:
--		bool atBeginning - true if the set is at the beginning, false otherwise
--]]

function SetState:atBeginning()
	return ( ( self.index == 1 ) and ( self.pet == nil ) )
end

--[[
--	SetState:GetName()
--		Returns the formatted name of this set
--
--	Args:
--		NO ARGS
--
--	Return:
--		string name - the name of this set
--]]

function SetState:GetName()
	if ( self.set.type == DivineBlessing.SETTYPE_PARTY ) then
		return string.format( DIVINEBLESSING_FMT_SETNAME_PARTY, self.set.id )
	elseif ( self.set.type == DivineBlessing.SETTYPE_CLASS ) then
		return string.format( DIVINEBLESSING_FMT_SETNAME_CLASS, self.set.id )
	elseif ( self.set.type == DivineBlessing.SETTYPE_RAID ) then
		return string.format( DIVINEBLESSING_FMT_SETNAME_RAID, self.set.id )
	else
		return string.format( DIVINEBLESSING_FMT_SETNAME_UNKNOWN, self.set.id )
	end
end

--[[
--	SetState:GetSetSize()
--		Returns the actual size of this set (number of targets)
--
--	Args:
--		NO ARGS
--
--	Return:
--		number size - the number of targets for this set
--]]

function SetState:GetSetSize()
	if ( self.set.type == DivineBlessing.SETTYPE_RAID ) then 
		return GetNumRaidMembers()
	end
	return GetNumPartyMembers() + 1
end

--[[
--	SetState:GetTargetSpell( string targetId )
--		Get the spell associated with this target
--
--	Args:
--		string targetId - the targetId to get the spell for
--
--	Return:
--		string spellName - the name of the spell for this target
--]]

function SetState:GetTargetSpell( targetId )
	if ( ( self.set.type == DivineBlessing.SETTYPE_RAID ) and ( UnitInRaid( targetId ) ) ) then
		local index = DivineBlessing.ParseTargetId( targetId )
		-- We have a specific targetId, so use the normal GetRaidRosterInfo call
		local _, _, subGroup = GetRaidRosterInfo( index )
		if ( ( self.set.skipGroups ) and ( self.set.skipGroups[ subGroup ] ) ) then
			return nil
		end
	end

	local spellIndex = targetId
	if ( self.set.type ~= DivineBlessing.SETTYPE_PARTY ) then spellIndex = DivineBlessing.UnitClass( targetId ) end
	return self.set.spells[ spellIndex ]
end

--[[
--	SetState:MoveNext()
--		Move to the next spell in the set
--]]

function SetState:MoveNext()
	if ( self.pet ) then
		self.index = self.index + 1
		self.pet = nil
	else
		self.pet = 1
	end
	
	if ( self.index > self:GetSetSize() ) then
		self:Reset()
		return 1
	end

	return nil
end

--[[
--	SetState:GetCurrentTargetName()
--		Return the name of the current target
--
--	Args:
--		NO ARGS
--
--	Return:
--		string targetName - the name of the current target
--]]

function SetState:GetCurrentTargetName()
	-- Pets are always single-target casts
	if ( ( not self.pet ) and ( self.set.type == DivineBlessing.SETTYPE_RAID ) ) then
		-- If the current spell is group or class, the target is the group or class name
		local buff = self:GetCurrentSpellBuffInfo()
		if ( buff ) then
			if ( buff.target == DivineBlessing.TARGET_CLASS ) then
				local class = DivineBlessing.UnitClass( self:GetCurrentTargetId() )
				return DIVINEBLESSING_CLASS_NAMES_PLURAL[ class ]
			elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
				local _, _, subGroup = DivineBlessing.GetRaidRosterInfo( self.index )
				return string.format( DIVINEBLESSING_FMT_GROUP, subGroup )						
			end					
		end				
	end
	
	-- No special case, return the current target's name (and possibly owner)
	local name = UnitName( self:GetCurrentTargetId() ) or DIVINEBLESSING_UNKNOWN_TARGET
	if ( self.pet ) then
		local ownerId = DivineBlessing.GetTargetId( self.index, nil, self.set.type == DivineBlessing.SETTYPE_RAID )
		if ( ownerId ) then
			name = string.format( DIVINEBLESSING_FMT_PETNAME, name, UnitName( ownerId ) )
		end
	end
	return name
end

--[[
--	SetState:GetCurrentTargetId()
--		Return the targetId of the current target
--
--	Args:
--		NO ARGS
--
--	Return:
--		string targetId - the targetId of the current target
--]]

function SetState:GetCurrentTargetId()
	if ( ( self.set.options.overrideFriendlyTarget ) and ( UnitIsFriend( "target", "player" ) ) ) then
		-- Override is set, and the player's target is valid
		return "target"
	end

	local size = self:GetSetSize()
	if ( not size ) then return nil end

	if ( self.index < 1 ) then return nil end
	if ( self.index > size ) then return nil end

	return DivineBlessing.GetTargetId( self.index, self.pet, self.set.type == DivineBlessing.SETTYPE_RAID )
end

--[[
--	SetState:GetCurrentSpellBuffInfo()
--		Return the buff info for the current spell, if any
--
--	Args:
--		NO ARGS
--
--	Return:
--		table buff - buff information for the current spell
--]]

function SetState:GetCurrentSpellBuffInfo()
	local spellName = self:GetCurrentSpellName()
	if ( spellName ) then
		if ( DivineBlessing.PlayerBuffs ) then
			return DivineBlessing.PlayerBuffs[ spellName ]
		end
	end
	return nil
end

--[[
--	SetState:GetCurrentSpellName()
--		Return the name of the current spell
--
--	Args:
--		NO ARGS
--
--	Return:
--		string spellName - the name of the current spell, nil if not set or no target
--]]

function SetState:GetCurrentSpellName()
	local targetId = self:GetCurrentTargetId()
	if ( ( targetId ) and ( UnitExists( targetId ) ) ) then
		return self:GetTargetSpell( targetId )
	end
	return nil
end

--[[
--	SetState:GetCurrentSpellId()
--		Return the id of the current spell
--
--	Args:
--		NO ARGS
--
--	Return:
--		number spellId - the id of the current spell, nil if not set, invalid, or no target
--]]

function SetState:GetCurrentSpellId()
	local targetId = self:GetCurrentTargetId()
	if ( ( targetId ) and ( UnitExists( targetId ) ) ) then
		local spellName = self:GetTargetSpell( targetId )
		if ( spellName ) then
			return DivineBlessing.GetBestSpellId( targetId, spellName )
		end
	end
	return nil
end

--[[
--	SetState:GetNextSpellID()
--		Return the next valid spell id (spell is set, target exists)
--
--	Args:
--		NO ARGS
--
--	Return:
--		number spellId - the id of the next spell, nil if not set, or if none are found
--]]

function SetState:GetNextSpellId()
	local setSize = self:GetSetSize()
	local spellId = nil
	repeat
		if ( self:MoveNext() ) then break end
		if ( not self:isCurrentSpellRestricted() ) then
			spellId = self:GetCurrentSpellId()
		end
	until spellId
	
	return spellId
end

--[[
--	SetState:SetLastSpell( number spellId, string targetId, bool retarget )
--		Store information about the most-recently-cast spell and target
--
--	Args:
--		number spellId - the id of the most recent spell
--		string targetId - the targetId for the most recent cast spell
--		bool retarget - should the previous active target be reacquired after casting
--
--	Return:
--		NO RETURN
--]]

function SetState:SetLastSpell( spellId, targetId, retarget )
	self.lastSpellId = spellId
	self.lastTargetId = targetId
	self.retarget = retarget
end

--[[
--	SetState:CastNextSpell()
--		Cast the next valid spell in the set
--
--	Args:
--		NO ARGS
--
--	Return:
--		string targetId - targetId of the target if a spell was cast, nil otherwise
--]]
-- Doesn't work anymore due to 2.0 changes
--function SetState:CastNextSpell()
--	if ( InCombatLockdown() ) then
--		DivineBlessing.Message( "In combat -- autocasting functionality blocked." )
--		return
--	end
--
--	local spellId = self:GetCurrentSpellId()
--	if ( not spellId ) then
--		spellId = self:GetNextSpellId()
--		if ( not spellId ) then return nil end
--	end
--	
--	if ( GetSpellCooldown( spellId, BOOKTYPE_SPELL ) == 0 ) then
--		local retarget = false
--		if ( ( not self.set.options.overrideFriendlyTarget ) and ( UnitIsFriend( "target", "player" ) ) ) then
--			ClearTarget()
--			retarget = true
--		end
--		
----		local targetId = self:GetCurrentTargetId()
----
----		-- If we have any specified buffs, check range	
----		if ( DivineBlessing_UserConfig.Options.findAlternateTargets ) then
----			if ( DivineBlessing.PlayerBuffs ) then
----				if ( not DivineBlessing.IsSpellInRange( spellId, targetId ) ) then
----					-- Spell isn't in range of the target.  Check to see if there's any
----					-- other valid targets (class or group targeting spells) in range.
----					targetId = DivineBlessing.FindTargetInRange( spellId, targetId, self:GetSetSize() )
----				end
----			end
----		end
----
----		-- Cast the spell on the specified target		
----		CastSpell( spellId, BOOKTYPE_SPELL )
----		if ( SpellIsTargeting( spellId ) ) then SpellTargetUnit( targetId ) end
--
--		local targetId = self:GetCurrentTargetId()
--
--		-- Cast the spell on the specified target		
--		CastSpell( spellId, BOOKTYPE_SPELL )
--		if ( SpellIsTargeting( spellId ) ) then
--			-- If we have any specified buffs, check for target validity
----			if ( DivineBlessing_UserConfig.Options.findAlternateTargets ) then
--				if ( DivineBlessing.PlayerBuffs ) then
--					if ( not DivineBlessing.SpellCanTargetUnit( targetId ) ) then
--						-- We can't cast on this target (this can fail for multiple
--						-- reasons: hostile unit targeted, out of range, no line of sight)
--						-- All of these are good reasons to find an alternate target
--						-- assuming this is a multi-target spell
--						targetId = DivineBlessing.FindAlternateTarget( spellId, targetId, self:GetSetSize(), self.set.type == DivineBlessing.SETTYPE_RAID )
--					end
--				end
----			end
--
--			SpellTargetUnit( targetId )
--		end
--
--
--		-- Record cast state, and return
--		self:SetLastSpell( spellId, targetId, retarget )
--		return targetId
--	end
--
--	return nil
--end

--[[
--	SetState:RestrictSpell( number index, bool pet, table history, bool restrictAllTargets )
--		Add the spell at the specified index to the provided cast history table
--
--	Args:
--		number index - the target index of the spell
--		bool pet - true if the target is a pet, false otherwise
--		table history - the cast history table to populate
--		bool restrictAllTargets - true to assume the spell hit all targets,
--								  false to only restrict successful targets
--
--	Return:
--		NO RETURN
--]]

function SetState:RestrictSpell( index, pet, history, restrictAllTargets )
	if ( ( not index ) or ( not history ) ) then return end
	if ( not DivineBlessing.PlayerBuffs ) then return end
	if ( pet ) then return end	-- Currently pet spells can't be restricted

--	local targetId = DivineBlessing.GetTargetId( index, pet, ( self.set.type == DivineBlessing.SETTYPE_RAID ) )
	local targetId = DivineBlessing.GetTargetId( index, false, ( self.set.type == DivineBlessing.SETTYPE_RAID ) )
	if ( not targetId ) then return end
	
	local spellName = self:GetTargetSpell( targetId )
	if ( not spellName ) then return end

	-- Record the spell's target
	local buff = DivineBlessing.PlayerBuffs[ spellName ]
	if ( buff ) then
		if ( buff.target == DivineBlessing.TARGET_CLASS ) then
			history[ DivineBlessing.UnitClass( targetId ) ] = 1
		elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
			local _, _, subGroup = DivineBlessing.GetRaidRosterInfo( index )
			history[ "raidgroup"..subGroup ] = 1
		end
	end

	-- Now add specific targetIds to show who it *actually* hit.
	local missedTargets = nil
	if ( buff ) then
		missedTargets = {}
		if ( buff.target == DivineBlessing.TARGET_CLASS ) then
			local targetClass = DivineBlessing.UnitClass( targetId )

			local size = self:GetSetSize()
			local index
			for index = 1, size, 1 do
				local id = DivineBlessing.GetTargetId( index, false, true )
				local class = DivineBlessing.UnitClass( id )
				if ( class == targetClass ) then
					if ( ( restrictAllTargets ) or ( DivineBlessing.TargetHasBuff( id, spellName ) ) ) then
						history[ id ] = 1
					else
						table.insert( missedTargets, id )
					end
				end
			end
		elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
			local targetIndex = DivineBlessing.ParseTargetId( targetId )
			-- Use GetRaidRosterInfo here directly, this targetIndex is correct
			local _, _, targetGroup = GetRaidRosterInfo( targetIndex )

			local size = self:GetSetSize()
			local index
			for index = 1, size, 1 do
				local _, _, group = DivineBlessing.GetRaidRosterInfo( index )
				if ( group == targetGroup ) then
					local id = DivineBlessing.GetTargetId( index, false, true )
					if ( ( restrictAllTargets ) or ( DivineBlessing.TargetHasBuff( id, spellName ) ) ) then
						history[ id ] = 1
					else
						table.insert( missedTargets, id )
					end
				end
			end
		end
	end
	return missedTargets
end

--[[
--	SetState:isSpellRestricted( number index, bool pet, table history )
--		Check to see if the specified spell is marked already-cast in the history table
--
--	Args:
--		number index - the target index of the spell
--		bool pet - true if the target is a pet, false otherwise
--		table history - the cast history table to check
--
--	Return:
--		bool isRestricted - 1 if the specified spell is restricted, nil otherwise
--		number restrictionType - 0 if not restricted,
--								 1 if restricted by class/group, but not the targetId
--								 2 if restricted by class/group and the targetId
--]]

function SetState:isSpellRestricted( index, pet, history )
	if ( ( not index ) or ( not history ) ) then return nil end
	if ( not DivineBlessing.PlayerBuffs ) then return nil end
	if ( pet ) then return nil end	-- Currently pet spells can't be restricted

	local targetId = DivineBlessing.GetTargetId( index, pet, ( self.set.type == DivineBlessing.SETTYPE_RAID ) )
	local spellName = self:GetTargetSpell( targetId )

	local buff = DivineBlessing.PlayerBuffs[ spellName ]
	if ( buff ) then
		if ( buff.target == DivineBlessing.TARGET_CLASS ) then
			if ( history[ DivineBlessing.UnitClass( targetId ) ] ) then
				if ( ( DivineBlessing_UserConfig.Options.rebuffMissedTargets ) and ( not history[ targetId ] ) ) then
					return nil
				end
				return 1
			end
		elseif ( buff.target == DivineBlessing.TARGET_GROUP ) then
			local _, _, subGroup = DivineBlessing.GetRaidRosterInfo( index )
			if ( history[ "raidgroup"..subGroup ] ) then
				if ( ( DivineBlessing_UserConfig.Options.rebuffMissedTargets ) and ( not history[ targetId ] ) ) then
					return nil
				end
				return 1
			end
		end
	end

	return nil
end

--[[
--	SetState:isCurrentSpellRestricted()
--		Check to see if the current spell is marked already-cast in the history table
--
--	Args:
--		NO ARGS
--
--	Return:
--		bool isRestricted - 1 if the current spell is restricted, nil otherwise
--]]

function SetState:isCurrentSpellRestricted()
	return self:isSpellRestricted( self.index, self.pet, self.history )
end

--[[
--	SetState:SpellCastSuccess()
--		Called on a spellcast success
--]]

function SetState:SpellCastSuccess()
	if ( self.retarget ) then TargetLastTarget() end
	local missedTargets = self:RestrictSpell( self.index, self.pet, self.history )
	self:GetNextSpellId()
	self:SetLastSpell( nil, nil, nil )
	return missedTargets
end

--[[
--	SetState:SpellCastFailure()
--		Called on a spellcast failure
--]]

function SetState:SpellCastFailure()
	if ( self.retarget ) then TargetLastTarget() end
	if ( not self.set.options.wait ) then self:GetNextSpellId() end
	self:SetLastSpell( nil, nil, nil )
end

--[[
--	SetState:GetRemainingCasts()
--		Get the number of remaining spellcasts until completion of the set
--
--	Args:
--		NO ARGS
--
--	Return:
--		number remaining - the number of casts until completion, taking into
--			account group-targeting and class-targeting spells
--]]

function SetState:GetRemainingCasts()
	local remaining = 0
	
	-- Build a local copy of the restriction list
	local localHistory = {}
	local targetId, value
	for targetId, value in pairs( self.history ) do
		localHistory[ targetId ] = value
	end

	-- Determine what's left to cast (and restrict to the local copy if necessary)	
	for currentIndex = self.index, self:GetSetSize(), 1 do
		-- Group check (GetRaidRosterInfo returns 1 for subGroup if not in a raid)
		local _, _, subGroup = DivineBlessing.GetRaidRosterInfo( currentIndex )
		if ( ( not self.set.skipGroups ) or ( not self.set.skipGroups[ subGroup ] ) ) then

			-- Check the target
			if ( self:IsTargetRemaining( currentIndex, nil, localHistory ) ) then
				remaining = remaining + 1
			end
			
			-- Check the target's pet
			if ( self:IsTargetRemaining( currentIndex, 1, localHistory ) ) then
				remaining = remaining + 1
			end
		end
	end

	return remaining
end

--[[
--	SetState:IsTargetRemaining( number index, bool pet, table history )
--		Is the specified target still pending casts?
--
--	Args:
--		number index - the index of the target to check
--		bool pet - true if the target's pet should be checked
--		table history - the history to update once the target is checked
--
--	Return:
--		bool remaining - true if this target has yet to be cast on, false otherwise
--]]

function SetState:IsTargetRemaining( index, pet, history )
	if ( self:isSpellRestricted( index, pet, history ) ) then return false end
	local targetId = DivineBlessing.GetTargetId( index, pet, self.set.type == DivineBlessing.SETTYPE_RAID )
	if ( not UnitExists( targetId ) ) then return false end
	local spellName = self:GetTargetSpell( targetId )
	local spellId = DivineBlessing.GetBestSpellId( targetId, spellName )
	if ( not spellId ) then return false end
	self:RestrictSpell( index, nil, history, true )
	return true
end
