// An experimental fake MySQL server for tricky integration tests. Expanded
// as needed.

var Net          = require('net');
var Packets      = require('../lib/protocol/packets');
var PacketWriter = require('../lib/protocol/PacketWriter');
var Parser       = require('../lib/protocol/Parser');
var Auth         = require('../lib/protocol/Auth');
var EventEmitter = require('events').EventEmitter;
var Util         = require('util');

module.exports = FakeServer;
Util.inherits(FakeServer, EventEmitter);
function FakeServer(options) {
  EventEmitter.call(this);

  this._server      = null;
  this._connections = [];
}

FakeServer.prototype.listen = function(port, cb) {
  this._server = Net.createServer(this._handleConnection.bind(this));
  this._server.listen(port, cb);
};

FakeServer.prototype._handleConnection = function(socket) {
  var connection = new FakeConnection(socket);
  this.emit('connection', connection);
  this._connections.push(connection);
};

FakeServer.prototype.destroy = function() {
  this._server.close();
  this._connections.forEach(function(connection) {
    connection.destroy();
  });
};

Util.inherits(FakeConnection, EventEmitter);
function FakeConnection(socket) {
  EventEmitter.call(this);

  this._socket = socket;
  this._parser = new Parser({onPacket: this._parsePacket.bind(this)});

  this._handshakeInitializationPacket = null;
  this._clientAuthenticationPacket    = null;
  this._oldPasswordPacket             = null;
  this._handshakeOptions              = {};

  socket.on('data', this._handleData.bind(this));
}

FakeConnection.prototype.handshake = function(options) {
  this._handshakeOptions = options || {};

  this._handshakeInitializationPacket = new Packets.HandshakeInitializationPacket({
    scrambleBuff1       : new Buffer(8),
    scrambleBuff2       : new Buffer(12),
    serverCapabilities1 : 512, // only 1 flag, PROTOCOL_41
    protocol41          : true
  });

  this._sendPacket(this._handshakeInitializationPacket);
};

FakeConnection.prototype.deny = function(message, errno) {
  this._sendPacket(new Packets.ErrorPacket({
    message: message,
    errno: errno,
  }));
};

FakeConnection.prototype._sendPacket = function(packet) {
  var writer = new PacketWriter();
  packet.write(writer);
  this._socket.write(writer.toBuffer(this._parser));
};

FakeConnection.prototype._handleData = function(buffer) {
  this._parser.write(buffer);
};

FakeConnection.prototype._parsePacket = function(header) {
  var Packet   = this._determinePacket(header);
  var packet   = new Packet();

  packet.parse(this._parser);

  switch (Packet) {
    case Packets.ClientAuthenticationPacket:
      this._clientAuthenticationPacket = packet;

      if (this._handshakeOptions.oldPassword) {
        this._sendPacket(new Packets.UseOldPasswordPacket());
      } else {
        if (this._handshakeOptions.user || this._handshakeOptions.password) {
          throw new Error('not implemented');
        }

        this._sendPacket(new Packets.OkPacket());
        this._parser.resetPacketNumber();
      }
      break;
    case Packets.OldPasswordPacket:
      this._oldPasswordPacket = packet;

      var expected = Auth.scramble323(this._handshakeInitializationPacket.scrambleBuff(), this._handshakeOptions.password);
      var got      = packet.scrambleBuff;

      var toString = function(buffer) {
        return Array.prototype.slice.call(buffer).join(',');
      };

      if (toString(expected) === toString(got)) {
        this._sendPacket(new Packets.OkPacket());
      } else {
        this._sendPacket(new Packets.ErrorPacket());
      }

      this._parser.resetPacketNumber();
      break;
    case Packets.ComQueryPacket:
      this.emit('query', packet);
      break;
    default:
      throw new Error('Unexpected packet: ' + Packet.name)
  }
};

FakeConnection.prototype._determinePacket = function() {
  if (!this._clientAuthenticationPacket) {
    return Packets.ClientAuthenticationPacket;
  } else if (this._handshakeOptions.oldPassword && !this._oldPasswordPacket) {
    return Packets.OldPasswordPacket;
  }

  var firstByte = this._parser.peak();
  switch (firstByte) {
    case 0x03: return Packets.ComQueryPacket;
    default:
      throw new Error('Unknown packet, first byte: ' + firstByte);
      break;
  }
};

FakeConnection.prototype.destroy = function() {
  this._socket.destroy();
};
