#===============================================================================
# ** Interpreter
#-------------------------------------------------------------------------------
#  This interpreter runs event commands. This class is used within the
#  Game_System class and the Game_Event class.
#===============================================================================
class Interpreter
  #-----------------------------------------------------------------------------
  # * Object Initialization
  #     depth : nest depth
  #     main  : main flag
  #-----------------------------------------------------------------------------
  def initialize(depth = 0, main = false)
    @depth = depth
    @main  = main
    if depth > 100
      print("Common event call has exceeded maximum limit.")
      exit
    end
    clear
  end

  def inspect
    str = super.chop
    str << format(' @event_id: %d>', @event_id)
    return str
  end

  def clear
    @map_id             = 0       # map ID when starting up
    @event_id           = 0       # event ID
    @message_waiting    = false   # waiting for message to end
    @move_route_waiting = false   # waiting for move completion
    @wait_count         = 0       # wait count
    @child_interpreter  = nil     # child interpreter
    @branch             = {}      # branch data
    @buttonInput        = false
  end
  #-----------------------------------------------------------------------------
  # * Event Setup
  #     list     : list of event commands
  #     event_id : event ID
  #-----------------------------------------------------------------------------
  def setup(list, event_id, map_id = nil)
    clear
    @map_id = map_id || $game_map.map_id
    @event_id = event_id
    @list = list
    @index = 0
    @branch.clear
  end

  def setup_starting_event
    $game_map.refresh if $game_map.need_refresh
    # Set up common event if one wants to start
    if $game_temp.common_event_id > 0
      setup($data_common_events[$game_temp.common_event_id].list, 0)
      $game_temp.common_event_id = 0
      return
    end
    # Check all map events for one that wants to start, and set it up
    for event in $game_map.events.values
      next if !event.starting
      if event.trigger < 3   # Isn't autorun or parallel processing
        event.lock
        event.clear_starting
      end
      setup(event.list, event.id, event.map.map_id)
      return
    end
    # Check all common events for one that is autorun, and set it up
    for common_event in $data_common_events.compact
      next if common_event.trigger != 1 || !$game_switches[common_event.switch_id]
      setup(common_event.list, 0)
      return
    end
  end

  def running?
    return @list != nil
  end
  #-----------------------------------------------------------------------------
  # * Frame Update
  #-----------------------------------------------------------------------------
  def update
    @loop_count = 0
    loop do
      @loop_count += 1
      if @loop_count > 100   # Call Graphics.update for freeze prevention
        Graphics.update
        @loop_count = 0
      end
      # If this interpreter's map isn't the current map or connected to it,
      # forget this interpreter's event ID
      if $game_map.map_id != @map_id && !$MapFactory.areConnected?($game_map.map_id, @map_id)
        @event_id = 0
      end
      # Update child interpreter if one exists
      if @child_interpreter
        @child_interpreter.update
        @child_interpreter = nil if !@child_interpreter.running?
        return if @child_interpreter
      end
      # Do nothing if a message is being shown
      return if @message_waiting
      # Do nothing if any event or the player is in the middle of a move route
      if @move_route_waiting
        return if $game_player.move_route_forcing
        for event in $game_map.events.values
          return if event.move_route_forcing
        end
        @move_route_waiting = false
      end
      # Do nothing while waiting
      if @wait_count > 0
        @wait_count -= 1
        return
      end
      # Do nothing if the pause menu is going to open
      return if $game_temp.menu_calling
      # If there are no commands in the list, try to find something that wants to run
      if @list.nil?
        setup_starting_event if @main
        return if @list.nil?   # Couldn't find anything that wants to run
      end
      # Execute the next command
      return if execute_command == false
      # Move to the next @index
      @index += 1
    end
  end
  #-----------------------------------------------------------------------------
  # * Execute script
  #-----------------------------------------------------------------------------
  def execute_script(script)
    begin
      result = eval(script)
      return result
    rescue Exception
      e = $!
      raise if e.is_a?(SystemExit) || "#{e.class}" == "Reset"
      event = get_self
      s = "Backtrace:\r\n"
      message = pbGetExceptionMessage(e)
      if e.is_a?(SyntaxError)
        script.each_line { |line|
          line.gsub!(/\s+$/, "")
          if line[/^\s*\(/]
            message += "\r\n***Line '#{line}' shouldn't begin with '('. Try\r\n"
            message += "putting the '(' at the end of the previous line instead,\r\n"
            message += "or using 'extendtext.exe'."
          end
          if line[/\:\:\s*$/]
            message += "\r\n***Line '#{line}' can't end with '::'. Try putting\r\n"
            message += "the next word on the same line, e.g. 'PBSpecies:" + ":MEW'"
          end
        }
      else
        for bt in e.backtrace[0, 10]
          s += bt + "\r\n"
        end
        s.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] }
      end
      message = "Exception: #{e.class}\r\nMessage: " + message + "\r\n"
      message += "\r\n***Full script:\r\n#{script}\r\n"
      if event && $game_map
        map_name = ($game_map.name rescue nil) || "???"
        err  = "Script error in event #{event.id} (coords #{event.x},#{event.y}), map #{$game_map.map_id} (#{map_name}):\r\n"
        err += "#{message}\r\n#{s}"
        if e.is_a?(Hangup)
          $EVENTHANGUPMSG = err
          raise
        end
      elsif $game_map
        map_name = ($game_map.name rescue nil) || "???"
        err = "Script error in map #{$game_map.map_id} (#{map_name}):\r\n"
        err += "#{message}\r\n#{s}"
        if e.is_a?(Hangup)
          $EVENTHANGUPMSG = err
          raise
        end
      else
        err = "Script error in interpreter:\r\n#{message}\r\n#{s}"
        if e.is_a?(Hangup)
          $EVENTHANGUPMSG = err
          raise
        end
      end
      raise err
    end
  end
  #-----------------------------------------------------------------------------
  # * Get Character
  #     parameter : parameter
  #-----------------------------------------------------------------------------
  def get_character(parameter = 0)
    case parameter
    when -1   # player
      return $game_player
    when 0    # this event
      events = $game_map.events
      return (events) ? events[@event_id] : nil
    else      # specific event
      events = $game_map.events
      return (events) ? events[parameter] : nil
    end
  end

  def get_player
    return get_character(-1)
  end

  def get_self
    return get_character(0)
  end

  def get_event(parameter)
    return get_character(parameter)
  end
  #-----------------------------------------------------------------------------
  # * Freezes all events on the map (for use at the beginning of common events)
  #-----------------------------------------------------------------------------
  def pbGlobalLock
    $game_map.events.values.each { |event| event.minilock }
  end
  #-----------------------------------------------------------------------------
  # * Unfreezes all events on the map (for use at the end of common events)
  #-----------------------------------------------------------------------------
  def pbGlobalUnlock
    $game_map.events.values.each { |event| event.unlock }
  end
  #-----------------------------------------------------------------------------
  # * Gets the next index in the interpreter, ignoring certain commands between messages
  #-----------------------------------------------------------------------------
  def pbNextIndex(index)
    return -1 if !@list || @list.length == 0
    i = index + 1
    loop do
      return i if i >= @list.length - 1
      case @list[i].code
      when 118, 108, 408   # Label, Comment
        i += 1
      when 413             # Repeat Above
        i = pbRepeatAbove(i)
      when 113             # Break Loop
        i = pbBreakLoop(i)
      when 119             # Jump to Label
        newI = pbJumpToLabel(i, @list[i].parameters[0])
        i = (newI > i) ? newI : i + 1
      else
        return i
      end
    end
  end

  def pbRepeatAbove(index)
    index = @list[index].indent
    loop do
      index -= 1
      return index + 1 if @list[index].indent == indent
    end
  end

  def pbBreakLoop(index)
    indent = @list[index].indent
    temp_index = index
    loop do
      temp_index += 1
      return index + 1 if temp_index >= @list.size - 1
      return temp_index + 1 if @list[temp_index].code == 413 &&
                               @list[temp_index].indent < indent
    end
  end

  def pbJumpToLabel(index, label_name)
    temp_index = 0
    loop do
      return index + 1 if temp_index >= @list.size - 1
      return temp_index + 1 if @list[temp_index].code == 118 &&
                               @list[temp_index].parameters[0] == label_name
      temp_index += 1
    end
  end
  #-----------------------------------------------------------------------------
  # * Various methods to be used in a script event command.
  #-----------------------------------------------------------------------------
  # Helper function that shows a picture in a script.
  def pbShowPicture(number, name, origin, x, y, zoomX = 100, zoomY = 100, opacity = 255, blendType = 0)
    number = number + ($game_temp.in_battle ? 50 : 0)
    $game_screen.pictures[number].show(name, origin, x, y, zoomX, zoomY, opacity, blendType)
  end

  # Erases an event and adds it to the list of erased events so that
  # it can stay erased when the game is saved then loaded again.
  def pbEraseThisEvent
    if $game_map.events[@event_id]
      if $game_map.events[@event_id].name[/cuttree/i]
        pbSmashThisEvent()
      end
        $game_map.events[@event_id].erase
      $PokemonMap.addErasedEvent(@event_id) if $PokemonMap
    end
    @index += 1
    return true
  end

  # Runs a common event.
  def pbCommonEvent(id)
    common_event = $data_common_events[id]
    return if !common_event
    if $game_temp.in_battle
      $game_system.battle_interpreter.setup(common_event.list, 0)
    else
      interp = Interpreter.new
      interp.setup(common_event.list, 0)
      loop do
        Graphics.update
        Input.update
        interp.update
        pbUpdateSceneMap
        break if !interp.running?
      end
    end
  end

  # Sets another event's self switch (eg. pbSetSelfSwitch(20, "A", true) ).
    def pbSetSelfSwitch(eventid, switch_name, value, mapid = -1)
    mapid = @map_id if mapid < 0
    old_value = $game_self_switches[[mapid, eventid, switch_name]]
    $game_self_switches[[mapid, eventid, switch_name]] = value
    if value != old_value && $MapFactory.hasMap?(mapid)
      $MapFactory.getMap(mapid, false).need_refresh = true
    end
  end

  def tsOff?(c)
    return get_self.tsOff?(c)
  end
  alias isTempSwitchOff? tsOff?

  def tsOn?(c)
    return get_self.tsOn?(c)
  end
  alias isTempSwitchOn? tsOn?

  def setTempSwitchOn(c)
    get_self.setTempSwitchOn(c)
  end

  def setTempSwitchOff(c)
    get_self.setTempSwitchOff(c)
  end

  def getVariable(*arg)
    if arg.length == 0
      return nil if !$PokemonGlobal.eventvars
      return $PokemonGlobal.eventvars[[@map_id, @event_id]]
    else
      return $game_variables[arg[0]]
    end
  end

  def setVariable(*arg)
    if arg.length == 1
      $PokemonGlobal.eventvars = {} if !$PokemonGlobal.eventvars
      $PokemonGlobal.eventvars[[@map_id, @event_id]] = arg[0]
    else
      $game_variables[arg[0]] = arg[1]
      $game_map.need_refresh = true
    end
  end

  def pbGetPokemon(id)
    return $Trainer.party[pbGet(id)]
  end

  def pbSetEventTime(*arg)
    $PokemonGlobal.eventvars = {} if !$PokemonGlobal.eventvars
    time = pbGetTimeNow
    time = time.to_i
    pbSetSelfSwitch(@event_id, "A", true)
    $PokemonGlobal.eventvars[[@map_id, @event_id]] = time
    for otherevt in arg
      pbSetSelfSwitch(otherevt, "A", true)
      $PokemonGlobal.eventvars[[@map_id, otherevt]] = time
    end
  end

  # Used in boulder events. Allows an event to be pushed.
  def pbPushThisEvent
    event = get_self
    old_x  = event.x
    old_y  = event.y
    # Apply strict version of passable, which treats tiles that are passable
    # only from certain directions as fully impassible
    # ^why?? - no
    return if !event.can_move_in_direction?($game_player.direction, false)
    case $game_player.direction
    when 2 then event.move_down
    when 4 then event.move_left
    when 6 then event.move_right
    when 8 then event.move_up
    end

    if old_x != event.x || old_y != event.y
      $game_player.lock
      loop do
        Graphics.update
        Input.update
        pbUpdateSceneMap
        break if !event.moving?
      end
      $game_player.unlock
    end
  end

  def pbPushThisBoulder
    pbPushThisEvent if $PokemonMap.strengthUsed
    return true
  end

  def pbSmashThisEvent
    event = get_self
    pbSmashEvent(event) if event
    @index += 1
    return true
  end

  def pbTrainerIntro(symbol)
    return true if $DEBUG && !GameData::TrainerType.exists?(symbol)
    tr_type = GameData::TrainerType.get(symbol).id
    pbGlobalLock
    pbPlayTrainerIntroME(tr_type)
    return true
  end

  def pbTrainerEnd
    pbGlobalUnlock
    event = get_self
    event.erase_route if event
  end

  def setPrice(item, buy_price = -1, sell_price = -1)
    item = GameData::Item.get(item).id
    $game_temp.mart_prices[item] = [-1, -1] if !$game_temp.mart_prices[item]
    $game_temp.mart_prices[item][0] = buy_price if buy_price > 0
    if sell_price >= 0   # 0=can't sell
      $game_temp.mart_prices[item][1] = sell_price * 2
    else
      $game_temp.mart_prices[item][1] = buy_price if buy_price > 0
    end
  end

  def setSellPrice(item, sell_price)
    setPrice(item, -1, sell_price)
  end
end
