var jCakeObj = new function()
{
    var self = this;

    this.baseUrl   = null;
    this.jsBaseUrl = null;
    this.jCakeUrl  = null;

    this.action = '';
    this.view   = '';

    this.controllers = [];
    this.models      = [];
    this.views       = [];
    this.elements    = [];
    this.vars        = [];

    this.domReady = false;

    this.initialize = function()
    {
        self.setBase();
        self.parseParams();

        jQuery(document).ready(function()
        {
            self.triggerGlobalEvent('domReady');
            self.triggerGlobalEvent('elementsReady');
        });
        
		jQuery(document).bind('ajaxSuccess', function()
		{
			// Delay the event a bit to give room for content updates
			
			setTimeout(function() {
				self.triggerGlobalEvent('domReady');
	        }, 100);
		});
	}

    this.Events = function(){};

    this.triggerGlobalEvent = function(name, params)
    {
    	jQuery.each(jCake.controllers, function()
        {
            if (eval('typeof ' + this + "Controller") != 'undefined')
            {
	        	var ControllerObj = eval(this+"Controller");

	            if (ControllerObj.Events[name])
	                ControllerObj.Events[name].apply(ControllerObj, params || []);

	            for(var view in ControllerObj.Views)
	            {
	                var ViewObj = ControllerObj.Views[view];

	                if (ViewObj.Events[name])
	                    ViewObj.Events[name].apply(ViewObj, params || []);
	            }
            }
        });

        jQuery.each(self.elements, function()
        {
            if (eval('typeof ' + this + "Element") != 'undefined')
            {
	            var ElementObj = eval(this+'Element');
	
	            if (ElementObj != null && typeof ElementObj == 'object' && ElementObj.Events && ElementObj.Events[name])
	            {
	                ElementObj.Events[name].apply(ElementObj, params || []);
	            }
	         }
        });

        if (jCake.Events[name])
        {
        	jCake.Events[name].apply(jCake, params);
        }
    }

    this.parseParams = function()
    {
        jQuery.extend(self, jCakeParams);

        jQuery.each(self.controllers, function()
        {
            self.invokeController(this, self.action, self.view);
        });
    }

    this.addVars = function(vars, base)
    {
        base = base || 'vars';
        base = jCake[base];

        if (base.constructor == Array && vars.constructor == Array)
        {
            base.push.apply(base, vars);
            return;
        }

        for (var key in vars)
        {
            if (vars[key].constructor == Array)
            {
                if (!base[key])
                {
                    base[key] = vars[key];
                }
                else if (base[key].constructor == Array)
                {
                    base[key].push.apply(base[key], vars[key]);
                }
            }
        }
    }

    this.invokeController = function(controllerName, action, view)
    {
        var ControllerObj = null;

    	if (eval('typeof ' + controllerName + "Controller") != 'undefined')
        {
	    	var ControllerObj = eval(controllerName+"Controller");

	        ControllerObj.name = controllerName;
	        jCake.extendClass(ControllerObj, new Controller);

	        if (action && ControllerObj.name!=='App')
	        {
	            ControllerObj.action = action;
	        }
        }

        if (ControllerObj != null)
        {
	        if (view && ControllerObj.name!=='App')
	        {
	            ControllerObj.view = view;
	            ControllerObj.View = eval('new '+ControllerObj.name+view+'View');
	            jCake.extendClass(ControllerObj.View, new View);
	            ControllerObj.View.vars = ControllerObj.viewVars;
	            ControllerObj.View.Controller = ControllerObj;
	            ControllerObj.Views[view] = ControllerObj.View;
	        }

	        jQuery.each(self.models, function()
	        {
	            ControllerObj.Models[this] = eval('new '+this);
	        });

	        ControllerObj.params = jCake.params;
	        ControllerObj.namedParams = jCake.namedParams;

	        if (ControllerObj.Events && ControllerObj.Events.beforeFilter)
	            ControllerObj.Events.beforeFilter.call(ControllerObj);

	        ControllerObj.views = self.views;
        }
    }

    this.setBase = function()
    {
		var javascriptFile = 'j_cake';

		// In case we are being packaged, use the package file as file locator

		if (typeof(__PackagedJavaScriptFile) != 'undefined')
		{
			javascriptFile = __PackagedJavaScriptFile;
		}

		if (!javascriptFile.match(/.*\.js$/))
		{
			javascriptFile += '.js';
		}

		if (javascriptFile.charAt(0) == '/')
		{
			javascriptFile = javascriptFile.substr(1);
		}

		var regex = new RegExp('\\/([^\\/]*)\\/' + javascriptFile.replace(/\./g, '\\.') + '(\\?.*)?$');
		
		jQuery('script').each(function()
		{
        	var script = this;
        	
        	if (script.src)
        	{
	            if (typeof(__PackagedJavaScriptFile) != 'undefined' && script.src.match(regex))
	            {
	            	var path = script.src.replace(new RegExp(javascriptFile.replace(/\./g, '\\.') + '(\\?.*)?$'),'')
	
	            	self.jsBaseUrl = path;
	            	self.baseUrl   = path.replace(/[^\/]+\/$/, '');
	            	self.jCakeUrl = script.src;
	            }
	            else if (script.src.match(/jcake\/j_cake\.js(\?.*)?$/))
	            {
	                self.jsBaseUrl = script.src.replace(/jcake\/j_cake\.js(\?.*)?$/,'');
	                self.baseUrl   = self.jsBaseUrl.replace(/[^\/]+\/$/, '');
	                self.jCakeUrl = script.src;
	            }
	        }
        });
        
        if (self.baseUrl == null)
        {
        	alert('Warning: could not determine baseUrl');
        }
    }

    this.extendClass = function(child, parent)
    {
        for (var property in parent)
        {
            if (!child[property])
            {
                child[property] = parent[property];
            }
            else if(property!=='bindAsEventListener' && property!=='bind')
            {
                arguments.callee(child[property], parent[property]);
            }
        }
    };

    // Returns a system URL
    this.url = function(url)
    {
        if (url.match(/^\//))
        {
            url = url.substr(1);
        }

        return jCake.baseUrl+url;
    }

    this.log = function()
    {
		/*
        try
        {
            console.log.apply(console.log, arguments);
        }
        catch(e)
        {
            return false;
        }
		*/
    }

    this.httpSerialize = function(items, parentName)
    {
        var serializedItems = [];

        var serialize = arguments.callee;

        var encodeItem =  function(key, value)
        {
           	if (value && (value.constructor == Array || value.constructor == Object)) {
               	return serialize(value, key);
      		}
            return key+"="+encodeURIComponent(value);
        }

        if (items.constructor == Array)
        {
            parentName = parentName || 'item';

			if (items.length == 0) {
				// handle empty arrays
				serializedItems.push(encodeItem(parentName, ''));
			} else {
	            for (var i = 0; i < items.length; i++) {
	                var key = parentName+'['+i+']';
	                var value = items[i];

	                serializedItems.push(encodeItem(key, value));
	            }
			}
        }
        else
        {
            parentName = parentName || '';

            for (var key in items)
            {
                var value = items[key];

                if (parentName)
                {
                    serializedItems.push(encodeItem(parentName+'['+encodeURIComponent(key)+']', value));
                }
                else
                {
                    serializedItems.push(encodeItem(encodeURIComponent(key), value));
                }
            }
        }

        return serializedItems.join("&");
    }

	/**
	 * Evaluates the json text and returns a JavaScript object.
	 *
	 * @param string json JSON string.
	 * @returns The JavaScript object, or null if the JSON string isn't valid.
	 */
	this.evalJson = function( json )
	{
		try
		{
			var obj = eval('('+json+')');
			return obj;
		}
		catch(e) {jCake.log('Error evaluating JSON response:', json)}

		return null;
	}

	/**
	 * Function used to preload any amount of images easily
	**/
    this.preload = function()
    {
        for(var i = 0; i < arguments.length; i++)
        {
            jQuery("<img>").attr("src", arguments[i]);
        }
    }

	/**
	 * Download the specified URL without redirecting browser.
	 *
	 * @param string url	Full URL from where the download will be issued.
	 */
	this.download = function ( url )
	{
		body = jQuery('body');
		iframe = document.createElement('IFRAME');

		iframe.src = url;
		iframe.width = 1;
		iframe.height = 1;

		body.append(iframe);
	}

	/**
	 * Print the specified URL without redirecting browser.
	 *
	 * @param string url	Full URL from where the document will be printed.
	 * @param bool preview	Use a preview screen instead of direct printing.
	 * @param object options	Settings to apply to popup overriding defaults.
	 * @param bool pdf	Force previewing of a PDF file.
	 */
	this.print = function ( url, preview, options, pdf )
	{
		// If URL is an anchor, get rid of anchor part

		if (url.indexOf('#') != -1)
		{
			url = url.substring(url.indexOf('#') + 1);
		}

		// Determine if we are printing a PDF file

		if ( pdf || url.toLowerCase().indexOf('.pdf') == (url.length - 4))
		{
			pdf = true;
		}

		// If previewing PDF, go through NemoFilesController with this URL

		if (pdf && preview)
		{
			url = jCake.url( '/nemo_files/pdf_preview' ) + '?pdf&source=' + encodeURIComponent(url);
		}

		// Either print or show preview

		if (preview != true)
		{
			body = jQuery('body');
			iframe = document.createElement('IFRAME');

			url += (url.indexOf('?') != -1 ? '&' : '?') + 'print';

			iframe.src = url;
			iframe.width = 1;
			iframe.height = 1;

			body.append(iframe);
		}
		else
		{
			url += (url.indexOf('?') != -1 ? '&' : '?') + 'print&preview';

			settings = {
				width: 816, // 8.5 inches wide at 96 dpi
				height: 700,
				pdfWidth: 816, // 8.5 inches wide at 96 dpi
				pdfHeight: 625,
				toolbar: false,
				scrollbars: 1,
				resizable: 1,
				callback: null,
				printToolbar: true
			};

			if ( pdf )
			{
				// add extra width to default to fit embeded pdf controls
				settings.width += 15;
			}

			if ( ! options.toolbar )
			{
				// remove the toolbar height
				settings.height -= 20;
			}

			if ( options && options != null && typeof(options) != 'string' )
			{
				jQuery.extend(settings, options);
			}

			if (pdf)
			{
				url += (url.indexOf('?') != -1 ? '&' : '?') + 'width=' + settings.pdfWidth + '&height=' + settings.pdfHeight;
			}

			if (!settings.printToolbar)
			{
				url += (url.indexOf('?') != -1 ? '&' : '?') + 'notoolbar';
			}

			var parameters = 'height=' + settings.height +
							 ',width=' + settings.width +
							 ',toolbar=' + settings.toolbar +
							 ',scrollbars=' + settings.scrollbars +
							 ',resizable=' + settings.resizable;

			// Build window
			var printWindow = window.open(url, 'print_preview_window', parameters);

			// Set callback
			printWindow.printCallback = function()
            {
                setTimeout(function()
                {
                	settings.callback();
                }, 1);
            }

			// Focus window
			printWindow.focus();
		}
	}

	/**
	 * Issues a popup window to edit notes attached to a model record
	 *
	 * @param string model Model in its underscore form (e.g: contact, opportunity_feedback)
	 * @param integer id The ID
	 * @param object properties Set of settings
	 */
	this.notes = function(model, id, properties)
	{
		// Load settings

		var settings = {
			add: false
			, edit: false
			, id: model + '_' + id + '_notesw'
			, label: 'List All Notes'
			, title: null
			, watchResize: false
			, width: 1200
			, height: 400
		};

		if (properties != null)
		{
			settings = jQuery.extend(settings, properties);
		}

		// Build URL

		var url = '/notes/index/' + model + '/' + id + '?' + 'add=' + (settings.add ? 1 : 0) + '&edit=' + (settings.edit ? 1 : 0);

		if (settings.title != null)
		{
			url += '&title=' + encodeURIComponent(settings.title);
		}

		settings = jQuery.extend(settings, { url: url });

		// Open window

        var jPopup = jQuery().jPopup(settings)[0];
	}

	/**
	 * Launch an HABTM popup editor using the specified ID, and URL
	 * if set in properties. If properties is not set, then it will
	 * assume Address editor.
	 *
	 * @param int id ID of record
	 * @param object properties Set of settings
	 * @return object Created popup window
	 */
	this.editHabtm = function(id, properties)
	{
		// Load settings

		var settings = {
			url: null
            , refreshUrl: null
            , refreshElement: null
            , refreshBindings: null
            , title: null
            , onClose: null
            , onSave: null
		};

		if (properties != null)
		{
			settings = jQuery.extend(settings, properties);
		}

		// Set up popup URL

		var url = settings.url;
		if (url.indexOf('?') >= 0) {
			url += '&popup_layout';
		} else {
			url += '?popup_layout';
		}

    	var popupId = 'edit_habtm_' + id + '_' + (1000 * Math.random());

        jPopup = jQuery().jPopup
        ({
            id: popupId
            , url: url
            , watchResize: false
            , title: settings.title
			, height: 150
			, width: settings.width
            , onClose: function()
            {
                if (typeof settings.onClose == 'function')
                {
                    settings.onClose.call(this);
                }
            }
        })[0];

        jCake.onHabtmSave = function()
        {
            if (typeof settings.onSave == 'function')
            {
                var r = settings.onSave.call(this);
                if (r === false)
                {
                    return false;
                }
            }
            if (settings.refreshUrl)
            {
                jPopup.busy(true);
                jQuery(settings.refreshElement).load
				(
					settings.refreshUrl
					, {}
					, function()
					{
						jPopup.close();
					}
                );
            }
            else
            {
                jPopup.close();
            }
        };

        jCake.onHabtmCancel = function()
        {
            jPopup.close();
        };

		return popupId;
	}

    /**
     * Generic function to set the global cursor that will overwrite all local cursors
     *
     * @param setCursor wait|progress|auto are supported right now. false disables custom cursor display!
     */
    this.setCursor = function(cursor)
    {
        var $body = jQuery('body');

        $body.attr('class', ($body.attr('class') && $body.attr('class').replace(/ ?[^ ]+Cursor ?/i)) || '');

        if (cursor === false)
        {
            return;
        }
        $body.addClass(((cursor || 'auto')+'Cursor') || '');
    }

    /**
     * Attaches a date picker to an element, and launches it when the element is clicked.
     * Date picker files must be already included, see NemoAjaxHelper::editor
     *
     * @param string objectId Object ID
     * @param string url Url where to post saved value
     */
    this.attachDatePicker = function(objectId, url, settings)
    {
		var object = jQuery("#" + objectId);
    	var originalLabel = object.html();

    	// Setup date picker settings

    	jQuery.datePicker.setDateFormat(settings.format);

    	object.click(function()
    	{
			// Try to figure out initial date

			var initialDate = '';

			if (jQuery.trim(originalLabel) != '')
			{
				initialDate = jQuery.trim(originalLabel).replace(/^(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
			}
			else
			{
				initialDate = settings.initialDate;
			}

			initialDate = initialDate.replace(/^(\d{1})\//, "0$1/");
			initialDate = initialDate.replace(/\/(\d{1})\//, "/0$1/");
			initialDate = initialDate.replace(/\/(\d{1})$/, "/0$1");

			// Make sure we don't show the same editor twice

			if (object.__showingCalendarInlineEditor)
			{
				// Clicks inside element will not propagate to avoid cancelling editor

				return false;
			}

			object.__showingCalendarInlineEditor = true;

	    	var clickHandler = function(e) {
				if (!object.__showingCalendarInlineEditor)
				{
					return;
				}

				// Unbind click handler

				jQuery(document).unbind("click", clickHandler);

				// And restore cell to its original status

				object.html(originalLabel);

				// No longer showing editor

				object.__showingCalendarInlineEditor = false;
			};


			// Clicks outside of element will be interpreted as editor cancel

			jQuery(document).bind('click', clickHandler);

			// Set up input and container

			var input = jQuery("<input type=\"text\" name=\"__datePickerValue\" value=\"" + initialDate + "\" size=\"10\" readonly=\"readonly\" />");
			var container = jQuery("<div />");

			// Add input to container, and container to cell

			container.append(input);
			object.append(container);

			// Initialize date picker

			input.datePicker(settings);

			// Clicks outside of cell will be interpreted as editor cancel

			jQuery(document).bind("click", clickHandler);

			// Bind change handler to submit new value

			input.bind("change", function(e)
			{
				// Set up new label

				var value = jQuery.trim(input.val());
				var label = value;

				// Check date format

				if (!isDate(value))
				{
					return false;
				}

				// Now convert it to proper SQL format

				value = value.replace(/^(\d{2})\/(\d{2})\/(\d{4})/, "$3-$1-$2");

				// Post the value

				var saved = false;
				var data = {};

				// For BC, add data to the URL

				url += '?';
				url += 'value=' + encodeURIComponent(value);

				// Set up post DATA

				data['value'] = value;

				// Post value

				jQuery.ajax(
				{
					type: 'POST'
					, async: false
					, url: url
					, data: data
					, success: function(response)
					{
						saved = true;

						if (response.error_message && jQuery.trim(response.error_message) != '')
						{
							alert('ERROR: ' + response.error_message);
							saved = false;
						}
						else if (typeof response == 'string')
						{
							saved = response;
						}
					}
				});

				if (typeof saved == "string")
				{
					label = saved;
					saved = true;
				}

				// If not saved, use original label

				if (!saved)
				{
					label = originalLabel;
				}

				// Unbind click handler

				jQuery(document).unbind("click", clickHandler);

				// And restore cell to its original status

				object.html(label);

				// No longer showing editor

				object.__showingCalendarInlineEditor = false;
			});

			// Launch date picker by faking a click

			jQuery(jQuery('span.date-picker-holder a').get(0)).trigger('click');

			// Clicks inside element will not propagate to avoid cancelling editor

			return false;
    	});
    }

	this.unescapeJsonAttribute = function(attribute)
	{
		attribute = attribute.replace(/&quot;/g, '"');
		attribute = attribute.replace(/&amp;/g, '&');
		return jCake.evalJson( attribute );
	}

	this.springLoad = function(callback, time)
	{
		return function()
		{
			if (arguments.callee.delayer)
			{
				// Clear it, so we can restart the delay
				clearTimeout(arguments.callee.delayer);
			}

			arguments.callee.delayer = setTimeout(function()
			{
				callback();
			}, time || 1500);
		}
	}

	this.customizerPopup = function(model, id, productId, customizerSettings, popupSettings)
	{
		var url = '/previews/popup_edit/' + model + '/' + id + '/' + productId;
		if (customizerSettings) {
			url = url + '&' + this.httpSerialize(customizerSettings, 'settings');
		}
		var title = 'Customize Product for ' + model + ' ' + id;

		popupSettings = jQuery.extend({
			url: url
			, width: 800
			, height: 500
			, title: title
			, onClose: function() {
				// Update all images for this product customization on page to reflect changes
				jCake.updateCustomizationImages(model, id, productId);
			}
		}, popupSettings || {});

		jQuery().jPopup('create', popupSettings);
	}

	this.updateCustomizationImages = function(model, id, productId)
	{
		jQuery('img#'+model+'-'+id+'-'+productId).each(function() {
			var $this = jQuery(this), src = $this.attr('src'), time = (new Date()).getTime();
			src = (/\?/.test(src))
				? src+'&'+time
				: src+'?'+time;

			$this.attr('src', src)
		})
	}
	
	/**
	 * Get the specified fn expression as a callback function with
	 * (optionally) the specified parameters.
	 *
	 * @param mixed fn Either a function callback, or a string
	 * @param string parameters Parameters for callback
	 * @return mixed A function callback, or null
	 * @access public
	 * @static
	 */
	this.getFunctionAsCallback = function(fn, parameters)
	{
		if (!parameters)
		{
			parameters = '';
		}
		
		if (!fn)
		{
			fn = '';
		}
		
		if (typeof fn == 'string')
		{
			if (fn.indexOf('function') != 0)
			{
				fn = 'function fn(' + parameters + ') { ' + "\n" + fn + "\n" + '}';
			}
			
			fn = fn.replace(/^\s*function\s*[^\(]*\([^\)]*\)/g, 'function ' + (jQuery.browser.mozilla && parseFloat(jQuery.browser.version) >= 1.9 ? 'fn' : '') + '(' + parameters + ')');
			result = eval(fn);
			
			if (typeof result == 'function')
			{
				fn = result;
			}
		}
		
		if (typeof fn != 'function')
		{
			fn = null;
		}
		
		return fn;
	}
}

if (!('jCake' in window))
{
    window.jCake = jCakeObj;
    jCake.initialize();
// If we already have a jCake object, because let's say WE LOADED AN ENTIRE HTML TREE INTO OUR PAGE : /, then
} else {
    // Loop through all current jCake params
    for (var p in jCakeParams)
    {
        // And merge them into our jCake object
        jCake.addVars(jCakeParams[p], p);
    }
}

