/**-----------------------------------------------------------------------------*\
 * Make an element become an editable text or textarea you can edit.			*
 * Useful when you wanna add some dynamic features to your web pages.			*
 * 																				*
 * What's new:																	*
 * 		1. support not only TD element, anything on dom tree					*
 * 		2. provide beforeSaveFunc you can call for some data checking,			*
 * 			the function you specify must return a boolean value,				*
 * 			otherwise it is considered as returning false		 				*
 * 																				*
 * @author Wang Jianchun														*
 * @version 1.2																	*
\*------------------------------------------------------------------------------*/
var DE_EDITSTATE_NEW = 0;// before anything is done
var DE_EDITSTATE_READY = 1;// before edit, ie. mouse overed
var DE_EDITSTATE_EDITING = 2;// when clicked, but before being edited
var DE_EDITSTATE_EDITED = 3;// data changed - this is not used

var DynaEdit = Class.create();
DynaEdit.prototype = {
	container:			null,
	
	// options - edit
	savingURL:			"",
	beforeSaveFunc:		null,
	notEmpty:			false,
	// options - display
	overBgColor:		"#FFFFDD",// bgColor when mouse overe
	editBgColor:		"#FFE7CD",// bgColor when editing
	restoreTime:		0,// restore time limit
	
	inputWidth:			0,
	inputHeight:		0,
	inputLimit:			0,// the max length you can input, 0 means no limit
	useTextarea:		false,
	
	// edit
	editState:			DE_EDITSTATE_NEW,
	editArea:			null,// edit area
	// behaviour
	restoreTimer:		null,
	beginBgColor:		"",
	beginValue:			"",
	editValue:			"",
	/**
	 * 
	 * @param {Object} elem
	 * @param {Object} savingUrl
	 * @param {Object} beforeSave
	 */
	initialize: function(con, savingUrl, beforeSave) {
		if(!con || !$(con)) {
			alert("Error:[DynaEdit.initialize]\n\tYou must specify an element for the container!\n\tcon=" + con + ", $(con)=" + $(con));
			return;
		}
		this.container = $(con);
		this.setSavingURL(savingUrl);
		this.setBeforeSaveFunc(beforeSave);
		
		// basic features
		this.container.style.cursor = "default";
		this.beginBgColor = this.container.style.backgroundColor;
		// value
		this.beginValue = this.container.innerHTML ? this.container.innerHTML.replace(/<br>/gi, "\n").trim() : "";
		// events
		this.container.onmouseover = this.doMouseOver.bind(this);
		this.container.onmouseout = this.doMouseOut.bind(this);
		this.container.onclick = this.doClick.bind(this);
	},
	/**
	 * setters
	 */
	setSavingURL: function(url) {
		this.savingURL = url ? url.trim() : "";
	},
	setBeforeSaveFunc: function(beforeSave) {
		if(!beforeSave) {
			this.beforeSaveFunc = null;
		} else if(typeof beforeSave != "function") {
			alert("Error:[DynaEdit.setBeforeSaveFunc]\n\tThe before-save-function must be a function or left empty!\n\tbeforeSave=" + beforeSave);
			this.beforeSaveFunc = null;
		} else {
			this.beforeSaveFunc = beforeSave;
		}
	},
	setNotEmpty: function(notEmpty) {
		this.notEmpty = (notEmpty === true);
	},
	setOverBgColor: function(bg) {
		this.overBgColor = bg || "#FFFFDD";
	},
	setEditBgColor: function(bg) {
		this.editBgColor = bg || "#FFE7CD";
	},
	setRestoreTime: function(sec) {
		this.restoreTime = Math.floor(sec)*1000 || 0;
	},
	setInputWidth: function(w) {
		this.inputWidth = Math.floor(w) || 0;
	},
	setInputHeight: function(h) {
		this.inputHeight = Math.floor(h) || 0;
	},
	setInputLimit: function(limit) {
		this.inputLimit = Math.floor(limit) || 0;
	},
	setUseTextarea: function(useTextarea) {
		this.useTextarea = (useTextarea === true);
	},
	/**
	 * If edit state is NEW, change the bgcolor of container and change edit state to READY.
	 * @private
	 */
	doMouseOver: function() {
		clearTimeout(this.restoreTimer);
		if(this.editState === DE_EDITSTATE_NEW) {
			this.container.style.backgroundColor = this.overBgColor;
			this.editState = DE_EDITSTATE_READY;
		}
	},
	/**
	 * Begin to restore the bgcolor of container only if edit state is READY.
	 * @private
	 */
	doMouseOut: function() {
		if(this.editState === DE_EDITSTATE_READY) {
			this.restoreTimer = setTimeout(this.restoreToNew.bind(this), this.restoreTime);
		}
	},
	/**
	 * Change container's innerHTML into a text or textarea if edit state is READY.
	 * @private
	 */
	doClick: function() {
		if(this.editState !== DE_EDITSTATE_READY) {
			return;
		}
		this.editState = DE_EDITSTATE_EDITING;
		this.container.innerHTML = "";
		this.container.style.backgroundColor = this.editBgColor;
		if(this.useTextarea) {
			this.textareaInit();
		} else {
			this.textInit();
		}
	},
	/**
	 * Restore the bgcolor, and change edit state back to 0(new).
	 * @private
	 */
	restoreToNew: function() {
		this.container.style.backgroundColor = this.beginBgColor;
		this.editState = DE_EDITSTATE_NEW;
	},
	/**
	 * Change container's innerHTML into a text
	 */
	textInit: function() {
		var aText = document.createElement("input")
		aText.type = "text";
		aText.value = this.beginValue;
		if(this.inputLimit > 0) {
			aText.maxLength = this.inputLimit;
		}
		if(this.inputWidth > 0) {
			aText.style.width = this.inputWidth + "px";
		}
		
		this.container.appendChild(aText);
		aText.select();
		
		this.editArea = aText;
		this.editArea.onblur = this.doBlur.bind(this);
	},
	/**
	 * Change container's innerHTML into a textarea
	 */
	textareaInit: function() {
		var aTextarea = document.createElement("textarea");
		aTextarea.value = this.beginValue;
//		aTextarea.innerHTML = this.beginValue.replace("<br>", "\n");
		if(this.inputWidth > 0) {
			aTextarea.style.width = this.inputWidth + "px";
		}
		if(this.inputHeight > 0) {
			aTextarea.style.height = this.inputHeight + "px";
		}
		
		this.container.appendChild(aTextarea);
		aTextarea.select();
		
		this.editArea = aTextarea;
		this.editArea.onblur = this.doBlur.bind(this);
	},
	/**
	 * Save the if-edited value
	 */
	doBlur: function() {
		this.editValue = this.editArea.value.trim();// edit value is only set here
		if(this.beginValue === this.editValue) {// ~no change
			this.restore4NoChange();
			return;
		}
		if(this.notEmpty && this.editValue === "") {// empty on notEmpty situation
			alert("Error:[DynaEdit.setBeforeSaveFunc]\n\tThe field cannot be left empty!");
			this.restore4IllegalEmpty();
			return;
		}

		if(this.savingURL) {
			if(this.beforeSaveFunc) {
				if(this.beforeSaveFunc.call(this, this.editValue) === true) {
					this.ajaxSave();
				}
			} else {
				this.ajaxSave();
			}
		} else {
			this.restore4NoneAjax();
		}
	},
	/**
	 * Saving in an ajax way
	 */
	ajaxSave: function() {
		new GlobalRequest(
			this.savingURL,
			"value=" + encodeURIComponent(this.editValue),
			this.restoreOnSuccess.bind(this),
			this.restoteOnFailure.bind(this)
		).request();
	},
	
	/**
	 * Restore when no change is made
	 * @private
	 */
	restore4NoChange: function() {
		this.removeEditArea();
		this.container.innerHTML = this.beginValue.replace(/\n/gi, "<br>");
		this.restoreToNew();
	},
	/**
	 * Restore when an empty input for a no-empty-allowed input
	 * @private
	 */
	restore4IllegalEmpty: function() {
		this.removeEditArea();
		this.container.innerHTML = this.beginValue.replace(/\n/gi, "<br>");
		this.restoreToNew();
	},
	/**
	 * Restore for none-ajax saving
	 * @private
	 */
	restore4NoneAjax: function() {
		this.beginValue = this.editValue;
		this.container.innerHTML = this.editValue.replace(/\n/gi, "<br>");
		this.restoreToNew();
	},
	/**
	 * Restore for ajax saving success
	 * @private
	 */
	restoteOnFailure: function() {
		this.container.innerHTML = this.beginValue.replace(/\n/gi, "<br>");
		this.restoreToNew();
	},
	/**
	 * Restore for ajax saving failure
	 * @private
	 */
	restoreOnSuccess: function() {
		this.removeEditArea();
		this.beginValue = this.editValue;
		this.container.innerHTML = this.beginValue.replace(/\n/gi, "<br>");
		this.restoreToNew();
	},
	/**
	 * @private
	 */
	removeEditArea: function() {
		if(this.editArea && this.editArea.parentNode) {
			this.editArea.parentNode.removeChild(this.editArea);
			this.editArea = null;
		}
	}
};