Source: core.utils.js

/*global require*/

// var _ = require('highland');
var _ = require('lodash');
_.assign(exports, _);

exports.Bimap = require('./core.bimap').Bimap;

/**
 * @module utils
 */

/**
 * @callback module:utils.array1dGenerator
 * @param {Number} i - index, starts from 0.
 * @returns {Any}
 */

/**
 * Returns a 1d js array by given dimension and generating function.
 * @function
 * @param {Number} m - length.
 * @param {module:utils.array1dGenerator|Any} fn - Generation function or constant value.
 * @returns {Array}
 */
exports.array1d = function array1d(m, fn) {
  if (typeof fn === 'function') {
    return Array.apply(null, Array(m)).map(function(x, i) { return fn(i); });
  } else {
    return Array.apply(null, Array(m)).map(function() { return fn; });
  }
};

var array1d = exports.array1d;

/**
 * @callback module:utils.array2dGenerator
 * @param {Number} i - row index, starts from 0.
 * @param {Number} j - column index, starts from 1.
 * @returns {Any}
 */

/**
 * Returns a 2d js array by given dimension and generating function.
 * @param {Number} m - number of rows.
 * @param {Number} n - number of columns.
 * @param {module:utils.array2dGenerator|Any} fn - Generation function or constant value.
 * @returns {Array}
 */
exports.array2d = function array2d(m, n, fn) {
  if (typeof fn === 'function') {
    return array1d(m, function(i) {
      return array1d(n, function(j) {
        return fn(i, j);
      });
    });
  } else {
    return array1d(m, function(i) {
      return array1d(n, fn);
    });
  }
};
var array2d = exports.array2d;

/**
 * Return a new vector that embeded in new dimension.
 * @example [1,2], 3 -> [1,2,0]
 * @example [0], 3 -> [0,0,0]
 * @param {Array} vec
 * @param {Number} dim
 * @returns {Array}
 */
exports.embed = function embed(vec, dim) {
  var i, len, out;
  if (_.isArray(vec) && typeof dim === 'number') {
    out = array1d(dim, 0.0);
    for (i = 0, len = vec.length < dim ? vec.length : dim; i < len; ++i) out[i] = vec[i];
    return out;
  }
  throw new Error('embed(vec, dim): vec must be a Javascript array.');
};

/**
 * Compare two array lexically.
 * @param {Array} a - first array.
 * @param {Array} b - second array.
 * @returns {Number}
 */
exports.byLexical = function byLexical(a, b) {
  var i = 0, l = a.length, res;
  while (i < l) {
    res = a[i] - b[i];
    if (res !== 0) return res;
    ++i;
  }
  return res;
};

/**
 * Returns a new array that roated by towards left by given offset.
 * @param {Array} arr
 * @param {Integer} offset
 * @returns {Array} - new rotated array.
 */
exports.rotateLeft = function rotateLeft(arr, offset) {
  if (typeof offset === 'undefined')
    throw new Error('rotateLeft(): no offset specified.');

  var len = arr.length;
  var out = new Array(len);
  var i = (len + (offset % len)) % len, j = 0;
  while (j < len) {
    out[j] = arr[(i+j) % len];
    ++j;
  }
  return out;
};
var rotateLeft = exports.rotateLeft;

/**
 * Returns a new array that roated by towards right by given offset.
 * @param {Array} arr
 * @param {Integer} offset
 * @returns {Array} - new rotated array.
 */
exports.rotateRight = function rotateRight(arr, offset) {
  return rotateLeft(arr, -offset);
};
var rotateRight = exports.rotateRight;

/**
 * Returns the index of the smallest value.
 * @param {Array} vec
 * @param {CompareFn|undefined} cmp
 * @returns {Index}
 */
exports.minIndex = function minIndex(vec, cmp) {
  if (typeof cmp === 'function') {
    return _.reduce(vec, function(sofar, x, i) {
      if (cmp(x, sofar.value) < 0) {
        sofar.value = x;
        sofar.index = i;
      }
      return sofar;
    }, {
      value: Infinity,
      index: -1
    }).index;
  } else {
    return _.reduce(vec, function(sofar, x, i) {
      if (x < sofar.value) {
        sofar.value = x;
        sofar.index = i;
      }
      return sofar;
    }, {
      value: Infinity,
      index: -1
    }).index;
  }
};
var minIndex = exports.minIndex;

/**
 * Check whether an object is an iterator. An iterator must implement
 * both hasNext() and next() method.
 * @param {Any} obj
 * @returns {Boolean}
 */
exports.isIterator = function isIterator(obj) {
  return obj && typeof obj.hasNext === 'function' &&
    typeof obj.next === 'function';
};
var isIterator = exports.isIterator;

exports.noopIterator = {
  hasNext: function() { return false; },
  next: function() { return null; }
};

/**
 * Construct an js array from iterator.
 * @param {Iterator} iter
 * @returns {Array}
 */
exports.listFromIterator = function listFromIterator(iter) {
  var out = [];
  while (iter.hasNext()) out.push(iter.next());
  return out;
};
var listFromIterator = exports.listFromIterator;

/**
 * Return a iterator of the list.
 * @param {Array} lst
 * @returns {Iterator}
 */
exports.iteratorFromList = function iteratorFromList(lst) {
  if (!_.isArray(lst)) {
    throw new Error('iteratorFromList(lst): lst must be an array.');
  }

  var i = 0, len = lst.length;
  return {
    hasNext: function() { return i < len; },
    next: function() { return lst[i++]; }
  };
};
var iteratorFromList = exports.iteratorFromList;

/**
 * Returns a unique identifier.
 * @returns {String}
 */
exports.uuid = function uuid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
};
var uuid = exports.uuid;

/**
 * Return a normalized connectivity list by rotating the original one
 * so that the smallest index is the first element. Useful when check
 * whether two cells are the same.
 * @function utils.normalizedCell
 * @param {ConnectivityList} conn - Connectivity list of cell.
 * @returns {ConnectivityList}
 */
exports.normalizedCell = function normalizedCell(conn) {
  var offset = minIndex(conn);
  return rotateLeft(conn, offset);
};
var normalizedCell = exports.normalizedCell;

// Type checking & contracts:
var check = require('check-types');
var assert = require('assert');

exports._env = 'dev';

function noop() {}
exports.noop = noop;

exports.defineContract = function defineContract(c, whatsWrong) {
  if (exports._env === 'dev') {
    return function() {
      try {
        c.apply(null, arguments);
      } catch(err) {
        if (typeof whatsWrong === 'function')
          err.message = whatsWrong.apply(null, arguments) + '\n' + err.message;

        if (whatsWrong)
          err.message = whatsWrong + '\n' + err.message;

        throw err;
      }
    };
  } else {
    return noop;
  }
};
var defineContract = exports.defineContract;

assert.string = check.assert.string;
assert.unemptyString = check.assert.unemptyString;
assert.webUrl = check.assert.webUrl;
assert.length = check.assert.length;
assert.number = check.assert.number;
assert.positive = check.assert.positive;
assert.negative = check.assert.negative;
assert.odd = check.assert.odd;
assert.even = check.assert.even;
assert.integer = check.assert.integer;
assert.function = check.assert.function;
assert.array = check.assert.array;
assert.length = check.assert.length;
assert.date = check.assert.date;
assert.object = check.assert.object;
assert.emptyObject = check.assert.emptyObject;
assert.instance = check.assert.instance;
assert.like = check.assert.like;
assert.null = check.assert.null;
assert.undefined = check.assert.undefined;
assert.assigned = check.assert.assigned;
assert.boolean = check.assert.boolean;

exports.check = check;
exports.assert = assert;

/**
 * Returns a contract of m by n matrix.
 * @param {Number} m
 * @param {Number} n
 * @param {String} msg
 * @returns {Contract}
 */
exports.ensureMatrixOfDimension = function ensureMatrixOfDimension(m, n, msg) {
  if (m !== '*') assert.positive(m);
  if (n !== '*') assert.positive(n);

  if (!msg) msg = 'input is not a matrix of ' + m + ' x ' + n + '.';
  return defineContract(function(mat) {
    assert.array(mat, 'mat is not a JS array');

    if (m === '*') m = mat.length;
    assert(mat.length === m, 'mat length ' + mat.length + ' is not '+ m);

    var i, j;
    for (i = 0; i < m; ++i) {
      assert.array(mat[i], 'row ' + i + ' is not a JS array');
      if (n === '*') n = mat[i].length;
      assert(mat[i].length === n, 'row ' + i + ' has ' + mat[i].length + ' elements' +
             ' instead of ' + n);
      for (j = 0; j < n; ++j) {
        assert.number(mat[i][j], 'mat(' + [i,j] + ') is not a number.');
      }
    }
  }, msg);
};
var ensureMatrixOfDimension =
      assert.ensureMatrixOfDimension =
      exports.ensureMatrixOfDimension;

/**
 * Check whether an object is a matrix of m by n.
 * @param {Any} mat
 * @param {Number} m
 * @param {Number} n
 * @returns {Boolean}
 */
exports.isMatrixOfDimension = function isMatrixOfDimension(mat, m, n) {
  try {
    ensureMatrixOfDimension(m, n)(mat);
  } catch(err) {
    return false;
  }
  return true;
};
var isMatrixOfDimension =
      check.isMatrixOfDimension =
      exports.isMatrixOfDimension;

/**
 * Returns a contract of m-D vector
 * @param {Number} m
 * @param {String} msg
 * @returns {Contract}
 */
exports.ensureVectorOfDimension = function ensureVectorOfDimension(n, msg) {
  if (n !== '*') assert.positive(n);
  if (!msg) msg = 'input is not a vector of ' + n + ' dimension.';
  return defineContract(function(vec) {
    assert.array(vec, 'vec is not a JS array');
    if (n === '*') n = vec.length;
    assert(vec.length === n, 'vec length ' + vec.length + ' is not ' + n);

    var i;
    for (i = 0; i < n; ++i)
      assert.number(vec[i], 'vec(' + i + '): ' + vec[i] + ' is not a number.');

  }, msg);
};

var ensureVectorOfDimension =
      assert.ensureVectorOfDimension =
      exports.ensureVectorOfDimension;

/**
 * Check whether an object is a vector of length m.
 * @param {Any} vec
 * @param {Number} m
 * @returns {Boolean}
 */
exports.isVectorOfDimension = function isVectorOfDimension(vector, n) {
  try {
    ensureVectorOfDimension(n)(vector);
  } catch(err) {
    return false;
  }
  return true;
};
var isVectorOfDimension =
      check.isVectorOfDimension =
      exports.isVectorOfDimension;