class MapManager
  MAPS_FOLDER = "data/maps/"
  MAPS_INFO_NAME = "data.json"

  TEXT_COUNT = 0

  MAP_NAME_FORMAT = "%02d-%02d"
  ENEMY_TILESET_NAME = "Enemies"

  attr_accessor :main_spawn, :start_room
  attr_reader :display_name_for_room, :map_links, :link_spawns

  def initialize(args, map_name)
    @name = map_name

    @maps = {}

    @links = {}
    @info = {x: 0, y: 0, shared_map: false}

    @start_room = {x: nil, y: nil}
    @main_spawn = {x: nil, y: nil}
    @display_name_for_room = {}
    @map_links = {}
    @link_spawns = {}
    @ground_items = {}
    @room_projectiles = {}
    @npcs = {}
    @enemies = {}


    @collision_maps = {}

    @tick_objects = {}

    __load_map_info__(args)

    if @main_spawn[:x].nil? or @main_spawn[:y].nil?
      raise StandardError, "We have a problem, \"#{map_name}\" doesn't have a main spawn point."
    end
  end

  def room_at(x, y)
    __check_bounds__(x, y)
    map_name = "#{"%02d" % y}-#{"%02d" % x}"
    @maps[map_name]
  end

  def __should_run_tick__(args)
    # args.state.inventory_state == :closed
    args.state.player.inventory.is_closed?
  end

  def tick args, x, y
    reset_text = false
    if args.inputs.keyboard.key_down.p
      reset_text = true      
    end

    return unless __should_run_tick__(args)
    map_name = MAP_NAME_FORMAT % [y, x]
    map = room_at(x, y)
    objects = @tick_objects[map_name]
    if objects
      args.state.text_typing = false
      objects.each do | object |
        if reset_text
          object.reset_text
        end
        object.tick args
        hash = object.to_h
        args.outputs[:buffer].primitives << hash
        args.state.text_typing = object.is_typing
      end
    end

    enemies = enemies_at(x, y)
    if enemies
      enemies.each do | enemy |
        enemy.tick(args)
      end
    end

    projectiles = projectiles_at(x, y)
    if projectiles && !projectiles.empty?
      projectiles.each do | projectile |
        projectile.tick(args)
      end
    end
  end

  def npcs_at(x, y)
    __check_bounds__(x, y)
    map_name = "#{"%02d" % y}-#{"%02d" % x}"
    @npcs[map_name]
  end

  def enemies_at(x, y)
    __check_bounds__(x, y)
    key = "#{"%02d" % y}-#{"%02d" % x}"
    result = @enemies[key]
    return [] if result.nil?
    result
  end

  def display_name_at(x, y)
    __check_bounds__(x, y)
    map_name = "#{"%02d" % y}-#{"%02d" % x}"
    @display_name_for_room[map_name]
  end

  def links_for_map(x, y)
    map_name = MAP_NAME_FORMAT % [y, x]#"#{"%02d" % y}-#{"%02d" % x}"
    @map_links[map_name]
  end

  def ground_items_at(x, y)
    map_name = MAP_NAME_FORMAT % [y, x]#"#{"%02d" % y}-#{"%02d" % x}"
    @ground_items[map_name]
  end

  def tick_objects_at(x, y)
    map_name = MAP_NAME_FORMAT % [y, x]#"#{"%02d" % y}-#{"%02d" % x}"
    @tick_objects[map_name]
  end

  def index_at(x, y)
    width, height = @info.width, @info.height
    index = x + (width * y)
    index
  end

  def position_at(index)
    x = index % width
    y = index.idv(width)
    {x: x, y: y}
  end
  
  def projectiles_at(x, y, from_main = false)
    __check_bounds__(x, y)

    shooting_enemies = enemies_at(x, y).reject do | enemy | 
      !enemy.can_shoot
    end
    enemies_with_projectiles = shooting_enemies.reject do | enemy |
      enemy.projectile.nil?
    end
    enemy_projectiles = enemies_with_projectiles.map do | enemy |
      enemy.projectile
    end

    enemy_projectiles
  end

  def reload_room_at!(args, x, y)
    maps_dir = "#{MAPS_FOLDER}#{@name}/"

    map_name = MAP_NAME_FORMAT % [y, x]
    map_file_name = "#{map_name}.tmx"
    map_file_path = "#{maps_dir}#{map_file_name}"

    if $gtk.stat_file(map_file_path)
      map = Tiled::Map.new(map_file_path)
      map.load()
      @maps[map_name] = map

      @collision_maps[map_name] = nil
      __calc_colllision_map__(args, map, x, y)
      if map.layers['RoomInfo']
        __setup_room_info__(args, map, x, y)
      end

      if map.layers['Interactive']
        __setup_interfactive_info__(args, map, x, y)
      end

      if map.layers['Enemies']
        __setup_enemies__(args, map, x, y)
      end
    end
  end

  def link_spawn_at(x, y)
    @link_spawns[map_name(x, y)]
  end

  def shared_map
    false unless @info.respond_to?(:is_shared)
    @info[:is_shared]
  end

  def remove_ground_item_at(item_ref, x, y)
    map_name = MAP_NAME_FORMAT % [y, x]#"#{"%02d" % y}-#{"%02d" % x}"
    items = @ground_items[map_name]
    puts items
    if !items.nil?
      items.delete item_ref
    end
  end
  private

  def map_name(x, y)
    MAP_NAME_FORMAT % [y, x]
  end

  def __load_map_info__(args)
    maps_dir = "#{MAPS_FOLDER}#{@name}/"
    info_path = "#{maps_dir}#{MAPS_INFO_NAME}"
    file_data = $gtk.read_file(info_path)

    data = LevisLibs::JSON.parse(file_data, symbolize_keys: true)
    raise KeyError, "data.json doesn't contain a 'width' property" unless data.has_key?(:width)
    raise KeyError, "data.json doesn't contain a 'height' property" unless data.has_key?(:height)
    file_data = nil
  
    width = data[:width].to_i
    height = data[:height].to_i
    is_shared = data[:is_shared]
    @info = {width: width, height: height, is_shared: is_shared}

    (0...width).each do | x |
      (0...height).each do | y |
        map_name = "#{"%02d" % y}-#{"%02d" % x}"
        map_file_name = "#{map_name}.tmx"
        map_file_path = "#{maps_dir}#{map_file_name}"

        if $gtk.stat_file(map_file_path)
          map = Tiled::Map.new(map_file_path)
          map.load()
          @maps[map_name] = map

          __calc_colllision_map__(args, map, x, y)
          if map.layers['RoomInfo']
            __setup_room_info__(args, map, x, y)
          end

          if map.layers['Interactive']
            __setup_interfactive_info__(args, map, x, y)
          end

          if map.layers["Enemies"]
            __setup_enemies__(args, map, x, y)
          end
        end
      end
    end
  end


  def __setup_room_info__(args, room, x, y)
    room_objects = room.layers['RoomInfo'].objects
    prop_key = "#{"%02d" % y}-#{"%02d" % x}"

    room_links = []
    tick_objects = []
    npcs = []
    room_objects.each do | object |
      next unless object.respond_to?(:name)
      next unless object.respond_to?(:object_type)
      case object.name
      when "MainSpawn"
        oX = object.x.idiv(32) * 32
        oY = object.y.idiv(32) * 32
        @main_spawn = {x: oX, y: oY}
        @start_room = {x: x, y: y}
      when "name"
        if object.object_type == :text
          @display_name_for_room[prop_key] = object
        end
      when "mapLink"
        if object.object_type == :rectangle
          mapLink = MapLink.new(args, object)
          room_links.append mapLink
        end
      when "LinkSpawn"
        @link_spawns[prop_key] = { x: object.x.to_i, y: object.y.to_i }
      when "ShopText"
        tick_objects.append TypingLabel.new(args, object)
      when "NPC"
        npcs.append NonPlayerCharacter.new(args, object)
      else
        # puts object
      end
    end

    if not room_links.empty?
      @map_links[prop_key] = room_links
    end

    if not tick_objects.empty?
      @tick_objects[prop_key] = tick_objects
    end

    if not npcs.empty?
      @npcs[prop_key] = npcs
    end
  end

  def __calc_colllision_map__(args, room, x, y)
    prop_key = "#{"%02d" % y}-#{"%02d" % x}"
    return @collision_maps[prop_key] if @collision_maps[prop_key]
    
    walls_layer = room.layers["Walls"]

    attrs = walls_layer.attributes
    layer_size = {w: attrs.width, h: attrs.height}

    collisions = []
    (0...layer_size.h).each do | row_x |
      row = []
      (0...layer_size.w).each do | col_y |
        value = walls_layer.tile_at(col_y, row_x) ? 1 : 0
        row.append(value)
      end
      collisions.append(row)
    end

    @collision_maps[prop_key] = collisions
  end

  def __setup_interfactive_info__(args, room, x, y)
    room_objects = room.layers['Interactive'].objects
    prop_key = "#{"%02d" % y}-#{"%02d" % x}"

    ground_items = []
    room_objects.each do | object |
      case object.name
      when "GroundItem"
        properties = object.properties
        ground_items.append GroundItem.new(object)
      end
    end

    if !ground_items.empty?
      @ground_items[prop_key] = ground_items
    end
  end

  def __setup_enemies__(args, room, x, y)
    enemy_objects = room.layers["Enemies"].objects
    prop_key = MAP_NAME_FORMAT % [y, x] #"#{"%02d" % y}-#{"%02d" % x}"

    enemy_tileset = room.tilesets.find do | tileset |
      tileset if tileset.attributes.name == ENEMY_TILESET_NAME
    end

    if enemy_tileset
      enemies = []
      puts "#{prop_key}"
      enemy_objects.each do | enemy |
        attrs = enemy.attributes
        props = enemy.properties
        enemy_gid = attrs.gid
        sprite = enemy_tileset.gid_to_id(enemy_gid)

        tile = enemy_tileset.find(enemy_gid)

        tile_attrs = tile.properties
        
        enemy_info = {type: tile_attrs.type.to_sym, position: {x: attrs.x.to_i, y: attrs.y.to_i}}
        enemy = EnemyCauldron::create_enemy(args, enemy_info)

        enemies.append( enemy )
      end

      if !enemies.empty?
        @enemies[prop_key] = enemies
        puts enemies
      end
    end
  end

  def __check_bounds__(x, y)
    width, height = @info.width, @info.height
    raise IndexError, "x is < 0" unless x >=0
    raise IndexError, "y is < 0" unless y >=0
    raise IndexError, "x is > Width (#{width})" unless x < width
    raise IndexError, "y is > Height (#{height})" unless y < height
  end
end