OBJECT_DROP_ON_TARGET_STATE	= 'ObjectDropOnTarget'
DRAG_OBJECT_USED_STATE		= 'DragObjectUsed'

class 'DragObject' (DragObjectBase)

function DragObject:__init(gameScene, entity)
	DragObjectBase.__init(self, gameScene, entity)

	self.bOnceDrop			= false
	self.bIntersectionMode	= false
	self.dropTime			= 0.5
	self.dropOnTargetCount	= 0
	self.takeCursor			= 'CursorActive'
	self.startPos			= entity and self:getDraggedEntity():getPosition() or nil
	self.bEnableDropTargetsOnDrag = false

	self.takeTarget	 = nil
	self.dropTargets = {}
end
-------------------------------------------------------------------------------

function DragObject:setDraggedEntity(entity)
	DragObjectBase.setDraggedEntity(self, entity)
	self.startPos = self:getDraggedEntity():getPosition()
end
-------------------------------------------------------------------------------

--[[
 * Метод устанавливает объект при клике по которому будет начинаться перетаскивание.
 * Если данный объект совпадает с объектом, зарегистрированым для перетаскивания, то
 * takeTarget устанавливается равным nil.
 * @param	target		Объект или имя объекта
--]]
function DragObject:setTakeTarget(target)
	target = self:recognizeObject(target)
	
	if target and target:getName() ~= self.draggedEntity:getName() then
		self.takeTarget = target
	else
		self.takeTarget = nil
	end
end
-------------------------------------------------------------------------------

--[[
 * Возвращает объект при клике по которомуц будет начинаться перетаскивание. Если
 * такой объект не установлен, то возвращается сам перетаскиваемый объект.
--]]
function DragObject:getTakeTarget()
	return self.takeTarget and self.takeTarget or self.draggedEntity
end
-------------------------------------------------------------------------------

--[[
 * Метод устанавливает объект или группу объектов, над которыми нужно совершить "дроп".
 * @param	target		Объект/имя объекта или их таблица, на которые нужно переместить
 *						self.draggedEntity. 
--]]
function DragObject:setDropTarget(target)
	assert(#self.dropTargets == 0)

	if type(target) == 'table' then
		for i, theTarget in ipairs(target) do
			table.insert(self.dropTargets, self:recognizeObject(theTarget))
		end
	else
		table.insert(self.dropTargets, self:recognizeObject(target))
	end
end
-------------------------------------------------------------------------------

function DragObject:getDropTarget()
	return self.dropTargets
end
-------------------------------------------------------------------------------

--[[
 * Метод проверяет является ли переданы параметру target объект целевым для "дропа".
 * @param	target		Объект или имя объекта, для которого выполняется проверка
--]]
function DragObject:isDropTarget(target)
	if target then
		target = self:recognizeObject(target)
		
		for i, dropTarget in ipairs(self.dropTargets) do
			if dropTarget:getName() == target:getName() then
				return true
			end
		end
	end
	
	return false
end
------------------------------------------------------------------------------

--[[
 * Метод устанавливает флаг, означающий то, что целевые для сброса объекты будут
 * кликабельны ТОЛЬКО во время перетаскивания предмета.
 * @param	bEnableDropTargetsOnDrag		Принимает булевое значение. Если true, то
 *											целевые для сброса объекты будут доступны
 *											только во время перетаскивания.
--]]
function DragObject:setEnableDropTargetsOnDrag(bEnableDropTargetsOnDrag)
	self.bEnableDropTargetsOnDrag = bEnableDropTargetsOnDrag
end
------------------------------------------------------------------------------

function DragObject:isEnableDropTargetsOnDrag()
	return self.bEnableDropTargetsOnDrag
end
------------------------------------------------------------------------------

function DragObject:setDropTargetsRespondable(bRespondable)
	for i, target in ipairs(self.dropTargets) do
		if not self:isDropOnTarget(target:getName()) then
			target:setRespondable(bRespondable)
		end
	end
end
-------------------------------------------------------------------------------

function DragObject:setIntersectionMode(bIntersectionMode)
	self.bIntersectionMode = bIntersectionMode
end
-------------------------------------------------------------------------------

function DragObject:isIntersectionMode()
	return self.bIntersectionMode
end
-------------------------------------------------------------------------------

--[[
 * Метод возвращает позицию объекта перетаскивания, которую он занимал при старте
 * сцены.
--]]
function DragObject:getStartPos()
	return Vector2f(self.startPos.x, self.startPos.y)
end
-------------------------------------------------------------------------------

--[[
 * в методе реализовано выполнение сохранки и регистрация клика по цели для взятия.
 * должен быть вызван после инициализации всех свойств
--]]
function DragObject:run()
	if self.bEnableDropTargetsOnDrag then
		self:setDropTargetsRespondable(false)
	end

	for i, target in ipairs(self.dropTargets) do
		if self:isDropOnTarget( target:getName() ) then
			self.dropOnTargetCount = self.dropOnTargetCount + 1
			target:setRespondable(false)
			
			local callback = self:getCallback('onDropTarget')

			if callback then
				local event	  = {}
				event.sender  = self
				event.target  = target
				event.save	  = true
				event.used	  = self.bOnceDrop or (self.dropOnTargetCount == #self.dropTargets)

				event.objName	 = self.draggedEntity:getName()
				event.targetName = target:getName()
				
				callback(event)
			end
		end
	end
	
	if not self:isUsed() then
		local target = self:getTakeTarget()

		if self.takeCursor then
			target:setCursorOverName(self.takeCursor)
		end

		self:activate(target)
	end
end
-------------------------------------------------------------------------------

--[[
 * сбрасывает стейты осуществленных перетаскиваний
--]]
function DragObject:reset()
	self:clearDropOnTargetStates()
	self:setUsed(false)

	if self.takeCursor then
		local target = self:getTakeTarget()
		target:setCursorOverName(self.takeCursor)
	end
	self:setDropTargetsRespondable(not self.bEnableDropTargetsOnDrag)
end
-------------------------------------------------------------------------------

function DragObject:createSessionData(screenFocus)
	local info = DragObjectBase.createSessionData(self, screenFocus)

	info.bCheckTarget = true
	info.bDropInstant = false

	return info
end
-------------------------------------------------------------------------------

function DragObject:drop(bCheckTarget, bDropInstant)
	if self:isDragging() then
		if bCheckTarget ~= nil then
			self.sessionData.bCheckTarget = bCheckTarget
		end

		if bDropInstant ~= nil then
			self.sessionData.bDropInstant = bDropInstant
		end

		DragObjectBase.drop(self)
	end
end
------------------------------------------------------------------------------

function DragObject:takeProcess(sender, screenFocus)
	if self.takeTarget then
		self.takeTarget:setRespondable(false)
	end

	if self.bEnableDropTargetsOnDrag then
		self:setDropTargetsRespondable(true)
	end

	DragObjectBase.takeProcess(self, sender, screenFocus)
end
------------------------------------------------------------------------------

function DragObject:dropProcess(bAborted, target, screenFocus)
	self.scene:getPlayField():hideBlackBar()
	self:stopDrag()

	if bAborted then
		self.sessionData.bCheckTarget = false
		self.sessionData.bDropInstant = true
	end

	local target = self:getDropTargetCandidat()

	if self.takeTarget then
		self.takeTarget:setRespondable(true)
	end

	if target and self:isDropTarget(target) then
		self:onDropTarget(target)
	else
		self:onDrop(target)
	end

	if self.bEnableDropTargetsOnDrag then
		self:setDropTargetsRespondable(false)
	end

	self.sessionData.screenFocus = nil
end
-------------------------------------------------------------------------------

function DragObject:getDropTargetCandidat()
	if self.sessionData.bCheckTarget then

		if self:isIntersectionMode() then
			for i, target in ipairs(self.dropTargets) do
				if not self:isDropOnTarget( target:getName() )
					and target:isRespondable()
					and isSpriteIntersection(self.draggedEntity, target)
				then
					return target
				end
			end
		end

		return self.sessionData.screenFocus:getPointedResponder()
	end

	return nil
end
------------------------------------------------------------------------------

function DragObject:onDropTarget(target)
	self:setDropOnTarget( target:getName() )
	target:setRespondable(false)
	self.draggedEntity:setOrder(self.sessionData.order)
	
	local bDropOnAllTargets = self.bOnceDrop or (self.dropOnTargetCount == #self.dropTargets)
	
	if bDropOnAllTargets then
		self:setUsed(true)
		self:deactivate()

		if self.takeCursor then
			self:getTakeTarget():setCursorOverName('CursorDefault')
		end

		if self.takeTarget then
			self.takeTarget:setRespondable(false)
		end
	end
	
	local callback = self:getCallback('onDropTarget')
	if callback then
		local event	  = {}
		event.sender  = self
		event.objName = self.draggedEntity:getName()
		event.target  = target
		event.save	  = false
		event.used	  = bDropOnAllTargets

		event.objName	 = self.draggedEntity:getName()
		event.targetName = target:getName()

		callback(event)
	end
end
-------------------------------------------------------------------------------

function DragObject:onDrop(target)
	local callback	= self:getCallback('onDrop')
	local afterDrop	= bind(self.defaultAfterDrop, self)

	if callback then
		local event		= {}
		event.sender	= self
		event.target	= target
		event.afterDrop	= afterDrop
		event.time		= self.sessionData.bDropInstant and 0 or self.dropTime

		event.objName	 = self.draggedEntity:getName()
		event.targetName = (target ~= nil) and target:getName() or nil

		callback(event)

		afterDrop = event.afterDrop
	end

	if afterDrop then
		afterDrop()
	end
end
-------------------------------------------------------------------------------

function DragObject:defaultAfterDrop()
	local object = self.draggedEntity

	if self.sessionData.bDropInstant then
		object:setPosition( self.sessionData.pos )
		object:setScale( self.sessionData.scale.x, self.sessionData.scale.y )
		object:setOrder( self.sessionData.order )
	else
		local time		= self.dropTime
		local effector	= CreateAnimationEffector()
		local scale		= object:getScale()
		local pos		= object:getPosition()

		effector:setDestroysOnEnd(true)
		effector:attachTo(object)
		effector:setKey(_TRACK_X,	0,		pos.x)
		effector:setKey(_TRACK_Y,	0,		pos.y)
		effector:setKey(_TRACK_SX,	0,		scale.x)
		effector:setKey(_TRACK_SY,	0,		scale.y)
		effector:setKey(_TRACK_X,	time,	self.sessionData.pos.x)
		effector:setKey(_TRACK_Y,	time,	self.sessionData.pos.y)
		effector:setKey(_TRACK_SX,	time,	self.sessionData.scale.x)
		effector:setKey(_TRACK_SY,	time,	self.sessionData.scale.y)
		effector:play()
		
		object:setRespondable(false)
		if self.takeTarget then
			self.takeTarget:setRespondable(false)
		end

		self.sessionData.dropEffector = effector

		local onDropFlightStart = self:getCallback('onDropFlightStart')
		if onDropFlightStart then
			local event		= {}
			event.sender	= self
			event.effector	= effector
			event.time		= time

			onDropFlightStart(event)
		end

		effector.onFinish:add(	function()
									object:setPosition(self.sessionData.pos)
									object:setOrder(self.sessionData.order)

									self:getTakeTarget():setRespondable(true)
									if self.takeTarget then
										self.takeTarget:setRespondable(true)
									end

									local onDropFlightEnd = self:getCallback('onDropFlightEnd')
									if onDropFlightEnd then
										local event	 = {}
										event.sender = self

										onDropFlightEnd(event)
									end

									self.sessionData.dropEffector = nil
								end)
	end
end
-------------------------------------------------------------------------------

--[[
 * Метод проверяет было ли уже перемещение на объект, имя которого передано
 * параметру dropTargetName.
--]]
function DragObject:isDropOnTarget(dropTargetName)
	local state = self:getDropOnTargetState(dropTargetName)
	
	return HasGameState(OBJECT_DROP_ON_TARGET_STATE, state)
end
-------------------------------------------------------------------------------

--[[
 * Метод помечает то, что было произведено перетаскивание на целевой объект, имя
 * которого передано параметру dropTargetName.
--]]
function DragObject:setDropOnTarget(dropTargetName)
	local state = self:getDropOnTargetState(dropTargetName)
	
	if not HasGameState(OBJECT_DROP_ON_TARGET_STATE, state) then
		AddGameState(OBJECT_DROP_ON_TARGET_STATE, state)
		
		self.dropOnTargetCount = self.dropOnTargetCount + 1
	end
end
-------------------------------------------------------------------------------

--[[
 * Метод отменяет метку того, что было произведено перетаскивание на целевой объект, имя
 * которого передано параметру dropTargetName.
--]]
function DragObject:removeDropOnTarget(dropTargetName)
	local state = self:getDropOnTargetState(dropTargetName)
	
	if HasGameState(OBJECT_DROP_ON_TARGET_STATE, state) then
		RemoveGameState(OBJECT_DROP_ON_TARGET_STATE, state)
		
		self.dropOnTargetCount = self.dropOnTargetCount - 1
	end
end
-------------------------------------------------------------------------------

--[[
 * Метод отменяет все метки перемещений на целевые объекты.
--]]
function DragObject:clearDropOnTargetStates()
	for i, target in ipairs(self.dropTargets) do
		self:removeDropOnTarget( target:getName() )
	end
end
-------------------------------------------------------------------------------

--[[
 * Метод возвращает уникальную строку, представляющую собой записываемое в gameState значение
 * при перетаскивании на целевой объект dropTargetName.
--]]
function DragObject:getDropOnTargetState(dropTargetName)
	return self.scene:getName()..'(obj:'..self.draggedEntity:getName()..' target:'..dropTargetName..')'
end
-------------------------------------------------------------------------------

--[[
 * Проверка, совершено ли перетаскивание на ВСЕ целевые объекты
--]]
function DragObject:isUsed()
	local state = self:getUsedState()
	
	return HasGameState(DRAG_OBJECT_USED_STATE, state)
end
-------------------------------------------------------------------------------

--[[
 * Метод устанавливает/удаляет состояние, отвечающее за проверку того, совершено ли
 * перетаскивания на ВСЕ целевые объекты.
 * @param	bUsed		Будевое значение, которое информирует про завершенность действия.
--]]
function DragObject:setUsed(bUsed)
	local state = self:getUsedState()
	
	if bUsed then
		AddUniqueGameState(DRAG_OBJECT_USED_STATE, state)
	else
		if HasGameState(DRAG_OBJECT_USED_STATE, state) then
			RemoveGameState(DRAG_OBJECT_USED_STATE, state)
		end
	end
end
-------------------------------------------------------------------------------

--[[
 * Переместить предмет на ОДИН ИЗ целей.
--]]
function DragObject:setOnceTargetDrop()
	self.bOnceDrop = true
end
-------------------------------------------------------------------------------

--[[
 * Метод возвращает уникальную строку, представляюую собой записываемое в gameState значение
 * при завершении перетаскивания на ВСЕ целевые объекты.
--]]
function DragObject:getUsedState()
	return self.scene:getName()..'('..self.draggedEntity:getName()..')'
end
-------------------------------------------------------------------------------

function DragObject:onDragFocusIn(sender, screenFocus)
	if not self:isDragWithinGameScene() then
	    local topScreen		= PopUpScreensMgr:GetTop()
	    local bDropInstant	= not topScreen or topScreen:getName() ~= self.scene:getName()

		self:drop(false, bDropInstant)
	end
end
-------------------------------------------------------------------------------