var adj = {};

/**
 * @static
 * A method for sniffing out whether we're running on our home URL
 */

adj.isDev = function() {
	
	//safest to default to false
	var isDev = false;

	var url = adj.siteConfig.productionHost;

	if ( !url ) {
		//throw an error
	}
	else {
		isDev = ( document.location.host.indexOf( url ) === -1 );
	}
	
	//memoize
	adj.isDev = function() {
		return isDev;
	};
	
	return isDev;
	
};

/**
 * @static
 * Namespacing utility
 */
adj.register = function() {

	var valid = /^([\$a-z]+[a-z0-9]?)+(\.[a-z]+[a-z0-9]?)*$/i;

	return function( input ) {
		
		var err = initialErr = "";

		//Validate input
		if ( ( "string" !== typeof( input ) ) || !valid.test( input ) ) {
			err = "Invalid input";
		}

		//Create namespace
		if ( err !== initialErr ) {
			
			//throw error
			
			return undefined;

		}
		else {

			var levels = input.split(".");
			var scope = window;

			for ( var i = 0, j = levels.length; i < j; i++ ) {

				var level = levels[ i ];

				if ( "undefined" === typeof scope[ level ] ) {
					scope[ level ] = {};
				}

				scope = scope[ level ];
			}
			return scope; 
		}

	};
}();

/**
 * Empty function for setting up empty callbacks, etc.
 */
adj.emptyFunction = function() {};

( function( $ ) {
	
	/**************************************************************************
	 * adj.analytics package
	 **************************************************************************/
	
	adj.register( "adj.analytics" );
	
	adj.analtyics = {

		/**
		 * Automatically track pageviews in Google Analtyics
		 */
		init: function() {
			window._gaq = window._gaq || [];

			window._gaq.push( [ "_setAccount", adj.siteConfig.googleId ] );
			window._gaq.push( [ "_trackPageview" ] );
		},

		/**
		 * Automatically track pageviews in Google Analtyics
		 */
		autoTrack: function() {
			
			var METHODNAME = "adj.analytics.autoTrack";

			this.init();

			//don't record analytics from dev site
			if ( adj.isDev() ) {
				adj.log.info( "Dev site - no analytics", METHODNAME );
				return;
			}

			adj.log.info( "Appending analytics script", METHODNAME );

			//create script tag
			var ga = document.createElement( "script" );
			ga.type = "text/javascript";
			ga.async = true;
			ga.src = (
				"https:" == document.location.protocol ?
				"https://ssl" :
				"http://www"
			) + ".google-analytics.com/ga.js";

			//append script tag
			(
				document.getElementsByTagName( "head" )[ 0 ] ||
				document.getElementsByTagName( "body" )[ 0 ]
			).appendChild( ga );

		}
	};
	
	/**************************************************************************
	 * adj.dom package
	 *
	 * Convenience wrappers and utilities for working with Pure templates
	 **************************************************************************/
	
	adj.register( "adj.dom" );

	/**
	 * Convenience wrapper for jquery.jqcanvas.
	 */
	adj.dom.Canvas = Base.extend(

		//Instance interface

		{
			
			$node: null,
			
			opts: null,
			
			callback: null,
			
			constructor: function( id, callback, options, params ) {
				
				var METHODNAME = "adj.dom.Canvas.constructor";

				this.opts = $.extend( true, {}, {
					pluginOptions: {},
					forceRebuild: true,
					autoDraw: false,
					initialWaitTime: 25,
					waitTime: 25
				}, options );
				
				this.params = params || {};
				
				this.$node = $( "#" + id );

				if ( this.$node.length === 0 ) {

					adj.log.info( "Building node", METHODNAME );

					this.$node = $( "<div />" ).attr( "id", id );

					$( "body" ).prepend( this.$node );
				}
				
				var that = this;
				
				this.callback = function( canvas, width, height ) {
					
					if ( !canvas.getContext ) {
						adj.log.error( "Failed to get canvas context", METHODNAME );
						return;
					}

					var ctx = canvas.getContext( "2d" );
					
					callback.call( that, ctx, width, height, canvas );
					
				};
				
				//Canvas element will be created and drawn immediately
				if ( this.opts.autoDraw ) {
					this.draw( true );
				}
				
			},
			
			draw: function( isInitial ) {
				if ( this.opts.forceRebuild ) {
					this.$node.html( "" );
				}

				var that = this;
				var waitTime = ( isInitial ? this.opts.initialWaitTime : this.opts.waitTime );

				setTimeout( function() {
					that.$node.jqcanvas( that.callback, that.opts.pluginOptions );
				}, waitTime );
			},
			
			setParam: function( key, value ) {
				this.params[ key ] = value;
			},
			setParams: function( obj ) {
				var _that = this;
				$.each( obj, function( key, value ) {
					_that.setParam( key, value );
				} );
			},
			clearParams: function( obj ) {
				this.params = {};
			}

		},

		//Class interface

		{
			
		}
	);

	/**
	 * 
	 */
	adj.dom.appendNode = function( selector, markup, options ) {
		
		var METHODNAME = "adj.dom.appendNode";
		
		var $root = $( selector );

		if ( $root.length === 0 || !markup ) {
			adj.log.error( "Missing root or markup so exiting", METHODNAME );	
			return;
		}
		
		var opts = options || {};

		var $node = adj.dom.setNodeAttributes( markup, opts.attributes );
		
		$root.append( $node );
		
	};

	/**
	 * Append a CSS file to the document HEAD or BODY
	 */
	adj.dom.appendStylesheet = function( href, options ) {
		
		var selector = $( "head, body" ).eq( 0 );
		
		var markup = "<link/>";
		
		var opts = $.extend( true, {}, options, {
			attributes: {
				rel: "stylesheet",
				type: "text/css",
				href: href
			}
		} );
			
		adj.dom.appendNode( selector, markup, opts );

	};

	/**
	 * Remove a Pure template from the DOM and return it for further use
	 */
	adj.dom.extractTemplate = function( outerSelector ) {
		return $( outerSelector + "> *" ).remove();
	};
	
	/**
	 * Remove a Pure template from the DOM, render it and re-append it
	 */
	
	//TODO - the $node it returns isn't actually the copy that's in the DOM!
	
	adj.dom.extractAndRenderTemplate = function( outerSelector, context, directive, options ) {

		var defaults = {
			appendSelector: 'body',
			appendMethod: 'append'
		};
		
		var opts = $.extend( true, {}, defaults, options );
		
		var $node = adj.dom.extractTemplate( outerSelector );
		
		if ( $node.length > 0 ) {
			
			//todo - error handling if appendToMethod not valid

			$( opts.appendSelector ).eq( 0 )[ opts.appendMethod ]( $node );
			
			$node.render( context, directive );
			
		}
		else {
			//log an error
		}
		
		return $node;
		
	};
	
	/**
	 * 
	 */
	adj.dom.instantiateTypekit = function() {
		
		var METHODNAME = "adj.dom.instantiateTypekit";
		
		var src = "http://use.typekit.com/" + adj.siteConfig.typekitId + ".js"
		
		$.getScript( src, function() {
			
			try {
				Typekit.load();
				adj.log.info( "SUCCESS - Typekit.load", METHODNAME );
			}
			catch( e ) {
				adj.log.error( "FAILURE - Typekit.load", METHODNAME );
			}
			
		} );
		
	};

	/**
	 * 
	 */
	adj.dom.setNodeAttributes = function( node, attributes ) {
		
		var METHODNAME = "adj.dom.setNodeAttributes";

		var $node = $( node );
		
		if ( $node.length === 0 || !attributes ) {
			adj.log.info( "Missing node or attributes so returning early", METHODNAME );
			return $node;
		}
	
		$.each( attributes, function( key, value ) {
			if ( value !== null && value !== undefined ) {
				$node.attr( key, value );
			}
		} );
		
		return $node;
		
	};
	
	/**
	 * A method for dynamically adding and removing stylesheets from the DOM
	 */

	adj.dom.styleswitcher = {

		getInstance: function( initialValue, whitelist, path ) {

			var $body = $( "body" );
			var $head = $( "head" );

			var that = {

				urlPattern: '${path}${filename}.css',

				hideClass: "styleswitcher_hidden",

				noneValue: 'none',

				whitelist: null,

				current: null,

				path: null,

				$stylesheet: null,

				init: function( initialValue, whitelist, path ) {
					
					var METHODNAME = "adj.dom.styleswitcher.init";

					this.whitelist = $.makeArray( whitelist );

					if ( !whitelist || ( !path && path !== "" ) || !this.test( initialValue ) ) {
						adj.log.error( "Missing parameters", METHODNAME );
						return;
					}

					this.path = path;
					this.current = initialValue;

					this.createStylesheet();

					setTimeout( function() {
						$body.removeClass( this.hideClass );
					}, 500 );

				},

				getUrl: function( value ) {

					var actualValue = (
						( value === this.noneValue ) ?
						"" :
						value
					);

					return this.path + actualValue + ".css";
				},

				createStylesheet: function() {
					
					adj.dom.appendStylesheet( null, {
						attributes: {
							title: this.current
						}
					} );

					this.$stylesheet = $head.find( "link[title=" + this.current + "] " );

					this.replace( this.current );

				},

				replace: function( value ) {

					if ( !this.test( value ) ) {
						//log an error
						return;
					}
					this.current = value;

					var url = this.getUrl( value );

					this.$stylesheet.attr( "href", url );

				},

				test: function( name ) {
					var ok = ( $.inArray( name, this.whitelist ) >= 0 );
					return ok;
				}

			};

			that.init( initialValue, whitelist, path );

			return that;

		}

	};

	
	/**************************************************************************
	 * adj.event package
	 *
	 * Convenience wrappers and utilities for jQuery event binding
	 **************************************************************************/
	
	adj.register( "adj.event" );
	
	adj.event = function() {
		
		var that = {
		
			$node: null,

			getGlobalEventNode: function() {
				if ( !this.$node || this.$node.length === 0 ) {
					this.$node = $( "body" );
				}
				return this.$node;
			},

		 	bind: function( eventName, handler ) {
				this.getGlobalEventNode().bind( eventName, handler );
			},
		
		 	triggerHandler: function( eventName ) {
				this.getGlobalEventNode().triggerHandler( eventName );
			}

		};
		
		return that;
		
	}();

	/**************************************************************************
	 * adj.ff - a callback function for the JSONP Friendfeed API
	 **************************************************************************/
	
	adj.register( "adj.ff" );
	
	adj.ff = {
		
		/**
		 * @property {String} href The URL for the FriendFeed JSONP service
		 */
		href: "http://friendfeed.com/api/feed/user/armchairdj?callback=adj.ff.processData",
		
		/**
		 * Main public method - load a JSONP script via the FriendFeed API.
		 * Script tag will trigger its own callback to process the data.
		 */
		load: function() {
			
			var METHODNAME = "adj.ff.load";
			
			adj.log.info( "About to load FriendFeed data", METHODNAME );
			
			var that = this;
			
			$.getScript(
				that.href,
				that.confirmSuccess
			);
				
		},
		
		/**
		 * Callback for jQuery.getScript
		 */
		confirmSuccess: function( data, textStatus ) {
			
			var METHODNAME = "adj.ff.confirmSuccess";
			
			adj.log.info( "Successfully processed FriendFeed data.", METHODNAME );

		},
		
		/**
		 * Callback for FriendFeed JSONP API
		 */
		processData: function( data ) {
			
			var METHODNAME = "adj.ff.processData";
			
			adj.log.info( "About to process FriendFeed data.", METHODNAME );
			
			var that = this;

			$( function() {
	
				$( '#news > div' )
					.attr( 'id','friendfeed' )
					.find( 'ul' )
					.friendfeed( data, that.pluginOptions )
				;

			} );
			
		},
		
		/**
		 * @property {Object} pluginOptions The options to pass to the
		 * $.friendfeed plugin - provides processing directives for formatting
		 * specific kinds of FriendFeed entries
		 */
	    pluginOptions: {

			filters: [
				{
					id: 'blog',
					to: 'Agile Ajax blog post',
					test: /agileajax/i
				},
				{
					id: 'blog',
					to: 'Armchair DJ post',
					test: /armchairdj/i
				},
				{
					id: 'tumblr',
					to: 'Tumblr post on Armchair Vagabond'
				},
				{
					id: 'amazon',
					to: 'Amazon wish list addition'
				},
				{
					id: 'digg',
					to: 'Digg recommendation'
				},
				{
					id: 'slideshare',
					to: 'Slideshare slideshow'
				},
				{
					id: 'flickr',
					to: 'Flickr tattoo photos'
				},
				{
					id: 'linkedin',
					to: 'LinkedIn profile updae'
				},
				{
					id: 'yelp',
					to: 'Yelp review'
				},
				{
					id: 'twitter',
					to: 'Tweet'
				},
				{
					id: 'internal',
					to: 'FriendFeed post'
				}
			]

		}

	};
	
	/**************************************************************************
	 * adj.log package
	 **************************************************************************/
	
	adj.register( "adj.log" );
	
	//TODO - create a queue of log messages so that if we
	//enable in production, we'll see the full log stack
	
	adj.log = ( function() {
		
		var initialized = false;
		var enabled = false;
		
		var init = function() {
			enabled = adj.isDev();
			initialized = true;
		};
		
		var write = function( method, msg, caller, params ) {

			$( function() {

				if ( !initialized ) {
					init();
				}

				if ( !enabled || !log || !log[ method ] ) {
					return;
				}
				
				if ( caller ) {
					msg = caller + ": " + msg;
				}
				if ( params ) {
					msg = msg + params;
				}
				
				msg = "<pre>" + msg + "</pre>";

				$( function() {
					log[ method ]( msg );
				} );
				
			} );			
		};

		return {
			info: function( msg, caller, params ) {
				write( "info", msg, caller, params );
			},
			debug: function( msg, caller, params ) {
				write( "debug", msg, caller, params );
			},
			warn: function( msg, caller, params ) {
				write( "warn", msg, caller, params );
			},
			error: function( msg, caller, params ) {
				write( "error", msg, caller, params );
			},
			enable: function() {
				enabled = true;
			}
		};
			
	} )();
	
	/**************************************************************************
	 * adj.siteConfig package
	 * 
	 * A place to stash site-wide configuration variables such as prodution URL,
	 * analytics handles, etc.
	 **************************************************************************/
	
	adj.register( "adj.siteConfig" );
	
	adj.siteConfig = {
		
		productionHost: null,
		googleId: null,
		
		/**
		 * @static
		 * A method for setting values of adj.siteConfig members
		 */
		init: function( options ) {

			$.extend( true, this, options );

		}
		
	};
	
	/**************************************************************************
	 * adj.utils package
	 * 
	 * A place to stash simple static utilities
	 **************************************************************************/
	
	adj.register( "adj.utils" );

} )( jQuery );

