import {
  RouteNames,
  SpeedCompetitionCategoryResult,
} from '../models/Competition';
import {
  Participant,
  participantFromApiParticipant,
  Result,
} from '../models/Participant';

export interface SpeedRoundParticipant {
  id: string;
  firstName: string;
  hasWon: boolean;
}

export interface SpeedLane {
  result?: Result;
  participant: Participant;
}

export interface SpeedRoundPair {
  laneA?: SpeedLane;
  laneB?: SpeedLane;
  winner?: 'A' | 'B';
}

export interface SpeedRound {
  pairs: SpeedRoundPair[];
  roundIndex: number;
  roundName?: string;
}

export interface SpeedFlowchartResult {
  rounds: SpeedRound[];
}

/**
 *
 * @param {number} roundNumber
 * @param {RouteNames} routeNames
 * @return {string | undefined}
 */
function getRoundName(
  roundNumber: number,
  routeNames: RouteNames,
): string | undefined {
  if (roundNumber < 2 || roundNumber > 6) return undefined;

  return routeNames[roundNumber];
}

/**
 *
 * @param {string} name
 * @return {number}
 */
function getRoundRank(name: string): number {
  const match = name.match(/1\/([842])/);
  if (match === undefined || match === null || match.length !== 2) {
    return 2;
  }
  return parseInt(match[1]);
}

/**
 *
 * @param {SpeedRoundPair} pair
 * @param {number} roundIndex
 */
function computeWinnerOfPair(pair: SpeedRoundPair, roundIndex: number) {
  if (
    pair.laneA?.participant.results[roundIndex]?.rank === undefined ||
    pair.laneB?.participant.results[roundIndex]?.rank === undefined
  )
    return;

  console.log('Conputing winner of round ', roundIndex);

  pair.laneA.result = pair.laneA.participant.results[roundIndex];
  pair.laneB.result = pair.laneB.participant.results[roundIndex];

  if (pair.winner === undefined) {
    pair.winner =
      pair.laneA.participant.results[roundIndex].rank >
      pair.laneB.participant.results[roundIndex].rank
        ? 'B'
        : 'A';
  }
}

/**
 *
 * @param {SpeedRoundPair} pair
 * @param {number} roundNumber
 * @return {Participant | undefined}
 */
function getWinnerOfPair(
  pair: SpeedRoundPair,
  roundNumber: number,
): Participant | undefined {
  computeWinnerOfPair(pair, roundNumber);
  return {
    ['A']: pair.laneA?.participant,
    ['B']: pair.laneB?.participant,
    ['']: undefined,
  }[pair.winner ?? ''];
}

/**
 *
 * @param {SpeedRoundPair} pair
 * @param {number} roundNumber
 * @return {Participant | undefined}
 */
function getLooserOfPair(
  pair: SpeedRoundPair,
  roundNumber: number,
): Participant | undefined {
  computeWinnerOfPair(pair, roundNumber);
  return {
    ['A']: pair.laneB?.participant,
    ['B']: pair.laneA?.participant,
    ['']: undefined,
  }[pair.winner ?? ''];
}

/**
 *
 * @param {number} roundIndex index of the new round
 * @param {string} roundName name of the new round
 * @param {SpeedRound} previousRound
 * @param {number} roundRank
 * @param {boolean} takeLooser
 * @return {SpeedRound}
 */
function computeRoundFromPreviousRound(
  roundIndex: number,
  roundName: string,
  previousRound: SpeedRound,
  roundRank: number,
  takeLooser = false,
): SpeedRound {
  const getAdvancingParticipant = takeLooser
    ? getLooserOfPair
    : getWinnerOfPair;
  const nextRoundPairs = new Array(roundRank / 2).fill(0).map((_, i) => {
    const laneAParticipant = getAdvancingParticipant(
      previousRound.pairs[i * 2],
      previousRound.roundIndex,
    );
    const laneBParticipant = getAdvancingParticipant(
      previousRound.pairs[i * 2 + 1],
      previousRound.roundIndex,
    );

    return {
      laneA:
        laneAParticipant === undefined
          ? undefined
          : {
              participant: laneAParticipant,
            },
      laneB:
        laneBParticipant === undefined
          ? undefined
          : {
              participant: laneBParticipant,
            },
    };
  });

  return {
    pairs: nextRoundPairs,
    roundIndex: roundIndex,
    roundName: roundName,
  };
}

/**
 *
 * @param {SpeedCompetitionCategoryResult} result The result to process
 * @return {SpeedFlowchartResult}
 */
export function convertResultsToSpeedFlowchartResult(
  result: SpeedCompetitionCategoryResult,
): SpeedFlowchartResult {
  const rounds: SpeedRound[] = [];
  const convertedParticipants = result.participants
    .map(fromApi => participantFromApiParticipant(fromApi))
    // sort by qualification result
    .sort((a, b) => a.results[0].rank - b.results[0].rank);

  const roundIndices = Object.keys(result.route_names)
    .map(number => parseInt(number))
    .filter(number => number > 0);

  // process first round
  const firstRoundName = getRoundName(roundIndices[0], result.route_names);
  const firstRoundRank = getRoundRank(
    getRoundName(roundIndices[0], result.route_names) ?? '',
  );

  const getOpponent = (ofRank: number): Participant => {
    return convertedParticipants[firstRoundRank * 2 - 1 - ofRank];
  };

  // Should be:
  //   0, 1, 2, 3, 4, 5, 6, 7
  // - 1,16, 8, 9, 4,13, 5,12, 2,15, 7,10, 3,14, 6,11
  // - 1, 8, 4, 5, 2, 7, 3, 6  for firstRoundNumber=8
  // - 1, 4, 2, 3               for firstRoundNumber=4
  // - 1, 2                     for firstRoundNumber=2
  // TODO: come up with a proper alogorithm maybe
  const ranksOfLaneAInOrder = [
    [1, 2],
    [1, 4, 2, 3],
    [1, 8, 4, 5, 2, 7, 3, 6],
  ][firstRoundRank / 4];

  const firstRoundPairs = ranksOfLaneAInOrder.map(rank => {
    return {
      laneA: { participant: convertedParticipants[rank - 1] },
      laneB: { participant: getOpponent(rank - 1) },
    };
  });

  const firstRound: SpeedRound = {
    pairs: firstRoundPairs,
    roundIndex: roundIndices[0],
    roundName: firstRoundName,
  };

  rounds.push(firstRound);

  console.log(firstRound);

  // compute following rounds
  let roundIndex = roundIndices[1];
  for (let roundRank = firstRoundRank; roundRank > 2; roundRank /= 2) {
    console.log('Processing rank', roundRank);
    rounds.push(
      computeRoundFromPreviousRound(
        roundIndex,
        result.route_names[roundIndex] ?? '',
        rounds[rounds.length - 1],
        roundRank,
      ),
    );
    roundIndex++;
  }

  // compute final and semi final
  const semifinalRoundIndex = roundIndex++;
  const semifinal = computeRoundFromPreviousRound(
    semifinalRoundIndex,
    result.route_names[semifinalRoundIndex] ?? '',
    rounds[rounds.length - 1],
    2,
    true,
  );
  computeWinnerOfPair(semifinal.pairs[0], semifinalRoundIndex);
  rounds.push(semifinal);

  const finalRoundIndex = roundIndex;
  const final = computeRoundFromPreviousRound(
    finalRoundIndex,
    result.route_names[finalRoundIndex] ?? '',
    rounds[rounds.length - 2],
    2,
  );
  computeWinnerOfPair(final.pairs[0], finalRoundIndex);
  rounds.push(final);

  return {
    rounds: rounds,
  };
}
