dofile('data/Scripts/match3/M3Screen.lua')
dofile('data/Scripts/match3/field/M3Field.lua')
dofile('data/Scripts/match3/panel/M3AbilitiesPanel.lua')
dofile('data/Scripts/match3/help/AboutMatch3.lua')

-------------------------------------------------------------------------------
class 'M3Game'
-------------------------------------------------------------------------------

M3Game.TEXTURE_PATH       = 'Data/Textures/Match3/'
M3Game.COMMON_SAVE_KEY    = 'M3_'
M3Game.APPEARING_TIME     = 1
M3Game.ABILITY_FLY_TIME   = 1
M3Game.START_X_CORRECTION = 10
-------------------------------------------------------------------------------
local split_string = function(s, delimiter) 
    local result = {}
    local delPos, delEndPos = s:find(delimiter)
    
    while delPos ~= nil do
        local subStr = s:sub(1, delPos-1)
        table.insert(result, subStr)
        s = s:sub(delEndPos + 1)
        
        delPos, delEndPos = s:find(delimiter)
    end
    
    if s:len() > 0 then
        table.insert(result, s)
    end
    
    return result
end
-------------------------------------------------------------------------------

function M3Game:__init()
    --Log('!!! M3Game:create')
    self.fieldPos = Vector2f(0,0)     -- позиция начала появлениея поля
    self.startPos = Vector2f(0,0)     -- позиция поля в игровом положении
    self.endPos   = Vector2f(0,0)     -- конечная позиция поля
    
    self.bOpen      = false
    self.bCompleted = false
    
    self.zoom  = nil       -- зум зона, к якорю которой аттачится поле
    self.field = nil       -- поле match3
    self.panel = nil       -- панель с индикацией дополнительных возможностей
    self.id    = nil       -- тесктовый идентификатор Match3. Используется для генерации ключей сохрануи. (Формируется: имя_локации..номер_прохождения_ho)
    self.info_screen = nil
    
    self.flyingAim     = {}
    self.flyingAbility = {}
    
    self:createScreen()
    self:createField()
    self:createPanel()    
end
-------------------------------------------------------------------------------

function M3Game:destroy()
    for itemUniqueId, info in pairs(self.flyingAim) do
        --Log('!!! destroy flyingAim ='..info.hoItemName)
        info.action:kill()
        info.action = nil
        
        info.item:destroy()
        info.item = nil
    end
    
    for itemUniqueId, info in pairs(self.flyingAbility) do
        --Log('!!! destroy flyingAbility ='..info.hoItemName)
        
        if info.action then
            info.action:kill()
            info.action = nil
        end
        
        info.item:destroy()
        info.item = nil
    end
    
    self.flyingAim = nil
    self.flyingAbility = nil
    
    --Log('!!! M3Game:destroy')
    self.field:dropAbility(0, 0)
    
    
    self.field:destroy()
    self.panel:destroy()
end
-------------------------------------------------------------------------------

function M3Game:getField()
    return self.field
end
-------------------------------------------------------------------------------

function M3Game:getFlyingAbilities(ability)
    local count = 0
    
    for itemUniqueId, info in pairs(self.flyingAbility) do
        if ability == nil or info.item.ability == ability then
            count = count + 1
        end
    end
    
    return count
end
-------------------------------------------------------------------------------

function M3Game:createScreen()     
    local entities = CreateSpriteEntity('')
    entities:setName('area_zoom_Match3')        
    entities:attachTo(current_scene:getPlayField():getMainEntity())        
    entities:hide()

    local pathDir   = 'Data/Scripts/match3'
    local nameZoom  = 'M3Screen'
    local pathZoomLua = pathDir..'/'..nameZoom..'.lua'
    dofile( pathZoomLua )
   
    self.zoom = Helpers.CallFunctionByName(nameZoom, 'Match3', ZOOM_TYPE_MATCH3, current_scene:getPlayField())    
    self.zoom.animated_close_open = false
    self.zoom.is_closable = false
    return self.zoom
end
-------------------------------------------------------------------------------

function M3Game:createField()
    self.field = M3Field()
    self.field:registerEvents(current_scene:getPlayField().eventDispatcher)
    self.field.ed:addEventListener('Change',          bind(self.onFieldChanged,    self))
    self.field.ed:addEventListener('AimPassed',       bind(self.onAimPassed,       self))
    self.field.ed:addEventListener('AbilityPassed',   bind(self.onAbilityPassed,   self))
    self.field.ed:addEventListener('AbilityAffected', bind(self.onAbilityAffected, self))
    self.field.ed:addEventListener('AbilityDropped',  bind(self.onAbilityDropped,  self))
    
    local anchor = self.field:getAnchor()
    anchor:attachTo(self.zoom:getMainEntity())
    anchor:setXY(0,0)
    anchor:setOrder(1)
end
-------------------------------------------------------------------------------

function M3Game:createPanel()
    self.panel = M3AbilitiesPanel()
    self.panel:registerEvents(current_scene:getPlayField().eventDispatcher)
    self.panel:addEventListener('AbilityChosen', bind(self.onAbilityChosen, self))
    
    local anchor = self.panel:getAnchor()
   -- self.zoom:AddObject(anchor:getName())
    anchor:attachTo(self.zoom:getMainEntity())
    anchor:setScrXY(WORK_W+171,SCR_H/2)
    anchor:setOrder(1)
end
-------------------------------------------------------------------------------

function M3Game:init(id)
    self.id = id

    local scene = current_scene:getPlayField():getMainEntity()
    local ho_logic = current_scene:getPlayField().ho_logic   
    
    if not self:load() then
	    local info = GetMatch3Level(id)
	    --info.aimsLeft = GetCurrentLevel():GetAdditionalLogic():GetIndicator().MAIN_TASKS_COUNT
	    info.aimsLeft = ho_logic:getTaskObjectsCount()
	    Console:pushText('!!! aimsLeft = '..info.aimsLeft)
	    
	    self:initField(info, true)
	    self:initPanel(info)
	end
	
	self.fieldPos.x = -self.field:getWidth()  / 2
	self.fieldPos.y = -self.field:getHeight() / 2
	
	self.startPos.x = -(self.zoom:getMainEntity():getScrX() + self.field:getWidth() + M3Game.START_X_CORRECTION)
	self.startPos.y = self.fieldPos.y
	
	self.endPos.x = SCR_W - self.zoom:getMainEntity():getScrX()  + 50 -- self.zoom.empty_obj:GetScrX()
	self.endPos.y = self.fieldPos.y
	
	self.field:getAnchor():setXY(self.fieldPos.x,
                                 self.fieldPos.y)
    
    if HasGameState('M3_OPEN', current_scene:getPlayField().name) then
        self:open()
        current_scene:getPanel():setMode(EPanelMode.MATCH3, 0)
    end
end
-------------------------------------------------------------------------------

function M3Game:load()
    if self:isSaved() then
        local level     = current_scene:getPlayField()
        local keyPrefix = self:_getSaveKeyPrefics()
        local info      = {}
        
        info.itemsInfo           = self:_stringTOitemsInfo(GetGameState(keyPrefix..'ITEMS_INFO'):get(0))
        info.cols                = tonumber(GetGameState(keyPrefix..'COLS'):get(0))
        info.rows                = tonumber(GetGameState(keyPrefix..'ROWS'):get(0))
        
        info.aimsLeft            = tonumber(GetGameState(keyPrefix..'AIMS_LEFT'       ):get(0))
        info.aimMinInterval      = tonumber(GetGameState(keyPrefix..'AIM_MIN_INTERVAL'):get(0))
        info.aimMaxInterval      = tonumber(GetGameState(keyPrefix..'AIM_MAX_INTERVAL'):get(0))
        info.aimCurInterval      = tonumber(GetGameState(keyPrefix..'AIM_CUR_INTERVAL'):get(0))
        
        info.abilityMinInterval  = tonumber(GetGameState(keyPrefix..'ABILITY_MIN_INTERVAL'):get(0))
        info.abilityMaxInterval  = tonumber(GetGameState(keyPrefix..'ABILITY_MAX_INTERVAL'):get(0))
        info.abilityCurInterval  = tonumber(GetGameState(keyPrefix..'ABILITY_CUR_INTERVAL'):get(0))
        
        info.lockedMaxCount      = tonumber(GetGameState(keyPrefix..'LOCKED_MAX_COUNT'):get(0))
        info.lockRate            = tonumber(GetGameState(keyPrefix..'LOCK_RATE'):get(0))
        info.cells               = GetGameState(keyPrefix..'CELLS'):get(0)
        info.items               = GetGameState(keyPrefix..'ITEMS'):get(0)
        info.locks               = GetGameState(keyPrefix..'LOCKS'):get(0)
        info.charges             = GetGameState(keyPrefix..'CHARGES'):get(0)
        info.abilityCounts       = self:_parseFlyingAbilityItemsString(GetGameState(keyPrefix..'ABILITY_COUNTS'):get(0))
        info.abilityMaxCounts    = self:_parseFlyingAbilityItemsString(GetGameState(keyPrefix..'ABILITY_MAX_COUNTS'):get(0))
        info.abilityAffectIDs    = self:_stringTOabilityAffectIDs(GetGameState(keyPrefix..'ABILITY_AFFECT_IDS'):get(0))
        
        if info.abilityCounts then
            local flyingAbilityCounts = self:_parseFlyingAbilityItemsString(GetGameState(keyPrefix..'FLYING_ABILITY_ITEMS'):get(0))
            
            if flyingAbilityCounts then
                for ability, count in pairs(info.abilityCounts) do
                    if flyingAbilityCounts[ability] then
                        info.abilityCounts[ability] = count + flyingAbilityCounts[ability]
                    end
                end
            end
        end
        
        self:initField(info)
        self:initPanel(info)
        
        local itemsStr       = GetGameState(keyPrefix..'FLYING_AIM_ITEMS'):get(0)
        local flyingAimItems = split_string(itemsStr, '|')
        
        level.timer:add(function()
                            for i, hoItemName in ipairs(flyingAimItems) do
                                current_scene:getPlayField().ho_logic:autoFindObject(hoItemName)
                            end
                        end,
                        0)
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Game:save()
    local keyPrefix       = self:_getSaveKeyPrefics()
    local itemsInfoString = self:_itemsInfoTOstring(self.field:getItemsInfo())
    local abilityAffectIDsString = self:_abilityAffectIDsTOstring(self.field.abilityAffectIDs)
    
    SetGameState(keyPrefix..'ITEMS_INFO',            itemsInfoString)
    SetGameState(keyPrefix..'COLS',                  tostring(self.field.cols))
    SetGameState(keyPrefix..'ROWS',                  tostring(self.field.rows))
    
    SetGameState(keyPrefix..'AIMS_LEFT',             tostring(self.field.aimsLeft))
    SetGameState(keyPrefix..'AIM_MIN_INTERVAL',      tostring(self.field.aimMinInterval))
    SetGameState(keyPrefix..'AIM_MAX_INTERVAL',      tostring(self.field.aimMaxInterval))
    SetGameState(keyPrefix..'AIM_CUR_INTERVAL',      tostring(self.field.aimCurInterval))
    
    SetGameState(keyPrefix..'ABILITY_MIN_INTERVAL',  tostring(self.field.abilityMinInterval))
    SetGameState(keyPrefix..'ABILITY_MAX_INTERVAL',  tostring(self.field.abilityMaxInterval))
    SetGameState(keyPrefix..'ABILITY_CUR_INTERVAL',  tostring(self.field.abilityCurInterval))
    
    SetGameState(keyPrefix..'LOCKED_MAX_COUNT',      tostring(self.field.lockedMaxCount))
    SetGameState(keyPrefix..'LOCK_RATE',             tostring(self.field.lockRate))
    SetGameState(keyPrefix..'CELLS',                 self.field:getCellsString())
    SetGameState(keyPrefix..'ITEMS',                 self.field:getItemsString())
    SetGameState(keyPrefix..'LOCKS',                 self.field:getLocksString())
    SetGameState(keyPrefix..'CHARGES',               self.field:getChargesString())
    
    SetGameState(keyPrefix..'FLYING_AIM_ITEMS',      self:_getFlyingAimSaveString())
    SetGameState(keyPrefix..'FLYING_ABILITY_ITEMS',  self:_getFlyingAbilitySaveString())
    SetGameState(keyPrefix..'ABILITY_COUNTS',        self.panel:getAbilityCountsSaveString())
    SetGameState(keyPrefix..'ABILITY_MAX_COUNTS',    self:_getAbilityMaxCountsSaveString())
    SetGameState(keyPrefix..'ABILITY_AFFECT_IDS',    abilityAffectIDsString)
    
    SetGameState(keyPrefix..'SAVED', 'true')
end
-------------------------------------------------------------------------------

function M3Game:clearSave()
    local keyPrefix = self:_getSaveKeyPrefics()
    local keys = {'ITEMS_INFO',
                  'COLS',
                  'ROWS',
                  'AIMS_LEFT',
                  'AIM_MIN_INTERVAL',
                  'AIM_MAX_INTERVAL',
                  'AIM_CUR_INTERVAL',
                  'LOCKED_MAX_COUNT',
                  'LOCK_RATE',
                  'CELLS',
                  'ITEMS',
                  'LOCKS',
                  'CHARGES',
                  'FLYING_AIM_ITEMS',
                  'FLYING_ABILITY_ITEMS',
                  'ABILITY_COUNTS',
                  'ABILITY_MAX_COUNTS',
                  'ABILITY_AFFECT_IDS',
                  'SAVED'}
    
    for _, key in ipairs(keys) do
        local state = keyPrefix..key
        
        if HasGameState(state) then
            RemoveGameState(state)
        end
    end
end
-------------------------------------------------------------------------------

function M3Game:initField(info, bFillRandom)
    self.field:setItemsInfo(info.itemsInfo)
    self.field:setAbilityCountBalance(info.abilityCounts)
    self.field:setAbilityMaxCounts(info.abilityMaxCounts)
    self.field:setAbilityAffectIDs(info.abilityAffectIDs)
    
    self.field.aimMinInterval     = info.aimMinInterval
    self.field.aimMaxInterval     = info.aimMaxInterval
    self.field.aimCurInterval     = info.aimCurInterval
    self.field.abilityMinInterval = info.abilityMinInterval
    self.field.abilityMaxInterval = info.abilityMaxInterval
    self.field.abilityCurInterval = info.abilityCurInterval
    
    self.field:build(info.cols, info.rows, info.cells)
    self.field:fill(info.items)
    
    if bFillRandom then
        self.field:fillRandom()
    end
    
    self.field:fillLocks(info.locks)
    self.field:fillCharges(info.charges)
    
    self.field.aimsLeft       = info.aimsLeft
    self.field.lockedMaxCount = info.lockedMaxCount
    self.field.lockRate       = info.lockRate
end
-------------------------------------------------------------------------------

function M3Game:initPanel(info)
    -- создание слотов "абилок"
    do
        local abilities = self.field:getAbilitiesInfo()
        
        for i, abilityInfo in ipairs(abilities) do
            local ability  = abilityInfo.ability
            local maxCount = self.field:getAbilityMaxCount(ability)
            
            self.panel:createSlot(ability, maxCount)
        end
    end
    
    -- заполнение слотов
    if info.abilityCounts then
        for ability, count in pairs(info.abilityCounts) do
            local slot = self.panel:getSlot(ability)
            slot:setCount(count)
            
            if count > 0 then
                slot:setEnable(true, 0)
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Game:open()
    if not self.bOpen and not self.bCompleted then
        local level       = current_scene:getPlayField()
        local panel       = level:getPanel():getControlsPanel()
        local ho_button   = panel:getEntityNamed('bttn_ho')
        local m3_button   = panel:getEntityNamed('bttn_m3')
        -- local help_button = panel.buttons.help_button
        
        -- panel:SetHintButtonVisible(false)
        -- panel:SetHelpButtonVisible(true)
        
        m3_button:hide()
        ho_button:show()
        panel:releasePlaceForButton('bttn_m3')
        panel:requestPlaceForButton('bttn_ho', function(x) ho_button:setScrX(x) end)  
        ho_button:setActive(false)     
        
        level.ho_logic:setMatch3Mode(true)
        self.zoom:open()
        self.bOpen = true
        
        AddUniqueGameState('M3_OPEN', level.name)

        self.zoom.is_closable = false
        
        level.timer:add(function()
                            local time = M3Game.APPEARING_TIME
                            self.field:open(time, self.startPos)
                            
                            level.timer:add(function()
                                                self.panel:open()
                                                self.field.bActive = true
                                                
                                                ho_button:setActive(true)
                                                -------------------------------------------------------------------------------
                                                if AboutMatch3.isNeeded() and self.info_screen == nil then
                                                    self.info_screen = AboutMatch3(self)
                                                end
                                                -------------------------------------------------------------------------------
                                                -- help_button:Go()
                                            end,
                                            time * 0.5)
                            
                            if not HasGameState(Match3HelpScreen.DONT_SHOW_STATE, 'true') then
                                level.timer:add(function()
                                                    if self.bOpen then
                                                        CreateMatch3HelpScreen()
                                                    end
                                                end,
                                                time)
                            end
                        end,
                        ZOOM_ZONE_APPEARING_TIME)
    end
end
-------------------------------------------------------------------------------

function M3Game:close()
    if self.bOpen then
        local level       = current_scene:getPlayField()
        local panel       = level:getPanel():getControlsPanel()
        local ho_button   = panel:getEntityNamed('bttn_ho')
        local m3_button   = panel:getEntityNamed('bttn_m3')
        
        -- panel:SetHintButtonVisible(true)
        -- panel:SetHelpButtonVisible(false)
        
        ho_button:hide()
        m3_button:show()

        panel:releasePlaceForButton('bttn_ho')
        panel:requestPlaceForButton('bttn_m3', function(x) m3_button:setScrX(x) end)
        m3_button:setActive(false)
       -- hint_button:stop()
        
        level.ho_logic:setMatch3Mode(false)
        self.field.bActive = false
        self.field:dropAbility(0)
        --ADD 5.12.2012
        self.field:clearCharges()
        self.bOpen = false
        
        if HasGameState('M3_OPEN', level.name) then
            RemoveGameState('M3_OPEN', level.name)
        end
        
        self.panel:close()
        self.zoom.is_closable = true
        
        level.timer:add(function()
                            local time = M3Game.APPEARING_TIME
                            self.field:close(time, self.endPos)
                            
                            level.timer:add(function()
                                                self.zoom:close()
                                                
                                                level.timer:add(function()
                                                                    m3_button:setActive(true)
                                                                  --  hint_button:Go()
                                                                end,
                                                                ZOOM_ZONE_APPEARING_TIME)
                                            end,
                                            time)
                        end,
                        M3AbilitySlot.APPEARING_TIME * 0.5)
    end
end
-------------------------------------------------------------------------------

function M3Game:IsOpened()
    return self.bOpen
end
-------------------------------------------------------------------------------

function M3Game:complete()
    self:close()
    self.bCompleted = true
end
-------------------------------------------------------------------------------

function M3Game:isSaved()
    return HasGameState(self:_getSaveKeyPrefics()..'SAVED', 'true')
end
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------
-- EVENTS
-------------------------------------------------------------------------------

function M3Game:onFieldChanged(e)
    self:save()
end
-------------------------------------------------------------------------------

function M3Game:onAimPassed(e)
    local level = current_scene:getPlayField()
    local objects = self:_getAvailableTaskObjects()
    
    if #objects > 0 then
        local hoItemIndex  = math.random(#objects)
        local hoItemName   = objects[hoItemIndex]
        local itemUniqueId = e.item:getUniqueId()
        
        local info      = {}
        info.item       = e.item
        info.hoItemName = hoItemName
        info.action     = nil
        
        self.flyingAim[itemUniqueId] = info
        current_scene:getEntityNamed(hoItemName)
        
        --Log('!!! onAimPassed 0 = '..info.hoItemName)
        info.action = level.timer:add(function()
                            --Log('!!! onAimPassed 1 = '..info.hoItemName..' | is level closed = '..tostring(level.bClosed))
                            if level.bClosed then
                                Log('!!!!')
                                return
                            end
                            
                            local scaleTime = 1
                            local flyTime   = 2
                            local hideTime  = 0.5
                            
                            local task          = GetTask(hoItemName)
                            local taskPos       = level.ho_logic:getIndicator():getTaskPosition(task)
                            local ho_panel      = level.ho_logic:getIndicator().game_scene:getHOPanel()
                            local anchor_tasks  = level.ho_logic:getIndicator():getAnchor()
                            local anchor        = e.item:getAnchor()

                            taskPos.x = taskPos.x + anchor_tasks:getX()
                            taskPos.y = taskPos.y + anchor_tasks:getY()
                            
                            anchor:attachTo(ho_panel:getMainEntity())
                            anchor:setOrder(100)
                            
                            do -- create fly effector
                                local effector = CreateAnimationEffector()
                                effector:attachTo(anchor)
                                effector:setDestroysOnEnd(true)
                                effector:setKey(_TRACK_S, 0,                              anchor:getScale().x)
                                effector:setKey(_TRACK_S, scaleTime * 1/4,                1.5)
                                effector:setKey(_TRACK_S, scaleTime * 2/4,                1)
                                effector:setKey(_TRACK_S, scaleTime * 3/4,                1.5)
                                effector:setKey(_TRACK_S, scaleTime,                      1)
                                effector:setKey(_TRACK_S, scaleTime + flyTime,            1)
                                effector:setKey(_TRACK_S, scaleTime + flyTime + hideTime, 0.2)
                                
                                effector:setKey(_TRACK_O, scaleTime + flyTime,            1)
                                effector:setKey(_TRACK_O, scaleTime + flyTime + hideTime, 0)
                                
                                do
                                    local startPos = Vector2f(anchor:getScrX(), anchor:getScrY())
                                    local dot1 = Vector2f(math.random() * SCR_W, RandomFloat(startPos.y, taskPos.y))
                                    local dot2 = Vector2f(math.random() * SCR_W, RandomFloat(startPos.y, taskPos.y))

                                    local keys = HelpersEffect.createSplineBezier(startPos, dot1, taskPos, dot2, 20)
    
                                    local time_step = flyTime / 20
                                    for i, position in ipairs(keys) do
                                        effector:setKey(_TRACK_X, scaleTime * (i - 1) * time_step, position.x)
                                        effector:setKey(_TRACK_Y, scaleTime * (i - 1) * time_step, position.y)
                                    end                                 
                                end
                                
                                effector:play()
                            end
                            ---------------------
                            
                            local callback = function()
                                --Log('!!! onAimPassed 3 = '..info.hoItemName)
                                local info = self.flyingAim[itemUniqueId]
                                
                                level.ho_logic:autoFindObject(info.hoItemName)
                                
                                info.item:destroy()
                                self.flyingAim[itemUniqueId] = nil
                                self:save()
                            end
                            
                            info.action = level.timer:add(callback, scaleTime + flyTime + hideTime)
                        end,
                        e.delay)
    end
end
-------------------------------------------------------------------------------

function M3Game:onAbilityPassed(e)
    local level        = current_scene:getPlayField()
    local itemUniqueId = e.item:getUniqueId()
    local ability      = e.item.ability
    local slot         = self.panel:getSlot(ability)
    
    slot:show()
    
    local info    = {}
    info.item     = e.item
    info.action   = nil
    self.flyingAbility[itemUniqueId] = info
    
    level.timer:add(function()
                        if level.bClosed then
                            Log('!!!!')
                            return
                        end
                        
                        local anchor = e.item:getAnchor()
                        
                        local effector = CreateAnimationEffector()
                        effector:attachTo(anchor)
                        effector:setDestroysOnEnd(true)
                        
                        do
                            if not slot:isVisible() then
                                Log('MATCH3: Fast show ability slot.')
                                slot:show(0)
                            end 
                            
                            local maxDeltaPos = 100
                            local startPos    = Vector2f(anchor:getScrX(), anchor:getScrY())
                            local finishPos   = self.panel:getAbilityFlyTargetPos(slot.id)
                            local dot1        = startPos  + Vector2f(-maxDeltaPos,                 RandomFloat(0, maxDeltaPos))
                            local dot2        = finishPos + Vector2f(RandomFloat(-maxDeltaPos, 0), RandomFloat(0, maxDeltaPos))                            
                        
                            local keys = HelpersEffect.createSplineBezier(startPos, dot1, finishPos, dot2, 20)
                            local scaleTime = 0
                            local time_step = M3Game.ABILITY_FLY_TIME / 20
                            for i, position in ipairs(keys) do
                                effector:setKey(_TRACK_X, scaleTime * (i - 1) * time_step, position.x)
                                effector:setKey(_TRACK_Y, scaleTime * (i - 1) * time_step, position.y)
                            end       
                        end
                        
                        effector:play()
                        
                        local callback = function()
                            local info = self.flyingAbility[itemUniqueId]
                            
                            slot:modifyCount(1)
                            if not self.field:isAbilityDragging(slot:getAbility()) then
                                slot:setEnable(true)
                            end
                            
                            info.item:destroy()
                            self.flyingAbility[itemUniqueId] = nil
                            self:save()
                        end
                        
                        info.action = level.timer:add(callback, M3Game.ABILITY_FLY_TIME)
                    end,
                    e.delay)
end
-------------------------------------------------------------------------------

function M3Game:onAbilityChosen(e)
    local spriteInfo = {}
    spriteInfo.texture   = e.slot:getIndicatorTexture()
    spriteInfo.startScrX = e.slot:getIndicator():getScrX()
    spriteInfo.startScrY = e.slot:getIndicator():getScrY()
    
    self.field:setAbility(e.ability, spriteInfo)
    e.slot:setEnable(false)
end
-------------------------------------------------------------------------------

function M3Game:onAbilityAffected(e)
    local slot          = self.panel:getSlot(e.ability)
    local bDontHideSlot = (self:getFlyingAbilities(e.ability) > 0)
    slot:modifyCount(-1, bDontHideSlot)
    
    if slot:getCount() > 0 then
        slot:setEnable(true)
    end
    
    self:save()
end
-------------------------------------------------------------------------------

function M3Game:onAbilityDropped(e)
    local slot = self.panel:getSlot(e.ability)
    local level = current_scene:getPlayField()
    
    if e.time == 0 then
        slot:setEnable(true, e.changeSlotActivityTime)
    else
        level.timer:Add(function()
                            slot:setEnable(true, e.changeSlotActivityTime)
                        end,
                        e.time)
    end
end
-------------------------------------------------------------------------------

function M3Game:onTaskObjectMouseDown(e)
    self.field:DecAimsLeft()
    self:save()
end
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------
-- PRIVATE
-------------------------------------------------------------------------------

function M3Game:_getSaveKeyPrefics()
    return M3Game.COMMON_SAVE_KEY..self.id..'_'
end
-------------------------------------------------------------------------------

function M3Game:_stringTOitemsInfo(saveStr)
    local itemsInfo = {}
    
    local infoArray = split_string(saveStr, '<||>')
    for _, itemInfoString in ipairs(infoArray) do
        local info = {}
        
        local paramPairs = split_string(itemInfoString, '|')
        for _, paramPair in ipairs(paramPairs) do
            
            local keyValue = split_string(paramPair, '=')
            
            if     keyValue[1] == 'id'          then  info.id          = keyValue[2]
            elseif keyValue[1] == 'name'        then  info.name        = keyValue[2]
            elseif keyValue[1] == 'rate'        then  info.rate        = tonumber(keyValue[2])
            elseif keyValue[1] == 'crashHeight' then  info.crashHeight = tonumber(keyValue[2])
            elseif keyValue[1] == 'aim'         then  info.aim         = keyValue[2]
            elseif keyValue[1] == 'ability'     then  info.ability     = keyValue[2]
            end
        end
        
        table.insert(itemsInfo, info)
    end
    
    return itemsInfo
end
-------------------------------------------------------------------------------

function M3Game:_itemsInfoTOstring(itemsInfo)
    local result
    
    for i, info in ipairs(itemsInfo) do
        local str = 'id='..info.id..'|'..
                    'name='..info.name..'|'..
                    'rate='..tostring(info.rate)
        
        if info.crashHeight then
            str = str..'|'..'crashHeight='..tostring(info.crashHeight)
        end
        
        if info.aim then
            str = str..'|'..'aim='..info.aim
        end
        
        if info.ability then
            str = str..'|'..'ability='..info.ability
        end
        
        result = (not result) and str or (result..'<||>'..str)
    end
    
    return result
end
-------------------------------------------------------------------------------

function M3Game:_getFlyingAimInfo(hoItemName)
    for uniqueId, info in pairs(self.flyingAim) do
        if info.hoItemName == hoItemName then
            return info
        end
    end
    
    return info
end
-------------------------------------------------------------------------------

function M3Game:_getAvailableTaskObjects()
    local pfObjects = current_scene:getPlayField():CreateLoaderTable()
    local objects = {}
    local additionObjects = {}
    
    for _, object in pairs(pfObjects) do
        local objectName = object.name
        
        if current_scene:getPlayField().ho_logic:isTaskObject(objectName) and
            not IsTaskObjectFound(current_scene:getName(), objectName) and
            self:_getFlyingAimInfo(objectName) == nil
        then
            if IsTaskActionObject(objectName) then
                table.insert(additionObjects, objectName)
            else
                table.insert(objects, objectName)
            end
        end
    end
    
    return (#objects > 0) and objects or additionObjects
end
-------------------------------------------------------------------------------

function M3Game:_getFlyingAimSaveString()
    local str = ''
    
    for uniqueId, info in pairs(self.flyingAim) do
        str = str..info.hoItemName..'|'
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Game:_getFlyingAbilitySaveString()
    local counts = {}
    local str = ''
    
    for uniqueId, info in pairs(self.flyingAbility) do
        local ability = info.item.ability
        
        counts[ability] = (counts[ability] ~= nil) and (counts[ability] + 1)
                                                    or 1
    end
    
    for ability, count in pairs(counts) do
        if str ~= '' then
            str = str..'|'
        end
        
        str = str..ability..'='..tostring(count)
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Game:_getAbilityMaxCountsSaveString()
    local str = ''
    
    for ability, maxCount in pairs(self.field.abilityMaxCounts) do
        if str ~= '' then
            str = str..'|'
        end
        
        str = str..ability..'='..tostring(maxCount)
    end
    
    return str
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------

function M3Game:_parseFlyingAbilityItemsString(str)
    local counts = {}
    local splitedStr = split_string(str, '|')
    
    for i, params in ipairs(splitedStr) do
        local paramsPair = split_string(params, '=')
        local ability = paramsPair[1]
        local count   = tonumber(paramsPair[2])
        
        counts[ability] = count
    end
    
    return counts
end
-------------------------------------------------------------------------------

function M3Game:_abilityAffectIDsTOstring(abilityAffectIDs)
    local str = ''
    
    for ability, affectIDs in pairs(abilityAffectIDs) do
        local affectIDsStr = ''
        
        if type(affectIDs) == 'table' then
            for i=1, #affectIDs do
               affectIDsStr = affectIDsStr..affectIDs[i]
            end
        else
            affectIDsStr = affectIDs
        end
        
        if str ~= '' then
            str = str..'|'
        end
        
        str = str..ability..'='..affectIDsStr
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Game:_stringTOabilityAffectIDs(str)
    local abilityAffectIDs = {}
    
    local splitedStr = split_string(str, '|')
    
    for i, params in ipairs(splitedStr) do
        local paramsPair      = split_string(params, '=')
        local ability         = paramsPair[1]
        local affectIDsStr    = paramsPair[2]
        local affectIDsStrLen = string.len(affectIDsStr)
        
        if affectIDsStrLen > 1 then
            abilityAffectIDs[ability] = {}
            
            for i=1, affectIDsStrLen do
                local id = string.sub(affectIDsStr, i, i)
                table.insert(abilityAffectIDs[ability], id)
            end
        else
            abilityAffectIDs[ability] = affectIDsStr
        end
    end
    
    return abilityAffectIDs
end
-------------------------------------------------------------------------------