import { DS4RollProvider, RollProvider } from "./roll-provider";

export function ds4test(testValue: number, rollOptions: RollOptions = new RollOptions()): RollResult {
    const finalRollValue = testValue;
    if (finalRollValue <= 20) {
        return rollCheckSingleDie(finalRollValue, rollOptions);
    } else {
        return rollCheckMultipleDice(finalRollValue, rollOptions);
    }
}

export function rollCheckSingleDie(
    testValue: number,
    rollOptions: RollOptions,
    provider: RollProvider = new DS4RollProvider(),
): RollResult {
    const roll = provider.getNextRoll();
    const dice = [roll];

    if (roll <= rollOptions.maxCritSucc) {
        return new RollResult(testValue, RollResultStatus.CRITICAL_SUCCESS, dice);
    } else if (roll >= rollOptions.minCritFail) {
        return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
    } else {
        if (roll <= testValue) {
            return new RollResult(roll, RollResultStatus.SUCCESS, dice);
        } else {
            return new RollResult(0, RollResultStatus.FAILURE, dice);
        }
    }
}

export function rollCheckMultipleDice(
    testValue: number,
    rollOptions: RollOptions,
    provider: RollProvider = new DS4RollProvider(),
): RollResult {
    const finalCheck = testValue % 20;
    const numberOfDice = Math.ceil(testValue / 20);

    const dice = provider.getNextRolls(numberOfDice);

    const firstResult = dice[0];

    if (firstResult >= rollOptions.minCritFail) {
        return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
    }

    const partitionCallback = (prev: [Array<number>, Array<number>], cur: number) => {
        if (cur <= rollOptions.maxCritSucc) {
            prev[0].push(cur);
        } else {
            prev[1].push(cur);
        }
        return prev;
    };

    const [critSuccesses, otherRolls] = dice
        .reduce(partitionCallback, [[], []])
        .map((a) => a.sort((r1, r2) => r2 - r1));

    const sortedRollResults: Array<number> = critSuccesses.concat(otherRolls);

    const evaluationResult = sortedRollResults
        .map((value, index) => {
            if (index == numberOfDice - 1) {
                if (value == 1) {
                    return finalCheck;
                } else {
                    return value <= finalCheck ? value : 0;
                }
            } else {
                if (value <= rollOptions.maxCritSucc) {
                    return 20;
                } else {
                    return value;
                }
            }
        })
        .reduce((a, b) => a + b);

    return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults);
}

export class RollOptions {
    public maxCritSucc = 1;
    public minCritFail = 20;
}

export class RollResult {
    constructor(public value: number, public status: RollResultStatus, public dice: Array<number>) {}
}

export enum RollResultStatus {
    FAILURE,
    SUCCESS,
    CRITICAL_FAILURE,
    CRITICAL_SUCCESS,
}