/* QPhotos
 * vim: ai foldenable foldmethod=marker foldclose=
 *
 * Module to handle photo prefetching and automatic roll-over of photos.
 *
 * Written by Ben Siemerink in 2008.
 * Copyright (c)2008 by Qoben, Ben Siemerink.
 */

function debug(msg) // {{{1
{
  var e=document.getElementById('debug');
  if (!e) return;

  if (typeof(debug_starttime)=='undefined') {
    debug_starttime=new Date();
    e.value='';
  }
  var now=new Date();

  var t='['+((now-debug_starttime)/1000)+'] ';

  if (typeof(photos)!='undefined')
  {
    t+=(photos.playing?'playing':'paused');
    t+=' : ';
    t+=(photos.sliding?'sliding':'no-effect');
    t+=' : ';
    t+=(photos.nexttimer?photos.nexttimer:'no-nexttimer');
    t+=' : ';
    t+=(photos.delay?photos.delay:'no-delay');
    t+=' : '+msg+'\n';
    for (i in photos.framelist) {
      img=photos.framelist[i];
      t+=i+' ['+img.index+'] ';
      t+=(img.fetched?'F':'-');
      t+=(img.error?'E':'-');
      t+=' ';
      t+=(photos.fetching==img.index?'F':'-');
      t+=(photos.requested==img.index?'R':'-');
      t+=(photos.ondisplay==img.index?'D':'-');
      t+=' '+img.imageurl+'\n';
    }
  }
  e.value=t;
}

function QPhotos(divid, maxframes, shownumbers, framespecs) // {{{1
{
  this.display=function(n) // {{{2
  {
    this._userinterrupt();
    this._paint(n);
  }

  this.next=function() // {{{2
  {
    this._userinterrupt();
    if (this.sliding) {
      this.sliding=false;
      this._paint(this.ondisplay+1);
    }
    else {
      if (this.requested==undefined) this._paint(1);
      else this._paint(this.requested+1);
    }
  }

  this.prev=function() // {{{2
  {
    this._userinterrupt();
    if (this.sliding) {
      this.sliding=false;
      this._paint(this.ondisplay);
    }
    else {
      if (this.requested==undefined) this._paint(1);
      else this._paint(this.requested-1);
    }
  }

  this.playstart=function(delay) // {{{2
  {
    this._userinterrupt();
    this.nexttimer=setTimeout("__qphotos_this_object.play("+delay+");", delay);
  }

  this.play=function(delay) // {{{2
  {
    this._userinterrupt();

    if (typeof(delay)=='undefined' && !this.delay) this.delay=4000;
    else this.delay=delay;

    if (this.sliding) {
      this.sliding=false;
      this._next();
    }

    this.playing=true;
    this._playnext();
  }

  this.pause=function() // {{{2
  {
    this._userinterrupt();
    this.playing=false;
    this.sliding=false;
    this._paint(this.requested);
  }

  this.set_linear=function(fps, step) // {{{2
  {
    this.fps=fps;
    this.linear_step=step;
    this.effect='linear';
  }

  this._userinterrupt=function() // {{{2
  {
    this.playing=false;
    this.waitingtoslide=false;
    if (this.nexttimer) clearTimeout(this.nexttimer);
    this.nexttimer=undefined;
    if (this.effecttimer) clearInterval(this.effecttimer);
    this.effecttimer=undefined;
  }

  this._next=function() // {{{2
  {
    if (this.requested==undefined) this._paint(1);
    else this._paint(this.requested+1);
  }

  this._playnext=function() // {{{2
  {
    this._paint();
    this.effecttimer=setTimeout("__qphotos_this_object._slide();", 1);
  }

  this._paint=function(n) // {{{2
  {
    if (!this.sliding) {
      // Wrap around
      if (n==undefined) {
        if (this.ondisplay) n=this.ondisplay;
	else n=1;
      }
      if (n>this.framelist.length) n=1;
      if (n<1) n=this.framelist.length;
      this.requested=n;

      // Update division
      var html='<table id="'+this.divid+'_table">';
      var i=0;
      var ii;
      var td;

      // Create first row with text and images
      html+='<tr>';
      while (i<this.maxframes) {
	ii=n+i;
	while (ii>this.framelist.length) ii-=this.framelist.length;
	frame=this.framelist[ii-1];

	html+='<td id="'+this.divid+'_td_'+ii+'"';
	if (frame.cssclass) html+=' class="'+frame.cssclass+'"';
	html+='>';

	if (frame.fetched) {
	  if (frame.imageurl) {
	    // an image
	    if (!frame.error) {
	      html+='<img ';
	      if (frame.width) html+='width="'+frame.width+'px"';
	      if (frame.height) html+='height="'+frame.height+'px"';
	      html+='src="'+frame.src+'"></td>';
	    }
	    else html+='image&nbsp;'+ii+'&nbsp;not&nbsp;available: '+this.framelist[ii-1].imageurl;
	  }
	  else {
	    // HTML code
	    html+='<div>'+frame.html+'</div>'; // The div is necessary for IE < 8
	  }
	}
	else {
	  html+='..........';
	}
	html+='</td>';
	i++;
      }
      html+='</tr>';

      // Create second row with numbers, labels and descriptions
      if (this.shownumbers || this.showdescriptions) {
	i=0;
	html+='<tr>';
	while (i<this.maxframes) {
	  ii=n+i;
	  while (ii>this.framelist.length) ii-=this.framelist.length;
	  frame=this.framelist[ii-1];

	  html+='<td';
	  if (frame.cssclass) html+=' class="'+frame.cssclass+'_label"';
	  else html+=' class="label"';
	  html+='>';

	  if (this.shownumbers) html+=ii+' / '+this.framelist.length;
	  if (this.shownumbers && frame.description) html+='<br>';
	  if (frame.description) html+=frame.description;

	  html+='</td>';
	  i++;
	}
	html+='</tr>';
      }

      // Finish table and display it
      html+='</table>';
      this.div.innerHTML=html;
      this.div.scrollLeft=0;
      this.ondisplay=n;
      if (this.waitingtoslide) this.effecttimer=setTimeout("__qphotos_this_object._slide();", 1);
    }
    debug('done _paint');
  }

  this._effectpositions=function(begin, end) // {{{2
  {
    // Returns a list of scroll positions to be used by _slidestep.
    // Its values depend on the effect being used.
    var i;
    var l=new Array();
    if (this.effect=='linear') {
      i=begin;
      if (begin<end) { while (i<end) { l.push(i); i+=this.linear_step; } }
      else { while (i>end) { l.push(i); i-=this.linear_step; } }
    }
    l.push(end);
    return l;
  }

  this._slide=function() // {{{2
  {
    this.waitingtoslide=false;
    if (!this.sliding) {
      // Make sure no timer is running
      if (this.nexttimer) clearTimeout(this.nexttimer);
      this.nexttimer=undefined;

      // Verify that both the current and the next image have been
      // fetched.
      if (this.ondisplay) {
	var n=this.ondisplay;
	if (!n) n=1;
	var nextn=n+1;
	if (nextn>this.framelist.length) nextn=1;

	if (this.framelist[n-1].fetched && this.framelist[nextn-1].fetched) {
	  // Verify if there is enough width to scroll
	  var table=document.getElementById(this.divid+'_table');
	  var td=document.getElementById(this.divid+'_td_'+n);
	  this.waitingtoslide=(!table || !td || (table.scrollWidth < this.div.clientWidth + td.scrollWidth));

	  // Move div to the left
	  if (!this.waitingtoslide) {
	    if (this.effect==undefined) this._paint(nextn);
	    else {
	      this.slideposlist=this._effectpositions(0, document.getElementById(this.divid+'_td_'+n).clientWidth);
	      this.sliding=true;
	      this.effecttimer=setInterval("__qphotos_this_object._slidestep();", (1000/this.fps));
	    }
	  }
	}
	else {
	  this.requested=nextn;
	  this.waitingtoslide=true;
	}
      }
      else {
        this.requested=1;
	this.waitingtoslide=true;
      }
    }
  }

  this._slidestep=function() // {{{2
  {
    if (this.sliding) {
      if (this.slideposlist.length>0) {
	this.div.scrollLeft=this.slideposlist.shift();
      }
      else {
	this.sliding=false;
	clearInterval(this.effecttimer);
	this.effecttimer=undefined;
	this._paint(this.ondisplay+1);
	clearTimeout(this.nexttimer);
	this.nexttimer=undefined;
	if (this.playing) this.nexttimer=setTimeout("__qphotos_this_object._playnext();", this.delay);
	debug('finished effect');
      }
    }
  }

  this._fetch=function() // {{{2
  {
    // Avoid stack overflows in Internet Explorer
    setTimeout("__qphotos_this_object.__fetch();", 0);
  }

  this.__fetch=function() // {{{2
  {
    // Fetches all images one by one (instead of all at once). This way,
    // the next image is available as soon as possible.
    //
    // At every call it fetches the next one, if no image is being
    // fetched.
    //
    // The next image is chosen using the following rules:
    // 1: the requested image;
    // 2: the one after the one being displayed;
    // 3: the one before the one being desplayed;
    // 4: the first one after the one being displayed in a circular
    // manner.
    var n;
    var ondisplay;
    if (typeof(this.fetchcount)=='undefined') this.fetchcount=1; // TODO remove this part with fetchcount
    else this.fetchcount++;
    // if (this.fetchcount>3) return;

    // Do nothing if fetching an image
    if (this.fetching!=undefined && !this.framelist[this.fetching-1].fetched) return;

    // Initialise
    this.fetching=undefined;
    if (this.ondisplay==undefined) ondisplay=1;
    else ondisplay=this.ondisplay;

    // 0: the image on display (this is a special case when nothing
    // has been fetched yet and no image is on display)
    if (!this.framelist[ondisplay-1].fetched) this.fetching=ondisplay;

    // 1: the requested image
    if (this.requested!=undefined && !this.framelist[this.requested-1].fetched) this.fetching=this.requested;

    // 2: the one after the one on display
    if (this.fetching==undefined) {
      n=ondisplay+1;
      if (n>=this.framelist.length) n=1;
      if (!this.framelist[n-1].fetched) this.fetching=n;
    }
    
    // 3: the one before the one on display
    if (false) {
    if (this.fetching==undefined) {
      n=ondisplay-1;
      if (n<1) n=this.framelist.length;
      if (!this.framelist[n-1].fetched) this.fetching=n;
    }
    }
    
    // 4: the first one after the one on display
    if (this.fetching==undefined) {
      n=ondisplay+1;
      if (n>this.framelist.length) n=1;
      while (this.fetching==undefined && n!=ondisplay) {
	if (!this.framelist[n-1].fetched) this.fetching=n;
	n++;
        if (n>this.framelist.length) n=1;
      }
    }

    // Fetched all images?
    if (this.fetching==undefined) {
    }
    else {
      // Fetch next image
      this.framelist[this.fetching-1].src=this.framelist[this.fetching-1].imageurl;
    }
    debug('done _fetch');
  }

  this._fetched_ok=function() { // {{{2
    debug('_fetched_ok');
    this.fetched=true;
    this.error=false;
    this.parent._paint();
    this.parent._fetch();
  }

  this._fetched_error=function() { // {{{2
    debug('_fetched_error');
    this.fetched=true;
    this.error=true;
    this.parent._paint();
    this.parent._fetch();
  }

  // QPhotos constructor body {{{2
  var i;
  this.div=document.getElementById(divid); // division element for the images
  if (!this.div) {
    alert('QPhotos: there is no element with id '+divid)
    return;
  }
  this.divid=divid;

  this.shownumbers=shownumbers;
  this.showdescriptions=false; // set to true further on if there are descriptions

  if (maxframes==undefined || maxframes<1) {
    alert('QPhotos: invalid value for argument maxframes; must be an integer and at least one')
    return;
  }
  this.maxframes=maxframes;

  if (framespecs.length<1) {
    alert('QPhotos: the framespecs list is empty')
    return;
  }

  // Build list of Image objects
  this.framelist=new Array();
  for (f in framespecs) {
    frame=framespecs[f];
    var img=new Image();
    img.index=Number(f)+1;
    img.cssclass=frame[1];
    if (frame.length>3) {
      this.showdescriptions=true;
      img.description=frame[3];
    }
    else img.description=undefined;
    img.parent=this;
    if (frame[0]=='img') {
      img.imageurl=frame[2];
      if (frame.length>3) img.description=frame[3];
      else img.description='';
      img.html=undefined;
      img.fetched=false; // finished fetching, successful
      img.error=false; // finished fetching, unsuccessful
      img.onload=this._fetched_ok;
      img.onerror=this._fetched_error;
    }
    else {
      if (frame[0]=='html') {
	img.imageurl=undefined;
	img.html=frame[2];
	img.fetched=true;
      }
    }
    this.framelist.push(img);
  }

  // General bookkeeping
  this.ondisplay=undefined;	// index of image on display
  this.fetching=undefined;	// index of image being fetched
  this.requested=undefined;	// index of image on display
  this.nexttimer=undefined;	// currently no timer is running
  this.effecttimer=undefined;	// currently no timer is running
  this.playing=false;		// if true: slide show is playing
  this.showloading=false;	// if true: show a message "loading image #..."
  this.sliding=false;		// true if currently executing a sliding effect
  this.waitingtoslide=false;	// true if the nexttimer timed out and the image was not available at that time
  __qphotos_this_object=this;	// for timers; IE does not have the global variables in the window iterable

  this.effect=undefined;	// do not use an effect unless overridden

  // Start downloading the images, one by one
  this._fetch();
  debug('done init');
}

