
var levelgraph = require('../lib/levelgraph')
  , level = require('memdb')
  , path = require('path')
  , osenv = require('osenv');

describe('a basic unicode triple store', function() {

  var db, leveldb = leveldb;

  beforeEach(function() {
    leveldb = level();
    db = levelgraph(leveldb);
  });

  afterEach(function(done) {
    db.close(done);
  });

  it('should put a triple', function(done) {
    var triple = { subject: '车', predicate: '是', object: '交通工具' };
    db.put(triple, done);
  });

  describe('with a triple inserted', function() {

    var triple;

    beforeEach(function(done) {
      triple = { subject: '􀃿', predicate: '🜁', object: '🚃' };
      db.put(triple, done);
    });

    it('should get it specifiying the subject', function(done) {
      db.get({ subject: '􀃿' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the object', function(done) {
      db.get({ object: '🚃' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the predicate', function(done) {
      db.get({ predicate: '🜁' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the subject and the predicate', function(done) {
      db.get({ subject: '􀃿', predicate: '🜁' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the subject and the object', function(done) {
      db.get({ subject: '􀃿', object: '🚃' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the predicate and the object', function(done) {
      db.get({ predicate: '🜁', object: '🚃' }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get it specifiying the subject and falsy params', function(done) {
      db.get({ subject: '􀃿', predicate: false, object: null }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    ['subject', 'predicate', 'object'].forEach(function(type) {
      it('should get nothing if nothing matches an only ' + type + ' query',
         function(done) {

        var query  = {};
        query[type] = 'notfound';
        db.get(query, function(err, list) {
          expect(list).to.eql([]);
          done();
        });
      });
    });

    it('should return the triple through the getStream interface', function(done) {
      var stream = db.getStream({ predicate: '🜁' });
      stream.on('data', function(data) {
        expect(data).to.eql(triple);
      });
      stream.on('end', done);
    });

    it('should return the triple through the getStream interface with falsy params', function(done) {
      var stream = db.getStream({ subject: null, predicate: '🜁', object: false });
      stream.on('data', function(data) {
        expect(data).to.eql(triple);
      });
      stream.on('end', done);
    });

    it('should get the triple if limit 1 is used', function(done) {
      db.get({ limit: 1 }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get the triple if limit 0 is used', function(done) {
      db.get({ limit: 0 }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should get the triple if offset 0 is used', function(done) {
      db.get({ offset: 0 }, function(err, list) {
        expect(list).to.eql([triple]);
        done();
      });
    });

    it('should not get the triple if offset 1 is used', function(done) {
      db.get({ offset: 1 }, function(err, list) {
        expect(list).to.eql([]);
        done();
      });
    });
  });

  it('should put an array of triples', function(done) {
    var t1 = { subject: '车', predicate: '是', object: '交通工具' }
      , t2 = { subject: '车', predicate: '是', object: '动物' };
    db.put([t1, t2], done);
  });

  it('should get only triples with exact match of subjects', function(done) {
    var t1 = { subject: '飞机', predicate: '是', object: '交通工具' }
      , t2 = { subject: '车', predicate: '是', object: '动物' };
    db.put([t1, t2], function() {
      db.get({ subject: '车' }, function(err, matched) {
        expect(matched.length).to.eql(1);
        expect(matched[0]).to.eql(t2);
        done();
      });
    });
  });

  it('should put a triple with an object to false', function(done) {
    var t = { subject: '车', predicate: '是', object: false };
    db.put(t, function() {
      leveldb.get('spo::车::是::false', done);
    });
  });

  describe('with two triple inserted with the same predicate', function() {

    var triple1
      , triple2;

    beforeEach(function(done) {
      triple1 = { subject: '飞机', predicate: '是', object: '交通工具' };
      triple2 = { subject: '狗熊', predicate: '是', object: '动物' };
      db.put([triple1, triple2], done);
    });

    it('should get one by specifiying the subject', function(done) {
      db.get({ subject: '飞机' }, function(err, list) {
        expect(list).to.eql([triple1]);
        done();
      });
    });

    it('should get one by specifiying the subject and a falsy predicate', function(done) {
      db.get({ subject: '飞机', predicate: null }, function(err, list) {
        expect(list).to.eql([triple1]);
        done();
      });
    });

    it('should get two by specifiying the predicate', function(done) {
      db.get({ predicate: '是' }, function(err, list) {
        expect(list).to.eql([triple1, triple2]);
        done();
      });
    });

    it('should get two by specifiying the predicate and a falsy subject', function(done) {
      db.get({ subject: null, predicate: '是' }, function(err, list) {
        expect(list).to.eql([triple1, triple2]);
        done();
      });
    });

    it('should remove one and still return the other', function(done) {
      db.del(triple2, function() {
        db.get({ predicate: '是' }, function(err, list) {
          expect(list).to.eql([triple1]);
          done();
        });
      });
    });

    it('should return both triples through the getStream interface', function(done) {
      var triples = [triple1, triple2]
        , stream = db.getStream({ predicate: '是' });
      stream.on('data', function(data) {
        expect(data).to.eql(triples.shift());
      });

      stream.on('end', done);
    });

    it('should return only one triple with limit 1', function(done) {
      db.get({ predicate: '是', limit: 1 }, function(err, list) {
        expect(list).to.eql([triple1]);
        done();
      });
    });

    it('should return two triples with limit 2', function(done) {
      db.get({ predicate: '是', limit: 2 }, function(err, list) {
        expect(list).to.eql([triple1, triple2]);
        done();
      });
    });

    it('should return three triples with limit 3', function(done) {
      db.get({ predicate: '是', limit: 3 }, function(err, list) {
        expect(list).to.eql([triple1, triple2]);
        done();
      });
    });

    it('should support limit over streams', function(done) {
      var triples = [triple1]
        , stream = db.getStream({ predicate: '是', limit: 1 });
      stream.on('data', function(data) {
        expect(data).to.eql(triples.shift());
      });

      stream.on('end', done);
    });

    it('should return only one triple with offset 1', function(done) {
      db.get({ predicate: '是', offset: 1 }, function(err, list) {
        expect(list).to.eql([triple2]);
        done();
      });
    });

    it('should return only no triples with offset 2', function(done) {
      db.get({ predicate: '是', offset: 2 }, function(err, list) {
        expect(list).to.eql([]);
        done();
      });
    });

    it('should support offset over streams', function(done) {
      var triples = [triple2]
        , stream = db.getStream({ predicate: '是', offset: 1 });
      stream.on('data', function(data) {
        expect(data).to.eql(triples.shift());
      });

      stream.on('end', done);
    });

    it('should return the triples in reverse order with reverse true', function(done) {
      db.get({ predicate: '是', reverse: true }, function(err, list) {
        expect(list).to.eql([triple2, triple1]);
        done();
      });
    });

    it('should return the last triple with reverse true and limit 1', function(done) {
      db.get({ predicate: '是', reverse: true, limit: 1 }, function(err, list) {
        expect(list).to.eql([triple2]);
        done();
      });
    });

    it('should support reverse over streams', function(done) {
      var triples = [triple2, triple1]
        , stream = db.getStream({ predicate: '是', reverse: true });
      stream.on('data', function(data) {
        expect(data).to.eql(triples.shift());
      });

      stream.on('end', done);
    });
  });

  describe('with 10 triples inserted', function() {
    beforeEach(function (done) {
      var triples = [];
      for (var i = 0; i < 10; i++) {
        triples[i] = { subject: '测试项', predicate: '推导', object: '目标' + i };
      }
      db.put(triples, done);
    });

    if (!process.browser) {
      it('should return the approximate size', function(done) {
        db.approximateSize({ predicate: '是' }, function (err, size) {
          expect(size).to.be.a('number');
          done(err);
        });
      });
    }
  });

  it('should put triples using a stream', function(done) {
    var t1 = { subject: '车', predicate: '是', object: '交通工具' };
    var t2 = { subject: '车', predicate: '是', object: '动物' };
    var stream = db.putStream();
    stream.on('close', done);

    stream.write(t1);
    stream.end(t2);
  });

  it('should store the triples written using a stream', function(done) {
    var t1 = { subject: '车', predicate: '是', object: '交通工具' };
    var t2 = { subject: '车', predicate: '是', object: '动物' };
    var stream = db.putStream();

    stream.write(t1);
    stream.end(t2);

    stream.on('close', function() {
      var triples = [t1, t2];
      var readStream = db.getStream({ predicate: '是' });

      readStream.on('data', function(data) {
        expect(data).to.eql(triples.shift());
      });

      readStream.on('end', done);
    });
  });

  it('should del the triples using a stream', function(done) {
    var t1 = { subject: '车', predicate: '是', object: '交通工具' };
    var t2 = { subject: '车', predicate: '是', object: '动物' };
    var stream = db.putStream();

    stream.write(t1);
    stream.end(t2);

    stream.on('close', function() {

      var delStream = db.delStream();
      delStream.write(t1);
      delStream.end(t2);

      delStream.on('close', function() {
        var readStream = db.getStream({ predicate: '是' });

        var results = [];
        readStream.on('data', function(data) {
          results.push(data);
        });

        readStream.on('end', function() {
          expect(results).to.have.property('length', 0);
          done();
        });
      });
    });
  });

  it('should support filtering', function(done) {
    var triple1 = { subject: '车', predicate: '是', object: '动物' }
      , triple2 = { subject: '车', predicate: '是', object: '交通工具' };

    db.put([triple1, triple2], function() {
      function filter(triple) {
        return triple.object === '动物';
      }

      db.get({ subject: '车', predicate: '是', filter: filter }, function(err, results) {
        expect(results).to.eql([triple1]);
        done();
      });
    });
  });

  describe('API deprecations', function() {

    var warnSpy;

    beforeEach(function() {
      warnSpy = sinon.stub(console, 'warn');
    });

    afterEach(function() {
      warnSpy.restore();
    });

    it('should call searchStream when calling joinStream', function() {
      var stub = sinon.stub(db, 'searchStream');
      db.joinStream('车', '是', '交通工具');
      expect(stub).to.be.calledWith('车', '是', '交通工具');
    });

    it('should alias search to join', function() {
      var stub = sinon.stub(db, 'search');
      db.join('车', '是', '交通工具');
      expect(stub).to.be.calledWith('车', '是', '交通工具');
    });

    it('should warn when calling joinStream', function() {
      var stub = sinon.stub(db, 'searchStream');
      db.joinStream('车', '是', '交通工具');

      expect(warnSpy).to.be.calledWith('joinStream is deprecated, use searchStream instead');
    });

    it('should warn when calling join', function() {
      var stub = sinon.stub(db, 'search');
      db.join('车', '是', '交通工具');

      expect(warnSpy).to.be.calledWith('join is deprecated, use search instead');
    });
  });
});

describe('generateBatch', function () {
  var db, leveldb = leveldb;

  beforeEach(function() {
    leveldb = level();
    db = levelgraph(leveldb);
  });

  afterEach(function(done) {
    db.close(done);
  });

  it('should generate a batch from a triple', function() {
    var triple = { subject: '车', predicate: '是', object: '交通工具' };
    var ops = db.generateBatch(triple);
    expect(ops).to.have.property('length', 6);
    ops.forEach(function (op) {
      expect(op).to.have.property('type', 'put');
      expect(JSON.parse(op.value)).to.eql(triple);
    });
  });

  it('should generate a batch of type', function() {
    var triple = { subject: '车', predicate: '是', object: '交通工具' };
    var ops = db.generateBatch(triple, 'del');
    expect(ops).to.have.property('length', 6);
    ops.forEach(function (op) {
      expect(op).to.have.property('type', 'del');
      expect(JSON.parse(op.value)).to.eql(triple);
    });
  });
});
