/**
* cssSelector.js
*
* Erstellt die noetigen Dialoge zur Auswahl und Speicherung der css-Definitionen in einer Box
*
* benoetigt:
* <script type="text/javascript" src="../../js/jquery-1.4.2.min.js"></script>
*
* TODO:
* Mehrere Select-Boxen gleichzeitig ermöglichen
* Code-Minimizer fertig stellen um default-Werte raus zu werfen
*
*/




function CssSelector (cssXmlPath, containerName, previewName, defName)
{
	// Parameterverarbeitung
	this.cssXmlPath    = cssXmlPath;	// Pfad zur CSS-XML-Datei
	this.containerName = containerName; // Id oder Name des Kontainers in welche die einzelnen divs gelegt werden sollen
	this.previewName   = previewName; 	// Id oder Name des Vorschauelements
	this.defName       = defName; 		// Id oder Name der Box in welcher die Tags gespeichert werden

	// Attribute
	this.curObj  = null;				// Speicherung des zuletzt erzeugten Objekts nach Laden einer Box
	this.element = null;				// Name + Objekt der einzelnen div's
	this.xml     = null					// XML-daten
	this.nextElement = {				// liefert anhand eines Tags den naechsten Tag in der Hierarchie
		'undefined': 'category',
		'category' : 'trait',
		'trait'    : 'values',
		'values'   : 'value'
	}
	
	// Konfiguration
	this.minBoxSize = 2;				// Minimale Hoehe der auszugebenden Select-Box
	this.maxBoxSize = 10;				// Maximale Hoehe der auszugebenden Select-Box
	




	/**
	* Bereitet die noetigten Daten vor
	* @return void
	*/
	this.prepare = function ( )
	{
		// Elemente initialisieren
		this.element = {
			'boxCategory'  : this.addObject(),
			'boxTrait'     : this.addObject(),
			'boxValueKind' : this.addObject(),
			'boxValue'     : this.addObject()
		};

		// Boxen erstellen
		newthis = this;
		$.each(this.element, function(key, val) {
			$(containerName).append('<div id="'+key+'" class="cssBox">'+key+'</div>');
		});

		// XML Daten lesen und abspeichern
		$(document).ready(function(){
			$.get(newthis.cssXmlPath, {}, function(data){
				newthis.xml = data;
				newthis.afterPrepare();
			});
		});
	}
	
	
	this.afterPrepare = function ( )
	{
		newthis = this;
		this.addEventHandler();

		//var doit=true;
		$(this.xml).find('ref').each(function() {
			$(this).replaceWith(newthis.getXmlByReferenz($(this).text()));
		});

		obj = this.getXmlByAttrRek(this.xml, {})
		$('#boxCategory').html(this.getContentBox(obj));
		$('#boxCategory').css({display: 'inline'});

		/*		
		$(obj['object']).find('value').each(function() {
			$(previewName).html($(previewName).html()+', '+$(this).text());
		});
		*/
	}


	/**
	* Fuehrt ein Highlighting anhand eines regulaeren Ausdrucks durch
	* @param regExpr : Regulaere Ausdruck
	* @param string  : string welcher auf gueltigkeit geprueft werden soll
	* @param highlightObject : das Objekt, welches ge-highlighted werden soll
	* @return bool
	*/
	this.pregMatchHighlighting = function(regExpr, string, highlightObject)
	{
		if(this.isValid(regExpr, string) == true) { // passed
			$(highlightObject).css({'background-color' : '#0F0'});	// green
			return true;
		} else { // failed
			$(highlightObject).css({'background-color' : '#F00'}); // red
			return false;
		}
	}
	

	/**
	* Prueft ob ein String dem entspr. regulaeren Ausdruck genuegt
	* @return bool
	*/
	this.isValid = function(regExpr, string)
	{
		if (regExpr != null) {
			if(string.match(regExpr) == null) { // failed
				return false;
			} else { // passed
				return true;
			}		
		}

		return true;
	}
	
	
	/**
	* Sucht rekursiv
	* @param xml           : XML-Object
	* @param jsonXmlObject : XML-Objekt im Json-Stiel
	* @return xmlobj
	*/
	this.getXmlByAttrRek = function(xml, jsonXmlObject)
	{
		obj = {'object' : this.xml};
		
		for( var key in jsonXmlObject ) {
			//alert(key+arrFind[key]);
			obj = this.getXmlByAttr(obj['object'], key, jsonXmlObject[key]);
			if (obj == null) {
				return null;
			}
		}
		
		return obj;
	}
	

	/**
	* Liefert die gewuenschten XML-Daten
	* @param xml     : XML-Object
	* @param findTag : Suche alle Tags im XML-Objekt
	* @param value   : Suche den Tag mit name="value"
	*                  wenn value == null, dann wird nur das letzte Objekt ausgegeben
	* @return xmlobj
	*/	
	this.getXmlByAttr = function(xml, findTag, value)
	{
		var obj = null;
		var newthis = this;
		
		$(xml).find(findTag).each(function(){
			var name    = $(this).attr('name');
			var desc    = $(this).attr('desc');
			var text    = $(this).text();
			var regExpr = null;
			var init    = '';
			var comb    = '';
			var before  = '';
			var after   = '';

			// Attribute nur bei Values ermitteln
			if (value == 'input') {
				var init = $(this).attr('initial');
				var comb = $(this).attr('combinable');
				regExpr  = $(this).find('regExpr').text();

				before   = $(this).find('before').text();
				if (before == null) before = '';

				after    = $(this).find('after').text();
				if (after == null) after = '';
			}

			//  Werte in Objekt speichern
			if (name == value || value == null) {
				obj = {
					'object'     : this,
					'name'       : name,
					'desc'       : desc,
					'initial'    : init,
					'combinable' : comb,
					'tag'        : findTag,
					'regExpr'    : regExpr,
					'before'     : before,
					'after'      : after
				};
			}
		});
		
		return obj;
	}

	/**
	* Liefert einen Datensatz anhand einer in XML definierten Referenz
	* Referenz-Tag muss wie folg aufgebaut sein: <referenz type="input|select">Path1|Path2|Path3</referenz>
	* @param strRef : Referenzstring im Format Pathq|Path2|Path3
	* @return xmlobj
	*/
	this.getXmlByReferenz = function (strRef)
	{
		result = '';
		$(this.xml).find('referenz').each(function(){
			var name = $(this).attr('name');
			if (name == strRef) {
				//alert($(this).attr('name'));
				result = $(this).clone(true);
			}
		});
		
		return result;
	}

	
	/**
	* Erstellt ein Object zur Abfrage offnen/schliessen einer Box
	* @return object
	*/
	this.addObject = function ( )
	{
		obj = new Object();
		obj['selected'] = '';
		obj['lastSelected'] = '';
		obj['lastX'] = ''; // Letzte X-Position beim Mausklick
		return obj;
	}


	/**
	* Gibt die entsprechende Box aus
	* @param XML-Objekt
	* @return string
	*/
	this.getContentBox = function(obj)
	{
		if ( obj == null) {
			return null;
		}

		tag = this.nextElement[obj['tag']];		
		if (tag == 'value') {
			if ($('#values').val() == 'input') {
				return this.getInputBox(obj);
			}
		}
		
		return this.getSelectBox(obj);
	}


	/**
	* Liefert durch Uebergabe des XML-Objekts den Inhalt in einer Select-Box aus
	* @param obj    : XML Objekt
	* @return string
	*/
	this.getSelectBox = function(obj)
	{
		var result = '';
		var size = 0;
		elName = this.nextElement[obj['tag']];

		// Options
		$(obj['object']).find(elName).each(function() {
			size++;

			if (elName == 'value') { // Datenermittlung fur den entgueltigen Wert
				key = $(this).text();
				val = $(this).text();
			} else { // Datenermittlung fuer alle Schritte, welche vor dem Value-Schritt passieren
				key = $(this).attr('desc');
				val = $(this).attr('name');
			}
			result += '<option value="'+val+'">'+key+' ['+val+']'+'</option>'+"\n";
		});

		// Boxhoehe		
		if (size > this.maxBoxSize) {
			size = this.maxBoxSize;
		}
		if (size < this.minBoxSize) {
			size = this.minBoxSize;
		}

		return '<select name="'+elName+'" id="'+elName+'" size="'+size+'">'+result+'</select>';	
	}


	/**
	* Liefert durch Uebergabe des XML-Objekts den Inhalt in einer Input-Box aus
	* @param obj    : XML Objekt
	* @return string
	*/
	this.getInputBox = function(obj)
	{
		elName = this.nextElement[obj['tag']];
		init = $(obj['object']).find('value').text();
		
		val = this.getInputValue($(this.defName).text(), $('#trait').val(), init);
		return '<input type="text" name="'+elName+'" id="'+elName+'" value="'+val+'" />';
	}


	/**
	* Erstellt die noetigen Daten
	* @param collector : Id/Name des Elements in welches die einzelnen genierierten CSS-Elemente geschrieben werden sollen
	* @return void
	*/
	this.show = function ( )
	{
		newthis = this; // behebt das JavaScript/JQuery-this Problem
		this.prepare();
	}

	
	/**
	* Fuegt die EventHandler hinzu
	* @return void
	*/
	this.addEventHandler = function ( )
	{	
		newthis = this;
		jQuery(document).ready(function(){

			// ===== Boxen-Events =====
			$("#category").live('click', function(e){
				jsonObject = {
					'category' : $('#category').val()
				};
				newthis.loadBox(e, 'boxCategory', 'boxTrait', 'category', jsonObject, '');
			});

			$("#trait").live('click', function(e){
				jsonObject = {
					'category' : newthis.element['boxCategory']['selected'],
					'trait'    : $('#trait').val()
				};
				newthis.loadBox(e, 'boxTrait', 'boxValueKind', 'trait', jsonObject, '');
			});

			$("#values").live('click', function(e){
				additional = '';
				additional += '<div>';
				additional += '<input type="submit" id="add" name="add" value="add" />';
				additional += '<input type="submit" id="close" name="close" value="close" />';
				additional += '</div>';

				jsonObject = {
					'category' : newthis.element['boxCategory']['selected'],
					'trait'    : newthis.element['boxTrait']['selected'],
					'values'   : $('#values').val()
				};
				newthis.loadBox(e, 'boxValueKind', 'boxValue', 'values', jsonObject, additional);
				$("#value").attr('title', 'Reg.Expression: '+newthis.curObj['regExpr']);
			});

			// ===== sonstige Events =====
			$("#value").live('change click keyup', function(){
				if (newthis.pregMatchHighlighting(newthis.curObj['regExpr'], $('#value').val(), '#value')) {
					newthis.preview();
				}
			});
			
			$("#close").live('click', function(e){
				newthis.addContent($(newthis.defName), $(newthis.defName).html(), 'rollback');
				newthis.closeAll();
			});
			
			// Css- generieren
			$("#add").live('click', function(e){
				if (newthis.pregMatchHighlighting(newthis.curObj['regExpr'], $('#value').val(), '#value')) {
					newthis.preview();
					newthis.getCss();
				}
			});
			$('#value').live('dblclick', function(e){
				if (newthis.pregMatchHighlighting(newthis.curObj['regExpr'], $('#value').val(), '#value')) {
					newthis.preview();
					newthis.getCss();
				}
			});
			$('#value').live('keydown', function(e){
				if (e.keyCode == 13) { // enter
					if (newthis.pregMatchHighlighting(newthis.curObj['regExpr'], $('#value').val(), '#value')) {
						newthis.preview();
						newthis.getCss();
					}
				}
				else if (e.keyCode == 27) { // escape
					newthis.closeAll();
				}
				if (e.keyCode == 38) { // keyup
					val = document.getElementById('value').value;
					if (val == parseInt(val) || val == parseFloat(val)) {
						newthis.preview();
						document.getElementById('value').value = parseInt(val)+1;
					}
				}
				else if (e.keyCode == 40) { // keydown
					val = document.getElementById('value').value;
					if (val == parseInt(val) || val == parseFloat(val)) {
						newthis.preview();
						document.getElementById('value').value = parseInt(val)-1;
					}
				}
			});
		});
	}
	
	
	/**
	* Blendet eine Box ein-/aus
	* @param e : Mauskoordinaten
	* @param curElId : Id des gegenwaertigen Elements
	* @param nextElId : Id des Elements welches angezeigt werden soll
	* @param selectBox : Id der Selektbox aus welcher die Werte gelesen werden sollen
	* @param jsonXmlObject : JsonObjekt
	* @param additional : Zusaetzliche auszugebende Daten
	* @return void
	*/
	this.loadBox = function ( e, curElId, nextElId, selectBox, jsonXmlObject, additional )
	{
		newthis = this;
		this.element[curElId]['selected'] = $('#'+selectBox).val();
		if (this.element[curElId]['selected'] != this.element[curElId]['lastSelected']) {
			$('#'+nextElId).css({left: e.pageX+15, top: e.pageY});
			this.element[curElId]['selected'] = $('#'+selectBox).val();


			obj = newthis.getXmlByAttrRek(newthis.xml, jsonXmlObject)
			this.curObj = obj; // Aktuelles Objekt offentlich machen um von ausserhalb zugreifen zu koennen
			$('#'+nextElId).html(newthis.getContentBox(obj)+additional);


			$('#'+nextElId).fadeIn("fast");
			this.element[curElId]['lastSelected'] = this.element[curElId]['selected'];
		} else {
			$('#'+nextElId).fadeOut("fast");
			this.element[curElId]['lastSelected'] = '';
		}

		this.closeLower(nextElId);
	}


	/**
	* Schliesst alle angegebenen Elemente
	* @return void
	*/
	this.closeAll = function ( )
	{
		this.closeLower(null);
	}


	/**
	* Schliesst alle angegebenen Elemente in der Hierarchie abwaerts ab dem angegebenen Element
	* name : Elementname ab welchem abwaerts geschlossen werden soll
	*        wenn null, dann werden alle Boxen (ausser der obersten) geschlossen
	* @return void
	*/
	this.closeLower = function ( name )
	{
		var now = false;
		for( var key in this.element ) {
			if (now == true) {
				this.closeBox(key);
			}
			if (key == name || name == null) {
				now = true;
			}
		}
	}


	/**
	* Schliesst die definierten Elemente
	* @param elName : Name des Box
	* @return void
	*/			
	this.closeBox = function ( elName )
	{
		$('#'+elName).fadeOut("fast");
		this.element[elName]['selected'] = '';
		this.element[elName]['lastSelected'] = '';
	}


	/**
	* Generiert nach Klick oder Aenderung den Code
	* @return void
	*/
	this.getCss = function ( )
	{
		obj = $(newthis.defName);
		data = this.cssMerge($(this.defName).text(), this.cssGenerator());

		newthis.addContent(obj, data, 'add');
	}


	/**
	* Erstellt eine Vorschau
	* @return void
	*/
	this.preview = function ( )
	{
		obj = $(newthis.defName);
		data = this.cssMerge($(this.defName).text(), this.cssGenerator());
		$('#cssTestTag').html('.cssTest{'+data+'}');
	}

	
	/**
	* Generiert den entgueltigen CSS-Code aus den gesammelten Daten
	* @return string
	*/
	this.cssGenerator = function ( )
	{
		newthis = this;
		selected = $('#value').val();
		return newthis.element['boxTrait']['selected'] + ': ' + newthis.curObj['before'] + selected + newthis.curObj['after'] + ';';
	}

	
	/**
	* Merged das aktuelle CSS-Element mit der CSS-Sammlung
	* @param haystack : Sammlung an CSS-Elemente
	* @param needle   : CSS-Element welches gemerged werden soll
	* @return string
	*/
	this.cssMerge = function ( haystack, needle )
	{
		result = '';
		
		// Trennung
		found = false;
		arrNeedle   = needle.split(" ");
		arrHaystack = haystack.split("\n");

		// Ersetzung
		if (arrHaystack.length >= 1) {
			for (var key in arrHaystack) {
				if (this.strpos(arrHaystack[key], arrNeedle[0]) !== false) { // TODO:
					arrHaystack[key] = needle;
					found = true;
					break;
				}
			}
		}
		
		if (found == false) {
			arrHaystack.push(needle);
		}

		// Elementsortierung
		arrHaystack = this.cssHierarchieOptimizer(arrHaystack);

		// Zusammenfuegung
		result = arrHaystack.join("\n");

		return result;		
	}

	
	
	/**
	* CSS-Minimierer: Entfernt unnoetige (default) Traits
	* @param arrHaystack : Datenpool
	* @return string
	*/
	this.cssCompressor = function ( arrHaystack )
	{
		
	}

	
	/**
	* Stellt die Definitionsauflistung nach gewissen Regeln um, damit jeder CSS-Block den Selben Aufbau hat
	* @param arrHaystack : Datenpool
	* @return string
	*/
	this.cssHierarchieOptimizer = function ( arrHaystack )
	{
		// Alphanumerische Sortierung mit gewissen definierten Regeln
		return arrHaystack.sort();
	}

	
	/**
	* Fuegt in die Input-Box den bereits definierten Wert ein. Falls keiner existiert wird der init-Wert verwendet
	*  @param haystack : Aktuell definierter CSS-Dump
	* @param init      : init-Wert
	* @return string
	*/
	this.getInputValue = function (haystack, trait, init)
	{
		result = '';
		trait += ':';
		arrHaystack = haystack.split("\n");

		if (arrHaystack.length >= 1) {
			for (var key in arrHaystack) {
				if (this.strpos(arrHaystack[key], trait) !== false) { // TODO: strpos ist ungeeignet, da background-image mit Suche Imgage und Background-image zutrifft!!
					arrTmp = arrHaystack[key].split(' ');
					arrTmp.shift();
					val = arrTmp.join(' ');
					val = this.str_replace(';', '', val);
					val = this.str_replace(this.curObj['before'], '', val);
					val = this.str_replace(this.curObj['after'], '', val);
					result = val;
					break;
				}
			}
		}
	
		// wenn wert nicht existiert oder Wert invalide mit regExpr ist, dann nehme defaultwert	
		if (result == '' || this.isValid(this.curObj['regExpr'], result) == false) {
			result = init;
		}

		return result;
	}
	
	
	/**
	* Fuegt die neue CSS-Definition der Sammelbox hinzu
	* @return void
	*/
	this.addContent = function (obj, data, action)
	{
		if (data != null) {
			if (data.indexOf('exception') == -1) {
				switch (action) {
					case 'add':
						$(obj).html(data);
						$('#cssTestTag').html('.cssTest{'+data+'}');
						this.closeAll();
						return;
					case 'insert':
						return;
					case 'rollback':
						$(obj).html($(obj).html()+"\n");
						$('#cssTestTag').html('.cssTest{'+$(obj).html()+'}');
						return;
					default:
						// ...
						return;
				}
			}
			else {
				alert("Ungültiges Format\n\n"+data);
				return;
			}
		}
		
		alert("Unbekannter Fehler");
	}

	
	this.strpos = function (haystack, needle, offset)
	{
	    var i = (haystack+'').indexOf(needle, (offset || 0));
	    return i === -1 ? false : i;
	}

	this.str_replace = function (search, replace, subject)
	{
		return subject.split(search).join(replace);
	}
	
	
	
	
	
	
	
	
	this.serialize = function(_obj)
	{
	   // Let Gecko browsers do this the easy way
	   if (typeof _obj.toSource !== 'undefined' && typeof _obj.callee === 'undefined')
	   {
	      return _obj.toSource();
	   }

	   // Other browsers must do it the hard way
	   switch (typeof _obj)
	   {
	      // numbers, booleans, and functions are trivial:
	      // just return the object itself since its default .toString()
	      // gives us exactly what we want
	      case 'number':
	      case 'boolean':
	      case 'function':
	         return _obj;
	         break;

	      // for JSON format, strings need to be wrapped in quotes
	      case 'string':
	         return '\'' + _obj + '\'';
	         break;

	      case 'object':
	         var str;
	         if (_obj.constructor === Array || typeof _obj.callee !== 'undefined')
	         {
	            str = '[';
	            var i, len = _obj.length;
	            for (i = 0; i < len-1; i++) { str += serialize(_obj[i]) + ','; }
	            str += serialize(_obj[i]) + ']';
	         }
	         else
	         {
	            str = '{';
	            var key;
	            for (key in _obj) { str += key + ':' + serialize(_obj[key]) + ','; }
	            str = str.replace(/\,$/, '') + '}';
	         }
	         return str;
	         break;

	      default:
	         return 'UNKNOWN';
	         break;
	   }
	}


}
