/*
 * Copyright © 2006 Philip S Tellis <http://bluesmoon.blogspot.com/>
 * currently, all rights reserved, but see back later for a different licence.
 */
var VERSION = "0.6.2";

var apikey = "883a3e3270351d0e8fe7c9c958d4419b";
var apiurl = "http://api.flickr.com/services/rest?api_key=" + apikey + "&format=json&method=flickr.";
var tdelta = 43200000;
var sdelta = 0.125;
var images_per_page = 200;
var colors = ['blue', 'green', 'amber', 'yellow', 'red', 'pink', 'purple', 'beige'];
var markerimages = [];

var usernamemap = {};
var nsidmap = {};
var map;

if(typeof(console) == 'undefined')
   console = {log: function() {}};

function call_api_method(method, url, callback, params, callback_params)
{
   var scr = document.createElement("script");

   var uname = url.replace(/^http:\/\/.*flickr\.com\/[^\/]+\/([^\/]+)\/?$/, "$1");

   usernamemap[uname] = usernamemap[uname] || {callbacks: [], len: 0};

   var i = usernamemap[uname].len++;

   var cb = function(json)
   {
      document.body.removeChild(scr);
      usernamemap[uname].callbacks['c' + i] = null;

      if(json.stat == 'fail')
         alert("Error " + json.code + " calling flickr." + method + ": " + json.message);
      else
         callback(json, uname, callback_params);

      scr = uname = method = callback = callback_params = null;
   };

   usernamemap[uname].callbacks['c' + i] = cb;

   scr.type="text/javascript";

   var url = apiurl + method
            + "&jsoncallback=" + encodeURIComponent( "usernamemap." + uname + ".callbacks.c" + i);
   for(var k in params)
      if(params.hasOwnProperty(k))
         url += '&' + encodeURIComponent(k) + '=' + encodeURIComponent(params[k]);

   if(method=='photos.search')
      console.log(url);

   scr.src = url;

   document.body.appendChild(scr);
}

document.getElementById('url').onchange = function(e)
{
   var url = this.value;
   if(!url)
      return;

   usernamemap[url] = usernamemap[url] || {callbacks: []};
   call_api_method("urls.lookupUser", url, map_url_to_nsid, {"url":url});
}

window.onload = function(e)
{
   param_init();
   init();

   for(var i=0; i<colors.length; i++)
   {
      var img = new YImage("http://bluesmoon.info/hacks/footsteppr/images/" + colors[i] + ".png", new YSize(16, 16), new YCoordPoint(8, 8), new YCoordPoint(8, 8));
      markerimages.push(img);
   }

   var un = document.getElementById('url');
   un.onchange.call(un);
}

function param_init()
{
   var td = document.getElementById('tdelta');
   var sd = document.getElementById('sdelta');
   if(!td.value)
      td.value = tdelta/3600000;
   if(!sd.value)
      sd.value = sdelta;

   var url = location.search.match(/url=([^&;]+)/);
   if(!url || url.length <= 1)
      return;

   url = decodeURIComponent(url[1]);
   document.getElementById('url').value = url;

}

document.getElementById('sets').onchange = function(e)
{
   var setid = this.value;
   if(!setid)
      return;
   var uname = this.getAttribute("uname");
   if(!uname)
   {
      var option = select.getElementsByTagName('option')[0];
      select.innerHTML = '';
      option.innerHTML = "--Enter url first--";
      option.value = '';
      select.appendChild(option);

      return;
   }
   call_api_method("photosets.getPhotos", uname, show_set_path, {"photoset_id":setid, "extras":"date_taken,geo"});
   document.getElementById('path-container').innerHTML = 'Loading path...';
};

document.getElementById('deltas').onsubmit = function(e)
{
   var st = document.getElementById('sets');
   st.onchange.call(st);
   return false;
};

function map_url_to_nsid(o, uname)
{
   // console.log("Got nsid " + o.user.nsid + " for user " + uname + " known to flickr as " + o.user.username._content);
   usernamemap[uname].nsid = o.user.id;
   usernamemap[uname].realname = o.user.username._content;
   nsidmap[o.user.nsid] = uname;

   var select = document.getElementById('sets');
   var option = select.getElementsByTagName('option')[0];
   option.innerHTML = "Fetching Sets...";
   option.value = '';

   call_api_method("photosets.getList", uname, show_set_list, {"user_id":o.user.id});
}

function show_set_list(o, uname)
{
   var i=0;

   var select = document.getElementById('sets');
   select.setAttribute("uname", uname);
   select.innerHTML = '';
   var option = document.createElement("option");
   if(o.photosets.photoset.length > 0)
      option.innerHTML = "-- Select Set --";
   else
      option.innerHTML = "-- No Sets --";
   option.value = '';
   select.appendChild(option);

   var set = location.search.match(/sets=([^&;]+)/);
   if(set && set.length > 1)
      set = decodeURIComponent(set[1]);
   else
      set = '';

   usernamemap[uname].sets = {};
   for(i=0; i<o.photosets.photoset.length; i++)
   {
      usernamemap[uname].sets[o.photosets.photoset[i].id] = o.photosets.photoset[i].title._content;

      option = document.createElement("option");
      option.id="set-" + o.photosets.photoset[i].id;
      option.innerHTML = o.photosets.photoset[i].title._content;
      option.value = o.photosets.photoset[i].id;
      if(option.value == set)
         option.selected = true;
      select.appendChild(option);
   }

   if(o.photosets.photoset.length > 0)
      blink_widget(select);

   select.onchange.call(select);
}

function blink_widget(widget)
{
   var ctr=0;
   if(typeof(widget) == 'string')
      widget = [widget];

   var blinker = function()
   {
      if(ctr%2 == 0)
         for(var i=0; i<widget.length; i++)
            widget[i].style.backgroundColor='#ffffb6';
      else
         for(var i=0; i<widget.length; i++)
            widget[i].removeAttribute("style");

      if(ctr <= 5)
         setTimeout(blinker, 200);
      else if(ctr == 6)
         setTimeout(blinker, 1000);
      else
         widget = null;

      ctr++;
   }
   blinker();
}

function date_comparator(a, b)
{
   return ( a.datetaken < b.datetaken ? -1 : ( a.datetaken > b.datetaken ? 1 : 0 ) );
}

function show_set_path(o, uname)
{
   var setid = o.photoset.id;
   var photos = usernamemap[uname].sets[setid] = o.photoset.photo.sort(date_comparator);
   var i=0;

   var td = document.getElementById('tdelta');
   var sd = document.getElementById('sdelta');

   if(td.value)
      tdelta = parseInt(td.value*3600000);
   if(sd.value)
      sdelta = parseFloat(sd.value);

   document.getElementById('mates-container').innerHTML = 'Searching for nearby pics...';
   var path = document.getElementById('path-container');
   var ol = document.createElement('ol');
   ol.id="photos";
   path.innerHTML = '';
   path.appendChild(ol);

   var li;

   var latlonseen = {};

   document.getElementById('map').innerHTML = '';

   usernamemap[uname].sets[setid].usable_pics = 0;
   usernamemap[uname].sets[setid].mated_pics = 0;

   var points = [];
   var pics = [];

   // console.log("Got " + photos.length + " for " + uname);
   for(i=0; i<photos.length; i++)
   {
      // console.log("Photo " + photos[i].title + " has geo accuracy " + photos[i].accuracy);
      if(photos[i].accuracy == 0)
         continue;

      if(latlonseen[photos[i].latitude + ',' + photos[i].longitude])
         continue;
      latlonseen[photos[i].latitude + ',' + photos[i].longitude] = true;

      usernamemap[uname].sets[setid].usable_pics++;

      li = document.createElement("li");
      li.id="photo-" + photos[i].id;
      li.innerHTML = '<a href="http://www.flickr.com/photos/' + usernamemap[uname].nsid + '/' + photos[i].id + '>' + photos[i].title + '</a>';
      ol.appendChild(li);

      var d = new Date(photos[i].datetaken.replace(/-/g, '/'));
      var dmin = new Date(d.getTime()-tdelta);
      var dmax = new Date(d.getTime()+tdelta);

      call_api_method("photos.search", uname, add_mate_photos, {
                  "min_taken_date": ""+dmin.getFullYear()+"-"+(dmin.getMonth()+1)+"-"+dmin.getDate(),
                  "max_taken_date": ""+dmax.getFullYear()+"-"+(dmax.getMonth()+1)+"-"+dmax.getDate(),
                  "bbox": ""+(photos[i].longitude-sdelta)+","+(photos[i].latitude-sdelta)+","+(photos[i].longitude+sdelta)+","+(photos[i].latitude+sdelta),
                  "sort": "relevance",
                  "extras": "date_taken,owner_name,geo",
                  "per_page": images_per_page,
               }, {setid: setid, photoindex: i});

      points.push(new YGeoPoint(photos[i].latitude, photos[i].longitude));
      pics.push(photos[i]);
   }

   if(points.length == 0)
   {
      var str = "No images to plot\nYou need to geocode some of your photos first."
      document.getElementById('mates-container').innerHTML = str.replace(/\n/g, '<br>');
      alert(str);
      return;
   }

   map = createMap('map', points);

   for(i=0; i<points.length; i++)
   {
      var marker = new YMarker(points[i], markerimages[0]);
      var photo = pics[i];
      var markup = '<a href="' + photo.url + '"><img src="http://static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_s.jpg"><br>' + photo.title + '<br><a href="http://www.flickr.com/photos/' + usernamemap[uname].nsid + '/">' + uname + '</a>';
      if(marker.addAutoExpand)
         marker.addAutoExpand(markup)
      map.addOverlay(marker);
   }

   var set = usernamemap[uname].sets[setid];
   set.processed = true;
   if(!set.shown && set.mated_pics == set.usable_pics)
      show_mates(set);
}

function add_mate_photos(o, uname, params)
{
   var setid = params.setid;
   var photoindex = params.photoindex;

   var set = usernamemap[uname].sets[setid];

   var photo = set[photoindex];

   var str = 'Matching pics near ' + photo.title + '... ';
   document.getElementById('mates-container').innerHTML = str; 

   photo.mates = photo.mates || {};
   set.mates = set.mates || {};

   var opoint = new YGeoPoint(photo.latitude, photo.longitude);
   var otime = new Date(photo.datetaken.replace(/-/g, '/'));
   otime = otime.getTime();

   var i;

   var mates = [];

   for(i=0; i<o.photos.photo.length; i++)
   {
      var mate = o.photos.photo[i];

      // if this is our own pic - skip
      // console.log(usernamemap[uname].nsid + ' : ' + mate.owner);
      if(usernamemap[uname].nsid == mate.owner)
         continue;

      var ntime = new Date(mate.datetaken.replace(/-/g, '/'));
      t = parseInt((ntime.getTime() - otime)/1000);

      var npoint = new YGeoPoint(mate.latitude, mate.longitude);
      var distance = opoint.distance(npoint);
      var delta = Math.sqrt(Math.pow(t/4, 2) + Math.pow(distance.kilometers*1000, 2))/1000;

      mate.tdiff = t;
      mate.distance = distance;
      mate.point = npoint;
      mate.delta = delta;

      t = Math.abs(t);

      // no time difference - how cool is that?
      if(t == 0)
         mate.tdifftext = 'at exactly the same time';
      // 5 seconds is about how long it takes to click and for the preview to
      // clear off from most compacts
      else if(t < 5)
         mate.tdifftext = 'at almost the same time';
      // anything else, we want to tell the user about
      else
      {
         mate.tdifftext = '' + (t>=3600?parseInt(t/3600) + 'h':'') + (t%3600>=60 ? ((t>=3600 && t%3600/60<10?'0':'') + parseInt(t%3600/60) + (t>=3600?'':'m')) : '');
         if(t<600 && t%60)
            mate.tdifftext += (t%60<10?'0':'') + (t%60) + 's';
         else if(t<3600)
            mate.tdifftext += 'in';
      }

      var kmaccuracy = 1000;
      // if it's more than 10 km, we only care about 1 decimal place
      if(mate.distance.kilometers > 10)
         kmaccuracy = 10;
      mate.distance.kilometers = Math.round(mate.distance.kilometers*kmaccuracy)/kmaccuracy;

      mate.url = "http://www.flickr.com/photos/" + mate.owner + '/' + mate.id;
      mate.otitle = photo.title;
      mate.oid = photo.id;

      set.mates[mate.owner] = set.mates[mate.owner] || {"count": 0, "nsid": mate.owner, "name": mate.ownername, "photos": []};
      set.mates[mate.owner].count++;
      set.mates[mate.owner].photos.push(mate);

      document.getElementById('mates-container').innerHTML = str + set.mates[mate.owner].count;
   }

   set.mated_pics++;

   if(set.processed && set.mated_pics == set.usable_pics)
      show_mates(set);
}

function avgdelta_comparator(a, b)
{
   var a_norm_ct, b_norm_ct;
   if(a.count == 1 || b.count == 1)
   {
      a_norm_ct = a.count;
      b_norm_ct = b.count;
   }
   else if(a.count < 12 || b.count < 12)
   {
      a_norm_ct = parseInt(a.count/3)+1;
      b_norm_ct = parseInt(b.count/3)+1;
   }
   else if(a.count < 100 || b.count < 100)
   {
      a_norm_ct = parseInt(a.count/12)+1;
      b_norm_ct = parseInt(b.count/12)+1;
   }
   else
   {
      a_norm_ct = a.count;
      b_norm_ct = b.count;
   }

   a_norm_ct = a_norm_ct*10 / a.avg;
   b_norm_ct = b_norm_ct*10 / b.avg;

   return (a_norm_ct < b_norm_ct ? 1 : ( a_norm_ct > b_norm_ct ? -1 : 0 ));
}

function show_mates(set)
{
   set.shown=true;
   var str="<ul>";
   var lis = [];
   var i, mate;

   document.getElementById('mates-container').innerHTML = 'Sorting results...';

   var mates = set.mates;

   for(mate in mates)
   {
      mates[mate].photos = mates[mate].photos.sort(
         function(a, b) {
            return (
               a.delta < b.delta ? -1 : (
                  a.delta > b.delta ? 1 : 0
               )
            );

/*
            return (
               a.distance.kilometers < b.distance.kilometers ? -1 : (
                  a.distance.kilometers > b.distance.kilometers ? 1 : (
                     a.tdiff < b.tdiff ? -1 : (
                        a.tiff > b.tdiff ? 1 : 0
                     )
                  )
               )
            );
*/
         }
      );
      mates[mate].avgdelta = 0;
      var points = [];
      var s = '';

      var photos = [];
      var myphotosseen = {};
      var photosseen = {};
      for(i=0; i<mates[mate].photos.length; i++)
      {
         var photo = mates[mate].photos[i];

//         console.log("D(" + photo.ownername + ':' + photo.title + ", " + photo.otitle + "): " + photo.distance.kilometers + ', ' + photo.tdiff + ', ' + (Math.round(photo.delta*100)/100));

         if(photosseen['' + photo.latitude + ':' + photo.longitude])
            continue;
         photosseen['' + photo.latitude + ':' + photo.longitude] = true;

//         console.log('not seen this');

         if(myphotosseen[photo.oid])
            continue;
         myphotosseen[photo.oid] = true;

//         console.log('not already matched');

         photos.push(mates[mate].photos[i]);

         mates[mate].avgdelta += photo.delta;
         points.push(photo.point);

         s += '<li><a href="' + photo.url + '">' + photo.title + '</a> '
               + 'taken <em class="distance" title="'
               + (photo.distance.kilometers<1?(photo.distance.kilometers*1000)+'m':(photo.distance.kilometers + 'km')) + ', ' + photo.tdifftext
               + '">' + (Math.round(photo.delta*100)/100) + 'units</em> away from your picture '
               + '<q class="title">' + photo.otitle + '</q></li>';
      }

      mates[mate].photos = photos;

      mates[mate].avgdelta /= photos.length;
      //console.log("A(" + photo.ownername + "): " + photos.length + ', ' + (Math.round(mates[mate].avgdelta*100)/100));

      s = '<a href="http://flickr.com/photos/' + mates[mate].nsid + '/">' + mates[mate].name+ '</a>'
         + '<a href="?url=' + encodeURIComponent('http://flickr.com/photos/' + mates[mate].nsid + '/') + '"><img src="images/search.png" alt="Search this user\'s pics"></a>'
         + ' (' + photos.length + ' pics taken on avg <em class="distance">' + (Math.round(mates[mate].avgdelta*100)/100) + 'units</em> away from you)'
         + '<ul>' + s + '</ul>';

      lis.push({count: photos.length, avg: mates[mate].avgdelta, html: s, points: points, id: mate});
   }

   lis = lis.sort(avgdelta_comparator);

   var markerctr=1;
   for(i=0; i<lis.length && markerctr < markerimages.length; i++)
   {
      if(lis[i].count > 1)
      {
         str += '<li id="m' + markerctr + '">' + lis[i].html + '</li>';
         for(j=0; j<lis[i].points.length; j++)
         {
            var marker = new YMarker(lis[i].points[j], markerimages[markerctr]);
            var photo = mates[lis[i].id].photos[j];
            var markup = '<a href="' + photo.url + '">'
                        + '<img src="http://static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_s.jpg">'
                        + '<br>'
                        + photo.title + '<br>'
                        + '<a href="http://www.flickr.com/photos/' + mates[lis[i].id].nsid + '/">' + mates[lis[i].id].name + '</a>';
            if(marker.addAutoExpand)
               marker.addAutoExpand(markup)
            map.addOverlay(marker);
         }
         markerctr++;
      }
   }

   if(str.match(/<ul>$/))
      str += '<li id="mno">No travelmates found</li>';
   str += '</ul>';
   str += '<small>Note that units are measured in kilometres assuming that a person ambling along taking pictures moves at a metre every few seconds.  Mouseover the units for a picture to see what it means in distance and time.</small>';

   document.getElementById("mates-container").innerHTML = str;
}

function createMap(container, points)
{
   var map = new YMap(document.getElementById(container));
   map.setMapType(YAHOO_MAP_SAT);
   map.addZoomLong();
   map.drawZoomAndCenter(points[parseInt(points.length/2)], 4);

   return map;
}

function init()
{
}

