/**
 * Copyright (c) 2005 - 2010, James Auldridge
 * All rights reserved.
 *
 * Licensed under the BSD, MIT, and GPL (your choice!) Licenses:
 *  http://code.google.com/p/cookies/wiki/License
 *
 * Last eror free JSLint: 20101109 19:54
 *                        Checked Options: Assume a browser,
 *                                         Allow one var statement per function
 *                                         Disallow undefined variables
 *                                         Disallow dangling _ in identifiers
 *                                         Disallow == and !=
 *                                         Disallow ++ and --
 *                                         Disallow bitwise operators
 *                                         Disallow insecure . and [^...] in /RegExp/
 *                                         Require Initial Caps for constructors
 *                                         Require parens around immediate invocations
 *                        Predefined: window
 */
( function( global )
{
	"use strict";

	var jaaulde;

	/*
	 * jaaulde Namespace preparation - the only var introduced into global space
	 */
	jaaulde = global.jaaulde = ( global.jaaulde || {} );
	jaaulde.utils = jaaulde.utils || {};

	jaaulde.utils.cookies = ( function()
	{
		var defaultOptions, resolveOptions, assembleOptionsString, parseCookies, Constructor;

		defaultOptions = {
			expiresAt: null,
			path: '/',
			domain:  null,
			secure: false
		};

		/**
		* resolveOptions - receive an options object and ensure all options are present and valid, replacing with defaults where necessary
		*
		* @access private
		* @static
		* @parameter Object options - optional options to start with
		* @return Object complete and valid options object
		*/
		resolveOptions = function( options )
		{
			var returnValue, expireDate;

			if( typeof options !== 'object' || options === null )
			{
				returnValue = defaultOptions;
			}
			else
			{
				returnValue = {
					expiresAt: defaultOptions.expiresAt,
					path: defaultOptions.path,
					domain: defaultOptions.domain,
					secure: defaultOptions.secure
				};

				if( typeof options.expiresAt === 'object' && options.expiresAt instanceof Date )
				{
					returnValue.expiresAt = options.expiresAt;
				}
				else if( typeof options.hoursToLive === 'number' && options.hoursToLive !== 0 )
				{
					expireDate = new Date();
					expireDate.setTime( expireDate.getTime() + ( options.hoursToLive * 60 * 60 * 1000 ) );
					returnValue.expiresAt = expireDate;
				}

				if( typeof options.path === 'string' && options.path !== '' )
				{
					returnValue.path = options.path;
				}

				if( typeof options.domain === 'string' && options.domain !== '' )
				{
					returnValue.domain = options.domain;
				}

				if( options.secure === true )
				{
					returnValue.secure = options.secure;
				}
			}

			return returnValue;
		};
		/**
		* assembleOptionsString - analyze options and assemble appropriate string for setting a cookie with those options
		*
		* @access private
		* @static
		* @parameter options OBJECT - optional options to start with
		* @return STRING - complete and valid cookie setting options
		*/
		assembleOptionsString = function( options )
		{
			options = resolveOptions( options );

			return (
				( typeof options.expiresAt === 'object' && options.expiresAt instanceof Date ? '; expires=' + options.expiresAt.toGMTString() : '' ) +
				'; path=' + options.path +
				( typeof options.domain === 'string' ? '; domain=' + options.domain : '' ) +
				( options.secure === true ? '; secure' : '' )
			);
		};
		/**
		* parseCookies - retrieve document.cookie string and break it into a hash with values decoded and unserialized
		*
		* @access private
		* @static
		* @return OBJECT - hash of cookies from document.cookie
		*/
		parseCookies = function()
		{
			var cookies, separated, i, splitOnEquals, name, rawValue, value, unparsedValue;
		
			cookies = {};
			separated = document.cookie.split( ';' );

			for( i = 0; i < separated.length; i = i + 1 )
			{
				splitOnEquals = separated[i].split( '=' );
				
				name = splitOnEquals.shift().replace( /^\s*/, '' ).replace( /\s*$/, '' );
				rawValue = splitOnEquals.join( '=' );

				try
				{
					value = decodeURIComponent( rawValue );
				}
				catch( e1 )
				{
					value = rawValue;
				}

				if( typeof JSON === 'object' && JSON !== null && typeof JSON.parse === 'function' )
				{
					try
					{
						unparsedValue = value;
						value = JSON.parse( value );
					}
					catch( e2 )
					{
						value = unparsedValue;
					}
				}

				cookies[name] = value;
			}
			return cookies;
		};

		Constructor = function(){};

		/**
		 * get - get one, several, or all cookies
		 *
		 * @access public
		 * @paramater Mixed cookieName - String:name of single cookie; Array:list of multiple cookie names; Void (no param):if you want all cookies
		 * @return Mixed - Value of cookie as set; Null:if only one cookie is requested and is not found; Object:hash of multiple or all cookies (if multiple or all requested);
		 */
		Constructor.prototype.get = function( cookieName )
		{
			var returnValue, item, cookies;

			cookies = parseCookies();

			if( typeof cookieName === 'string' )
			{
				returnValue = ( typeof cookies[cookieName] !== 'undefined' ) ? cookies[cookieName] : null;
			}
			else if( typeof cookieName === 'object' && cookieName !== null )
			{
				returnValue = {};
				for( item in cookieName )
				{
					if( typeof cookies[cookieName[item]] !== 'undefined' )
					{
						returnValue[cookieName[item]] = cookies[cookieName[item]];
					}
					else
					{
						returnValue[cookieName[item]] = null;
					}
				}
			}
			else
			{
				returnValue = cookies;
			}

			return returnValue;
		};
		/**
		 * filter - get array of cookies whose names match the provided RegExp
		 *
		 * @access public
		 * @paramater Object RegExp - The regular expression to match against cookie names
		 * @return Mixed - Object:hash of cookies whose names match the RegExp
		 */
		Constructor.prototype.filter = function( cookieNameRegExp )
		{
			var cookieName, returnValue, cookies;

			returnValue = {};
			cookies = parseCookies();

			if( typeof cookieNameRegExp === 'string' )
			{
				cookieNameRegExp = new RegExp( cookieNameRegExp );
			}

			for( cookieName in cookies )
			{
				if( cookieName.match( cookieNameRegExp ) )
				{
					returnValue[cookieName] = cookies[cookieName];
				}
			}

			return returnValue;
		};
		/**
		 * set - set or delete a cookie with desired options
		 *
		 * @access public
		 * @paramater String cookieName - name of cookie to set
		 * @paramater Mixed value - Any JS value. If not a string, will be JSON encoded (http://code.google.com/p/cookies/wiki/JSON); NULL to delete
		 * @paramater Object options - optional list of cookie options to specify
		 * @return void
		 */
		Constructor.prototype.set = function( cookieName, value, options )
		{
			if( typeof options !== 'object' || options === null )
			{
				options = {};
			}

			if( typeof value === 'undefined' || value === null )
			{
				value = '';
				options.hoursToLive = -8760;
			}

			else if( typeof value !== 'string' )
			{
				if( typeof JSON === 'object' && JSON !== null && typeof JSON.stringify === 'function' )
				{
					value = JSON.stringify( value );
				}
				else
				{
					throw new Error( 'cookies.set() received non-string value and could not serialize.' );
				}
			}


			var optionsString = assembleOptionsString( options );

			document.cookie = cookieName + '=' + encodeURIComponent( value ) + optionsString;
		};
		/**
		 * del - delete a cookie (domain and path options must match those with which the cookie was set; this is really an alias for set() with parameters simplified for this use)
		 *
		 * @access public
		 * @paramater MIxed cookieName - String name of cookie to delete, or Bool true to delete all
		 * @paramater Object options - optional list of cookie options to specify ( path, domain )
		 * @return void
		 */
		Constructor.prototype.del = function( cookieName, options )
		{
			var allCookies, name;

			allCookies = {};

			if( typeof options !== 'object' || options === null )
			{
				options = {};
			}

			if( typeof cookieName === 'boolean' && cookieName === true )
			{
				allCookies = this.get();
			}
			else if( typeof cookieName === 'string' )
			{
				allCookies[cookieName] = true;
			}

			for( name in allCookies )
			{
				if( typeof name === 'string' && name !== '' )
				{
					this.set( name, null, options );
				}
			}
		};
		/**
		 * test - test whether the browser is accepting cookies
		 *
		 * @access public
		 * @return Boolean
		 */
		Constructor.prototype.test = function()
		{
			var returnValue, testName, testValue;

			testName = 'cT';
			testValue = 'data';

			this.set( testName, testValue );

			if( this.get( testName ) === testValue )
			{
				this.del( testName );
				returnValue = true;
			}

			return returnValue;
		};
		/**
		 * setOptions - set default options for calls to cookie methods
		 *
		 * @access public
		 * @param Object options - list of cookie options to specify
		 * @return void
		 */
		Constructor.prototype.setOptions = function( options )
		{
			if( typeof options !== 'object' )
			{
				options = null;
			}

			defaultOptions = resolveOptions( options );
		};

		return new Constructor();
	}() );

	if( global.jQuery )
	{
		( function( $ )
		{
			$.cookies = jaaulde.utils.cookies;

			var extensions = {
				/**
				* $( 'selector' ).cookify - set the value of an input field, or the innerHTML of an element, to a cookie by the name or id of the field or element
				*                           (field or element MUST have name or id attribute)
				*
				* @access public
				* @param options OBJECT - list of cookie options to specify
				* @return jQuery
				*/
				cookify: function( options )
				{
					return this.each( function()
					{
						var nameAttrs, $this, getN, n, name, value;

						nameAttrs = ['name', 'id'];
						$this = $( this );

						getN = function()
						{
							n = nameAttrs.shift();
							return !! n;
						};

						while( getN() )
						{
							name = $this.attr( n );
							if( typeof name === 'string' && name !== '' )
							{
								if( $this.is( ':checkbox, :radio' ) )
								{
									if( $this.attr( 'checked' ) )
									{
										value = $this.val();
									}
								}
								else if( $this.is( ':input' ) )
								{
									value = $this.val();
								}
								else
								{
									value = $this.html();
								}

								if( typeof value !== 'string' || value === '' )
								{
									value = null;
								}

								$.cookies.set( name, value, options );

								break;
							}
						}
					} );
				},
				/**
				* $( 'selector' ).cookieFill - set the value of an input field or the innerHTML of an element from a cookie by the name or id of the field or element
				*
				* @access public
				* @return jQuery
				*/
				cookieFill: function()
				{
					return this.each( function()
					{
						var n, getN, nameAttrs = ['name', 'id'], name, $this = $( this ), value;

						getN = function()
						{
							n = nameAttrs.shift();
							return !! n;
						};

						while( getN() )
						{
							name = $this.attr( n );
							if( typeof name === 'string' && name !== '' )
							{
								value = $.cookies.get( name );
								if( value !== null )
								{
									if( $this.is( ':checkbox, :radio' ) )
									{
										if( $this.val() === value )
										{
											$this.attr( 'checked', 'checked' );
										}
										else
										{
											$this.removeAttr( 'checked' );
										}
									}
									else if( $this.is( ':input' ) )
									{
										$this.val( value );
									}
									else
									{
										$this.html( value );
									}
								}
							
								break;
							}
						}
					} );
				},
				/**
				* $( 'selector' ).cookieBind - call cookie fill on matching elements, and bind their change events to cookify()
				*
				* @access public
				* @param options OBJECT - list of cookie options to specify
				* @return jQuery
				*/
				cookieBind: function( options )
				{
					return this.each( function()
					{
						var $this = $( this );
						$this.cookieFill().change( function()
						{
							$this.cookify( options );
						} );
					} );
				}
			};

			$.each( extensions, function( i )
			{
				$.fn[i] = this;
			} );

		}( global.jQuery ) );
	}
}( window ) );
