//	ironcoin js suite v0.2a

var ICjs = { version: "0.2a" };

ICjs.FormDepot = $H();
ICjs.DimDepot = $H();
ICjs.StopWatch = {};

Object.extend(Object, {
	isBlank: function (object) {
		return Object.isNot(object) || String(object).blank();
	}, 

	isNull: function (object) {
		return object == null;
	}, 

	isNot: function (object) {
		return Object.isUndefined(object) || Object.isNull(object);
	}
});

Hash.addMethods({
	allocate: function (key, value) {
		var out;
		if (!(out = this.get(key))) {
			out = this.set(key, value);
		}
		return out;
	},

	deallocate: function (key, value) {
		var out;
		if (out = this.get(key)) {
			out.unset(value);
			if (!out.keys().size()) {
				this.unset(key);
			}
		}
		return this;
	}
});

ICjs.Utils = {
	watchstart: function (watch) {
		ICjs.StopWatch[watch] = new Date;
	}, 

	watchstop: function (watch) {
		return new Date - ICjs.StopWatch[watch];
	}, 

	debug: function (e, value, caller) {
		// e.message, e.name
		window.alert(
			"error!" + 
			"\n\rcode: " + (4294967296 + e.number).toString(16) +
			"\n\rdescription: " + e.description +
			(caller ? "\n\rcaller: " + caller : "") +
			"\n\rvalue: " + value
		);
	}
}

ICjs.Elements = {
	messagebox: function (element, message, _options) {
		element = $(element);
		
		var options = Object.extend({
			klass: "messagebox",
			template: new Template("<div class='#{klass}'>#{message}</div>"), 
			position: "after", 
			focus: true, 
			center: false
		}, _options || {});
		
		var box;
		
		if (Object.isNot(options.bookmark)) {
			options.bookmark = element;
		} else {
			options.bookmark = $(options.bookmark);
		}
		
		switch (options.position) {
			case "after":
				box = options.bookmark.insert({after: options.template.evaluate({klass: options.klass, message: message})}).next();
			break;
			case "before":
				box = options.bookmark.insert({before: options.template.evaluate({klass: options.klass, message: message})}).previous();
			break;
			case "top":
				box = options.bookmark.insert({top: options.template.evaluate({klass: options.klass, message: message})}).down();
			break;
			case "bottom":
				box = options.bookmark.insert({bottom: options.template.evaluate({klass: options.klass, message: message})}).down(":last-child");
			break;
		}

		if (options.center) {
			box.center();
		}
		
		if (options.focus && element.isInput()) {
			element.setFocus();
		}
		
		return box;
	}, 
	
	isInput: function (element) {
		element = $(element);
		return !!["INPUT", "SELECT", "TEXTAREA"].include(element.tagName.toUpperCase());
	}, 
	
	dim: function (element, _options) {
		element = $(element);
		
		var options = Object.extend({
			klass: "waitingblack",
			position: "after", 
			template: new Template("<div class='#{klass}'></div>"),
			opacity: 0.75, 
			showeffect: Effect.Appear, 
			hideeffect: Effect.Fade, 
			duration: .2
		}, _options || {});

		var dimmer, depot = {}, count;
		
		depot.element		= ICjs.DimDepot		.allocate(element.identify(), $H());
		depot.element									.set("_options", options);
		
		if (!(count = Number(depot.element.get("_count")))) {
			switch (options.position) {
				case "after":
					dimmer = element.insert({after: options.template.evaluate({klass: options.klass})}).next();
				break;
				case "before":
					dimmer = element.insert({before: options.template.evaluate({klass: options.klass})}).previous();
				break;
				case "top":
					dimmer = element.insert({top: options.template.evaluate({klass: options.klass})}).down();
				break;
				case "bottom":
					dimmer = element.insert({bottom: options.template.evaluate({klass: options.klass})}).down(":last-child");
				break;
			}
			dimmer.hide().setOpacity(options.opacity).clonePosition(element, {setWidth: true, setHeight: true});
			options.showeffect(dimmer, {duration: options.duration, from: 0, to: options.opacity});
			depot.element.set("_count", 1);
			depot.element.set("_id", dimmer.identify());
		} else {
			depot.element.set("_count", count + 1);
		}
		
		return element;
	},

	undim: function (element) {
		var dimmer, depot = {}, count, options;
		
		if (depot.element		= ICjs.DimDepot				.get(element.identify())) {
			options				= depot.element				.get("_options");
			count					= Number(depot.element	.get("_count")) - 1;
			id						= depot.element				.get("_id");
			if (count) {
				depot.element										.set("_count", count);
			} else {
				dimmer = $(id);
				depot.element										.set("_count", 0);
				options.hideeffect(dimmer, { duration: options.duration});
			}
		}
		
		return element;
	}, 
	
	center: function (element) {
		element = $(element);
		var dimdv = document.viewport.getDimensions();
		var offdv = document.viewport.getScrollOffsets();
		var dimel = element.getDimensions();
		element.style.left = (Math.ceil((dimdv.width - dimel.width) / 2) + offdv.left) + "px";
		element.style.top = (Math.ceil((dimdv.height - dimel.height) / 3) + offdv.top) + "px";
		return element;
	}
}
Element.addMethods(ICjs.Elements);

ICjs.Forms = {
	prepare: function (form, _options) {
		form = $(form);
		
		var options = Object.extend({
			description: "form options", 
			klass: "validatorbox", 
			requiredklass: "validator",
			position: "before"
		}, _options || {});
		
		var depot = {};
		
		depot.form			= ICjs.FormDepot		.allocate(form.identify(), $H());
		depot.form										.set("_options", options);
			
		form.observe("submit", form.validate);
		
		return form;
	},

	validate: function (form, event) {
		form = $(form);
																							// ICjs.Utils.watchstart("validate()");
		var depot = {}, ids = [];
		
		depot.form 			= ICjs.FormDepot		.get(form.identify());
		depot.inputs		= depot.form			.get("_inputs");

		ids = depot.inputs.keys().collect( function (e) { return "#" + e; });
		
		depot.inputs.values().each ( function (e) {
			e.set("_pending", e.get("_attached").clone());
		});
		
		form.select(".validator").invoke("remove");
		form.select(ids).invoke("wipe").invoke("validate");
		
		event.stop();
																							// alert(ICjs.Utils.watchstop("validate()"));
	},

	trysubmit: function (form, _options) {
		form = $(form);
		
		var depot = {}, pending = false;
		
		depot.form 			= ICjs.FormDepot		.get(form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		
		pending = depot.inputs.values().any( function (e) { return e.get("_pending"); });
		
		if (!pending) {
			try {
				form.submit();
			} catch (error) {
				alert("Критическая ошибка при отправке формы. Проверьте все поля !!!");
			}
		}
	},

	isStained: function (form) {
		form = $(form);
		
		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.get(form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		
		return depot.inputs.values().any( function (e) { return e.get("_stained"); });
	},
	
	setUpload: function (form) {
		// LX L8R: refactor this
		form.select("a.uploader.flipper").each( function (e) {
			e.observe("click", function () {
				var src = this;
				var tgt = src.next("div.uploader.flipped");
				if (tgt.hasClassName("closed")) {
					tgt.addClassName("open").removeClassName("closed");
					src.addClassName("open").removeClassName("closed");
				} else if (tgt.hasClassName("open")) {
					tgt.addClassName("closed").removeClassName("open");
					src.addClassName("closed").removeClassName("open");
				}
			});
		});
		form.select("a.displayer.flipper").each( function (e) {
			e.observe("click", function (ev) {
				var src = this;
				var tgt = src.next("div.displayer.flipped");
				if (tgt.hasClassName("closed")) {
					tgt.addClassName("open").removeClassName("closed");
					src.addClassName("open").removeClassName("closed");
				} else if (tgt.hasClassName("open")) {
					tgt.addClassName("closed").removeClassName("open");
					src.addClassName("closed").removeClassName("open");
				}
			});
		});
		form.select("a.deleter").each( function (e) {
			e.observe("click", function (ev) {
				var src = this, tgt, names;
				
				names = $w(src.readAttribute("rel")).collect( function (e) { return "[name='" + e + "']"} );
				tgt = form.select(names);
				tgt.invoke("clear");
				
				tgt = src.previous("span.displayer");
				tgt.remove();
				tgt = src.previous("a.displayer");
				tgt.remove();
				tgt = src.previous("span.deleter");
				tgt.remove();
				tgt = src.next("div.displayer");
				tgt.remove();
				
				src.remove();
			});
		});
	}
}
Element.addMethods(["FORM"], ICjs.Forms);

ICjs.Inputs = {
	attach: function (element, validator, _options) {
		element = $(element);
		
		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.allocate(element.form.identify(), $H());

		if (!(depot.options = depot.form.get("_options"))) {
			element.form.prepare();
			depot.options = depot.form.get("_options");
		}
		
		var options = Object.extend({
			description: "validator options", 
			validator: validator, 
			klass: depot.options.klass, 
			requiredklass: depot.options.requiredklass, 
			position: depot.options.position
		}, _options || {});
		
		depot.inputs		= depot.form			.allocate("_inputs", $H());
		depot.element		= depot.inputs			.allocate(element.identify(), $H());
		depot.attached	= depot.element		.allocate("_attached", $H());
		depot.validator	= depot.attached		.set(validator, options);
		
		return element;
	},

	validate: function (element) {
		element = $(element);
		
		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.get(element.form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		depot.element		= depot.inputs			.get(element.identify());
		depot.pending		= depot.element		.get("_pending");
		
		depot.pending.each( function (e) {
			element[e.key](e.value);
		});
	},

	isStained: function (element) {
		element = $(element);
		
		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.get(element.form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		depot.element		= depot.inputs			.get(element.identify());
		
		return !!depot.element.get("_stained");
	},

	stain: function (element, validator) {
		element = $(element);

		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.get(element.form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		depot.element		= depot.inputs			.get(element.identify());
		
		depot.element.allocate("_stained", $H()).set(validator);
		
		return element;
	},

	wipe: function (element, validator) {
		element = $(element);

		var depot = {};
		
		depot.form 			= ICjs.FormDepot		.get(element.form.identify());
		depot.inputs		= depot.form			.get("_inputs");
		depot.element		= depot.inputs			.get(element.identify());
		
		if (validator) {
			depot.element								.deallocate("_stained", validator)
															.deallocate("_pending", validator);
		} else {
			depot.element								.unset("_stained");
		}
		
		return element;
	}, 
	
	setFCK: function (element, _options) {
		element = $(element);
		
		var options = Object.extend({
			description: "setFCK options", 
			basepath: siteFCK,
			toolbarset: "ironcoin"
		}, _options || {});
		
		var fck;
		
		if (element.tagName.toUpperCase() == "TEXTAREA") {
			fck = new FCKeditor(element.identify());
			fck.BasePath = options.basepath;
			fck.ToolbarSet = "ironcoin";
			fck.ReplaceTextarea();
		}
		
		return element;
	}, 
	
	isFCK: function (element) {
		element = $(element);
		var isfck = element.readAttribute("fck");
		if (isfck) {
			return isfck;
		}
		if (typeof FCKeditorAPI != "undefined" && FCKeditorAPI.GetInstance(element.identify())) {
			isfck = true;
		} else {
			isfck = false;
		}
		element.writeAttribute("fck", isfck);
		return isfck;
	},
	
	getFCK: function (element) {
		element = $(element);
		if (element.isFCK()) {
			return FCKeditorAPI.GetInstance(element.identify());
		}
	},
	
	setFocus: function (element) {
		element = $(element);
		if (element.isFCK()) {
			element.getFCK().Focus();
		} else {
			element.focus();
		}
		return element;
	}
}
Element.addMethods(["INPUT", "SELECT", "TEXTAREA"], ICjs.Inputs);

ICjs.Validators = {
	valRequired: function (element, _options) {
		element = $(element); if (element.isStained() || element.form.isStained() ) { return; }
		
		var value = element.getValue();
		if (Object.isBlank(value)) {
			element.stain(_options.validator).messagebox(ICjs.Validators.Messages[_options.validator], {klass: _options.requiredklass + " " + _options.klass, bookmark: _options.bookmark, position: _options.position});
		} else {
			element.wipe(_options.validator).form.trysubmit();
		}
	},
	
	valDate: function (element, _options) {
		element = $(element); if (element.isStained() || element.form.isStained() ) { return; }
		
		var value = element.getValue();
		
		var parsed, d, m, y, date, built;
		parsed = value.match(/(\d{1,2})\.(\d{1,2})\.(\d{4})/);
		d = Number(RegExp.$1); m = Number(RegExp.$2) - 1; y = Number(RegExp.$3);
		value = value.replace(/0(\d)\.(\d{1,2})\.(\d{4})/, "$1.$2.$3").replace(/(\d{1,2})\.0(\d)\.(\d{4})/, "$1.$2.$3");
		date = new Date(y, m, d);
		built = date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
			
		if (!Object.isBlank(value) && value != built) {
			element.stain(_options.validator).messagebox(ICjs.Validators.Messages[_options.validator], {klass: _options.requiredklass + " " + _options.klass, bookmark: _options.bookmark, position: _options.position});
		} else {
			element.wipe(_options.validator).form.trysubmit();
		}
	},

	valEMail: function (element, _options) {
		element = $(element); if (element.isStained() || element.form.isStained() ) { return; }
		
		var value = element.getValue();
		if (!Object.isBlank(value) && !Object.isBlank(value.replace(/([\w-\._]+@[\w-\.]+)/, ""))) {
			element.stain(_options.validator).messagebox(ICjs.Validators.Messages[_options.validator], {klass: _options.requiredklass + " " + _options.klass, bookmark: _options.bookmark, position: _options.position});
		} else {
			element.wipe(_options.validator).form.trysubmit();
		}
	},

	valInt: function (element, _options) {
		element = $(element); if (element.isStained() || element.form.isStained() ) { return; }
		
		var value = element.getValue();
		var template = new Template(ICjs.Validators.Messages[_options.validator]);
		if (!Object.isBlank(value) && (isNaN(value) || Number(value) != Math.ceil(Number(value)) || Number(value) < Number(_options.min) || Number(value) > Number(_options.max))) {
			element.stain(_options.validator).messagebox(template.evaluate({min: _options.min, max: _options.max}), {klass: _options.requiredklass + " " + _options.klass, bookmark: _options.bookmark, position: _options.position});
		} else {
			element.wipe(_options.validator).form.trysubmit();
		}
	},

	// this is common template for AJAX validator: it passes when element == boolean true; it fails when element == boolean false 
	valAJAXStub: function (element, _options) {		
		element = $(element); if (element.isStained() || element.form.isStained() ) { return; }
		
		var value = element.getValue();
		// if (Object.isBlank(value)) {
		if (true) {
			new Ajax.Request(siteAJAX + "?ajax=" + _options.validator + "&field=" + element.name + "&value=" + encodeURIComponent(value), {
																																	
				// LX TODO: этот элемент нужен был для вывода AJAX.onFailure-обработчика?
				// element: element,
				
				method: "get",
				onSuccess: function (xhr) {						// server side is steady; xhr.status is like 200 (200-299) or absent at all

					var result = xhr.responseText;

					if (result == "FAIL") {							// nope

						// we can get here asynchronously, so repeating this check to avoid multiple simultaneous messages
						if (element.isStained() || element.form.isStained() ) { return; }

						element.stain(_options.validator).messagebox(ICjs.Validators.Messages[_options.validator], {klass: _options.requiredklass + " " + _options.klass, position: _options.position});

					} else if (result == "WIN") {					// yep

						element.wipe(_options.validator).form.trysubmit();

					} else {												// something unexpected; but we can skip this branch
					}
				}
			});
		}
	}
}
Element.addMethods(["INPUT", "SELECT", "TEXTAREA"], ICjs.Validators);

function FCKeditor_OnComplete (fck) {
	fck.Events.AttachEvent("OnBlur", FCKeditor_OnBlur);
	fck.Events.AttachEvent("OnFocus", FCKeditor_OnFocus);
}

function FCKeditor_OnBlur (fck) {
	fck.ToolbarSet.Collapse();
	fck.UpdateLinkedField();
}

function FCKeditor_OnFocus(fck) {
	fck.ToolbarSet.Expand();
}

Ajax.Responders.register({
	onCreate: function (requester, xhr) {
		var options = Object.extend({
			dimelement: "alpha",
			dimposition: "top", 
			dimklass: "waitingblack"
		}, requester.options || {});
		
		options.dimelement = $(options.dimelement);
		options.dimelement.dim({klass: options.dimklass, position: options.dimposition});
	},

	onComplete: function (requester, xhr) {
		var options = Object.extend({
			dimelement: "alpha"
		}, requester.options || {});
		
		options.dimelement = $(options.dimelement);
		
		if (xhr.status == 200) {
			options.dimelement.undim();
		} else {
			options.dimelement.messagebox("http status: " + xhr.status, {klass: "ajaxerror", position: "after", center: true});
		}
	},

	onException: function (requester, exception) {
		ICjs.Utils.debug(exception, requester.url);
	}
});

// LX L8R: i18n (через темплейты?)
// LX TODO: абсолютную позицию для мессаджбокса (привязывать к элементу)
// LX TODO: объединить мессаджбокс и дим?
// LX TODO: написать серверный аяксер, взять за основу билетовский: обрабатывать mainAJAX; обрабатывать mainField; обрабатывать mainValue
// LX TODO: в серверном аяксере разобраться с throw null / throw custom (new Error (...))
// LX TODO: написать более сложный валидатор: обрабатывать excludefield, includefield
