if (!window['google']) {
window['google'] = {};
}
if (!window['google']['loader']) {
window['google']['loader'] = {};
google.loader.ServiceBase = 'http://www.google.com/uds';
google.loader.GoogleApisBase = 'http://ajax.googleapis.com/ajax';
google.loader.ApiKey = 'notsupplied';
google.loader.KeyVerified = true;
google.loader.LoadFailure = false;
google.loader.Secure = false;
google.loader.ClientLocation = null;
google.loader.AdditionalParams = '';
(function() {var e=true,f=null,h=false,i=encodeURIComponent,j=window,k=google,m=undefined,n=document;function o(a,b){return a.load=b}var p="push",q="length",r="prototype",s="setTimeout",t="replace",v="charAt",w="loader",x="substring",A="ServiceBase",B="name",C="getTime",D="toLowerCase";function E(a){if(a in F)return F[a];return F[a]=navigator.userAgent[D]().indexOf(a)!=-1}var F={};function G(a,b){var c=function(){};c.prototype=b[r];a.K=b[r];a.prototype=new c}
function H(a,b){var c=a.w||[];c=c.concat(Array[r].slice.call(arguments,2));if(typeof a.r!="undefined")b=a.r;if(typeof a.q!="undefined")a=a.q;var d=function(){var g=c.concat(Array[r].slice.call(arguments));return a.apply(b,g)};d.w=c;d.r=b;d.q=a;return d}function I(a){var b=new Error(a);b.toString=function(){return this.message};return b}function J(a,b){for(var c=a.split(/\./),d=j,g=0;g<c[q]-1;g++){d[c[g]]||(d[c[g]]={});d=d[c[g]]}d[c[c[q]-1]]=b}function K(a,b,c){a[b]=c}if(!L)var L=J;if(!aa)var aa=K;k[w].s={};L("google.loader.callbacks",k[w].s);var M={},N={};k[w].eval={};L("google.loader.eval",k[w].eval);
o(k,function(a,b,c){var d=M[":"+a];if(d){if(c&&!c.language&&c.locale)c.language=c.locale;if(c&&typeof c.callback=="string"){var g=c.callback;if(g.match(/^[[\]A-Za-z0-9._]+$/)){g=j.eval(g);c.callback=g}}var l=c&&c.callback!=f;if(l&&!d.p(b))throw I("Module: '"+a+"' must be loaded before DOM onLoad!");else if(l)d.k(b,c)?j[s](c.callback,0):d.load(b,c);else d.k(b,c)||d.load(b,c)}else throw I("Module: '"+a+"' not found!");});L("google.load",k.load);k.J=function(a,b){b?ba(a):O(j,"load",a)};
L("google.setOnLoadCallback",k.J);function O(a,b,c){if(a.addEventListener)a.addEventListener(b,c,h);else if(a.attachEvent)a.attachEvent("on"+b,c);else{var d=a["on"+b];a["on"+b]=d!=f?ca([c,d]):c}}function ca(a){return function(){for(var b=0;b<a[q];b++)a[b]()}}var P=[];
function ba(a){if(P[q]==0){O(j,"load",R);if(!E("msie")&&!(E("safari")||E("konqueror"))&&E("mozilla")||j.opera)j.addEventListener("DOMContentLoaded",R,h);else if(E("msie"))n.write("<script defer onreadystatechange='google.loader.domReady()' src=//:><\/script>");else(E("safari")||E("konqueror"))&&j[s](S,10)}P[p](a)}k[w].D=function(){var a=j.event.srcElement;if(a.readyState=="complete"){a.onreadystatechange=f;a.parentNode.removeChild(a);R()}};L("google.loader.domReady",k[w].D);var da={loaded:e,complete:e};
function S(){if(da[n.readyState])R();else P[q]>0&&j[s](S,10)}function R(){for(var a=0;a<P[q];a++)P[a]();P.length=0}
k[w].d=function(a,b,c){if(c){var d;if(a=="script"){d=n.createElement("script");d.type="text/javascript";d.src=b}else if(a=="css"){d=n.createElement("link");d.type="text/css";d.href=b;d.rel="stylesheet"}var g=n.getElementsByTagName("head")[0];g||(g=n.body.parentNode.appendChild(n.createElement("head")));g.appendChild(d)}else if(a=="script")n.write('<script src="'+b+'" type="text/javascript"><\/script>');else a=="css"&&n.write('<link href="'+b+'" type="text/css" rel="stylesheet"></link>')};
L("google.loader.writeLoadTag",k[w].d);k[w].G=function(a){N=a};L("google.loader.rfm",k[w].G);k[w].I=function(a){for(var b in a)if(typeof b=="string"&&b&&b[v](0)==":"&&!M[b])M[b]=new T(b[x](1),a[b])};L("google.loader.rpl",k[w].I);k[w].H=function(a){if((a=a.specs)&&a[q])for(var b=0;b<a[q];++b){var c=a[b];if(typeof c=="string")M[":"+c]=new U(c);else{var d=new V(c[B],c.baseSpec,c.customSpecs);M[":"+d[B]]=d}}};L("google.loader.rm",k[w].H);k[w].loaded=function(a){M[":"+a.module].i(a)};
L("google.loader.loaded",k[w].loaded);J("google_exportSymbol",J);J("google_exportProperty",K);function U(a){this.a=a;this.n={};this.b={};this.j=e;this.c=-1}
U[r].f=function(a,b){var c="";if(b!=m){if(b.language!=m)c+="&hl="+i(b.language);if(b.nocss!=m)c+="&output="+i("nocss="+b.nocss);if(b.nooldnames!=m)c+="&nooldnames="+i(b.nooldnames);if(b.packages!=m)c+="&packages="+i(b.packages);if(b.callback!=f)c+="&async=2";if(b.other_params!=m)c+="&"+b.other_params}if(!this.j){if(k[this.a]&&k[this.a].JSHash)c+="&sig="+i(k[this.a].JSHash);var d=[];for(var g in this.n)g[v](0)==":"&&d[p](g[x](1));for(g in this.b)g[v](0)==":"&&d[p](g[x](1));c+="&have="+i(d.join(","))}return k[w][A]+
"/?file="+this.a+"&v="+a+k[w].AdditionalParams+c};U[r].u=function(a){var b=f;if(a)b=a.packages;var c=f;if(b)if(typeof b=="string")c=[a.packages];else if(b[q]){c=[];for(var d=0;d<b[q];d++)typeof b[d]=="string"&&c[p](b[d][t](/^\s*|\s*$/,"")[D]())}c||(c=["default"]);var g=[];for(d=0;d<c[q];d++)this.n[":"+c[d]]||g[p](c[d]);return g};
o(U[r],function(a,b){var c=this.u(b),d=b&&b.callback!=f;if(d)var g=new W(b.callback);for(var l=[],u=c[q]-1;u>=0;u--){var y=c[u];d&&g.z(y);if(this.b[":"+y]){c.splice(u,1);d&&this.b[":"+y][p](g)}else l[p](y)}if(c[q]){if(b&&b.packages)b.packages=c.sort().join(",");if(!b&&N[":"+this.a]!=f&&N[":"+this.a].versions[":"+a]!=f&&!k[w].AdditionalParams&&this.j){var z=N[":"+this.a];k[this.a]=k[this.a]||{};for(var Q in z.properties)if(Q&&Q[v](0)==":")k[this.a][Q[x](1)]=z.properties[Q];k[w].d("script",k[w][A]+
z.path+z.js,d);z.css&&k[w].d("css",k[w][A]+z.path+z.css,d)}else if(!b||!b.autoloaded)k[w].d("script",this.f(a,b),d);if(this.j){this.j=h;this.c=(new Date)[C]();if(this.c%100!=1)this.c=-1}for(u=0;u<l[q];u++){y=l[u];this.b[":"+y]=[];d&&this.b[":"+y][p](g)}}});
U[r].i=function(a){if(this.c!=-1){X("al_"+this.a,"jl."+((new Date)[C]()-this.c),e);this.c=-1}for(var b=0;b<a.components[q];b++){this.n[":"+a.components[b]]=e;var c=this.b[":"+a.components[b]];if(c){for(var d=0;d<c[q];d++)c[d].C(a.components[b]);delete this.b[":"+a.components[b]]}}X("hl",this.a)};U[r].k=function(a,b){return this.u(b)[q]==0};U[r].p=function(){return e};function W(a){this.B=a;this.l={};this.o=0}W[r].z=function(a){this.o++;this.l[":"+a]=e};
W[r].C=function(a){if(this.l[":"+a]){this.l[":"+a]=h;this.o--;this.o==0&&j[s](this.B,0)}};function V(a,b,c){this.name=a;this.A=b;this.m=c;this.t=this.g=h;this.h=[];k[w].s[this[B]]=H(this.i,this)}G(V,U);o(V[r],function(a,b){var c=b&&b.callback!=f;if(c){this.h[p](b.callback);b.callback="google.loader.callbacks."+this[B]}else this.g=e;if(!b||!b.autoloaded)k[w].d("script",this.f(a,b),c);X("el",this[B])});V[r].k=function(a,b){return b&&b.callback!=f?this.t:this.g};V[r].i=function(){this.t=e;for(var a=0;a<this.h[q];a++)j[s](this.h[a],0);this.h=[]};
var Y=function(a,b){return a.string?i(a.string)+"="+i(b):a.regex?b[t](/(^.*$)/,a.regex):""};V[r].f=function(a,b){return this.F(this.v(a),a,b)};
V[r].F=function(a,b,c){var d="";if(a.key)d+="&"+Y(a.key,k[w].ApiKey);if(a.version)d+="&"+Y(a.version,b);var g=k[w].Secure&&a.ssl?a.ssl:a.uri;if(c!=f)for(var l in c)if(a.params[l])d+="&"+Y(a.params[l],c[l]);else if(l=="other_params")d+="&"+c[l];else if(l=="base_domain")g="http://"+c[l]+a.uri[x](a.uri.indexOf("/",7));k[this[B]]={};if(g.indexOf("?")==-1&&d)d="?"+d[x](1);return g+d};V[r].p=function(a){return this.v(a).deferred};
V[r].v=function(a){if(this.m)for(var b=0;b<this.m[q];++b){var c=this.m[b];if((new RegExp(c.pattern)).test(a))return c}return this.A};function T(a,b){this.a=a;this.e=b;this.g=h}G(T,U);o(T[r],function(a,b){this.g=e;k[w].d("script",this.f(a,b),h)});T[r].k=function(){return this.g};T[r].i=function(){};T[r].f=function(a,b){if(!this.e.versions[":"+a]){if(this.e.aliases){var c=this.e.aliases[":"+a];if(c)a=c}if(!this.e.versions[":"+a])throw I("Module: '"+this.a+"' with version '"+a+"' not found!");}var d=k[w].GoogleApisBase+"/libs/"+this.a+"/"+a+"/"+this.e.versions[":"+a][b&&b.uncompressed?"uncompressed":"compressed"];X("el",this.a);return d};
T[r].p=function(){return h};var ea=h,Z=[],fa=(new Date)[C](),X=function(a,b,c){if(!ea){O(j,"unload",ga);ea=e}if(c){if(!k[w].Secure&&(!k[w].Options||k[w].Options.csi===h)){a=a[D]()[t](/[^a-z0-9_.]+/g,"_");b=b[D]()[t](/[^a-z0-9_.]+/g,"_");var d="http://csi.gstatic.com/csi?s=uds&v=2&action="+i(a)+"&it="+i(b);j[s](H($,f,d),10000)}}else{Z[p]("r"+Z[q]+"="+i(a+(b?"|"+b:"")));j[s](ga,Z[q]>5?0:15000)}},ga=function(){if(Z[q]){$(k[w][A]+"/stats?"+Z.join("&")+"&nc="+(new Date)[C]()+"_"+((new Date)[C]()-fa));Z.length=0}},$=function(a){var b=
new Image,c=ha++;ia[c]=b;b.onload=b.onerror=function(){delete ia[c]};b.src=a;b=f},ia={},ha=0;J("google.loader.recordStat",X);J("google.loader.createImageForLogging",$);

}) ();google.loader.rm({"specs":[{"name":"books","baseSpec":{"uri":"http://books.google.com/books/api.js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"}}}},"feeds",{"name":"friendconnect","baseSpec":{"uri":"http://www.google.com/friendconnect/script/friendconnect.js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":false,"params":{}}},"spreadsheets","gdata","visualization",{"name":"sharing","baseSpec":{"uri":"http://www.google.com/s2/sharing/js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":false,"params":{"language":{"string":"hl"}}}},"search",{"name":"maps","baseSpec":{"uri":"http://maps.google.com/maps?file\u003dgoogleapi","ssl":"https://maps-api-ssl.google.com/maps?file\u003dgoogleapi","key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"regex":"callback\u003d$1\u0026async\u003d2"},"language":{"string":"hl"}}},"customSpecs":[{"uri":"http://maps.google.com/maps/api/js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"}},"pattern":"^(3|3..*)$"}]},{"name":"annotations_v2","baseSpec":{"uri":"http://www.google.com/uds?file\u003dannotations","ssl":"https://www.google.com/uds?file\u003dannotations","key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"},"country":{"string":"gl"}}}},"language","earth",{"name":"annotations","baseSpec":{"uri":"http://www.google.com/reviews/scripts/annotations_bootstrap.js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"},"country":{"string":"gl"}}}},"ads","elements"]});
google.loader.rfm({":feeds":{"versions":{":1":"1",":1.0":"1"},"path":"/api/feeds/1.0/8e09eed7fc0dd59c80503ea502548a85/","js":"default+en.I.js","css":"default.css","properties":{":JSHash":"8e09eed7fc0dd59c80503ea502548a85",":Version":"1.0"}},":search":{"versions":{":1":"1",":1.0":"1"},"path":"/api/search/1.0/d85483bcfbc461611f2cc9e086379764/","js":"default+en.I.js","css":"default.css","properties":{":JSHash":"d85483bcfbc461611f2cc9e086379764",":NoOldNames":false,":Version":"1.0"}},":language":{"versions":{":1":"1",":1.0":"1"},"path":"/api/language/1.0/29395d8ca8eacf88fb7b105f17c65fa5/","js":"default+en.I.js","properties":{":JSHash":"29395d8ca8eacf88fb7b105f17c65fa5",":Version":"1.0"}},":earth":{"versions":{":1":"1",":1.0":"1"},"path":"/api/earth/1.0/56ce34c6d009ea6795ba3ac23670c3ee/","js":"default.I.js","properties":{":JSHash":"56ce34c6d009ea6795ba3ac23670c3ee",":Version":"1.0"}},":annotations":{"versions":{":1":"1",":1.0":"1"},"path":"/api/annotations/1.0/98b5d27a5a3979daa3918ef6ff6e3bb4/","js":"default+en.I.js","properties":{":JSHash":"98b5d27a5a3979daa3918ef6ff6e3bb4",":Version":"1.0"}},":ads":{"versions":{":1":"1",":1.0":"1"},"path":"/api/ads/1.0/aee805412754800f4b39b137da2474f1/","js":"default.I.js","properties":{":JSHash":"aee805412754800f4b39b137da2474f1",":Version":"1.0"}}});
google.loader.rpl({":scriptaculous":{"versions":{":1.8.2":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"},":1.8.1":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"}},"aliases":{":1.8":"1.8.2",":1":"1.8.2"}},":yui":{"versions":{":2.6.0":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":2.7.0":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"}},"aliases":{":2":"2.7.0",":2.7":"2.7.0",":2.6":"2.6.0"}},":swfobject":{"versions":{":2.1":{"uncompressed":"swfobject_src.js","compressed":"swfobject.js"},":2.2":{"uncompressed":"swfobject_src.js","compressed":"swfobject.js"}},"aliases":{":2":"2.2"}},":ext-core":{"versions":{":3.0.0":{"uncompressed":"ext-core-debug.js","compressed":"ext-core.js"}},"aliases":{":3":"3.0.0",":3.0":"3.0.0"}},":mootools":{"versions":{":1.2.3":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.1":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.2":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.11":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"}},"aliases":{":1":"1.11"}},":jqueryui":{"versions":{":1.7.2":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.6.0":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.0":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.1":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.5.3":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.5.2":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"}},"aliases":{":1.7":"1.7.2",":1":"1.7.2",":1.6":"1.6.0",":1.5":"1.5.3"}},":prototype":{"versions":{":1.6.0.2":{"uncompressed":"prototype.js","compressed":"prototype.js"},":1.6.0.3":{"uncompressed":"prototype.js","compressed":"prototype.js"}},"aliases":{":1":"1.6.0.3",":1.6":"1.6.0.3"}},":jquery":{"versions":{":1.2.3":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.3.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.3.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.3.2":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.2.6":{"uncompressed":"jquery.js","compressed":"jquery.min.js"}},"aliases":{":1":"1.3.2",":1.3":"1.3.2",":1.2":"1.2.6"}},":dojo":{"versions":{":1.2.3":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.3.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.1.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.3.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.3.2":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.2.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"}},"aliases":{":1":"1.3.2",":1.3":"1.3.2",":1.2":"1.2.3",":1.1":"1.1.1"}}});
}
/**
 * Copyright (c) 2008 Google Inc.
 *
 * You are free to copy and use this sample.
 * License can be found here: http://code.google.com/apis/ajaxsearch/faq/#license
*/

/**
 * @fileoverview A slideshow control based on the AJAX Feed API.
 * @author dcollison@google.com (Derek Collison)
 */

/**
 * GFslideshow
 * @param {String} photoFeed The feed URL.
 * @param {String|Object} container Either the id string or the element itself.
 * @param {Object} options Options map.
 * @constructor
 */

function GFslideShow(photoFeed, container, options) {
  this.feedUrl = null;
  this.directEntries = null;
  if (typeof photoFeed == 'string') {
    this.feedUrl = photoFeed;
  } else if (photoFeed && photoFeed.length && photoFeed.length > 1) {
    this.directEntries = photoFeed;
  } else {
    throw "invalid argument: photoFeed";
  }
  if (typeof container == "string") {
    container = document.getElementById(container);
  }
  this.container = container;
  this.parseOptions(options);
  this.setup();
}

// Thumbnail size preferences.

GFslideShow.THUMBNAILS_SMALL = "small";
GFslideShow.THUMBNAILS_MEDIUM = "medium";
GFslideShow.THUMBNAILS_LARGE = "large";

// Thumbnail tag names and namespaces.

// MediaRSS.
GFslideShow.MRSS_THUMBNAIL_TAG = "thumbnail";
GFslideShow.MRSS_THUMBNAIL_NS  = "http://search.yahoo.com/mrss/";

// iTunes.
GFslideShow.ITMS_THUMBNAIL_TAG = "coverArt";
GFslideShow.ITMS_THUMBNAIL_NS  = "http://phobos.apple.com/rss/1.0/modules/itms/";

// MediaRSS is default.
GFslideShow.DEFAULT_THUMBNAIL_TAG = GFslideShow.MRSS_THUMBNAIL_TAG;
GFslideShow.DEFAULT_THUMBNAIL_NS  = GFslideShow.MRSS_THUMBNAIL_NS;

// Default display timings, all in milliseconds.
GFslideShow.DEFAULT_DISPLAY_TIME = 3000;
GFslideShow.DEFAULT_TRANSISTION_TIME = 1000;
GFslideShow.DEFAULT_TRANSISTION_STEP = 40;

GFslideShow.DEFAULT_PAUSE_PNG = google.loader.ServiceBase +
                                "/solutions/slideshow/pause.png";
GFslideShow.DEFAULT_PLAY_PNG = google.loader.ServiceBase +
                               "/solutions/slideshow/play.png";

// Full Control Setting
GFslideShow.FC_PAUSE_PNG = {
  small : google.loader.ServiceBase + "/solutions/slideshow/btn_pause_small.png",
  big   : google.loader.ServiceBase + "/solutions/slideshow/btn_pause.png"
};
GFslideShow.FC_PLAY_PNG = {
  small : google.loader.ServiceBase + "/solutions/slideshow/btn_play_small.png",
  big   : google.loader.ServiceBase + "/solutions/slideshow/btn_play.png"
};
GFslideShow.FC_PREV_PNG = {
  small : google.loader.ServiceBase + "/solutions/slideshow/btn_prev_small.png",
  big   : google.loader.ServiceBase + "/solutions/slideshow/btn_prev.png"
};
GFslideShow.FC_NEXT_PNG = {
  small : google.loader.ServiceBase + "/solutions/slideshow/btn_next_small.png",
  big   : google.loader.ServiceBase + "/solutions/slideshow/btn_next.png"
};

GFslideShow.DEFAULT_FC_FADEOUT_TIME = 5000;
GFslideShow.DEFAULT_FC_OPACITY = 0.65;

/**
 * Setup default option map and apply overrides from constructor.
 * @param {Object} options Options map.
 * @private
 */
GFslideShow.prototype.parseOptions = function(options) {
  var maxEntries;
  if (google != undefined && google.feeds != undefined) {
    maxEntries = google.feeds.Feed.MAX_ENTRIES;
  } else {
    maxEntries = 20;
  }
  // Default Options
  this.options = {
    numResults : maxEntries,
    scaleImages : false,
    thumbnailTag : GFslideShow.DEFAULT_THUMBNAIL_TAG,
    thumbnailNamespace : GFslideShow.DEFAULT_THUMBNAIL_NS,
    thumbnailSize : GFslideShow.THUMBNAILS_LARGE,
    linkTarget : null,
    displayTime : GFslideShow.DEFAULT_DISPLAY_TIME,
    transitionTime : GFslideShow.DEFAULT_TRANSISTION_TIME,
    transitionStep : GFslideShow.DEFAULT_TRANSISTION_STEP,
    pauseOnHover : true,
    pauseImage : GFslideShow.DEFAULT_PAUSE_PNG,
    pauseStateCallback : null,
    scalePauseImage : true,
    autoCleanup : true,
    thumbnailUrlResolver : null,
    transitionCallback : null,
    transitionAnimationCallback : null,
    feedLoadCallback : null,
    feedProcessedCallback : null,
    imageClickCallback : null,
    centerBias : { topBias : 0, leftBias : 0 },
    pauseCenterBias : { topBias : 0, leftBias : 0 },
    fullControlPanel : false,
    fullControlPanelCursor : false,
    fullControlPanelFadeOutTime : GFslideShow.DEFAULT_FC_FADEOUT_TIME,
    fullControlPanelPlayCallback : null,
    fullControlPanelSmallIcons : false,
    maintainAspectRatio : true
  };

  if (options) {
    for (o in this.options) {
      if (typeof options[o] != "undefined") {
        this.options[o] = options[o];
      }
    }
  }
  // Override strange options
  if (this.options.displayTime < 100) {
    this.options.displayTime = 100;
  }
  // Calculated
  var ts = (this.options.transitionTime / this.options.transitionStep);
  this.delta = Math.min(1, (1.0/ts));

  // Flag to start
  this.started = false;
};

/**
 * Basic setup.
 * @private
 */
GFslideShow.prototype.setup = function() {
  if (this.container == null) return;

  // Browser fun.
  if (window.ActiveXObject) {
    this.ie = this[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
  } else if (window.opera) {
    this.opera = true;
  } else if (document.childNodes && !document.all && !navigator.taintEnabled) {
    this.safari = true;
    if (navigator.userAgent.indexOf('iPhone') > 0) {
      this.iphone = true;
    }
  } else if (document.getBoxObjectFor != null) {
    this.gecko = true;
  }

  // Feeds..
  if (this.feedUrl) {
    this.feed = new google.feeds.Feed(this.feedUrl);
    this.feed.setResultFormat(google.feeds.Feed.MIXED_FORMAT);
    this.feed.setNumEntries(this.options.numResults);
    this.feed.load(this.bind(this.feedLoaded));
  } else if (this.directEntries) {
    this.feedLoaded(this.directEntries);
  }
};

/**
 * Add new entries to the existing ones. Only useful in direct entry mode.
 * @param {Object} newEntries the additional entries Array.
 */
GFslideShow.prototype.addEntries = function(newEntries) {
  this.processEntries(newEntries);
  if (!this.thumb_timer) {
    this.processThumbs();
  }
};


/**
 * Helper method to bind this instance correctly.
 * @param {Object} method function/method to bind.
 * @return {Function}
 * @private
 */
GFslideShow.prototype.bind = function(method) {
  var self = this;
  var opt_args = [].slice.call(arguments, 1);
  return function() {
    var args = opt_args.concat([].slice.call(arguments));
    return method.apply(self, args);
  }
};

/**
 * Process mouseover event.
 * @param {Event} e Optional passed in event.
 * @private
 */
GFslideShow.prototype.mouseOver = function(e) {
  var event = e || window.event;
  var relatedTarget = event.relatedTarget || event.fromElement;

  while(relatedTarget != null) {
    if (relatedTarget == this.container) {
      return;
    }
    relatedTarget = relatedTarget.parentNode;
  }

  if (this.options.fullControlPanel) {
    if (this.options.pauseOnHover && !this.display_paused) {
      this.pauseOrPlayFullControl();
    }
    this.fadeInFullControl();
  } else {
    this.display_paused = true;
    if (this.pauseImage) {
      this.pauseImage.style.visibility = "visible";
    }
  }

  if (this.options.pauseStateCallback) {
    this.options.pauseStateCallback(this.display_paused);
  }
};

/**
 * Process mouseout event.
 * @param {Event} e Optional passed in event.
 * @private
 */
GFslideShow.prototype.mouseOut = function(e) {
  var event = e || window.event;
  var relatedTarget = event.relatedTarget || event.toElement;

  while(relatedTarget != null) {
    if (relatedTarget == this.container) {
      return;
    }
    relatedTarget = relatedTarget.parentNode;
  }

  if (this.options.fullControlPanel) {
    this.fadeOutFullControl();
    this.container.onmousemove = null;
    if (this.options.pauseOnHover && this.display_paused) {
      this.pauseOrPlayFullControl();
    }
  } else {
    this.display_paused = false;
    if (this.pauseImage) {
      this.pauseImage.style.visibility = "hidden";
    }
  }

  if (this.options.pauseStateCallback) {
    this.options.pauseStateCallback(this.display_paused);
  }

  if (this.display_timer == null && this.transition_timer == null) {
    // restart.
    this.displayNextPhoto();
  }
};

GFslideShow.prototype.operaClickAndCallout = function() {
  var entry = this.entries[this.photo_index];
  var tmpLink = this.createLink(entry.link);
  tmpLink.click();
};

/**
 * Programatic pause action.
 */
GFslideShow.prototype.pause = function(opt_suppressPauseImage) {
  var pi = this.pauseImage;
  if (opt_suppressPauseImage) {
    this.pauseImage = null;
  }
  this.pauseAndCallout();
  this.pauseImage = pi;
};

/**
 * Programatic resume action.
 */
GFslideShow.prototype.resume = function() {
  this.resumeSlideShow();
};

/**
 * Process pause action and associated user callout.
 * @private
 */
GFslideShow.prototype.pauseAndCallout = function() {
  this.display_paused = true;
  if (this.pauseImage) {
    this.pauseImage.style.visibility = "visible";
  }

  // for some reason a mouseout happens
  // when we click and swap divs...
  this.container.onmouseout = null;
  if (this.options.imageClickCallback) {
    this.options.imageClickCallback(this.entries[this.photo_index]);
  }
};

/**
 * Resume the slideshow after a pause action.
 */
GFslideShow.prototype.resumeSlideShow = function() {
  if (this.options.pauseOnHover || this.options.fullControlPanel) {
    this.container.onmouseover = this.bind(this.mouseOver);
    this.container.onmouseout = this.bind(this.mouseOut);
  }
  this.display_paused = false;
  if (this.pauseImage) {
    this.pauseImage.style.visibility = "hidden";
  }
  if (this.display_timer == null && this.transition_timer == null) {
    // restart.
    this.displayNextPhoto();
  }
};

/**
 * Helper method to properly clear a node and its children.
 * @param {Object} node Node to clear.
 * @private
 */
GFslideShow.prototype.clearNode = function(node) {
  if (node == null) return;
  var child;
  while (child = node.firstChild) {
    node.removeChild(child);
  }
};

/**
 * Setup our own subcontainer to the user supplied container.
 * @private
 */
GFslideShow.prototype.createSubContainer = function() {
  var div = document.createElement("div");
  div.style.width = "100%";
  div.style.height = "100%";
  div.style.position = "relative";
  div.style.overflow = "hidden";
  this.clearNode(this.container);
  this.container.appendChild(div);
  // Hold onto our sub-container.
  this.container = div;
};

/**
 * Select the appropriate thumbnail url from the array of thumbnails provided
 * based on options.
 * @param {Array} thumbNodes Array of thumbnails urls.
 * @private
 */
GFslideShow.prototype.grabThumb = function(thumbNodes) {
  var ti = 0;
  if (thumbNodes.length > 1) {
    // Use size hint.
    if (this.options.thumbnailSize == GFslideShow.THUMBNAILS_LARGE) {
      ti = thumbNodes.length - 1;
    } else if (this.options.thumbnailSize == GFslideShow.THUMBNAILS_MEDIUM) {
      ti = Math.floor(thumbNodes.length / 2);
    }
  }
  var node = thumbNodes[ti];
  var thumb = null;
  var thumb = node.getAttribute("url");
  if (!thumb) {
    thumb = node.firstChild.nodeValue;
  }
  return thumb;
};

/**
 * Process the thumbs and create appropriate images. These can be done in
 * chunks.
 * @param {Number} opt_chunk optional chunk size to process.
 * @param {Number} opt_timeout optional timeout for next chunk.
 * @private
 */
GFslideShow.prototype.processThumbs = function(opt_chunk, opt_timeout) {
  this.thumb_timer = null;
  var start = this.thumbs_index;
  var num = this.entries.length;
  var chunk = opt_chunk || 4;
  if (num > (start + chunk)) {
    num = (start + chunk);
    // schedule next batch.
    var cb = this.bind(this.processThumbs);
    var to = opt_timeout || Math.round(this.options.displayTime / 4);
    this.thumb_timer = window.setTimeout(cb, to);
  }
  for (var i = start; i < num; i++) {
    var thumbUrl = this.entries[i].thumbUrl;
    var image = this.createImage(thumbUrl);
    this.images.push(image);
    if (this.options.linkTarget) {
      if (!this.opera) {
        var link = this.createLink(this.entries[i].link);
        link.appendChild(image);
        this.container.appendChild(link);
      } else { // Opera Hack
        image.onclick = this.bind(this.operaClickAndCallout);
        image.style.cursor = 'pointer';
        this.container.appendChild(image);
      }
    } else {
      this.container.appendChild(image);
    }
    if (image.complete) {
      // We are already loaded and we have size dimensions.
      this.imageLoaded(image);
    } else {
      // We need to wait for the image to load..
      image.onerror = this.bind(this.imageError, image);
      image.onload = this.bind(this.imageLoaded, image);
    }
    this.thumbs_index++;
  }
};


/**
 * Process and setup the entries
 * @param {Object} entries Entries array.
 * @private
 */

GFslideShow.prototype.processEntries = function(entries) {
  for (var i = 0; i < entries.length; i++) {
    var thumbUrl = null;
    if (this.options.thumbnailUrlResolver) {
      thumbUrl = this.options.thumbnailUrlResolver(entries[i]);
    } else {
      var thumbNodes = google.feeds.getElementsByTagNameNS(
                           entries[i].xmlNode,
                           this.options.thumbnailNamespace,
                           this.options.thumbnailTag);
      if (thumbNodes && thumbNodes.length > 0) {
        thumbUrl = this.grabThumb(thumbNodes);
      }
    }
    if (thumbUrl) {
      entries[i].thumbUrl = thumbUrl;
      this.entries.push(entries[i]);
    }
  }

};

/**
 * Callback associated with the AJAX Feed api after load.
 * @param {Object} result Loaded result.
 * @private
 */
GFslideShow.prototype.feedLoaded = function(result) {
  if (this.options.feedLoadCallback) {
    this.options.feedLoadCallback(result);
  }

  if ((this.feedUrl && result.error) ||
      (this.directEntries && this.directEntries.length == 0) ) {
    if (!this.options.feedLoadCallback) {
      this.container.innerHTML = "<center>feed could not be loaded.</center>";
    }
    return;
  }

  this.createSubContainer();
  if (this.container.offsetWidth) {
    // snapshot.
    this.width = this.container.offsetWidth;
    this.height = this.container.offsetHeight;
  }
  this.createPauseImage();
  this.images = [];
  this.entries = [];
  this.thumbs_index = 0;
  var entries;
  if (this.feedUrl) {
    entries = result.feed.entries;
  } else {
    entries = this.directEntries;
  }

  // Process the entries.
  this.processEntries(entries);

  if (this.options.feedProcessedCallback) {
    this.options.feedProcessedCallback(result);
  }

  // Enable full panel control mode.
  if (this.options.fullControlPanel && this.entries.length > 0) {
    this.createFullControlPanel();
  }

  // Attach mouse handlers if applicable for pausing.
  if ((this.options.pauseOnHover || this.options.fullControlPanel) &&
      this.entries.length > 0) {
    this.container.onmouseover = this.bind(this.mouseOver);
    this.container.onmouseout = this.bind(this.mouseOut);
  }

  if (this.options.imageClickCallback) {
    this.container.onclick = this.bind(this.pauseAndCallout);
  }

  // Seed with first image and quick timeout for next chunk.
  this.processThumbs(1, 100+(Math.random()*100));
};

/**
 * Callback asscoiated with an image load.
 * @param {Element} image Image instance that was loaded.
 * @private
 */
GFslideShow.prototype.imageLoaded = function(image) {
  image.__gfloaded = true;
  this.adjustImage(image);

  // Once the first image is loaded, begin the slideshow..
  if (!this.started) {
    for (var i = 0; i < this.images.length; i++) {
      if (image == this.images[i]) {
        this.beginSlideShow(i);
      }
      break;
    }
  }
};

/**
 * Callback asscoiated with an image load error.
 * @param {Element} image Image instance that was loaded.
 * @private
 */
GFslideShow.prototype.imageError = function(image) {
  image.__gferror = true;
};

/**
 * Adjust the image to the container after load. Will scale and center.
 * @param {Element} image Image instance that needs adjusting.
 * @private
 */
GFslideShow.prototype.adjustImage = function(image) {
  // Scale if requested.
  if (this.options.scaleImages) {
    if (this.options.maintainAspectRatio) {
      this.scaleImage(image);
    } else {
      image.style.height = this.height + "px";
      image.style.width = this.width + "px";
    }
  }
  // Center the image.
  this.centerImage(image);
};

/**
 * Scale the image appropriately to fit in the container.
 * @param {Element} image Image instance that needs scaling.
 * @private
 */

GFslideShow.prototype.scaleImage = function(image, opt_width, opt_height) {
  // These change when the first one is set, so we need to remember them.
  var width = opt_width || this.width;
  var height = opt_height || this.height;
  var imgW = image.offsetWidth;
  var imgH = image.offsetHeight;
  if (imgW <= 0 || imgH <= 0) return;

  var scaleH = height / imgH;
  var scaleW = width / imgW;

  if (scaleH < scaleW) {
    image.style.height = height + "px";
    image.style.width = Math.round(imgW * scaleH) + "px";
  } else {
    image.style.width = width + "px";
    image.style.height = Math.round(imgH * scaleW) + "px";
  }
};

/**
 * Center the image appropriately within the container.
 * @param {Element} image Image instance.
 * @private
 */
GFslideShow.prototype.centerImage = function(image) {
  var oh = this.height - image.offsetHeight;
  var ow = this.width - image.offsetWidth;

  // Don't assume these are zero..
  image.style.top = "0px";
  image.style.left = "0px";

  // center the image
  if (oh > 0) {
    var ah = Math.round(oh/2);
    image.style.top = image.offsetTop + ah +
                      this.options.centerBias.topBias + "px";
  }
  if (ow > 0) {
    var aw = Math.round(ow/2);
    image.style.left = image.offsetLeft + aw +
                       this.options.centerBias.leftBias + "px";
  }
};

/**
 * Create a link element.
 * @param {String} href Href attribute for the element.
 * @return {Element} Link element.
 * @private
 */
GFslideShow.prototype.createLink = function(href) {
  var link = document.createElement('a');
  link.setAttribute('href', href);
  if (this.options.linkTarget) {
    link.setAttribute('target', this.options.linkTarget);
  }
  return link;
};

/**
 * Create an image element.
 * @param {String} src Source attribute for the image element.
 * @private
 */
GFslideShow.prototype.createImage = function(src) {
  var image = document.createElement("img");
  image.style.position = "absolute";
  image.setAttribute("src", src);
  this.setOpacity(image, 0);
  return image;
};

/**
 * Properly adjust the pause image if need be.
 * @param {Element} image Image representing pause state.
 * @private
 */
GFslideShow.prototype.adjustPauseImage = function(image) {
  if (this.options.scalePauseImage) {
    var height = Math.round(this.height * 0.33);
    var width = Math.round(this.width * 0.33);
    this.scaleImage(image, width, height);
  }
  this.placePauseImage(image);
};

/**
 * Properly place the pause image for overlay on a pause state.
 * @param {Element} image Image representing pause state.
 * @private
 */
GFslideShow.prototype.placePauseImage = function(image) {
  var oh = this.height - image.offsetHeight;
  var ow = this.width - image.offsetWidth;

  // Don't assume these are zero..
  image.style.top = "0px";
  image.style.left = "0px";

  // center the image
  if (oh > 0) {
    var off = Math.round(this.height * 0.10);
    if (off < 15) off = 10;
    var ah = this.height - (image.offsetHeight + off);
    if (ah < 0) ah = 0;
    image.style.top = image.offsetTop + ah +
                      this.options.pauseCenterBias.topBias + "px";
  }
  if (ow > 0) {
    var aw = Math.round(ow/2);
    image.style.left = image.offsetLeft + aw +
                       this.options.pauseCenterBias.leftBias + "px";
  }
};

/**
 * Properly create the alpha transparent version of the pause image.
 * @param {Element} image Image representing pause state.
 * @private
 */
GFslideShow.prototype.createAlphaPauseImage = function(image) {
  // Work with offscreen version first to get the correct sizes and offsets..
  this.adjustPauseImage(image);

  var imgW = image.offsetWidth;
  var imgH = image.offsetHeight;
  var imgT = image.style.top;
  var imgL = image.style.left;

  // Now create real one.
  var element = null;
  if (this.ie) {
    var src = this.options.pauseImage;
    element = document.createElement("div");
    element.style.filter = "progid:DXImageTransform.Microsoft." +
        "AlphaImageLoader(src='" + src + "', sizingMethod='scale')";
    element.style.position = "absolute";
    element.style.width = imgW + "px";
    element.style.height = imgH + "px";
    element.style.left = imgL;
    element.style.top = imgT;
  } else {
    element = image;
    element.style.opacity = "";
  }

  element.style.visibility = "hidden";
  element.style.zIndex = 222;

  if (element != image) {
    this.container.appendChild(element);
    this.container.removeChild(image);
  }
  this.pauseImage = element;
};

/**
 * Callback asscoiated with the pause image load.
 * @param {Element} image Pause image instance that was loaded.
 * @private
 */
GFslideShow.prototype.pauseImageLoaded = function(image) {
  this.createAlphaPauseImage(image);
};

/**
 * Create the pause image element.
 * @param {String} src Source attribute for the pause image element.
 * @private
 */
GFslideShow.prototype.createPauseImage = function(src) {
  if (!this.options.pauseOnHover) return;
  var pauseOff = this.createImage(this.options.pauseImage);
  this.container.appendChild(pauseOff);
  if (pauseOff.complete) {
    this.createAlphaPauseImage(pauseOff);
  } else {
    pauseOff.onload = this.bind(this.pauseImageLoaded, pauseOff);
  }
};

/**
 * Create the fullControlPanel setup.
 * @private
 */
GFslideShow.prototype.createFullControlPanel = function() {
  var h = (this.options.fullControlPanelSmallIcons?25:45);
  if (this.options.fullControlPanelCursor) h += 10;
  var padTop = (this.options.fullControlPanelSmallIcons?5:10);
  var padBottom = 5;
  var div = document.createElement('div');
  div.style.backgroundColor = '#000000';
  div.style.height = h + 'px';
  div.style.top = (this.height - (h+padBottom+padTop)) + 'px';
  div.style.width = '100%';
  div.style.zIndex = '222';
  div.style.position = 'relative';
  div.style.textAlign = 'center';
  div.style.direction = 'ltr';
  div.style.paddingTop = padTop + 'px';
  div.style.paddingBottom = padBottom + 'px';

  var iconSize = this.options.fullControlPanelSmallIcons?'small':'big';
  var handCursor = this.ie?'hand':'pointer';

  var pause = document.createElement("img");
  pause.src = GFslideShow.FC_PAUSE_PNG[iconSize];
  pause.style.cursor = handCursor;

  var next = document.createElement("img");
  next.src = GFslideShow.FC_NEXT_PNG[iconSize];
  next.style.cursor = handCursor;

  var prev = document.createElement("img");
  prev.src = GFslideShow.FC_PREV_PNG[iconSize];
  prev.style.cursor = handCursor;

  pause.style.marginLeft = '5px';
  pause.style.marginRight = '5px';

  div.appendChild(prev);
  div.appendChild(pause);
  div.appendChild(next);

  var cursor = null;
  if (this.options.fullControlPanelCursor) {
    cursor = document.createElement('div');
    cursor.style.height = '1.3em';
    cursor.style.fontSize = '11px';
    cursor.style.color = '#bbbbbb';
    div.appendChild(cursor);
  }

  // Hold onto the ui elements..
  this.fc = {};
  this.fc.container = div;
  this.fc.pause = pause;
  this.fc.next = next;
  this.fc.prev = prev;
  this.fc.cursor = cursor;

  next.onclick = this.bind(this.goForward);
  prev.onclick = this.bind(this.goBackward);
  pause.onclick = this.bind(this.pauseOrPlayClick);

  this.fc.container.style.visibility = "hidden";
  this.container.appendChild(div);
};

/**
 * Clear the transition timer. Used to prevent leaks.
 * @private
 */
GFslideShow.prototype.clearTransitionTimer = function() {
  if (this.transition_timer) {
    clearInterval(this.transition_timer);
    this.transition_timer = null;
  }
};

/**
 * Sets the transition timer for fadeout.
 * @private
 */
GFslideShow.prototype.setTransitionTimer = function() {
  this.clearTransitionTimer();
  this.lastTick = GFslideShow.timeNow();
  var cb = this.bind(this.transitionAnimation);
  this.transition_timer = window.setInterval(cb, this.options.transitionStep);
};

/**
 * Clear the display timer. Used to prevent leaks.
 * @private
 */
GFslideShow.prototype.clearDisplayTimer = function() {
  if (this.display_timer) {
    clearTimeout(this.display_timer);
    this.display_timer = null;
  }
};

/**
 * Sets the display timer.
 * @private
 */
GFslideShow.prototype.setDisplayTimer = function() {
  if (this.display_timer) return;
  var cb = this.bind(this.displayNextPhoto);
  this.display_timer = window.setTimeout(cb, this.options.displayTime);
};

/**
 * Clear the thumb timer. Used to prevent leaks.
 * @private
 */
GFslideShow.prototype.clearThumbTimer = function() {
  if (this.thumb_timer) {
    clearTimeout(this.thumb_timer);
    this.thumb_timer = null;
  }
};

/**
 * Displays the slideshow, starting at the corresponding index.
 * @param {Number} index Index of image to start with.
 * @private
 */
GFslideShow.prototype.beginSlideShow = function(index) {
  this.photo_index = index;
  this.next = this.images[this.photo_index];
  this.snapToNextPhoto();
  this.started = true;
};

/**
 * Class helper method for the time now in milliseconds
 * @private
 */
GFslideShow.timeNow = function() {
  var d = new Date();
  return d.getTime();
};

/**
 * Move to the next photo.
 */
GFslideShow.prototype.goForward = function() {
  this.finishTransition();
  this.setNextPhoto();
  this.snapToNextPhoto();
  this.clearFullControlTimeoutTimer();
};

/**
 * Move to the previous photo.
 */
GFslideShow.prototype.goBackward = function() {
  this.finishTransition();
  this.setPreviousPhoto();
  this.snapToNextPhoto();
  this.clearFullControlTimeoutTimer();
};

/**
 * Goto the specified indexed photo.
 */
GFslideShow.prototype.gotoIndex = function(index) {
  if (index == this.photo_index) {
    return;
  }
  this.clearTransitionTimer();
  this.setPhotoIndex(index);
  this.snapToNextPhoto();
  this.clearFullControlTimeoutTimer();
}

/**
 * Handle mouse clicks on the pause or play button.
 */
GFslideShow.prototype.pauseOrPlayClick = function() {
  // Trap a play click if we have a callout registered.
  if (this.options.fullControlPanelPlayCallback && this.display_paused) {
    // for some reason a mouseout happens
    // when we click and swap divs...
    this.container.onmouseover = null;
    this.container.onmouseout = null;
    this.options.fullControlPanelPlayCallback(this.entries[this.photo_index]);
    this.fadeOutFullControl();
  } else {
    this.pauseOrPlayFullControl();
  }
}

/**
 * Toggle Pause or Play in FullControl Mode.
 */
GFslideShow.prototype.pauseOrPlayFullControl = function() {
  // todo, pause callout?
  var iconSize = this.options.fullControlPanelSmallIcons?'small':'big';
  if (this.display_paused) {
    this.display_paused = false;
    this.fc.pause.src = GFslideShow.FC_PAUSE_PNG[iconSize];
    if (this.display_timer == null && this.transition_timer == null) {
      // restart.
      this.displayNextPhoto();
    }
  } else {
    this.display_paused = true;
    this.fc.pause.src = GFslideShow.FC_PLAY_PNG[iconSize];
  }
};

/**
 * monitors mouse motion inside the container while fullcontrol panel is
 * active.
 * @private
 */
GFslideShow.prototype.fullControlMotion = function() {
  var op = this.fc.container.opacity;
  if (op < GFslideShow.DEFAULT_FC_OPACITY) {
    this.container.onmousemove = null;
    this.clearFullControlTimeoutTimer();
    this.fadeInFullControl();
  } else {
    this.setFullControlTimeoutTimer();
  }
}

/**
 * Clears the timeout timer itself.
 * @private
 */
GFslideShow.prototype.clearFullControlTimeoutTimer = function() {
  if (!this.fc) {
    return;
  }
  if (this.fc.timeout) {
    clearTimeout(this.fc.timeout);
    this.fc.timeout = null;
  }
}

/**
 * Set up the timeout timer itself.
 * @private
 */
GFslideShow.prototype.setFullControlTimeoutTimer = function() {
  if (this.fc.timeout) {
    clearTimeout(this.fc.timeout);
    this.fc.timeout = null;
  }
  if (this.options.fullControlPanelFadeOutTime > 0) {
    var cb = this.bind(this.fadeOutFullControl);
    this.fc.timeout = setTimeout(cb, this.options.fullControlPanelFadeOutTime);
  }
}

/**
 * Set up a timeout to fadeout the control if no mouse activity.
 * @private
 */
GFslideShow.prototype.setFullControlTimeout = function() {
  this.container.onmousemove = this.bind(this.fullControlMotion);
  this.setFullControlTimeoutTimer();
}

/**
 * Begin fading in of the FullControl.
 */
GFslideShow.prototype.fadeInFullControl = function() {
  this.setOpacity(this.fc.container, 0);
  var cb = this.bind(this.fadeInFullControlAnimation);
  this.setFullControlFadeTimer(cb);
}

/**
 * Fade in animation for the FullControl
 */
GFslideShow.prototype.fadeInFullControlAnimation = function() {
  var op = this.fc.container.opacity;
  op += 0.075; // Approximation
  op = Math.min(GFslideShow.DEFAULT_FC_OPACITY, op);
  this.setOpacity(this.fc.container, op);
  if (op >= GFslideShow.DEFAULT_FC_OPACITY) {
    this.setFullControlFadeTimer();
    this.setFullControlTimeout();
  }
}

/**
 * Begin fading out of the FullControl.
 */
GFslideShow.prototype.fadeOutFullControl = function() {
  var cb = this.bind(this.fadeOutFullControlAnimation);
  this.setFullControlFadeTimer(cb);
}

/**
 * Fade out animation for the FullControl
 */
GFslideShow.prototype.fadeOutFullControlAnimation = function() {
  var op = this.fc.container.opacity;
  op -= 0.075;
  this.setOpacity(this.fc.container, op);
  if (op <= 0) {
    this.setFullControlFadeTimer();
  }
}

/**
 * Set the fade timer, clearing previous ones.
 * @param {Object} opt_callback function/method to bind timer to.
 * @private
 */
GFslideShow.prototype.setFullControlFadeTimer = function(opt_callback) {
  if (this.fc.fade_timer) {
    clearInterval(this.fc.fade_timer);
    this.fc.fade_timer = null;
  }
  if (opt_callback) {
    this.fc.fade_timer = window.setInterval(opt_callback, 40);
  }
}

/**
 * Transition animation to next photo. Cleanup when finished.
 * @private
 */
GFslideShow.prototype.transitionAnimation = function() {
  if (this.current && this.next) {
    var delta = this.delta;
    var ts = this.options.transitionStep;
    var now = GFslideShow.timeNow();
    var tick = now - this.lastTick;
    this.lastTick = now;
    delta *= (tick/ts);
    if (delta < 0) return;

    var cur_op = this.current.opacity - delta;
    var next_op = this.next.opacity + delta;

    this.setOpacity(this.current, cur_op);
    this.setOpacity(this.next, next_op);

    if (this.options.transitionAnimationCallback) {
      this.options.transitionAnimationCallback(this.next.opacity);
    }

    // Still more to go?
    if (cur_op > 0) {
      return;
    }
  }

  // Finished transition.
  this.finishTransition();
};

/**
 * Select the next photo to display. This takes into account different load
 * times for images and bad links, etc.
 * @private
 */
GFslideShow.prototype.setNextPhoto = function() {
  if (this.images.length == 0) {
    return;
  }
  var ci = this.photo_index;
  var done = false;
  while (!done) {
    // wrap
    if (++this.photo_index >= this.images.length) {
      this.photo_index = 0;
    }
    var image = this.images[this.photo_index];
    if (image && image.__gfloaded) {
      this.next = image;
      done = true;
    } else {
      // Image not loaded for some reason, skip it. But don't loop forever.
      if (this.photo_index == ci) {
        this.next = this.images[0];
        done = true;
      }
    }
  }
};


/**
 * Select the previous photo to display. This takes into account different load
 * times for images and bad links, etc.
 * @private
 */
GFslideShow.prototype.setPreviousPhoto = function() {
  var ci = this.photo_index;
  var done = false;
  while (!done && this.images.length != 0) {
    if (--this.photo_index < 0) {
      this.photo_index = this.images.length-1;
    }
    var image = this.images[this.photo_index];
    if (image && image.__gfloaded) {
      this.next = image;
      done = true;
    } else {
      // Image not loaded for some reason, skip it. But don't loop forever.
      if (this.photo_index == ci) {
        this.next = this.images[0];
        done = true;
      }
    }
  }
};

/**
 * Select the photo index to display.
 * @private
 */
GFslideShow.prototype.setPhotoIndex = function(index) {
  if (index < 0 || index >= this.images.length) {
    return;
  }
  var image = this.images[index];
  if (image && image.__gfloaded) {
    this.next = image;
    this.photo_index = index;
  }
};

/**
 * Clears the pause events, prevents leaks.
 * @private
 */
GFslideShow.prototype.clearPauseEvents = function() {
  this.container.onmouseover = null;
  this.container.onmouseout = null;
};

/**
 * Cleanup when we notice we have been removed or replaced. Try to be
 * as GC friendly as possible.
 * @private
 */
GFslideShow.prototype.cleanup = function() {
  // Try to be gc friendly.
  this.clearTransitionTimer();
  this.clearDisplayTimer();
  this.clearThumbTimer();
  this.clearPauseEvents();
  this.clearNode(this.container);
  this.container = null;
};

/**
 * Display the next appropriate photo if we can. Also determine if we
 * need to start transition animation if applicable.
 * @private
 */
GFslideShow.prototype.displayNextPhoto = function() {
  this.display_timer = null;

  if (!this.started) {
    return false;
  }

  // Just return if we are in a paused state.
  if (this.display_paused) return;

  // See if we have been orphaned and cleanup if needed.
  if ((!this.container || !this.container.parentNode) &&
      this.options.autoCleanup) {
    this.cleanup();
    return;
  }

  this.setNextPhoto();
  this.beginTransition();
};

/**
 * Helper method to snap to next photo
 * @private
 */
GFslideShow.prototype.snapToNextPhoto = function() {
  this.setOpacity(this.next, 1);
  this.setOpacity(this.current, 0);
  this.current = this.next;
  this.setDisplayTimer();
  if (this.options.transitionCallback) {
    this.options.transitionCallback(this.entries[this.photo_index],
                                    this.options.transitionTime);
  }
  if (this.options.fullControlPanel && this.options.fullControlPanelCursor) {
    var index = (this.photo_index+1) + ' / ' + this.images.length;
    this.fc.cursor.innerHTML = index;
  }
}

/**
 * Helper method to start transition to next selected photo.
 * Takes into account transition parameters.
 * @private
 */
GFslideShow.prototype.beginTransition = function() {
  if (!this.current || !this.next || (this.current == this.next)) {
    // Skip if we are missing something or trying to transition to
    // ourselves.
    this.setDisplayTimer();
    return;
  }
  if (this.options.transitionTime >= this.options.transitionStep) {
    this.setTransitionTimer();
  } else {
    this.snapToNextPhoto();
  }
};

/**
 * Helper method to finish the transition to next selected photo.
 * @private
 */
GFslideShow.prototype.finishTransition = function() {
  this.clearTransitionTimer();
  this.snapToNextPhoto();
};

/**
 * Helper method to set opacity for images.. Also takes into account
 * visibility in general.
 * @param {Element} image Image element.
 * @param {Number} opacity alpha level.
 * @private
 */
GFslideShow.prototype.setOpacity = function(image, opacity) {
   if (image == null) return;
   opacity = Math.max(0, Math.min(1, opacity));
   if (opacity == 0) {
     if (image.style.visibility != "hidden") {
       image.style.visibility = "hidden";
     }
   } else {
     if (image.style.visibility != "visible") {
       image.style.visibility = "visible";
     }
   }
   if (this.ie) image.style.filter = "alpha(opacity=" + opacity*100 + ")";
   image.style.opacity = image.opacity = opacity;
};