/**
   * Runs the server. The server will wait for some match info (which specifies the teams and set of
   * maps to run) and then begin running matches.
   */
  public void run() {
    int aWins = 0, bWins = 0;

    while (!matches.isEmpty()) {
      Match match = matches.peek();
      if (!finished.isEmpty())
        match.setInitialTeamMemory(finished.getLast().getComputedTeamMemory());

      try {
        debug("running match " + match);
        match.initialize();
        runMatch(match, proxyWriter);
        finished.add(match);
        matches.remove(match);

        if (match.getWinner() == Team.A) aWins++;
        else if (match.getWinner() == Team.B) bWins++;

        match.finish();

        // Allow best of three scrimmages -- single game scrims should still work fine
        // TODO:This "win mode" should probably be something from the database
        if (mode == Mode.TOURNAMENT || mode == Mode.SCRIMMAGE) {
          if (aWins == 2 || bWins == 2) break;
        }

      } catch (Exception e) {
        ErrorReporter.report(e);
        error("couldn't run match: ");

        this.state = State.ERROR;
      }
    }

    proxyWriter.terminate();
  }
  /**
   * Runs a match; configures the controller and list of proxies, and starts running the game in a
   * separate thread.
   */
  private void runMatch(final Match match, final ProxyWriter proxyWriter) throws Exception {
    if (Mode.HEADLESS.equals(mode) || Mode.SCRIMMAGE.equals(mode) || Mode.TOURNAMENT.equals(mode)) {
      this.state = State.RUNNING;
      this.runUntil = Integer.MAX_VALUE;
    }

    // Poll for RUNNING, if mode == Mode.LOCAL
    while (!State.RUNNING.equals(state)) {
      try {
        Thread.sleep(250);
      } catch (InterruptedException e) {
      }
    }

    long startTime = System.currentTimeMillis();

    say("-------------------- Match Starting --------------------");
    say(match.toString());

    // Compute the header and send it to all listeners.
    MatchHeader header = match.getHeader();
    proxyWriter.enqueue(header);
    ExtensibleMetadata exHeader = match.getHeaderMetadata();
    proxyWriter.enqueue(exHeader);

    this.state = State.RUNNING;

    int count = 0;

    final String throttle = options.get("bc.server.throttle");
    final int throttleCount = options.getInt("bc.server.throttle-count");
    final boolean doYield = "yield".equals(throttle);
    final boolean doSleep = "sleep".equals(throttle);

    // If there are more rounds to be run, run them and
    // and send the round (and optionally stats) bytes to
    // recipients.
    while (match.hasMoreRounds()) {

      // If not paused/stopped:
      switch (this.state) {
        case RUNNING:
          if (match.getRoundNumber() == runUntil) {
            Thread.sleep(25);
            break;
          }

          final RoundDelta round = match.getRound();
          if (round == null) break;

          if (GameState.BREAKPOINT.equals(match.getGameState())) {
            this.state = State.PAUSED;
            proxyWriter.enqueue(new PauseEvent());
          } else if (GameState.DONE.equals(match.getGameState())) {
            this.state = State.FINISHED;
          }

          proxyWriter.enqueue(round);

          if (count++ == throttleCount) {
            if (doYield) Thread.yield();
            else if (doSleep) Thread.sleep(1);
            count = 0;
          }

          break;

        case PAUSED:
          Thread.sleep(250);
          break;
      }
    }

    // Compute footer data.
    GameStats gameStats = match.getGameStats();
    MatchFooter footer = match.getFooter();

    proxyWriter.enqueue(gameStats);
    proxyWriter.enqueue(footer);

    say(match.getWinnerString());
    say("-------------------- Match Finished --------------------");

    double timeDiff = (System.currentTimeMillis() - startTime) / 1000.0;
    debug(String.format("match completed in %.4g seconds", timeDiff));

    this.state = State.FINISHED;
  }