class PokemonTemp
  attr_writer :dependentEvents

  def dependentEvents
    @dependentEvents = DependentEvents.new if !@dependentEvents
    return @dependentEvents
  end
end



def pbRemoveDependencies()
  $PokemonTemp.dependentEvents.removeAllEvents()
  pbDeregisterPartner() rescue nil
end

def pbAddDependency(event,follows=true)
  $PokemonTemp.dependentEvents.addEvent(event)
  $PokemonTemp.dependentEvents.follows_player = follows
end

def pbRemoveDependency(event)
  $PokemonTemp.dependentEvents.removeEvent(event)
end

def pbAddDependency2(eventID, eventName, commonEvent)
  $PokemonTemp.dependentEvents.addEvent($game_map.events[eventID],eventName,commonEvent)
end

# Gets the Game_Character object associated with a dependent event.
def pbGetDependency(eventName)
  return $PokemonTemp.dependentEvents.getEventByName(eventName)
end

def pbRemoveDependency2(eventName)
  $PokemonTemp.dependentEvents.removeEventByName(eventName)
end



class PokemonGlobalMetadata
  attr_writer :dependentEvents

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



def pbTestPass(follower,x,y,_direction=nil)
  return $MapFactory.isPassableStrict?(follower.map.map_id,x,y,follower)
end

# Same map only
def moveThrough(follower,direction)
  oldThrough=follower.through
  follower.through=true
  case direction
  when 2 then follower.move_down
  when 4 then follower.move_left
  when 6 then follower.move_right
  when 8 then follower.move_up
  end
  follower.through=oldThrough
end

# Same map only
def moveFancy(follower,direction)
  deltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0))
  deltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0))
  newX = follower.x + deltaX
  newY = follower.y + deltaY
  # Move if new position is the player's, or the new position is passable,
  # or the current position is not passable
  if ($game_player.x==newX && $game_player.y==newY) ||
     pbTestPass(follower,newX,newY,0) ||
     !pbTestPass(follower,follower.x,follower.y,0)
    oldThrough=follower.through
    follower.through=true
    case direction
    when 2 then follower.move_down
    when 4 then follower.move_left
    when 6 then follower.move_right
    when 8 then follower.move_up
    end
    follower.through=oldThrough
  end
end

# Same map only
def jumpFancy(follower,direction,leader)
  deltaX=(direction == 6 ? 2 : (direction == 4 ? -2 : 0))
  deltaY=(direction == 2 ? 2 : (direction == 8 ? -2 : 0))
  halfDeltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0))
  halfDeltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0))
  middle=pbTestPass(follower,follower.x+halfDeltaX,follower.y+halfDeltaY,0)
  ending=pbTestPass(follower,follower.x+deltaX,    follower.y+deltaY,    0)
  if middle
    moveFancy(follower,direction)
    moveFancy(follower,direction)
  elsif ending
    if pbTestPass(follower,follower.x,follower.y,0)
      if leader.jumping?
        follower.jump_speed_real = leader.jump_speed_real * Graphics.frame_rate / 40.0
      else
        follower.jump_speed_real = leader.move_speed_real * Graphics.frame_rate / 20.0
      end
      follower.jump(deltaX,deltaY)
    else
      moveThrough(follower,direction)
      moveThrough(follower,direction)
    end
  end
end

def pbFancyMoveTo(follower,newX,newY,leader)
  if follower.x-newX==-1 && follower.y==newY
    moveFancy(follower,6)
  elsif follower.x-newX==1 && follower.y==newY
    moveFancy(follower,4)
  elsif follower.y-newY==-1 && follower.x==newX
    moveFancy(follower,2)
  elsif follower.y-newY==1 && follower.x==newX
    moveFancy(follower,8)
  elsif follower.x-newX==-2 && follower.y==newY
    jumpFancy(follower,6,leader)
  elsif follower.x-newX==2 && follower.y==newY
    jumpFancy(follower,4,leader)
  elsif follower.y-newY==-2 && follower.x==newX
    jumpFancy(follower,2,leader)
  elsif follower.y-newY==2 && follower.x==newX
    jumpFancy(follower,8,leader)
  elsif follower.x!=newX || follower.y!=newY
    follower.moveto(newX,newY)
  end
end



class DependentEvents
  attr_reader :lastUpdate
  attr_reader :realEvents
  attr_writer :follows_player

  def createEvent(eventData)
    rpgEvent = RPG::Event.new(eventData[3],eventData[4])
    rpgEvent.id = eventData[1]
    if eventData[9]
      # Must setup common event list here and now
      commonEvent = Game_CommonEvent.new(eventData[9])
      rpgEvent.pages[0].list = commonEvent.list
    end
    newEvent = Game_Event.new(eventData[0],rpgEvent,$MapFactory.getMap(eventData[2]))
    newEvent.character_name = eventData[6]
    newEvent.character_hue  = eventData[7]
    case eventData[5]   # direction
    when 2 then newEvent.turn_down
    when 4 then newEvent.turn_left
    when 6 then newEvent.turn_right
    when 8 then newEvent.turn_up
    end
    return newEvent
  end

  def initialize
    # Original map, Event ID, Current map, X, Y, Direction
    events=$PokemonGlobal.dependentEvents
    @realEvents=[]
    @lastUpdate=-1
    for event in events
      @realEvents.push(createEvent(event))
    end
    @follows_player=true
  end

  def pbEnsureEvent(event, newMapID)
    events=$PokemonGlobal.dependentEvents
    for i in 0...events.length
      # Check original map ID and original event ID
      if events[i][0]==event.map_id && events[i][1]==event.id
        # Change current map ID
        events[i][2]=newMapID
        newEvent=createEvent(events[i])
        # Replace event
        @realEvents[i]=newEvent
        @lastUpdate+=1
        return i
      end
    end
    return -1
  end

  def pbFollowEventAcrossMaps(leader,follower,instant=false,leaderIsTrueLeader=true)
    d=leader.direction
    areConnected=$MapFactory.areConnected?(leader.map.map_id,follower.map.map_id)
    # Get the rear facing tile of leader
    facingDirection=10-d
    if !leaderIsTrueLeader && areConnected
      relativePos=$MapFactory.getThisAndOtherEventRelativePos(leader,follower)
      # Assumes leader and follower are both 1x1 tile in size
      if (relativePos[1]==0 && relativePos[0]==2)   # 2 spaces to the right of leader
        facingDirection=6
      elsif (relativePos[1]==0 && relativePos[0]==-2)   # 2 spaces to the left of leader
        facingDirection=4
      elsif relativePos[1]==-2 && relativePos[0]==0   # 2 spaces above leader
        facingDirection=8
      elsif relativePos[1]==2 && relativePos[0]==0   # 2 spaces below leader
        facingDirection=2
      end
    end
    facings=[facingDirection] # Get facing from behind
#    facings.push([0,0,4,0,8,0,2,0,6][d])   # Get right facing
#    facings.push([0,0,6,0,2,0,8,0,4][d])   # Get left facing
    if !leaderIsTrueLeader
      facings.push(d) # Get forward facing
    end
    mapTile=nil

    if areConnected
      bestRelativePos=-1
      oldthrough=follower.through
      follower.through=false
      for i in 0...facings.length
        facing=facings[i]
        tile=$MapFactory.getFacingTile(facing,leader)
        # Assumes leader is 1x1 tile in size
        passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
        if i==0 && !passable && tile &&
           $MapFactory.getTerrainTag(tile[0],tile[1],tile[2]).ledge
          # If the tile isn't passable and the tile is a ledge,
          # get tile from further behind
          tile=$MapFactory.getFacingTileFromPos(tile[0],tile[1],tile[2],facing)
          passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
        end
        if passable
          relativePos=$MapFactory.getThisAndOtherPosRelativePos(
             follower,tile[0],tile[1],tile[2])
          # Assumes follower is 1x1 tile in size
          distance=Math.sqrt(relativePos[0]*relativePos[0]+relativePos[1]*relativePos[1])
          if bestRelativePos==-1 || bestRelativePos>distance
            bestRelativePos=distance
            mapTile=tile
          end
          if i==0 && distance<=1 # Prefer behind if tile can move up to 1 space
            break
          end
        end
      end
      follower.through=oldthrough
    else
      tile=$MapFactory.getFacingTile(facings[0],leader)
      # Assumes leader is 1x1 tile in size
      passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
      mapTile=passable ? mapTile : nil
    end
    if mapTile && follower.map.map_id==mapTile[0]
      return if !@follows_player
      # Follower is on same map
      newX=mapTile[1]
      newY=mapTile[2]
      deltaX=(d == 6 ? -1 : d == 4 ? 1 : 0)
      deltaY=(d == 2 ? -1 : d == 8 ? 1 : 0)
      posX = newX + deltaX
      posY = newY + deltaY

      # if !@follows_player
      #   posX=follower.original_x
      #   posY=follower.original_y
      #
      # end

      follower.move_speed=leader.move_speed # sync movespeed
      if (follower.x-newX==-1 && follower.y==newY) ||
         (follower.x-newX==1 && follower.y==newY) ||
         (follower.y-newY==-1 && follower.x==newX) ||
         (follower.y-newY==1 && follower.x==newX)
        if instant
          follower.moveto(newX,newY)
        else
          pbFancyMoveTo(follower,newX,newY,leader)
        end
      elsif (follower.x-newX==-2 && follower.y==newY) ||
            (follower.x-newX==2 && follower.y==newY) ||
            (follower.y-newY==-2 && follower.x==newX) ||
            (follower.y-newY==2 && follower.x==newX)
        if instant
          follower.moveto(newX,newY)
        else
          pbFancyMoveTo(follower,newX,newY,leader)
        end
      elsif follower.x!=posX || follower.y!=posY
        if instant
          follower.moveto(newX,newY)
        else
          pbFancyMoveTo(follower,posX,posY,leader)
          pbFancyMoveTo(follower,newX,newY,leader)
        end
      end
    else
      if !mapTile
        # Make current position into leader's position
        mapTile=[leader.map.map_id,leader.x,leader.y]
      end
      if follower.map.map_id==mapTile[0]
        # Follower is on same map as leader
        follower.moveto(leader.x,leader.y)
      else
        # Follower will move to different map
        events=$PokemonGlobal.dependentEvents
        eventIndex=pbEnsureEvent(follower,mapTile[0])
        if eventIndex>=0
          newFollower=@realEvents[eventIndex]
          newEventData=events[eventIndex]
          newFollower.moveto(mapTile[1],mapTile[2])
          newEventData[3]=mapTile[1]
          newEventData[4]=mapTile[2]
        end
      end
    end
  end

  def debugEcho
    self.eachEvent { |e,d|
      echoln d
      echoln [e.map_id,e.map.map_id,e.id]
    }
  end

  def pbMapChangeMoveDependentEvents
    events=$PokemonGlobal.dependentEvents
    updateDependentEvents
    leader=$game_player
    for i in 0...events.length
      event=@realEvents[i]
      pbFollowEventAcrossMaps(leader,event,true,i==0)
      # Update X and Y for this event
      events[i][3]=event.x
      events[i][4]=event.y
      events[i][5]=event.direction
      # Set leader to this event
      leader=event
    end
  end

  def pbMoveDependentEvents
    events=$PokemonGlobal.dependentEvents
    updateDependentEvents
    leader=$game_player
    for i in 0...events.length
      event=@realEvents[i]
      if !@follows_player
        pbRemoveDependencies if leader.map.map_id != event.map.map_id
        pbFollowEventAcrossMaps(leader,event,false,i==0)
        events[i][3]=event.original_x
        events[i][4]=event.original_y
      else
        pbFollowEventAcrossMaps(leader,event,false,i==0)
        # Update X and Y for this event
        events[i][3]=event.x
        events[i][4]=event.y
        events[i][5]=event.direction
        # Set leader to this event
        leader=event
      end
    end
  end

  def pbTurnDependentEvents
    events=$PokemonGlobal.dependentEvents
    updateDependentEvents
    leader=$game_player
    for i in 0...events.length
      event=@realEvents[i]
      pbTurnTowardEvent(event,leader)
      # Update direction for this event
      events[i][5]=event.direction
      # Set leader to this event
      leader=event
    end
  end

  def eachEvent
    events=$PokemonGlobal.dependentEvents
    for i in 0...events.length
      yield @realEvents[i],events[i]
    end
  end

  def updateDependentEvents
    events=$PokemonGlobal.dependentEvents
    return if events.length==0
    for i in 0...events.length
      break if !@follows_player

      event=@realEvents[i]
      next if !@realEvents[i]
      event.transparent=$game_player.transparent
      if event.jumping? || event.moving? ||
         !($game_player.jumping? || $game_player.moving?)
        event.update
      elsif !event.starting
        event.set_starting
        event.update
        event.clear_starting
      end
      events[i][3]=event.x
      events[i][4]=event.y
      events[i][5]=event.direction
    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
      facingTile=$MapFactory.getFacingTile()
      # Assumes player is 1x1 tile in size
      self.eachEvent { |e,d|
        next if !d[9]
        if e.at_coordinate?($game_player.x, $game_player.y)
          # On same position
          if !e.jumping? && (!e.respond_to?("over_trigger") || e.over_trigger?)
            if e.list.size>1
              # Start event
              $game_map.refresh if $game_map.need_refresh
              e.lock
              pbMapInterpreter.setup(e.list,e.id,e.map.map_id)
            end
          end
        elsif facingTile && e.map.map_id==facingTile[0] &&
              e.at_coordinate?(facingTile[1], facingTile[2])
          # On facing tile
          if !e.jumping? && (!e.respond_to?("over_trigger") || !e.over_trigger?)
            if e.list.size>1
              # Start event
              $game_map.refresh if $game_map.need_refresh
              e.lock
              pbMapInterpreter.setup(e.list,e.id,e.map.map_id)
            end
          end
        end
      }
    end
  end

  def removeEvent(event)
    events=$PokemonGlobal.dependentEvents
    mapid=$game_map.map_id
    for i in 0...events.length
      if events[i][2]==mapid && # Refer to current map
         events[i][0]==event.map_id && # Event's map ID is original ID
         events[i][1]==event.id
        events[i]=nil
        @realEvents[i]=nil
        @lastUpdate+=1
      end
    end
    events.compact!
    @realEvents.compact!
  end

  def getEventByName(name)
    events=$PokemonGlobal.dependentEvents
    for i in 0...events.length
      if events[i] && events[i][8]==name # Arbitrary name given to dependent event
        return @realEvents[i]
      end
    end
    return nil
  end

  def removeAllEvents
    events=$PokemonGlobal.dependentEvents
    events.clear
    @realEvents.clear
    @lastUpdate+=1
  end

  def removeEventByName(name)
    events=$PokemonGlobal.dependentEvents
    for i in 0...events.length
      if events[i] && events[i][8]==name # Arbitrary name given to dependent event
        events[i]=nil
        @realEvents[i]=nil
        @lastUpdate+=1
      end
    end
    events.compact!
    @realEvents.compact!
  end

  def addEvent(event,eventName=nil,commonEvent=nil)
    return if !event
    events=$PokemonGlobal.dependentEvents


    for i in 0...events.length
      if events[i] && events[i][0]==$game_map.map_id && events[i][1]==event.id
        # Already exists
        return
      end
    end
    # Original map ID, original event ID, current map ID,
    # event X, event Y, event direction,
    # event's filename,
    # event's hue, event's name, common event ID
    eventData=[
       $game_map.map_id,event.id,$game_map.map_id,
       event.x,event.y,event.direction,
       event.character_name.clone,
       event.character_hue,eventName,commonEvent
    ]
    newEvent=createEvent(eventData)
    events.push(eventData)
    @realEvents.push(newEvent)
    @lastUpdate+=1
    event.erase
  end
end



class DependentEventSprites
  def initialize(viewport,map)
    @disposed=false
    @sprites=[]
    @map=map
    @viewport=viewport
    refresh
    @lastUpdate=nil
  end

  def refresh
    for sprite in @sprites
      sprite.dispose
    end
    @sprites.clear
    $PokemonTemp.dependentEvents.eachEvent { |event,data|
      if data[0]==@map.map_id # Check original map
        @map.events[data[1]].erase
      end
      if data[2]==@map.map_id # Check current map
        @sprites.push(Sprite_Character.new(@viewport,event))
      end
    }
  end

  def update
    if $PokemonTemp.dependentEvents.lastUpdate!=@lastUpdate
      refresh
      @lastUpdate=$PokemonTemp.dependentEvents.lastUpdate
    end
    for sprite in @sprites
      sprite.update
    end
  end

  def dispose
    return if @disposed
    for sprite in @sprites
      sprite.dispose
    end
    @sprites.clear
    @disposed=true
  end

  def disposed?
    @disposed
  end
end



Events.onSpritesetCreate += proc { |_sender,e|
  spriteset = e[0]   # Spriteset being created
  viewport  = e[1]   # Viewport used for tilemap and characters
  map = spriteset.map   # Map associated with the spriteset (not necessarily the current map)
  spriteset.addUserSprite(DependentEventSprites.new(viewport,map))
}

Events.onMapSceneChange += proc { |_sender,e|
  mapChanged = e[1]
  if mapChanged
    $PokemonTemp.dependentEvents.pbMapChangeMoveDependentEvents
  end
}
