// loads AMO browser stats for an AMO public-stats addon (748 = Greasemonkey) for the
// last half a year (or some other number of days), cleans them up and passes them as
// cb([{ date: <Date object>, total: N, "Firefox/6.0": N, … }, { date: … }, …])
function get_amo_stats(cb, addon, days) {
  var pipe = 'ed7df07cb426304321a88e3cb875226c'
    , nth  = get_amo_stats.nth = (get_amo_stats.nth || 0) + 1
    , name = 'cb' + nth.toString(36)
    , now  = new Date
    , from = new Date(+now - 864e5 * (days || 365 >> 1))
    , aurl = 'https://addons.mozilla.org/en-US/firefox/statistics/csv/'+ (addon || 748)
           + '/application?start='+ y_m_d(from) +'&end='+ y_m_d(now)
    , purl = 'http://pipes.yahoo.com/pipes/pipe.run?_id='+ pipe +'&_render=json&c=9&u='
           + encodeURIComponent(aurl) + '&x=8&_callback=get_amo_stats.' + name
    , load = document.createElement('script');

   get_amo_stats[name] = function(json) {
     delete get_amo_stats[name];
     document.head.removeChild(load);
     cb(cleanup(json));
   };

   load.src = purl;
   document.head.appendChild(load);
}

var y_m_d = d3.time.format('%Y-%m-%d')
  , guids =
    { '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox'
    , '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}': 'SeaMonkey'
    , '{3550f703-e582-4d05-9a08-453d09bdfdc6}': 'ThunderBird'
    , '{a463f10c-3994-11da-9945-000d60ca027b}': 'Flock'
    , '{3db10fab-e461-4c80-8b97-957ad5f8ea47}': 'Netscape'
    , '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'Fennec'
    };

// AMO produces borked csv; the header line is formatted "# Fields: [date;count;...]",
// where each ... is a column name. After that, all lines are properly comma-separated
// though. Each ... is a {GUID}/n.n[...] value, where the GUID maps to a browser name.
// Drop columns that don't match this format or are from unknown GUIDs (these data are
// raw, so there's some unknown, some null / undefined / Invalid cruft in there, too).
function cleanup(raw) {
  function legal(col) { return valid.test(col); }
  function remap(app) { return app.replace(valid, fix); }
  function fix(o,a,v) { return guids[a.toLowerCase()] + v; }

  // rows are listed as objects with col_1, ... col_n properties; produce an array
  function rollup(row) {
    for (var c = 1, col, arr = []; row.hasOwnProperty(col = 'col_'+ c); c++)
      arr.push(row[col]);
    return arr;
  }
  function deref(array) { return function(d, i) { return array[i]; }; }

  var valid = new RegExp( '^('+ Object.keys(guids).join('|').replace(/([{}])/g, '\\$1')
                        + ')(/\\d+(?:\\.\\d+)*)$', 'i')
    , raw_h = rollup(raw.value.items[0]).join(',').split(';')
    , is_ok = [true, true].concat(raw_h.map(legal).slice(2))
    , label = ['date', 'total'].concat(raw_h.filter(legal).map(remap))
    , y_m_d = d3.time.format('%Y-%m-%d');

  valid = deref(is_ok); // is_ok Array<Boolean> => function(x, index) : Boolean
  valid = (function(test) { return function(a) { return a.filter(test); }; })(valid);

  return raw.value.items.slice(1).map(rollup).map(valid).map(function(arr) {
    function stow(val, idx) { data[label[idx]] = idx ? Number(val) : y_m_d.parse(val); }
    var data = {};
    arr.forEach(stow);
    return data;
  });
}