// Generated by CoffeeScript 1.6.3
/*
  k-Nearest Neighbour library
  -------
  subject:  vantage point object - will consider each attribute present in this object as a feature
  objects:  array of objects that should all have at least the attributes of subject
  options:
      - k: (default = unlimited) specifies how many objects to return
      - standardize: (default = false) if true, will apply standardization across all attributes using stdvs - set this to true if your attributes do not have the same scale
      - weights: (default = {}) a hash describing the weights of each attribute
      - key: (default = none) a key function to map over objects, to be used if the subject attributes are nested within key
          e.g. if subject is {a:0} and objects are [{x: {a: 0}}, {x: {a: 2}}], then provide key: function(o) {return o.x}
      - filter: (default = none) a filter function that returns true for items to be considered
          e.g. to only consider objects with non-negative a: function(o) {return o.a >= 0})
      - debug: (default = false) if true, for every object will return distances of individual attributes as well as the overall distance from the subject under a property called 'debug'
          e.g. if subject is {a:0, b:0} and object is {a:3, b:4}, the returned object will be {a: 3, b: 4, debug: {distance:25, details: {a: 9, b: 16}}}
*/


(function() {
  var stable, util;

  util = require('./util');

  stable = require('stable');

  module.exports = function(subject, objects, options) {
    var attr, debug, distances, i, o, obj, object, objects_filtered, objects_mapped, sortMap, sortedObjects, stdv, weights, _i, _len;
    if (arguments.length < 2) {
      throw new Error('Expecting at least 2 arguments');
    }
    if (!(Array.prototype.slice.call(arguments).every(function(i) {
      return i && typeof i === 'object';
    }))) {
      throw new Error('Expecting object arguments');
    }
    if (!Array.isArray(arguments[1])) {
      throw new Error('Expecting an array as second argument');
    }
    objects_mapped = objects;
    objects_filtered = objects;
    if ((options != null ? options.filter : void 0) != null) {
      objects_filtered = (function() {
        var _i, _len, _results;
        _results = [];
        for (_i = 0, _len = objects.length; _i < _len; _i++) {
          obj = objects[_i];
          if (options.filter(obj)) {
            _results.push(obj);
          }
        }
        return _results;
      })();
    }
    objects_mapped = objects_filtered;
    if ((options != null ? options.key : void 0) != null) {
      objects_mapped = (function() {
        var _i, _len, _results;
        _results = [];
        for (_i = 0, _len = objects_filtered.length; _i < _len; _i++) {
          obj = objects_filtered[_i];
          _results.push(options.key(obj));
        }
        return _results;
      })();
    }
    if (!objects_mapped.length) {
      return [];
    }
    for (attr in subject) {
      for (_i = 0, _len = objects_mapped.length; _i < _len; _i++) {
        o = objects_mapped[_i];
        if (!(attr in o)) {
          throw new Error("Missing attribute '" + attr + "' in '" + (JSON.stringify(o)) + "'");
        }
      }
    }
    stdv = {};
    if ((options != null ? options.standardize : void 0) != null) {
      stdv = util.allStdvs(subject, objects_mapped);
    }
    weights = {};
    if (options != null ? options.weights : void 0) {
      weights = options.weights;
    }
    debug = (options != null ? options.debug : void 0) === true ? true : false;
    distances = (function() {
      var _j, _len1, _results;
      _results = [];
      for (i = _j = 0, _len1 = objects_mapped.length; _j < _len1; i = ++_j) {
        object = objects_mapped[i];
        _results.push({
          index: i,
          dist: util.distance(subject, object, {
            stdv: stdv,
            weights: weights,
            debug: debug
          })
        });
      }
      return _results;
    })();
    sortMap = stable(distances, debug ? function(a, b) {
      return a.dist.distance - b.dist.distance;
    } : function(a, b) {
      return a.dist - b.dist;
    });
    sortedObjects = (function() {
      var _j, _len1, _results;
      _results = [];
      for (_j = 0, _len1 = sortMap.length; _j < _len1; _j++) {
        i = sortMap[_j];
        if (debug) {
          objects_filtered[i.index].debug = i.dist;
        }
        _results.push(objects_filtered[i.index]);
      }
      return _results;
    })();
    if (options != null ? options.k : void 0) {
      return sortedObjects.slice(0, options.k);
    } else {
      return sortedObjects;
    }
  };

}).call(this);
