var MemoryStream = require('memorystream');
var agi = require('./../lib')
var expect = require('expect.js');
var Context = require('./../lib/context');
var state = require('./../lib/state');

//helpers
var writeVars = function(stream) {
  stream.write('agi_network: yes\n');
  stream.write('agi_uniqueid: 13507138.14\n');
  stream.write('agi_arg_1: test\n');
  stream.write('\n\n');
};

var context = function(cb) {
  var stream = new MemoryStream();
  var ctx = new Context(stream);
  //TODO nasty
  ctx.send = function(msg, cb) {
    ctx.pending = cb;
    ctx.sent = ctx.sent || [];
    ctx.sent.push(msg);
  };

  ctx.once('variables', function(vars) {
    cb(ctx);
  });
  
  writeVars(stream);
};

describe('Context', function() {
  beforeEach(function (done) {
    var self = this;
    context(function (context) {
      self.context = context;
      done();
    });
  });

  describe('parsing variables', function() {
    it('works', function(done) {
      var vars = this.context.variables;
      expect(vars['agi_network']).ok();
      expect(vars['agi_network']).to.eql('yes');
      expect(vars['agi_uniqueid']).to.eql('13507138.14');
      expect(vars['agi_arg_1']).to.eql('test');
      done();

    });

    it('puts context into waiting state', function() {
      expect(this.context.state).to.eql(state.waiting);
    });
  });

  describe('sending command', function() {
    it('writes out', function() {
      this.context.send('EXEC test');
      expect(this.context.sent.length).to.eql(1);
      expect(this.context.sent.join('')).to.eql('EXEC test');
    });
  });

  describe('context.exec', function() {
    it('sends exec command', function() {
      this.context.exec('test', 'bang', 'another');
      expect(this.context.sent.join('')).to.eql('EXEC test bang another\n');
    });
  });

  describe('command flow', function() {
    describe('success', function() {
      it('emits proper repsonse', function(done) {
        var context = this.context;
        
        process.nextTick(function() {
          context.exec('test', 'bang', 'another');
          context.stream.write('200');
          context.stream.write(' result=0\n\n');
        });

        context.on('response', function(msg) {
          expect(msg.code).to.equal(200);
          expect(msg.result).to.eql('0');
          done();
        });

      });

      it('invokes callback with response', function(done) {
        var context = this.context;

        process.nextTick(function(){
          context.stream.write('200 result=0');
          context.stream.write('\n');
          context.stream.write('200 result=0');
          context.stream.write('\n');
        });

        context.exec('test', 'boom').then(function(){
          done();
        });
        
      });

      it('includes the response value', function(done) {
        var context = this.context;

        process.nextTick(function() {
          context.exec('test', 'bang', 'another');
          context.stream.write('200');
          context.stream.write(' result=0 (a value)\n\n');
        });

        context.on('response', function(msg) {
          expect(msg.code).to.equal(200);
          expect(msg.result).to.eql('0');
          expect(msg.value).to.eql('a value');
          done();
        });

      });
    });

    describe('two commands', function(done) {

      it('invokes two callbacks', function(done) {
        var context = this.context;

        process.nextTick(function() {
          context.stream.write('200 result=0\n');
        });

        context.exec('test')
          .then(function (res) {
            expect(res.result).to.eql('0');
            process.nextTick(function() {
              context.stream.write('200 result=1\n');
            });
            return context.exec('test 2');
          })
          .then(function (res) {
            expect(res.result).to.eql('1');
            done();
          });
      });
    });
  });

  describe('hangup', function() {
    it('raises hangup on context', function(done) {
      this.context.on('hangup', done);
      this.context.stream.write('HANGUP\n');
    });

    describe('in command response', function() {
      it('is passed to callback', function(done) {
        var context = this.context;
        this.context.exec('whatever', function(err, res) {
        });
        this.context.on('hangup', done);
        process.nextTick(function() {
          context.stream.write('200 result=-1\nHANGUP\n');
        })
      });
    });
  });

  describe('databaseDel', function() {
    it('sends correct command', function() {
      this.context.databaseDel('family', 'test');
      expect(this.context.sent.join('')).to.eql('DATABASE DEL family test\n');
    });
  });

  describe('databaseDelTree', function() {
    it('sends correct command', function() {
      this.context.databaseDelTree('family', 'test');
      expect(this.context.sent.join('')).to.eql('DATABASE DELTREE family test\n');
    });
  });

  describe('databaseGet', function() {
    it('sends correct command', function() {
      this.context.databaseGet('family', 'test');
      expect(this.context.sent.join('')).to.eql('DATABASE GET family test\n');
    });
  });

  describe('databasePut', function() {
    it('sends correct command', function() {
      this.context.databasePut('family', 'test', 'value');
      expect(this.context.sent.join('')).to.eql('DATABASE PUT family test value\n');
    });
  });

  describe('speechCreate', function() {
    it('sends correct command', function() {
      this.context.speechCreate('engine');
      expect(this.context.sent.join('')).to.eql('SPEECH CREATE engine\n');
    });
  });

  describe('speechDestroy', function() {
    it('sends correct command', function() {
      this.context.speechDestroy();
      expect(this.context.sent.join('')).to.eql('SPEECH DESTROY\n');
    });
  });

  describe('speechActivateGrammar', function() {
    it('sends correct command', function() {
      this.context.speechActivateGrammar('name');
      expect(this.context.sent.join('')).to.eql('SPEECH ACTIVATE GRAMMAR name\n');
    });
  });

  describe('speechDeactivateGrammar', function() {
    it('sends correct command', function() {
      this.context.speechDeactivateGrammar('name');
      expect(this.context.sent.join('')).to.eql('SPEECH DEACTIVATE GRAMMAR name\n');
    });
  });

  describe('speechLoadGrammar', function() {
    it('sends correct command', function() {
      this.context.speechLoadGrammar('name', 'path');
      expect(this.context.sent.join('')).to.eql('SPEECH LOAD GRAMMAR name path\n');
    });
  });

  describe('speechUnloadGrammar', function() {
    it('sends correct command', function() {
      this.context.speechUnloadGrammar('name');
      expect(this.context.sent.join('')).to.eql('SPEECH UNLOAD GRAMMAR name\n');
    });
  });

  describe('speechSet', function() {
    it('sends correct command', function() {
      this.context.speechSet('name', 'value');
      expect(this.context.sent.join('')).to.eql('SPEECH SET name value\n');
    });
  });

  describe('speechRecognize', function() {
    it('sends correct command', function() {
      this.context.speechRecognize('prompt', 'timeout', 'offset');
      expect(this.context.sent.join('')).to.eql('SPEECH RECOGNIZE prompt timeout offset\n');
    });
  });

  describe('setVariable', function() {
    it('sends correct command', function() {
      this.context.setVariable('test', 'test test test');
      expect(this.context.sent.join('')).to.eql('SET VARIABLE test "test test test"\n');
    });
  });

  describe('setAutoHangup', function() {
    it('sends correct command', function() {
      this.context.setAutoHangup(10);
      expect(this.context.sent.join('')).to.eql('SET AUTOHANGUP 10\n');
    });
  });

  describe('setCallerID', function() {
    it('sends correct command', function() {
      this.context.setCallerID('246');
      expect(this.context.sent.join('')).to.eql('SET CALLERID 246\n');
    });
  });

  describe('setContext', function() {
    it('sends correct command', function() {
      this.context.setContext('outbound');
      expect(this.context.sent.join('')).to.eql('SET CONTEXT outbound\n');
    });
  });

  describe('setExtension', function() {
    it('sends correct command', function() {
      this.context.setExtension('245');
      expect(this.context.sent.join('')).to.eql('SET EXTENSION 245\n');
    });
  });

  describe('setPriority', function() {
    it('sends correct command', function() {
      this.context.setPriority('2');
      expect(this.context.sent.join('')).to.eql('SET PRIORITY 2\n');
    });
  });

  describe('setMusic', function() {
    it('sends correct command', function() {
      this.context.setMusic('default');
      expect(this.context.sent.join('')).to.eql('SET MUSIC default\n');
    });
  });

  describe('channelStatus', function() {
    it('sends correct command', function() {
      this.context.channelStatus('test');
      expect(this.context.sent.join('')).to.eql('CHANNEL STATUS test\n');
    });
  });

  describe('getFullVariable', function() {
    it('sends correct command', function() {
      this.context.getFullVariable('test', 'test');
      expect(this.context.sent.join('')).to.eql('GET FULL VARIABLE test test\n');
    });
  });

  describe('getData', function() {
    it('sends correct command', function() {
      this.context.getData('test', 10, 5);
      expect(this.context.sent.join('')).to.eql('GET DATA test 10 5\n');
    });
  });

  describe('getOption', function() {
    it('sends correct command', function() {
      this.context.getOption('test', '#', 5);
      expect(this.context.sent.join('')).to.eql('GET OPTION test "#" 5\n');
    });
  });

  describe('getVariable', function() {
    it('sends correct command', function() {
      this.context.getVariable('test');
      expect(this.context.sent.join('')).to.eql('GET VARIABLE test\n');
    });    
  });

  describe('receiveChar', function() {
    it('sends correct command', function() {
      this.context.receiveChar(5);
      expect(this.context.sent.join('')).to.eql('RECEIVE CHAR 5\n');
    });
  });

  describe('receiveText', function() {
    it('sends correct command', function() {
      this.context.receiveText(5);
      expect(this.context.sent.join('')).to.eql('RECEIVE TEXT 5\n');
    });
  });

  describe('stream file', function() {
    it('sends', function () {
      this.context.streamFile('test', '1234567890#*', function() {});
      expect(this.context.sent.join('')).to.eql('STREAM FILE "test" "1234567890#*"\n');
    });    
  });

  describe('record file', function() {
    it('record', function () {
      this.context.recordFile('test', 'wav', '#', 10, 0, 1, 2, function() {});
      expect(this.context.sent.join('')).to.eql('RECORD FILE "test" wav # 10000 0 1 2\n');
    });    
  });

  describe('say number', function() {
    it('say number', function () {
      this.context.sayNumber('1234', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY NUMBER 1234 "#"\n');
    });    
  });

  describe('say alpha', function() {
    it('say alpha', function () {
      this.context.sayAlpha('1234', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY ALPHA 1234 "#"\n');
    });    
  });

  describe('say date', function() {
    it('say date', function () {
      this.context.sayDate('1234', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY DATE 1234 "#"\n');
    });    
  });

  describe('say time', function() {
    it('say time', function () {
      this.context.sayTime('1234', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY TIME 1234 "#"\n');
    });    
  });

  describe('say datetime', function() {
    it('say datetime', function () {
      this.context.sayDateTime('1234', '#', 'Y', 'DST',function() {});
      expect(this.context.sent.join('')).to.eql('SAY DATETIME 1234 "#" Y DST\n');
    });
  });

  describe('say phonetic', function() {
    it('say phonetic', function () {
      this.context.sayPhonetic('1234ABCD', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY PHONETIC 1234ABCD "#"\n');
    });    
  });

  describe('context dial', function() {
    it('context dial', function() {
      this.context.dial('123', 10, 'A', function() {});
      expect(this.context.sent.join('')).to.eql('EXEC Dial 123,10,A\n');
    });    
  });

  describe('say digits', function() {
    it('say digits', function () {
      this.context.sayDigits('1234', '#', function() {});
      expect(this.context.sent.join('')).to.eql('SAY DIGITS 1234 "#"\n');
    });    
  });

  describe('send image', function() {
    it('send image', function () {
      this.context.sendImage('1234', function() {});
      expect(this.context.sent.join('')).to.eql('SEND IMAGE 1234\n');
    });    
  });

  describe('send text', function() {
    it('send text', function () {
      this.context.sendText('1234');
      expect(this.context.sent.join('')).to.eql('SEND TEXT "1234"\n');
    });    
  });

  describe('waitForDigit', function () {
    it('sends with default timeout', function() {
      this.context.waitForDigit(5000);
      expect(this.context.sent.join('')).to.eql('WAIT FOR DIGIT 5000\n');
    });

    it('sends with specified timeout', function() {
      this.context.waitForDigit(-1, function() {});
      expect(this.context.sent.join('')).to.eql('WAIT FOR DIGIT -1\n');
    });
  });

  describe('hangup', function() {
    it('sends "HANGUP\\n"', function() {
      this.context.hangup();
      expect(this.context.sent.join('')).to.eql('HANGUP\n');
    });
  });

  describe('asyncAGIBreak', function() {
    it('sends "ASYNCAGI BREAK\\n"', function() {
      this.context.asyncAGIBreak();
      expect(this.context.sent.join('')).to.eql('ASYNCAGI BREAK\n');
    });
  });

  describe('answer', function() {
    it('sends "ANSWER\\n"', function() {
      this.context.answer();
      expect(this.context.sent.join('')).to.eql('ANSWER\n');
    });
  });

  describe('verbose', function() {
    it('sends correct command', function() {
      this.context.verbose('good', 2);
      expect(this.context.sent.join('')).to.eql('VERBOSE "good" 2\n');
    });
  });

  describe('tddMode', function() {
    it('sends correct command', function() {
      this.context.tddMode("on");
      expect(this.context.sent.join('')).to.eql('TDD MODE on\n');
    });
  });

  describe('noop', function() {
    it('sends correct command', function() {
      this.context.noop();
      expect(this.context.sent.join('')).to.eql('NOOP\n');
    });
  });

  describe('gosub', function() {
    it('sends correct command', function() {
      this.context.gosub('out','241','6','do');
      expect(this.context.sent.join('')).to.eql('GOSUB out 241 6 do\n');
    });
  });

  describe('events', function() {
    describe('error', function () {
      it('is emitted when socket emits error', function(done) {
        this.context.on('error', function(err) {
          expect(err).to.eql('test');
          done();
        });
        this.context.stream.emit('error', "test");
      });
    });

    describe('close', function() {
      it('is emitted when socket emits close', function(done) {
        this.context.on('close', function(hasError) {
          expect(hasError).ok();
          done();
        });

        this.context.stream.emit('close', true);
      });
    });
  });
});

describe('agi#createServer', function() {
  it('returns instance of net.Server', function() {
    var net = require('net');
    var server = (new agi()).start(3000);
    expect(server instanceof net.Server).ok();
  });

  it('invokes callback when a new connection is established', function(done) {
    var server = new agi(function(context) {
      expect(context instanceof Context);
      done();
    }).start(3000);

    server.emit('connection', new MemoryStream());
  });
});