class 'M3AbilityLogicMirror' (M3AbilityLogicBase)
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:__init(ability, field)
    M3AbilityLogicBase.__init(self, ability, field)
    
    self.anchor    = nil
    self.ray       = nil
    self.waves     = {}
    self.wavesPool = {}
    self.spots     = {}
    
    self.affectTarget = 2
    self.startPos     = nil
    
    self.waveDelay    = 0
    self.waveMinDelay = 0.1
    self.waveMaxDelay = 2
    self.waveSpeed    = 200
    
    self.minWaveScale = 0.5
    self.maxWaveScale = 1.3
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:destroy()
    M3AbilityLogicBase.destroy(self)
    
    for i, info in ipairs(self.waves) do
        if play_field then
            play_field:DoNotIgnoreOnTestPoint(info.wave:GetName())
        end
        info.wave:Destroy()
    end
    
    for i, wave in ipairs(self.wavesPool) do
        if play_field then
            play_field:DoNotIgnoreOnTestPoint(wave:GetName())
        end
        wave:Destroy()
    end
    
    for i, spot in ipairs(self.spots) do
        if play_field then
            play_field:DoNotIgnoreOnTestPoint(spot:GetName())
        end
        spot:Destroy()
    end
    
    self.waves     = nil
    self.wavesPool = nil
    self.spots     = nil
    
    if self.ray then
        if play_field then
            play_field:DoNotIgnoreOnTestPoint(self.ray:GetName())
        end
        self.ray:Destroy()
        self.ray = nil
    end
    
    if self.anchor then
        self.anchor:Destroy()
        self.anchor = nil
    end
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:run(spriteInfo, time)
    M3AbilityLogicBase.run(self, spriteInfo, time)
    
    self.pField:resetSelect()
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:affect(cell)
    M3AbilityLogicBase.affect(self, cell)
    
    local selectCell = self.pField:getSelectCell()
    
    if selectCell then
        if self.pField:checkSwapCells(cell, selectCell, true, true) then
            self:_stopRay()
        else
            self.affectCount = 1
        end
    else
        self.pField.selectCell = cell
        cell:setSelected(true)
        
        local pos = cell:getCenterPos()
        self:_startRay(pos)
    end
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:canAffect(cell)
    if M3AbilityLogicBase.canAffect(self, cell) then
        local selectCell = self.pField:getSelectCell()
        
        return selectCell == nil or selectCell.row ~= cell.row or selectCell.col ~= cell.col
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:update(dt)
    M3AbilityLogicBase.update(self, dt)
    
    if self.affectCount > 0 then
        if not self.pField:getSelectCell() then
            self:_stopRay()
            self.affectCount = 0
        else
            local mousePos = GetMousePos()
            local endPos   = Vector(mousePos.x - self.pField:getAnchor():GetScrX(),
                                    mousePos.y - self.pField:getAnchor():GetScrY())
            local dPos = endPos - self.startPos
    
            self:_updateRay(dt, dPos)
            self:_updateWaves(dt, dPos)
        end
    end
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:stop(time)
    if self.affectCount > 0 then
        self:_stopRay()
    end
    
    M3AbilityLogicBase.stop(self, time)
end
-------------------------------------------------------------------------------


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

function M3AbilityLogicMirror:_startRay(pos)
    if not self.ray then
        self:_createAnchor()
        self:_createRay()
    end
    
    self.anchor:SetXY(pos.x, pos.y)
    self.startPos  = pos
    self.waveDelay = RandomFloat(self.waveMinDelay, self.waveMaxDelay)
    
    self.ray:SetVisible(true)
    self.pField:playSound('m3_mirror_loop', nil, true)
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_stopRay()
    self.ray:SetVisible(false)
    
    for i, spot in ipairs(self.spots) do
        spot:SetVisible(false)
    end
    
    for i, info in ipairs(self.waves) do
        info.wave:SetVisible(false)
    end
    
    self.pField:stopSound('m3_mirror_loop')
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_updateRay(dt, dPos)
    local len = dPos:Length()
    
    self.anchor:SetAngle(dPos:Angle())
    self.ray:SetScaleX(len / self.ray:GetAnimation():GetWidth())
    
    local spot2 = self.spots[2]
    spot2:SetX(len)
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_updateWaves(dt, dPos)
    self.waveDelay = self.waveDelay - dt
    
    if self.waveDelay <= 0 then
        self.waveDelay = RandomFloat(self.waveMinDelay, self.waveMaxDelay)
        
        local info = {}
        info.wave  = self:_getWave()
        info.scale = RandomFloat(self.minWaveScale, self.maxWaveScale)
        info.pos   = 0
        info.halfW = info.wave:GetAnimation():GetWidth() * info.scale / 2
        
        info.wave:SetScale(info.scale)
        info.wave:SetVisible(true)
        
        table.insert(self.waves, info)
    end
    
    local length = dPos:Length()
    
    for i=#self.waves, 1, -1 do
        local info = self.waves[i]
        info.pos = info.pos + self.waveSpeed * dt
        
        if info.pos < length then
            local dx     = math.min(info.pos, length - info.pos)
            local delta  = info.halfW - dx
            local xScale = info.scale
            
            if delta > 0 then
                xScale = info.scale * dx / info.halfW
            end
            
            info.wave:SetX(info.pos)
            info.wave:SetScaleX(xScale)
        else
            info.wave:SetVisible(false)
            table.insert(self.wavesPool, info.wave)
            table.remove(self.waves, i)
        end
    end
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_createAnchor()
    self.anchor = CreateAutoRegisterEntity()
    self.anchor:AttachTo(self.pField:getAnchor())
    self.anchor:SetZ(M3Field.ABILITY_Z)
    
    local time = 1.5
    local effector = CreateAnimationEffector()
    effector:SetDestroysOnEnd(true)
    effector:AttachTo(self.anchor)
    effector:SetMode(_ANIM_LOOP)
    
    effector:SetKey(_TRACK_O, 0,          1)
    effector:SetKey(_TRACK_O, time * 0.5, 0.5)
    effector:SetKey(_TRACK_O, time,       1)
    effector:Play()
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_createRay()
    do
        local objectName = 'm3abilityMirrorRay'
        local anim = CreateAnimationByTextureName(M3Game.TEXTURE_PATH..'Abilities/Affects/laser_line.png')
        anim:SetHotSpot(0, anim:GetHeight()/2)
        
        self.ray = play_field:CreateObject(objectName,anim,0,0,1,0,1,1)
        self.ray:AttachTo(self.anchor)
        play_field:IgnoreOnTestPoint(objectName)
    end
    
    do
        local texture = M3Game.TEXTURE_PATH..'Abilities/Affects/laser_spot.png'
        
        for i=1, 2 do
            local objectName = 'm3abilityMirrorSpot'..i
            local anim = CreateAnimationByTextureName(texture)
            local spot = play_field:CreateObject(objectName,anim,0,0,2 + i,0,1,1)
            
            spot:AttachTo(self.anchor)
            spot:SetXY(0, 0)
            
            play_field:IgnoreOnTestPoint(objectName)
            table.insert(self.spots, spot)
        end
    end
end
-------------------------------------------------------------------------------

function M3AbilityLogicMirror:_getWave()
    if #self.wavesPool > 0 then
        return table.remove(self.wavesPool)
    else
        local uniqueId = #self.waves + #self.wavesPool + 1
        local objectName = 'm3abilityMirrorWave'..uniqueId
        local anim = CreateAnimationByTextureName(M3Game.TEXTURE_PATH..'Abilities/Affects/laser_wave.png')
        
        local wave = play_field:CreateObject(objectName,anim,0,0,2,0,1,1)
        wave:AttachTo(self.anchor)
        
        play_field:IgnoreOnTestPoint(objectName)
        return wave
    end
end
-------------------------------------------------------------------------------