--- Procedural generation methods, and other aspects of generating the
--- environment

function init_world()
   -- constants for map tiles' sprite locations, as long as we have the
   -- tokens
   tile_tree = 1
   tile_tree_stump = 3
   tile_bush = 5
   tile_empty_bush = 7
   tile_cactus = 9
   tile_flowering_cactus = 11
   tile_cactus_stump = 13
   tile_big_mushroom = 32
   tile_mushroom_stump = 34
   tile_pebbles = 36
   tile_long_grass = 38
   tile_red_flowers = 40
   tile_pink_flowers = 42
   tile_fairy_ring = 44
   tile_rock = 46
   
   -- Metadata for different biomes
   -- tile_frequencies tuples are {frequency, sprite_index}, see index_map.md
   -- frequencies by convention add up to 1000, but this is arbitrary, the
   -- only concern is the space consumed by the resulting tables.
   biome_data = {
      meadow = {
         biome_frequency = 20,
         base_color = 3,
         tile_frequencies = {
            {525, 0},
            {200, tile_red_flowers},
            {200, tile_pink_flowers},
            {55,  tile_long_grass},
            {19,  tile_bush},
            {1,   tile_tree}
         }
      },
      grassland = {
         biome_frequency = 55,
         base_color = 3,
         tile_frequencies = {
            {500, 0},
            {345, tile_long_grass},
            {4, tile_bush},
            {1, tile_tree},
            {100, tile_red_flowers},
            {50, tile_pink_flowers}
         }
      },
      forest = {
         biome_frequency = 20,
         base_color = 3,
         tile_frequencies = {
            {600, 0},
            {200, tile_tree},
            {50, tile_bush},
            {50, tile_red_flowers},
            {40, tile_mushrooms},
            {59, tile_pink_flowers},
            {1, tile_big_mushroom}
         }
      },
      desert = {
         biome_frequency = 5,
         base_color = 9,
         tile_frequencies = {
            {800, 0},
            {109, tile_pebbles},
            {60, tile_cactus},
            {30, tile_rock},
            {1, tile_flowering_cactus}
         }
      }
   }

   -- Why is this hard-coded separately from the biome_data? glad you asked.
   -- Lua's pairs() function appears not to guarantee a consistent return order,
   -- and we want our world to be deterministically generated,
   -- so the biome_metadata array needs to have its entries appear consistently.
   biome_list = {"grassland", "meadow", "forest", "desert"}

   -- this is the frequency list for the biomes themselves
   biome_metadata = {}

   for i=1,#biome_list do
      local biome = biome_list[i]

      -- add the biome's name N times to the biome metadata 'hat'
      for i=1,biome_data[biome].biome_frequency do
         add(biome_metadata, biome)
      end

      build_biome(biome, biome_data[biome])
   end

   object_interaction_map = {
      [tile_bush] = {
         replacement = tile_empty_bush,
         sfx = 13,
         drop = 81
      },

      [tile_tree] = {
         replacement = tile_tree_stump,
         sfx = 11,
         drop = 66
      },

      [tile_big_mushroom] = {
         replacement = tile_mushroom_stump,
         sfx = 12,
         drop = 64
      },

      [tile_flowering_cactus] = {
         replacement = tile_cactus_stump,
         sfx = 12,
         drop = 80
      },

      [tile_cactus] = {
         replacement = tile_cactus_stump,
         sfx = 12,
         drop = 65
      }
   }

   -- initialize a ring buffer of changed positions. In use, this will be keyed
   -- using strings of the form mod_buffer["x:y"], using absolute world
   -- coordinates. this is to flatten the buffer so that #mod_cache is useful
   -- for checking against the number of allowed entries
   mod_buffer = {}
   mod_queue = {}
end

-- draw the sprites for this part of the world to the screen
-- this calculates everything about the world fresh every frame,
-- but pico-8 handles this just fine!
function draw_world(start_x, start_y)
   for x=0,6 do
      for y=0,6 do
         -- color the background for this segment
         rectfill(x*16+8, y*16+8, x*16+24, y*16+24,
                  get_base_color(start_x-3+x, start_y-3+y))

         -- now get the sprite, and render as long as it isn't 0
         local sprite = get_tile(start_x-3+x, start_y-3+y)
         if sprite ~= 0 then
            spr(sprite, x*16+8, y*16+8, 2, 2)
         end
      end
   end   
end

-- build the lookup table for a given biome, based on the biome_meta data for
-- that string.
function build_biome(biome_name, data)
   local meta_frequencies = data.tile_frequencies
   local tile_lookup = {}

   for i=1,#meta_frequencies do
      local tuple = meta_frequencies[i]
      for j=1,tuple[1] do
         add(tile_lookup, tuple[2])
      end
   end

   data.tile_lookup = tile_lookup
end

-- generates a unique identifier for a position
-- uses srand() and rand() to get an unpredictable value (is this too slow?)
-- the two seed values are randomly chosen. Change them and change the world.
function generate_uid(pos_x, pos_y)
   srand((pos_x + 2229) * (pos_y + 12295))
   return flr(rnd(0xffff))
end

-- given an {x,y} position, calculates the aligned starting position for the
-- biome that position is in.
-- biomes are currently defined to be 128x128
function calculate_biome_pos(pos_x, pos_y)
   return flr(pos_x / 128), flr(pos_y / 128)
end

-- determines which biome a given world map position should be,
-- returns the object out of the biome_data table
function get_biome_name(pos_x, pos_y)
   local biome_pos_x, biome_pos_y = calculate_biome_pos(pos_x, pos_y)
   local uid = generate_uid(biome_pos_x, biome_pos_y)
   return biome_metadata[(uid % #biome_metadata) + 1]
end

-- get the background color for the current biome
function get_base_color(x, y)
   local biome = get_biome_name(x, y)
   return biome_data[biome].base_color
end

-- determine what sprite to render for a given position.
function get_tile(pos_x, pos_y)
   -- lookup changes in the change buffer
   local modded_sprite = mod_buffer[get_mod_key(pos_x, pos_y)]
   if (modded_sprite) return modded_sprite

   local biome_name = get_biome_name(pos_x, pos_y)
   local biome = biome_data[biome_name]
   local uid = generate_uid(pos_x, pos_y)

   return biome.tile_lookup[(uid % #biome.tile_lookup) + 1]
end


---
--- mod buffer functions - these handle locations on the world map that have
--- changed from their 'default' state
---

-- x and y are global coords
function get_mod_key(x, y)
   return tostr(x) .. ":" .. tostr(y)
end

-- x and y are map-local coords
function write_map_change(new_sprite, x, y, perm)
   local key = get_mod_key(x, y)
   mod_buffer[key] = new_sprite

   -- the queue gives us a time-ordered list of items to delete.
   -- anything that should persist is simply not added to the queue,
   -- making it un-deletable. It also doesn't count against the maximum
   -- size of the mod queue before deletion, meaning it permanently inflates
   -- the size of ram.
   -- obviously if we end up with a *very large number* of persistent
   -- objects we can run into trouble, but this is functionally a design
   -- limitation. We also only save 32 of these on exit, further limiting
   -- the possible size of the 'memory leak' pool, as it were.
   if not perm then
      add(mod_queue, key)
   end

   if #mod_queue >= 8192 then
      cull_mod_buffer()
   end
end

function cull_mod_buffer()
   -- we cull 512 entries at a time.
   local count = 0
   for i=1,511 do
      local key = mod_queue[1]
      if (not key) return -- check that we're not out of items for some reason
      mod_buffer[key] = nil
      del(mod_queue, key)
   end
end