@PokeBattle.mixins.BattleProtocolParser =
  update: (actions) ->
    return  if actions.length == 0
    @notify()
    hadStuff = (@updateQueue.length > 0)
    @updateQueue.push(actions...)
    @_update()  unless hadStuff

  _update: (wasAtBottom) ->
    view = @view
    queue = @updateQueue
    return  if !queue  # closed battle in the middle of getting updates
    if queue.length == 0
      view.renderUserInfo()
      view.resetPopovers()
      if wasAtBottom || view.skip? then view.chatView.scrollToBottom()
      if view.skip?
        delete view.skip
        view.$('.battle_pane').show()
      return
    wasAtBottom ||= view.chatView.isAtBottom()
    action = queue.shift()
    [ type, rest... ] = action
    protocol = (key  for key, value of Protocol when value == type)[0]
    try
      if window.localStorage.debug == 'true'
        console.log "Received protocol: #{protocol} with args: #{rest}"
    catch

    done = () =>
      return  if done.called
      done.called = true
      if view.skip?
        @_update.call(this, wasAtBottom)
      else
        # setTimeout 0 lets the browser breathe.
        setTimeout(@_update.bind(this, wasAtBottom), 0)

    doneTimeout = ->
      setTimeout(done, 0)

    doneSpeedTimeout = () =>
      if view.skip? || view.speed <= 1
        done()
      else
        setTimeout(done, (view.speed - 1) * 1000)

    try
      switch type
        when Protocol.CHANGE_HP
          [player, slot, newPercent] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.set('percent', newPercent)
          if view.skip? then done() else setTimeout(done, 500)
        when Protocol.CHANGE_EXACT_HP
          [player, slot, newHP] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.set('hp', newHP)
          done()
        when Protocol.SWITCH_OUT
          [player, slot] = rest
          view.switchOut(player, slot, done)
        when Protocol.SWITCH_IN
          # TODO: Get Pokemon data, infer which Pokemon it is.
          # Currently, it cheats with `fromSlot`.
          [player, toSlot, fromSlot] = rest
          team = @getTeam(player).get('pokemon').models
          [team[toSlot], team[fromSlot]] = [team[fromSlot], team[toSlot]]
          # TODO: Again, automatic.
          view.switchIn(player, toSlot, fromSlot, doneSpeedTimeout)
        when Protocol.CHANGE_PP
          [player, slot, moveIndex, newPP] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.setPP(moveIndex, newPP)
          done()
        when Protocol.REQUEST_ACTIONS
          [validActions] = rest
          view.enableButtons(validActions)
          PokeBattle.notifyUser(PokeBattle.NotificationTypes.ACTION_REQUESTED, @id + "_" + @get('turn'))
          done()
        when Protocol.START_TURN
          [turn] = rest
          view.beginTurn(turn, doneTimeout)
        when Protocol.CONTINUE_TURN
          view.continueTurn(doneTimeout)
        when Protocol.RAW_MESSAGE
          [message] = rest
          view.addLog("#{message}<br>")
          done()
        when Protocol.FAINT
          [player, slot] = rest
          view.faint(player, slot, done)
        when Protocol.MAKE_MOVE
          # TODO: Send move id instead
          [player, slot, moveName] = rest
          view.logMove(player, slot, moveName, done)
        when Protocol.END_BATTLE
          [winner] = rest
          view.announceWinner(winner, done)
        when Protocol.FORFEIT_BATTLE
          [forfeiter] = rest
          view.announceForfeit(forfeiter, done)
        when Protocol.TIMER_WIN
          [winner] = rest
          view.announceTimer(winner, done)
        when Protocol.BATTLE_EXPIRED
          view.announceExpiration(done)
        when Protocol.MOVE_SUCCESS
          [player, slot, targetSlots, moveName] = rest
          view.moveSuccess(player, slot, targetSlots, moveName, done)
        when Protocol.CANNED_TEXT
          cannedInteger = rest.splice(0, 1)
          view.parseCannedText(cannedInteger, rest, done)
        when Protocol.EFFECT_END
          [player, slot, effect] = rest
          view.endEffect(player, slot, effect, done)
        when Protocol.POKEMON_ATTACH
          [player, slot, attachment] = rest
          view.attachPokemon(player, slot, attachment, done)
        when Protocol.TEAM_ATTACH
          [player, attachment] = rest
          view.attachTeam(player, attachment, done)
        when Protocol.BATTLE_ATTACH
          [attachment] = rest
          view.attachBattle(attachment, done)
        when Protocol.POKEMON_UNATTACH
          [player, slot, attachment] = rest
          view.unattachPokemon(player, slot, attachment, done)
        when Protocol.TEAM_UNATTACH
          [player, attachment] = rest
          view.unattachTeam(player, attachment, done)
        when Protocol.BATTLE_UNATTACH
          [attachment] = rest
          view.unattachBattle(attachment, done)
        when Protocol.INITIALIZE
          # TODO: Handle non-team-preview
          [teams] = rest
          @receiveTeams(teams)
          view.preloadImages()
          if !@get('spectating')
            PokeBattle.notifyUser(PokeBattle.NotificationTypes.BATTLE_STARTED, @id)
          done()
        when Protocol.START_BATTLE
          view.removeTeamPreview()
          view.renderBattle()
          done()
        when Protocol.REARRANGE_TEAMS
          arrangements = rest
          @get('teams').forEach (team, i) ->
            team.rearrange(arrangements[i])
          done()
        when Protocol.RECEIVE_TEAM
          [team] = rest
          @receiveTeam(team)
          done()
        when Protocol.SPRITE_CHANGE
          [player, slot, newSpecies, newForme] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.set('species', newSpecies)
          pokemon.set('forme', newForme)
          view.changeSprite(player, slot, newSpecies, newForme, done)
        when Protocol.NAME_CHANGE
          [player, slot, newName] = rest
          pokemon = @getPokemon(player, slot)
          view.changeName(player, slot, newName, done)
        when Protocol.BOOSTS
          [player, slot, deltaBoosts] = rest
          view.boost(player, slot, deltaBoosts, floatText: true)
          done()
        when Protocol.SET_BOOSTS
          [player, slot, boosts] = rest
          view.setBoosts(player, slot, boosts)
          done()
        when Protocol.RESET_BOOSTS
          [player, slot] = rest
          view.resetBoosts(player, slot)
          done()
        when Protocol.MOVESET_UPDATE
          [player, slot, movesetJSON] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.set(movesetJSON)
          done()
        when Protocol.WEATHER_CHANGE
          [newWeather] = rest
          view.changeWeather(newWeather, done)
        when Protocol.TEAM_PREVIEW
          view.renderTeamPreview()
          done()
        when Protocol.ACTIVATE_ABILITY
          [player, slot, ability] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.set('ability', ability)
          view.activateAbility(player, slot, ability, done)
        when Protocol.END_MOVE
          doneSpeedTimeout()
        when Protocol.ILLUSION_CHANGE
          [player, slot, change] = rest
          pokemon = @getPokemon(player, slot)
          pokemon.setIllu(change)
          done()
        when Protocol.VISIBLE_TEAM
          view.setVisibleTeam()
        else
          done()
    catch e
      console.error(e)
      console.error(e.stack)
      done()
    if wasAtBottom && !view.chatView.isAtBottom()
      view.chatView.scrollToBottom()
