<commit_before>import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/distinctUntilChanged';

import { Pairing, Player } from '../models';
import { PlayerService } from './player.service';

@Injectable()
export class PairingService {
  pairings: Observable<Pairing[]>;
  selectedPairing: Observable<Pairing>;
  submittedPairings: Observable<Pairing[]>;

  private _pairings: Pairing[];
  private pairingsByRoundsMap: {[round: number]: Pairing[]} = {};
  private pairingsSubject = new BehaviorSubject<Pairing[]>([]);
  private players: Player[];
  private _selectedPairing: Pairing;
  private selectedPairingSubject = new BehaviorSubject<Pairing>(null);

  private readonly lsKeys = {
    pairings: 'pairings'
  };

  constructor(private playerService: PlayerService) {
    // Load data.
    this.loadFromLocalStorage();
    this.playerService.players.subscribe((players: Player[]) => this.players = players);

    // Setup Observables.
    this.pairings = this.pairingsSubject.asObservable().distinctUntilChanged();
    this.selectedPairing = this.selectedPairingSubject.asObservable().distinctUntilChanged();
    this.submittedPairings = this.pairings.map((pairings: Pairing[]) => {
      return pairings.filter((pairing: Pairing) => pairing.submitted);
    }).distinctUntilChanged();

    this.pairingsSubject.next(this._pairings.slice());
  }

  clearResults(round: number): void {
    this.pairingsByRoundsMap[round].forEach(pairing => {
      pairing.player1Wins = 0;
      pairing.player2Wins = 0;
      pairing.draws = 0;
      pairing.submitted = false;
    });

    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  createPairings(round: number): void {
    this.pairingsByRoundsMap[round] = [];
    let players = this.shufflePlayers(this.players);

    if (round === 1) {
      let table = 1;

      while (players.length > 1) {
        const pairing = new Pairing(round, table++, players.shift(), players.shift());
        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }

      if (players.length) {
        const pairing = new Pairing(round, table, players.shift(), null);
        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }
    } else {
      // Phase 1
      players = players.sort((a: Player, b: Player) => {
        // At this point only match points matter.
        return b.matchPoints - a.matchPoints;
      });

      const playerPreferenceMap = this.createPlayerPreferenceMap(players);
      console.log(playerPreferenceMap);
      this.reducePlayerPreferenceMap(playerPreferenceMap);
      console.log(playerPreferenceMap);

      let done = true;

      for (const player in playerPreferenceMap) {
        if (playerPreferenceMap[player].length > 1) {
          done = false;
          break;
        }
      }

      if (!done) {
        // Phase 2
        console.error('Phase 2 got hit!!');
        while (true) {
          // Identify a rotation.
          let rotation = this.findRotation(playerPreferenceMap);
          console.log(rotation);
          break;
          // TODO: Maybe stop shuffling players before making pairings.
        }
      }

      let table = 1;

      while (players.length > 0) {
        const player1 = players.shift();
        const player2Id = playerPreferenceMap[player1.id][0];
        let pairing = null;

        if (player2Id === -1) {
          // player1 got a bye.
          pairing = new Pairing(round, table++, player1, null);
        } else {
          const player2Index = players.findIndex((player: Player) => player.id === player2Id);
          const player2 = players.splice(player2Index, 1)[0];
          pairing = new Pairing(round, table++, player1, player2);
        }

        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }
    }

    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  deletePairings(round: number): void {
    const pairingsForRound = this.pairingsByRoundsMap[round];
    delete this.pairingsByRoundsMap[round];
    this._pairings = this._pairings.filter((pairing: Pairing) => {
      return pairingsForRound.indexOf(pairing) === -1;
    });
    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  saveAndClearSelected(): void {
    this.saveToLocalStorage();
    this._selectedPairing = null;
    this.selectedPairingSubject.next(this._selectedPairing);
    this.pairingsSubject.next(this._pairings.slice());
  }

  setSelectedPairing(pairing: Pairing) {
    this._selectedPairing = pairing;
    this.selectedPairingSubject.next(this._selectedPairing);
  }

  private createPlayerPreferenceMap(players: Player[]): {[playerId: number]: number[]} {
    const playerPreferenceMap = {};
    const needsBye = players.length % 2 === 1;

    players.forEach((player: Player) => {
      const potentialOpps = this.shufflePlayers(players).filter((opp: Player) => {
        return player !== opp && player.opponentIds.indexOf(opp.id) === -1;
      }).sort((a: Player, b: Player) => {
        return b.matchPoints - a.matchPoints;
      }).map((opp: Player) => {
        return opp.id;
      });

      if (needsBye) {
        potentialOpps.push(-1);
      }

      playerPreferenceMap[player.id] = potentialOpps;
    });

    if (needsBye) {
      const potentialOpps = players.sort((a: Player, b: Player) => {
        return a.matchPoints - b.matchPoints;
      }).map((opp: Player) => {
        return opp.id;
      });

      playerPreferenceMap[-1] = potentialOpps;
    }

    return playerPreferenceMap;
  }

  private findRotation(playerPreferenceMap: {[playerId: number]: number[]}): number[][] {
    // Find player with multiple opponents on reduced list.
    let player = null;
    const rotation = [];
    const playersInRotation = [];

    for (const playerId in playerPreferenceMap) {<commit_msg>#22: Finishing implementing phase 2 of pairing algorithm. - The way I have it implemented I'm not sure if it will ever even get hit, but better to be safe.<commit_after>import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/distinctUntilChanged';

import { Pairing, Player } from '../models';
import { PlayerService } from './player.service';

@Injectable()
export class PairingService {
  pairings: Observable<Pairing[]>;
  selectedPairing: Observable<Pairing>;
  submittedPairings: Observable<Pairing[]>;

  private _pairings: Pairing[];
  private pairingsByRoundsMap: {[round: number]: Pairing[]} = {};
  private pairingsSubject = new BehaviorSubject<Pairing[]>([]);
  private players: Player[];
  private _selectedPairing: Pairing;
  private selectedPairingSubject = new BehaviorSubject<Pairing>(null);

  private readonly lsKeys = {
    pairings: 'pairings'
  };

  constructor(private playerService: PlayerService) {
    // Load data.
    this.loadFromLocalStorage();
    this.playerService.players.subscribe((players: Player[]) => this.players = players);

    // Setup Observables.
    this.pairings = this.pairingsSubject.asObservable().distinctUntilChanged();
    this.selectedPairing = this.selectedPairingSubject.asObservable().distinctUntilChanged();
    this.submittedPairings = this.pairings.map((pairings: Pairing[]) => {
      return pairings.filter((pairing: Pairing) => pairing.submitted);
    }).distinctUntilChanged();

    this.pairingsSubject.next(this._pairings.slice());
  }

  clearResults(round: number): void {
    this.pairingsByRoundsMap[round].forEach(pairing => {
      pairing.player1Wins = 0;
      pairing.player2Wins = 0;
      pairing.draws = 0;
      pairing.submitted = false;
    });

    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  createPairings(round: number): void {
    this.pairingsByRoundsMap[round] = [];
    let players = this.shufflePlayers(this.players);

    if (round === 1) {
      let table = 1;

      while (players.length > 1) {
        const pairing = new Pairing(round, table++, players.shift(), players.shift());
        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }

      if (players.length) {
        const pairing = new Pairing(round, table, players.shift(), null);
        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }
    } else {
      // Phase 1
      players = players.sort((a: Player, b: Player) => {
        // At this point only match points matter.
        return b.matchPoints - a.matchPoints;
      });

      const playerPreferenceMap = this.createPlayerPreferenceMap(players);
      this.reducePlayerPreferenceMap(playerPreferenceMap);
      let done = true;

      for (const player in playerPreferenceMap) {
        if (playerPreferenceMap[player].length > 1) {
          done = false;
        } else if (playerPreferenceMap[player].length === 0) {
          throw new Error('Player preference list should always have at least 1 opponent.');
        }
      }

      if (!done) {
        // Phase 2
        console.error('Phase 2 got hit!!');
        while (true) {
          // Identify a rotation.
          const rotation = this.findRotation(playerPreferenceMap);
          console.log(rotation);

          // Eliminate the rotation.
          this.eliminateRotation(playerPreferenceMap, rotation);

          done = true;

          for (const player in playerPreferenceMap) {
            if (playerPreferenceMap[player].length > 1) {
              done = false;
            } else if (playerPreferenceMap[player].length === 0) {
              throw new Error('Eliminating rotation has reduced a preference list to zero.');
            }
          }

          if (done) {
            break;
          }
        }
      }

      let table = 1;

      while (players.length > 0) {
        const player1 = players.shift();
        const player2Id = playerPreferenceMap[player1.id][0];
        let pairing = null;

        if (player2Id === -1) {
          // player1 got a bye.
          pairing = new Pairing(round, table++, player1, null);
        } else {
          const player2Index = players.findIndex((player: Player) => player.id === player2Id);
          const player2 = players.splice(player2Index, 1)[0];
          pairing = new Pairing(round, table++, player1, player2);
        }

        this._pairings.push(pairing);
        this.pairingsByRoundsMap[round].push(pairing);
      }
    }

    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  deletePairings(round: number): void {
    const pairingsForRound = this.pairingsByRoundsMap[round];
    delete this.pairingsByRoundsMap[round];
    this._pairings = this._pairings.filter((pairing: Pairing) => {
      return pairingsForRound.indexOf(pairing) === -1;
    });
    this.saveToLocalStorage();
    this.pairingsSubject.next(this._pairings.slice());
  }

  saveAndClearSelected(): void {
    this.saveToLocalStorage();
    this._selectedPairing = null;
    this.selectedPairingSubject.next(this._selectedPairing);
    this.pairingsSubject.next(this._pairings.slice());
  }

  setSelectedPairing(pairing: Pairing) {
    this._selectedPairing = pairing;
    this.selectedPairingSubject.next(this._selectedPairing);
  }

  private createPlayerPreferenceMap(players: Player[]): {[playerId: number]: number[]} {
    const playerPreferenceMap = {};
    const needsBye = players.length % 2 === 1;

    players.forEach((player: Player) => {
      const potentialOpps = players.filter((opp: Player) => {
        return player !== opp && player.opponentIds.indexOf(opp.id) === -1;
      }).map((opp: Player) => {
        return opp.id;
      });

      if (needsBye) {
        potentialOpps.push(-1);
      }

      playerPreferenceMap[player.id] = potentialOpps;
    });

    if (needsBye) {
      const potentialOpps = players.sort((a: Player, b: Player) => {
        return a.matchPoints - b.matchPoints;
      }).map((opp: Player) => {
        return opp.id;
      });

      playerPreferenceMap[-1] = potentialOpps;
    }

    return playerPreferenceMap;
  }

  private eliminateRotation(playerPreferenceMap: {[playerId: number]: number[]}, rotation: number[][]): void {
    for (const pair of rotation) {
      const playerAId = pair[0];
      const playerBId = pair[1];
      const playerAIdIndex = playerPreferenceMap[playerBId].indexOf(playerAId);
      playerPreferenceMap[playerBId].splice(playerAIdIndex, 1);
      const playerBIdIndex = playerPreferenceMap[playerAId].indexOf(playerBId);
      playerPreferenceMap[playerAId].splice(playerBIdIndex, 1);
    }
  }

  private findRotation(playerPreferenceMap: {[playerId: number]: number[]}): number[][] {
    // Find player with multiple opponents on reduced list.
    let player = null;
    const rotation = [];
    const playersInRotation = [];

    for (const playerId in playerPreferenceMap) {
{'fraction_non_alphanumeric': 0.1284954407294833, 'fraction_numerical': 0.006306990881458967, 'mean_word_length': 2.986973644350197, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '921951', 'n_tokens_mistral': 4000, 'n_tokens_neox': 3829, 'n_words': 811}