/* global BigInt */
/* eslint-disable new-cap, no-new, no-unused-expressions */

var benchmark = require('benchmark');
var crypto = require('crypto');
var bn = require('../');
var bignum;
try {
  bignum = require('bignum');
} catch (err) {
  console.log('Load bignum error: ' + err.message.split('\n')[0]);
}
var sjcl = require('eccjs').sjcl.bn;
var bigi = require('bigi');
var BigInteger = require('js-big-integer').BigInteger;
var SilentMattBigInteger = require('biginteger').BigInteger;
var XorShift128Plus = require('xorshift.js').XorShift128Plus;
var benchmarks = [];

var selfOnly = process.env.SELF_ONLY;
var seed = process.env.SEED || crypto.randomBytes(16).toString('hex');
console.log('Seed: ' + seed);
var prng = new XorShift128Plus(seed);

var fixtures = [];
var findex = 0;
function findexRefresh () {
  findex = 0;
}

function add (op, obj) {
  benchmarks.push({
    name: op,
    start: function start () {
      var suite = new benchmark.Suite();

      console.log('Benchmarking: ' + op);

      Object.keys(obj).forEach(function (name) {
        if (name === 'BigInt' && typeof BigInt === 'undefined') {
          return;
        }

        if (name === 'bignum' && bignum === undefined) {
          return;
        }

        if (!selfOnly || name === 'bn.js') {
          var testFn = obj[name];
          suite.add(name + '#' + op, function () {
            var fixture = fixtures[findex++];
            if (findex === fixtures.length) {
              findexRefresh();
            }

            testFn(fixture);
          }, {
            onStart: findexRefresh,
            onCycle: findexRefresh
          });
        }
      });

      suite
        .on('cycle', function (event) {
          console.log(String(event.target));
        })
        .on('complete', function () {
          console.log('------------------------');
          console.log('Fastest is ' + this.filter('fastest')[0].name);
        })
        .run();

      console.log('========================');
    }
  });
}

function start () {
  var re = process.argv[2] ? new RegExp(process.argv[2], 'i') : /./;

  benchmarks
    .filter(function (b) {
      return re.test(b.name);
    })
    .forEach(function (b) {
      b.start();
    });
}

if (/fast/i.test(process.argv[3])) {
  console.log('Running in fast mode...');
  benchmark.options.minTime = 0.3;
  benchmark.options.maxTime = 1;
  benchmark.options.minSamples = 3;
} else {
  benchmark.options.minTime = 1;
}

while (fixtures.length < 25) {
  var fixture = {};
  fixtures.push(fixture);

  var a = prng.randomBytes(32).toString('hex');
  var b = prng.randomBytes(32).toString('hex');
  var aj = prng.randomBytes(768).toString('hex');
  var bj = prng.randomBytes(768).toString('hex');

  fixture.a10base = new bn(a, 16).toString(10);
  fixture.a16base = new bn(a, 16).toString(16);

  // BN
  fixture.a1 = new bn(a, 16);
  fixture.b1 = new bn(b, 16);
  fixture.a1j = new bn(aj, 16);
  fixture.b1j = new bn(bj, 16);
  fixture.as1 = fixture.a1.mul(fixture.a1).iaddn(0x2adbeef);
  fixture.am1 = fixture.a1.toRed(bn.red('k256'));
  fixture.pow1 = fixture.am1.fromRed();

  // BigInt
  fixture.a2 = BigInt(fixture.a1.toString(10));
  fixture.b2 = BigInt(fixture.b1.toString(10));
  fixture.a2j = BigInt(fixture.a1j.toString(10));
  fixture.b2j = BigInt(fixture.b1j.toString(10));
  fixture.as2 = fixture.a2 * fixture.a2 + 0x2adbeefn;

  // bignum
  if (bignum) {
    fixture.a3 = new bignum(a, 16);
    fixture.b3 = new bignum(b, 16);
    fixture.a3j = new bignum(aj, 16);
    fixture.b3j = new bignum(bj, 16);
    fixture.as3 = fixture.a3.mul(fixture.a3).add(0x2adbeef);
  }

  // bigi
  fixture.a4 = new bigi(a, 16);
  fixture.b4 = new bigi(b, 16);
  fixture.a4j = new bigi(aj, 16);
  fixture.b4j = new bigi(bj, 16);
  fixture.as4 = fixture.a4.multiply(fixture.a4).add(bigi.valueOf(0x2adbeef));

  // sjcl
  fixture.a5 = new sjcl(a, 16);
  fixture.b5 = new sjcl(b, 16);
  fixture.a5j = new sjcl(aj, 16);
  fixture.b5j = new sjcl(bj, 16);
  // fixture.as5 = fixture.a5.mul(fixture.a5).add(0x2adbeef);
  fixture.am5 = new sjcl.prime.p256k(fixture.a5);

  // BigInteger
  fixture.a6 = new BigInteger(a, 16);
  fixture.b6 = new BigInteger(b, 16);
  fixture.a6j = new BigInteger(aj, 16);
  fixture.b6j = new BigInteger(bj, 16);
  fixture.as6 = fixture.a6.multiply(fixture.a6).add(
    new BigInteger('2adbeef', 16));

  // SilentMattBigInteger
  fixture.a7 = SilentMattBigInteger.parse(a, 16);
  fixture.b7 = SilentMattBigInteger.parse(b, 16);
  fixture.a7j = SilentMattBigInteger.parse(aj, 16);
  fixture.b7j = SilentMattBigInteger.parse(aj, 16);
  fixture.as7 = fixture.a7.multiply(fixture.a7).add(
    SilentMattBigInteger.parse('2adbeef', 16));
}

add('create-10', {
  'bn.js': function (fixture) {
    new bn(fixture.a10base, 10);
  },
  BigInt: function (fixture) {
    BigInt(fixture.a10base);
  },
  bignum: function (fixture) {
    new bignum(fixture.a10base, 10);
  },
  bigi: function (fixture) {
    new bigi(fixture.a10base, 10);
  },
  yaffle: function (fixture) {
    new BigInteger(fixture.a10base, 10);
  },
  'silentmatt-biginteger': function (fixture) {
    SilentMattBigInteger.parse(fixture.a10base, 10);
  }
});

add('create-hex', {
  'bn.js': function (fixture) {
    new bn(fixture.a16base, 16);
  },
  bignum: function (fixture) {
    new bignum(fixture.a16base, 16);
  },
  bigi: function (fixture) {
    new bigi(fixture.a16base, 16);
  },
  sjcl: function (fixture) {
    new sjcl(fixture.a16base);
  },
  yaffle: function (fixture) {
    new BigInteger(fixture.a16base, 16);
  },
  'silentmatt-biginteger': function (fixture) {
    SilentMattBigInteger.parse(fixture.a16base, 16);
  }
});

add('toString-10', {
  'bn.js': function (fixture) {
    fixture.a1.toString(10);
  },
  BigInt: function (fixture) {
    fixture.a2.toString(10);
  },
  bignum: function (fixture) {
    fixture.a3.toString(10);
  },
  bigi: function (fixture) {
    fixture.a4.toString(10);
  },
  yaffle: function (fixture) {
    fixture.a6.toString(10);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7.toString(10);
  }
});

add('toString-hex', {
  'bn.js': function (fixture) {
    fixture.a1.toString(16);
  },
  BigInt: function (fixture) {
    fixture.a2.toString(16);
  },
  bignum: function (fixture) {
    fixture.a3.toString(16);
  },
  bigi: function (fixture) {
    fixture.a4.toString(16);
  },
  sjcl: function (fixture) {
    fixture.a5.toString(16);
  },
  yaffle: function (fixture) {
    fixture.a6.toString(16);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7.toString(16);
  }
});

add('add', {
  'bn.js': function (fixture) {
    fixture.a1.add(fixture.b1);
  },
  BigInt: function (fixture) {
    fixture.a2 + fixture.b2;
  },
  bignum: function (fixture) {
    fixture.a3.add(fixture.b3);
  },
  bigi: function (fixture) {
    fixture.a4.add(fixture.b4);
  },
  sjcl: function (fixture) {
    fixture.a5.add(fixture.b5);
  },
  yaffle: function (fixture) {
    fixture.a6.add(fixture.b6);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7.add(fixture.a7);
  }
});

add('sub', {
  'bn.js': function (fixture) {
    fixture.b1.sub(fixture.a1);
  },
  BigInt: function (fixture) {
    fixture.a2 - fixture.b2;
  },
  bignum: function (fixture) {
    fixture.b3.sub(fixture.a3);
  },
  bigi: function (fixture) {
    fixture.b4.subtract(fixture.a4);
  },
  sjcl: function (fixture) {
    fixture.b5.sub(fixture.a5);
  },
  yaffle: function (fixture) {
    fixture.b6.subtract(fixture.a6);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.b7.subtract(fixture.a7);
  }
});

add('mul', {
  'bn.js': function (fixture) {
    fixture.a1.mul(fixture.b1);
  },
  'bn.js[FFT]': function (fixture) {
    fixture.a1.mulf(fixture.b1);
  },
  BigInt: function (fixture) {
    fixture.a2 * fixture.b2;
  },
  bignum: function (fixture) {
    fixture.a3.mul(fixture.b3);
  },
  bigi: function (fixture) {
    fixture.a4.multiply(fixture.b4);
  },
  sjcl: function (fixture) {
    fixture.a5.mul(fixture.b5);
  },
  yaffle: function (fixture) {
    fixture.a6.multiply(fixture.b6);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7.multiply(fixture.b7);
  }
});

add('mul-jumbo', {
  'bn.js': function (fixture) {
    fixture.a1j.mul(fixture.b1j);
  },
  'bn.js[FFT]': function (fixture) {
    fixture.a1j.mulf(fixture.b1j);
  },
  BigInt: function (fixture) {
    fixture.a2j * fixture.b2j;
  },
  bignum: function (fixture) {
    fixture.a3j.mul(fixture.b3j);
  },
  bigi: function (fixture) {
    fixture.a4j.multiply(fixture.b4j);
  },
  sjcl: function (fixture) {
    fixture.a5j.mul(fixture.b5j);
  },
  yaffle: function (fixture) {
    fixture.a6j.multiply(fixture.b6j);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7j.multiply(fixture.b7j);
  }
});

add('sqr', {
  'bn.js': function (fixture) {
    fixture.a1.mul(fixture.a1);
  },
  BigInt: function (fixture) {
    fixture.a2 * fixture.a2;
  },
  bignum: function (fixture) {
    fixture.a3.mul(fixture.a3);
  },
  bigi: function (fixture) {
    fixture.a4.square();
  },
  sjcl: function (fixture) {
    fixture.a5.mul(fixture.a5);
  },
  yaffle: function (fixture) {
    fixture.a6.multiply(fixture.a6);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.a7.multiply(fixture.a7);
  }
});

add('div', {
  'bn.js': function (fixture) {
    fixture.as1.div(fixture.a1);
  },
  BigInt: function (fixture) {
    fixture.as2 / fixture.a2;
  },
  bignum: function (fixture) {
    fixture.as3.div(fixture.a3);
  },
  bigi: function (fixture) {
    fixture.as4.divide(fixture.a4);
  },
  yaffle: function (fixture) {
    fixture.as6.divide(fixture.a6);
  },
  'silentmatt-biginteger': function (fixture) {
    fixture.as7.divide(fixture.a7);
  }
});

add('mod', {
  'bn.js': function (fixture) {
    fixture.as1.mod(fixture.a1);
  },
  BigInt: function (fixture) {
    fixture.as2 / fixture.a2;
  },
  bignum: function (fixture) {
    fixture.as3.mod(fixture.a3);
  },
  bigi: function (fixture) {
    fixture.as4.mod(fixture.a4);
  },
  yaffle: function (fixture) {
    var remainder = fixture.as6.remainder(fixture.a6);
    return remainder.compareTo(BigInteger.ZERO) < 0
      ? remainder.add(fixture.a6)
      : remainder;
  },
  'silentmatt-biginteger': function (fixture) {
    var remainder = fixture.as7.remainder(fixture.a7);
    return remainder.compare(BigInteger.ZERO) < 0
      ? remainder.add(fixture.a7)
      : remainder;
  }
});

add('mul-mod k256', {
  'bn.js': function (fixture) {
    fixture.am1.redSqr();
  },
  sjcl: function (fixture) {
    fixture.am5.square().fullReduce();
  }
});

var prime1;
if (bignum) {
  prime1 = new bignum(
    'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f',
    16);
}
// var prime4 = new bigi(
//   'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f',
//   16);
var prime5 = new sjcl(
  'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');

add('pow k256', {
  'bn.js': function (fixture) {
    fixture.am1.redPow(fixture.pow1);
  },
  bignum: function (fixture) {
    fixture.a3.powm(fixture.a3, prime1);
  }
});

add('invm k256', {
  'bn.js': function (fixture) {
    fixture.am1.redInvm();
  },
  sjcl: function (fixture) {
    fixture.am5.inverseMod(prime5);
  }
});

add('gcd', {
  'bn.js': function (fixture) {
    fixture.a1.gcd(fixture.b1);
  },
  bigi: function (fixture) {
    fixture.a4.gcd(fixture.b4);
  }
});

add('egcd', {
  'bn.js': function (fixture) {
    fixture.a1.egcd(fixture.b1);
  }
});

add('bitLength', {
  'bn.js': function () {
    fixture.a1.bitLength();
  }
});

add('toArray', {
  'bn.js': function () {
    fixture.a1j.toArray();
  }
});

start();
