-- this module respawns fish in all water tiles on every surface with a player on it -- by mewmew --
-- cpu heavy -- fixed processing rate is 1 chunk per tick

local respawn_interval = 7200                        --interval in ticks
storage.fish_respawner_water_tiles_per_fish = 32     --amount of water tiles required per fish >> high values = less fish density, low values = high fish density
storage.fish_respawner_max_respawnrate_per_chunk = 1 --maximum amount of fish that will spawn each interval in one chunk

local valid_water_tiles = {
    'water',
    'deepwater',
    'water-green',
    'deepwater-green'
}

local event = require 'utils.event'
local math_random = math.random

local function shuffle(tbl)
    local size = #tbl
    for i = size, 1, -1 do
        local rand = math_random(size)
        tbl[i], tbl[rand] = tbl[rand], tbl[i]
    end
    return tbl
end

local function get_surfaces()
    local surfaces = {}
    for _, player in pairs(game.connected_players) do
        if not surfaces[player.surface.index] then
            surfaces[player.surface.index] = player.surface
        end
    end
    return surfaces
end

local function create_new_fish_spawn_schedule()
    storage.fish_respawn_chunk_schedule = {}

    local surfaces = get_surfaces()
    if #surfaces == 0 then
        return
    end

    for _, surface in pairs(surfaces) do
        for chunk in surface.get_chunks() do
            storage.fish_respawn_chunk_schedule[#storage.fish_respawn_chunk_schedule + 1] = { chunk = { x = chunk.x, y = chunk.y }, surface_index = surface.index }
        end
    end

    storage.fish_respawn_chunk_schedule = shuffle(storage.fish_respawn_chunk_schedule)

    return storage.fish_respawn_chunk_schedule
end

local function respawn_fishes_in_chunk(schedule)
    local surface = game.surfaces[schedule.surface_index]
    local chunk = schedule.chunk
    local chunk_area = { { chunk.x * 32, chunk.y * 32 }, { chunk.x * 32 + 32, chunk.y * 32 + 32 } }

    local water_tiles = surface.find_tiles_filtered({ area = chunk_area, name = valid_water_tiles })
    if #water_tiles < storage.fish_respawner_water_tiles_per_fish then
        return
    end

    local chunk_fish_count = surface.count_entities_filtered({ area = chunk_area, type = 'fish' })

    local fish_to_spawn = math.floor((#water_tiles - (storage.fish_respawner_water_tiles_per_fish * chunk_fish_count)) / storage.fish_respawner_water_tiles_per_fish)
    if fish_to_spawn <= 0 then
        return
    end

    if fish_to_spawn > storage.fish_respawner_max_respawnrate_per_chunk then
        fish_to_spawn = storage.fish_respawner_max_respawnrate_per_chunk
    end

    water_tiles = shuffle(water_tiles)
    for _, tile in pairs(water_tiles) do
        if surface.can_place_entity({ name = 'fish', position = tile.position }) then
            surface.create_entity({ name = 'water-splash', position = tile.position })
            surface.create_entity({ name = 'fish', position = tile.position })
            fish_to_spawn = fish_to_spawn - 1
            if fish_to_spawn <= 0 then
                return
            end
        end
    end
end

local function on_tick()
    local i = game.tick % respawn_interval
    if i == 0 then
        create_new_fish_spawn_schedule()
        return
    end
    if not storage.fish_respawn_chunk_schedule[i] then
        return
    end
    respawn_fishes_in_chunk(storage.fish_respawn_chunk_schedule[i])
    storage.fish_respawn_chunk_schedule[i] = nil
end

event.add(defines.events.on_tick, on_tick)
