const BigInt = require('big-integer');
const { modExp } = require('../Helpers');

class Factorizator {
    /**
     * Calculates the greatest common divisor
     * @param a {BigInteger}
     * @param b {BigInteger}
     * @returns {BigInteger}
     */
    static gcd(a, b) {
        while (b.neq(BigInt.zero)) {
            const temp = b;
            b = a.remainder(b);
            a = temp;
        }
        return a;
    }

    /**
     * Factorizes the given number and returns both the divisor and the number divided by the divisor
     * @param pq {BigInteger}
     * @returns {{p: *, q: *}}
     */
    static factorize(pq) {
        if (pq.remainder(2)
            .equals(BigInt.zero)) {
            return {
                p: BigInt(2),
                q: pq.divide(BigInt(2)),
            };
        }
        let y = BigInt.randBetween(BigInt(1), pq.minus(1));
        const c = BigInt.randBetween(BigInt(1), pq.minus(1));
        const m = BigInt.randBetween(BigInt(1), pq.minus(1));

        let g = BigInt.one;
        let r = BigInt.one;
        let q = BigInt.one;
        let x = BigInt.zero;
        let ys = BigInt.zero;
        let k;

        while (g.eq(BigInt.one)) {
            x = y;
            for (let i = 0; BigInt(i)
                .lesser(r); i++) {
                y = (modExp(y, BigInt(2), pq)).add(c)
                    .remainder(pq);
            }
            k = BigInt.zero;

            while (k.lesser(r) && g.eq(BigInt.one)) {
                ys = y;
                const condition = BigInt.min(m, r.minus(k));
                for (let i = 0; BigInt(i)
                    .lesser(condition); i++) {
                    y = (modExp(y, BigInt(2), pq)).add(c)
                        .remainder(pq);
                    q = q.multiply(x.minus(y)
                        .abs())
                        .remainder(pq);
                }
                g = Factorizator.gcd(q, pq);
                k = k.add(m);
            }

            r = r.multiply(2);
        }


        if (g.eq(pq)) {
            // eslint-disable-next-line no-constant-condition
            while (true) {
                ys = (modExp(ys, BigInt(2), pq)).add(c)
                    .remainder(pq);
                g = Factorizator.gcd(x.minus(ys)
                    .abs(), pq);

                if (g.greater(1)) {
                    break;
                }
            }
        }
        const p = g;
        q = pq.divide(g);
        return p < q ? {
            p,
            q,
        } : {
            p: q,
            q: p,
        };
    }
}

module.exports = Factorizator;
