/*
 * index.js: Top-level include for the `utile` module.
 *
 * (C) 2011, Charlie Robbins & the Contributors
 * MIT LICENSE
 *
 */

var fs = require('fs'),
    path = require('path'),
    util = require('util');

var utile = module.exports;

//
// Extend the `utile` object with all methods from the
// core node `util` methods.
//
Object.keys(util).forEach(function (key) {
  utile[key] = util[key];
});

Object.defineProperties(utile, {

  //
  // ### function async
  // Simple wrapper to `require('async')`.
  //
  'async': {
    get: function() {
      return utile.async = require('async');
    }
  },

  //
  // ### function inflect
  // Simple wrapper to `require('i')`.
  //
  'inflect': {
    get: function() {
      return utile.inflect = require('i')();
    }
  },

  //
  // ### function mkdirp
  // Simple wrapper to `require('mkdirp')`
  //
  'mkdirp': {
    get: function() {
      return utile.mkdirp = require('mkdirp');
    }
  },

  //
  // ### function deepEqual
  // Simple wrapper to `require('deep-equal')`
  // Remark: deepEqual is 4x faster then using assert.deepEqual
  //         see: https://gist.github.com/2790507
  //
  'deepEqual': {
    get: function() {
      return utile.deepEqual = require('deep-equal');
    }
  },

  //
  // ### function rimraf
  // Simple wrapper to `require('rimraf')`
  //
  'rimraf': {
    get: function() {
      return utile.rimraf = require('rimraf');
    }
  },

  //
  // ### function cpr
  // Simple wrapper to `require('ncp').ncp`
  //
  'cpr': {
    get: function() {
      return utile.cpr = require('ncp').ncp;
    }
  },

  //
  // ### @file {Object}
  // Lazy-loaded `file` module
  //
  'file': {
    get: function() {
      return utile.file = require('./file');
    }
  },

  //
  // ### @args {Object}
  // Lazy-loaded `args` module
  //
  'args': {
    get: function() {
      return utile.args = require('./args');
    }
  },

  //
  // ### @base64 {Object}
  // Lazy-loaded `base64` object
  //
  'base64': {
    get: function() {
      return utile.base64 = require('./base64');
    }
  },

  //
  // ### @format {Object}
  // Lazy-loaded `format` object
  //
  'format': {
    get: function() {
      return utile.format = require('./format');
    }
  }

});


//
// ### function rargs(_args)
// #### _args {Arguments} Original function arguments
//
// Top-level method will accept a javascript "arguments" object
// (the actual keyword "arguments" inside any scope) and return
// back an Array.
//
utile.rargs = function (_args, slice) {
  if (!slice) {
    slice = 0;
  }

  var len = (_args || []).length,
      args = new Array(len - slice),
      i;

  //
  // Convert the raw `_args` to a proper Array.
  //
  for (i = slice; i < len; i++) {
    args[i - slice] = _args[i];
  }

  return args;
};

//
// ### function each (obj, iterator)
// #### @obj {Object} Object to iterate over
// #### @iterator {function} Continuation to use on each key. `function (value, key, object)`
// Iterate over the keys of an object.
//
utile.each = function (obj, iterator) {
  Object.keys(obj).forEach(function (key) {
    iterator(obj[key], key, obj);
  });
};

//
// ### function find (o)
//
//
utile.find = function (obj, pred) {
  var value, key;

  for (key in obj) {
    value = obj[key];
    if (pred(value, key)) {
      return value;
    }
  }
};

//
// ### function pad (str, len, chr)
// ### @str {String} String to pad
// ### @len {Number} Number of chars to pad str with
// ### @chr {String} Optional replacement character, defaults to empty space
// Appends chr to str until it reaches a length of len
//
utile.pad = function pad(str, len, chr) {
  var s;
  if (!chr) {
    chr = ' ';
  }
  str = str || '';
  s = str;
  if (str.length < len) {
    for (var i = 0; i < (len - str.length); i++) {
      s += chr;
    }
  }
  return s;
}

//
// ### function path (obj, path, value)
// ### @obj {Object} Object to insert value into
// ### @path {Array} List of nested keys to insert value at
// Retreives a value from given Object, `obj`, located at the
// nested keys, `path`.
//
utile.path = function (obj, path) {
  var key, i;

  for (i in path) {
    if (typeof obj === 'undefined') {
      return undefined;
    }

    key = path[i];
    obj = obj[key];
  }

  return obj;
};

//
// ### function createPath (obj, path, value)
// ### @obj {Object} Object to insert value into
// ### @path {Array} List of nested keys to insert value at
// ### @value {*} Value to insert into the object.
// Inserts the `value` into the given Object, `obj`, creating
// any keys in `path` along the way if necessary.
//
utile.createPath = function (obj, path, value) {
  var key, i;

  for (i in path) {
    key = path[i];
    if (!obj[key]) {
      obj[key] = ((+i + 1 === path.length) ? value : {});
    }

    obj = obj[key];
  }
};

//
// ### function mixin (target [source0, source1, ...])
// Copies enumerable properties from `source0 ... sourceN`
// onto `target` and returns the resulting object.
//
utile.mixin = function (target) {
  utile.rargs(arguments, 1).forEach(function (o) {
    Object.getOwnPropertyNames(o).forEach(function(attr) {
      var getter = Object.getOwnPropertyDescriptor(o, attr).get,
          setter = Object.getOwnPropertyDescriptor(o, attr).set;

      if (!getter && !setter) {
        target[attr] = o[attr];
      }
      else {
        Object.defineProperty(target, attr, {
          get: getter,
          set: setter
        });
      }
    });
  });

  return target;
};


//
// ### function capitalize (str)
// #### @str {string} String to capitalize
// Capitalizes the specified `str`.
//
utile.capitalize = utile.inflect.camelize;

//
// ### function escapeRegExp (str)
// #### @str {string} String to be escaped
// Escape string for use in Javascript regex
//
utile.escapeRegExp = function (str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};

//
// ### function randomString (length)
// #### @length {integer} The number of bits for the random base64 string returned to contain
// randomString returns a pseude-random ASCII string (subset)
// the return value is a string of length ⌈bits/6⌉ of characters
// from the base64 alphabet.
//
utile.randomString = function (length) {
  var chars, rand, i, ret, mod, bits;

  chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
  ret = '';
  // standard 4
  mod = 4;
  // default is 16
  bits = length * mod || 64;

  // in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)
  while (bits > 0) {
    // 32-bit integer
    rand = Math.floor(Math.random() * 0x100000000);
    //we use the top bits
    for (i = 26; i > 0 && bits > 0; i -= mod, bits -= mod) {
      ret += chars[0x3F & rand >>> i];
    }
  }

  return ret;
};

//
// ### function filter (object, test)
// #### @obj {Object} Object to iterate over
// #### @pred {function} Predicate applied to each property. `function (value, key, object)`
// Returns an object with properties from `obj` which satisfy
// the predicate `pred`
//
utile.filter = function (obj, pred) {
  var copy;
  if (Array.isArray(obj)) {
    copy = [];
    utile.each(obj, function (val, key) {
      if (pred(val, key, obj)) {
        copy.push(val);
      }
    });
  }
  else {
    copy = {};
    utile.each(obj, function (val, key) {
      if (pred(val, key, obj)) {
        copy[key] = val;
      }
    });
  }
  return copy;
};

//
// ### function requireDir (directory)
// #### @directory {string} Directory to require
// Requires all files and directories from `directory`, returning an object
// with keys being filenames (without trailing `.js`) and respective values
// being return values of `require(filename)`.
//
utile.requireDir = function (directory) {
  var result = {},
      files = fs.readdirSync(directory);

  files.forEach(function (file) {
    if (file.substr(-3) === '.js') {
      file = file.substr(0, file.length - 3);
    }
    result[file] = require(path.resolve(directory, file));
  });
  return result;
};

//
// ### function requireDirLazy (directory)
// #### @directory {string} Directory to require
// Lazily requires all files and directories from `directory`, returning an
// object with keys being filenames (without trailing `.js`) and respective
// values (getters) being return values of `require(filename)`.
//
utile.requireDirLazy = function (directory) {
  var result = {},
      files = fs.readdirSync(directory);

  files.forEach(function (file) {
    if (file.substr(-3) === '.js') {
      file = file.substr(0, file.length - 3);
    }
    Object.defineProperty(result, file, {
      get: function() {
        return result[file] = require(path.resolve(directory, file));
      }
    });
  });

  return result;
};

//
// ### function clone (object, filter)
// #### @object {Object} Object to clone
// #### @filter {Function} Filter to be used
// Shallow clones the specified object.
//
utile.clone = function (object, filter) {
  return Object.keys(object).reduce(filter ? function (obj, k) {
    if (filter(k)) obj[k] = object[k];
    return obj;
  } : function (obj, k) {
    obj[k] = object[k];
    return obj;
  }, {});
};

//
// ### function camelToUnderscore (obj)
// #### @obj {Object} Object to convert keys on.
// Converts all keys of the type `keyName` to `key_name` on the
// specified `obj`.
//
utile.camelToUnderscore = function (obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (Array.isArray(obj)) {
    obj.forEach(utile.camelToUnderscore);
    return obj;
  }

  Object.keys(obj).forEach(function (key) {
    var k = utile.inflect.underscore(key);
    if (k !== key) {
      obj[k] = obj[key];
      delete obj[key];
      key = k;
    }
    utile.camelToUnderscore(obj[key]);
  });

  return obj;
};

//
// ### function underscoreToCamel (obj)
// #### @obj {Object} Object to convert keys on.
// Converts all keys of the type `key_name` to `keyName` on the
// specified `obj`.
//
utile.underscoreToCamel = function (obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (Array.isArray(obj)) {
    obj.forEach(utile.underscoreToCamel);
    return obj;
  }

  Object.keys(obj).forEach(function (key) {
    var k = utile.inflect.camelize(key, false);
    if (k !== key) {
      obj[k] = obj[key];
      delete obj[key];
      key = k;
    }
    utile.underscoreToCamel(obj[key]);
  });

  return obj;
};
