// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
//
// SPDX-License-Identifier: MIT

import { getGame } from "../utils/utils";

export function evaluateCheck(
  dice: number[],
  checkTargetNumber: number,
  {
    maximumCoupResult = 1,
    minimumFumbleResult = 20,
    canFumble = true,
  }: { maximumCoupResult?: number; minimumFumbleResult?: number; canFumble?: boolean } = {},
): SubCheckResult[] {
  const diceWithSubChecks = assignSubChecksToDice(dice, checkTargetNumber, {
    maximumCoupResult: maximumCoupResult,
  });
  return evaluateDiceWithSubChecks(diceWithSubChecks, {
    maximumCoupResult: maximumCoupResult,
    minimumFumbleResult: minimumFumbleResult,
    canFumble: canFumble,
  });
}

interface DieWithSubCheck {
  result: number;
  checkTargetNumber: number;
}

function assignSubChecksToDice(
  dice: number[],
  checkTargetNumber: number,
  {
    maximumCoupResult = 1,
  }: {
    maximumCoupResult?: number;
  } = {},
): DieWithSubCheck[] {
  const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);

  if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
    throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
  }

  const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);

  const indexOfSmallestNonCoup = findIndexOfSmallestNonCoup(dice, maximumCoupResult);
  const indexOfFirstCoup = dice.findIndex((die) => die <= maximumCoupResult);
  const indexForLastSubCheck = shouldUseCoupForLastSubCheck(
    indexOfSmallestNonCoup,
    indexOfFirstCoup,
    dice,
    checkTargetNumberForLastSubCheck,
  )
    ? indexOfFirstCoup
    : indexOfSmallestNonCoup;

  return dice.map((die, index) => ({
    result: die,
    checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20,
  }));
}

function findIndexOfSmallestNonCoup(dice: number[], maximumCoupResult: number): number {
  return dice
    .map((die, index): [number, number] => [die, index])
    .filter((indexedDie) => indexedDie[0] > maximumCoupResult)
    .reduce(
      (smallestIndexedDie, indexedDie) => (indexedDie[0] < smallestIndexedDie[0] ? indexedDie : smallestIndexedDie),
      [Infinity, -1],
    )[1];
}

function shouldUseCoupForLastSubCheck(
  indexOfSmallestNonCoup: number,
  indexOfFirstCoup: number,
  dice: readonly number[],
  checkTargetNumberForLastSubCheck: number,
) {
  if (indexOfFirstCoup !== -1 && indexOfSmallestNonCoup === -1) {
    return true;
  }
  const smallestNonCoup = dice[indexOfSmallestNonCoup];
  if (
    !Number.isInteger(indexOfFirstCoup) ||
    indexOfFirstCoup < -1 ||
    !Number.isInteger(indexOfSmallestNonCoup) ||
    smallestNonCoup === undefined
  ) {
    throw new Error("Received an invalid value for the parameter indexOfSmallestNonCoup or indexOfFirstCoup,");
  }
  return (
    indexOfFirstCoup !== -1 &&
    smallestNonCoup > checkTargetNumberForLastSubCheck &&
    smallestNonCoup + checkTargetNumberForLastSubCheck > 20
  );
}

interface SubCheckResult extends DieWithSubCheck {
  active?: boolean;
  discarded?: boolean;
  success?: boolean;
  failure?: boolean;
  count?: number;
}

function evaluateDiceWithSubChecks(
  results: DieWithSubCheck[],
  {
    maximumCoupResult,
    minimumFumbleResult,
    canFumble,
  }: { maximumCoupResult: number; minimumFumbleResult: number; canFumble: boolean },
): SubCheckResult[] {
  return results.map((dieWithSubCheck, index) => {
    const result: SubCheckResult = {
      ...dieWithSubCheck,
      active: dieWithSubCheck.result <= dieWithSubCheck.checkTargetNumber,
      discarded: dieWithSubCheck.result > dieWithSubCheck.checkTargetNumber,
    };
    if (result.result <= maximumCoupResult) {
      result.success = true;
      result.count = result.checkTargetNumber;
      result.active = true;
      result.discarded = false;
    }
    if (index === 0 && canFumble && result.result >= minimumFumbleResult) result.failure = true;
    return result;
  });
}

export function getRequiredNumberOfDice(checkTargetNumber: number): number {
  return Math.max(Math.ceil(checkTargetNumber / 20), 1);
}