/* Product rotation script
	Author: Christopher Richard Wojno <cwojno@farcode.com>
	Date: May 26, 2009
	Copyright (c) 2009 Christopher Richard Wojno
	
	Description:
	Loads product information from a JSON-encoded file and displays
	that information as a clickable image and a clickable title with price.
	Also includes effects and automatic rotation of products in list.
	
	Remember: add the JSON mime-type to your webserver if it's too stupid to recognize it, such as IIS
	
	Dependencies:
	* Prototype-1.6.0.3
	* Scriptaculous-1.8.2
*/
var FCRotatingProduct = Class.create({
	initialize: function(name,price,link_url,image_url) {
		this.name = name;
		this.price = new FCUSD(price);
		this.link_url = link_url;
		this.image_url = image_url;
	},
		
	usd_price: function() {
		// format for number
		return "$" + this.price.formatAsPrice();
	},
	
	html_name_link: function() {
		return '<a href="' + this.link_url + '">' + this.name + '</a>';
	},
	
	html_img_link: function() {
		return '<a href="' + this.link_url + '"><img src="' + this.image_url + '" alt="' + this.name + '" /></a>';
	},
	
	inspect: function() {
		return 'name:' + this.name + ',price:$' + this.price.formatAsPrice() + ',link_url:'+this.link_url+',image_url:'+this.image_url+"\n";
	}
	
});

var FCUSD = Class.create({
	initialize: function(price) {
		this.price = price;
	},
	
	formatAsPrice: function() {
		var strprice = this.price.toFixed(2).toArray();
		var ret = new String();
		ret += strprice[0];
		for( var i = 1; i < strprice.size() - 3; ++i ) {
			// Should insert ,
			if( ( ( ( strprice.size() - 3 ) - i ) % 3 ) == 0 ) {
				ret += ',';
			}
			ret += strprice[i];
		}
		for( var i = strprice.size() - 3; i < strprice.size(); ++i ) {
			ret += strprice[i];
		}
		
		return ret;
	}
	
});












/* FCProductRotator
	Support class, do not use directly */
var FCProductRotator = Class.create(Ajax.Request,{
  initialize: function($super, container, button_container, url, options, onLoad, onFail, debug ) {
    this.container = container;
	this.button_container = button_container;
	this.config = null;
	this.current_number = 0;
	this.rotation_pe = null;
	this.products = null;
	this.onFail = onFail;
	this.onLoad = onLoad;
	if( debug == null ) {
		this.debug = false;
	} else {
		this.debug = debug;
	}
    return $super(url, options);
  },
  
  clear_container: function() {
	FCProductRotator.clear_element( $(this.container) );
  },
  clear_buttons: function() {
	FCProductRotator.clear_element( $(this.button_container) );
  },
  activate_transition: function(event) {
	/* if they hover, stop transitions so they can view */
	this.pause_rotation();
	this.transition_to(
		new Number(
			FCProductRotator.product_number_from_button_id(event.element().id)
		)
	);
  },
  transition_to: function( tonumber ) {
	/* do not start transition if current is selected */
	if( tonumber != this.current_number ) {
		this.log(tonumber);
		var currentid = FCProductRotator.make_product_container_id(this.config,this.current_number);
		var nextid = FCProductRotator.make_product_container_id(this.config,tonumber);
		
		var currentbutton = FCProductRotator.make_button_container_id(this.config,this.current_number);
		var nextbutton = FCProductRotator.make_button_container_id(this.config,tonumber);
		$(currentbutton).up().removeClassName('selected');
		$(nextbutton).up().addClassName('selected');
		
		if( this.config.transition_duration_in_seconds != 0 ) {
			/* finite duration */
			/* Cut in 1/2, each transition given equal time */
			var each_duration = this.config.transition_duration_in_seconds/2.0;
			Effect.Fade( currentid,
			{ 'queue': { 'position': 'end', 'scope': nextid }, 'duration': each_duration });
			Effect.Appear( nextid,
			{ 'queue': { 'position': 'end', 'scope': nextid }, 'duration': each_duration });
		} else {
			/* no duration, change is immediate */
			$(currentid).hide();
			$(nextid).show();
		}
		this.current_number = tonumber;
	}
  },
  pause_rotation: function() {
	this.rotation_pe.stop();
  },
  resume_rotation: function() {
	this.rotation_pe = new FCProductUpdaterLogic( this, FCProductRotator.rotation_logic, this.config.seconds_to_wait_before_transition );
  },
  next_product_number: function() {
	return ( this.current_number + 1 ) % this.products.size();
  },
  has_failed: function(message) {
	if( this.onFail != null ) {
		this.onFail( message );
	}
  },
  has_loaded: function() {
	if( this.onLoad != null ) {
		this.onLoad();
	}	
  },
  should_debug: function() {
	return this.debug;
  },
  debug_id_name: function() {
	return 'product_rotator_debug_console';
  },
  log: function( msg ) {
	if( this.should_debug() ) {
		var now = new Date();
		var linelimit = 10;
		var sep = '<br/>';
		var ctxt = new String($(this.debug_id_name()).innerHTML);
		var matches = $A(ctxt.match( /(<br\/>)/ ) ); /* find and convert to array */
		
		if( matches.size() > linelimit + 1 ) {
			ctxt = ctxt.substr( ctxt.indexOf( sep ) );
		}
		$(this.debug_id_name()).update( $(this.debug_id_name()).innerHTML + sep +
		new String(now.getHours()) + ':' +
		new String(now.getMinutes()) + ':' +
		new String(now.getSeconds()) + '> '
		+ msg );
	}
  }
  
});
FCProductRotator.clear_element = function(elemp) {
	/* clear the text */
	elemp.update('');
	/* remove target child elements */
	elemp.childElements().each( function (elem) {
		elem.remove();
	} );
}

FCProductRotator.product_number_from_button_id = function( theid ) {
	var regex = /(\d+)$/i;
	return theid.match( regex )[1];
}

FCProductRotator.rotation_logic = function( pe ) {
	pe.arg.transition_to( pe.arg.next_product_number() );
}






/** Create Product Rotater
	Use this function to fetch the configuration and populate pre-existing structures with
	the dynamic content
	@param[in] container_id			The HTML tag id for the element that will contain
									the images and text
	@param[in] button_container_id	The HTML tag id for the element that will contain
									the buttons
	@param[in] url					The URL that points to the configuration file,
									must be located on same domain for security (XSS) reasons
	@param[in] onLoad (optional)	Runs this function just before starting the fetch
									for the configuration. Lets you change things as you see
									fit just before the request is made. You don't have to use it
	@param[in] onFail(optional)		Runs this function if anything goes wrong (AJAX-wise or bad data-wise)
	@param[in] debug (optional)		Turns on debugging annotations if true. Leave blank or false to turn off
	@return FCProductRotator object used in the request
*/
FCProductRotator.create = function( container_id, button_container_id, url, onLoad, onFail, debug ) {
	// load configuration
	
	/* Create a call-back function for the transitions & buttons */
	
	
	/* Fetch the products */
	return new FCProductRotator( container_id, button_container_id, url, { method: 'get',
						onSuccess: FCProductRotator.load_products,
						onFailure: FCProductRotator.failed_to_load_products,
						onCreate: onLoad }, onLoad, onFail, debug );
}







FCProductRotator.load_products = function(transport) {
	try{
		var products_json = (transport.responseText).evalJSON();
		var products = transport.request.products =
			FCProductRotator.products_from_JSON( products_json );
		var config = FCProductRotator.config_from_JSON( products_json );
		transport.request.config = config;
		
		var product_tree = FCProductRotator.create_product_DOM_tree(
			products, config, transport.request );
		var button_tree = FCProductRotator.create_button_DOM_tree(
			products.size(), config, transport.request );
		
		// mark first product & button as visible
		var names = product_tree.childElements()[0].show();
		button_tree.childElements()[0].addClassName('selected');
		
		/* clear the targets */
		transport.request.clear_container();
		transport.request.clear_buttons();
				
		/* Add our products into the page */
		$(transport.request.container).appendChild( product_tree );
		
		/* Add buttons to our page */
		$(transport.request.button_container).appendChild( button_tree );
		
		/* Add debugging display */
		if( transport.request.should_debug() ) {
			$(transport.request.container).appendChild(
				new Element('div',{'id': transport.request.debug_id_name()})
			);
		}
		
		/* Transition to the first product */
		Effect.Appear( transport.request.container,
			{ 'queue':  { 'position': 'end', 'scope': transport.request.container } } );
			
		/* start the rotation array */
		transport.request.resume_rotation();
		
		/* Call the callback */
		transport.request.has_loaded();
		
	} catch( se ) {
	/* FIXME */
		transport.request.has_failed( se );
	}
}







FCProductRotator.make_product_container_id = function( config, i ) {
	return config.element_id_prefix + '_' + new String(i);
}
FCProductRotator.make_button_container_id = function( config, i ) {
	return config.element_id_prefix + '_button_' + new String(i);
}


FCProductRotator.failed_to_load_products = function(transport) {
	transport.request.onFail();
}

FCProductRotator.products_from_JSON = function (json) {
	var products = new Array();
	json[0].each( function(product) {
		products.push( new FCRotatingProduct(
			product.name,
			product.price,
			product.link_url,
			product.image_url) );
	} );
	return products;
}

FCProductRotator.config_from_JSON = function (json) {
	return json[1];
}

FCProductRotator.create_product_DOM_tree = function( products, config, request ) {
	/* build the DOM tree for the products */
	var tree = new Element( 'div' );
	var c = 0;
	products.each( function(product) {
		/* new item container */
		var cont = new Element('div', { 'style': 'position:static;top:0;display:block;zoom:1;',
			'id': (FCProductRotator.make_product_container_id(config,c)) } );
		/* Create A tag for IMG */
		cont.hide();
		var aforimg = new Element('a', { 'href': product.link_url } );
		/* Create IMG */
		aforimg.appendChild(
			new Element('img', { 'src': product.image_url, 'alt': product.name } )
		);
		/* Add A{IMG} to container */
		cont.appendChild(
			aforimg
		);
		/* Create P for A */
		var prepend_text_to_price = config.prepend_text_to_price || ' ';
		var append_text_to_text_link = config.append_text_to_text_link || '';
		
		
		var product_title = new Element('span', { 'class': 'title' } );
		var product_price = new Element('span', { 'class': 'price' } );
		var before_price = new Element('span', { 'class': 'before_price' } );
		var after_price = new Element('span', { 'class': 'after_price' } );
		var product_link = new Element('a', { 'href': product.link_url } );
		product_title.update( product.name );
		product_price.update( product.usd_price() );
				
		/*var text = new Element( 'div', { 'class': 'product_information' } );
		text.appendChild( product_link );
		product_link.appendChild( product_title );
		if( prepend_text_to_price != '' ) {
			before_price.update( prepend_text_to_price );
			product_link.appendChild( before_price );
		}
		product_link.appendChild( product_price );
		if( append_text_to_text_link != '' ) {
			after_price.update( append_text_to_text_link );
			product_link.appendChild( after_price );
		}
		
		/* Add P{A} to container * /
		cont.appendChild(
			text
		);*/
		
		/* Add event listeners */
		Event.observe( cont, 'mouseover', request.pause_rotation.bind(request) );
		Event.observe( cont, 'mouseout', request.resume_rotation.bind(request) );
		
		tree.appendChild( cont );
		c += 1;
	} );
	return tree;
}

FCProductRotator.create_button_DOM_tree = function( count, config, request ) {
	var tree = new Element('ul');
	var elem = null;
	var aelem = null;
	var hash = null;
	
	for( var i = 0; i < count; i++ ) {
		hash = {};
		if( i == 0 ) {
			hash = { 'class': 'first' };
		} else if( i == count - 1 ) {
			hash = { 'class': 'last' };
		}
		elem = new Element( 'li', hash );
		aelem = new Element(
			'a',
			{ 'href': '#', 'id': FCProductRotator.make_button_container_id( config, i ) } ).update(
				new String( i + 1 )
		);
		
		Event.observe( aelem, 'mouseover', request.activate_transition.bind(request) );
		Event.observe( aelem, 'mouseout', request.resume_rotation.bind(request) );
				
		elem.appendChild( aelem );
		tree.appendChild( elem );
	}
	return tree;
}




var FCProductUpdaterLogic = Class.create(PeriodicalExecuter,{
	initialize: function( $super, arg, fun, interval ) {
		this.arg = arg;
		return $super( fun, interval );
	}
});
