try {
  Microformats;
} catch (ex) {
  alert("Microformats are not available. Microformats.js must be included");
}

//const env = Components.classes[""].getService(Components.interfaces.nsIEnvironment);
const process = Components.classes["@mozilla.org/process/util;1"].getService(Components.interfaces.nsIProcess);
const emacsclient = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);

// you may have to tweak this path to match the location where your
// emacsclient is installed
emacsclient.initWithPath('/usr/local/bin/emacsclient');
process.init(emacsclient);

// runs the emacs client and passes a lisp expression to it
function emacs(sexp)
{
  if (emacsclient.exists())
  {
    process.run(true, ['-e', sexp], 2);
    return process.exitValue;
  }

  return -1;
}

// The arguments object that you get inside of a function is not a real array,
// much to everyone's detriment, so if we need an array of a function's
// arguments, we have to do something like this.
function clone(arry)
{
  var cloned = [];
  for (var i = 0; i < arry.length; i++)
    cloned.push(arry[i]);
  return cloned;  
}

// maketype and makelist are used to create a set of objects that's
// rather like a syntax tree. I had started out using the js types
// as direct equivalents to the Lisp types, but you have to do
// different things with function applications |(foo bar)|, lists
// of strings |("foo" "bar")|, vectors |["foo" "bar"]| and so this
// is what it evolved to.
function maketype(type, arg, def)
{
  return { type: type, data: arg || def };
}

function makelist(arry, open, sep, close)
{
  var items = []

  if (arry.length > 1)
    items = clone(arry);
  else
    items = arry[0];

  var type = maketype('list', items, []);
  type.open = open;
  type.sep = sep;
  type.close = close;

  return type;
}

// These functions create the different kinds of objects that lisp code can express
function identifier(str) { return maketype('name', str, ""); }
function quote(item) { return maketype('quote', item, []); }
function pair() { return makelist(arguments, '(', ' . ', ')'); }
function list() { return makelist(arguments, '(', ' ', ')'); }
function vector() { return makelist(arguments, '[', ' ', ']'); }
function alist(obj) { return maketype('alist', obj, {}); }

// and this function recursively serializes the syntax tree that's
// passed in into a lisp s-expression.
function lisp(expr)
{
  if (typeof expr == 'string')
  {
    expr.replace(/"/g, '\\"');
    return '"' + expr + '"';
  }

  if (typeof expr == 'object')
  {
    var data = expr.data;

    switch (expr.type)
    {
      case 'name':
        return data;
      case 'quote':
        return "'" + lisp(data);
      case 'list':
        return expr.open + data.map(lisp).join(expr.sep) + expr.close;
      case 'alist':
        var temp = [];
        for (prop in data)
          temp.push(pair(identifier(prop), data[prop]));

        return lisp(quote(list(temp)));
    }
  }
  else
    return expr || '""';
}

// this function converts the data struture used by the hCard impl into a list
// of vectors. The hCard impl is a list of objects, and each object has a
// phone number and a list of types (such as 'cell', 'work', etc). BBDB needs
// a list that looks like (['cell' "1234"] ['work' "1234"]), even if the numbers
// are the same. Frankly, I think the structure that the extension uses is a
// pretty weird. It's probably a premature optimization.
function flatten_tels(obj)
{
  var temp = [];
  obj.map(function(tel) { if (tel.type)
                            tel.type.map(function(type) { temp.push(vector(type, tel.value)); });
                          else
                            temp.push(vector('phone', tel.value)); });
  return list(temp);
}

Microformats.hCard.handlers['BBDB'] = {
  description: "Emacs BBDB",

  icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAPBAMAAAAfXVIcAAAABGdBTUEAALGPC/xhBQAAABhQTFRF////AAAA////oKCgw8PDgICAWFhYMDAwSeb/PgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH0QMVFiYNkBWYDAAAAGFJREFUeJwtTUEKgDAMS3+wVtHz4rzrBB8g4lnwI/7/ZDtsKUlD0gKjAtAF8iIqBVdx8BbVAFWr+6PJd06sbkDHTEvut8IzR3A4eIUAKWRThNzWdtRsvhuRXJrH//Q/gfh8XeoIL0xrtUAAAAAASUVORK5CYII=",

  action: function(item)
  {
    var hcard = Microformats.hCard.create(item);

    var addrs = list(hcard.adr.map(function(adr) { return vector(adr.type || "home",
                                                                 list(adr.street_address,
                                                                      adr.extended_address),
                                                                 adr.locality,
                                                                 adr.region,
                                                                 adr.postal_code,
                                                                 adr.country_name); }));
    var notes = {};
    hcard.url && (notes.www = hcard.url.join(' '));
    hcard.geo && (notes.geo = hcard.geo.latitude + ', ' + hcard.geo.longitude);

    var add_sexp = lisp(list(identifier('bbdb-create-internal'),
                             hcard.fn,
                             hcard.org,
                             quote(list(hcard.email)),
                             quote(addrs),
                             quote(flatten_tels(hcard.tel)),
                             alist(notes)));

    //Microformats.loadUrl('data:text/plain,' + add_sexp);
    emacs(add_sexp);
  }
}

// (defun bbdb-create-internal (name company net addrs phones notes)
//   "Adds a record to the database; this function does a fair amount of
// error-checking on the passed in values, so it's safe to call this from
// other programs.
//
// NAME is a string, the name of the person to add.  An error is signalled
// if that name is already in use and `bbdb-no-duplicates-p' is t.
// COMPANY is a string or nil.
// NET is a comma-separated list of email addresses, or a list of strings.
// An error is signalled if that name is already in use.
// ADDRS is a list of address objects.  An address is a vector of the form
// [\"location\" (\"line1\" \"line2\" ... ) \"City\" \"State\" \"Zip\" \"Country\"].
// PHONES is a list of phone-number objects.  A phone-number is a vector of
// the form
// [\"location\" areacode prefix suffix extension-or-nil]
// or
// [\"location\" \"phone-number\"]
// NOTES is a string, or an alist associating symbols with strings."