local data = require("internal.data")
local Log = require("api.Log")
local save = require("internal.global.save")

local MapArchetype = {}

function MapArchetype.generate_map(map_archetype_id, area, floor, params)
   local map_archetype = data["base.map_archetype"]:ensure(map_archetype_id)
   assert(type(map_archetype.on_generate_map) == "function", ("Map archetype '%s' was associated with floor '%d' of area archetype '%s', but it doesn't have an `on_generate_floor` callback.")
             :format(map_archetype_id, floor, area._archetype))
   assert(type(area) == "table", "No area provided")
   floor = floor or 1
   params = params or {}

   params.is_first_generation = true

   local map = map_archetype.on_generate_map(area, floor, params)
   if map._archetype == nil then
      Log.debug("Map archetype unset on new floor, setting to %s", map_archetype_id)
      map:set_archetype(map_archetype_id, { set_properties = true })
   else
      Log.debug("Map archetype was already set to %s on generation", map._archetype)
   end

   map:emit("base.on_map_generated_from_archetype", {area=area,floor=floor,params=params})

   return map
end

function MapArchetype.generate_map_and_area(map_archetype_id, floor, params, parent_area)
   local InstancedArea = require("api.InstancedArea")
   local Area = require("api.Area")
   local Map = require("api.Map")

   local area = InstancedArea:new()
   local floor = floor or 1
   local map = MapArchetype.generate_map(map_archetype_id, area, floor, params)
   area:add_floor(map)
   Area.register(area, { parent = parent_area or Area.for_map(Map.current()) })

   return map
end

local function iter_declared_entrances_for(world_area_archetype_id, floor)
   assert(math.type(floor) == "integer")
   if world_area_archetype_id == nil then
      return fun.iter({})
   end

   local filter = function(arc)
      return arc.parent_area
         and world_area_archetype_id == arc.parent_area._id
         and floor == arc.parent_area.on_floor
   end
   return data["base.area_archetype"]:iter():filter(filter)
end

function MapArchetype.generate_area_archetype_entrances(parent_map, force)
   local IFeat = require("api.feat.IFeat")
   local Area = require("api.Area")
   local Feat = require("api.Feat")

   local parent_area = Area.for_map(parent_map)
   if parent_area == nil then
      return
   end
   local floor = assert(parent_area:floor_of_map(parent_map.uid))

   for _, child_area in iter_declared_entrances_for(parent_area._archetype, floor) do
      -- First check if an entrance to this area already exists in the parent
      -- area. If so, do nothing.
      local pred = function(feat)
         if not feat.area_uid then
            return false
         end

         local feat_area = Area.get(feat.area_uid)
         if not feat_area then
            return false
         end

         return feat_area._archetype == child_area._id
            and feat_area.params.area_floor == child_area.parent_area.on_floor
      end

      local entrances = Feat.iter(parent_map):filter(pred)
      local do_create = true
      if entrances:length() > 0 then
         if force then
            entrances:each(IFeat.remove_ownership)
            do_create = true
         else
            do_create = false
         end
      end

      if do_create then
         -- Generate the unique area if it hasn't been generated yet and
         -- register it if necessary.
         local area
         if Area.is_created(child_area._id) then
            area = Area.get_unique(child_area._id)
         else
            area = Area.create_unique(child_area._id, parent_area)
         end

         local entry = assert(save.base.unique_areas[child_area._id])

         -- Next check if the entrance has been autogenerated yet, and if so, do
         -- nothing. This is to support removing an autogenerated entrance after
         -- it's generated and not having it show up again.
         if not entry.entrance_was_generated and not force then
            local x = assert(child_area.parent_area.x)
            local y = assert(child_area.parent_area.y)
            local area_floor = assert(child_area.parent_area.starting_floor)

            Log.debug("Autogenerating area entrance of '%s' (floor %d) in parent '%s' at (%d, %d)", child_area._id, area_floor, area._archetype, x, y)

            local feat, err = Area.create_entrance(area, area_floor, x, y, {}, parent_map)
            if feat then
               entry.entrance_was_generated = true
            else
               Log.error("Failed to place map entrance of '%s': %s'", child_area._id, err)
            end
         end
      end
   end
end

return MapArchetype
