var FreeRunSS = new Class.create()
FreeRunSS.prototype = {
	DefaultOptions : {
		container : 'fss_container',
		nav : null,
		img1 : 'fss_img1',
		img2 : 'fss_img2',
		img3 : 'fss_img3',
		logging : false,
		runonce : false,
		manual : false,
		delay : 2000,
		crossfadetime : 0.5,
		crossfade : true,
		resize : false	
	},
	
	initialize : function (url,options) {
		this.url = url
		
		this.options = 
			Object.extend(Object.extend({} , this.DefaultOptions) , options || {});
			
		this.log = ( this.options.logging ) ? this._log : function(){}
		
		this.slides = []
		this.slideOptions = []
		this.current = 0
		this.count = null
		
		this.started = false
		
		this.container = $( this.options.container );
		
		this.queue = Effect.Queues.get('FreeRunSS');
		
		// set up a broadcaster to send out slide changes
		this.caster = {}
		JSBroadcaster.initialize(this.caster)
		
		this.loadTimer = null
		this.switchTimer = null

		this.img1 = {
			el : $( this.options.img1 ),
			ready : false,
			pos : null,
			name : '1'
		}
		
		this.img2 = {
			el : $( this.options.img2 ),
			ready: false,
			pos : null,
			name : '2'
		}
		
		this.img3 = {
			el : $( this.options.img3 ),
			ready : false,
			pos : null,
			name : '3'
		}
		
		this.stack = [ this.img1 , this.img2 , this.img3 ]
		this.active = null
		
		this.stack.each(function(img) {
			img.el.onload = this._imgLoad.bind(this,img)
			//img.el.onerror = function(msg) { this.log('img err is ' + msg) }.bind(this)
			img.el.onreadystatechange = this._imgState.bind(this,img)		
		}.bind(this))
		
		// loading stack
		this.lstack = []
		
		this.size = { w : null, h : null }
		
		this.log('getting slides')
		this.getSlides()
	},
	_log : function (msg) {
		Log.write('fss: ' + msg)
	},
	
	addListener : function (obj) {
		this.caster.addListener(obj)
	},
	
	getSlides : function () {
		var req = new Ajax.Request(
			this.url,
			{
				method: 'get',
				onComplete: this._getSlidesResult.bind(this)
			}
		)
	},
	_getSlidesResult : function (req) {
		var json;
		try {
			json = eval('('+req.responseText+')');
		} catch (e) {}

		this.slides = json	
		this.count = this.slides.length
		this.log('got slides... setting options')	
		
		this.slides.each(function(s,i) {
			this.log('slide options for ' + s[0])
			
			this.slideOptions.push(
				Object.extend(Object.extend({},this.options), s[1] || {})
			)			
		}.bind(this))
		
		this.log('calling startShow')
		
		this.startShow()
	},
	startShow : function () {
		this.log('in startShow')
		
		if (!this.options.manual)
			this.caster.broadcastMessage('playing')
		
		// fire a load request for img1
		this.log('calling loadImg to start show')
		this._loadImg(0)

		// and start the show...
		this.trySwitch()
	},
	restart : function () {
		this.log('got restart request')
		this.options.manual = false
		
		this.startShow()
	},
	
	pause : function () {
		this.log('got a pause')
		
		this.options.manual = true
		this.clearStack()
		
		this.caster.broadcastMessage('paused')
		
		if (this.loadTimer)
			clearTimeout(this.loadTimer)
			
		if (this.switchTimer)
			clearTimeout(this.switchTimer)
	},
	
	playFromHere : function () {
		//Log.write('in playFromHere')
		this.log('play from here')
		this.options.manual = false

		var n = this.getNextIndex()
		
		if (!n)
			return false
			
		this.caster.broadcastMessage('playing')

		//Log.write('loading img ' + n)
		this._loadImg(n)
		this.trySwitch()	
			
		return true
	},
	
	showNext : function () {
		this.log('advance one')
		var n = this.getNextIndex()
		
		if (!n)
			n = 0
		
		this.loadImg(n)
		
		return true
	},
	
	showPrev : function () {
		this.log('back one')
		
		var n = this.getLastIndex()
		
		if (!n)
			n = this.count - 1
			
		this.loadImg(n)
		
		return true
	},
	
	getNextIndex : function (i) {
		i = i || this.active.pos

		if ( i == null )
			return null
		
		if ( i + 1 < this.count ) {
			return i + 1
		} else {
			return null
			//return ( this.options.runonce ) ? null : null
		}
	},
	
	getLastIndex : function (i) {
		i = i || this.active.pos
		
		if ( i == null )
			return null
			
		if ( i - 1 >= 0 )
			return i - 1
		else {
			return null
		}
	},

	loadImg : function (i) {
		this.log('got loadImg... adding to queue: ' + i)

		// note that we're under user control
		this.options.manual = true

		// clear the load queue
		this.clearStack()
		
		if (this.loadTimer)
			clearTimeout(this.loadTimer)
			
		if (this.switchTimer)
			clearTimeout(this.switchTimer)
			
		//if (this.getNextIndex()) {
			this.caster.broadcastMessage('paused')			
		//} else {
		//	this.caster.broadcastMessage('finished')
		//}

		// fire off the load request
		this.tryLoad(i)
		this.trySwitch()
	},
	
	getImgContainer : function () {
		var img = this.stack.shift()
		return img
	},
	
	cleanAndReturn : function (img) {
		if (!img) return true
		
		this.log('clean and return pushing img back on stack')
		img.ready = false
		img.pos = null
		//img.src = ''
		
		this.stack.push(img)
		this.log('stack is now ' + this.stack.length)
	},
	
	clearStack : function() {
		this.log('clearing load stack')
		var ostack = this.lstack
		
		this.lstack = []
		
		ostack.each(function(img) {
			this.log('clearStack for img: ' + img.name)
			this.cleanAndReturn(img)
		}.bind(this))
	},
	
	_loadImg : function(i) {
		this.log('_loadImg called with i ' + i)
		
		if (!this.slides[i])
			return null
		
		var img = this.getImgContainer()
		
		this.log('img is ' + img.name)

		this.log('starting load ' + this.slides[i][0])
		
		img.pos = i

		// add the image to the loading stack
		this.lstack.push(img)


		if (img.el.src.indexOf(this.slides[i][0]) != -1) {
			// happy coincidence...  we've already got the img
			this.log('coincidence!  src match')
			this._imgLoad(img)
		} else {
			this.log('firing new src load')
			img.el.src = this.slides[i][0]			
		}
	},
	_imgLoad : function (img) {
		this.log('got load for ' + img.el.src)
		img.ready = true
		
		// check if we can go load more...
		if (this.options.manual) {
			this.log('_imgLoad on manual...  doing nothing.')
			// do nothing
		} else {
			this.log('loaded pos for ' + img.name + ' is ' + img.pos)
			if (img.pos != null) {
				if ( (img.pos + 1) < this.count) {
					this.tryLoad( img.pos + 1 )
				} else {
					if ( this.options.runonce ) {
						// done						
					} else {
						this.tryLoad(0)
					}
				}
			}
		}
	},
	_imgState : function (img) {
		this.log('got state change to ' + img.el.readyState)
		
		if ( img.el.readyState == 'complete') 
			img.ready = true
	},
	
	tryLoad : function(i) {
		this.log('in tryLoad for ' + i)
		if ( i >= this.count ) {
			this.log('pos reached count.  loading is done.')
			return true
		}
		
		// still got work to do...
		
		if ( this.stack.length ) {
			this.log('starting new load for ' + i)
			this.loadTimer = null
			this._loadImg(i)
		} else {
			this.log('stalling for a container...')
			this.loadTimer = setTimeout( this.tryLoad.bind(this,i) , 0 )
		}
	},
	
	trySwitch : function() {
		// check if we're ready
				
		if ( this.lstack.length ) {
			this.log('images on load stack: ' + this.lstack.length)
			this.lstack.each(function(img,i) { 
				this.log('stack i/img is: ' + i + '/' + img.pos)
			}.bind(this))
			if (this.lstack[0].ready) {
				// go ahead
				this.log('image is ready')
				this.switchTimer = null
				var img = this.lstack.shift()
				this.showImg(img)
			} else {
				// delay...
				this.log('delaying')
				this.switchTimer = setTimeout( this.trySwitch.bind(this),0)
			}
		} else {
			// nothing for us to load
			
			// if we're not on manual, let's assume we're done and send out a finished message
			if (!this.options.manual)
				this.caster.broadcastMessage('finished')
		}
	},

	showImg : function (img) {
		// if we made it here we're ready to fade between images
		this.log('going to show image ' + img.name)
		this.log('pos is ' + img.pos)
		this.log('file is ' + img.el.src)
		
		var opts = this.slideOptions[img.pos]
		this.log('opts is ' + opts.delay)
		
		if (this.caster) 
			new Effect.Execute(
				function () { 
					this.caster.broadcastMessage('onChange',img.pos) 
				}.bind(this),
				{
					duration : 0.1,
					queue : { position: 'end', scope: this.queue }
				}
			)
		
		if ( opts.crossfade && this.active) {
			// we need to bump the loading img z-index up
			Element.setStyle(img.el,{ 'z-index' : 2 })
			Element.setStyle(this.active.el,{ 'z-index' : 1 })
			
			// just fade in the new image...  old one will be covered
		
			new Effect.Appear(
				img.el,
				{
					duration : opts.crossfadetime,
					queue : { position: 'end', scope: this.queue }
				}
			)
			
			// now hide the old image
			
			new Effect.Execute(
				function () { Element.hide(this.active.el) }.bind(this),
				{
					duration : 0.1,
					queue : { position: 'end', scope: this.queue }
				}
			)
		} else {
			// if we have an active image fade it out
			new Effect.Execute(
				function () {
					if ( this.active ) {
						this.log('fading ' + this.active.el.src)
						new Effect.Fade(
							this.active.el, { 
								queue: { position: 'front', scope: this.queue }
							}
						)
					} else {
						this.log('no active to fade out')
					}					
				}.bind(this),
				{
					duration : 0.1,
					queue: { position: 'end', scope: this.queue }
				}
			)
		
			var d = Element.getDimensions(img.el)
			this.log('new image is ' + d.width + 'x' + d.height)
			var cW = d.width
			var cH = d.height
	
			this.log('resizing to ' + cW + 'x' + cH)
		
			// resize our space for the new image
			if ( this.options.resize && (cW != this.size.w || cH != this.size.h) ) {
				new Effect.ScaleToSize(
					this.container, cW, cH, 
					{ 
						center: true, 
						duration : .4,
						queue: { position: 'end', scope: this.queue }
					}
				)
			}

			this.size.w = cW
			this.size.h = cH
	
			// fade the new image in
			new Effect.Appear(
				img.el, {
					queue: { position: 'end', scope: this.queue } 				
				}	
			)
		}

		new Effect.Execute(
			function() {
				// now throw the old image away
				this.cleanAndReturn(this.active)
		
				// mark our new image as active
				this.active = img
		
				// set a timeout for our next switch attempt
				setTimeout( this.trySwitch.bind(this) , opts.delay )
			}.bind(this),
			{ 
				duration : 0.1,
				queue: { position: 'end', scope: this.queue }
			}
		)
	}
}

1;

