/*
 * Copyright (c) 2022.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import {isNumber, IValidator, ValidationResult} from "./index";

type NumberRule =
    | { type: "equal", value: number }
    | { type: "notEqual", value: number }

export class NumberValidator implements IValidator<number> {
    constructor(private rules?: NumberRule[]) {
        if (!Array.isArray(this.rules)) {
            this.rules = [];
        }
    }

    /**
     * Adds a rule to the array of rules, or replaces a rule if it already exists.
     * Use this function to prevent having multiple rules of the same type.
     */
    private addRule: (rule: NumberRule) => NumberRule[] = rule => {
        // Filter the current rule set, removing any rule that has the same type of the one being added
        const filtered = this.rules.filter(r => r.type !== rule.type);

        // Add the new rule to the filtered rule array
        return [...filtered, rule]
    }

    /**
     * Fails if the value being validated is not equal to @param value.
     */
    equals: (value: number) => NumberValidator = value => {
        this.rules = this.addRule({ type: "equal", value: value });
        return this;
    }

    /**
     * Fails if the value being validated is equal to @param value.
     */
    notEquals: (value: number) => NumberValidator = value => {
        this.rules = this.addRule({ type: "notEqual", value: value });
        return this;
    }

    /**
     * Checks an individual rule against the value being validated.
     */
    checkRule: (rule: NumberRule, value: number) => ValidationResult<number> = (rule: NumberRule, value : number) => {
        const err = (msg: string) : ValidationResult<number> => ({ success: false, message: msg });
        const ok = () : ValidationResult<number> => ({ success: true, value: value });

        switch (rule.type) {
            case "equal":
                return rule.value !== value
                    ? err(`Value was expected to be ${rule.value} but was ${value}.`)
                    : ok();

            case "notEqual":
                return rule.value === value
                    ? err(`Value must not be ${rule.value}.`)
                    : ok();
        }
    }

    go: (value: unknown) => ValidationResult<number> = value => {
        if (!isNumber(value)) {
            return {
                success: false,
                message: `Expected a number but received ${typeof value}.`
            }
        }

        for (let rule of this.rules) {
            const result = this.checkRule(rule, value);

            if (result.success === false) {
                return result;
            }
        }

        // If none of the rules in the loop had an error, the value passed validation!
        return {
            success: true,
            value: value
        }
    }
}
