/**
 * A class for performing mathematical calculations on a given formula.
 * 魔方魔派前端计算器
 */
class MofangMopaiCalculator {
    
    _formula: string

    /**
     * Constructs a new instance of MfCalculator.
     *
     * @param {string} formula - The mathematical formula to be calculated.
     * @throws Will throw an error if the formula is not provided or is not a string.
     */
    constructor(formula: string){
        if(!formula) throw new Error('[传参错误] - 传入公式不能为空')
        if(typeof formula !== 'string') throw new Error('[传参错误] - 传入公式必须是字符串格式')
        this._formula = formula.replace(/\s*/g, '')
    }

    /**
     * Calculates the final result of the mathematical formula.
     *
     * @returns {number} The final result of the calculation.
     */
    get(): number {
        const res = this._splitFormula(this._formula)
        return Number(res || 0)
    }

    /**
     * Splits the mathematical formula into smaller parts for individual calculation.
     * It continues to do this until there are no more operators left in the formula.
     *
     * @param {string} formula - The mathematical formula to be split.
     * @returns {string} The final result of the calculation.
     *
     * @example
     * _splitFormula('1.23+34.23+33-4-7.8*8.12+103.21') // returns '104.334'
     */
    _splitFormula(str: string): string{
        // 先乘除 后加减
        const methodA = str.match(/\*|\//)  // 判断有没有乘除法公式
        const methodB = str.match(/\+|\-/)  // 判断有没有加减法公式
        // 如果未匹配到计算公式，则是计算后的最终结果，直接返回
        if(!methodA && !methodB) return str
        // 定义计算公式正则表达式
        let reg: RegExp
        if(methodA){
            reg = this._getRegExp(methodA[0])
        } else if(methodB){
            reg = this._getRegExp(methodB[0])
        } else {
            throw new Error('[计算错误] - 未能成功获得计算公式正则表达式')
        }
        // 匹配字符串中的计算公式
        const arr: string[] | null = str.match(reg)
        const formula: string = arr?.[0] || ''
        if(!formula) throw new Error('[计算错误] - 未匹配到计算公式')
        // 对公式进行计算
        const calculationResult: number = this._startCalc(formula)
        // 把相应的计算公式替换成计算后的结果值
        const replaceFormula = str.replace(formula, calculationResult.toString())
        // 继续进行计算 直到字符串中没有计算公式为止
        return this._splitFormula(replaceFormula)
    }

    /**
     * Gets a regular expression based on the given calculation method.
     *
     * @param {string} method - The calculation method.
     * @returns {RegExp} The regular expression.
     */
    _getRegExp(method: string): RegExp{
        return new RegExp(`\\d+(\\.\\d+)?\\${method}\\d+(\\.\\d+)?`)
    }

    /**
     * Starts the calculation process for a given formula.
     *
     * @param {string} formula - The mathematical formula to be calculated.
     * @returns {number} The result of the calculation.
     *
     * @example
     * _startCalc('1.23+34.23') // returns 35.46
     * _startCalc('34.23-33') // returns 1.23
     * _startCalc('33*7.8') // returns 257.4
     * _startCalc('103.21/8.12') // returns 12.699999999999998
     */
    _startCalc(formula: string): number{
        if(!formula) throw new Error('[计算错误] - 未找到需要计算的公式')
        const reg = /\+|\-|\*|\//g
        const method = formula.match(reg)?.[0]   // 获取计算符
        if(!method) throw new Error('[计算错误] - 计算方法错误')
        const numArray = formula.split(reg)
        return this._execMath(numArray[0], numArray[1], method)
    }

    /**
     * Executes a mathematical operation on two numbers.
     *
     * @param {number | string} [a=0] - The first number.
     * @param {number | string} [b=0] - The second number.
     * @param {string} method - The mathematical operation to be performed.
     * @returns {number} The result of the mathematical operation.
     * @throws Will throw an error if the method is not one of the following: '+', '-', '*', '/'.
     *
     * @example
     * _execMath(1.23, 34.23, '+') // returns 35.46
     * _execMath(34.23, 33, '-') // returns 1.23
     * _execMath(33, 7.8, '*') // returns 257.4
     * _execMath(103.21, 8.12, '/') // returns 12.699999999999998
     */
    _execMath(a: number | string = 0, b: number | string = 0, method: string): number{
        // 获取小数点后位数
        const decimalLength = Math.max(this._getDecimalLength(a), this._getDecimalLength(b))
        const x = this._transformToInteger(a, decimalLength) // 补位后的a值
        const y = this._transformToInteger(b, decimalLength) // 补位后的b值
        const z = Number(('1').padEnd(decimalLength + 1, '0'))  // 补位数
        switch (method) {
            case '+': {
                return (x + y) / z
            }
            case '-': {
                return (x - y) / z
            }
            case '*': {
                return x * y / Math.pow(z, 2)
            }
            case '/': {
                return x / y
            }
            default: {
                return 0
            }
        }
    }

    /**
     * Gets the decimal length of a number.
     *
     * @param {number} num - The number to get the decimal length from.
     * @returns {number} The decimal length of the number.
     *
     * @example
     * _getDecimalLength(1.23) // returns 2
     * _getDecimalLength(34.23) // returns 2
     * _getDecimalLength(33) // returns 0
     */
    _getDecimalLength(num: string | number): number{
        const n = num.toString()
        const arr = n.split(/\./)
        return arr[1]?.length || 0
    }

    /**
     * Transforms a decimal number into an integer by padding the decimal part with zeros.
     * !因为直接进位计算还是会出现小数点后无穷位的问题，所以直接用字符串进行格式化
     * 
     * @param {number} num - The decimal number to be transformed.
     * @param {number} offset - The number of decimal places to pad.
     * @returns {number} The transformed integer.
     *
     * @example
     * _transformToInteger(1.23, 2) // returns 123
     * _transformToInteger(34.23, 3) // returns 34230
     * _transformToInteger(33, 1) // returns 330
     */
    _transformToInteger(num: string | number, offset: number): number{
        if(offset === 0) return Number(num)
        const n = num.toString()
        const arr = n.split(/\./)
        const dec = arr[1]?.toString() || '0'
        arr[1] = dec.padEnd(offset, '0')
        return Number(arr.join(''))
    }
}

export default MofangMopaiCalculator