/***
| Name|TwabPlugin|
| Author|Vincent ~DiBartolo ([[vadibart@gmail.com|mailto:vadibart@gmail.com]])|
| Version|2.2|
| Date|12/08/2008|
| Source|http://www.tiddly-twab.com/#TwabPlugin|
| License|BSD License - http://www.tiddly-twab.com/#TheBSDLicense|
| Requires|~TW2.x, [[DataTiddlerPlugin]], [[FormTiddlerPlugin]], [[InlineJavascriptPlugin]], [[PartTiddlerPlugin]], and any Tiddlers with the [[twab]] tag in the source file |
!Description
Elegant system for keeping your Address Book inside a TiddlyWiki document.  Supports import and export of contacts via CSV data.  Built-in support for Google, Yahoo, MSN, and Outlook CSV formats.  Supports customized formats for those not built-in.

!History
* 08-Dec-08, version 2.2 - use company name if neither first name or last name exists on import, allow for multiple contacts with same title
* 21-Jul-08, version 2.1 - use contact's first and last name in email link if it's present (Thanks to Lyall)
* 20-Feb-08, version 2.0 - import and export Google, Yahoo, MSN, Outlook, or custom CSV formats
* 29-Jun-07, version 1.1 - adding support for mailto:, http:, and map directions
* 20-Jun-07, version 1.0 - preparing for release, changed name to twab
* 19-Jun-07, version 0.4 - modified how new contacts are added
* 21-Nov-06, version 0.3 - changed name from ContactParserMacro to AddressBookMacro
* 10-Oct-06, version 0.2 - converted from regex parsing for title of contact Tiddler to JSON
* 09-Oct-06, version 0.1 - created file

!Example of Adding New Contact
Place the following code in any Tiddler:
{{{
<<twab>>
}}}
Which will result in: <<twab>>
You can add extra parameters to change the button's name as in:
{{{
<<twab press this button>>
}}}
Which will result in: <<twab press this button>>
See [[About:twab:Overview]] for more information.

!Example of Import
Place the following code in any Tiddler:
{{{
<<twab Import AddressBook>>
}}}
Which will result in: <<twab Import AddressBook>>
See [[About:twab:Import]] for more information.

!Example of Export
Place the following code in any Tiddler:
{{{
<<twab Export AddressBook>>
}}}
Which will result in: <<twab Export AddressBook>>
See [[About:twab:Export]] for more information.

! Generate Test Data for Import
Place the following code in any Tiddler:
{{{
<<twab ImportTest>>
}}}
Which will result in: <<twab ImportTest>>
See [[About:twab:Import]] for more information.

!Code
***/
//{{{


version.extensions.TwabMacro = { 
   major: 2, 
   minor: 0, 
   revision: 0, 
   date: new Date(2008,02,16), 
   source: "http://www.tiddly-twab.com"
};


config.macros.twab = {};
config.macros.twab.newButtonText    = "new contact";
config.macros.twab.importButtonText = "import contacts";
config.macros.twab.exportButtonText = "export contacts";
config.macros.twab.preClean         = true;
config.macros.twab.importTiddler    = "TwabImport";
config.macros.twab.exportTiddler    = "TwabExport";
config.macros.twab.importTags       = "AddressBook";
config.macros.twab.exportTags       = "AddressBook";
config.macros.twab.fnameField       = "first.name";
config.macros.twab.lnameField       = "last.name";
config.macros.twab.companyNameField = "company";
config.macros.twab.mapTagPrefix     = "format:";
config.macros.twab.skipFlag         = "<skip>";
config.macros.twab.unmappedFlag     = "unmapped";


config.macros.twab.handler = function (place, macroName, params) {

   if( (params.length == 1) && (params[0] == "ImportTest") ){

      //if they want to create an import test button
      createTiddlyButton(place, "Populate " + config.macros.twab.importTiddler, "", this.testImport);
      return;

   } else if( (params.length >= 1) && (params[0] == "Import") ){

      //any other params are interpreted as tags to be placed on imported tiddlers
      if( params.length >= 2 ){
         config.macros.twab.importTags = "";
         for( var i=1; i<params.length; i++){
            config.macros.twab.importTags += params[i] + " ";
         }//for

      }//if

      //if they want to import data
      createTiddlyButton(place, config.macros.twab.importButtonText, "", this.importContacts);

   } else if( (params.length >= 1) && (params[0] == "Export") ){

      //the first tag of the remainder is the one to use for exports
      if( params.length >= 2 ){
         config.macros.twab.exportTags = params[1];
      }//if

      //if they want to export data
      createTiddlyButton(place, config.macros.twab.exportButtonText, "", this.exportContacts);

   } else {
   
      //assume want to create a new contact - any extra params are button name
      var buttonText = "";
      for( var i=0; i<params.length; i++){
         buttonText += " " + params[i] + " ";
      }//for

      createTiddlyButton(place, ((buttonText == "") ? config.macros.twab.newButtonText : buttonText), "", this.newContact);

   }//if-else ifs

}//function handler



//from: http://www.nicknettleton.com/zine/javascript/trim-a-string-in-javascript
config.macros.twab.trim = function(word) { return word.replace(/^\s+|\s+$/g, ''); }



//proxy that retrieves some data and sets null to empty string
config.macros.twab.getData = function(tiddlerName, fieldName){

   var data = DataTiddler.getData(tiddlerName, fieldName);

   if( data == null ){
      data = "";
   }//if

   return data;

}//function getData



config.macros.twab.testImport = function(e){

   var title = config.macros.twab.importTiddler;

   var text = "first.name,last.name,job.title,webpage,phone,email\nPrincess,Leia,\"Leader, Rebels\",http://starwars.wikia.com/wiki/Leia_Organa,,leia@alderaan.com\nDarth,Vader,\"Sith Lord\",http://starwars.wikia.com/wiki/Darth_Vader,555-1212,darth@deathstar.com\nLuke,Skywalker,Jedi Master,http://starwars.wikia.com/wiki/Luke_Skywalker,,luke@tatooine.com";

   store.saveTiddler(title, title, text, config.options.txtUserName);
   story.displayTiddler(null, title, DEFAULT_VIEW_TEMPLATE);
   return true;

}//function import



config.macros.twab.newContact = function(e){

   var title = prompt("Please enter contact's name", "");
   if( (title == null) || (title == "") ){
      return;
   }//if

   store.saveTiddler(title, title, "<<tiddler ContactsFormTemplate>><data>{}</data>", config.options.txtUserName, new Date(), config.macros.twab.importTags);

   story.displayTiddler(null, title, DEFAULT_VIEW_TEMPLATE);

}//function new



config.macros.twab.importContacts = function(e){
   config.macros.twab.deleteAll();
   config.macros.twab.parseAll( config.macros.twab.getImportedCSVText() );
   alert("Contacts successfully imported.");
   return true;
}//function importContacts



config.macros.twab.exportContacts = function(e){
   var title   = config.macros.twab.exportTiddler;
   var mapping = config.macros.twab.getMapping(title, true);
   store.saveTiddler(title, title, config.macros.twab.getExportedCSVText(mapping), config.options.txtUserName );
   story.displayTiddler(null, title, DEFAULT_VIEW_TEMPLATE);
   return true;
}//function exportContacts



config.macros.twab.deleteAll = function(){

   if( !config.macros.twab.preClean ){
      return;
   }//if

   //only remove tiddlers tagged with only first tag if contacts each get more than one
   var tags = config.macros.twab.importTags.split(" ");
   var tag  = tags[0];
   if( !confirm("Are you sure you want to clear existing Tiddlers tagged \"" + tag + "\"?") ){
      return;
   }//if

   var contacts = store.getTaggedTiddlers(tag);
   for( var i=0; i<contacts.length; i++ ){
      store.removeTiddler( contacts[i].title );
   }//for

}//function deleteAll



config.macros.twab.getImportedCSVText = function(){
   return store.getTiddler(config.macros.twab.importTiddler).text;
}//function getImportedCSVText



config.macros.twab.getExportedCSVText = function(mapping){

   var returnStr = "";

   //get the mapped header columns
   for( var i=0; i<mapping.length; i++ ){

      if( mapping[i] && (mapping[i] != config.macros.twab.unmappedFlag) ){
         var twabCol = mapping[i];
         var mapCol  = mapping[twabCol];
         returnStr  += '"' + mapCol + '",';
      }//if

   }//for

   //get the unmapped header columns
   var unmappedStr = mapping[config.macros.twab.unmappedFlag];
   if( unmappedStr ){

      var unmappedArr = unmappedStr.split(",");
      for( var i=0; i<unmappedArr.length; i++ ){
         returnStr += '"' + unmappedArr[i] + '",';
      }//for

      //strip off the last ","
      returnStr = returnStr.substring(0, returnStr.length-1);

   }//if

   returnStr += "\n";

   //get all contacts
   var tags     = config.macros.twab.exportTags.split(" ");
   var contacts = store.getTaggedTiddlers( tags[0] );
   for( var i=0; i<contacts.length; i++ ){
      returnStr += config.macros.twab.exportContact(contacts[i], mapping) + "\n";
   }//for

   return returnStr;

}//function getExportedCSVText



config.macros.twab.parseAll = function (contactStr){

   var rows = contactStr.split("\n");
   if( rows.length < 2 ){
      alert("Two or more rows must be present to parse contacts.");
      return;
   }//if

   var header = config.macros.twab.parseHeader(rows[0]);
   if( header.length == 0 ){
      return;
   }//if

   var contacts = new Array();
   for( i=1; i<rows.length; i++){
      contacts[ i-1 ] = config.macros.twab.parseContact(header, rows[i]);
   }//for

   //uncomment this to get contact-by-contact alerts
   //config.macros.twab.debugAll(contacts);
   config.macros.twab.addAll(contacts);

}//function parseAll



config.macros.twab.parseHeader = function(row){

   var mappedHeader = new Array();

   //get the raw data
   var unmappedHeader = config.macros.twab.parseCSV(row);

   //get the appropriate mapping
   var mapping = config.macros.twab.getMapping(config.macros.twab.importTiddler, false);

   //now convert the unmapped header to the mapped header
   for( var i=0; i<unmappedHeader.length; i++ ){

      var colName = unmappedHeader[i].replace(/ /g, ".").toLowerCase();
      if( mapping[colName] ){
         mappedHeader[i] = mapping[colName];
      } else {
         mappedHeader[i] = config.macros.twab.skipFlag;
      }//if

      //uncomment this to get field-by-field alerts from the header
      //alert("Header field " + i + " is '" + mappedHeader[i] + "'");

   }//for

   return mappedHeader;

}//function parseHeader



config.macros.twab.getMapping = function(tiddlerName, isExport){

   var mapTiddlerName = "";
   if( store.getTiddler(tiddlerName) ){

      //see if they've declared a mapping (preset or custom) by looking
      //at a tag on the tiddler passed in
      var tagStr = ""+store.getTiddler(tiddlerName).tags; 
      var tags   = tagStr.split(',');
   
      for( var i=0; i<tags.length; i++ ){
   
         if( tags[i].indexOf( config.macros.twab.mapTagPrefix ) == 0 ){
            mapTiddlerName = tags[i].replace(config.macros.twab.mapTagPrefix, "");
         }//if
   
      }//for
         
   }//if

   //parse the mapping Tiddler
   var mapTiddler = config.macros.twab.getMappingTiddler(mapTiddlerName);
   if( !mapTiddler ){
      alert("Import/Export Format Tiddler " + mapTiddler + " does not exist.  Can't proceed.");
      return new Array();
   }//if

   var mapText = ""+store.getTiddler(mapTiddler).text;
   var mapArr  = mapText.split("\n");

   var mapping = new Array();
   for( var i=0; i<mapArr.length; i++ ){

      var rule = mapArr[i].split("=");
      if( !rule || (rule.length < 2) ){
         continue;
      }//if

      //uncomment this to see what mapping is being applied to your import file
      //alert("Twab column '" + twabCol + "' is mapped to input tiddler column '" + mapCol + "'");

      if( isExport ){

         var twabCol = config.macros.twab.trim( rule[0] );
         var mapCol  = config.macros.twab.trim( rule[1] );
         if( mapCol == config.macros.twab.skipFlag ){
            continue;
         }//inner if

         mapping[twabCol] = mapCol;
         mapping[i]       = twabCol;

      } else {

         var twabCol = config.macros.twab.trim( rule[0].replace(/ /g, ".").toLowerCase() );
         var mapCol  = config.macros.twab.trim( rule[1].replace(/ /g, ".").toLowerCase() );
         mapping[mapCol] = twabCol;
         mapping[i]      = mapCol;

      }//outer if-else

   }//for 

   return mapping;

}//function getMapping



config.macros.twab.getMappingTiddler = function(name){

   var tiddlerName = "TwabDefaultFieldMap";

   if( name == "google" ){
      tiddlerName = "TwabGoogleFieldMap";

   } else if( name == "yahoo" ){
      tiddlerName = "TwabYahooFieldMap";

   } else if( name == "msn" ){
      tiddlerName = "TwabMSNFieldMap";

   } else if( name == "outlook" ){
      tiddlerName = "TwabOutlookFieldMap";

   } else {

      //see if a Tiddler by this name exists, if not use the default
      if( (name != "default") && store.getTiddler(name) ){
         tiddlerName = name;
      } else {
         tiddlerName = "TwabDefaultFieldMap";
      }//inner if-else

   }//outer if-else ifs

   return tiddlerName;

}//function getMappingTiddler



config.macros.twab.parseCSV = function(row){

   var scrubbed = new Array();
   var fields   = row.split(",");
   for( var i=0; i<fields.length; i++){

      //if starts with quote but doesn't end with a quote, likely had a comma in the middle
      if( (fields[i].charAt(0) == '"') && (fields[i].charAt( fields[i].length-1) != '"') ){

         //Hotmail bug: last contact doesn't have ending double-quote
         if( i == (fields.length-1) ){
            scrubbed[ scrubbed.length ] = fields[i].replace(/"/g, "");
            continue;
         }//if

         var quoted = fields[i++];
         if( !fields[i] ){
            continue;
         }//if

         while( fields[i].charAt( fields[i].length-1 ) != '"' ){
            quoted += "," + fields[i++];
         }//while

         quoted += "," + fields[i];
         scrubbed[ scrubbed.length ] = quoted.replace(/"/g, "");

      } else {
         scrubbed[ scrubbed.length ] = fields[i].replace(/"/g, "");

      }//if-else

   }//for

   return scrubbed;

}//function parseCSV



config.macros.twab.parseContact = function (header, row){

   var returnStr = "";
   var fields    = config.macros.twab.parseCSV(row);
   for( var i=0; i<fields.length; i++ ){

      if( header[i] == config.macros.twab.skipFlag ){
         continue;

      } else if( fields[i] ){
         returnStr += "\"" + header[i] + "\":\"" + fields[i] + "\",";

      }//if-else

   }//for

   return returnStr.substr(0, returnStr.length-1);

}//function parseContact



//export a particular contact to a particular mapping
config.macros.twab.exportContact = function(contactTiddler, mapping){

   var returnStr = "";
   var text      = contactTiddler.text;

   //have to strip out FormTiddler stuff
   text = text.replace(/<<tiddler ContactsFormTemplate>>/g, "");
   text = text.replace(/<data>/g, "");
   text = text.replace(/<\/data>/g, "");
   text = text.replace(/\n/g, "");

   //use JSON format to our advantage
   var contact = eval( "("+text+")" ); 

   for( var i=0; i<mapping.length; i++){

      var twabCol = mapping[i];
      if( !(twabCol) || 
          !(mapping[twabCol]) || 
          (twabCol == config.macros.twab.unmappedFlag) || 
          (mapping[twabCol] == config.macros.twab.skipFlag)){
         continue;
      }//if

      //Google hack - on export, "Name" should be "first.name" and "last.name" together.
      //Know this because "first.name" is mapped to "Name" and "last.name" is not mapped
      if( (twabCol == config.macros.twab.fnameField) && !mapping[config.macros.twab.lnameField] ){
         returnStr += '"' + contact[config.macros.twab.fnameField] + " " + contact[config.macros.twab.lnameField] + '",'
      } else if( contact[twabCol] ){
         returnStr += '"' + contact[twabCol] + '",';
      } else {
         returnStr += ",";
      }//if-else

   }//for

   //get the unmapped columns
   var unmappedStr = mapping[config.macros.twab.unmappedFlag];
   if( unmappedStr ){

      var unmappedArr = unmappedStr.split(",");
      for( var i=0; i<unmappedArr.length; i++ ){
         returnStr += ",";
      }//for

      //strip out the last ","
      returnStr = returnStr.substr(0, returnStr.length-1);

   }//if

   return returnStr.replace(/\n/g, "");

}//function exportContact



//add/overwrite existing contacts with the data parsed out of the import tiddler
config.macros.twab.addAll = function(contacts){

   for( var i=0; i<contacts.length; i++ ){

      if( !contacts[i] ){
         continue;
      }//if

      //use JSON format to our advantage
      var toEval  = "({" + contacts[i] + "})";  
      var contact = eval(toEval); 
      var title   = config.macros.twab.getSaveTitle(contact);
      if( title == "" ){
         continue;
      }//if

      //add it now
      var text = config.macros.twab.toTiddlyFormat(contacts[i]);
      store.saveTiddler(title, title, text, config.options.txtUserName, new Date(), config.macros.twab.importTags);

   }//for

}//function addAll



config.macros.twab.getSaveTitle = function(contact){

   var title = "";

   if( contact[config.macros.twab.fnameField] ){
      title += contact[config.macros.twab.fnameField];
   }//if

   if( contact[config.macros.twab.lnameField] ){
      title += contact[config.macros.twab.lnameField];
   }//if

   if( title == "" ){
      title = contact[config.macros.twab.companyNameField];
   }//if

   if( title == "" ){
      alert("Contact missing name field - could not be added: \n" + contacts[i]);
      return "";
   }//if

   if( store.tiddlerExists(title) ){

      //try up to 50 times
      var seqTitle = "";
      var foundOne = false;
      for( var i=2; i<51; i++){

         seqTitle = title + " (" + i + ")";
         if( !store.tiddlerExists(seqTitle) ){
            title    = seqTitle;
            foundOne = true;
            break;
         }//innermost if

      }//inner if

      //if got to 50 then there's a problem
      if( !foundOne ){
         alert("Seriously, you really have that many contacts named '" + title + "'?  Wow.");
         return "";
      }//if

   }//if

   return title;

}//function getSaveTitle



config.macros.twab.toTiddlyFormat = function(contact){

   var tpl = config.macros.twab.getContactTemplate();
   return tpl.replace( config.macros.twab.getMacroName(), contact);

}//function toTiddlyFormat



config.macros.twab.getContactTemplate = function(){
   var macroName = config.macros.twab.getMacroName();
   return "<<tiddler ContactsFormTemplate>>\n<data>{" + macroName + "}</data>";
}//function getContactTemplate



config.macros.twab.getMacroName = function(){
   return "#thisContact#";
}//function getMacroName



config.macros.twab.debugAll = function(contacts){

   for( var i=0; i<contacts.length; i++ ){
      //alert( contacts[i] );
      alert( config.macros.twab.toTiddlyFormat(contacts[i]) );
   }//for

}//function debugAll



config.macros.twab.populateLinks = function(place){
   config.macros.twab.populateEmail(place, "email");
   config.macros.twab.populateEmail(place, "other.email");
   config.macros.twab.populateEmail(place, "business.email");
   config.macros.twab.populateHref(place, "webpage");
   config.macros.twab.populateMap(place, "home");
   config.macros.twab.populateHref(place, "business.webpage");
   config.macros.twab.populateMap(place, "business");
}//function populateLinks



config.macros.twab.populateEmail = function(place, fieldName){
   var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(place);
   var element     = document.getElementById("twab." + fieldName);
   if( element ){
      element.innerHTML = config.macros.twab.formatEmail(tiddlerName, fieldName);
   }//if
}//function populateEmail



//Thanks to Udo for the idea for this solution and to Lyall for the code to use display name
config.macros.twab.formatEmail = function(tiddlerName, fieldName){

   var returnStr   = "";
   var mailTo      = config.macros.twab.getData(tiddlerName, fieldName);
   var displayName = config.macros.twab.getData(tiddlerName, "first.name") + " " + config.macros.twab.getData(tiddlerName, "last.name");

   if( mailTo != "" ){

      if( displayName != "" ){
         mailTo = displayName + "<" + mailTo + ">";
      }//if

      returnStr = "<a href=\"mailto:" + mailTo + "\" content=\"\">(email)</a>";

   }//if

   return returnStr;

}//function formatEmail



config.macros.twab.populateHref = function(place, fieldName){
   var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(place);
   var element = document.getElementById("twab." + fieldName);
   if( element ){
      element.innerHTML = config.macros.twab.formatHref(tiddlerName, fieldName);
   }//if
}//function populateHref



//Thanks to Udo for the idea for this solution
config.macros.twab.formatHref = function(tiddlerName, fieldName){

   var returnStr = "";
   var href     = config.macros.twab.getData(tiddlerName, fieldName);

   if( href != "" ){

      if( href.indexOf("http") != 0 ){
         href = "http://" + href;
      }//inner if

      returnStr = "<a href=\"" + href + "\" content=\"\" target=\"twab\">(visit)</a>";

   }//if

   return returnStr;

}//function formatHref



config.macros.twab.populateMap = function(place, fieldType){
   var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(place);
   var element = document.getElementById("twab." + fieldType + ".map");
   if( element ){
      element.innerHTML = config.macros.twab.formatMap(tiddlerName, fieldType);
   }//if
}//function populateHref



//Thanks to Udo for the idea for this solution
config.macros.twab.formatMap = function(tiddlerName, fieldType){

   //hack for lack of planning of home versus business labels
   if( fieldType == "business"){
      fieldType += ".";
   } else if( fieldType == "home" ){
      fieldType = "";
   }//if-else if

   var returnStr = "";

   var address = "";
   if( DataTiddler.getData(tiddlerName, fieldType+"address") ){
      address += DataTiddler.getData(tiddlerName, fieldType+"address") + " ";
   }//if

   if( DataTiddler.getData(tiddlerName, fieldType+"city") ){
      address += DataTiddler.getData(tiddlerName, fieldType+"city") + " ";
   }//if

   if( DataTiddler.getData(tiddlerName, fieldType+"state") ){
      address += DataTiddler.getData(tiddlerName, fieldType+"state") + " ";
   }//if

   if( DataTiddler.getData(tiddlerName, fieldType+"postal") ){
      address += DataTiddler.getData(tiddlerName, fieldType+"postal") + " ";
   }//if

   if( address == "" ){
      return "";
   }//if

   var href  = "http://maps.google.com/maps?ie=UTF8&hl=en&q=" + address + "&f=q&sampleq=1";
   returnStr = "<a href=\"" + href + "\" content=\"\" target=\"twab\">(map)</a>";

   returnStr.replace(/ /g, "\+");
   return returnStr;

}//function formatMap

//}}}