var util = require('../util');

var TYPES = '__types__';

var PARSERS = {
  boolean: util.boolean,
  integer: util.number,
  number:  util.number,
  date:    util.date,
  string:  function(x) { return x == null || x === '' ? null : x + ''; }
};

var TESTS = {
  boolean: function(x) { return x==='true' || x==='false' || util.isBoolean(x); },
  integer: function(x) { return TESTS.number(x) && (x=+x) === ~~x; },
  number: function(x) { return !isNaN(+x) && !util.isDate(x); },
  date: function(x) { return !isNaN(Date.parse(x)); }
};

function annotation(data, types) {
  if (!types) return data && data[TYPES] || null;
  data[TYPES] = types;
}

function type(values, f) {
  values = util.array(values);
  f = util.$(f);
  var v, i, n;

  // if data array has type annotations, use them
  if (values[TYPES]) {
    v = f(values[TYPES]);
    if (util.isString(v)) return v;
  }

  for (i=0, n=values.length; !util.isValid(v) && i<n; ++i) {
    v = f ? f(values[i]) : values[i];
  }

  return util.isDate(v) ? 'date' :
    util.isNumber(v)    ? 'number' :
    util.isBoolean(v)   ? 'boolean' :
    util.isString(v)    ? 'string' : null;
}

function typeAll(data, fields) {
  if (!data.length) return;
  fields = fields || util.keys(data[0]);
  return fields.reduce(function(types, f) {
    // don't expand embedded periods
    return (types[f] = type(data, '[' + f + ']'), types);
  }, {});
}

function infer(values, f) {
  values = util.array(values);
  f = util.$(f);
  var i, j, v;

  // types to test for, in precedence order
  var types = ['boolean', 'integer', 'number', 'date'];

  for (i=0; i<values.length; ++i) {
    // get next value to test
    v = f ? f(values[i]) : values[i];
    // test value against remaining types
    for (j=0; j<types.length; ++j) {
      if (util.isValid(v) && !TESTS[types[j]](v)) {
        types.splice(j, 1);
        j -= 1;
      }
    }
    // if no types left, return 'string'
    if (types.length === 0) return 'string';
  }

  return types[0];
}

function inferAll(data, fields) {
  fields = fields || util.keys(data[0]);
  return fields.reduce(function(types, f) {
    // don't expand embedded periods
    types[f] = infer(data, '[' + f + ']');
    return types;
  }, {});
}

type.annotation = annotation;
type.all = typeAll;
type.infer = infer;
type.inferAll = inferAll;
type.parsers = PARSERS;
module.exports = type;

@@ -636,24 +636,148 @@
 = types;%0A%7D%0A%0A
+function fieldNames(datum) %7B%0A  return util.keys(datum);%0A%7D%0A%0Afunction bracket(fieldName) %7B%0A  return '%5B' + fieldName + '%5D';%0A%7D%0A%0A
 function typ
@@ -1322,44 +1322,76 @@
 ;%0A  
-fields = fields %7C%7C util.keys(data%5B0%5D
+var get = fields ? identity : (fields = fieldNames(data%5B0%5D), bracket
 );%0A 
@@ -1437,45 +1437,8 @@
 ) %7B%0A
-    // don't expand embedded periods%0A
     
@@ -1467,29 +1467,22 @@
 e(data, 
-'%5B' + f + '%5D'
+get(f)
 ), types
@@ -2136,44 +2136,76 @@
 %7B%0A  
-fields = fields %7C%7C util.keys(data%5B0%5D
+var get = fields ? identity : (fields = fieldNames(data%5B0%5D), bracket
 );%0A 
@@ -2251,45 +2251,8 @@
 ) %7B%0A
-    // don't expand embedded periods%0A
     
@@ -2278,21 +2278,14 @@
 ta, 
-'%5B' + f + '%5D'
+get(f)
 );%0A 

Fix PR #80.