.pragma library

/**
 * Function to extract results from PartiipantFromApi
 * @param {ParticipantFromApi} fromApi
 * @return {Result[]}
 */
function _extractResults(fromApi) {
  const results = Array(7);

  const existingResults = Object.keys(fromApi).filter(key =>
    key.match(/result[0-9]/),
  );

  for (const result of existingResults) {
    let roundNumber = 0;
    const match = result.match(/result([0-9])/);
    if (match !== undefined && match !== null) {
      roundNumber = parseInt(match[1]);
    }
    if (roundNumber < 0) {
      continue;
    }
    results[roundNumber] = {
      rank: parseInt(fromApi[`result_rank${roundNumber}`] as string),
      result: fromApi[`result${roundNumber}`] as string,
    };
  }
  return results;
}

/**
 * Function to clean up participants from the api
 * @param {ParticipantFromApi} fromApi
 * @return {Participant}
 */
function participantFromApiParticipant(
  fromApi,
) {
  const results = _extractResults(fromApi);

  return {
    id: fromApi.PerId,
    firstName: fromApi.firstname,
    lastName: fromApi.lastname,
    results: results,
    overallRank: fromApi.result_rank,
    startNumber: parseInt(fromApi.start_number),
  };
}


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

  return routeNames[roundNumber];
}

/**
 *
 * @param {string} name
 * @return {number}
 */
function getRoundRank(name) {
  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, roundIndex) {
  if (
    !(pair.laneA && pair.laneA.participant.results[roundIndex] && pair.laneA.participant.results[roundIndex].rank) ||
    !(pair.laneB && pair.laneB.participant.results[roundIndex] && pair.laneB.participant.results[roundIndex].rank)
  )
    return;

  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,
  roundNumber,
) {
  computeWinnerOfPair(pair, roundNumber);
  return {
    ['A']: pair.laneA ? pair.laneA.participant : undefined,
    ['B']: pair.laneB ? pair.laneB.participant : undefined,
    ['']: undefined,
  }[pair.winner ? pair.winner:''];
}

/**
 *
 * @param {SpeedRoundPair} pair
 * @param {number} roundNumber
 * @return {Participant | undefined}
 */
function getLooserOfPair(
  pair,
  roundNumber,
) {
  computeWinnerOfPair(pair, roundNumber);
    return {
      ['A']: pair.laneB ? pair.laneB.participant : undefined,
      ['B']: pair.laneA ? pair.laneA.participant : undefined,
      ['']: undefined,
    }[pair.winner ? 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,
  roundName,
  previousRound,
  roundRank,
  takeLooser = false,
) {
  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}
 */
function convertResultsToSpeedFlowchartResult(
  result,
) {
  const rounds = [];
  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 tmpRoundName = getRoundName(roundIndices[0], result.route_names);
  const firstRoundRank = getRoundRank(
     tmpRoundName ? tmpRoundName:'',
  );

  const getOpponent = (ofRank) => {
    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],
  ][Math.floor(firstRoundRank / 4)];

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

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

  rounds.push(firstRound);

  // compute following rounds
  let roundIndex = roundIndices[1];
  for (let roundRank = firstRoundRank; roundRank > 2; roundRank /= 2) {
    rounds.push(
      computeRoundFromPreviousRound(
        roundIndex,
        result.route_names[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] ? 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] ? result.route_names[finalRoundIndex]:'',
    rounds[rounds.length - 2],
    2,
  );
  computeWinnerOfPair(final.pairs[0], finalRoundIndex);
  rounds.push(final);

  return {
    rounds: rounds,
  };
}
