/* Parse - packet parsing */
var protocol = require('./protocol');
var utils = require('./parsing_utils');

var parser = module.exports;

parser.header = function(buf, packet) {
  var zero = buf.readUInt8(0);
  packet.cmd = protocol.types[zero >> protocol.CMD_SHIFT];
  packet.retain = (zero & protocol.RETAIN_MASK) !== 0;
  packet.qos = (zero >> protocol.QOS_SHIFT) & protocol.QOS_MASK;
  packet.dup = (zero & protocol.DUP_MASK) !== 0;
  return packet;
};

parser.connect = function(buf, packet, encoding) {
  parser._pos = 0;
  parser._len = buf.length;

  var protocolId // Protocol id
    , clientId // Client id
    , topic // Will topic
    , payload // Will payload
    , password // Password
    , username // Username
    , flags = {};
  
  // Parse protocol id
  protocolId = parser.parse_string(buf);
  if (protocolId === null) return new Error('Parse error - cannot parse protocol id');
  packet.protocolId = protocolId;

  // Parse protocol version number
  if(parser._pos > parser._len) return null;
  packet.protocolVersion = buf.readUInt8(parser._pos);
  parser._pos += 1;
  
  // Parse connect flags
  flags.username = (buf.readUInt8(parser._pos) & protocol.USERNAME_MASK);
  flags.password = (buf.readUInt8(parser._pos) & protocol.PASSWORD_MASK);
  flags.will = (buf.readUInt8(parser._pos) & protocol.WILL_FLAG_MASK);
  
  if(flags.will) {
    packet.will = {};
    packet.will.retain = (buf.readUInt8(parser._pos) & protocol.WILL_RETAIN_MASK) !== 0;
    packet.will.qos = (buf.readUInt8(parser._pos) & protocol.WILL_QOS_MASK) >> protocol.WILL_QOS_SHIFT;
  }
  
  packet.clean = (buf.readUInt8(parser._pos) & protocol.CLEAN_SESSION_MASK) !== 0;
  parser._pos += 1;
  
  // Parse keepalive
  packet.keepalive = this.parse_num(buf);
  if(packet.keepalive === null) return null;
  
  // Parse client ID
  clientId = this.parse_string(buf);
  if(clientId === null) return new Error('Parse error - cannot parse client id');
  packet.clientId = clientId;

  if(flags.will) {
    // Parse will topic
    topic = this.parse_string(buf);
    if(topic === null) return new Error('Parse error - cannot parse will topic');
    packet.will.topic = topic;

    // Parse will payload
    payload = this.parse_string(buf);
    if(payload === null) return new Error('Parse error - cannot parse will payload');
    packet.will.payload = payload;
  }
  
  // Parse username
  if(flags.username) {
    username = this.parse_string(buf, encoding);
    if(username === null) return new Error('Parse error - cannot parse username');
    packet.username = username;
  }
  
  // Parse password
  if(flags.password) {
    password = this.parse_string(buf, encoding);
    if(password === null) return ;
    packet.password = password;
  }
  
  return packet;
};

parser.connack = function(buf, packet) {
  parser._len = buf.length;
  parser._pos = 0;
    
  packet.returnCode = parser.parse_num(buf);
  if(packet.returnCode === null) return new Error('Parse error - cannot parse return code');
  
  return packet;
};

parser.publish = function(buf, packet, encoding) {
  parser._len = buf.length;
  parser._pos = 0;
  var topic;
  
  // Parse topic name
  topic = parser.parse_string(buf);
  if(topic === null) return new Error('Parse error - cannot parse topic');
  packet.topic = topic;

  // Parse message ID
  if (packet.qos > 0) {
    packet.messageId = parser.parse_num(buf);
    if(packet.messageId === null) return new Error('Parse error - cannot parse message id');
  }

  utils.parseEncodedPayload(parser, buf, encoding, packet);
  
  return packet; 
}

// Parse puback, pubrec, pubrel, pubcomp and suback
parser.puback =
parser.pubrec =
parser.pubrel =
parser.pubcomp =
parser.unsuback = function (buf, packet) {
  parser._len = buf.length;
  parser._pos = 0;

  packet.messageId = parser.parse_num(buf, parser._len, parser._pos);
  if (packet.messageId === null) return new Error('Parse error - cannot parse message id');

  return packet;
};

parser.subscribe = function(buf, packet) {
  parser._len = buf.length;
  parser._pos = 0;

  packet.subscriptions = [];

  // Parse message ID
  packet.messageId = parser.parse_num(buf);
  if (packet.messageId === null) return new Error('Parse error - cannot parse message id');
  
  while(parser._pos < parser._len) {
    var topic
      , qos;
    
    // Parse topic
    topic = parser.parse_string(buf);
    if(topic === null) return new Error('Parse error - cannot parse topic');

    // Parse QoS
    // TODO: possible failure location
    qos = buf[parser._pos++];
    
    // Push pair to subscriptions
    packet.subscriptions.push({topic: topic, qos: qos});
  }
  return packet;
};

parser.suback = function(buf, packet) {
  parser._len = buf.length;
  parser._pos = 0;

  packet.granted = [];
  
  // Parse message ID
  packet.messageId = parser.parse_num(buf, parser._len, parser._pos);
  if(packet.messageId === null) return new Error('Parse error - cannot parse message id');
  
  // Parse granted QoSes
  while(parser._pos < parser._len) {
    packet.granted.push(buf[parser._pos++]);
  }

  return packet;
};

parser.unsubscribe = function(buf, packet) {
  parser._len = buf.length;
  parser._pos = 0;

  packet.unsubscriptions = [];

  // Parse message ID
  packet.messageId = parser.parse_num(buf, parser._len, parser._pos);
  if(packet.messageId === null) return new Error('Parse error - cannot parse message id');
  
  while(parser._pos < parser._len) {
    var topic;
    
    // Parse topic
    topic = parser.parse_string(buf);
    if(topic === null) return new Error('Parse error - cannot parse topic');

    // Push topic to unsubscriptions
    packet.unsubscriptions.push(topic);
  }

  return packet;
};

parser.reserved = function(buf, packet) {
  return new Error("reserved is not a valid command");
};

var empties = ['pingreq', 'pingresp', 'disconnect'];

parser.pingreq =
parser.pingresp =
parser.disconnect = function (buf, packet) {
  return packet;
};

parser.parse_num = function(buf) {
  if(2 > parser._pos + parser._len) return null;

  var result = buf.readUInt16BE(parser._pos);
  parser._pos += 2;
  return result;
}

parser.parse_string = function(buf, encoding) {
  var length = parser.parse_num(buf)
    , result;

  if(length === null || length + parser._pos > parser._len) return null;

  result = utils.toString(parser, buf, length, encoding);

  parser._pos += length;

  return result;
}
