'use strict';

import Queue from './queue';
import sinon from 'sinon';
import 'should';

/**
 * @test {Queue}
 */
describe('Queue', () => {

  let queue: Queue<number>;

  beforeEach(() => {
    queue = new Queue();
  });

  /**
   * @test {Queue#fromArray}
   */
  describe('fromArray', () => {

    /**
     * @test {Queue#fromArray}
     */
    it('should create the queue from array', () => {
      queue = Queue.fromArray([1, 2, 3]);
      queue.toArray().should.deepEqual([1, 2, 3]);
      queue.length.should.equal(3);
    });

  });

  /**
   * @test {Queue#pop}
   */
  describe('pop', () => {

    /**
     * @test {Queue#pop}
     */
    it('should remove elements from the end', () => {
      queue.push(1, 2, 3, 4);
      queue.pop();
      queue.pop();
      queue.toArray().should.deepEqual([1, 2]);
    });

    /**
     * @test {Queue#pop}
     */
    it('should remove elements completely several cycles', () => {
      for (let i = 0; i < 3; ++i) {
        queue.push(1, 2, 3);
        queue.pop();
        queue.pop();
        queue.pop();
      }
      queue.push(4, 5, 6);
      queue.toArray().should.deepEqual([4, 5, 6]);
      queue.front().should.equal(4);
      queue.back().should.equal(6);
    });

    /**
     * @test {Queue#pop}
     */
    it('should return removed element', () => {
      queue.push(1);
      queue.pop().should.equal(1);
    });
    
  });

  /**
   * @test {Queue#unshift}
   */
  describe('unshift', () => {

    /**
     * @test {Queue#unshift}
     */
    it('should add elements to the front', () => {
      queue.unshift(1);
      queue.unshift(2);
      queue.unshift(3);
      queue.push(4);
      queue.toArray().should.deepEqual([3, 2, 1, 4]);
    });

  });

  /**
   * @test {Queue#shift}
   */
  describe('shift', () => {

    /**
     * @test {Queue#shift}
     */
    it('should remove elements from start', () => {
      queue.push(1, 2, 3, 4);
      queue.shift();
      queue.shift();
      queue.toArray().should.deepEqual([3, 4]);
    });

    /**
     * @test {Queue#shift}
     */
    it('should remove elements completely several cycles', () => {
      for (let i = 0; i < 3; ++i) {
        queue.push(1, 2, 3);
        queue.shift();
        queue.shift();
        queue.shift();
      }
      queue.push(4, 5, 6);
      queue.toArray().should.deepEqual([4, 5, 6]);
      queue.front().should.equal(4);
      queue.back().should.equal(6);
    });

    /**
     * @test {Queue#shift}
     */
    it('should return removed element', () => {
      queue.push(1);
      queue.shift().should.equal(1);
    });

  });

  /**
   * @test {Queue#remove}
   */
  describe('remove', () => {

    /**
     * @test {Queue#remove}
     */
    it('should remove elements when the predicate returns true', () => {
      queue.push(0, 1, 2, 3, 4, 5, 6, 7);
      queue.remove(value => [0, 1, 3, 4, 6, 7].includes(value));
      queue.toArray().should.deepEqual([2, 5]);
    });

    /**
     * @test {Queue#remove}
     */
    it('should stop iterating elements when the predicate returns null', () => {
      queue.push(0, 1, 2, 3);
      let predicate = sinon.stub().callsFake(value => value === 1 ? null : undefined);
      queue.remove(predicate);
      
      queue.toArray().should.deepEqual([0, 1, 2, 3]);
      sinon.assert.callCount(predicate, 2);
    });

  });

  /**
   * @test {Queue#removeOne}
   */
  describe('removeOne', () => {

    /**
     * @test {Queue#remove}
     */
    it('should remove first occurence of matched element in the middle', () => {
      queue.push(1, 2, 3, 1, 2, 3);
      queue.removeOne(value => value === 2);
      queue.toArray().should.deepEqual([1, 3, 1, 2, 3]);
    });

    /**
     * @test {Queue#remove}
     */
    it('should remove first occurence of matched element if this is the first element', () => {
      queue.push(1, 2, 3, 1, 2, 3);
      queue.removeOne(value => value === 1);
      queue.toArray().should.deepEqual([2, 3, 1, 2, 3]);
    });

    /**
     * @test {Queue#remove}
     */
    it('should remove first occurence of matched element if this is the last element', () => {
      queue.push(1, 2, 3, 1, 2, 3, 4);
      queue.removeOne(value => value === 4);
      queue.toArray().should.deepEqual([1, 2, 3, 1, 2, 3]);
    });

    /**
     * @test {Queue#remove}
     */
    it('should not remove anything if there are no elements matched', () => {
      queue.push(1, 2, 3, 1, 2, 3);
      queue.removeOne(value => value === 5);
      queue.toArray().should.deepEqual([1, 2, 3, 1, 2, 3]);
    });

  });

  /**
   * @test {Queue#reversed}
   */
  describe('reversed', () => {

    /**
     * @test {Queue#reversed}
     */
    it('should create reversed queue from empty queue', () => {
      let reversed = queue.reversed();
      reversed.toArray().should.deepEqual([]);
      reversed.length.should.equal(0);
    });

    /**
     * @test {Queue#reversed}
     */
    it('should create reversed queue of one element', () => {
      queue.push(1);
      let reversed = queue.reversed();
      reversed.toArray().should.deepEqual([1]);
      reversed.length.should.equal(1);
    });

    /**
     * @test {Queue#reversed}
     */
    it('should create reversed queue of several elements', () => {
      queue.push(1, 2, 3, 4);
      let reversed = queue.reversed();
      reversed.toArray().should.deepEqual([4, 3, 2, 1]);
      reversed.length.should.equal(4);
    });

  });

  /**
   * @test {Queue}
   */
  describe('iteration', () => {

    /**
     * @test {Queue}
     */
    it('should iterate the queue with for loop', () => {
      let elements = [];
      queue = new Queue([1, 5, 2, 4, 3]);
      for (let item of queue) {
        elements.push(item);
      }
      elements.should.deepEqual([1, 5, 2, 4, 3]);
    });

  });

});
