/*
 * common.js: Common utility functions for requesting against Loggly APIs
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENSE
 *
 */

var https = require('https'),
    util = require('util'),
    request = require('request'),
    loggly = require('../loggly');

var common = exports;

//
// Failure HTTP Response codes based
// off Loggly specification.
//
var failCodes = common.failCodes = {
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Forbidden',
  404: 'Not Found',
  409: 'Conflict / Duplicate',
  410: 'Gone',
  500: 'Internal Server Error',
  501: 'Not Implemented',
  503: 'Throttled'
};

//
// Success HTTP Response codes based
// off Loggly specification.
//
var successCodes = common.successCodes = {
  200: 'OK',
  201: 'Created',
  202: 'Accepted',
  203: 'Non-authoritative information',
  204: 'Deleted'
};

//
// Core method that actually sends requests to Loggly.
// This method is designed to be flexible w.r.t. arguments
// and continuation passing given the wide range of different
// requests required to fully implement the Loggly API.
//
// Continuations:
//   1. 'callback': The callback passed into every node-loggly method
//   2. 'success':  A callback that will only be called on successful requests.
//                  This is used throughout node-loggly to conditionally
//                  do post-request processing such as JSON parsing.
//
// Possible Arguments (1 & 2 are equivalent):
//   1. common.loggly('some-fully-qualified-url', auth, callback, success)
//   2. common.loggly('GET', 'some-fully-qualified-url', auth, callback, success)
//   3. common.loggly('DELETE', 'some-fully-qualified-url', auth, callback, success)
//   4. common.loggly({ method: 'POST', uri: 'some-url', body: { some: 'body'} }, callback, success)
//
common.loggly = function () {
  var args = Array.prototype.slice.call(arguments),
      success = args.pop(),
      callback = args.pop(),
      responded,
      requestBody,
      headers,
      method,
      auth,
      proxy,
      uri;

  //
  // Now that we've popped off the two callbacks
  // We can make decisions about other arguments
  //
  if (args.length === 1) {
    if (typeof args[0] === 'string') {
      //
      // If we got a string assume that it's the URI
      //
      method = 'GET';
      uri    = args[0];
    }
    else {
      method      = args[0].method || 'GET';
      uri         = args[0].uri;
      requestBody = args[0].body;
      auth        = args[0].auth;
      headers     = args[0].headers;
      proxy       = args[0].proxy;
    }
  }
  else if (args.length === 2) {
    method = 'GET';
    uri    = args[0];
    auth   = args[1];
  }
  else {
    method = args[0];
    uri    = args[1];
    auth   = args[2];
  }

  function onError(err) {
    if (!responded) {
      responded = true;
      if (callback) { callback(err) }
    }
  }

  var requestOptions = {
    uri: uri,
    method: method,
    headers: headers || {},
    proxy: proxy
  };

  if (auth) {
    requestOptions.headers.authorization = 'Basic ' + new Buffer(auth.username + ':' + auth.password).toString('base64');
  }

  if (requestBody) {
    requestOptions.body = requestBody;
  }

  try {
    request(requestOptions, function (err, res, body) {
      if (err) {
        return onError(err);
      }

      var statusCode = res.statusCode.toString();
      if (Object.keys(failCodes).indexOf(statusCode) !== -1) {
        return onError((new Error('Loggly Error (' + statusCode + '): ' + failCodes[statusCode])));
      }

      success(res, body);
    });
  }
  catch (ex) {
    onError(ex);
  }
};

//
// ### function serialize (obj, key)
// #### @obj {Object|literal} Object to serialize
// #### @key {string} **Optional** Optional key represented by obj in a larger object
// Performs simple comma-separated, `key=value` serialization for Loggly when
// logging for non-JSON values.
//
common.serialize = function (obj, key) {
  if (obj === null) {
    obj = 'null';
  }
  else if (obj === undefined) {
    obj = 'undefined';
  }
  else if (obj === false) {
    obj = 'false';
  }

  if (typeof obj !== 'object') {
    return key ? key + '=' + obj : obj;
  }

  var msg = '',
      keys = Object.keys(obj),
      length = keys.length;

  for (var i = 0; i < length; i++) {
    if (Array.isArray(obj[keys[i]])) {
      msg += keys[i] + '=[';

      for (var j = 0, l = obj[keys[i]].length; j < l; j++) {
        msg += common.serialize(obj[keys[i]][j]);
        if (j < l - 1) {
          msg += ', ';
        }
      }

      msg += ']';
    }
    else {
      msg += common.serialize(obj[keys[i]], keys[i]);
    }

    if (i < length - 1) {
      msg += ', ';
    }
  }

  return msg;
};

//
// function clone (obj)
//   Helper method for deep cloning pure JSON objects
//   i.e. JSON objects that are either literals or objects (no Arrays, etc)
//
common.clone = function (obj) {
  var clone = {};
  for (var i in obj) {
    clone[i] = obj[i] instanceof Object ? common.clone(obj[i]) : obj[i];
  }

  return clone;
};
