/**
 * @fileoverview Tests for BufferDecoder.
 */

goog.module('protobuf.binary.varintsTest');

const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {CHECK_CRITICAL_STATE, CHECK_STATE} = goog.require('protobuf.internal.checks');

goog.setTestOnly();

/**
 * @param {...number} bytes
 * @return {!ArrayBuffer}
 */
function createArrayBuffer(...bytes) {
  return new Uint8Array(bytes).buffer;
}

describe('setCursor does', () => {
  it('set the cursor at the position specified', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1));
    expect(bufferDecoder.cursor()).toBe(0);
    bufferDecoder.setCursor(1);
    expect(bufferDecoder.cursor()).toBe(1);
  });
});

describe('skip does', () => {
  it('advance the cursor', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1, 0x2));
    bufferDecoder.setCursor(1);
    bufferDecoder.skip(1);
    expect(bufferDecoder.cursor()).toBe(2);
  });
});

describe('Skip varint does', () => {
  it('skip a varint', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
    bufferDecoder.skipVarint();
    expect(bufferDecoder.cursor()).toBe(1);
  });

  it('fail when varint is larger than 10 bytes', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));

    if (CHECK_CRITICAL_STATE) {
      expect(() => bufferDecoder.skipVarint()).toThrow();
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      bufferDecoder.skipVarint();
      expect(bufferDecoder.cursor()).toBe(11);
    }
  });

  it('fail when varint is beyond end of underlying array', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x80));
    expect(() => bufferDecoder.skipVarint()).toThrow();
  });
});

describe('readVarint64 does', () => {
  it('read zero', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
    const {lowBits, highBits} = bufferDecoder.getVarint(0);
    expect(lowBits).toBe(0);
    expect(highBits).toBe(0);
    expect(bufferDecoder.cursor()).toBe(1);
  });

  it('read one', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
    const {lowBits, highBits} = bufferDecoder.getVarint(0);
    expect(lowBits).toBe(1);
    expect(highBits).toBe(0);
    expect(bufferDecoder.cursor()).toBe(1);
  });

  it('read max value', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
    const {lowBits, highBits} = bufferDecoder.getVarint(0);
    expect(lowBits).toBe(-1);
    expect(highBits).toBe(-1);
    expect(bufferDecoder.cursor()).toBe(10);
  });
});

describe('readUnsignedVarint32 does', () => {
  it('read zero', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
    const result = bufferDecoder.getUnsignedVarint32();
    expect(result).toBe(0);
    expect(bufferDecoder.cursor()).toBe(1);
  });

  it('read one', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
    const result = bufferDecoder.getUnsignedVarint32();
    expect(result).toBe(1);
    expect(bufferDecoder.cursor()).toBe(1);
  });

  it('read max int32', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF, 0x0F));
    const result = bufferDecoder.getUnsignedVarint32();
    expect(result).toBe(4294967295);
    expect(bufferDecoder.cursor()).toBe(5);
  });

  it('read max value', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
    const result = bufferDecoder.getUnsignedVarint32();
    expect(result).toBe(4294967295);
    expect(bufferDecoder.cursor()).toBe(10);
  });

  it('fail if data is longer than 10 bytes', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
    if (CHECK_CRITICAL_STATE) {
      expect(() => bufferDecoder.getUnsignedVarint32()).toThrow();
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      const result = bufferDecoder.getUnsignedVarint32();
      expect(result).toBe(4294967295);
      expect(bufferDecoder.cursor()).toBe(10);
    }
  });
});

describe('readUnsignedVarint32At does', () => {
  it('reads from a specific index', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x1, 0x2));
    const result = bufferDecoder.getUnsignedVarint32At(1);
    expect(result).toBe(2);
    expect(bufferDecoder.cursor()).toBe(2);
  });
});

describe('getFloat32 does', () => {
  it('read one', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0x00, 0x00, 0x80, 0x3F));
    const result = bufferDecoder.getFloat32(0);
    expect(result).toBe(1);
    expect(bufferDecoder.cursor()).toBe(4);
  });
});

describe('getFloat64 does', () => {
  it('read one', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F));
    const result = bufferDecoder.getFloat64(0);
    expect(result).toBe(1);
    expect(bufferDecoder.cursor()).toBe(8);
  });
});

describe('getInt32 does', () => {
  it('read one', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0x01, 0x00, 0x00, 0x00));
    const result = bufferDecoder.getInt32(0);
    expect(result).toBe(1);
    expect(bufferDecoder.cursor()).toBe(4);
  });

  it('read minus one', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
    const result = bufferDecoder.getInt32(0);
    expect(result).toBe(-1);
    expect(bufferDecoder.cursor()).toBe(4);
  });
});

describe('getUint32 does', () => {
  it('read one', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x00, 0x00, 0x0));
    const result = bufferDecoder.getUint32(0);
    expect(result).toBe(1);
    expect(bufferDecoder.cursor()).toBe(4);
  });

  it('read max uint32', () => {
    const bufferDecoder = BufferDecoder.fromArrayBuffer(
        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
    const result = bufferDecoder.getUint32(0);
    expect(result).toBe(4294967295);
    expect(bufferDecoder.cursor()).toBe(4);
  });
});

describe('subBufferDecoder does', () => {
  it('can create valid sub buffers', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));

    expect(bufferDecoder.subBufferDecoder(0, 0))
        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
    expect(bufferDecoder.subBufferDecoder(0, 1))
        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00)));
    expect(bufferDecoder.subBufferDecoder(1, 0))
        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
    expect(bufferDecoder.subBufferDecoder(1, 1))
        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01)));
    expect(bufferDecoder.subBufferDecoder(1, 2))
        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x02)));
  });

  it('can not create invalid', () => {
    const bufferDecoder =
        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));
    if (CHECK_STATE) {
      expect(() => bufferDecoder.subBufferDecoder(-1, 1)).toThrow();
      expect(() => bufferDecoder.subBufferDecoder(0, -4)).toThrow();
      expect(() => bufferDecoder.subBufferDecoder(0, 4)).toThrow();
    }
  });
});