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 RandomTableSort(t)
    table_size = table.getn(t)
    for i = 1, table_size, 1 do
        ind1 = math.random(1, table_size)
        ind2 = math.random(1, table_size)
        tmp = t[ind1]
        t[ind1] = t[ind2]
        t[ind2] = tmp
    end
end

function CEntity:Move(dx, dy, time)
    -- TODO: Need using adopt policy
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_X, 0.0, self:GetX())
    af:SetKey(_TRACK_Y, 0.0, self:GetY())
    af:SetKey(_TRACK_X, time, self:GetX() + dx)
    af:SetKey(_TRACK_Y, time, self:GetY() + dy)
    af:Play()
end

function CEntity:Rotate(dangle, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_A, 0.0, self:GetAngle())
    af:SetKey(_TRACK_A, time, self:GetAngle() + dangle)
    af:Play()
end

function CEntity:ChangeScale(start, finish, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_S, 0.0, start)
    af:SetKey(_TRACK_S, time, finish)
    af:Play()
    
    return af
end

function CEntity:ChangeOpacity(start, finish, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_O, 0.0, start)
    af:SetKey(_TRACK_O, time, finish)
    af:Play()
end

function CreateCloseButton(pos, message, listener)
	local button = CreateButton('OKButton',
								'OKButtonOver',
								'OKButtonDown',
								message,
								listener)
	button:AttachTo(play_field)
	button:SetX(pos.x)
	button:SetY(pos.y)
	button:SetZ(500)
	button:SetRect(-30, -30, 60, 60)
	button:GetCaption():SetXY(-4., 2.)
    return button
end

function CreateCloseZoomButton(pos, message, listener)
	local button = CreateButton('CloseZoomButton',
								'CloseZoomButtonOver',
								'CloseZoomButtonDown',
								message,
								listener)
	button:AttachTo(play_field)
	button:SetX(pos.x)
	button:SetY(pos.y)
	button:SetZ(500)
	button:SetRect(-30, -30, 60, 60)
    return button
end

function CreateCloseInfoButton(pos, message, listener)
	local button = CreateButton('OKButton',
                                'OKButtonOver',
								'OKButtonDown',
								message,
								listener)
	button:AttachTo(play_field)
	--button:SetMouseOverScale(1.05)
	button:SetX(pos.x)
	button:SetY(pos.y)
	button:SetZ(500)
	--button:SetRect(-80, -50, 160, 10)
	--button:GetCaption():SetText(GetString('OK'))
	--button:GetCaption():SetFont('data/Font/options.fnt')
	--button:GetCaption():SetXY(-4., 2.)
    return button
end

function CreateNextButton(pos, message, listener)
	local button = CreateButton('ButtonNextUp',
								'ButtonNextOver',
								'ButtonNextDown',
								message,
								listener)
	button:AttachTo(play_field)
	button:SetX(pos.x)
	button:SetY(pos.y)
	button:SetZ(220)
	--button:SetRect(-17, -14, 34, 28)
    return button
end

function Vector:__tostring()
    return 'X: '..self.x..' Y: '..self.y
end

function CGameObject:__tostring()
    local str = 'Name: '..self:GetName().." X: "..self:GetX().." Y: "..self:GetY().." Z: "..self:GetZ()
    if self:GetAnimation():GetFrames() > 0 then
        str = str..' NFrames: '..self:GetAnimation():GetFrames()
    end
    str = str..' Color:'..tostring(self:GetAnimation():GetColorStr())
    return str
end

function CTask:__tostring()
    return 'Text singular: '..self:GetTextSingular()
end

function GetAnimationLength(anim)
    assert(anim:GetSpeed() ~= 0.)
    return anim:GetFrames() / anim:GetSpeed()
end

function CScreenEx:__tostring()
    return self:GetName()
end

function CPanel:__tostring()
    return self:GetName()
end

function CInventory:__tostring()
    return self:GetName()
end

function CButtonExEntity:__tostring()
    return self:GetName()
end

function CTextEntity:__tostring()
    return self:GetText()
end

function CGameObject:Move(dx, dy, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_X, 0.0, self:GetX())
    af:SetKey(_TRACK_Y, 0.0, self:GetY())
    af:SetKey(_TRACK_X, time, self:GetX() + dx)
    af:SetKey(_TRACK_Y, time, self:GetY() + dy)
    af:Play()
    
    return af;
end

function CGameObject:Rotate(dangle, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_A, 0.0, self:GetAngle())
    af:SetKey(_TRACK_A, time, self:GetAngle() + dangle)
    af:Play()
    
    return af
end

function CGameObject:ChangeScale(start, finish, time)
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    af:SetKey(_TRACK_S, 0.0, start)
    af:SetKey(_TRACK_S, time, finish)
    af:Play()
end

function CGameObject:ChangeOpacity(start, finish, time, delay)
    if not delay then
        delay = 0;
    end
    
    local af = CreateAnimationEffector()--AnimationEffector()
    af:AttachTo(self)
    
    af:SetKey(_TRACK_O, 0.0, start)
    
    if delay ~= 0 then
        af:SetKey(_TRACK_O, delay, start)
    end
    
    af:SetKey(_TRACK_O, time + delay, finish)
    af:Play()
    
    return af;
end

function CGameObject:MarkWithPS()
    self:AddParticleSystem('data/ParticleSystem/ObjectHighlight.psi',
                           'SolveLaterObjectParticle')
end

function IsAboveReturnFromZoomZone()
    local panel_obj          = play_field:GetLevelScreen():GetPanel():GetObjectUnderMouse()
    local inv_obj            = play_field:GetLevelScreen():GetInventory():GetObjectUnderMouse()
    local is_in_proper_pos   = false
    local return_zone_height = (game_state.return_zone_height) and game_state.return_zone_height or RETURN_ZONE_HEIGHT;
    
    if game_state.level_mode == 'hidden_object' then
        is_in_proper_pos = panel_obj and panel_obj:GetName() == 'background'
    else
        is_in_proper_pos = GetMousePos().y > 768. - return_zone_height 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 SwayObjectByX(objName, amplitude, time)
    local swayObj = play_field:GetObject( objName )
    
    local af = CreateAnimationEffector()
    af:AttachTo(swayObj)
    af:SetMode(_ANIM_FWD + _ANIM_LOOP);
    
    local x = swayObj:GetX()
    local n = 20;
    for i=0, n do
         af:SetKey( _TRACK_X, time * i/n, x + math.sin(M_TWO_PI * i/n)*amplitude );
    end
    
    af:Play();
end

-- функция создает эффектор для покачивания (поворот)
function SwayObjectByAngle(objName, amplitude, time)
    local swayObj = play_field:GetObject( objName )
    
    local af = CreateAnimationEffector()
    af:AttachTo(swayObj)
    af:SetMode(_ANIM_FWD + _ANIM_LOOP);
    
    amplitude = amplitude * M_PI / 180;
    local angle = swayObj:GetAngle()
    local n = 20;
    for i=0, n do
         af:SetKey( _TRACK_A, time * i/n, angle + math.sin(M_TWO_PI * i/n)*amplitude );
    end
    
    af:Play();
end

--функция создает анимационную группы объектов.
--Эффект проигрывания анимации с помощью изменения прозрачности объектов
function CreateAnimGroup(...)
    local args      = {...}
    
    local ag        = {};
    ag.objects      = {};
    ag.effectors    = {};
    ag.count        = 0;
    ag.index        = 0;
    ag.hide_effect  = nil;
    ag.show_effect  = nil;
    
    for i,v in ipairs(args) do
        ag.objects[ag.count] = v;
        ag.count = ag.count + 1;
        
        local obj = play_field:GetObject(v);
        obj:GetAnimation():SetColor('0xFFFFFFFF');
        
        if i == 1 then
            obj:SetOpacity(1.);
        else
            obj:SetOpacity(0.);
        end
    end
    
    ag.isAnim = function()
        for i, v in pairs(ag.effectors) do
            if not v:IsPaused() then
                return true;
            end
        end
        
        return false;
    end
    
    ag.clearEffectors = function()
        for i, v in pairs(ag.effectors) do
            v:Destroy();
            v = nil;
        end
    end
    
    ag.setIndex = function(index)
        assert(index >= 0 and index < ag.count);
        
        local hide_obj_name = ag.objects[ag.index];
        local show_obj_name = ag.objects[index];
        
        play_field:GetObject(hide_obj_name):SetOpacity(0.);
        play_field:GetObject(show_obj_name):SetOpacity(1.);
        
        ag.index = index;
    end
    
    ag.changeIndexTo = function(index, time)
        assert(index >= 0 and index < ag.count);
        
        if not time then
            time = 0;
        end
        
        if index == ag.index then
            return;
        end
        
        ag.clearEffectors();
        
        local hide_obj = play_field:GetObject( ag.objects[ag.index] );
        local show_obj = play_field:GetObject( ag.objects[index]    );
        
        local eff1 = CreateAnimationEffector();
        local eff2 = CreateAnimationEffector();
        
        eff1:SetMode(_ANIM_FWD + _ANIM_NOLOOP);
        eff2:SetMode(_ANIM_FWD + _ANIM_NOLOOP);
        
        eff1:AttachTo( hide_obj );
        eff2:AttachTo( show_obj );
        
        eff1:SetKey(_TRACK_O, 0.,   1.);
        eff1:SetKey(_TRACK_O, time, 0.);
        
        eff2:SetKey(_TRACK_O, 0.,   0.);
        eff2:SetKey(_TRACK_O, time, 1.);
        
        ag.effectors[ag.index] = eff1
        ag.effectors[index]    = eff2
        
        ag.index = index;
    end
    
    ag.animTo = function(index, time)
        assert(index >= 0 and index < ag.count);
        
        local steps = index - ag.index;
        
        if steps == 0 then
            return;
        end
        
        local dTime = time  / math.abs(steps);
        local step  = steps / math.abs(steps);
        local hide_obj;
        local show_obj;
        local time_past = 0;
        
        ag.clearEffectors();
        
        while ag.index ~= index do
            hide_obj = play_field:GetObject(ag.objects[ag.index]);
            show_obj = play_field:GetObject(ag.objects[ag.index + step]);
            
            if ag.effectors[ag.index] == nil then
                local eff = CreateAnimationEffector();
                ag.effectors[ag.index] = eff
                
                eff:AttachTo( hide_obj );
                eff:SetMode(_ANIM_FWD + _ANIM_NOLOOP);
                eff:SetKey(_TRACK_O, 0., 1.);
            end
            
            if ag.effectors[ag.index + step] == nil then
                local eff = CreateAnimationEffector();
                ag.effectors[ag.index + step] = eff
                
                eff:AttachTo( show_obj );
                eff:SetMode(_ANIM_FWD + _ANIM_NOLOOP);
                eff:SetKey(_TRACK_O, 0., 0.);
            end
            
            ag.effectors[ag.index]:SetKey       (_TRACK_O, time_past + dTime, 0.);
            ag.effectors[ag.index + step]:SetKey(_TRACK_O, time_past + dTime, 1.);
            
            time_past = time_past + dTime;
            ag.index  = ag.index + step;
        end
        
        for i, v in pairs(ag.effectors) do
            v:Play();
        end
    end
    
    if ag.count ~= 0 then
        ag.setIndex(0);
    end
    
    return ag;
end

function PlayAnimationPeriodically(anim_name, period)
    local anim_obj  = play_field:GetObject(anim_name);
    local time_anim = GetAnimationLength(anim_obj:GetAnimation());
    local effector  = CreateAnimationEffector()
    effector:AttachTo(anim_obj)
    effector:SetMode(_ANIM_LOOP)
    effector:SetKey(_TRACK_O, 0.,                     1.);
    effector:SetKey(_TRACK_O, time_anim - 0.0001,     1.);
    effector:SetKey(_TRACK_O, time_anim,              0.);
    effector:SetKey(_TRACK_O, period * time_anim - 0.0001, 0.);
    effector:SetKey(_TRACK_O, period * time_anim,          1.);
    effector:Play();
    
    return effector;
end

function PlayAnimationPeriodicallyVsDelay(anim_name, max_delay, soundName)
    local anim = play_field:GetObject( anim_name ):GetAnimation();
    local time = GetAnimationLength( anim );
    
    anim:SetFrame(0);
    anim:Play();
    play_field:GetObject(anim_name):SetVisible(true);
    
    if soundName then
        PlaySound(soundName);
    end
    
    TimerDoString("play_field:GetObject('"..anim_name.."'):SetVisible(false)", time,                        play_field:GetTimer());
    if soundName then
        TimerDoString("PlayAnimationPeriodicallyVsDelay('"..anim_name.."', "..max_delay..", '"..soundName.."')",  time+math.random(max_delay), play_field:GetTimer());
    else
        TimerDoString("PlayAnimationPeriodicallyVsDelay('"..anim_name.."', "..max_delay..")",  time+math.random(max_delay), play_field:GetTimer());
    end
end

function CreateAnimationsGroup(name, ...)
    local args = {...}
    
    local obj;
    local obj_name;
    local obj_delay;
    
    local ag   = {};
    ag.anims   = {};
    ag.delay   = {};
    ag.count   = 0;
    ag.current = 0;
    ag.name    = name;
    ag.is_playing = false;
    
    if not game_state.anim_groups then
        game_state.anim_groups = {};
    end
    
    game_state.anim_groups[name] = ag;
    
    for i=1, table.getn(args), 2 do
        obj_name  = args[i];
        obj_delay = args[i + 1];
        
        ag.count = ag.count + 1;
        
        ag.anims[ag.count] = obj_name;
        ag.delay[obj_name] = obj_delay;
        
        obj = play_field:GetObject(obj_name);
        obj:GetAnimation():SetMode(HGEANIM_FWD + HGEANIM_NOLOOP);
        obj:GetAnimation():Stop();
        obj:SetVisible(false);
    end
    
    ag.Play = function()
        if not ag.is_playing then
            if ag.current ~= 0 then
                obj_name = ag.anims[ag.current]
                
                play_field:GetObject(obj_name):GetAnimation():Stop();
                play_field:GetObject(obj_name):SetVisible(false);
            end
        
            ag.is_playing = true;
            ag.current    = 0;
            ag.StartNextAnim();
        end
    end
    
    ag.Stop = function()
        if ag.current ~= 0 then
            obj_name = ag.anims[ag.current]
            play_field:GetObject(obj_name):GetAnimation():Stop();
        end
        ag.is_playing = false;
    end
    
    ag.StartNextAnim = function()
        if ag.is_playing then
            local obj_name;
            local obj;
            
            if ag.current ~= 0 then
                obj_name = ag.anims[ag.current]
                
                play_field:GetObject(obj_name):GetAnimation():Stop();
                play_field:GetObject(obj_name):SetVisible(false);
            end
            
            ag.current = (ag.current < ag.count) and ag.current + 1 or 1;
            
            obj_name = ag.anims[ag.current]
            obj      = play_field:GetObject(obj_name);
            
            obj:GetAnimation():SetFrame(0);
            obj:GetAnimation():Play();
            obj:SetVisible(true);
            
            local time_next_anim = GetAnimationLength(obj:GetAnimation()) + ag.delay[obj_name];
            
            TimerDoString("game_state.anim_groups['"..ag.name.."'].StartNextAnim()", time_next_anim, play_field:GetTimer());
        end
    end
    
    return ag;
end


function CreateScrollInfo(obj_name, targetX, targetY)
    local si     = {};
    si.obj       = play_field:GetObject(obj_name);
    si.sx        = si.obj:GetX();
    si.sy        = si.obj:GetY();
    
    local dx = targetX - si.sx;
    local dy = targetY - si.sy;
    
    si.a         = math.atan2(dy, dx);
    si.len       = math.sqrt(dx^2 + dy^2);
    
    si.dx        = 0;
    si.dy        = 0;
    si.draging   = false;
    
    si.startDrag = function()
        if not si.draging then
            local mPos = GetMousePos();
            
            si.dx = mPos.x - si.obj:GetX();
            si.dy = mPos.y - si.obj:GetY();
            
            si.draging = true;
        end
    end
    
    si.stopDrag = function()
        si.draging = false;
    end
    
    si.update = function()
        if si.draging then
            local mPos = GetMousePos();
            local mDx = mPos.x - si.dx - si.sx;
            local mDy = mPos.y - si.dy - si.sy;
            
            local mA   = math.atan2(mDy, mDx);
            local mLen = math.sqrt(mDx^2 + mDy^2);
            
            local a = si.a - mA;
            local p = mLen * math.cos(a);
            
            if p > si.len then
                p = si.len;
            elseif p < 0 then
                p = 0;
            end
            
            
            local x = p * math.cos(si.a)
            local y = p * math.sin(si.a)
            
            si.obj:SetXY(si.sx + x, si.sy + y);
        end
    end
    
    return si;
end