(function() {

	SL.byID = function(id) {
		var e = $(id);
		if (!e) {
			console.error("SL.byID: unable to find element with id '" + id + "'");
			return;
		}

		var component = e.retrieve('sl_component');
		if (component) {
			return component;
		}

		if (!SL.legacy) {
			console.error("SL.byID: element '" + id + "' has no component");
		}

		var info = e.retrieve('sl_component_info');
		if (info) {
			console.log("Init configured component:", e, info);
			return new info.component(e, info.config);
		}

		return SL.legacy.initWidget(e);
	};

	SL.getC = function(id) {
		var e = $(id);
		if (e) {
			return e.retrieve('sl_component');
		}
	};

	SL.getCC = function(element) {
		var e = element;
		while (e) {
			var component = e.retrieve('sl_component');
			if (component) {
				return component;
			}
			e = e.up();
		}
	};

	SL.register = function(c, e, config) {
		e = $(e);
		if (!e) {
			console.error("No element to regsiter:", e, c, config);
		}
		console.log("Configure component:", e, c, config);
		e.store('sl_component_info', {
			component : c,
			config : config
		});
	};

	SL.initDefered = function(c, e, config) {
		SL.utils.onDomReady(function(c, e, config) {
			new c(e, config);
		}.curry(c, e, config));
	};

	SL.initOnEvent = function(eventElement, event, c, e, config, connectDestroy) {

		var ev = $(eventElement);
		if (!ev) {
			return;
		}

		var h = ev.retrieve('sl_init_events');
		if (!h) {
			h = $H();
			ev.store('sl_init_events', h);
		}

		var events = h.get(event);
		if (!events) {
			events = $A();
			h.set(event, events);

			var ef = function(e, eventName, event) {
				var h = e.retrieve('sl_init_events');
				h.get(eventName).each(function(e, f) {
					f(event);
				}.curry(e));
				e.stopObserving(eventName, h.get('_f_' + eventName));
				h.set('_f_' + eventName, undefined);
				h.set(eventName, undefined);
			}.curry(ev, event);
			h.set('_f_' + event, ef);
			ev.observe(event, ef);
		}

		var f = function(c, e, config, connectDestroyElement, event) {
			var element = $(e);
			if (element && element.retrieve('sl_component')) {
				return;
			}
			var i = new c(e, config);
			i.connectDestroy(connectDestroyElement);
			i.notifyInitEvent(event);
		}.curry(c, e, config, connectDestroy ? eventElement : null);

		events.push(f);
	};

	SL.ensureComponent = function(c, id, config) {
		var e = $(id);
		if (!e) {
			return new c(id, config);
		}

		var component = e.retrieve('sl_component');
		if (component) {
			return component;
		}

		return new c(id, config);
	};

	SL.componentExists = function(id) {
		var e = $(id);
		if (!e) {
			return false;
		}
		return e.retrieve('sl_component') ? true : false;
	};

	SL.Component = Class.create({

		initialize : function(e, config) {

			if (arguments.length == 1 && (typeof e) != 'string' && !$(e)) {
				config = e;
				this.e = undefined;
			} else {
				this.e = $(e);
			}

			if (!this.e) {
				this.e = SL.utils.createBodyElement('div');
				this.e.setStyle({
					display : 'none'
				});

				if (e) {
					this.e.writeAttribute('id', e);
				}
			} else if (!this.e.up()) {
				var body = document.getElementsByTagName('body').item(0);
				body.appendChild(this.e);
			}

			this.id = this.e.identify();
			this.e.store('sl_component', this);
			this.e.addClassName('sljs__component__');
			this.config = $H(config);
			this.version = 1;

			this.config.setDefault('spinner_class', 'gui_busy_img');

			if (this.config.get("add_css")) {
				this.e.addClassName(this.config.get("add_css"));
			}

			this.url = this.config.get('url');
			if (!this.url) {
				this.url = this.e.readAttribute('slUrl');
			}

			// console.log('init component:', this);

			if (this.config.get('admin_reset')) {
				this.e.addClassName(this.config.get('admin_reset'));
			}

			this.init();

			if (this.config.get('after_init')) {
				this.config.get('after_init')(this);
			}

			if (this.config.get('auto_init')) {
				if (!this.url) {
					console.log("Auto init component without url", this);
				} else {
					console.log("Auto init component", this);
					this.load(this.url, null);
				}
			}
		},

		init : function() {
		},

		notifyInitEvent : function(event) {
		},

		setUrl : function(url) {
			this.url = url;
		},

		setLoading : function(element) {
			this._cleanup(true);

			var loading = $(this.id + '__custom_loading__');
			if (loading) {
				loading.remove();
				loading.setStyle({
					visibility : 'visible'
				});

				var tempE = new Element('div');
				tempE.setStyle({
					width : this.e.getWidth() + 'px',
					height : this.e.getHeight() + 'px',
					position : 'relative',
					margin : '0',
					padding : '0'
				});

				element.update(tempE);
				tempE.appendChild(loading);
			} else {
				element.update(this._getLoadingContent());
			}

			this._initLoadingDelay();

			this.e.addClassName('component_loading');
		},

		_initLoadingDelay : function() {

			var delay = this.config.get('loading_delay');
			if (!delay || delay <= 0) {
				return;
			}

			this.e.setStyle({
				visibility : 'hidden'
			});

			setTimeout(function(version) {
				if (this.version == version) {
					this.e.setStyle({
						visibility : 'visible'
					});
				}
			}.bind(this, this.version + 1), delay);
		},

		_resetLoadingDelay : function() {

			if (this.e.getStyle('visibility') != 'hidden') {
				return;
			}

			this.version++;
			setTimeout(function(version) {
				if (this.version == version) {
					this.e.setStyle({
						visibility : 'visible'
					});
				}
			}.bind(this, this.version), 1);
		},

		_getLoadingContent : function(large) {
			return '<div class="' + this.config.get('spinner_class') + (large ? " large" : "") + '"></div>';
		},

		_onSuccess : function(o) {
			this.e.removeClassName('component_loading');
			this._onSuccessContent(o.request.options.slVersion, o.responseText);
		},

		_onSuccessContent : function(version, data) {
			/* legacy */
			if (SL.legacy) {
				slCloseTooltips();
			}

			if (version != undefined && version != this.version) {
				return;
			}

			try {
				this._setContent(data);
				this.onSuccess();
				this._emitContentChanged();

				if (this.nextLoadJS) {
					this.nextLoadJS.each(function(f) {
						f();
					});
					this.nextLoadJS = undefined;
				}
			} catch (e) {
				log.error(e);
			}

			this._resetLoadingDelay();
		},

		onFailure : function(o) {

			this.e.removeClassName('component_loading');
			this.e.setStyle({
				visibility : 'visible'
			});

			var version = o.request.options.slVersion;
			if (version != undefined && version != this.version) {
				console.error('SL.Component failure on old version: ', o, ":", version, ":", this.version);
				return;
			}

			if (this.id == 'admin_panel_main') {
				// Reload with small timeout to reduce risk of possible 503
				// errors
				setTimeout(function() {
					document.location.reload();
				}, 1500);
			}

			// TODO: Add way to disable error for save components
			if (this.e.up('html').readAttribute('lang') == 'de') {
				this._setContent('<div class="gui_component_error">Fehler beim Laden der Komponente</div>');
			} else {
				this._setContent('<div class="gui_component_error">Error loading component</div>');
			}
			// }

			console.error('SL.Component failure: ', o);
		},

		onError : function(e) {
			console.error("SL.Component error: ", e);
		},

		_setContent : function(content) {
			this._cleanup(true);
			try {
				this.e.update(content);

				/* legacy */
				if (SL.legacy) {
					SL.legacy.autoInitWidgets(this.e);
				}
			} catch (e) {
				this.onError(e);
			}

			Element._cleanupPrototypeEventCache.defer();
		},

		_emitContentChanged : function(wasNoReload) {
			var parent = this.e.up();
			while (parent) {
				if (parent.tagName == 'BODY') {
					break;
				}
				var component = parent.retrieve('sl_component');
				if (component) {
					component.onChildComponentChanged(this, wasNoReload);
				}
				parent = parent.up();
			}
		},

		_getReloadURL : function(url) {
			if (!url) {
				url = this.url;
			}
			if (this.reloadArgs) {
				$H(this.reloadArgs).each(function(a) {
					url = SL.utils.addOrReplaceArg(url, a.key, a.value);
				});
			}
			return url;
		},

		load : function(url, spinnerID) {
			this.updatedArgs = undefined;

			if (spinnerID) {
				this.setLoadingSpinner(spinnerID);
			} else if (this.config.get('load_shaded')) {
				this.setLoadingSpinner(this.id);
			} else {
				this.setLoading(this.e);
			}

			this.version++;

			this.url = url;
			new Ajax.Request(this.getReloadURL(url), {
				method : 'post',
				onSuccess : this._onSuccess.bind(this),
				onFailure : this.onFailure.bind(this),
				slVersion : this.version
			});
		},

		loadPost : function(url, args, spinnerID) {
			this.updatedArgs = undefined;

			if (spinnerID) {
				this.setLoadingSpinner(spinnerID);
			} else if (this.config.get('load_shaded')) {
				this.setLoadingSpinner(this.id);
			} else {
				this.setLoading(this.e);
			}

			if (this.reloadArgs) {
				$H(this.reloadArgs).each(function(a) {
					if (!args) {
						args = $H();
					}
					args.set(a.key, a.value);
				});
			}

			new Ajax.Request(this.getReloadURL(url, true), {
				method : 'post',
				parameters : args,
				onSuccess : this._onSuccess.bind(this),
				onFailure : this.onFailure.bind(this)
			});
		},

		setReloadArg : function(key, value) {
			if (!this.reloadArgs) {
				this.reloadArgs = $H({});
			}
			this.reloadArgs.set(key, value);
		},

		onSuccess : function() {
		},

		onChildComponentChanged : function(component) {
		},

		setDimension : function(width, height) {
			this.e.setStyle({
				width : width + 'px',
				height : height + 'px'
			});
		},

		setLoadingSpinner : function(spinnerID) {
			var e = $(spinnerID);
			if (e && !e.down('#__' + this.id + '__loading')) {

				var height = this.e.getHeight();
				var inline = height <= 0 || height >= 100;

				var pStyle = e.getStyle('position');
				if (!pStyle || (pStyle != 'absolute' && pStyle != 'relative' && pStyle != 'fixed')) {
					e.setStyle({
						position : 'relative'
					});
				}

				var div = new Element('div', {
					style : 'position:absolute;top:0;bottom:0;left:0;right:0;background-color:#fff;cursor:progress',
					'class' : this.config.get('spinner_class') + (inline ? "_overlay inline" : "_overlay")
				});

				div.setOpacity(0.3);
				e.insert(div);

				var spinner = new Element('div', {
					id : this.id + '__loading',
					style : 'position:absolute;top:0;bottom:0;left:0;right:0;cursor:progress',
					'class' : this.config.get('spinner_class') + (inline ? " inline" : "")
				});

				spinner.setOpacity(0.3);

				e.insert(spinner);
			}

			this.e.addClassName('component_loading');
		},

		loadBackground : function(url) {
			this.url = url;
			this.updatedArgs = undefined;

			this.version++;

			new Ajax.Request(this.getReloadURL(url), {
				method : 'get',
				onSuccess : this._onSuccess.bind(this),
				onFailure : this.onFailure.bind(this),
				slVersion : this.version
			});
		},

		replaceArgs : function(args, background, spinnerID) {

			var url = this.url;

			if (!url) {
				console.error("Replace args: No url for component:", this);
				return;
			}

			$H(args).each(function(a) {
				url = addOrReplaceArg(url, a.key, a.value);
			});

			if (background) {
				this.loadBackground(this.getReloadURL(url));
			} else {
				this.load(this.getReloadURL(url), spinnerID);
			}
		},

		updateArgs : function(args) {

			if (!this.updatedArgs) {
				this.updatedArgs = $H();
			}

			$H(args).each(function(a) {
				this.url = addOrReplaceArg(this.url, a.key, a.value);
				this.updatedArgs.set(a.key, a.value);
			}.bind(this));
		},

		toggleArg : function(arg, initState) {

			if (!this.updatedArgs) {
				this.updatedArgs = $H();
			}
			var old = this.updatedArgs.get(arg);

			if (old == undefined && this.url) {
				old = SL.utils.getArg(this.url);
			}

			var newValue;

			if (old != undefined) {
				if (old === true) {
					newValue = true;
				} else if (old === false) {
					newValue = false;
				} else if (old == 'true') {
					newValue = true;
				} else {
					newValue = false;
				}
				newValue = !old;
			} else {
				newValue = initState;
			}

			this.url = SL.utils.addOrReplaceArg(this.url, arg, newValue);
			this.updatedArgs.set(arg, newValue);
		},

		refresh : function(background, keysToRemove, shaded) {

			if (!this.url) {
				var parent = this.getParentComponent();
				if (parent != null) {
					parent.refresh(background, keysToRemove, shaded);
				} else {
					console.error("No parent to refresh:", this);
				}
				return;
			}

			if (keysToRemove) {
				var parts = this.url.split('?');
				if (parts.length > 1) {
					this.url = parts[0] + '?';
					parts = parts[1].split('&');

					var first = true;
					for ( var i = 0; i < parts.length; i++) {
						var remove = false;
						for ( var u = 0; u < keysToRemove.length; u++) {
							if (parts[i].startsWith('args.' + keysToRemove[u])) {
								remove = true;
							}
						}

						if (!remove) {
							if (first) {
								first = false;
							} else {
								this.url += '&';
							}
							this.url += parts[i];
						}
					}
				}
			}

			if (shaded) {
				this.load(this.getReloadURL(), this.id);
			} else if (background) {
				this.loadBackground(this.getReloadURL());
			} else {
				this._cleanup(true);
				this.load(this.getReloadURL());
			}
		},

		refreshDelay : function(background, millis) {
			this.version++;
			if (this.e && this.e.parentNode) {
				setTimeout(this._delayedRefresh.bind(this, background, this.version), millis);
			}
		},

		_delayedRefresh : function(background, theVersion) {
			if (this.version == theVersion) {
				this.refresh(background);
			}
		},

		getParentComponent : function() {
			var parent = this.e.up();
			while (parent) {
				if (parent.tagName == 'BODY') {
					return null;
				}
				var component = parent.retrieve('sl_component');
				if (component) {
					return component;
				}

				parent = parent.up();
			}

			return null;
		},

		getReloadURL : function(url, post) {
			if (!url) {
				url = this.url;
			}
			if (!post && this.reloadArgs) {
				$H(this.reloadArgs).each(function(a) {
					url = SL.utils.addOrReplaceArg(url, a.key, a.value);
				});
			}
			return url;
		},

		_destroy : function() {

			console.log("Destroying component:", this.id);

			this._cleanup();

			this.e.store('sl_component', undefined);
			if (this.e.parentNode) {
				this.e.remove();
			}
			// async test do prevent leaks
			(function(id) {
				var e = $(id);
				if (e && e.parentNode) {
					e.remove();
				}
			}.curry(this.id).defer());
		},

		uploadForm : function(formId, submit, foreignForm) {

			this.version++;

			try {
				var elements = this.e.select('.sljs__component__');
				if (elements) {
					elements.each(function(e) {
						try {
							var c = e.retrieve('sl_component');
							if (c) {
								c.updateForPost();
							}
						} catch (e) {
							console.error(e);
						}
					});
				}
			} catch (e) {
				console.log(e);
			}

			try {
				slUpdateEditors(this.e, true);
			} catch (e) {
				console.error(e);
			}

			var e = SL.utils.createBodyElement('div');
			var id = e.identify() + "_iframe";

			var iframe = new Element('iframe', {
				id : id,
				name : id,
				stlye : 'display:none',
				src : 'about:blank'
			});
			e.appendChild(iframe);

			iframe.observe('load', this._iframeLoaded.bind(this, e, id, this.version));
			var f = $(formId);

			if (this.updatedArgs) {
				try {
					this.updatedArgs.each(function(a) {
						var e = f.down('input[name="args.' + a.key + '"]');
						if (e) {
							e.writeAttribute('value', a.value + '');
						} else {
							f.appendChild(new Element('input', {
								type : 'hidden',
								name : 'args.' + a.key,
								value : a.value + ''
							}));
						}
					});
				} catch (ex) {
					console.error(ex);
				}
			}

			if (foreignForm) {
				var f2 = $(foreignForm);
				try {
					if (f2) {
						var h = $H(f2.serialize(true));
						h.each(function(f, v) {
							f.insert({
								bottom : new Element("input", {
									type : 'hidden',
									name : v.key,
									value : SL.utils.toArrayString(v.value)
								})
							});
						}.curry(f));
					}
				} catch (e) {
					console.error("Error merging foreign form:", e);
				}
			}

			f.writeAttribute('target', id);
			if (submit) {
				f.submit();
			}

			return true;
		},

		updateForPost : function() {
		},

		_iframeLoaded : function(e, id, version) {
			if (this.version != version) {
				console.error("Old version:", this.version, " ", version);
				return;
			}

			console.log("Update for form upload:", this.id);

			var url = $(id).contentWindow.sl_reload_url;
			if (url) {
				this.url = url;
			}

			var content = $(id).contentWindow.sl_reload_content;
			this._onSuccessContent(version, content);

			(function(e) {
				e.remove();
			}.curry(e).defer());
		},

		submitUploadForm : function(formId, key, value) {
			var f = $(formId);

			if (key && value) {
				f.appendChild(new Element('input', {
					type : 'hidden',
					name : key,
					value : value
				}));
			}
			this.uploadForm(formId, true);
		},

		_cleanup : function(withoutSelf) {
			if (!withoutSelf) {
				if (this.isCleanedUp) {
					return;
				}

				this.isCleanedUp = true;
				try {
					this.cleanup();
				} catch (e) {
					console.error(e);
				}

				var h = this.e.retrieve('sl_delete_notify');
				if (h) {
					h.each(function(p) {
						try {
							p.value._destroy();
						} catch (ex) {
							console.error(ex);
						}
					});
					this.e.store('sl_delete_notify', undefined);
				}
			}

			try {
				var elements = this.e.select('.sljs__component__');
				if (elements) {
					elements.each(function(e) {
						var c = e.retrieve('sl_component');
						if (c) {
							c._cleanup();
						}
					});
				}
				elements = this.e.select('.sljs__delete_notify__');

				if (elements) {
					elements.each(function(e) {
						var h = e.retrieve('sl_delete_notify');
						if (h) {
							h.each(function(p) {
								try {
									p.value._destroy();
								} catch (ex) {
									console.error(ex);
								}
							});
						}
						e.store('sl_delete_notify', undefined);
					});
				}
			} catch (e) {
				console.error(e);
			}
		},

		connectDestroy : function(triggerElement) {
			if (!triggerElement) {
				return;
			}
			var e = $(triggerElement);
			var h = e.retrieve('sl_delete_notify');
			if (!h) {
				h = $H();
				e.store('sl_delete_notify', h);
				e.addClassName('sljs__delete_notify__');
			}
			if (!h.get(this.id)) {
				h.set(this.id, this);
			}
		},

		cleanup : function() {
		},

		hide : function(recursive) {
			if (recursive === true) {
				var parent = this.getParentComponent();
				if (parent) {
					parent.hide();
				}
				return;
			}
			// normal components should ignore non recursive hides
		},

		registerAfterNextLoadJS : function(f) {

			if (!this.nextLoadJS) {
				this.nextLoadJS = $A();
			}

			this.nextLoadJS.push(f);
		}
	});

})();

