  'use strict';

  const assert = require('chai').assert
  const H = require('./harness');

  function genericTests(collFn) {
    describe('#list', () => {
      const testKeyLst = H.genTestKey();
      var listObj = null;

      it('should successfully get a list reference', async () => {
        listObj = collFn().list(testKeyLst);
      });

      it('should successfully append to a list', async () => {
        await listObj.push('test2');
      });

      it('should successfully prepend to a list', async () => {
        await listObj.unshift('test1');
      });

      it('should successfully fetch the whole list', async () => {
        var items = await listObj.getAll();

        assert.lengthOf(items, 2);
        assert.deepEqual(items[0], 'test1');
        assert.deepEqual(items[1], 'test2');
      });

      it('should successfully fetch a particular index', async () => {
        var item = await listObj.getAt(1);
        assert.deepEqual(item, 'test2');
      });

      it('should error accessing an invalid index', async () => {
        await H.throwsHelper(async () => {
          await listObj.getAt(4);
        }, H.CouchbaseError);
      });

      H.requireForAwaitOf(() => {
        it('should successfully iterate a list', async () => {
          var iteratedItems = [];
          // We run this with eval to ensure that the use of await which is a
          // reserved word in some versions does not trigger a script parse error.
          await eval(`
            (async function() {
              for await (var item of listObj) {
                iteratedItems.push(item);
              }
            })()
          `);

          assert.lengthOf(iteratedItems, 2);
          assert.deepEqual(iteratedItems[0], 'test1');
          assert.deepEqual(iteratedItems[1], 'test2');
        });
      })

      it('should successfully iterate a list with forEach', async () => {
        var iteratedItems = [];
        await listObj.forEach((item) => {
          iteratedItems.push(item);
        });

        assert.lengthOf(iteratedItems, 2);
        assert.deepEqual(iteratedItems[0], 'test1');
        assert.deepEqual(iteratedItems[1], 'test2');
      });

      it('should successfully find the index of an item', async () => {
        var test2Idx = await listObj.indexOf('test2');
        assert.equal(test2Idx, 1);
      });

      it('should successfully return -1 for an invalid item', async () => {
        var missingIdx = await listObj.indexOf('missing-item');
        assert.equal(missingIdx, -1);
      });

      it('should successfully get the size', async () => {
        var listSize = await listObj.size();
        assert.equal(listSize, 2);
      });

      it('should successfully remove by index', async () => {
        await listObj.removeAt(1);

        var remainingItems = await listObj.getAll();
        assert.lengthOf(remainingItems, 1);
        assert.deepEqual(remainingItems[0], 'test1');
      });

      it('should fail to remove an invalid index', async () => {
        await H.throwsHelper(async () => {
          await listObj.removeAt(4);
        }, H.CouchbaseError);
      });
    });

    describe('#map', () => {
      const testKeyMap = H.genTestKey();
      var mapObj = null;

      it('should successfully get a map reference', async () => {
        mapObj = collFn().map(testKeyMap);
      });

      it('should successfully add an item', async () => {
        await mapObj.set('foo', 'test2');
      });

      it('should successfully set a second item', async () => {
        await mapObj.set('bar', 'test4');
      });

      it('should successfully fetch the whole map', async () => {
        var items = await mapObj.getAll();

        assert.deepEqual(items, {
          foo: 'test2',
          bar: 'test4'
        });
      });

      it('should successfully fetch a particular item', async () => {
        var item = await mapObj.get('bar');
        assert.deepEqual(item, 'test4');
      });

      it('should error fetching an invalid item', async () => {
        await H.throwsHelper(async () => {
          await mapObj.get('invalid-item');
        }, H.CouchbaseError);
      });

      it('should successfully check the existance of a particular item', async () => {
        var result = await mapObj.exists('bar');
        assert.equal(result, true);
      });

      it('should return false when checking existance of an invalid item', async () => {
        var result = await mapObj.exists('invalid-item');
        assert.equal(result, false);
      });

      H.requireForAwaitOf(() => {
        it('should successfully iterate', async () => {
          var iteratedItems = {};
          var iteratedCount = 0;
          // We run this with eval to ensure that the use of await which is a
          // reserved word in some versions does not trigger a script parse error.
          await eval(`
            (async function() {
              for await (var [v, k] of mapObj) {
                iteratedItems[k] = v;
                iteratedCount++;
              }
            })()
          `);

          assert.equal(iteratedCount, 2);
          assert.deepEqual(iteratedItems, {
            foo: 'test2',
            bar: 'test4'
          });
        });
      });

      it('should successfully iterate with forEach', async () => {
        var iteratedItems = {};
        var iteratedCount = 0;
        await mapObj.forEach((v, k) => {
          iteratedItems[k] = v;
          iteratedCount++;
        });

        assert.equal(iteratedCount, 2);
        assert.deepEqual(iteratedItems, {
          foo: 'test2',
          bar: 'test4'
        });
      });

      it('should successfully get the size', async () => {
        var mapSize = await mapObj.size();
        assert.equal(mapSize, 2);
      });

      it('should successfully remove an item', async () => {
        await mapObj.remove('bar');

        var stillExists = await mapObj.exists('bar');
        assert.equal(stillExists, false);
      });

      it('should fail to remove an invalid item', async () => {
        await H.throwsHelper(async () => {
          await mapObj.remove('invalid-item');
        }, H.CouchbaseError);
      });
    });

    describe('#queue', () => {
      const testKeyQue = H.genTestKey();
      var queueObj = null;

      it('should successfully get a queue reference', async () => {
        queueObj = collFn().queue(testKeyQue);
      });

      it('should successfully push', async () => {
        await queueObj.push('test2');
      });

      it('should successfully get the size', async () => {
        var queueSize = await queueObj.size();
        assert.equal(queueSize, 1);
      });

      it('should successfully pop', async () => {
        var res = await queueObj.pop();
        assert.deepEqual(res, 'test2');
      });

      it('should successfully push-pop in order', async () => {
        await queueObj.push('test1');
        await queueObj.push('test2');
        await queueObj.push('test3');

        var res1 = await queueObj.pop();
        var res2 = await queueObj.pop();
        var res3 = await queueObj.pop();

        assert.deepEqual(res1, 'test1');
        assert.deepEqual(res2, 'test2');
        assert.deepEqual(res3, 'test3');
      });

      it('should error poping with no items', async () => {
        await H.throwsHelper(async () => {
          await queueObj.pop();
        }, H.lib.CouchbaseError);
      });

      it('should error popping from an empty array', async () => {
        await collFn().upsert(testKeyQue, []);
        await H.throwsHelper(async () => {
          await queueObj.pop();
        }, H.lib.CouchbaseError);
      });
    });

    describe('#sets', () => {
      const testKeySet = H.genTestKey();
      var setObj = null;

      it('should successfully get a set reference', async () => {
        setObj = collFn().set(testKeySet);
      });

      it('should successfully add an item', async () => {
        var res = await setObj.add('test1');
        assert.equal(res, true);
      });

      it('should successfully add a second item', async () => {
        var res = await setObj.add('test2');
        assert.equal(res, true);
      });

      it('should semi-successfully add another matching item', async () => {
        var res = await setObj.add('test2');
        assert.equal(res, false);
      });

      it('should successfully get the size', async () => {
        var setSize = await setObj.size();
        assert.equal(setSize, 2);
      });

      it('should successfully fetch all the values', async () => {
        var values = await setObj.values();

        assert.lengthOf(values, 2);
        assert.oneOf('test1', values);
        assert.oneOf('test2', values);
      });

      it('should successfully check if an item exists', async () => {
        var res = await setObj.contains('test1');
        assert.equal(res, true);
      });

      it('should successfully check if an invalid item exists', async () => {
        var res = await setObj.contains('invalid-item');
        assert.equal(res, false);
      });

      it('should successfully remove an item', async () => {
        await setObj.remove('test2');
      });

      it('should error removing an invalid item', async () => {
        await H.throwsHelper(async () => {
          await setObj.remove('invalid-item');
        }, H.lib.CouchbaseError);
      });
    });
  }

  describe('#datastructures', () => {
    genericTests(() => H.dco);
  });

  describe('#collections-datastructures', () => {
    H.requireFeature(H.Features.Collections, () => {
      genericTests(() => H.co);
    });
  });
