'use strict';

var assert = require('assert');
var harness = require('./harness.js');

var Vq = harness.lib.ViewQuery;

describe('#Querying', function() {
  function allTests(H) {
    describe('#View Queries', function () {
      it('should set ddoc/name properly', function () {
        var q = Vq.from('d', 'v');
        assert.equal(q.ddoc, 'd');
        assert.equal(q.name, 'v');
      });
      it('should set stale to ok properly', function () {
        var q = Vq.from('d', 'v').stale(Vq.Update.NONE);
        assert.equal(q.options.stale, 'ok');
      });
      it('should set stale to false properly', function () {
        var q = Vq.from('d', 'v').stale(Vq.Update.BEFORE);
        assert.equal(q.options.stale, 'false');
      });
      it('should set stale to update_after properly', function () {
        var q = Vq.from('d', 'v').stale(Vq.Update.AFTER);
        assert.equal(q.options.stale, 'update_after');
      });
      it('should fail with incorrect stale parameter', function () {
        assert.throws(function () {
          Vq.from('d', 'v').stale('foo');
        }, TypeError);
      });
      it('should set skip properly', function () {
        var q = Vq.from('d', 'v').skip(11);
        assert.equal(q.options.skip, 11);
      });
      it('should set limit properly', function () {
        var q = Vq.from('d', 'v').limit(11);
        assert.equal(q.options.limit, 11);
      });
      it('should work with ascending order', function () {
        var q = Vq.from('d', 'v').order(Vq.Order.DESCENDING);
        assert.equal(q.options.descending, true);
      });
      it('should work with ascending order', function () {
        var q = Vq.from('d', 'v').order(Vq.Order.ASCENDING);
        assert.equal(q.options.descending, false);
      });
      it('should fail with incorrect order parameter', function () {
        assert.throws(function () {
          Vq.from('d', 'v').order('foo');
        }, TypeError);
      });
      it('should work with reduce as true', function () {
        var q = Vq.from('d', 'v').reduce(true);
        assert.equal(q.options.reduce, true);
      });
      it('should work with reduce as false', function () {
        var q = Vq.from('d', 'v').reduce(false);
        assert.equal(q.options.reduce, false);
      });
      it('should work with grouping off', function () {
        var q = Vq.from('d', 'v').group(-1);
        assert.equal(q.options.group, true);
        assert.equal(q.options.group_level, 0);
      });
      it('should work with key', function () {
        var q = Vq.from('d', 'v').key('test1');
        assert.equal(q.options.key, '"test1"');
      });
      it('should work with keys', function () {
        var q = Vq.from('d', 'v').keys(['test1', 'test2']);
        assert.deepEqual(q.postoptions.keys, ['test1','test2']);
      });
      it('should work with grouping set', function () {
        var q = Vq.from('d', 'v').group(3);
        assert.equal(q.options.group, false);
        assert.equal(q.options.group_level, 3);
      });
      it('should work with range begin', function () {
        var q = Vq.from('d', 'v').range('test1');
        assert.equal(q.options.startkey, '"test1"');
        assert.equal(q.options.endkey, undefined);
        assert.equal(q.options.inclusive_end, undefined);
      });
      it('should work with range end', function () {
        var q = Vq.from('d', 'v').range(undefined, 'test1');
        assert.equal(q.options.startkey, undefined);
        assert.equal(q.options.endkey, '"test1"');
        assert.equal(q.options.inclusive_end, undefined);
      });
      it('should work with range between', function () {
        var q = Vq.from('d', 'v').range('test1', 'test2');
        assert.equal(q.options.startkey, '"test1"');
        assert.equal(q.options.endkey, '"test2"');
        assert.equal(q.options.inclusive_end, undefined);
      });
      it('should work with inclusive end', function () {
        var q = Vq.from('d', 'v').range('test1', 'test2', true);
        assert.equal(q.options.startkey, '"test1"');
        assert.equal(q.options.endkey, '"test2"');
        assert.equal(q.options.inclusive_end, 'true');
      });
      it('should work with id range begin', function () {
        var q = Vq.from('d', 'v').id_range('test1');
        assert.equal(q.options.startkey_docid, 'test1');
        assert.equal(q.options.endkey_docid, undefined);
      });
      it('should work with id range end', function () {
        var q = Vq.from('d', 'v').id_range(undefined, 'test1');
        assert.equal(q.options.startkey_docid, undefined);
        assert.equal(q.options.endkey_docid, 'test1');
      });
      it('should work with id range between', function () {
        var q = Vq.from('d', 'v').id_range('test1', 'test2');
        assert.equal(q.options.startkey_docid, 'test1');
        assert.equal(q.options.endkey_docid, 'test2');
      });
      it('should work with full_set (default)', function () {
        var q = Vq.from('d', 'v').full_set();
        assert.equal(q.options.full_set, 'true');
      });
      it('should work with full_set', function () {
        var q = Vq.from('d', 'v').full_set(true);
        assert.equal(q.options.full_set, 'true');
      });
      it('should work with on_error stop', function () {
        var q = Vq.from('d', 'v').on_error(Vq.ErrorMode.STOP);
        assert.equal(q.options.on_error, 'stop');
      });
      it('should work with on_error continue', function () {
        var q = Vq.from('d', 'v').on_error(Vq.ErrorMode.CONTINUE);
        assert.equal(q.options.on_error, 'continue');
      });
      it('should set custom options properly', function () {
        var q = Vq.from('d', 'v').custom({x: 4, y: 'hi'});
        assert.equal(q.options.x, 4);
        assert.equal(q.options.y, 'hi');
      });
      it('should work for spatial queries', function () {
        var q = Vq.fromSpatial('d', 'v').bbox([1, 2, 3, 4]);
        assert.equal(q.options.bbox, '1,2,3,4');
      });
    });
    describe('#View Queries (slow)', function () {
      var key = H.key();
      var ddKey = H.key();
      before(function (done) {
        var bm = H.b.manager();
        this.timeout(8000);
        bm.insertDesignDocument(ddKey, {
          views: {
            simple: {
              map: 'function(doc, meta){emit(meta.id);}'
            }
          }
        }, function (err, res) {
          assert(!err);
          assert(res);
          H.b.insert(key, 'foo', H.okCallback(function() {
            function _tryQuery() {
              H.b.query(
                  Vq.from(ddKey, 'simple').stale(Vq.Update.BEFORE).limit(1),
                  function (err) {
                    if (err) {
                      setTimeout(_tryQuery, 100);
                      return;
                    }

                    done();
                  });
            };
            _tryQuery();
          }));
        });
      });
      after(function (done) {
        var bm = H.b.manager();
        H.b.remove(key, H.okCallback(function() {
          bm.removeDesignDocument(ddKey, function(err, res) {
            assert.ifError(err);
            assert(res);
            done();
          });
        }));
      });

      it('should fail for invalid query objects', function () {
        assert.throws(function () {
          H.b.query({}, H.noCallback())
              .on('row', H.noCallback())
              .on('rows', H.noCallback())
              .on('end', H.noCallback())
              .on('error', H.noCallback());
        }, TypeError);
      });
      it('view queries should callback', function (done) {
        H.b.query(Vq.from(ddKey, 'simple').limit(1),
            function (err, res, meta) {
              assert(!err);
              assert(res);
              assert(meta);
              assert(meta.total_rows > 0);
              done();
            });
      });

      it('view queries should callback on error', function (done) {
        H.b.query(Vq.from(ddKey, 'no_exist_view').limit(1),
            function (err, res) {
              assert(err);
              assert(!res);
              done();
            });
      });

      it('view queries should emit individual rows', function (done) {
        var rowCount = 0;
        H.b.query(Vq.from(ddKey, 'simple').limit(1))
            .on('row', function (row) {
              assert(row);
              rowCount++;
            }).on('end', function () {
              assert(rowCount > 0);
              done();
            }).on('error', function () {
              assert();
            });
      });

      it('view queries should emit a rows events', function (done) {
        H.b.query(Vq.from(ddKey, 'simple').limit(1))
            .on('rows', function (rows, meta) {
              assert(rows);
              assert(meta);
              assert(meta.total_rows > 0);
              done();
            }).on('error', function () {
              assert();
            });
      });

      it('view queries should be able to emit all events', function (done) {
        var rowCount = 0;
        var rowsMeta = null;
        H.b.query(Vq.from(ddKey, 'simple').limit(1))
            .on('row', function (row) {
              assert(row);
              rowCount++;
            }).on('rows', function (rows, meta) {
              assert(rows);
              assert(meta);
              assert(meta.total_rows > 0);
              rowsMeta = meta;
            }).on('end', function (meta) {
              assert(rowCount > 0);
              assert(meta, rowsMeta);
              done();
            }).on('error', function () {
              assert();
            });
      });

      it('view queries should emit an error event with a row handler',
          function (done) {
            H.b.query(Vq.from(ddKey, 'no_exist_view').limit(1))
                .on('row', function (rows) {
                  // Do Nothing
                }).on('error', function (err) {
                  assert(err);
                  done();
                });
          });

      it('view queries should emit an error event with a rows handler',
          function (done) {
            H.b.query(Vq.from(ddKey, 'no_exist_view').limit(1))
                .on('rows', function (rows) {
                  // Do Nothing
                }).on('error', function (err) {
                  assert(err);
                  done();
                });
          });
    });
  }

  describe('#RealBucket', allTests.bind(this, harness));
  describe('#MockBucket', allTests.bind(this, harness.mock));
});
