function AssignZoneMethods(zone)
    zone.IsVisible = function()
        for i, v in pairs(zone.objects) do
            if v == true and play_field:HasObject(i) then
                local obj = play_field:GetObject(i)
                if obj:IsVisible() and obj:GetOpacity() > 0. then
                    return true
                end
            end
        end

        return false
    end

    zone.SetVisible = function(is_visible)
        if is_visible == zone.IsVisible() then
            return
        end
        if zone.blocker then
            return
        end
        play_field:HideHint()
    
        if is_visible then
            game_state.active_zone = zone
        else
            game_state.active_zone = nil			
        end
        
        zone.close_button:SetVisible(is_visible)
        if zone.object_under_mouse_name then
            zone.object_under_mouse_name:SetVisible(is_visible)
        end

        if is_visible then
--            TimerDo(play_field:GetTimer(), ZOOM_ZONE_APPEARING_TIME, function()
                                                                        zone.close_button:Go()
                                                                        game_state.locker:AddRef()
--                                                                      end )
        else
--            TimerDo(play_field:GetTimer(), ZOOM_ZONE_APPEARING_TIME, function()
                                                                        zone.close_button:Stop()
                                                                        game_state.locker:Release()
--                                                                     end) 
        end

        for i, v in pairs(zone.objects) do
            if v == true and play_field:HasObject(i) then
                obj = play_field:GetObject(i)
                if is_visible then
                    obj:ChangeOpacity(0, obj:GetMaxOpacity(), ZOOM_ZONE_APPEARING_TIME)
                else
                    obj:ChangeOpacity(obj:GetMaxOpacity(), 0, ZOOM_ZONE_APPEARING_TIME)
                end
            end
        end
        
        if is_visible then
			TimerDo(play_field:GetTimer(), ZOOM_ZONE_APPEARING_TIME, zone.OnShown)
			zone.OnShownImmediate()
		else
			TimerDo(play_field:GetTimer(), ZOOM_ZONE_APPEARING_TIME, zone.OnHidden)
			zone.OnHiddenImmediate()
		end	
        
        zone.back_object:SetVisible(is_visible)
    end
    
    zone.SetOffset = function(offsetX, offsetY)        
        for i, v in pairs(zone.objects) do
            if play_field:HasObject(i) then
                local x = play_field:GetObject(i):GetX()
                local y = play_field:GetObject(i):GetY()
                play_field:GetObject(i):SetXY(x + offsetX, y + offsetY)
             else
                zone.RemoveObject(i)
             end
        end

        zone.offset_x = offsetX	
        zone.offset_y = offsetY
    end

    zone.HasObject = function(object_name)
        return zone.objects[object_name] == true
    end

    zone.AddObject = function(object_name)
        zone.objects[object_name] = true
        if zone.IsVisible() then
            local object = play_field:GetObject(object_name)
--[[            
            local x = object:GetX()
            local y = object:GetY()
            object:SetXY(x + zone.offset_x, y + zone.offset_y)
]]
            object:ChangeOpacity(0, object:GetMaxOpacity(), ZOOM_ZONE_APPEARING_TIME)
        end
    end

    zone.RemoveObject = function(object_name, change_opacity)
        if change_opacity == nil then
            change_opacity = true
        end
    
        zone.objects[object_name] = false
        if zone.IsVisible() and change_opacity then
            local object = play_field:GetObject(object_name)
            object:ChangeOpacity(object:GetMaxOpacity(), 0, ZOOM_ZONE_APPEARING_TIME)
        end
    end
    
    zone.OnShown = function()
    end
    
    zone.OnShownImmediate = function()
    end
    
    zone.OnHidden = function()
    end
    
    zone.OnHiddenImmediate = function()
    end
    
    zone.GetMinObjectZ = function()
        local minz = 1000
        for i, v in pairs(zone.objects) do
            if play_field:HasObject(i) then            
                local obj = play_field:GetObject(i)
                if obj:GetZ() < minz then
                    minz = obj:GetZ()
                end
            else
                zone.RemoveObject(i)
            end
		end
		return minz
    end
    
    zone.GetMaxObjectZ = function()
        local maxz = -1
        for i, v in pairs(zone.objects) do
            if play_field:HasObject(i) then
                local obj = play_field:GetObject(i)
                if obj:GetZ() > maxz then
                    maxz = obj:GetZ()
                end
            else    
                zone.RemoveObject(i)
            end
		end
		return maxz
    end
    
    zone.Update = function(dt)
        if game_state.locker:GetNReferences() == 0 then
            zone.highlight_delay = zone.highlight_delay - dt
            if zone.highlight_delay < 0 then
                zone.highlight_delay = zone.max_highlight_delay
                zone.Highlight()
            end
        end
    end
    
    zone.SetOffsetHighlight = function(x, y)
        if zone.object_name_highlight then
            local obj_highlight  = play_field:GetObject(zone.object_name_highlight)
            local old_x = obj_highlight:GetX()
            local old_y = obj_highlight:GetY()
            obj_highlight:SetXY(old_x + x, old_y + y)
        end            
    end
    
    zone.Highlight = function()
        local effect_time = 0.9
        
        local obj_highlight  = play_field:GetObject(zone.object_name_highlight)
    
        local af = CreateAnimationEffector()
        af:AttachTo( obj_highlight )
        af:SetMode(_ANIM_NOLOOP)
        af:SetKey(_TRACK_O, effect_time * 0.,   0.)
        af:SetKey(_TRACK_A, effect_time * 0.,   0.)
        af:SetKey(_TRACK_S, effect_time * 0.,   0.3)
        af:SetKey(_TRACK_O, effect_time * 0.05, 1.)
        af:SetKey(_TRACK_S, effect_time * 0.25, 1.)
        af:SetKey(_TRACK_O, effect_time * 0.45, 1.)
        af:SetKey(_TRACK_A, effect_time * 0.45, 35. * M_PI / 180.)
        af:SetKey(_TRACK_S, effect_time * 0.45, 1.4)
        af:SetKey(_TRACK_O, effect_time * 0.70, 1.)
        af:SetKey(_TRACK_A, effect_time * 1.,   50. * M_PI / 180.)
        af:SetKey(_TRACK_S, effect_time * 1.,   1.)
        af:SetKey(_TRACK_O, effect_time * 1.,   0.)
        af:Play()
    end

    zone.ChangeObjOpacity = function(obj_name, start, finish, time)
        for i, v in pairs(zone.objects) do
            if play_field:HasObject(i) and i == obj_name then
                local obj = play_field:GetObject(i)
                obj:ChangeOpacity(start, finish, time)
                obj:SetMaxOpacity(finish)
                zone.Blocked(time)
                return true
            end
        end

        return false
    end
    
    zone.Blocked = function(time)
        zone.UnBlocked()
        zone.blocker = true
        if time then
            zone.timer_blocker = TimerDo(play_field:GetTimer(), time, function()
                                                                        zone.blocker = false
                                                                        zone.timer_blocker = nil
                                                                      end)
        end
    end
    
    zone.UnBlocked = function()
        if zone.timer_blocker then
            RemoveTimerFunction(zone.timer_blocker)
            zone.timer_blocker = nil
            zone.blocker = false
        end
    end
    
    zone.IsBlocked = function()
        return zone.blocker
    end
    
    zone.SetManualUnregister = function(value)
        zone.manual_unregister = value
    end
    
    zone.IsManualUnregister = function()
        return zone.manual_unregister
    end
    
    zone.IsRegistered = function()
        return zone.registered
    end
end

function CreateZoomZone(close_button_message, trigger_name, ...)
    local arg = {...}

    local zone = {}

    zone.close_button = CreateCloseButton(ZOOM_ZONE_CLOSE_BUTTON_POS, close_button_message, play_field:GetLevelScreen())
    zone.close_button_message = close_button_message
    zone.close_button:SetVisible(false)
    zone.close_button:Stop()
    
    zone.offset_x = 0
    zone.offset_y = 0
    
    zone.manual_unregister = false

    local biggest_object
    zone.objects = {}
    for i, v in ipairs(arg) do
        if play_field:HasObject(v) then
            local obj = play_field:GetObject(v)

            if biggest_object == nil or
               obj:GetAnimation():GetWidth() > biggest_object:GetAnimation():GetWidth() then
                biggest_object = obj
            end

            obj:GetAnimation():SetColor('0xFFFFFFFF')
            obj:SetOpacity(0.)
            zone.objects[v] = true
            if game_state.min_zone_z == nil or
               (play_field:HasObject(v) and play_field:GetObject(v):GetZ() < game_state.min_zone_z)
            then
                game_state.min_zone_z = play_field:GetObject(v):GetZ()
            end
        end
    end

    if biggest_object then
        zone.close_button:AttachTo(biggest_object)
    end

    AssignZoneMethods(zone)
    
    if trigger_name ~= '' then		
        RegisterZoomZone(zone, trigger_name)
    end
    
    if not game_state.unregistered_zoom_zones then
        game_state.unregistered_zoom_zones = {}            
    end
    
    return zone
end

function RegisterZoomZone(zone, trigger_name)
    if not game_state.zones then
        game_state.zones = {}
    end
    
    if zone.registered then
	    return;
    end
    
    if game_state.unregistered_zoom_zones then
        for i, v in ipairs(game_state.unregistered_zoom_zones) do
            if v == trigger_name then
                return false
            end
        end
    end

    local info = {}
    info.zone = zone
    info.trigger_name = trigger_name
    table.insert(game_state.zones, info)

    custom_cursors[trigger_name] = GetCursorZoom()
    zone.trigger_name = trigger_name
    zone.blocker = false
    zone.timer_blocker = nil
    zone.max_highlight_delay = 5
    zone.highlight_delay = GetRandomFloat(1, 5)
    
    ----------------------------------------------------------------------
    -- Create border for zoom zone
    local anim = CreateAnimation('data/Texture/GUI/Zoom/Border.png',0,0,670,537,1,0)
	anim:SetHotSpot(335,269)
	anim:SetBlendMode(2)
	anim:SetColor('0xFFFFFFFF')
	local border_name = 'Border' .. trigger_name
	local obj = play_field:CreateObject(border_name ,anim,512,384,302,0,1,1)	
	local fBorderZ = zone.GetMaxObjectZ()
	obj:SetZ(zone.GetMaxObjectZ() + 1)
	zone.border_object = obj
	zone.objects[border_name] = true
	obj:SetOpacity(0)
	zone.close_button:SetScrZ(fBorderZ + 100)
	
	----------------------------------------------------------------------
    -- Create pixel object for blocked
    local anim = CreateAnimation('data/Texture/Scenes/Common/pixel.jpg',0,0,8,8,1,0)
	anim:SetHotSpot(4,4)
	anim:SetBlendMode(2)
	anim:SetColor('0x01FFFFFF')
	local obj = play_field:CreateObject('Back' .. trigger_name,anim,512,384,100,0,1024,768)
	obj:SetZ(zone.GetMinObjectZ() - 1)
	zone.back_object = obj
	obj:SetVisible(false)
	
	----------------------------------------------------------------------
	-- create Highlight
	local anim = CreateAnimation('data/Texture/Effect/ObjectHighlight.png',0,0,47,47,1,0)
	anim:SetHotSpot(23.5,23.5)
	anim:SetBlendMode(2)
	anim:SetColor('0xFFFFFFFF')
	zone.object_name_highlight = 'highlight' .. trigger_name
	local obj_highlight = play_field:CreateObject(zone.object_name_highlight,anim,512,384,140,0,1,1)
    play_field:GetObject(zone.object_name_highlight):SetOpacity(0)
    play_field:IgnoreOnTestPoint(zone.object_name_highlight)
    
    local obj_trigger = play_field:GetObject(info.trigger_name)
    obj_highlight:SetXY(obj_trigger:GetX(), obj_trigger:GetY())
    obj_highlight:SetZ(obj_trigger:GetZ() + 1)

--  if trigger_name and play_field:HasObject(trigger_name) then
--      play_field:GetObject(trigger_name):AddParticleSystem('data/ParticleSystem/ObjectHighlight.psi', 'PhotoZoneHighlightParticle', true);
--  end

    if zone.trigger_name and HasGameState('ZoomZoneUnregister', game_state.scene_path.."("..zone.trigger_name..")") then
        UnregisterZoomZone(zone)
    end
    
    zone.registered = true
    zone.SetOffset(100, 0)
   
	return true;
end

function UnregisterZoomZone(zone)
    if play_field:HasObject(zone.trigger_name) then
        local value = game_state.scene_path.."("..zone.trigger_name..")"
        if not HasGameState('ZoomZoneUnregister', value) then
            AddGameState('ZoomZoneUnregister', value)
        end            
        play_field:GetObject(zone.trigger_name):ResetParticleSystem()
        play_field:DeleteObject(zone.trigger_name)
    end
    if not game_state.unregistered_zoom_zones then
        game_state.unregistered_zoom_zones = {}
    end
    table.insert(game_state.unregistered_zoom_zones, zone.trigger_name)
end

function IsUnregisterZoomZone(zone)
    if game_state.unregistered_zoom_zones then
        for i, v in ipairs(game_state.unregistered_zoom_zones) do
            if zone.trigger_name == trigger_name then
                return true
            end
        end
    end
    
    return false
end

function CloseZoomZone(zone)
end

function IsExistOpenZoomZone()
    for i, info in ipairs(game_state.zones) do
        if info.zone.IsVisible() then
            return true
        end
    end

    return false
end

function IsCanCloseZoomZone(zone)
    local pf = play_field
    local level = pf:GetLevelScreen()

	for i, v in pairs(game_state.active_objects) do
		if zone.HasObject(i) and v then
			return false
		end
	end

    for i, v in pairs(zone.objects) do
        if play_field:HasObject(i) then
            local obj = play_field:GetObject(i)
            local obj_name = obj:GetName()
            
            -- Проверка на таски
            if level:IsTaskObject(obj_name) then
                local task = level:GetTask(obj_name)
                if task:GetNumberOfObjects() > 0 then
                    return false
                end
            end -- if level:IsTaskObject(obj_name) then
            
            -- Проверка на фотообъекты
            if game_state.photo_objects then
                for i, photo_info in ipairs(game_state.photo_objects) do
                    if  photo_info.tool_target then
                        for k, target in ipairs(photo_info.tool_target) do            
                            if target == obj_name then
                                return false
                            end
                        end
                    end
                end -- for i, photo_info in ipairs(game_state.photo_objects) do
            end            
            
            -- Проверка на активные зоны
            if game_state.active_objects then
                for name_active_obj, info_active in pairs(game_state.active_objects) do
                    if name_active_obj == obj_name then
                        return false
                    end 
                end
            end
        end -- if v == true and play_field:HasObject(i) then
    end -- for i, v in pairs(zone.objects) do
    
    return true
end

function DisableZoomZoneRequest(zone)
    if IsUnregisterZoomZone(zone) then
        return
    end
    
    if zone.manual_unregister then
        return
    end
    --[[
    if zone.IsVisible() == false then
        return;
    end
    ]]
    if IsCanCloseZoomZone(zone) then
        UnregisterZoomZone(zone)
        if zone.IsVisible() == true and game_state.locker:GetNReferences() > 0 then
            zone.SetVisible(false)
        end
    end
end

function HintCloseButton()
	for i, v in ipairs(game_state.zones) do
		if v.zone.IsVisible() then
			local z = v.zone.border_object:GetZ()
			play_field:Hint(v.zone.close_button:GetScrX() + 20, v.zone.close_button:GetScrY(), z)
			return true
		end
	end
	return false
end


