/**
 * @author zp
 */
/**
 * 多平台一致精确计算库，比decimal更轻量更快
 * [Math.round、Math.min、Math.max，Math.floor、Math.ceil，这些系统方法一般情况下是可以放心使用的]
 */
const ExactMath = Object.create(null);
module.exports = ExactMath;

// 计算精度
// sin、cos、tan方法的误差小数点后16位
const ACCURACY_SIN_ERROR = 1e-16;
const ACCURACY_COS_ERROR = 1e-16;
const ACCURACY_TAN_ERROR = 1e-16;

// 角度弧度常量
const DEG = 57.29577951308232;
const RAD = 0.017453292519943295;

// 系统常量
ExactMath.PI = 3.141592653589793;
ExactMath.E = 2.718281828459045;
ExactMath.LN2 = 0.6931471805599453;
ExactMath.LN10 = 2.302585092994046;
ExactMath.LOG2E = 1.4426950408889634;
ExactMath.LOG10E = 0.4342944819032518;
ExactMath.SQRT1_2 = 0.7071067811865476;
ExactMath.SQRT2 = 1.4142135623730951;

/**
 * 链式调用
 * @example
 * const value = ExactMath.value(10).add(20.123).mul(2).sqrt().value;
 */
let chain = null;
ExactMath.value = function (value) {
    if (!chain) {
        chain = {
            value: 0,
            valueOf() { return this.value; },
            toString() { return String(this.value); }
        }
        for (const key in ExactMath) {
            if (key !== 'value' && typeof ExactMath[key] === 'function') {
                chain[key] = function (...args) {
                    this.value = ExactMath[key].call(ExactMath, this.value, ...args);
                    return this;
                }
            }
        }
    }

    chain.value = value;
    return chain;
}

/****************************************************基础****************************************************/
/**
 * 获得小数位数
 * @param {Number} num 浮点数
 * @returns {Number}
 */
ExactMath.getDecimalPlace = function (num) {
    if (num && num !== Math.floor(num)) {
        for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
            temp = num * m;
            if (temp == Math.floor(temp)) return n;
        }
        return 20;
    } else {
        return 0;
    }
}
/**
 * 保留n为小数，并四舍五入
 * @example
 * (2.335).toFixed(2)
 * ExactMath.toFixed(2.335, 2)
 * @param {Number} num 浮点数
 * @param {Number} n 整数
 * @returns {Number}
 */
ExactMath.toFixed = function (num, n = 0) {
    if (n == 0) {
        return Math.round(num);
    } else {
        const m = Math.pow(10, n);
        return Math.round(num * (m * 10) / 10) / m;
    }
}

ExactMath.abs = function (x) {
    return Math.abs(x);
}
ExactMath.round = function (x) {
    return Math.round(x);
}
ExactMath.ceil = function (x) {
    return Math.ceil(x)
}
ExactMath.floor = function (x) {
    return Math.floor(x)
}
ExactMath.min = function (...args) {
    return Math.min(...args);
}
ExactMath.max = function (...args) {
    return Math.max(...args);
}

/**
 * 小数相加
 * @param {Number} num1 浮点数
 * @param {Number} num2 浮点数
 * @returns {Number}
 */
ExactMath.add = function (...args) {
    if (args.length === 2) {
        const num1 = args[0];
        const num2 = args[1];
        const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
        return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
    } else {
        return args.reduce((a, b) => this.add(a, b))
    }
};
/**
 * 小数相减
 * @param {Number} num1 浮点数
 * @param {Number} num2 浮点数
 * @returns {Number}
 */
ExactMath.sub = function (...args) {
    if (args.length === 2) {
        const num1 = args[0];
        const num2 = args[1];
        const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));

        return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
    } else {
        return args.reduce((a, b) => this.sub(a, b))
    }
};
/**
 * 小数相乘
 * @param {Number} num1 浮点数
 * @param {Number} num2 浮点数
 * @returns {Number}
 */
ExactMath.mul = function (...args) {
    if (args.length === 2) {
        let num1 = args[0];
        let num2 = args[1];

        // 方案1：
        // 直接相乘，但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
        // const n1 = this.getDecimalPlace(num1);
        // const n2 = this.getDecimalPlace(num2);
        // const m1 = Math.pow(10, n1);
        // const m2 = Math.pow(10, n2);
        // return (n1 * m1) * (n2 * m2) / (m1 * m2);

        // 方案2：
        // 用除法实现乘法，不会存在过大中间值
        let n1 = this.getDecimalPlace(num1);
        let n2 = this.getDecimalPlace(num2);

        let m = Math.pow(10, n2);
        num2 = m / this.toFixed(num2 * m);

        m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
        m = this.toFixed(num1 * m) / this.toFixed(num2 * m);

        let n = Math.min(this.getDecimalPlace(m), n1 + n2);
        return this.toFixed(m, n);
    } else {
        return args.reduce((a, b) => this.mul(a, b))
    }
};
/**
 * 小数相除法
 * @param {Number} num1 浮点数
 * @param {Number} num2 浮点数
 * @returns {Number}
 */
ExactMath.div = function (...args) {
    if (args.length === 2) {
        const num1 = args[0];
        const num2 = args[1];

        const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
        return this.toFixed(num1 * m) / this.toFixed(num2 * m);
    } else {
        return args.reduce((a, b) => this.div(a, b))
    }
};
/**
 * 取余
 * @param {Number} num1 浮点数
 * @param {Number} num2 浮点数
 * @returns {Number}
 */
ExactMath.rem = function (...args) {
    if (args.length === 2) {
        const num1 = args[0];
        const num2 = args[1];
        const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));

        return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
    } else {
        return args.reduce((a, b) => this.rem(a, b))
    }
};

/**
 * n次方，仅支持整数次方(正负都可以)
 * @param {Number} num 浮点数
 * @param {Number} n 整数
 */
ExactMath.pow = function (num, n) {
    if (num == 0 && n == 0) {
        return 1;
    }
    if (num == 0 && n > 0) {
        return 0
    }
    if (num == 0 && n < 0) {
        return Infinity;
    }
    // num为负数，n为负小数，返回NaN
    if (num < 0 && n < 0 && Math.round(n) != n) {
        return NaN;
    }

    if (Math.round(n) != n) {
        throw new Error('n must be an integer');
    }

    let result = 1;

    if (n > 0) {
        for (let index = 0; index < n; index++) {
            result = this.mul(result, num);
        }
    } else if (n < 0) {
        for (let index = 0, len = Math.abs(n); index < len; index++) {
            result = this.div(result, num);
        }
    }

    return result;
};
/**
 * 开方运算【牛顿迭代法】
 * 
 * @param {Number} n
 * @returns 
 */
ExactMath.sqrt = function (n) {
    if (n < 0) return NaN;
    if (n === 0) return 0;
    if (n === 1) return 1;
    let last = 0;
    let res = 1;
    let c = 50;
    while (res != last && --c >= 0) {
        last = res;
        res = this.div(this.add(res, this.div(n, res)), 2)
    }
    return res;

    // float InvSqrt(float x)
    // {
    //     float xhalf = 0.5f * x;
    //     int i = * (int *) & x; // get bits for floating VALUE 
    //     i = 0x5f375a86 - (i >> 1); // gives initial guess y0
    //     x = * (float *) & i; // convert bits BACK to float
    //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
    //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
    //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
    //     return 1 / x;
    // }
};

/****************************************************随机****************************************************/
function getSeed(seed) {
    if (isNaN(seed)) {
        seed = Math.floor(Math.random() * 233280);
    } else {
        seed = Math.floor(seed % 233280);
    }
    return seed;
}

let randomSeed = getSeed();

/**
 * 设置随机种子
 */
ExactMath.setSeed = function (seed) {
    randomSeed = getSeed(seed);
};

/**
 * 随机
 */
ExactMath.random = function () {
    randomSeed = (randomSeed * 9301 + 49297) % 233280;
    return randomSeed / 233280.0;
};

/**
 * 根据随机种子随机
 * @param {number} seed
 */
ExactMath.randomBySeed = function (seed) {
    seed = getSeed(seed);
    seed = (seed * 9301 + 49297) % 233280;
    return seed / 233280.0;
};

/****************************************************角度弧度转换****************************************************/
/**
 * 弧度数转角度数
 * @param {Number} radians 浮点数
 * @returns {Numbe} 浮点数
 */
ExactMath.radiansToDegrees = function (radians) {
    return this.div(radians, RAD);
};
/**
 * 角度数转弧度数
 * @param {Number} degrees 浮点数
 * @returns {Numbe} 浮点数
 */
ExactMath.degreesToRadians = function (degrees) {
    return this.div(degrees, DEG);
};
/**
 * 将角度值转换到[0, 360)范围内
 * @param {Number} angle 浮点数
 * @returns {Number} 整数
 */
ExactMath.get0To360Angle = function (angle) {
    if (angle === 0) {
        return 0;
    } else if (angle < 0) {
        return this.add(this.rem(angle, 360), 360);
    } else {
        return this.rem(angle, 360);
    }
};
/****************************************************三角函数****************************************************/
/**
 * 查表
 */
ExactMath._sin = {};
ExactMath._cos = {};
ExactMath._tan = {};

/**
 * 3个三角函数，根据需求自行添加
 * 为了效率，应该尽量使用查表法
 * 表内查不到的，目前使用系统方法的结果并取前4位小数
 */
ExactMath.sin = function (x) {
    if (this._sin.hasOwnProperty(x)) {
        return this._sin[x];
    }

    // if (x == 0) {
    //     return 0;
    // } else if (x == 90) {
    //     return 1;
    // }

    // let n = x, sum = 0, i = 1;
    // do {
    //     i++;
    //     sum = this.add(sum, n);
    //     // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
    //     n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
    // } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
    // return sum;

    return this.toFixed(Math.sin(x), 4);
};
ExactMath.cos = function (x) {
    if (this._cos.hasOwnProperty(x)) {
        return this._cos[x];
    }

    return this.toFixed(Math.cos(x), 4);
};
ExactMath.tan = function (x) {
    if (this._tan.hasOwnProperty(x)) {
        return this._tan[x];
    }

    return this.toFixed(Math.tan(x), 4);
};