/*
 * 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 Protocol = require('../../lib/streams').Protocol;
const types = require('../../lib/types');
const utils = require('../../lib/utils');

describe('Protocol', function () {
  it('should emit a single frame with 0-length body', function (done) {
    const p = newInstance();
    const items = [];
    p.on('readable', function () {
      let item;
      while ((item = p.read())) {
        items.push(item);
      }
    });
    const buffer = generateBuffer(4, [ 0 ]);
    p.readItems(buffer);
    process.nextTick(() => {
      assert.strictEqual(items.length, 1);
      assert.strictEqual(items[0].header.bodyLength, 0);
      done();
    });
  });
  it('should emit a single frame with 0-length body chunked', function (done) {
    const p = newInstance();
    const items = [];
    p.on('readable', function () {
      let item;
      while ((item = p.read())) {
        items.push(item);
      }
    });
    const buffer = generateBuffer(4, [ 0 ]);
    p.readItems(buffer.slice(0, 2));
    p.readItems(buffer.slice(2));
    process.nextTick(() => {
      assert.strictEqual(items.length, 1);
      assert.strictEqual(items[0].header.bodyLength, 0);
      done();
    });
  });
  it('should emit multiple frames from a single chunk', function (done) {
    const p = newInstance();
    const items = [];
    p.on('readable', function () {
      let item;
      while ((item = p.read())) {
        items.push(item);
      }
    });
    const bodyLengths = [ 0, 10, 0, 20, 30, 0];
    const buffer = generateBuffer(4, bodyLengths);
    p.readItems(buffer);
    process.nextTick(() => {
      assert.strictEqual(items.length, bodyLengths.length);
      bodyLengths.forEach(function (length, index) {
        assert.strictEqual(items[index].header.bodyLength, length);
      });
      done();
    });
  });
  it('should emit multiple frames from multiples chunks', function (done) {
    const p = newInstance();
    const items = {};
    p.on('readable', function () {
      let item;
      while ((item = p.read())) {
        items[item.header.streamId] = items[item.header.streamId] || [];
        items[item.header.streamId].push(item);
      }
    });
    const bodyLengths = [ 0, 10, 0, 20, 30, 0];
    const buffer = generateBuffer(4, bodyLengths);
    p.readItems(buffer.slice(0, 9));
    p.readItems(buffer.slice(9, 31));
    p.readItems(buffer.slice(31, 33));
    p.readItems(buffer.slice(33, 45));
    p.readItems(buffer.slice(45, 65));
    p.readItems(buffer.slice(65));
    process.nextTick(() => {
      bodyLengths.forEach(function (length, index) {
        const item = items[index];
        assert.ok(item);
        assert.ok(item.length);
        const sumLength = item.reduce(function (previousValue, subItem) {
          return previousValue + subItem.chunk.length - subItem.offset;
        }, 0);
        assert.ok(sumLength >= length, sumLength + ' >= ' + length + ' failed');
      });
      done();
    });
  });
  it('should emit multiple frames from multiples small chunks', function (done) {
    const p = newInstance();
    const items = {};
    p.on('readable', function () {
      let item;
      while ((item = p.read())) {
        items[item.header.streamId] = items[item.header.streamId] || [];
        items[item.header.streamId].push(item);
      }
    });
    const bodyLengths = [ 0, 10, 15, 15, 20, 12, 0, 6];
    const buffer = generateBuffer(4, bodyLengths);
    for (let i = 0; i < buffer.length; i = i + 2) {
      if (i + 2 > buffer.length) {
        p.readItems(buffer.slice(i));
        break;
      }
      p.readItems(buffer.slice(i, i + 2));
    }
    process.nextTick(() => {
      bodyLengths.forEach(function (length, index) {
        const item = items[index];
        assert.ok(item);
        assert.ok(item.length);
        const sumLength = item.reduce(function (previousValue, subItem) {
          return previousValue + subItem.chunk.length - subItem.offset;
        }, 0);
        assert.ok(sumLength >= length, sumLength + ' >= ' + length + ' failed');
      });
      done();
    });
  });
});

/**
 * @param {Number} version
 * @param {Array.<Number>} frameBodyLengths
 * @returns {Buffer}
 */
function generateBuffer(version, frameBodyLengths) {
  const buffers = frameBodyLengths.map(function (bodyLength, index) {
    const header = new types.FrameHeader(version, 0, index, 0, bodyLength);
    return Buffer.concat([ header.toBuffer(), utils.allocBuffer(bodyLength) ]);
  });
  return Buffer.concat(buffers);
}

/** @returns {Protocol} */
function newInstance() {
  return new Protocol({ objectMode: true });
}