--[[

Profiler functions

LuaProfiler is desync unsafe so you cannot use it in multiplayer.

Prints summary to log file and to console every 5 seconds.


Example 1:
Profiler.start('test1')
  ...code...
Profiler.split('test1','chunk1') -- optional
  ...code...
Profiler.split('test1','chunk2') -- optional
  ...code...
Profiler.stop('test1')

Example 2:
Profiler.start('test2', table_size(items))
for _, item in pairs(items) do
  ...code...
end
Profiler.stop('test2')

Example 3:
Profiler.count('counter1')

]]--

local Profiler = {
  enabled = false,
  print_to_console = true
}

if is_debug_mode and Profiler.enabled then
  local active_profilers = {}
  local active_splits = {}
  local active_counters = {}

  -- LuaProfiler makes the game slower as more and more profilers are created,
  -- thus we create a pool of profilers to mitigate the issue
  local profilers_pool = {}
  local profilers_used = {}

  local function get_make_profiler()
    local profiler
    if table_size(profilers_pool) > 0 then
      profiler = table.remove(profilers_pool)
      profiler.reset()
    else
      profiler = game.create_profiler(true)
    end
    table.insert(profilers_used, profiler)
    return profiler
  end

  local function recycle_profilers()
    for _, profiler in pairs(profilers_used) do
      table.insert(profilers_pool, profiler)
    end
    profilers_used = {}
  end

  local function dup_profiler(profiler)
    local new_profiler = get_make_profiler()
    new_profiler.reset()
    new_profiler.stop()
    new_profiler.add(profiler)
    return new_profiler
  end


  function Profiler.start(name, unit_count)
    if not active_profilers[name] then
      active_profilers[name] = {
        profiler = get_make_profiler(),
        tick = game.tick,
        count = 0
      }
    end
    if unit_count == nil then unit_count = 1 end
    active_profilers[name].count = active_profilers[name].count + unit_count
    active_profilers[name].profiler.restart()
  end

  function Profiler.stop(name)
    if active_profilers[name] then
      if active_splits[name] then
        Profiler.stop(active_splits[name])
        active_splits[name] = nil
      end
      active_profilers[name].profiler.stop()
    end
  end

  function Profiler.split(name, part)
    if active_profilers[name] then
      if active_splits[name] then
        Profiler.stop(active_splits[name])
        active_splits[name] = nil
      end
      if part then
        local split_name = name .. "--" .. part
        Profiler.start(split_name)
        active_splits[name] = split_name
      end
    end
  end


  function Profiler.count(name, value)
    if not active_counters[name] then
      active_counters[name] = {
        counter = 0,
        tick = game.tick
      }
    end
    if value == nil then value = 1 end
    active_counters[name].counter = active_counters[name].counter + value
  end

  function Profiler.collect_print()
    local results = {}
    for name, entry in pairs(active_profilers) do
      entry.profiler.stop()
      if entry.count == 0 then
        entry.profiler.divide(game.tick - entry.tick + 1)
        results[name] = { "", "PerTick ", entry.profiler }
      else
        local per_tick_count = entry.count / (game.tick - entry.tick + 1)
        local per_unit_profiler = dup_profiler(entry.profiler)
        per_unit_profiler.divide(entry.count)
        entry.profiler.divide(game.tick - entry.tick + 1)
        results[name] = { "", "PerUnit (", per_tick_count, "/", entry.count, ") ", per_unit_profiler,
                          " / PerTick ", entry.profiler }
      end
    end
    for name, entry in pairs(active_counters) do
      local value = entry.counter / (game.tick - entry.tick + 1)
      local key = results[name] and (name .. "-count") or name
      results[key] = { "", "Count: ", value }
    end

    local log_fn = Profiler.print_to_console and Log.trace or Log.debug_log
    -- sort the results
    local keys = {}
    for k in pairs(results) do table.insert(keys, k) end
    table.sort(keys)
    -- print
    log_fn('---')
    for _, name in pairs(keys) do
      log_fn({ "", name, ": ", results[name]})
    end
    -- cleanup
    active_profilers = {}
    active_splits = {}
    active_counters = {}
    recycle_profilers()
  end
  Event.addListener("on_nth_tick_301", Profiler.collect_print) -- 5 seconds
else
  Profiler.start = function() end
  Profiler.stop = function() end
  Profiler.split = function() end
  Profiler.count = function() end
  Profiler.collect_print = function() end
end

return Profiler
