/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";

const assert = require('assert');
const format = require('util').format;
const Long = require('long');
const MutableLong = require('../../lib/types/mutable-long');

describe('MutableLong', function () {
  describe('fromNumber() and #toNumber()', function () {
    it('should convert from and to a Number', function () {
      const values = [ 1, 2, -1, -999999, 256, 1024 * 1024, 1024 * 1025 + 13, Math.pow(2, 52), -1 * Math.pow(2, 52) ];
      values.forEach(function (value) {
        const ml = MutableLong.fromNumber(value);
        assert.strictEqual(ml.toNumber(), value);
      });
    });
  });
  describe('fromString()', function () {
    it('should parse from string representation for decimal numbers', function () {
      [
        [ '-1', [ 0xffff, 0xffff, 0xffff, 0xffff ]],
        [ '0', []],
        [ '255', [ 0xff ]],
        [ '4294901760', [ 0, 0xffff ]],
        [ '9223372036854775807', [ 0xffff, 0xffff, 0xffff, 0x7fff ]],
        [ '8354511557626137073', [ 0x89f1, 0x6033, 0x2fff, 0x73f1 ]],
        [ '-6989252372825142799', [ 0x89f1, 0x6033, 0x2fff, 0x9f01 ]],
        [ '-8142173877431989775', [ 0x89f1, 0x6033, 0x2fff, 0x8f01 ]],
      ].forEach(function (item) {
        const expected = new MutableLong(item[1][0], item[1][1], item[1][2], item[1][3]);
        assert.ok(MutableLong.fromString(item[0]).equals(expected));
      });
    });
  });
  describe('#multiply()', function () {
    it('should return the product for values < 2^53', function () {
      [
        // a * b
        [ 1, 0 ],
        [ 2, 2],
        [ 1, 4],
        [ 13, 163 ],
        [ 22631153906384, 199 ],
        [ -1, Math.pow(2, 43) ]
      ].forEach(function (item) {
        const expected = item[0] * item[1];
        const a = MutableLong.fromNumber(item[0]);
        const b = MutableLong.fromNumber(item[1]);
        assert.ok(a.multiply(b).equals(MutableLong.fromNumber(expected)), format('failed for value %d*%d',
          item[0], item[1]));
      });
    });
    it('should return the product for int64 values', function () {
      [
        // [a, b, c]
        [ new MutableLong(0x4f23, 0xff32, 0x4220, 0x0d32), new MutableLong(0x07),
          new MutableLong(0x29f5, 0xfa60, 0xcee6, 0x5c5f) ],
        [ new MutableLong(0x2EBF, 0x4611, 0x9867, 0xFFFF), new MutableLong(0x030),
          new MutableLong(0xC3D0, 0x2338, 0x935D, 0xFFEC)],
        [ MutableLong.fromBits(0xe084bca4, 0x28b1), MutableLong.fromBits(0xed558ccd, 0xff51afd7),
          MutableLong.fromBits(0xd7e8bf54, 0x9e9ed5ab)]
      ].forEach(function (item) {
        const a = Long.fromBits(item[0].getLowBitsUnsigned(), item[0].getHighBitsUnsigned(), false);
        const b = Long.fromBits(item[1].getLowBitsUnsigned(), item[1].getHighBitsUnsigned(), false);
        const c = Long.fromBits(item[2].getLowBitsUnsigned(), item[2].getHighBitsUnsigned(), false);
        assert.ok(a.multiply(b).equals(c));
        assert.ok(item[0].multiply(item[1]).equals(item[2]));
      });
    });
    it('should handle multiplication by the minimum integer value', function () {
      [
        // [a, b, c]
        [ new MutableLong(0, 0, 0, 0x8000), new MutableLong(0),
          new MutableLong(0) ],
        [ new MutableLong(0, 0, 0, 0x8000), new MutableLong(1),
          new MutableLong(0, 0, 0, 0x8000) ],
        [ new MutableLong(0, 0, 0, 0x8000), new MutableLong(-1),
          new MutableLong(0, 0, 0, 0x8000) ],
        [ new MutableLong(0, 0, 0, 0x8000), new MutableLong(2),
          new MutableLong(0) ],
      ].forEach(function (item) {
        const a = Long.fromBits(item[0].getLowBitsUnsigned(), item[0].getHighBitsUnsigned(), false);
        const b = Long.fromBits(item[1].getLowBitsUnsigned(), item[1].getHighBitsUnsigned(), false);
        const c = Long.fromBits(item[2].getLowBitsUnsigned(), item[2].getHighBitsUnsigned(), false);
        assert.ok(a.multiply(b).equals(c));
        assert.ok(item[0].multiply(item[1]).equals(item[2]));
      });
    });
  });
  describe('#shiftRightUnsigned()', function () {
    it('should shift across int16 blocks', function () {
      for (let i = 1; i < 64; i++) {
        const l = MutableLong.fromBits(0xffffffff, 0xffffffff).shiftRightUnsigned(i);

        let expectedHigh = 0xffffffff;
        let expectedLow = 0xffffffff;
        if (i < 32) {
          expectedLow = (expectedLow >>> i) | (expectedHigh << (32 - i));
          expectedHigh = expectedHigh >>> i;
        }
        else {
          expectedLow = expectedHigh >>> (i - 32);
          expectedHigh = 0;
        }
        expectedLow = expectedLow >>> 0;
        assert.strictEqual(l.getLowBitsUnsigned().toString(16), expectedLow.toString(16));
        assert.strictEqual(l.getHighBitsUnsigned().toString(16), expectedHigh.toString(16));
      }
    });
  });
  describe('#shiftLeft()', function () {
    it('should shift across int16 blocks', function () {
      for (let i = 1; i < 64; i++) {
        const l = MutableLong.fromBits(1, 0).shiftLeft(i);
        let expectedHigh = 0;
        let expectedLow = 0;
        if (i < 32) {
          expectedLow = 1 << i;
        }
        else {
          expectedHigh = 1 << (i - 32);
        }
        expectedLow = expectedLow >>> 0;
        expectedHigh = expectedHigh >>> 0;
        assert.strictEqual(l.getLowBitsUnsigned().toString(16), expectedLow.toString(16));
        assert.strictEqual(l.getHighBitsUnsigned().toString(16), expectedHigh.toString(16));
      }
    });
  });
  describe('#compare()', function () {
    it('should compare the values provided', function () {
      [
        [ new MutableLong(1), new MutableLong() ],
        [ new MutableLong(1, 2, 3, 4), new MutableLong(0, 1, 2, 3) ],
        [ new MutableLong(1, 2, 2, 2), new MutableLong(0, 0, 2, 2) ],
        [ new MutableLong(0xffff, 0xffff, 0xffff, 0xffff), new MutableLong(0xfffe, 0xffff, 0xffff, 0xffff) ],
        [ new MutableLong(), new MutableLong(0xfffe, 0xffff, 0xffff, 0xffff) ],
      ].forEach(function (item) {
        assert.strictEqual(item[0].compare(item[1]), 1);
        assert.strictEqual(item[1].compare(item[0]), -1);
        assert.strictEqual(item[0].compare(item[0]), 0);
        assert.strictEqual(item[1].compare(item[1]), 0);
      });
    });
    it('should compare random numbers', function () {
      const max = Math.pow(2, 52);
      const length = 100;
      let i;
      const arr1 = new Array(length);
      const arr2 = new Array(length);
      for (i = 0; i < length; i++) {
        let n = 0;
        if (i !== 0) {
          n = Math.floor(Math.random() * max);
          if (i%2 === 1) {
            n = -n;
          }
        }
        arr1[i] = n;
        arr2[i] = MutableLong.fromNumber(n);
      }
      arr1.sort(function compare(a, b) {
        return a - b;
      });
      arr2.sort(function (a, b) {
        return a.compare(b);
      });
      for (i = 0; i < length; i++) {
        const expected = MutableLong.fromNumber(arr1[i]);
        assert.ok(arr2[i].equals(expected));
      }
    });
  });
});