/*
 * autoSave: Pain free automatic updates via ajax
 *
 * Version 1.1
 *
 * Copyright (c) 2007 Daemach (John Wilson) <daemach@gmail.com>, http://ideamill.synaptrixgroup.com
 * Modified by (c) 2008 Robert Zoralski <rzoralski@ivosoftware.com>, http://ivosoftware.com
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Includes: createCSSClass function by Sam Collett
 * See below for full credit information
 *
 * Special thanks to Blair Mitchelmore for patiently explaining closures :)
 *
 * ============================================================================================
 * Usage:  $("yourFormInputs").autoSave( Function, Map )     
 * 
 * Function: any ajax function.  The function is called in the scope of the element itself
 * so "this" refers to the element's properties: this.id, this.value, this.checked, etc.
 *
 * Map: key value pairs as parameters 
 *    delay(ms) - for text fields, how long to wait before posting.  default 1000ms
 *    beforeClass - class to apply to the element when the value has changed
 *    afterClass - class to apply to the element after the ajax call has returned
 *    onChange - function to run when the value changes.  By default applies beforeClass
 *    preSave - function to run just before the ajax function is run.  Returning a boolean 
 *              "false" from this function will prevent the ajax call from running and reverts 
 *              the field to its previous value.  All other return values allow the ajax call
 *              to run normally. ( Of limited use, but why not... )
 *
 *              ex: { preSave : function (){ return confirm('are you sure?'); } }
 *             
 *    postSave - function to run when the ajax call completes.  By default applies afterClass
 *
 * Caveats: Checkboxes have to be handled specially since they don't have separate values for 
 *          checked and unchecked states.  See below for one possible solution.
 *  
 * Example: 
 *
 *		$(document).ready(function(){
 *			$(":input").autoSave(function(){
 *				var ele = new Object();
 * 
 *				// remember that this function runs in the scope of the element itself.
 *				ele[this.name] = (this.type =="checkbox") ? this.value + "|" + this.checked : this.value;
 *				$.post("test.cfm", ele, function (data) {$("#ResultDiv").empty().append(data);} )
 *			});
 *		});
 *
 */

var asCookieExpires = 7;	//cookie (and autosave value) expires after days
var asCookieMaxSize = 3000;	//cookie max size
var asRestoreKeys = null;	//global variable for keys to restore
var asKeySep = "*";         //be careful with changing this value. it should be changed both on backend and frontend of autosave

//save element functionality
jQuery.fn.autoSave = function (fcn, settings) {
    $daemach = (typeof $daemach !== "undefined") ? $daemach : {};
    if (typeof $daemach.autoSave == "undefined") {
        $daemach.autoSave =  new Object();
        $daemach.autoSave["timer"] =  new Array();
        $daemach.autoSave["fn"] =  new Array();
    }
	 
	 var _as = $daemach.autoSave;
	 
    settings = jQuery.extend( {
        delay : 500,
		  doClassChange: false,
		  beforeClass : "asBefore", 
		  afterClass : "asAfter", 
		  onChange: null, 
		  preSave : null,
		  postSave : null,
		  minLength: 0,
		  textareadelay: 10000

    }
    , settings);
	 
	 if (settings.doClassChange){
	 	if (settings.beforeClass == "asBefore") { createCSSClass(".asBefore", "background-color:#FFdddd"); }
    	if (settings.afterClass == "asAfter") { createCSSClass(".asAfter", "background-color:#ddFFdd"); }
	 }
	 // Start building...
    return this.each(function () {
        var p = this.name;
        if (typeof  _as["fn"][p] == "undefined") {
            _as["fn"][p] =  new Array();
            _as["fn"][p][0] = null;
        }
        var bindType;
        var initialState;
        switch (this.type) {
            case "text":
                bindType = "keyup";
                 initialState = this.value;
                break;
            case "hidden":
                bindType = "keyup";
                 initialState = this.value;
                break;
            case "textarea":
                bindType = "keyup";
                initialState = "";	//textarea could have large-text
                break;
            case "password":
            	return;				//ignore this type
                bindType = "keyup";
                initialState = this.value;
                break;
            case "select-one":
                bindType = "change";
                 initialState = this.value;
                break;
            case "select-multiple":
                bindType = "change";
                 initialState = this.value;
                break;
            case "radio":
                bindType = "click";
                 initialState = this.value;
                break;
            case "checkbox":
                bindType = "click";
                 initialState = this.checked;
                break;
                default  : bindType = "keyup";
                 initialState = this.value;
                break;
        }
        if (bindType == "keyup") {
             _as["timer"][p] = null;
        }
        if ( this.type !== "radio" || (this.type == "radio" && this.checked)) {
             _as["fn"][p][0] =  initialState;
        }
         _as["fn"][p][1] = function (e) {
            if (e && e.type == 'blur' &&  _as["fn"][p][2]) {
                if ( _as["timer"][p]) { window.clearTimeout( _as["timer"][p]) };
            }
				// check lock in case settings.onChange function contained something that caused the blur element to fire
				if ( _as["fn"][p][2] ){
					if ( _as["fn"][p][0] !== this.value || (this.type == "checkbox")) {
							
						if (settings.preSave) {
							// lock again for preSave function
							 _as["fn"][p][2] = false;
							
							var proceed = settings.preSave.apply(this);
							if (!(typeof proceed == "boolean" && proceed == false)){
								 _as["fn"][p][2] = true;
							}
						}
						
						if ( _as["fn"][p][2]){
							 // call the main function
							 fcn.apply(this); 
							 // record new state
							 if(this.type !== "textarea" ) {
								_as["fn"][p][0] = this.value;
							 }	
						} else {
							// revert 
							if (this.type == "checkbox"){
								this.checked =  _as["fn"][p][0];
							} else {
								this.value =  _as["fn"][p][0];
							}
						}
						 //run post save function
						 if (settings.postSave) {
							  settings.postSave.apply(this);
						 }
						 
						 if (settings.doClassChange) {
							jQuery(this).removeClass(settings.beforeClass).addClass(settings.afterClass);
						 }
					}
				}
        }
		  // init locking mechanism
		   _as["fn"][p][2] = true;
		  
        jQuery(this).bind(bindType, function () {
        
            if ( (this.type == "textarea") || (_as["fn"][p][0] !== this.value) || (this.type == "checkbox")) {
                if (settings.onChange) {
						// lock handler in case onChange function causes this field to lose focus
						 _as["fn"][p][2] = false;
						
						var proceed = settings.onChange.apply(this);
						if (!(typeof proceed == "boolean" && proceed == false)){
							 _as["fn"][p][2] = true;
						}
                }
					 if (settings.doClassChange) {
						var ele = jQuery(this);
						if (ele.is('.' + settings.afterClass))ele.removeClass(settings.afterClass);
						if (!ele.is('.' + settings.beforeClass))ele.addClass(settings.beforeClass); 
					 }

                var me = this;
                if (bindType == "keyup") {
						 if (this.value.length >= settings.minLength){
						 
						 	if ( _as["timer"][p]){ 
                				window.clearTimeout( _as["timer"][p]) 
                			};
						 
						     if( this.type !== "textarea" ) {
							    _as["timer"][p] = window.setTimeout(function () { _as["fn"][p][1].apply(me);}, settings.delay);
							 } else {
								_as["timer"][p] = window.setTimeout(function () { _as["fn"][p][1].apply(me);}, settings.textareadelay);
							 }
						 }
                } else {                	
                     _as["fn"][p][1].apply(me);	
                }
            }
        }
        );
        
        if (bindType == "keyup") {
            jQuery(this).blur(function(){ if (this.value.length  >= settings.minLength){  _as["fn"][p][1] } });
        }
    });
};

//store-one functionality - R.Zoralski, 2008 
if( typeof asStoreOne == "undefined" ) {
	function asStoreOne(o) {
		var ele = new Object();
		ele['command'] = 'STORE';

		if( o.type == "checkbox" ) {
			ele[asUserId + asKeySep + asFormId + asUniqFormId + asKeySep + o.name + asKeySep + o.value] = o.checked;
		} else if( !(o.type == "radio" && !o.checked ) && o.type != "password") {			//skip unchecked radio and password field
			ele[asUserId + asKeySep + asFormId + asUniqFormId + asKeySep + o.name] = o.value;
		}	
		
		var showAlert = ( o.type == "textarea" ? true : false );
		
		//before function
		if( showAlert && typeof asBeforeSaveAlert !== "undefined" ) {
			asBeforeSaveAlert();
		}

		//send command to state server and invoke response function
		$.post(asAutosaveScript, ele, function (data) { 
			asSaveDates(data);			//save dates in cookie
			if( typeof asAfterSaveAlert !== "undefined" ) {
				asAfterSaveAlert( data, showAlert );
			} 
		}, "json");			
		return false;
	}
}

//store-all functionality - R.Zoralski, 2008
if( typeof asStoreAll == "undefined" ) {
	function asStoreAll(form_id) {
		var ele = new Object();
		ele['command'] = 'STORE';
		jQuery("#" + form_id + " > :input").each( function() {
			if( this.type == "checkbox" ) {
				ele[asUserId + asKeySep + form_id + asUniqFormId + asKeySep + this.name + asKeySep + this.value] = this.checked;
			} else if( !(this.type == "radio" && !this.checked ) && this.type != "password") {			//skip unchecked radio and password field
				ele[asUserId + asKeySep + form_id + asUniqFormId + asKeySep + this.name] = this.value;
			}	
		});
				
		//before function
		if( typeof asBeforeSaveAlert !== "undefined" ) {
			asBeforeSaveAlert();
		}
			
		//send command to state server
		$.post(asAutosaveScript, ele, function (data) { 
			asSaveDates(data);						//save dates in cookie
			if( typeof asAfterSaveAlert !== "undefined" ) {
				asAfterSaveAlert( data, true );				//invoke custom alert
			} 
		}, "json");
		return false;
	}
}

//checks if data in memcache available - R.Zoralski, 2008
//returns identifiers
if( typeof asCheckStored == "undefined" ) {
	function asCheckStored(form_id) {
		var c = $.cookie('asData');
		
		if(c===null) {
			return false;
		} else {
			var obj = new Object();
			var resArr = [];
			var isStored = false;
			
			obj = asUnserialize(c);
			for(var key in obj) {
				
				if(key != "_sid" && key.startsWith(form_id + asKeySep)) {	//skip sid and data from other forms
					if(asServerTime > obj[key]) {
						isStored = true;
						resArr.push(asUserId + asKeySep + key);
					}
				}		
			}
			
			if(isStored) {
				return resArr;
			} else {
				return false;
			}	
		}
	}
}


//serialize-unserialize for cookie functionality - R.Zoralski, 2008
if( typeof asSerialize == "undefined" ) {
	function asSerialize(obj) {
		var str = "";
		var it = -1;
		for( var key in obj ) {
			if( key != "command" ) {	//skip command field
				it++;
				if(it>0) str = str + "|";		//add word separator 
				str = str + key + "^" + obj[key];
			}
		}	
		return str;
	}
}

if( typeof asUnerialize == "undefined" ) {
	function asUnserialize(str) {
		var t = str.split('|');
		var obj = new Object(); 
		for(var it=0; it<t.length; it++) {
			var t2 = t[it].split('^');
			obj[t2[0]] = t2[1];
		}
		return obj;
	}
}

//elem timestamps storage in cookie - R.Zoralski, 2008
if( typeof asSaveDates == "undefined" ) {
	function asSaveDates(data) {
	
		var c = $.cookie('asData');
		
		if(c===null) {
			//save response data in cookie "as is"
			$.cookie('asData', data, { expires: asCookieExpires });
		} else {
			
			//join cookie and response data
			var obj = data;
			var obj2 = asUnserialize(c);
			
			//update cookie data with response data and fill with new
			for( var key in obj ) {
				obj2[key] = obj[key];
			}
			
			//delete expired basing on script generation server time - not required
			for(var key in obj2) {
				if( obj2[key] < asServerTime - (asCookieExpires * 24 * 60 * 60 * 1000) ) {
					delete obj2[key];
				}
			}
			
			c = asSerialize(obj2);
			
			//trim too long cookie - remove oldest entries
			if(c.length>asCookieMaxSize) {
				//build timestamps arr (uniq)
				tm_arr = [];
				for(var key in obj2) {
					if(key!="_sid" && !tm_arr.in_array(obj2[key]) )
						tm_arr.push(obj2[key]);
				}
				//sort it
				function numOrdD(a, b){ return (b-a); }
				tm_arr.sort( numOrdD );
				
				//trim all following by timestamp
				for(var it=0;it<tm_arr.length;it++) {
					for(var key in obj2) {
						if( tm_arr[it] == obj2[key] )
							delete( obj2[key] );
					}
					
					c = asSerialize(obj2);
					if(c.length<asCookieMaxSize)
						break;
				}				
			}
			
			//save cookie
			$.cookie('asData', null);
			$.cookie('asData', c, { expires: asCookieExpires });
		}			
	}
}

//user identifier - R.Zoralski, 2008
if( typeof asGetId == "undefined" ) {
	function asGetId(rsid) {
		var c = $.cookie('asData');

		if(c===null) {
			var obj = new Object();
		} else {
			var obj = asUnserialize(c);
		}	
					
		if( typeof obj['_sid'] !== "undefined" ) {
			return obj['_sid'];
		} else {
			obj['_sid'] = rsid;
			$.cookie('asData', asSerialize(obj), { expires: asCookieExpires });
			return obj['_sid'];
		}
		
	}
}

//clear storage (cookie entries) functionality - R.Zoralski, 2008
if( typeof asClearStorage == "undefined" ) {
	function asClearStorage(form_id) {
		var c = $.cookie('asData');

		if(c===null) {
			return true;
		} else {
			var obj = asUnserialize(c);
		}	

		jQuery("#" + form_id + " > :input").each( function() {
			if( this.type == "checkbox" ) {
				var cname = form_id + asUniqFormId + asKeySep + this.name + asKeySep + this.value;
			} else {	
				var cname = form_id + asUniqFormId + asKeySep + this.name;
			}
			 
			if(typeof obj[cname] !== "undefined" ) {
				delete obj[cname];
			}
		});

		$.cookie('asData', asSerialize(obj), { expires: asCookieExpires });
		return true;
		
	}
}

//restore functionality - R.Zoralski, 2008 
if( typeof asRestore == "undefined" ) {
	function asRestore(fields) {
		flen = fields.length;
		if(flen>0) {
				
			var ele = new Object();
			ele['command'] = 'RESTORE';
					
			//before function
			if( typeof asBeforeRestoreAlert !== "undefined" ) {
				asBeforeRestoreAlert();
			}

			for(var i=0;i<flen;i++) {
				ele[fields[i]] = "";			
			}
			
			//send command to state server
			$.post(asAutosaveScript, ele, function (data) {
 				asRestoreFill( data );
				if( typeof asAfterRestoreAlert !== "undefined" ) {
					asAfterRestoreAlert();				//invoke custom alert
				} 
			}, "json");
			return false;
		
		}	
	}
}

//restore functionality - R.Zoralski, 2008 
if( typeof asRestoreFill == "undefined" ) {
	function asRestoreFill( data ) {
		
		obj = data;
		for(var key in obj) {
			
			var t = key.split(asKeySep);

			if( typeof t[0] !== "undefined" && typeof t[1] !== "undefined" ) {
				var form_id = t[0];
				var ulen = asUniqFormId.length;
				var flen = form_id.length;
				form_id = form_id.substring(0, flen-ulen);
				var fname = t[1];
				var chvalue = t[2];

				jQuery("#" + form_id + " > :input").each( function() {
				
					if( this.name == fname ) {
						if( this.type == "checkbox" ) {
							if( this.value == chvalue ) {
								if( obj[key] == "true" ) {
									this.checked = "checked";
								} else if (obj[key] == "false" ) {
									this.checked = "";
								}
							}		
						} else if( this.type == "radio" ) {
							if( this.value == obj[key] ) { 
								this.checked = "checked";
							}	
						} else if( this.type == "select" ) {
							if( this.children("option[value=" + obj[key] + "]").is("*") ) {	//do not change current selected if no value on select list
								this.value = obj[key];
							}
						} else {
							this.value = obj[key];
						}
					}	
				});
				 					
			}		
		}	
	}
}

if (typeof createCSSClass == "undefined") {
    function createCSSClass(selector, style) {
        // Created by Sam Collett - 2007
        // http://webdevel.blogspot.com/2006/06/create-css-class-javascript.html
        //
        // using information found at: http://www.quirksmode.org/dom/w3c_css.html
        // doesn't work in older versions of Opera (< 9) due to lack of styleSheets support
        if (!document.styleSheets)return;
        if (document.getElementsByTagName("head").length == 0)return;
        var stylesheet;
        var mediaType;
        if (document.styleSheets.length > 0) {
            for (var i = 0; i < document.styleSheets.length; i++) {
                if (document.styleSheets[i].disabled)continue;
                var media = document.styleSheets[i].media;
                mediaType = typeof media;
                // IE
                if (mediaType == "string") {
                    if (media == "" || media.indexOf("screen") !=  - 1) {
                        styleSheet = document.styleSheets[i];
                    }
                }
                else if (mediaType == "object") {
                    if (media.mediaText == "" || media.mediaText.indexOf("screen") !=  - 1) {
                        styleSheet = document.styleSheets[i];
                    }
                }
                // stylesheet found, so break out of loop
                if (typeof styleSheet != "undefined")break;
            }
        }
        // if no style sheet is found
        if (typeof styleSheet == "undefined") {
            // create a new style sheet
            var styleSheetElement = document.createElement("style");
            styleSheetElement.type = "text/css";
            // add to <head>
            document.getElementsByTagName("head")[0].appendChild(styleSheetElement);
            // select it
            for (var i = 0; i < document.styleSheets.length; i++) {
                if (document.styleSheets[i].disabled)continue;
                styleSheet = document.styleSheets[i];
            }
            // get media type
            var media = styleSheet.media;
            mediaType = typeof media;
        }
        // IE
        if (mediaType == "string") {
            for (var i = 0; i < styleSheet.rules.length; i++) {
                // if there is an existing rule set up, replace it
                if (styleSheet.rules[i].selectorText.toLowerCase() == selector.toLowerCase()) {
                    styleSheet.rules[i].style.cssText = style;
                    return;
                }
            }
            // or add a new rule
            styleSheet.addRule(selector, style);
        }
        else if (mediaType == "object") {
            for (var i = 0; i < styleSheet.cssRules.length; i++) {
                // if there is an existing rule set up, replace it
                if (styleSheet.cssRules[i].selectorText.toLowerCase() == selector.toLowerCase()) {
                    styleSheet.cssRules[i].style.cssText = style;
                    return;
                }
            }
            // or insert new rule
            styleSheet.insertRule(selector + "{" + style + "}", styleSheet.cssRules.length);
        }
    }
}

Array.prototype.in_array = function ( obj ) {
	var len = this.length;
	for ( var x = 0 ; x <= len ; x++ ) {
		if ( this[x] == obj ) return true;
	}
	return false;
}

String.prototype.startsWith = function (str) {
  return this.indexOf(str) === 0;
};


