dofile('Data/Scripts/Common/Utils/Helpers/HelpersCustom.lua')

---------------------------------- HELPERS ------------------------------------
Helpers = {
-------------------------------------------------------------------------------

CallFunctionByName = function(functionName, ...)
     local getSubObject = function(object, subObjectName)
               object = object or _G
               return object[subObjectName]
          end
     local obj = nil
     for name in string.gmatch(functionName, '[_%a][_%w]*') do
          obj = getSubObject(obj, name)
     end
     assert((type(obj) == 'function' or getmetatable(obj).__call ~= nil) and "Specified object can't be invoked !")
     return obj(...)
end,

------------------------------------------------------------------------------
SaveObjectToGameState = function(stateName, object, tag)

	-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	-- 
	local CreateValueDump			= nil
	local CreateTableElementDump	= nil
	local CreateTableDump			= nil
	local CreateObjectDump			= nil

	CreateValueDump = function (value, fieldType)
	    if (fieldType == 'number') then
	        return tostring(value)
	    elseif (fieldType == 'boolean') then
	        return (value == true and 'true' or 'false')
	    elseif (fieldType == 'string') then
	        return '\'' .. value .. '\''
	    elseif (fieldType == 'table') then
	        return '{}'
	    end
        assert(false and 'Unacceptable value was passed !')
	end

	CreateTableElementDump = function (prefix, fieldName, value, fieldType)
	    local result;
	    local adjustedFieldName = fieldName
	    local prefixLen = string.len(prefix)

		if (string.find(fieldName, "^%d.*")) then
	        assert(string.sub(prefix, prefixLen, prefixLen) == '.' and "Unacceptable prefix format. It must ends on char '.'!")
	        local fieldNameAdjusted = fieldName

	        if (tonumber(fieldName) == nil) then
	            fieldNameAdjusted = '\'' .. fieldName .. '\''
	        end
	        adjustedFieldName = '[' .. fieldNameAdjusted .. ']'
	        result = string.sub(prefix, 1, prefixLen - 1) .. adjustedFieldName .. ' = ';
	    else
	        result = prefix .. fieldName .. ' = ';
	    end
    
		result = result .. CreateValueDump(value, fieldType) .. ';'

	    return result, adjustedFieldName;
	end

	CreateTableDump = function (tbl, prefix)
	    local data = ''

	    for fieldName, value in pairs(tbl) do
	        local fieldType = type(value)

	        if (fieldType == 'number' or fieldType == 'boolean' or fieldType == 'string' or fieldType == 'table') then
	            fieldData, fieldAdjustedName = CreateTableElementDump(prefix, fieldName, value, fieldType)
	            data = data .. fieldData

                newPrefix = (fieldName == fieldAdjustedName and prefix or string.sub(prefix, 1, string.len(prefix) - 1)) .. fieldAdjustedName .. '.'

	            if (fieldType == 'table') then
	                data = data .. CreateTableDump(value, newPrefix)
	            end
	        end
	    end
	    return data
	end

	CreateObjectDump = function (obj)
        local objType = type(obj)

        if (objType == 'number' or objType == 'boolean' or objType == 'string') then
			return 'object = ' .. CreateValueDump(obj, objType)
		elseif (objType == 'table') then
			return 'object = {};' .. CreateTableDump(obj, 'object.')
		else
			return 'object = nil;'
		end
	end
	
	local dump = 'function __RESTORE()\n local '..CreateObjectDump(object)..'\n return object end\n'
	SetGameState(stateName, dump, tag)
end,

------------------------------------------------------------------------------
RestoreObjectFromGameState = function(stateName, tag)
	local object = nil
	
	if (HasGameState(stateName, nil, tag)) then
    	local dump = GetGameState(stateName, tag):get(0)

    	assert(not __RESTORE and "Global restoring function already exists! Please check what code it belongs to!")

		runSucced,errorMessage = pcall(loadstring(dump))

		if (runSucced) then
			if (not __RESTORE) then
				Console:pushText("Restoring dump for '"..stateName.."' state hasn't been properly created! It hasn't created global __RESTORE() routine!")
			else
				object = __RESTORE()
				__RESTORE = nil
			end
		else
			Console:pushText("Restoring dump for '"..stateName.."' = \""..dump.."\" state hasn't been properly created! Some error occured during resoting code execution with error message: \""..errorMessage.."\".")
		end
    end
    
	return object
end,

-------------------------------------------------------------------------------
screenToLocal = function(object, scrX, scrY)
    local startX, startY = object:getX(), object:getY()
    object:setScrXY(scrX, scrY)
    
    local resultX, resultY = object:getX(), object:getY()
    object:setXY(startX, startY)
    
    return resultX, resultY
end,

-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   --[[ Usage Example:
   --// ...
    local gameStateName = '13_res_gamestate'
    
    --// restoring from the gamestate/initializing lua object
	local data = Helpers.RestoreObjectFromGameState(gameStateName) or 
            {objects = {{name = "obj1", pos = {x = 1.3, y = 2}, active = true},
                        {name = 'obj2', pos = {x = 1.5, y = 2}, active = false}}, 
             objectCount = 2,
             PI = 3.1415,
             momot = 'some text',
             done = false}

    --// saving lua object to the gamestate
	Helpers.SaveObjectToGameState(gameStateName, data)

	--// restoring from the gamestate
	local res1 = Helpers.RestoreObjectFromGameState(gameStateName) 
	
	--// ...
	--]]
------------------------------------------------------------------------------
    Move = function(entity, dx, dy, time, delay)
		delay = delay or 0
		
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_X,	delay, 			entity:getX())
		af:setKey(_TRACK_Y, delay, 			entity:getY())
		af:setKey(_TRACK_X, delay + time, 	entity:getX() + dx)
		af:setKey(_TRACK_Y, delay + time,	entity:getY() + dy)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
    bezierMoveTo = function(entity, x1, y1, time, delta_0_handle, delta_1_handle)

        local accumulated_time = 0

        local x0 = entity:getX()
        local y0 = entity:getY()

        local xH0 = x0 + (delta_0_handle and delta_0_handle.x or 0)
        local yH0 = y0 + (delta_0_handle and delta_0_handle.y or 0)

        local xH1 = x1 + (delta_1_handle and delta_1_handle.x or 0)
        local yH1 = y1 + (delta_1_handle and delta_1_handle.y or 0)
        
        local connection
        connection = entity.onUpdate:add(
            function(entity, dt)
        	    if accumulated_time >= time then
	                entity:setXY(x1, y1)
	                connection:remove()
		            return
	            end

	            accumulated_time = accumulated_time + dt

	            local t = accumulated_time / time
                local oT = 1 - t

                local t2 = t*t
                local oT2 = oT*oT

                local a0 = oT2*oT
                local a1 = 3 * t * oT2
                local a2 = 3 * t2 * oT
                local a3 = t2*t

                local x = a0*x0 + a1*xH0 + a2*xH1 + a3*x1
                local y = a0*y0 + a1*yH0 + a2*yH1 + a3*y1
        
	            entity:setXY(x,y)
	        end
	   )
	   
	   return connection
	end,
	------------------------------------------------------------------------------
	MoveTo = function(entity, x, y, time, delay, is_scr)
		time = time or 0
		delay = delay or 0
		
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		if is_scr then
			af:setKeysMode(_KEYS_XY_SCR)
			af:setKey(_TRACK_X, delay, entity:getScrX())
			af:setKey(_TRACK_Y, delay, entity:getScrY())
			WriteWarning("MoveTo is using screen coordinates while moving " .. entity:getName())
		else
			af:setKey(_TRACK_X, delay, entity:getX())
			af:setKey(_TRACK_Y, delay, entity:getY())
		end
		af:setKey(_TRACK_X, delay+time, x)
		af:setKey(_TRACK_Y, delay+time, y)
		af:play()
		
		return af
	end,
------------------------------------------------------------------------------
	
	Rotate = function(entity, dangle, time, delay)
		delay = delay or 0
		
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_A, delay,			entity:getRotationRad())
		af:setKey(_TRACK_A, delay + time,	entity:getRotationRad() + dangle)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
	
	RotateTo = function(entity, angle, time, delay)
		delay = delay or 0
		
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_A, delay,			entity:getRotationRad())
		af:setKey(_TRACK_A, delay + time,	angle)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
	
	ChangeScale = function(entity, start, finish, time, delay)
		delay = delay or 0
	
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_SX, delay, 		start)
		af:setKey(_TRACK_SY, delay, 		start)
		af:setKey(_TRACK_SX, delay + time,	finish)
		af:setKey(_TRACK_SY, delay + time,	finish)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
	
	ChangeScaleX = function(entity, start, finish, time, delay)
		delay = delay or 0
	
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_SX, delay, 		start)
		af:setKey(_TRACK_SX, delay + time,	finish)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
	
	ChangeScaleY = function(entity, start, finish, time, delay)
		delay = delay or 0
	
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)
		af:setKey(_TRACK_SY, delay, 		start)
		af:setKey(_TRACK_SY, delay + time,	finish)
		af:play()
		
		return af
	end,
	------------------------------------------------------------------------------
	
	ChangeOpacity = function(entity, start, finish, time, delay)
		delay = delay or 0
		start = start or entity:getOpacity()
		
		local af = CreateAnimationEffector()
		af:setDestroysOnEnd(true)
		af:attachTo(entity)	
		af:setKey(_TRACK_O, delay, 			start)
		af:setKey(_TRACK_O, delay + time,	finish)
		af:play()
    
		return af
	end,
	
	attachInPlace = function(object, parent_object)
		local x, y   = object:getScrX(), object:getScrY()
		local angle   = object:getScrRotationRad()
		local sx, sy  = object:getScrScale().x, object:getScrScale().y
	
		object:attachTo(parent_object)
		object:setScrPosition(Vector2f(x, y))
		object:setScrRotationRad(angle)
		object:setScrScale(Vector2f(sx, sy))
	end,
	
	------------------------------------------------------------------------------
} -- Helpers

function FixAngle(angle)
	while angle < 0. do
		angle = angle + M_TWO_PI
    end

	while angle >= M_TWO_PI do
		angle = angle - M_TWO_PI
    end

    return angle
end
-------------------------------------------------------------------------------

function RandomBool()
    return math.random(0, 1)
end
-------------------------------------------------------------------------------

function RandomFloat(min, max)
    return min + math.random() * (max - min)
end
-------------------------------------------------------------------------------

function SelectRandom(...)
    local arg = {...}
    return arg[math.random(#arg)]
end
--------------------------------------------------------------------------------

function CorrectValue(value, min, max)
    if value < min then
        value = min
    end
    
    if value > max then
        value = max
    end
    
    return value
end
-------------------------------------------------------------------------------
function getAnimationLength(anim)
    assert(anim:getFPS() ~= 0.)
    return anim:getFramesCount() / anim:getFPS()
end
-------------------------------------------------------------------------------
function IsAboveReturnFromZoomZone()
    if IsDragging() then
        return;
    end
    
    local level     = GetCurrentLevel()
    local panel_obj = level:GetPanel():GetObjectUnderMouse()
    local inv_obj   = level:GetInventory():GetObjectUnderMouse()
    
    local is_in_proper_pos = false
    
    if  level.mode == 'hidden_object' then
        is_in_proper_pos = panel_obj and ( panel_obj:GetName() == 'panel_ho' or 
                                           panel_obj:GetName() == 'area_under_panel')
    else
        is_in_proper_pos = GetMousePos().y > SCR_H - (RETURN_ZONE_HEIGHT + level.return_zone_dheight) and
                           GetMousePos().x > 110. and -- left panel gui border
                           GetMousePos().x < 870. and -- right panel gui border
                           not panel_obj and
                           not inv_obj
    end
    return is_in_proper_pos
end
-------------------------------------------------------------------------------

function table.filter(t, predicate)
	assert(predicate)
	local result = {}

	for i, value in pairs(t) do
		if predicate(value) then
			table.insert(result, value)
		end
	end

	return result 
end
-------------------------------------------------------------------------------

function table.has_value(t, value)
	for _, v in pairs(t) do
		if v == value then
			return true
		end
	end
	return false
end
-------------------------------------------------------------------------------

function table.has_key(t, key)
	for k, _ in pairs(t) do
		if k == key then
			return true
		end
	end
	return false
end
-------------------------------------------------------------------------------

function table.remove_by_value(t, value)
	for i, v in pairs(t) do
		if v == value then
			table.remove(t, i)
			break
		end
	end
end
-------------------------------------------------------------------------------

function table.random_index(t)
	local indexed_table = {}
	for k, v in pairs(t) do
		table.insert(indexed_table, k)
	end
	local index  = math.random(#indexed_table)
    return indexed_table[index]
end
-------------------------------------------------------------------------------

function table.random_value(t)
    return t[table.random_index(t)]
end
-------------------------------------------------------------------------------

function table.shuffle(t)
  local n = #t -- n is the last pertinent index (loop invariant)
  while n > 1 do
    local k = math.random(n) -- 1 <= k <= n
    -- Quick swap
    t[n], t[k] = t[k], t[n]
    n = n - 1
  end
 
  return t
end
-------------------------------------------------------------------------------
table.copy = function(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end
-------------------------------------------------------------------------------
table.size = function( table )
    local result = 0
    
    for i,v in pairs(table) do
        result = result+1
    end
    
    return result
end
-------------------------------------------------------------------------------
table.unpack = function(value, depth)
    local result = {}
    
    local function UnpackTable(value, _depth)
        for i,v in pairs(value) do
            if type(v) == 'table' and ((not depth) or _depth<depth) then
                UnpackTable(v, _depth+1)
            else
                table.insert(result, v)
            end
        end
    end
    UnpackTable(value, 1)
    
    return result
end
-------------------------------------------------------------------------------
table.equal = function ( table1, table2 )
	if ( #table1 == #table2 ) then
		for ind, val in ipairs( table1 ) do
			if ( type( table1[ ind ] ) == 'table' ) and ( type( table2[ ind ] ) == 'table' ) then
				if not table.equal( table1[ ind ], table2[ ind ] ) then
					return false
				end
			elseif ( table1[ ind ] ~= table2[ ind ] ) then
					return false
			end
		end
	else
		return false
	end

	return true
end
-------------------------------------------------------------------------------

math._and = function( val1, val2 )
    return bitwise_and(val1, val2)
end

math._or = function( val1, val2 )
    return bitwise_or(val1, val2)
end

math._xor = function( val1, val2 )
    return bitwise_xor(val1, val2)
end

math._not = function( val1 )
    return bitwise_not(val1)
end
-------------------------------------------------------------------------------

math.radius = function(x1, y1, x2, y2)
    return math.sqrt( (x1 - x2)^2 + (y1 - y2)^2 )
end

math.radiusV = function(P1, P2)
    return math.radius(P1.x, P1.y, P2.x, P2.y)
end
-------------------------------------------------------------------------------

function toboolean(X)
    if type(X) == 'string' then
        if X == 'true' then
            return true
        else
            return false
        end
    end
    return not not X
end
-------------------------------------------------------------------------------

function ScriptCreateButton(object, message, screen)
	local anim = object:GetAnimation()

	object:SetVisible(false);
	--screen:IgnoreOnTestPoint(object:GetName());

	local button = CreateButton("", "", "", message, screen)
	if play_field and play_field:GetLevelScreen():GetName() == screen:GetName() then
	    button:AttachTo(play_field)
	else
	    button:AttachTo(screen)
	end

	local button = CreateButton("", "", "", message, screen)
	button:AttachTo(screen)
	button:SetXY(object:GetX(), object:GetY())
	button:SetZ(object:GetZ())
	button:SetScaleX(object:GetScaleX())
	button:SetScaleY(object:GetScaleY())
	button:SetAngle(object:GetAngle())
	button:SetOpacity(object:GetOpacity())
	button:SetVisible(true)
	
	if anim:GetFrames() == 3 then
        anim:SetFrame(0)
        button:SetSpriteUp(anim)
        anim:SetFrame(1)
        button:SetSpriteOver(anim)
        anim:SetFrame(2)
        button:SetSpriteDown(anim)
	elseif anim:GetFrames() == 2 then
        anim:SetFrame(0)
        button:SetSpriteUp(anim)
        anim:SetFrame(1)
        button:SetSpriteOver(anim)
        anim:SetFrame(1)
        button:SetSpriteDown(anim)
	end

	return button
end
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------
function table.deep_copy(t)
    local lookup_table = {}
    local function _copy(t)
        if type(t) ~= "table" then
            return t
        elseif lookup_table[t] then
            return lookup_table[t]
        end
        local new_table = {}
        lookup_table[t] = new_table
        for index, value in pairs(t) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(t))
    end
    return _copy(t)
end

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

function string:split(sep)
	local sep, fields = sep or " ", {}
	local pattern = string.format("([^%s]+)", sep)
	self:gsub(pattern, function(c) fields[#fields+1] = c end)
	return fields
end
-------------------------------------------------------------------------------

function each(tab, func)
    function walker(t, n)
        if t[n] then
            coroutine.yield(t[n])
            return walker(t, n+1)
        end
    end
    
    if tab and type(tab) == 'table' then
        return coroutine.wrap(walker), tab, 1
    end
end

-- Пересечение двух спрайтов, сравниваем size, scale пока не учитывается ----
-----------------------------------------------------------------------------
function IsIntersects(object_1, object_2)
    local object_1_size = object_1:getSize()
    local object_2_size = object_2:getSize()

    local object_1_hs = object_1:getHotSpot()
    local object_2_hs = object_2:getHotSpot()

    local right =   (object_1:getScrX() - object_1_hs.x + object_1_size.x) < 
                    (object_2:getScrX() - object_2_hs.x)

    local left =    (object_1:getScrX() - object_1_hs.x) >
                    (object_2:getScrX() - object_2_hs.x + object_2_size.x)

    local bottom =  (object_1:getScrY() - object_1_hs.y + object_1_size.y) < 
                    (object_2:getScrY() - object_2_hs.y)

    local top =     (object_1:getScrY() - object_1_hs.y) >
                    (object_2:getScrY() - object_2_hs.y + object_2_size.y)

    return not (right or left or top or bottom)
end
-----------------------------------------------------------------------------

function IsDragObjectUsed(objectName, levelName)
	assert(levelName or current_scene)
	levelName = levelName or current_scene:getName()
    
    local state = levelName..'('..objectName..')'
	return HasGameState(DRAG_OBJECT_USED_STATE, state)
end
-----------------------------------------------------------------------------

function IsObjectDropTarget(objectName)
	return IsDragObjectUsed(objectName)
end
-----------------------------------------------------------------------------