#===============================================================================
# Data saved in $PokemonGlobal.followers.
#===============================================================================
class FollowerData
  attr_accessor :original_map_id
  attr_accessor :event_id
  attr_accessor :event_name
  attr_accessor :current_map_id
  attr_accessor :x, :y
  attr_accessor :direction
  attr_accessor :character_name, :character_hue
  attr_accessor :name
  attr_accessor :common_event_id
  attr_accessor :visible
  attr_accessor :invisible_after_transfer

  def initialize(original_map_id, event_id, event_name, current_map_id, x, y,
                 direction, character_name, character_hue)
    @original_map_id          = original_map_id
    @event_id                 = event_id
    @event_name               = event_name
    @current_map_id           = current_map_id
    @x                        = x
    @y                        = y
    @direction                = direction
    @character_name           = character_name
    @character_hue            = character_hue
    @name                     = nil
    @common_event_id          = nil
    @visible                  = true
    @invisible_after_transfer = false
  end

  def visible?
    return @visible && !@invisible_after_transfer
  end

  def interact(event)
    return if !event || event.list.size <= 1
    return if !@common_event_id
    # Start event
    $game_map.refresh if $game_map.need_refresh
    event.lock
    pbMapInterpreter.setup(event.list, event.id, event.map.map_id)
  end
end

#===============================================================================
# Permanently stores data of follower events (i.e. in save files).
#===============================================================================
class PokemonGlobalMetadata
  attr_accessor :dependentEvents   # Deprecated - to be removed in v22
  attr_writer   :followers

  def followers
    @followers = [] if !@followers
    return @followers
  end
end

#===============================================================================
# Stores Game_Follower instances just for the current play session.
#===============================================================================
class Game_Temp
  attr_writer :followers

  def followers
    @followers = Game_FollowerFactory.new if !@followers
    return @followers
  end
end

#===============================================================================
#
#===============================================================================
class Game_FollowerFactory
  attr_reader :last_update

  def initialize
    @events = []
    $PokemonGlobal.followers.each do |follower|
      @events.push(create_follower_object(follower))
    end
    @last_update = -1
  end

  #-----------------------------------------------------------------------------

  def add_follower(event, name = nil, common_event_id = nil)
    return if !event
    followers = $PokemonGlobal.followers
    if followers.any? { |data| data.original_map_id == $game_map.map_id && data.event_id == event.id }
      return   # Event is already dependent
    end
    eventData = FollowerData.new($game_map.map_id, event.id, event.name,
                                 $game_map.map_id, event.x, event.y, event.direction,
                                 event.character_name.clone, event.character_hue)
    eventData.name            = name
    eventData.common_event_id = common_event_id
    newEvent = create_follower_object(eventData)
    followers.push(eventData)
    @events.push(newEvent)
    @last_update += 1
  end

  def remove_follower_by_event(event)
    followers = $PokemonGlobal.followers
    map_id = $game_map.map_id
    followers.each_with_index do |follower, i|
      next if follower.current_map_id != map_id
      next if follower.original_map_id != event.map_id
      next if follower.event_id != event.id
      followers[i] = nil
      @events[i] = nil
      @last_update += 1
    end
    followers.compact!
    @events.compact!
  end

  def remove_follower_by_name(name)
    followers = $PokemonGlobal.followers
    followers.each_with_index do |follower, i|
      next if follower.name != name
      followers[i] = nil
      @events[i] = nil
      @last_update += 1
    end
    followers.compact!
    @events.compact!
  end

  def remove_all_followers
    $PokemonGlobal.followers.clear
    @events.clear
    @last_update += 1
  end

  def get_follower_by_index(index = 0)
    @events.each_with_index { |event, i| return event if i == index }
    return nil
  end

  def get_follower_by_name(name)
    each_follower { |event, follower| return event if follower&.name == name }
    return nil
  end

  def each_follower
    $PokemonGlobal.followers.each_with_index { |follower, i| yield @events[i], follower }
  end

  #-----------------------------------------------------------------------------

  def turn_followers
    leader = $game_player
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      event.turn_towards_leader(leader)
      follower.direction = event.direction
      leader = event
    end
  end

  def move_followers
    leader = $game_player
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      event.follow_leader(leader, false, (i == 0))
      follower.x              = event.x
      follower.y              = event.y
      follower.current_map_id = event.map.map_id
      follower.direction      = event.direction
      leader = event
    end
  end

  def map_transfer_followers
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      event.map = $game_map
      event.moveto($game_player.x, $game_player.y)
      event.direction = $game_player.direction
      event.opacity   = 255
      follower.x                        = event.x
      follower.y                        = event.y
      follower.current_map_id           = event.map.map_id
      follower.direction                = event.direction
      follower.invisible_after_transfer = true
    end
  end

  def follow_into_door
    # Setting an event's move route also makes it start along that move route,
    # so we need to record all followers' current positions first before setting
    # any move routes
    follower_pos = []
    follower_pos.push([$game_player.map.map_id, $game_player.x, $game_player.y])
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      follower_pos.push([event.map.map_id, event.x, event.y])
    end
    # Calculate and set move route from each follower to player
    move_route = []
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      leader = follower_pos[i]
      vector = $map_factory.getRelativePos(event.map.map_id, event.x, event.y,
                                           leader[0], leader[1], leader[2])
      if vector[0] != 0
        move_route.prepend((vector[0] > 0) ? PBMoveRoute::RIGHT : PBMoveRoute::LEFT)
      elsif vector[1] != 0
        move_route.prepend((vector[1] > 0) ? PBMoveRoute::DOWN : PBMoveRoute::UP)
      end
      pbMoveRoute(event, move_route + [PBMoveRoute::OPACITY, 0])
    end
  end

  # Used when coming out of a door.
  def hide_followers
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      event.opacity = 0
    end
  end

  # Used when coming out of a door. Makes all followers invisible until the
  # player starts moving.
  def put_followers_on_player
    $PokemonGlobal.followers.each_with_index do |follower, i|
      event = @events[i]
      event.moveto($game_player.x, $game_player.y)
      event.opacity = 255
      follower.x                        = event.x
      follower.y                        = event.y
      follower.invisible_after_transfer = true
    end
  end

  #-----------------------------------------------------------------------------

  def update
    return if $game_temp.in_menu
    followers = $PokemonGlobal.followers
    return if followers.length == 0
    # Update all followers
    leader = $game_player
    player_moving = $game_player.moving? || $game_player.jumping?
    followers.each_with_index do |follower, i|
      event = @events[i]
      next if !@events[i]
      if follower.invisible_after_transfer && player_moving
        follower.invisible_after_transfer = false
        event.turn_towards_leader($game_player)
      end
      event.move_speed  = leader.move_speed
      event.transparent = !follower.visible?
      if $PokemonGlobal.ice_sliding
        event.straighten
        event.walk_anime = false
      else
        event.walk_anime = true
      end
      if event.jumping? || event.moving? || !player_moving
        event.update
      elsif !event.starting
        event.set_starting
        event.update
        event.clear_starting
      end
      follower.direction = event.direction
      leader = event
    end
    # Check event triggers
    if Input.trigger?(Input::USE) && !$game_temp.in_menu && !$game_temp.in_battle &&
       !$game_player.move_route_forcing && !$game_temp.message_window_showing &&
       !pbMapInterpreterRunning?
      # Get position of tile facing the player
      facing_tile = $map_factory.getFacingTile
      # Assumes player is 1x1 tile in size
      each_follower do |event, follower|
        next if !facing_tile || event.map.map_id != facing_tile[0] ||
                !event.at_coordinate?(facing_tile[1], facing_tile[2])   # Not on facing tile
        next if event.jumping?
        follower.interact(event)
      end
    end
  end

  #-----------------------------------------------------------------------------

  private

  def create_follower_object(event_data)
    return Game_Follower.new(event_data)
  end
end

#===============================================================================
#
#===============================================================================
class FollowerSprites
  def initialize(viewport)
    @viewport    = viewport
    @sprites     = []
    @last_update = nil
    @disposed    = false
  end

  def dispose
    return if @disposed
    @sprites.each { |sprite| sprite.dispose }
    @sprites.clear
    @disposed = true
  end

  def disposed?
    return @disposed
  end

  def refresh
    @sprites.each { |sprite| sprite.dispose }
    @sprites.clear
    $game_temp.followers.each_follower do |event, follower|
      @sprites.push(Sprite_Character.new(@viewport, event))
    end
  end

  def update
    if $game_temp.followers.last_update != @last_update
      refresh
      @last_update = $game_temp.followers.last_update
    end
    @sprites.each { |sprite| sprite.update }
  end
end

#===============================================================================
# Helper module for adding/removing/getting followers.
#===============================================================================
module Followers
  module_function

  # @param event_id [Integer] ID of the event on the current map to be added as a follower
  # @param name [String] identifier name of the follower to be added
  # @param common_event_id [Integer] ID of the Common Event triggered when interacting with this follower
  def add(event_id, name, common_event_id)
    $game_temp.followers.add_follower($game_map.events[event_id], name, common_event_id)
  end

  # @param event [Game_Event] map event to be added as a follower
  def add_event(event)
    $game_temp.followers.add_follower(event)
  end

  # @param name [String] identifier name of the follower to be removed
  def remove(name)
    $game_temp.followers.remove_follower_by_name(name)
  end

  # @param event [Game_Event] map event to be removed as a follower
  def remove_event(event)
    $game_temp.followers.remove_follower_by_event(event)
  end

  # Removes all followers.
  def clear
    $game_temp.followers.remove_all_followers
    pbDeregisterPartner rescue nil
  end

  # @param name [String, nil] name of the follower to get, or nil for the first follower
  # @return [Game_Follower, nil] follower object
  def get(name = nil)
    return $game_temp.followers.get_follower_by_name(name) if name
    return $game_temp.followers.get_follower_by_index
  end

  def follow_into_door
    $game_temp.followers.follow_into_door
  end

  def hide_followers
    $game_temp.followers.hide_followers
  end

  def put_followers_on_player
    $game_temp.followers.put_followers_on_player
  end
end
