
----------------------------------------------------------------------------
-- Spawn positioning
----------------------------------------------------------------------------

local function IMPL_pos_single(pos)
  return { 
    pos_type = CPP_pos_type_positioned,
    pos_min = pos, 
    pos_max = pos, 
  }
end

-- One enemy at one position
set_global("pos_single", function(pos)
  return { IMPL_pos_single(pos, vec(0, 0)) }
end)

-- Multiple enemies in one line formation
set_global("pos_single_line", function(pos_a, pos_b, amount)
  assert(amount > 1)
  local result = {}
  local len = vec_len(vec_sub(pos_b, pos_a))
  assert(len > 0)
  local dir = vec_direction(pos_a, pos_b)
  local step = len / (amount - 1)
  for i=1,amount do
    local distance = step * (i-1)
    local spawn_pos = vec_add(pos_a, vec_mul_scalar(dir, distance))
    table.insert(result, IMPL_pos_single(spawn_pos))
  end
  return result
end)

set_global("pos_plus", function(pos_center, radius_param)
  local result = {}
  local radius = radius_param or 1.0
  local offset_x = vec(radius, 0)
  local offset_y = vec(0, radius)
  table.insert(result, IMPL_pos_single(pos_center))
  table.insert(result, IMPL_pos_single(vec_add(pos_center, offset_x)))
  table.insert(result, IMPL_pos_single(vec_sub(pos_center, offset_x)))
  table.insert(result, IMPL_pos_single(vec_add(pos_center, offset_y)))
  table.insert(result, IMPL_pos_single(vec_sub(pos_center, offset_y)))
  return result
end)

set_global("pos_gen_circle", function(pos, radius, amount)
  local result = {}
  for i=1,amount do
    local angle = ((i-1) / amount) * 3.14159 * 2.0
    local dir = vec(math.sin(angle), math.cos(angle))
    local pos = vec_add(pos, vec_mul_scalar(dir, radius))
    table.insert(result, IMPL_pos_single(pos))
  end
  return result
end)

set_global("pos_gen_random_rect", function(cell, amount, size_vec)
  local result = {}
  local corner_offset = vec(size_vec.x * 0.5, size_vec.y * 0.5)
  for i=1,amount do
    table.insert(result, {
      pos_type = CPP_pos_type_positioned,
      pos_min = vec_sub(cell, corner_offset),
      pos_max = vec_add(cell, corner_offset),
      })
  end
  return result
end)

set_global("pos_gen_random_square", function(cell, amount, square_size)
  return pos_gen_random_rect(cell, amount, vec(square_size, square_size))
end)

set_global("pos_random_free_tiles", function(amount)
  local result = {}
  for i=1,amount do
    table.insert(result, {
      pos_type = CPP_pos_type_random_free_tile,
      pos_min = vec(0, 0),
      pos_max = vec(0, 0),
      })
  end
  return result
end)

set_global("pos_as_player_relative", function(pos_table)
  for _,t in pairs(pos_table) do
    t.pos_type = CPP_pos_type_player_relative
  end
  return pos_table
end)

----------------------------------------------------------------------------
-- Wave ID generation
----------------------------------------------------------------------------

local g_wave_id = 0
local function new_wave_id()
  g_wave_id = g_wave_id + 1
  return g_wave_id
end

----------------------------------------------------------------------------
-- Basic waves
----------------------------------------------------------------------------

set_global("wave_spawn", function(pos_table, enemy_type, time_interval_param)

  -- New spawning wave
  local wave = {
    wave_id = new_wave_id(),
    wave_type = CPP_type_sequential,
    spawn = { spawns = {}, time_interval = time_interval_param or 0 }
  }

  -- Clone the given positions and add enemy type stuff
  for _,t in pairs(pos_table) do
    local spawn = table_clone(t)
    spawn.spawn_group_id = enemy_type
    table.insert(wave.spawn.spawns, spawn)
  end

  return wave
end)

set_global("wave_timed_wait", function(time)
  return {
    wave_id = new_wave_id(),
    wait = wait_time(time),
  }
end)

----------------------------------------------------------------------------
-- Waits
----------------------------------------------------------------------------

set_global("wait_none", function()
  return { waits = { } }
end)

set_global("wait_time", function(time)
  return { waits = { { wait_type = CPP_wait_type_time, time = time, } } }
end)

set_global("wait_dead_all", function()
  return { waits = { { wait_type = CPP_wait_type_dead_all, } } }
end)

set_global("wait_dead", function(fraction)
  return { waits = { { wait_type = CPP_wait_type_dead_fraction, dead_fraction = fraction, } } }
end)

set_global("wait_alive_count", function(alive_count)
  return { waits = { { wait_type = CPP_wait_type_alive_more_than_count, count = alive_count, } } }
end)

set_global("wait_ping", function(event_ping_id)
  assert(event_ping_id >= 0)
  return { waits = { { wait_type = CPP_wait_type_event_ping, ping_id = event_ping_id } } }
end)

set_global("wait_time_or_alive_count", function(time, alive_count)
  return { 
    wait_combiner = CPP_wait_combiner_one,
    waits = { wait_time(time).waits[1], wait_alive_count(alive_count).waits[1], }
  }
end)

set_global("wait_time_or_dead", function(time, fraction)
  return { 
    wait_combiner = CPP_wait_combiner_one,
    waits = { wait_time(time).waits[1], wait_dead(fraction).waits[1], }
  }
end)

set_global("wait_time_and_dead", function(time, fraction)
  return { 
    wait_combiner = CPP_wait_combiner_all,
    waits = { wait_time(time).waits[1], wait_dead(fraction).waits[1], }
  }
end)

----------------------------------------------------------------------------
-- Wave combination
----------------------------------------------------------------------------

-- Take a table of waves and collapse sub tables away
local function waves_collapse(waves)
  assert(type(waves) == 'table')
  local wave_count = #waves
  local res = {}
  for i=1,wave_count do
    local entry = waves[i]
    assert(entry)
    if entry.wave_id ~= nil then
      table.insert(res, entry)
    else
      -- TODO: make this recursive if there's any need
      local subwave_count = #entry
      assert(type(entry) == 'table')
      assert(subwave_count > 0)
      for j=1,subwave_count do
        local sub_e = entry[j]
        assert(sub_e.wave_id ~= nil)
        table.insert(res, sub_e)
      end
    end
  end
  return res
end

set_global("waves_parallel", function(wait, waves)
  return {
    wave_id     = new_wave_id(),
    wave_type   = CPP_type_parallel,
    wait        = wait,
    child_waves = waves_collapse(waves),
  }
end)

set_global("waves_sequential", function(wait, waves)
  return {
    wave_id     = new_wave_id(),
    wave_type   = CPP_type_sequential,
    wait        = wait,
    child_waves = waves_collapse(waves),
  }
end)

set_global("waves_repeat", function(waves, times, interval)
  local result = {}
  for i=1,times do
    for _,wave in ipairs(waves) do
      -- Clone wave and generate a new wave id for it
      local clone_wave = table_clone(wave)
      clone_wave.wave_id = new_wave_id()
      -- Insert into result
      table.insert(result, clone_wave)
    end
    -- Add wait if needed
    if i < times then
      table.insert(result, wave_timed_wait(interval))
    end
  end
  return result
end)

-- Kludge to wrap given blocking waves to be run parallel, used with waves_repeat
set_global("parallelize_waves", function(waves)
  local wrapped_waves = {}

  for i=1,#waves do
    table.insert(wrapped_waves, waves_sequential(wait_none(), { waves[i] }))
  end

  return waves_parallel(wait_none(), wrapped_waves)
end)

----------------------------------------------------------------------------
-- Custom generic waves
----------------------------------------------------------------------------

set_global("wave_spammer", function(pos_fun, amount, wait_interval, enemy_type)
  local wave_positions = {}
  for i=1,amount do
    table.insert(wave_positions, pos_fun()[1])
  end

  local wave = { wave_spawn(wave_positions, enemy_type, wait_interval) }
  local wave_seq = waves_sequential(wait_none(), wave)

  -- Kludge wrapper wave so parent wave doesn't wait for this to complete before continuing (needed anymore?)
  local wave_no_wait = waves_parallel(wait_none(), { wave_seq })
  return wave_no_wait
end)

set_global("wave_spammer_box", function(pos, size, amount, interval, et)
  return wave_spammer(function () return pos_gen_random_square(pos, 1, size) end, amount, interval, et)
end)

set_global("wave_spammer_box_on_player", function(size, amount, interval, et)
  return wave_spammer(function () return pos_as_player_relative(pos_gen_random_square(vec(0,0), 1, size)) end, amount, interval, et)
end)

set_global("wave_spammer_point", function(pos, amount, interval, et)
    return wave_spammer(function () return pos_single(pos) end, amount, interval, et)
end)

set_global("wave_spammer_free_in_time", function(amount, total_time, enemy_type)
  return wave_spammer(function () return pos_random_free_tiles(1) end, amount, total_time / amount, enemy_type)
end)

set_global("wave_chase", function(amount, interval, enemy_type)
  local wave_positions = {}
  for i=1,amount do
    table.insert(wave_positions, pos_as_player_relative(pos_single(vec(0, 0)))[1])
  end
  return wave_spawn(wave_positions, enemy_type, interval)
end)

----------------------------------------------------------------------------
-- Level construction
----------------------------------------------------------------------------

local g_wave_sequence = {}

set_global("add_wave", function(fun)
  table.insert(g_wave_sequence, fun())
end)

-- Wait a set period of time doing nothing
set_global("add_wave_time_wait", function (time)
  add_wave(function () return waves_sequential(wait_time(time), {}) end)
end)

-- Wait a bit before starting
set_global("add_wave_initial_wait", function ()
  add_wave_time_wait(2.5)
end)

-- Wait for a specific event from native code
set_global("add_wave_ping_event_wait", function (event_id)
  add_wave(function () return waves_sequential(wait_ping(event_id), {}) end)
end)

-- Wait until every enemy is dead
set_global("add_wave_final_wait", function ()
  if global_exists("g_debug_never_end") and g_debug_never_end then
    -- Debugging enabled: no level end, wait "forever"
    add_wave(function () return waves_sequential(wait_time(100000.0), {}) end)
  else
    -- Normal level end, wait for all to die
    add_wave(function () return waves_sequential(wait_dead_all(), {}) end)
  end
end)

set_global("generate_level_script", function()
  if global_exists("g_debug_start_at_wave") then
    -- Debug cutoff specified, remove waves before the specified wave
    local debug_waves = {}
    for i,v in ipairs(g_wave_sequence) do
      if i >= g_debug_start_at_wave then
        table.insert(debug_waves, v)
      end
    end
    return { wave_sequence = debug_waves }
  else
    -- No debug variable, use whole sequence
    return { wave_sequence = g_wave_sequence, }
  end
end)

----------------------------------------------------------------------------

print "base.lua: Finished adding level script generic functions."
