/*
 * @copyright Chamaeleon AG
 * 
 * @depends prototype 1.6.0.3
 * @depends script.aculo.us 1.8.2
 */

var SEARCHALYZR_EXCEPTION_URL_NOT_A_STRING = 1;
var SEARCHALYZR_EXCEPTION_URL_MISSING = 2;

var SEARCHALYZR_EXCEPTION_SYSTEMS_NOT_AN_ARRAY = 3;
var SEARCHALYZR_EXCEPTION_SYSTEMS_MISSING = 4;

var SEARCHALYZR_EXCEPTION_SYSTEM_ID_NOT_A_STRING = 5;
var SEARCHALYZR_EXCEPTION_SYSTEM_ID_MISSING = 6;
var SEARCHALYZR_EXCEPTION_SYSTEM_ID_DUPLICATE = 7;

var SEARCHALYZR_EXCEPTION_SYSTEM_LABEL_NOT_A_STRING = 8;
var SEARCHALYZR_EXCEPTION_SYSTEM_LABEL_MISSING = 9;

var SEARCHALYZR_EXCEPTION_SYSTEM_URL_NOT_A_STRING = 10;

var SEARCHALYZR_EXCEPTION_SYSTEM_COLUMNS_NOT_AN_ARRAY = 21;

var SEARCHALYZR_EXCEPTION_PARAMETER_ID_NOT_A_STRING = 11;
var SEARCHALYZR_EXCEPTION_PARAMETER_ID_MISSING = 12;

var SEARCHALYZR_EXCEPTION_PARAMETER_VALUES_NOT_AN_ARRAY = 15;
var SEARCHALYZR_EXCEPTION_PARAMETER_VALUES_MISSING = 16;

var SEARCHALYZR_EXCEPTION_BUILDER_MISSING = 19;

var SEARCHALYZR_EXCEPTION_CONTAINER_MISSING = 20;

/**
 * Exception im JSFE4searchalyzr Context.
 */
var SearchalyzrException = Class.create({
	/**
	 * @param error - Der Fehlercode
	 * @param message - Die Fehlermeldung
	 * @param value - Der fehlerhafte Wert, zu dem der Fehler gehört
	 * @param data - referenziertes Datenmodell
	 */
	initialize: function(error, message, value, data) {
		this.error = error;
		this.message = message;
		this.value = value;
		this.data = data;
	},
	
	toString: function() {
		return 'Error ' + this.error + ': "' + this.message + '" with value ' + Object.toJSON(this.value) + ' on data ' + Object.toJSON(this.data);
	}
});

/**
 * JavaScriptFrontEnd4searchalyzr
 * 
 * Die Klasse ist der Zentrale Kern, die alle Instanzen der restlichen Klassen
 * verwaltet. Diese Klasse + der Builder stellen das öffentliche Interface dar. 
 * 
 */
var Searchalyzr = Class.create({
	/**
	 * @param settings [JSON] 
	 * 
	 */
	initialize: function(settings) {
		var self = this;
		/**
		 * Default-Suchparameter
		 */
		this.defaultSearchParams = {
			"categoryStates" : [],
			"columns" : [],
			"searchQuery" : {
				"parameterAssignments" : [], // wird bei Initialisierung befüllt
				"terms" : [],
				"limit" : 10
			},
			"vrbSessionId" : { "sid" : "", "uid" : "" }
		};
		this.systems = {};
		this.url = null;
		this.pageCount = 10;
		/**
		 * jeweils aktuelle Suchparameter
		 */
		this.searchParams = {};
		/**
		 * @type SearchalyzrBuilder
		 */
		this.builder = null;
		/**
		 * @type SearchalyzrSystemRepresentationManager
		 */
		this.manager = null;
		/**
		 * @type string
		 */
		this.session = null;
		
		// URL ist eine Zeichenkette?
		if (!Object.isString(settings.url))
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_URL_NOT_A_STRING,
					"url is not a string",
					settings.url,
					settings);
		
		// URL ist leer?
		if (settings.url.strip().empty())
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_URL_MISSING,
					"url is missing or empty",
					null,
					settings);
		
		// Parameters setzen, falls nicht vorhanden.
		if (!settings.parameters)
			settings.parameters = [];

		// Systems ist ein Array?
		if (!Object.isArray(settings.systems))
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_SYSTEMS_NOT_AN_ARRAY,
					"systems is not an array",
					settings.systems,
					settings);
		
		// Systems ist leer?
		if (!settings.systems.size())
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_SYSTEMS_MISSING,
					"systems is missing or empty",
					null,
					settings);
		
		// Builder ist leer?
		if (!settings.builder)
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_BUILDER_MISSING,
					"builder instance is missing or empty",
					null,
					settings);
		
		// Container ist leer?
		if (!settings.container)
			throw new SearchalyzrException(
					SEARCHALYZR_EXCEPTION_CONTAINER_MISSING,
					"container dom object is missing or empty",
					null,
					settings);
		
		// --------------------
		
		// URL setzen
		this.url = settings.url;
		
		// pageCount ist durch Settings überschreibbar
		if (settings.pageCount){
			this.defaultSearchParams.searchQuery.limit = this.pageCount = settings.pageCount;
		}
		
		// vrbSession ist durch Settings überschreibbar
		if (settings.vrbSessionId){
			this.defaultSearchParams.vrbSessionId = settings.vrbSessionId;
		}

		// Builder setzen
		this.builder = settings.builder;
		
		// Container setzen
		this.container = settings.container;
		
		// Session übernehmen
		this.session = settings.session;
		
		// System Representation Manager erzeugen
		this.manager = new SearchalyzrSystemRepresentationManager(this.builder, this.container);
		
		// Parameter durchgehen, prüfen und setzen
		settings.parameters.each(function(parameter) {
			// --- Typ und Wertprüfungen -->
			
			// parameterId ist eine Zeichenkette?
			if (!Object.isString(parameter.parameterId))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_PARAMETER_ID_NOT_A_STRING,
						"parameter parameterId is not a string",
						parameter.parameterId,
						parameter);
			
			// parameterId ist leer?
			if (parameter.parameterId.strip().empty())
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_PARAMETER_ID_MISSING,
						"parameter parameterId is missing or empty",
						null,
						parameter);

			// Values ist ein Array?
			if (!Object.isArray(parameter.values))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_PARAMETER_VALUES_NOT_AN_ARRAY,
						"parameter values is not an array",
						parameter.values,
						parameter);

			// Parameter Values auf Zeichenketten reduzieren
			parameter.values = parameter.values.collect(function(item) {
				if(Object.isString(item) && !item.strip().empty())
					return item.strip();
			});
			
			// Values sind leer?
			if (!parameter.values.size())
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_PARAMETER_VALUES_MISSING,
						"parameter values is missing or empty",
						null,
						parameter);

			// <-- Typ und Wertprüfungen ---
			
			// Parameter hinzufügen
			self.defaultSearchParams.searchQuery.parameterAssignments.push({
				parameterId: parameter.parameterId.strip(),
				values: parameter.values
			});
		});
	
		var i = 0;
		
		// Systeme durchgehen, prüfen und setzen
		settings.systems.each(function(system) {
			// --- Typ und Wertprüfungen -->
			// ID ist eine Zeichenkette?
			if (!Object.isString(system.id))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_ID_NOT_A_STRING,
						"system id is not a string",
						system.id,
						system);
			
			// ID ist leer?
			if (system.id.strip().empty())
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_ID_MISSING,
						"system id is missing or empty",
						null,
						system);
			
			// Label ist eine Zeichenkette?
			if (!Object.isString(system.label))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_LABEL_NOT_A_STRING,
						"system label is not a string",
						system.label,
						system);
			
			// Label ist leer?
			if (system.label.strip().empty())
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_LABEL_MISSING,
						"system label is missing or empty",
						null,
						system);
			
			// Columns ist ein Array?
			if (system.columns && !Object.isArray(system.columns))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_COLUMNS_NOT_AN_ARRAY,
						"system columns is not an array",
						system.columns,
						system);
			
			if (Object.isString(system.url) && system.url.strip().empty())
				system.url = null;
			
			if (system.url && !Object.isString(system.url))
				throw new SearchalyzrException(
						SEARCHALYZR_EXCEPTION_SYSTEM_URL_NOT_A_STRING,
						"system url is not a string",
						system.url,
						system);
			// <-- Typ und Wertprüfungen ---
			
			// System hinzufügen
			var id = system.id.strip();
			var tabid = i + "_" + system.id.strip();
			var label = system.label.strip();
			var header = system.header;
			var columns = system.columns;
			var parameters = system.parameters;

			self.manager.provideTab(tabid, label, header);
			
			var s = new SearchalyzrSystem(
				id,
				tabid,
				label,
				header,
				(system.url ? system.url.strip() : null),
				parameters,
				columns,
				self.manager.getTabTitleContainer(tabid),
				self.manager.getTabContentContainer(tabid),
				self.builder.createEmptyIndicator(tabid, label, (system.url ? system.url.strip() : '')),
				self.builder.createErrorMessage(tabid)
			);
					
			// System hinzufügen
			self.systems[s.getTabid()] = s;
			// Empty Indicator hinzufügen
			self.builder.addEmptyIndicator(s.getContainer(), s.emptyIndicator);
			self.builder.addErrorMessage(s.getContainer(), s.errorMessage);
			if (system.linkTarget) {
				self.builder.setLinkTarget(system.linkTarget);
			}
			if (system.urlOverwrite) {
				self.builder.setUrlOverwrite(system.urlOverwrite);
			}
			if (system.searchResultFilter) {
				s.setSearchResultFilter(system.searchResultFilter);
			}
			
			i++;
		});
		// SystemManager's erzeugen
		Object.keys(self.systems).each(function(key) {
			new SearchalyzrSystemManager(
					self,
					self.systems[key]);
		});
		this.manager.activateTab(this.manager.getRecent());
	},
	
	/**
	 * Führt eine Suche auf den gegebenen Systemen durch.
	 * 
	 * @param terms [String], [Array<String>] oder searchParams-Objekt
	 * Suchterm, nach dem gesucht wird.
	 * 
	 * @param systems [Array<String>] (optional)
	 * Liste von System-IDs. Ist diese nicht angegeben, wird in allen definierten Systemen gesucht.
	 * 
	 * @return void
	 */
	doSearch: function(term, systems) {
		systems = this._getSystems(systems);
		
		var self = this;
		
		var systemSet = {};
		for (var i = 0; i < systems.length; i++) systemSet[systems[i]] = true;
		
		Object.keys(self.systems).each(
			function(system) {
				if (systemSet[system] || systemSet[self.systems[system].id]) {
					if (term && term.searchQuery) {
						self.searchParams[system] = self.deepClone(term);
					} else {
						self.searchParams[system] = self.getDefaultSearchParams();
						self.searchParams[system].searchQuery.terms = self.normalizeTerms(term);
					}
					self.builder.showTab(self.systems[system].getTitle(), self.systems[system].getContainer());
					self.systems[system].manager.updateAll();
				} else {
					self.builder.hideTab(self.systems[system].getTitle(), self.systems[system].getContainer());
				}
			}
		);
		
		self.manager.activateTab(Object.keys(self.systems).first());
	},
	
	normalizeTerms: function(term) {
		term = term != null ? (term.push ? term : term.split(/\s+/)) : [];
		for (var i = 0; i < term.length; i++) {
			if (!term[i]) {
				term.splice(i--, 1);
			}
		}
		return term;
	},

	/**
	 * Hier wird nichts visualisiert, sondern es werden nur die Daten abgerufen.
	 * (Anforderung aus dem IONAS Context)
	 */
	doExtendedSearch: function(searchParams, systems) {
		systems = this._getSystems(systems);
		
		var self = this;
		var first = true;
		
		var systemSet = {};
		for (var i = 0; i < systems.length; i++) systemSet[systems[i]] = true;
		
		Object.keys(self.systems).each(
			function(system) {
				if (systemSet[system] || systemSet[self.systems[system].id]) {
					if (first) {
						self.manager.activateTab(system);
						first = false;
					}
					var mySearchParams = self.deepClone(searchParams);
					self.searchParams[system] = mySearchParams;
					self.builder.showTab(self.systems[system].getTitle(), self.systems[system].getContainer());
					self.systems[system].manager.getSearchResult(mySearchParams);
				} else {
					self.builder.hideTab(self.systems[system].getTitle(), self.systems[system].getContainer());
				}
			}
		);
	},
	
	_getSystems: function(systems) {
		if (Object.isString(systems)) {
			systems = [ systems ];
		}
		
		if (!systems || !Object.isArray(systems)) {
			systems = Object.keys(this.systems);
		}
		
		systems = systems.without(null, undefined).uniq();

		return systems;
	},

	getDefaultSearchParams: function() {
		return this.deepClone(this.defaultSearchParams);
	},
	
	getSearchParams: function(tabid) {
		var self = this;
		if (!self.searchParams[tabid]) self.searchParams[tabid] = self.getDefaultSearchParams();
		// Parameter durchgehen, prüfen und setzen
		self.systems[tabid].getParameters().each(function(parameter) {		
			// Parameter hinzufügen
			self.searchParams[tabid].searchQuery.parameterAssignments.push(
				{parameterId: parameter.parameterId.strip(),
				 values: parameter.values}
				);
		});
		return self.searchParams[tabid];
	},
	
	deepClone: function(obj) {
		if (Object.isArray(obj)) {
			var result = [];
			for (var i = 0; i < obj.length; i++) {
				result[i] = this.deepClone(obj[i]);
			}
			return result;
		} else if (typeof(obj) == "object") {
			var result = {};
			for (var i in obj) {
				result[i] = this.deepClone(obj[i]);
			}
			return result;
		} else {
			return obj;
		}
	}
});
