
/*!
 * socket.io-node
 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

/**
 * Packet types.
 */

var packets = exports.packets = {
      'disconnect': 0
    , 'connect': 1
    , 'heartbeat': 2
    , 'message': 3
    , 'json': 4
    , 'event': 5
    , 'ack': 6
    , 'error': 7
    , 'noop': 8
  }
  , packetslist = Object.keys(packets);

/**
 * Errors reasons.
 */

var reasons = exports.reasons = {
      'transport not supported': 0
    , 'client not handshaken': 1
    , 'unauthorized': 2
  }
  , reasonslist = Object.keys(reasons);

/**
 * Errors advice.
 */

var advice = exports.advice = {
      'reconnect': 0
  }
  , advicelist = Object.keys(advice);

/**
 * Encodes a packet.
 *
 * @api private
 */

exports.encodePacket = function (packet) {
  var type = packets[packet.type]
    , id = packet.id || ''
    , endpoint = packet.endpoint || ''
    , ack = packet.ack
    , data = null;

  switch (packet.type) {
    case 'message':
      if (packet.data !== '')
        data = packet.data;
      break;

    case 'event':
      var ev = { name: packet.name };

      if (packet.args && packet.args.length) {
        ev.args = packet.args;
      }

      data = JSON.stringify(ev);
      break;

    case 'json':
      data = JSON.stringify(packet.data);
      break;

    case 'ack':
      data = packet.ackId
        + (packet.args && packet.args.length
            ? '+' + JSON.stringify(packet.args) : '');
      break;

    case 'connect':
      if (packet.qs)
        data = packet.qs;
      break;

     case 'error':
      var reason = packet.reason ? reasons[packet.reason] : ''
        , adv = packet.advice ? advice[packet.advice] : ''

      if (reason !== '' || adv !== '')
        data = reason + (adv !== '' ? ('+' + adv) : '')

      break;
  }

  // construct packet with required fragments
  var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;

  // data fragment is optional
  if (data !== null && data !== undefined)
    encoded += ':' + data;

  return encoded;
};

/**
 * Encodes multiple messages (payload).
 *
 * @param {Array} messages
 * @api private
 */

exports.encodePayload = function (packets) {
  var decoded = '';

  if (packets.length == 1)
    return packets[0];

  for (var i = 0, l = packets.length; i < l; i++) {
    var packet = packets[i];
    decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
  }

  return decoded;
};

/**
 * Decodes a packet
 *
 * @api private
 */

var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;

/**
 * Wrap the JSON.parse in a seperate function the crankshaft optimizer will
 * only punish this function for the usage for try catch
 *
 * @api private
 */

function parse (data) {
  try { return JSON.parse(data) }
  catch (e) { return false }
}

exports.decodePacket = function (data) {
  var pieces = data.match(regexp);

  if (!pieces) return {};

  var id = pieces[2] || ''
    , data = pieces[5] || ''
    , packet = {
          type: packetslist[pieces[1]]
        , endpoint: pieces[4] || ''
      };

  // whether we need to acknowledge the packet
  if (id) {
    packet.id = id;
    if (pieces[3])
      packet.ack = 'data';
    else
      packet.ack = true;
  }

  // handle different packet types
  switch (packet.type) {
    case 'message':
      packet.data = data || '';
      break;

    case 'event':
      pieces = parse(data);
      if (pieces) {
        packet.name = pieces.name;
        packet.args = pieces.args;
      }

      packet.args = packet.args || [];
      break;

    case 'json':
      packet.data = parse(data);
      break;

    case 'connect':
      packet.qs = data || '';
      break;

    case 'ack':
      pieces = data.match(/^([0-9]+)(\+)?(.*)/);
      if (pieces) {
        packet.ackId = pieces[1];
        packet.args = [];

        if (pieces[3]) {
          packet.args = parse(pieces[3]) || [];
        }
      }
      break;

    case 'error':
      pieces = data.split('+');
      packet.reason = reasonslist[pieces[0]] || '';
      packet.advice = advicelist[pieces[1]] || '';
  }

  return packet;
};

/**
 * Decodes data payload. Detects multiple messages
 *
 * @return {Array} messages
 * @api public
 */

exports.decodePayload = function (data) {
  if (undefined == data || null == data) {
    return [];
  }

  if (data[0] == '\ufffd') {
    var ret = [];

    for (var i = 1, length = ''; i < data.length; i++) {
      if (data[i] == '\ufffd') {
        ret.push(exports.decodePacket(data.substr(i + 1).substr(0, length)));
        i += Number(length) + 1;
        length = '';
      } else {
        length += data[i];
      }
    }

    return ret;
  } else {
    return [exports.decodePacket(data)];
  }
};
