dofile('data/Scripts/Helpers/CustomHelpers/CustomHelpers.lua')

---------------------------------- HELPERS ------------------------------------
Helpers = {
-------------------------------------------------------------------------------
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,
------------------------------------------------------------------------------

GetObjectRect = function(object)
     local obj_x = object:GetX() - object:GetAnimation():GetHotX()
     local obj_y = object:GetY() - object:GetAnimation():GetHotY()
     local obj_w = object:GetAnimation():GetWidth()
     local obj_h = object:GetAnimation():GetHeight()
     
     return obj_x, obj_y, obj_w, obj_h
end,

GetObjectScrRect = function(object)
    local obj_x = object:GetScrX() - object:GetAnimation():GetHotX()*object:GetScrScaleX()
    local obj_y = object:GetScrY() - object:GetAnimation():GetHotY()*object:GetScrScaleY()
    local obj_w = object:GetAnimation():GetWidth()*object:GetScrScaleX()
    local obj_h = object:GetAnimation():GetHeight()*object:GetScrScaleY()

    return obj_x, obj_y, obj_w, obj_h
end,
------------------------------------------------------------------------------

InsertText = function(object, textAlias, font, Alignment)
    local x, y, width, height = Helpers.GetObjectRect( object )
    
    Alignment = Alignment or (HGETEXT_LEFT + HGETEXT_TOP + HGETEXT_JUSTIFIED)
    
    local text = CreateTextEntity(font, textAlias)
    text:AttachTo(object)
    text:SetZ(0)
    text:SetScale( 1/object:GetScaleX() )
    text:GetScale()
    text:SetTextAlignment(Alignment)
    text:SetBorderSize(width, height)
    
    local textX
    
    if     math._and(Alignment, HGETEXT_RIGHT)>0 then
        textX = width/2
    elseif math._and(Alignment, HGETEXT_CENTER)>0 then
        textX = 0
    else
        textX = -width/2
    end
    
    textY = -height/2
    
    text:SetXY(textX, textY)
    
    return text
end,
------------------------------------------------------------------------------

AttachInPlace = function(object, parentObject)
	local parentAngle = parentObject:GetScrAngle()
	
	local x,y,z   = object:GetScrX(), object:GetScrY(), object:GetScrZ()
	local Angle   = object:GetScrAngle()
	local sx, sy  = object:GetScrScaleX(), object:GetScrScaleY()

	object:AttachTo(parentObject)
    object:SetScrXY(x,y)
	object:SetScrZ(z)
	object:SetScrAngle(Angle)
	object:SetScrScaleX(sx)
	object:SetScrScaleY(sy)
end,

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)

	-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	-- 
	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)
end,

------------------------------------------------------------------------------
RestoreObjectFromGameState = function(stateName)
	local object = nil
	
	if (HasGameState(stateName)) then
    	local dump = GetGameState(stateName):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
				Log("Restoring dump for '"..stateName.."' state hasn't been properly created! It hasn't created global __RESTORE() routine!")
			else
				object = __RESTORE()
				__RESTORE = nil
			end
		else
			Log("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,
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   --[[ 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,
	------------------------------------------------------------------------------
	MoveTo = function(entity, x, y, time, delay, is_scr)
		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())
		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:GetAngle())
		af:SetKey(_TRACK_A, delay + time,	entity:GetAngle() + 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:GetAngle())
		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
		
		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,
	------------------------------------------------------------------------------

}

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:GetSpeed() ~= 0.)
    return anim:GetFrames() / anim:GetSpeed()
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 AddTimer(action, delay, is_cyclic, pass_dt_on_call)
	GetCurrentLevel().timer:Add(action, delay, is_cyclic, pass_dt_on_call)
end
-------------------------------------------------------------------------------

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

function table.random_value(t)
    return t[math.random(#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, Deth)
    local result = {}
    
    local function UnpackTable(Value, _deth)
        for i,v in pairs(Value) do
            if type(v) == 'table' and ((not Deth) or _deth<Deth) then
                UnpackTable(v, _deth+1)
            else
                table.insert(result, v)
            end
        end
    end
    UnpackTable(Value, 1)
    
    return result
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 PlayAnimationPeriodicallyVsDelay(anim_name, max_delay, soundName, hide_when_dalay, loopCondition, on_play)
    if loopCondition and not loopCondition() then
        return;
    end
    
    local anim = play_field:GetObject( anim_name ):GetAnimation()
    local time = GetAnimationLength( anim )
    
    anim:SetMode(HGEANIM_FWD + HGEANIM_NOLOOP)
    anim:Play()
    
    if on_play then
        on_play()
    end
    
    if hide_when_dalay then
        play_field:GetObject(anim_name):SetVisible(true)
    end
    
    if soundName then
        MakeSoundEntity(soundName):PlaySound(false)
    end
    
    if hide_when_dalay then
        GetCurrentLevel().timer:Add(function()
                                        play_field:GetObject(anim_name):SetVisible(false)
                                    end, time)
    end
    
    GetCurrentLevel().timer:Add(function()
                                    PlayAnimationPeriodicallyVsDelay(anim_name, max_delay, soundName, hide_when_dalay, loopCondition, on_play)
                                end, time + (math.random() * max_delay) )
end
-------------------------------------------------------------------------------

function StartCycledAnimation(obj_name, delay, period)
    if play_field:HasObject(obj_name) then
        local obj   = play_field:GetObject(obj_name)
        local timer = play_field:GetTimer()
        
        if obj:GetAnimation():GetMode() ~= HGEANIM_NOLOOP + HGEANIM_PINGPONG then
            obj:GetAnimation():SetMode(HGEANIM_NOLOOP)
        end

        if delay == 0 then
            delay = period
            obj:GetAnimation():Play()
        else
            obj:GetAnimation():Stop()
        end

        local action = "StartCycledAnimation('"..obj_name.."', 0, "..period..")"
        TimerDoString(action, delay, timer)
    end
end
-------------------------------------------------------------------------------

function StartCycledAnimationEx(obj_name, delay, period, hide_when_not_playing)
    if play_field:HasObject(obj_name) then
        local obj = play_field:GetObject(obj_name)
        local timer = play_field:GetTimer()
        if obj:GetAnimation():GetMode() ~= HGEANIM_NOLOOP + HGEANIM_PINGPONG then
            obj:GetAnimation():SetMode(HGEANIM_NOLOOP)
        end

        local anim_length = GetAnimationLength(obj:GetAnimation())

        if delay == 0 then
            delay = period
            obj:GetAnimation():Play()
            obj:SetVisible(true)
            TimerDoString("play_field:GetObject('"..obj_name.."'):SetVisible(false)", anim_length, timer)
        else
            obj:SetVisible(false)
        end

        local action = "StartCycledAnimationEx('"..
                        obj_name..
                        "', 0, "..
                        period..
                        ','..
                        tostring(hide_when_not_playing)..
                        ")"

        TimerDoString(action, delay, timer)
    end
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 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
