import { Injectable } from "@nestjs/common";
import { Decimal } from 'decimal.js';

/*
 * Service pour les opérations sur la finance ou les nombres décimaux
 * @service FinancesService
*/
@Injectable()
export class FinancesService{

    /**
     * le traitement de l'adition de deux nombres décimaux
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns {Decimal} La somme de ces deux nombres
     */
    add(a: Decimal, b: Decimal): Decimal {
        return a.plus(b);
    }

    /**
     * Le traitement de la soustraction de deux nombres décimaux
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns {Decimal} La soustration de ces deux nombres
     */
    subtract(a: Decimal, b: Decimal): Decimal {
        return a.minus(b);
    }

    /**
     * Le traitement de la multiplication de deux nombres décimaux
     * @param {Decimal} a - le premier nombre 
     * @param {Decimal} b - le deuxième nombre
     * @returns {Decimal} Le produit de ces deux nombres
     */
    multiply(a: Decimal, b: Decimal): Decimal {
        return a.mul(b);
    }

    /**
     * Division sécurisée avec gestion des erreurs et précision configurable
     * @param {Decimal} dividend - le dividende
     * @param {Decimal} divisor - le diviseur
     * @param {number} decimalPlaces Nombre de décimales (par défaut: 2 pour les montants financiers)
     * @throws {Decimal} le quotient de ce deux nombres ou Error si division par zéro 
   */
    divide(dividend: Decimal,   divisor: Decimal, decimalPlaces: number = 2): Decimal {
        if (divisor.equals(0)) {
            throw new Error('Division par zéro non autorisée');
        }

        return dividend.dividedBy(divisor).toDecimalPlaces(decimalPlaces);
    }

    /**
     * 
     * @param {Decimal} dividend - le dividende 
     * @param {Decimal} divisor - le diviseur
     * @param {number} decimalPlaces - le nombre de chiffres après la virgule
     * @returns {Decimal} - Le quotient de ces deux nombres ou l'erreur
     */
    divideWithBankersRounding(
                                dividend: Decimal,
                                divisor: Decimal,
                                decimalPlaces: number = 2
                            ): Decimal {
        if (divisor.equals(0)) {
            throw new Error('Division par zéro non autorisée');
        }
        return dividend.dividedBy(divisor)
                .toDecimalPlaces(decimalPlaces, Decimal.ROUND_HALF_EVEN);
    }

    /**
     * La comparaison pour déterminer la valeur la plus grande de ces deux nombres
     * @param {Decimal} a - le premier nombre 
     * @param {Decimal} b - le deuxième nombre
     * @returns {boolean} - la réponse
     */
    isGreaterThan(a: Decimal, b: Decimal): boolean {
        return a.greaterThan(b);
    }

    /**
     * La comparaison pour déterminer la valeur la plus petite de ces deux nombres
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns {boolean} - la réponse
     */
    isLessThan(a: Decimal, b: Decimal): boolean {
        return a.lessThan(b);
    }

    /**
     * La comparaison pour déterminer si les deux nombres donnés sont égaux
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns - la réponse
     */
    isEqualThan(a: Decimal, b: Decimal): boolean {
        return a.equals(b);
    }

    /**
     * La comparaison si un nombre est supérieur ou égal à l'autre
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns - la réponse
     */
    isGreaterOrEqualThan(a: Decimal, b: Decimal): boolean {
        return a.greaterThanOrEqualTo(b);
    }

    /**
     * La comparaison si un nombre est inférieur ou égal à un autre
     * @param {Decimal} a - le premier nombre
     * @param {Decimal} b - le deuxième nombre
     * @returns - la réponse
     */
    isLessOrEqualThan(a: Decimal, b: Decimal): boolean {
        return a.lessThanOrEqualTo(b);
    }

    /**
     * Le test si un nombre donné, est un nombre décimal
     * @param {number} value - le nombre à tester
     * @returns {boolean} - la réponse
     */
    isDecimal(value: number): boolean {
        try {
            const d = new Decimal(value);
            const entier: number = +(d.toString().split('.')[0]);
            // Vérifie si le nombre a une partie décimale non nulle
            return !d.equals(entier);
        } catch (e) {
            return false;
        }
    }

    /**
     * La conversion d'un nombre en décimal
     * @param {number} value - le nombre à convertir
     * @param {number} precision - le nombre de chiffres après la virgule
     * @returns {Decimal} - le nombre décimal converti
     */
    convertToDecimal(value: number, precision: number = 2): Decimal {
        return new Decimal(value).toDecimalPlaces(precision);
    }

    /**
     * L'arrondissement d'un nombre décimal
     * @param {number | string} value - le nombre à arrondir
     * @param {number} precision - le nombre de chiffres après la virgule
     * @returns {Decimal} - le nombre décimal arrondi
     */
    roundDecimal(value: number | string, precision: number = 2): Decimal {
      return new Decimal(value).toDecimalPlaces(precision);
    }

    /**
     * L'arrondissement avancé d'un nombre donné
     * @param {number | Decimal} value - le nombre à arrondir
     * @param {number} precision - le nombre de chiffres après la virgule
     * @param {number} codeRounding - pour la détermination de rounding à appliquer
     * @returns {Decimal} - le nombre arrondi
     */
    advacedRoundDecimal(value: number | Decimal, precision: number = 2, codeRounding: number = 0): Decimal{
        let rounding: any;
        if(codeRounding === 0)
            rounding = Decimal.ROUND_UP;          // Vers +∞
        else if(codeRounding === 1)
            rounding = Decimal.ROUND_DOWN;        // Vers -∞
        else if(codeRounding === 2)
            rounding = Decimal.ROUND_CEIL;        // Vers +∞
        else if(codeRounding === 3)
            rounding = Decimal.ROUND_FLOOR;       // Vers -∞
        else if(codeRounding === 4)
            rounding = Decimal.ROUND_HALF_UP;     // Arrondi mathématique (0.5 vers le haut)
        else if(codeRounding === 5)
            rounding = Decimal.ROUND_HALF_DOWN;   // 0.5 vers le bas
        else if(codeRounding === 6)
            rounding = Decimal.ROUND_HALF_EVEN;   // Arrondi bancaire
        else if(codeRounding === 7)
            rounding = Decimal.ROUND_HALF_CEIL;
        else if(codeRounding === 8)
            rounding = Decimal.ROUND_HALF_FLOOR;
        return new Decimal(value).toDecimalPlaces(precision, rounding);
    }

}