// ==UserScript==
// @name          finn.no-add-hcal-data-to-realesate
// @namespace     http://coffeebreaks.org/userscripts
// @description	  Introduce hcalendar information info finn.no realestate pages in order to ease integration with web calendars
// @include       http://finn.no/finn/realestate/*
// @include       http://www.finn.no/finn/realestate/*
// ==/UserScript==
// Notes:
//   * is a wildcard character
//   .tld is magic that matches all top-level domains (e.g. .com, .co.uk, .us, etc.)

// combined with something like a converthcalendartogooglecal.user.js
// it makes adding the events automatic to your google calendar
// Author: Jerome Lacoste <jerome@coffeebreaks.org>
// Date: 2 Aug 2007

// notes:
// * hopefully Finn will provide an API instead of having to do this raw parsing...
// * I've had to modify the original hcalendar publisher script as I had problems with timezones.
// * There are modified/improved version of the original hcalendar publisher script on the web, <a href="http://blog.chanezon.com/articles/2006/04/21/post-hcalendar-events-to-google-calendar-programmatically">in particular from Patrick Chanezon</a> of Google (and <a href="http://www.ossgtp.org/">ossgtp</a>) fame. I've chosen to use the original user script.
// * I've also had encoding issues in the add event page to google calendar. The problem may come from the button url generated publishing script instead, but I've chosen to to keep the differences minimal with the original script. Thus the generated hcalendar contains encoded data...
// * Timezone is hardcoded at +2 hours...

// further ideas and links...
// what about using: http://microformats.org/wiki/adr ???
// http://wiki.apexdevnet.com/index.php/Building_a_Google_Calendar_Mash-up_with_Salesforce_SOA
// http://torrez.us/code/googlehcal/converthcalendartogooglecal.user.js
// http://snyke.net/blog/wp-content/uploads//gcalendar.js
// http://microformats.org/wiki/hcalendar
// http://microformats.org/wiki/hcalendar-brainstorming

function getElementsByClassName(classname, node)  {
  if (!node) {
      node = body;
  }
  var a = [];
  var re = new RegExp('\\b' + classname + '\\b');
  var els = node.getElementsByTagName("*");
  for (var i = 0, j = els.length; i < j; i++) {
      if (re.test(els[i].className)) {
          a.push(els[i]);
      }
  }
  return a;
}

function replaceElt(currentElt, newElt) {
  var next = currentElt.nextSibling;
  var parent = currentElt.parentNode;
  parent.removeChild(currentElt);
  parent.insertBefore(newElt, next);
}

function normalize_space(stringToTrim) {
    // Replace repeated spaces, newlines and tabs with a single space
    // not sure why I have to use these 2 lines, but I've found that the last line wasn't doing its job...
    stringToTrim = stringToTrim.replace(/\t/g, " ");
    stringToTrim = stringToTrim.replace(/\u0a0/g, ' ')
    return stringToTrim.replace(/^\s*|\s(?=\s)|\s*$/g, "");
}

function trim(stringToTrim) {
	return stringToTrim.replace(/^\s+|\s+$/g,"");
}

function ltrim(stringToTrim) {
	return stringToTrim.replace(/^\s+/,"");
}
function rtrim(stringToTrim) {
	return stringToTrim.replace(/\s+$/,"");
}

// array: [ignore, d, m, y, h1, m1, h2, m2 ]
function array_to_ISO8601_date_range(a) {
    // GM_log("array range:" + a);
    var prefix = a[3]+a[2]+a[1]+'T'
    return [prefix+a[4]+a[5]+'Z0200',prefix+a[6]+a[7]+'Z0200']; // FIXME parametrize tz offset. Probably can do something like a (new Time().getTimeZoneOffset() / 60 * -1)
}

// this is not particularly Beautiful Code but does the job so far.
// will probably break on first finn.no upgrade...
function parseFinnNoPage() {
  // "FINN-kode: 10593497 - sist endret: 11.07.2007 19:53 "
  var tmp=document.getElementById('advert').childNodes[1].firstChild.firstChild.nodeValue.split(' ')
  var finnkode = tmp[1]
  var tmp2 = tmp[5].split('.')
  var tmp3 = tmp[6].split(':')
  var changeddate = new Date(tmp2[2], tmp2[1], tmp2[0], tmp3[0], tmp3[1])
  // GM_log(changeddate)

  var advert = document.getElementById("advert");
  var title = "Visning: " + trim(advert.childNodes[3].firstChild.nodeValue);
  // GM_log("title= " + title);
  var adUrl="http://finn.no/finn/realestate/object?finnkode=" + finnkode;
  // var mapurl= FIXME to find...

  var rightColumn = getElementsByClassName('right', advert)[0]

  var street = null;
  var zip = null;
  var town = null;
  var visningEltHeader = null;

  var visningPeriods = [];
  var visningElts = [];
  for (var i = 0; i <rightColumn.childNodes.length; i++) {
    var aH4 = rightColumn.childNodes[i];
    if (aH4.nodeName == 'H4')  {
      // alert(aH4.firstChild.nodeValue)
      if (aH4.firstChild.nodeValue == 'Visning') {
      	// keep track of it for later modification of the page
        visningEltHeader = aH4;        
        var aP = rightColumn.childNodes[i+2];
        if (aP.nodeName == 'P')  {
          // find the 'visning' periods
          for (var j = 0; j <aP.childNodes.length; j++) {
            var aText = aP.childNodes[j];
            if (aText.nodeType == 3 && /\d\d\.\d\d\.\d\d\d\d/.test(aText.data)) { // looks like a date...
              visningElts.push(aText);
              dateStr = normalize_space(aText.data);
              // GM_log("dateStr: " + dateStr);
              visningPeriods.push(dateStr);
            }
          }
        }
      }
      else if (aH4.firstChild.nodeValue == 'Info') {
        var aTable = rightColumn.childNodes[i+2];
        if (aTable.nodeName == 'TABLE')  {
          // find the address info
          var tbody = aTable.childNodes[1];
          for (var j = 0; j < tbody.childNodes.length; j++) {
            var aT = tbody.childNodes[j];
            if (aT.nodeName == 'TR') {
              for (var k = 0; k < aT.childNodes.length; k++) {
                if (aT.childNodes[k].nodeName == 'TD') {
                  var firstTd = aT.childNodes[k];
                  if (street == null) {
                    street = firstTd.firstChild.nodeValue;
//                    GM_log("street" +street);
                  } else if (zip == null) {
//                    GM_log("zip/town:" +aT.nodeName + "  " + aT.innerHTML); 
                    for (var l = 0; l < firstTd.childNodes.length; l++) {
                      var aText = firstTd.childNodes[l];
                      if (aText.nodeType == 3) {
//                        GM_log(aText)
                        var t = normalize_space(aText.data).split(" ");
                        zip = t[0];
                        town = t[1];
//                        GM_log("zip:" + zip + " town:" + town);
                      }
                    }
                  }
                }      
              } 
            }
          }
        }
      }
    }
  }

  loc = {
    "street" : street,
    "town" : town,
    "zip" : zip,
  };
  
  urls = [ adUrl ];

  text = "";

  return { "dates" : visningPeriods, 
	   "text" : text, 
	   "description" : title, 
	   "loc" : loc, 
	   "urls" : urls,
           "visningElt" : visningEltHeader,
           "visningElts" : visningElts
        };
}

function toHCal(parsed, dateIdx) {
  // note: I do some encoding that I could probably avoid but I run into some problems with the Google calendar hcalendar publishing user script

  var root = document.createElement('div');
  root.setAttribute('class', 'vevent');
  root.setAttribute('id', 'hcalendar-visning-' + parsed.finnkode + "-" + dateIdx);

  var url = document.createElement('a');
  url.setAttribute('class', 'url');
  url.setAttribute('href', parsed.urls[0]);
  root.appendChild(url);

  // now we have "Søndag 05.08.2007, 17.00 - 18.00"
  // convert to ['20070805T170000','20070805T180000']
  var match = parsed.dates[dateIdx].match(/.*[ ]+(\d\d).(\d\d).(\d\d\d\d), (\d\d)\.(\d\d) - (\d\d)\.(\d\d)/);
  var dates = array_to_ISO8601_date_range(match);
  
  var datesStr = parsed.dates[dateIdx].split('-');

  var dtstart = document.createElement('abbr');
  dtstart.setAttribute('class', 'dtstart');
  dtstart.setAttribute('title', dates[0]);
  dtstart.appendChild(document.createTextNode(trim(datesStr[0])));
  url.appendChild(dtstart);

  var sep = document.createTextNode(' — '); // &mdash;
  url.appendChild(sep);

  var dtend = document.createElement('abbr');
  dtend.setAttribute('class', 'dtend');
  dtend.setAttribute('title', dates[1]);
  dtend.appendChild(document.createTextNode(trim(datesStr[1])));
  url.appendChild(dtend);

  var summary = document.createElement('span');
  summary.setAttribute('class', 'summary')
  summary.setAttribute('style', 'display:none;');
  summary.appendChild(document.createTextNode(encodeURIComponent(parsed.description)));
  url.appendChild(summary);

  var desc = document.createElement('span');
  desc.setAttribute('class', 'description')
  desc.setAttribute('style', 'display:none;');
  desc.appendChild(document.createTextNode(encodeURIComponent(parsed.urls[0]))); // parsed.text
  url.appendChild(desc);

  var loc = document.createElement('span');
  loc.setAttribute('class', 'location')
  loc.setAttribute('style', 'display:none;');
  loc.appendChild(document.createTextNode(encodeURIComponent(parsed.loc.street + ", " + parsed.loc.zip + " "
   + parsed.loc.town)));
  url.appendChild(loc);

  return root;
}

function replaceVisningsWithHCal() {
  var parsed = parseFinnNoPage();

  GM_log("Found " + parsed.visningElts.length + " visning(s).");

  for (var i = 0 ; i < parsed.visningElts.length ; i++) {
    var current = parsed.visningElts[i];
    var hcal = toHCal(parsed, i);
    replaceElt(current, hcal);
  }
}

replaceVisningsWithHCal();

