-- Copyright (C) 2022  veden

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.

-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <https://www.gnu.org/licenses/>.


if (chunkProcessorG) then
    return chunkProcessorG
end
local chunkProcessor = {}

-- imports

local chunkUtils = require("ChunkUtils")
local queryUtils = require("QueryUtils")
local mapUtils = require("MapUtils")
-- local constants = require("Constants")

-- constants

-- local CHUNK_SIZE = constants.CHUNK_SIZE
-- local HALF_CHUNK_SIZE = constants.HALF_CHUNK_SIZE
-- local QUARTER_CHUNK_SIZE = constants.QUARTER_CHUNK_SIZE

-- imported functions

local findInsertionPoint = mapUtils.findInsertionPoint
local removeChunkFromMap = mapUtils.removeChunkFromMap
local setPositionInQuery = queryUtils.setPositionInQuery
local registerEnemyBaseStructure = chunkUtils.registerEnemyBaseStructure
local unregisterEnemyBaseStructure = chunkUtils.unregisterEnemyBaseStructure

local createChunk = chunkUtils.createChunk
local initialScan = chunkUtils.initialScan
local chunkPassScan = chunkUtils.chunkPassScan

local next = next
local table_size = table_size

local tInsert = table.insert

-- module code

function chunkProcessor.processPendingChunks(universe, tick, flush)
    local pendingChunks = universe.pendingChunks
    local eventId = universe.chunkProcessorIterator
    local event

    if not eventId then
        eventId, event = next(pendingChunks, nil)
    else
        event = pendingChunks[eventId]
    end
    local endCount = 1
    if flush then
        endCount = table_size(pendingChunks)
        eventId, event = next(pendingChunks, nil)
    end
    for _=1,endCount do
        if not eventId then
            universe.chunkProcessorIterator = nil
            if (table_size(pendingChunks) == 0) then
                -- this is needed as the next command remembers the max length a table has been
                universe.pendingChunks = {}
            end
            break
        else
            if not flush and (event.tick > tick) then
                universe.chunkProcessorIterator = eventId
                return
            end
            local newEventId, newEvent = next(pendingChunks, eventId)
            pendingChunks[eventId] = nil
            local map = event.map
            if not map.surface.valid then
                universe.chunkProcessorIterator = newEventId
                return
            end

            local topLeft = event.area.left_top
            local x = topLeft.x
            local y = topLeft.y

            if not map[x] then
                map[x] = {}
            end

            if map[x][y] then
                local oldChunk = map[x][y]
                local chunk = initialScan(oldChunk, map, tick)
                if (chunk == -1) then
                    removeChunkFromMap(map, oldChunk)
                end
            else
                local initialChunk = createChunk(map, x, y)
                map[x][y] = initialChunk
                universe.chunkIdToChunk[initialChunk.id] = initialChunk
                local chunk = initialScan(initialChunk, map, tick)
                if (chunk ~= -1) then
                    tInsert(
                        map.processQueue,
                        findInsertionPoint(map.processQueue, chunk),
                        chunk
                    )
                else
                    map[x][y] = nil
                    universe.chunkIdToChunk[initialChunk.id] = nil
                end
            end

            eventId = newEventId
            event = newEvent
        end
    end
    universe.chunkProcessorIterator = eventId
end

function chunkProcessor.processPendingUpgrades(universe, tick)
    local entityId = universe.pendingUpgradeIterator
    local entityData
    if not entityId then
        entityId, entityData = next(universe.pendingUpgrades, nil)
    else
        entityData = universe.pendingUpgrades[entityId]
    end
    if not entityId then
        universe.pendingUpgradeIterator = nil
        if table_size(universe.pendingUpgrades) == 0 then
            universe.pendingUpgrades = {}
        end
    else
        local entity = entityData.entity
        if entity.valid then
            universe.pendingUpgradeIterator = next(universe.pendingUpgrades, entityId)
            if entityData.delayTLL and tick < entityData.delayTLL then
                return
            end
            universe.pendingUpgrades[entityId] = nil
            local surface = entity.surface
            local query = universe.ppuUpgradeEntityQuery
            local position = entityData.position or entity.position
            query.name = entityData.name
            unregisterEnemyBaseStructure(entityData.map, entity, nil, true)
            entity.destroy()
            local foundPosition = surface.find_non_colliding_position(universe.buildingSpaceLookup[entityData.name],
                                                                      position,
                                                                      2,
                                                                      1,
                                                                      true)
            setPositionInQuery(query, foundPosition or position)
            local createdEntity = surface.create_entity(query)
            if createdEntity and createdEntity.valid then
                registerEnemyBaseStructure(entityData.map, createdEntity, entityData.base, true)
                if remote.interfaces["kr-creep"] then
                    remote.call("kr-creep", "spawn_creep_at_position", surface, foundPosition or position)
                end
            end
        else
            universe.pendingUpgradeIterator = next(universe.pendingUpgrades, entityId)
            universe.pendingUpgrades[entityId] = nil
        end
    end
end


function chunkProcessor.processScanChunks(universe)
    local chunkId = universe.chunkToPassScanIterator
    local chunkPack
    if not chunkId then
        chunkId, chunkPack = next(universe.chunkToPassScan, nil)
    else
        chunkPack = universe.chunkToPassScan[chunkId]
    end

    if not chunkId then
        universe.chunkToPassScanIterator = nil
        if (table_size(universe.chunkToPassScan) == 0) then
            -- this is needed as the next command remembers the max length a table has been
            universe.chunkToPassScan = {}
        end
    else
        universe.chunkToPassScanIterator = next(universe.chunkToPassScan, chunkId)
        universe.chunkToPassScan[chunkId] = nil
        local map = chunkPack.map
        if not map.surface.valid then
            return
        end
        local chunk = chunkPack.chunk

        if (chunkPassScan(chunk, map) == -1) then
            removeChunkFromMap(map, chunk)
        end
    end
end

chunkProcessorG = chunkProcessor
return chunkProcessor
