require 'scripts/core/core.lua'

--------------------------------------------------------------------------------
-- GUIElement Object script
--! 
--! States
--! * idle
--! * disabled
--!
--! Attributes
--! * listener : script component that listens to the element events.
--! * idleVisual : visual for idle state.
--! * focusedVisual : visual for focused state.
--! * disabledVisual : visual for disabled state.
--! * focusedAnimation : reference to the animator to launch on focus
--! 
--! Events
--! * onElementFocus(element)
--! * onElementUnfocus(element)
--!
--------------------------------------------------------------------------------

GUIElement = class()
GUIElement.states = class()
GUIElement.isGUIElement = true
--------------------------------------------------------------------------------
-- State idle callbacks
--------------------------------------------------------------------------------
GUIElement.states.idle = class()
GUIElement.states.idle.enter = function(element, previousState)
	GUI:debugPrint("GUIElement : Enter idle " .. tostring(element) .. " previousState = " .. tostring(previousState), "states")
	element:enableVisual(element.idleVisual)
end
GUIElement.states.idle.leave = function(element, nextState)
	GUI:debugPrint("GUIElement : leave idle " .. tostring(element), "states")
	element:disableVisual(element.idleVisual)
end
--------------------------------------------------------------------------------
-- State disabled callbacks
--------------------------------------------------------------------------------
GUIElement.states.disabled = class()
GUIElement.states.disabled.enter = function(element, previousState)
	GUI:debugPrint("GUIElement : Enter disabled " .. tostring(element) .. " previousState = " .. tostring(previousState), "states")
	if element.disabledVisual then
		element:enableVisual(element.disabledVisual)
	else
		element:enableVisual(element.idleVisual)
	end
	Component_disable(element.clickable)
end
GUIElement.states.disabled.leave = function(element, nextState)
	GUI:debugPrint("GUIElement : leave disabled " .. tostring(element), "states")
	if element.disabledVisual then
		element:disableVisual(element.disabledVisual)
	else
		element:disableVisual(element.idleVisual)
	end
	Component_enable(element.clickable)
end


--------------------------------------------------------------------------------
-- start
--! @brief Callback when the object is added to the world
--------------------------------------------------------------------------------
function GUIElement:start()

	self.currentState = "none"

	GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:start()", "runtime")
	
	self:disableVisual(self.idleVisual)
	self:disableVisual(self.disabledVisual)
	if self.focusedVisual ~= nil then
		Component_disable(self.focusedVisual)
	end
	
	if self.listener ~= nil then
		self.listener = ScriptComponent_getScriptTable(self.listener)
	end
	
	if self.ownClickable == nil then
		self.ownClickable = false
	end
	
	if not self.parent then
		self:findParentElement()
	end
	
	self:setupAnimator(self.focusedAnimation)

	self:setState("idle")
end

--------------------------------------------------------------------------------
-- stop
--! @brief Callback when object is removed from the world
--------------------------------------------------------------------------------
function GUIElement:stop()
	GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:stop()", "runtime")
	
	if self.clickable ~= nil and self.ownClickable then
		removeAndDestroyComponentFromNode(self.worldNode, self.clickable)
	end
	
end

--------------------------------------------------------------------------------
-- update
--! @brief Callback called every frames
--! @param dt delta time since last frame
--------------------------------------------------------------------------------
function GUIElement:update(dt)
	
	if self.states ~= nil and self.states[self.currentState] ~= nil and self.states[self.currentState].update ~= nil then
		self.states[self.currentState]:update(self, dt)
	end

end

--------------------------------------------------------------------------------
-- setState
--! @brief Changes the current state of the element calling the appropriate
--!        state callbacks.
--! @note Nothing is performed if provided state is the current one
--! @param newState state to change to
--------------------------------------------------------------------------------
function GUIElement:setState(newState)
	
	if newState == self.currentState then
		return
	end
	
	GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:setState() setState = " .. newState, "states")
	GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:setState() curState = " .. self.currentState, "states")

	if self.states[self.currentState] ~= nil and self.states[self.currentState].leave ~= nil then
		self.states[self.currentState].leave(self, newState)
	end
	
	local previousState = self.currentState
	self.currentState = newState
	
	if self.states[self.currentState] ~= nil and self.states[self.currentState].enter ~= nil then
		self.states[self.currentState].enter(self, previousState)
	end
	
end

--------------------------------------------------------------------------------
-- setupClickableArea
--! @brief Creates a clickable component that is linked to this GUI element (as a listener).
--!        If already exist, check that its listener references the gui element
--------------------------------------------------------------------------------
function GUIElement:setupClickableArea()
	if self.clickable == nil then
		GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:setupClickableArea()", "hit_test")
		self.clickable = addNewComponentToNode(self.worldNode, COMPONENT_TYPE_CLICKABLE)
		self.ownClickable = true
		ClickableComponent_setClickListener(self.clickable, self._ptr)
		if self.autoAdjustClickableArea == nil then
			self.autoAdjustClickableArea = true
		end
	else
		local listener = ClickableComponent_getClickListener(self.clickable)
		if listener ~= nil and listener ~= self._ptr then
			GUI:warning("Warning! the listener of a clickable associated to a GUIButton script does not reference the GUIButton. Overriding...")
		end
		ClickableComponent_setClickListener(self.clickable, self._ptr)
		if self.autoAdjustClickableArea == nil then
			self.autoAdjustClickableArea = false
		end
		
		self.ownClickable = false
	end
	
	Component_setEnabled(self.clickable, Component_isEnabled(self._ptr))
end

--------------------------------------------------------------------------------
-- setupAnimator
--! @brief First disables the animator component and links it to the gui element by setting its event listener
--! @param animator Animator component to setup
--------------------------------------------------------------------------------
function GUIElement:setupAnimator(animator)
	if animator ~= nil then
		Component_disable(animator)
		local listener = AnimatorComponent_getEventListener(animator)

		if listener ~= nil and listener ~= self._ptr then
			GUI:warning("Warning! the event listener of an animator associated to a GUIElement script does not reference the GUIElement. Overriding...")
		end
		AnimatorComponent_setEventListener(animator, self._ptr)
	end
end

--------------------------------------------------------------------------------
-- enforceListenerToSelf
--! @brief Makes sure that the provided element listener is referencing self
--! @param element element to enforce the listener attribute
--! @param element_type name of the element
--! @param sub_element_type name of element to enforce
--------------------------------------------------------------------------------
function GUIElement:enforceListenerToSelf(element, element_type, sub_element_type)
	if element.listener ~= nil and element.listener ~= self then
		GUI:warning("Warning! the listener of the " .. tostring(sub_element_type) .. " associated to a " .. tostring(element_type) .. " script does not reference the " .. tostring(element_type) .. ". Value will be overriden.")
	end
	
	element.listener = self
end

--------------------------------------------------------------------------------
-- playAnimation
--! @brief Starts the provided animator
--! @param animator Animator component controlling the animation
--------------------------------------------------------------------------------
function GUIElement:playAnimation(animator)
	if animator ~= nil then
		Component_enable(animator)
		AnimatorComponent_reset(animator)
		AnimatorComponent_play(animator)
	end
end


--------------------------------------------------------------------------------
-- enableVisual
--! @brief Enables the provided visual component and the clickable component.
--! @note It adjusts the clickable component size according to the visual size if the attribute autoAdjustClickableArea is true
--! @param visual Visual to enable
--------------------------------------------------------------------------------
function GUIElement:enableVisual(visual)
	if visual ~= nil then
		Component_enable(visual)
		self.currentVisual = visual
		if self.clickable ~= nil then
			GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement enableClickable for visual " .. tostring(visual), "hit_test")
			Component_enable(self.clickable)
			
			local layer = VisualComponent_getLayer(visual)
			
			ClickableComponent_setLayer(self.clickable, layer)
			
			local areaWidth, areaHeight = ClickableComponent_getBoxShapeSize(self.clickable)
			
			if self.autoAdjustClickableArea or areaWidth == 0 or areaHeight == 0 then
				local sizeX, sizeY = VisualComponent_getSize(visual)
				GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement clickableArea size = " .. tostring(sizeX) .. " " .. tostring(sizeY), "hit_test")
				ClickableComponent_setBoxShape(self.clickable, sizeX, sizeY)
			end
			
		end
	end
end

--------------------------------------------------------------------------------
-- disableVisual
--! @brief Disables the provided visual component and the clickable component
--! @param visual Visual to disable. if nil, the current visual is disabled
--------------------------------------------------------------------------------
function GUIElement:disableVisual(visual)
	if visual ~= nil then
		Component_disable(visual)
		self.currentVisual = nil
		if self.clickable ~= nil then
			GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement disableClickable for visual " .. tostring(visual), "hit_test")
			Component_disable(self.clickable)
		end
	end
end

--------------------------------------------------------------------------------
-- sendCommand
--! @brief Function to handle a gui command sent by the GUI system to the element
--! @param command command to handle
--! @return true if command is supported by the element
--------------------------------------------------------------------------------
function GUIElement:sendCommand(command)
	GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement:sendCommand " .. tostring(command.id), "commands")
		
	if command.id == GUI_COMMAND_FOCUS then
		GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement focused " .. tostring(self), "actions")
		if self.focusedVisual ~= nil then
			Component_enable(self.focusedVisual)
		end
		if self.listener ~= nil and self.listener.onElementFocus ~= nil then
			self.listener:onElementFocus(self)
		end
		
		if self.focusedAnimation ~= nil then
			GUI:debugPrint("GUIElement : play focused animation", "animations")
			Component_enable(self.focusedAnimation)
			AnimatorComponent_setSpeed(self.focusedAnimation, 1.0)
			AnimatorComponent_play(self.focusedAnimation)
		end
		
	elseif command.id == GUI_COMMAND_UNFOCUS then
		GUI:debugPrint("[" .. tostring(self) .. "]\tGUIElement unfocused " .. tostring(self), "actions")
		if self.focusedVisual ~= nil then
			Component_disable(self.focusedVisual)
		end
		if self.listener ~= nil and self.listener.onElementUnfocus ~= nil then
			self.listener:onElementUnfocus(self)
		end
		
		if self.focusedAnimation ~= nil then
			GUI:debugPrint("GUIElement : play unfocused animation", "animations")
			AnimatorComponent_setSpeed(self.focusedAnimation, -1.0)
			AnimatorComponent_play(self.focusedAnimation)
		end
	end

	return false
end

--------------------------------------------------------------------------------
-- findParentElement
--! @brief Finds the parent gui element if the direct parent node has a gui element script.
--------------------------------------------------------------------------------
function GUIElement:findParentElement()
	local parent = WorldNode_getParentNode(self.worldNode)
	local hasScript = false
	local script
	
	while parent ~= nil and not hasScript do
		local components = WorldNode_getComponentsByTypeName(parent, "script")
		
		for i = 1, #components do
			script = ScriptComponent_getScriptTable(components[i])
				
			if script.isGUIElement then
				hasScript = true
				break
			end
		end
		
		if not hasScript then
			parent = WorldNode_getParentNode(parent)
		end
	end
	
	if hasScript then
		self.parent = script
	else
		self.parent = nil
	end
end

--------------------------------------------------------------------------------
-- getCurrentSize
--! @brief Returns the current size of the element according to the current visual
--! @return width, height
--------------------------------------------------------------------------------
function GUIElement:getCurrentSize()
	if self.currentVisual then
		return VisualComponent_getSize(self.currentVisual)
	end
	
	return 0, 0
end
