jQuery(function($) {
  
  if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item) {
    for (var i=0, thing; thing = this[i]; i++) {
      if (thing == item) return i;
    };
  };
  
  // Utilities
  $.extend({
    bind: function(func, scope) {
      return function() {
        return func.apply(scope, $.makeArray(arguments));
      }
    },
    delegate: function(rules) {
      return function(e) {
        var target = $(e.target), parent = null;
        for (var selector in rules) {
          if (target.is(selector) || ((parent = target.parents(selector)) && parent.length > 0)) {
            return rules[selector].apply(this, [parent || target].concat($.makeArray(arguments)));
          }
          parent = null;
        }
      }
    },
    template: function(template, data) {
      var result = template;
      
      for (var key in data) {
        var rx = new RegExp('{' + key + '}', 'g');
        result = result.replace(rx, data[key]);
      }
      
      return result;
    }
  });
  
  // main RouteMap constructor
  var RouteMap = function(element) {
    this.element = $(element);
    this.initialize();
  }
  
  $.extend (RouteMap.prototype, {
    initialize: function() {
      this._buildMarkup();
      this._hideInfo();
      
      this.map = new VEMap('map');
      this.route = HCalendar.discover(this.element.find('.hcalendar').get(0));
      
      var current = this._findCurrent();
      
      this.map.LoadMap(this._loc(current.geo.latitude, current.geo.longitude), 3);
      this.currentZoom = 0;
      
      this.bounds = RouteMap.BOUNDS;
      this.previousPos = this.map.GetCenter();
      
      this.map.SetZoomLevel(4);
      
      this.map.HideDashboard();
      this.control = this._mapControl();
      this.map.AddControl(this.control);
      
      this.control.style.zIndex = 1;
      
      this._placeMarkers();
      
      this.map.AttachEvent('onclick', $.bind(function(e) {
        if (e.elementID) {
          for (var i=0, stop; stop = this.route[i]; i++) {
            if (e.elementID.indexOf(stop.marker.GetID()) == 0) {
              this.select(stop);
              return false;
            }
          }
        }
      }, this));
      
      this._drawRoute();
      
      this._selectCurrent();
      this.map.SetCenter(this.current.marker.GetPoints()[0]);
      this._restrictDrag();
    },
    select: function(stop) {
      if (stop != this.current) {
        if (this.current) {
          this.current.marker.SetCustomIcon(RouteMap.ICON.standard);
          this.infoArea.empty();
        } 
      
        this.current = stop;
        stop.marker.SetCustomIcon(RouteMap.ICON.selected);
        this.infoArea.append(this.current.parentElement);
        
        this.map.PanToLatLong(this.current.marker.GetPoints()[0]);
      }
    },
    nextStop: function() {
      var next = this.route[this._stopIndex(this.current) + 1];
      if (next) this.select(next);
      return false;
    },
    previousStop: function() {
      var prev = this.route[this._stopIndex(this.current) - 1];
      if (prev) this.select(prev);
      return false;
    },
    reset: function() {
      this.map.SetZoomLevel(4);
      this.map.PanToLatLong(this.current.marker.GetPoints()[0]);
    },
    _stopIndex: function(stop) {
      for (var i=0, r; r = this.route[i]; i++) 
        if (r == stop) return i;
      return null;
    },
    _buildMarkup: function() {
      this.element.prepend(RouteMap.MARKUP.base);
      this.element.find('.nav').append(RouteMap.MARKUP.legend);
      this.element.find('.nav').append(RouteMap.MARKUP.nav);
                           
      this.element.find('#coming-up').appendTo(this.element.find('.route-map .route-info'));
      
      this.infoArea = this.element.find('.route-info .stop');
      
      this.element.find('a.next').click($.bind(this.nextStop, this));
      this.element.find('a.previous').click($.bind(this.previousStop, this));
    },
    _selectCurrent: function() {
      this.select(this._findCurrent());
    },
    _findCurrent: function() {
      if (this._current) return this._current;
      
      for (var i=0, stop; stop = this.route[i]; i++) {
        if ($(stop.parentElement).hasClass('current')) {
          return this._current = stop;
        }
      }
      
      return this._current = this.route[0];
    },
    _mapControl: function() {
      var base = document.createElement('div');
      base.innerHTML = RouteMap.MARKUP.controls;
      var controls = base.firstChild;
      
      $(controls).find('.zoom-in').click($.bind(function() {
        this.map.ZoomIn();
        return false;
      }, this));
      
      $(controls).find('.zoom-out').click($.bind(function() {
        this.map.ZoomOut();
        return false;
      }, this));
      
      $(controls).find('.reset').click($.bind(function() {
        this.reset();
        return false;
      }, this));
      
      return controls;
    },
    _hideInfo:function() {
      this.element.find('.hcalendar').hide();
    },
    _loc: function(lat, lng) {
      if (arguments.length == 1) {
        if (arguments[0] instanceof VELatLong) 
          return arguments[0];

        var coords = arguments[0];
        lat = coords[0];
        lng = coords[1];
      }

      return new VELatLong(Number(lat), Number(lng));
    },
    _placeMarkers: function() {
      for (var i=0, stop; stop = this.route[i]; i++) {
        stop.marker = new VEShape(VEShapeType.Pushpin, this._loc(stop.geo.latitude, stop.geo.longitude));
        stop.marker.SetCustomIcon(RouteMap.ICON.standard);
        
        this.map.AddShape(stop.marker);
      }
    },
    _restrictDrag: function() {
      this.map.AttachEvent('onendpan', $.bind(function() {
        if (!this._withinBounds(this.map.GetCenter())) {
          this.map.SetCenter(this.previousPos);
        } else {
          this.previousPos = this.map.GetCenter();
        }
      }, this));
      
      this.map.AttachEvent('onstartzoom', $.bind(function() {
        this.previousZoom = this.map.GetZoomLevel();
      }, this));
      
      this.map.AttachEvent('onendzoom', $.bind(function() {
        if (this.map.GetZoomLevel() < 3) this.map.SetZoomLevel(this.previousZoom);
      }, this));
    },
    _withinBounds: function(point) {
      return  (point.Latitude <= this.bounds.TopLeftLatLong.Latitude &&
               point.Longitude >= this.bounds.TopLeftLatLong.Longitude &&
               point.Latitude >= this.bounds.BottomRightLatLong.Latitude &&
               point.Longitude <= this.bounds.BottomRightLatLong.Longitude);
    },
    _drawRoute: function() {
      for (var i=0, from; from = this.route[i]; i++) {
        var to = this.route[i+1];
        
        if (to) {
          var line = new VEShape(VEShapeType.Polyline, [
            from.marker.GetPoints()[0],
            to.marker.GetPoints()[0]
          ]);
          
          line.SetLineColor(to.categoryList ? RouteMap.ROUTE.planned : RouteMap.ROUTE.actual);
          line.SetCustomIcon('<div></div>');
          line.SetLineWidth(5);
          
          this.map.AddShape(line);
        }
      }
    }
  });
  
  RouteMap.icon = function(cls) {
    return '<div class="pin ' + cls + '"></div>';
  }
  
  
  // Config and constants
  $.extend(RouteMap, {
    
    BOUNDS: new VELatLongRectangle(
      new VELatLong(50.736455137010665, -128.408203125),
      new VELatLong(20.550508894195637, -61.875)
    ),
    ICON: {
      standard: RouteMap.icon('standard'),
      selected: RouteMap.icon('selected')
    },
    ROUTE: {
      planned: new VEColor(102, 102, 102, 0.75),
      actual: new VEColor(255, 85, 0, 0.75)
    },
    MARKUP: {
      base:     '<div class="route-map"><div class="map-nav"><div class="map" id="map"></div><div class="nav"></div>' +
                '</div><div class="route-info"><div class="pin"></div><div class="stop"></div></div>',
      legend:   '<div class="legend"><span>Travelled route:</span><br /><span>Planned route:</span></div>',
      nav:      '<div class="prev-next"><a class="previous" href="#">Previous Stop</a>' + 
                '<a class="next" href="#">Next Stop</a></div>',
      controls: '<div class="route-map-controls">' + 
                '<a href="#" class="button reset"><span>Reset</span></a>' +
                '<a href="#" class="button zoom-in"><span>In</span></a>' +
                '<a href="#" class="button zoom-out"><span>Out</span></a>' +
                '</div>'
    }
  });
  
  // light interface to flickr search (global)
  Flickr = {
    searchURL: 'http://api.flickr.com/services/rest/?method=flickr.photos.search' +
               '&api_key={api_key}' + 
               '&user_id={user_id}&tags={tags}&format=json&jsoncallback=?',
    imageURL:  'http://farm{farm_id}.static.flickr.com/{server_id}/{id}_{secret}{type}.jpg',
    pageURL:   'http://www.flickr.com/photos/{user_id}/{photo_id}',
               
    search: function(user, tags, callback) {
      $.getJSON($.template(this.searchURL, { user_id: user, tags: tags, api_key: this.apiKey }), callback);
    },
    getImageURL: function(photo, type) {
      return $.template(this.imageURL, {
        farm_id: photo.farm,
        id: photo.id,
        server_id: photo.server,
        secret: photo.secret,
        type: type ? '_' + type : ''
      });
    },
    getPageURL: function(photo) {
      return $.template(this.pageURL, {
        user_id: photo.owner,
        photo_id: photo.id
      });
    }
  };
  
  
  Flickr.Popup = {
    photoTemplate: '<div><a href="#" class="prev">Previous</a>' +
                   '<a href="#" class="next">Next</a> <div id="holder"><img src="{image_url}" alt="{title}" /></div>' +
                   '<h3>{title}</h3><p class="info"><a href="{page_url}">See photo on Flickr.com <img src="/worldservice/images/f/index_arrow.gif" alt="" /></a>' +
                   'Image {current_image} of {num_images}</p><a href="#" class="close">Close</a></div>',
                   
    noPhotoTemplate: '<div class="no-photos"><p>Sorry, there are no photos for this stop yet.<br />' + 
                     'Please check our <a href="http://www.flickr.com/photos/bbcworldservice/collections/72157606867869622/">Flickr collection</a> for more</p><a href="#" class="close">Close</a></div>',
    photos: null,
    current: 0,
    
    open : function(url) {
      this._createHTML();
      this.overlay.show();
      this._getPhotos(url);
      return false;
    },

    close : function() {
      this.content.hide();
      this.overlay.hide();
      return false;
    },
    
    next: function() {
      this.showPhoto(this.current + 1);
      return false;
    },
    
    previous: function() {
      this.showPhoto(this.current - 1);
      return false;
    },
    
    showPhoto: function(num) {
      var photo = this.photos[num];
      if (photo) {
        this.current = num;
        this.content.empty();
        this.content.append($.template(this.photoTemplate, {
          title: photo.title,
          image_url: Flickr.getImageURL(photo),
          page_url: Flickr.getPageURL(photo),
          current_image: this.current + 1,
          num_images: this.photos.length
        }));
        
        if (this.current == 0) this.content.find('a.prev').hide();
        if (this.current == this.photos.length - 1) this.content.find('a.next').hide();
      }
    },
    
    _getPhotos: function(url) {
      var user = url.match(/photos\/([^\/]+)/)[1]
      var tag = url.match(/tags\/([^\/]+)/)[1]
      this.content.hide();
      
      Flickr.search(user, tag, $.bind(function(data) {
        this.photos = data.photos.photo;
        if (this.photos && this.photos.length > 0) {
          this.showPhoto(0);
        } else {
          this.content.empty().append(this.noPhotoTemplate);
        }
        this.content.show();
      }, this))
    },

    _createHTML : function() {
      if (!this.overlay) {;
        var body = $(document.body);
        this.overlay = body.append('<div id="lb"><div id="lbbg"></div><div id="pane"></div></div>').find('#lb');
        this.bg = this.overlay.find('#bg');
        this.content = body.find('#pane');
        this._setOverlayHeight();
        this.overlay.hide();
        
        this.content.click($.delegate({
          'a.close' : $.bind(this.close, this),
          'a.next'  : $.bind(this.next, this),
          'a.prev'  : $.bind(this.previous, this)
        }));
      }
    },

    _setOverlayHeight : function() {
      var documentHeight = $(document.body).outerHeight();
      this.overlay.get(0).style.height = (documentHeight + 40) + 'px';
    }

  };
  
    // fix odd IE glitch in rendering
    if ($.browser.msie) {
      $('a.flink').after('&nbsp;&nbsp;&nbsp;&nbsp;');
    }
  
    // Fire up the route map if VE is supported
    if (_routeMapSupported) {
      var routeMap = new RouteMap('#tour');
    
      // attach the flickr lightbox loader
      $('#tour').click($.delegate({
        'a.flink': function(el) {
          Flickr.Popup.open(el.attr('href'));
          return false;
        }
      }));
    }
    
    
	var WorldService={
		o:{author:'BBC World Service',version:'0.0.1'},
		wsCorners:function(){return Add.corners();},
		wsQuotes:function(){return Add.quotes();}
	}
	var Add={
			quotes:function(){
				$('<span class="open-quote"><span>&#8220;</span></span>').prependTo('blockquote > .body');
				$('<span class="close-quote"><span>&#8221;</span></span>').appendTo('blockquote > .body');
			},
			corners:function(){
				$('#content > .g-block').children().wrap('<div class="jq-c-wrap"><div class="jq-c-content"></div></div>');
				$('#content > .g-block').addClass("jq-c");
			}
	};
	$.ws=WorldService;
	
    $.fn.wsFlash=function(o){return this.each(function(){new $ws(this,o);});};$.ws.wsFlash=function(e,o){this.o=$.extend({e:e,version:"",target:"",width:100,height:100,bgcolor:"",path:"",base:"",align:"",allowFullScreen:false,flashvars:"",loop:false,menu:false,play:false,quality:"autohigh",scale:false,scriptAccess:"always",swLiveConnect:true},o||{});this.embed();};var $ws=$.ws.wsFlash;$ws.fn=$ws.prototype={wsFlash:"0.0.1"};$ws.fn.extend=$.extend;$ws.fn.extend({embed:function(){var o=this.o;if(!$(o.e)){return false;}if(this.detect()){for(var i=0;i<o.length;i++){if(o[i].match(/width|height/)){o[i]=parseFloat(o[i]);}if(o[i].match(/path/)){o[i]=o[i].toString();var q=o[i].split("?")[1];if(q){o["flashvars"]=q;}}}if(o.path){$(o.e).html('<!--[if !IE]> --><object type="application/x-shockwave-flash" data="'+o.path+'" width="'+o.width+'" height="'+o.height+'"><!-- <![endif]--><!--[if IE]><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version='+o.target+'" width="'+o.width+'" height="'+o.height+'"><param name="movie" value="'+o.path+'" /><!--><!--ws--><param name="loop" value="'+o.loop+'" /><param name="menu" value="'+o.menu+'" /><param name="quality" value="'+o.quality+'" /><param name="wmode" value="'+o.wmode+'" /><param name="flashVars" value="'+o.flashvars+'" /><param name="base" value="'+o.base+'"><param name="AllowScriptAccess" value="'+o.scriptAccess+'"><param name="scale" value="'+o.scale+'"></object><!-- <![endif]-->');}return true;}return false;},detect:function(){var o=this.o,t=o.target.split(","),v=o.version=this.version().match(/\d+/g);if(v.length){for(var i=0;i<t.length;i++){v[i]=parseInt(v[i]||0);t[i]=parseInt(t[i]||0);if(v[i]<t[i]){return false;}if(v[i]>t[i]){return true;}}}return true;},version:function(){try{return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version").replace(/\D+/g,",").match(/^,?(.+),?$/)[1];}catch(e){try{if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){return(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g,",").match(/^,?(.+),?$/)[1];}}catch(e){}}return"0,0,0";}});

});

var _routeMapSupported = !$.browser.opera && !/KDE/.test(navigator.vendor) && 
                         !($.browser.safari && parseFloat($.browser.version) < 2) && 
                         !($.browser.msie && parseFloat($.browser.version) < 6);

if (_routeMapSupported) {
  jQuery('head').append('<style type="text/css">.hcalendar { display:none; }</style>');
}
