/**
 * Ten plik jest częścią pakietu SOWA-WWW. Ten plik jest częścią pakietu SOWA-WWW.
 *
 * Plik JavaScript definiujący metody obsługi kontrolki symulującej zachowanie znanego z Delphi pola z maską.
 *
 * @package SOWA-WWW 
 * @author Michał Fryska
 * @copyright SOKRATES-Software (http://www.sokrates.pl)
 * 
 * $Id: delphimask.js 130 2008-08-21 08:03:34Z szumek $
 */

/**
 * Główna funkcja obsługi kontrolki ala Delphi
 * @param String mask Maska w stylu Delphi.
 */
function DelphiMaskEdit(mask) {
	/** Oryginalny string maski */
	this.maskString = mask;
	/** String maski właściwej */
	this.mask = '';
	/** Informacja o tym, czy statyczne znaki maski wchodzą w skład wartości fielda, czy nie. */
	this.saveStaticValue = false;
	/** Znak wykorzystywany do zapełnienia wartości pustej [domyślnie spacji] */
	this.emptyCharacter = ' ';
	/** Czy ostatecznie wiodące znaki spacji mają być zachowane. */
	this.saveLeadingSpaces = true;
	/** Czy ostatecznie końcowe znaki spacji maja byc zachowane. */
	this.saveEndingSpaces = false;

	/** automatyczny funkszyn na znaku. U - upper case, L - lower case */
	this.autoFunctionTab = '';
	/** informacja o dopuszczalnym znaku. L - tylko litera, A - alfanumeryczny, C - dowolny znak, 0 - cyfra, # - cyfra lub + lub -, Z - znak statyczny */
	this.validCharacterTab = '';
	/** informacja o konieczności niespacyjnego znaku w danej pozycji 1 - obowiązkowy, 0 - nieobowiązkowy */
	this.obligatoryTab = '';
	/** informacja o występowaniu znaku statycznego w danej pozycji. */
	this.staticCharacterTab = '';

	/** edytowany tekst, zawartość wpisanych znaków w kontrolce. Uwaga! Nie jest to jednoznaczne z tym, że taka jest wartość 
	 * @see saveStaticValue */
	this.editableText = '';
	/** faktyczna zawartość w kontrolce [dlaczego? bo jest parametr emptyCharacter !!! rzecz w tym, że można go przecież WPISAĆ i wtedy nie jest oznaczeniem pustego, ale oznaczeniem znaku] */
	this.controlText = '';

	/**
	 * Parametry kontrolne, służące do tego, aby właściwie interpretować długość całej machy
	 * Pełna długość maski po przetworzeniu, z uwzględnieniem znaków statycznych
	 */
	this.fullMaskLength = 0;
	/** Ilość znaków statycznych */
	this.staticCount = 0;

	/** referencja do kontrolki w formularzu */
	this.control = null;
	/** referencja do pola w formularzu */
	this.field = null;

	this.init();
}

/**
 * Inicjacja maski, jej wstępny parsing.
 */
DelphiMaskEdit.prototype.init = function() {
	// Rozbijamy string maski: separatorem pól maski jest średnik.
	seppos2 = this.maskString.lastIndexOf(';');
	seppos1 = this.maskString.lastIndexOf(';',seppos2-1);

	this.mask = this.maskString.substring(0,seppos1);
	this.saveStaticValue = (this.maskString.substring(seppos1+1,seppos2) == '1');
	this.emptyCharacter = this.maskString.substring(seppos2+1);

	uppercaseFunction = ' ';
	for (i=0; i<this.mask.length; i++) {
		a = uppercaseFunction; v = ' '; o = '1'; s = ' '; e = ' '; c = this.emptyCharacter;
		doContinue = false;

		z = this.mask.charAt(i);
		switch (z) {
		case '!':	this.saveLeadingSpaces = false; this.saveEndingSpaces = true;
				doContinue = true;
				break;
		case '>':	if (uppercaseFunction=='L') uppercaseFunction = ' '; else if (uppercaseFunction==' ') uppercaseFunction = 'U';
				doContinue = true;
				break;
		case '<':	if (uppercaseFunction=='U') uppercaseFunction = ' '; else if (uppercaseFunction==' ') uppercaseFunction = 'L';
				doContinue = true;
				break;
		case '\\':	i++;
				v = 'Z'; o = '1'; s = this.mask.charAt(i); e = s; c = s;
				this.staticCount++;
				break;
		case 'L':	v = 'L'; o = '1';
				break;
		case 'I':	v = 'L'; o = '0';
				break;
		case 'A':	v = 'A'; o = '1';
				break;
		case 'a':	v = 'A'; o = '0';
				break;
		case 'C':	v = 'C'; o = '1';
				break;
		case 'c':	v = 'c'; o = '0';
				break;
		case '0':	v = '0'; o = '1';
				break;
		case '9':	v = '0'; o = '0';
				break;
		case '#':	v = '#'; o = '0';
				break;
		case ':':
		case '/':
		default:	v = 'Z'; o = '1'; s = z; e = z; c = z;
				this.staticCount++;
				break;
		}

		if ( !doContinue) {
			this.autoFunctionTab += a;
			this.validCharacterTab += v;
			this.obligatoryTab += o;
			this.staticCharacterTab += s;
			this.editableText += e;
			this.controlText += c;
			this.fullMaskLength++;
		}
	}
}

/**
 * Wstawia znak w podanym miejscu.
 * @param String character Znak, który ma być wstawiony
 * @param Int position Pozycja, na którą ma być wstawiony znak
 * @param Bool checkOnly Nakazuje tylko sprawdzić poprawność, ale nie modyfikuje tekstu! Domyslnie na false.
 * @return Bool TRUE jeśli wstawienie sie powiodło, FALSE gdy nie.
 */
DelphiMaskEdit.prototype.insertCharacter = function(character,position,checkOnly) {
	if ( position >= this.fullMaskLength ) return false;

	a = this.autoFunctionTab.charAt(position);
	v = this.validCharacterTab.charAt(position);
	s = this.staticCharacterTab.charAt(position);
	// 1. Jeśli to znak PUSTY, to sprawdzamy, czy to nie jest pozycja statyczna. Jeśli jest, to spadejszyn.
	if ( character == ' ' ) {
		if ( v == 'Z' )	return false;
		e = ' '; c = this.emptyCharacter;
	}
	// 2. Jeśli w danym miejscu ma być znak statyczny, a chcemy mu wstawić cosik innego, to spadejszyn. Robimy osobno, bo na staticach nie odpala się funkcyj!
	else if ( v == 'Z' ) {
		if ( character != s ) return false;
		e = s; c = s;
	}
	else {
		e = character; c = character;
		switch ( v ) {
		case 'L': // unicode hack - litery jeno
			if (!character.match(/[\u0041-\u005a\u0061-\u007a\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u01ba\u01bc-\u01bf\u01c4-\u02ad\u0386\u0388-\u0481\u048c-\u0556\u0561-\u0587\u10a0-\u10c5\u1e00-\u1fbc\u1fbe\u1fc2-\u1fcc\u1fd0-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ffc\u207f\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2131\u2133\u2134\u2139\ufb00-\ufb17\uff21-\uff3a\uff41-\uff5a]/)) return false;
			break;
		case 'A': // unicode hack - alfanumeryczne wszech języków
			if (!character.match(/[\u0041-\u005a\u0061-\u007a\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u01ba\u01bc-\u01bf\u01c4-\u02ad\u0386\u0388-\u0481\u048c-\u0556\u0561-\u0587\u10a0-\u10c5\u1e00-\u1fbc\u1fbe\u1fc2-\u1fcc\u1fd0-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ffc\u207f\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2131\u2133\u2134\u2139\ufb00-\ufb17\uff21-\uff3a\uff41-\uff5a\u0030-\u0039\u0660-\u0669\u06f0-\u06f9\u0966-\u096f\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be7-\u0bef\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29\u1040-\u1049\u1369-\u1371\u17e0-\u17e9\u1810-\u1819\uff10-\uff19]/)) return false;
			break;
		case 'C': // Dowolny znak drukowalny: będzie si. Zawsze.
			break;
		case '0': // unicode hack - tylko cyfry
			if (!character.match(/[\u0030-\u0039\u0660-\u0669\u06f0-\u06f9\u0966-\u096f\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be7-\u0bef\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29\u1040-\u1049\u1369-\u1371\u17e0-\u17e9\u1810-\u1819\uff10-\uff19]/)) return false;
			break;
		case '#':
			if (!character.match(/[\+\-\u0030-\u0039\u0660-\u0669\u06f0-\u06f9\u0966-\u096f\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be7-\u0bef\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29\u1040-\u1049\u1369-\u1371\u17e0-\u17e9\u1810-\u1819\uff10-\uff19]/)) return false;
			break;
		}
		// Mamy sprawdzone. Teraz funkcya.
		switch (a) {
		case 'U':	e = character.toUpperCase(); c = character.toUpperCase(); break;
		case 'L':	e = character.toLowerCase(); c = character.toLowerCase(); break;
		}
	}
	// A skoro tak: to robimy podstawkę.
	if ( checkOnly == undefined || !checkOnly ) {
		et = this.editableText; ct = this.controlText;
		this.editableText = et.substring(0,position)+e+et.substring(position+1);
		this.controlText = ct.substring(0,position)+c+ct.substring(position+1);
	}

	return true;
}

/**
 * Ustawia tekst kontrolki, kontrolując oczywiście poprawność z maską. Wprowadza znaki sekwencyjnie, aż sie uda.
 * @param String text Tekst do wstawienia
 * @param Bool checkOnly Okresla, czy ma być tylko sprawdzenie. Jeśli True, to nie modyfikuje treści kontrolki.
 * @return Int ilość błędnych wprowadzeń.
 */
DelphiMaskEdit.prototype.setText = function(text,checkOnly) {
	var maskLength = this.fullMaskLength;
	var position = 0, failedCount = 0;
	if ( !this.saveStaticValue ) maskLength -= this.staticCount;
	// 1. String może byc krótszy niż faktyczna długość maski. I w zależności od tego czy maska miała wykrzyknik czy nie, to spacje są z przodu, albo z tyłu.
	if ( this.saveLeadingSpaces ) insertedString = ''; else insertedString = text;
	for (i=0;i<maskLength-text.length;i++) insertedString += ' ';
	if ( this.saveEndingSpaces ) insertedString += ''; else insertedString += text;

	for ( i=0; i<insertedString.length; i++ ) {
		if ( !this.saveStaticValue && this.validCharacterTab.charAt(position) == 'Z' ) position++;
		if ( this.insertCharacter(insertedString.charAt(i),position,checkOnly) )
			position++;
		else
			failedCount++;
	}

	return failedCount;
}

/**
 * Zwraca faktyczny tekst, jaki jest zawarty w polu, z uwzględnieniem znaków stałych, które maska nakazywała ignorować, z lub bez spacji itp.
 * @return String Faktyczny tekst pola.
 */
DelphiMaskEdit.prototype.getText = function() {
	var text = '', b_position = 0, e_position = this.fullMaskLength-1;
	if (!this.saveLeadingSpaces) while( this.editableText.charAt(b_position) == ' ' ) b_position++;
	if (!this.saveEngingSpaces) while( this.editableText.charAt(e_position) == ' ' ) e_position--;
	for (var position=b_position; position<=e_position; position++) {
		if ( !this.saveStaticValue && this.validCharacterTab.charAt(position) == 'Z' ) continue;
		text += this.editableText.charAt(position);
	}
	return text;
}

/**
 * Zwraca informację, czy aktualna wartość jest zgodna z maską [poprawna]
 * @return Bool
 */
DelphiMaskEdit.prototype.isValidValue = function() {
	for (i=0;i<this.fullMaskLength;i++)
		if (this.obligatoryTab.charAt(i) == '1' && this.editableText.charAt(i) == ' ') return false;
	return true;
}

/**
 * Rejestruje kontrolki formularza w obiekcie. Metoda musi byc wywołana raz.
 * @param DOMObject control kontrolka widoczna - edycyjna.
 * @param DOMObject field kontrolka ukryta - zawierająca faktyczną wartość pola
 * @param Bool initialize Określa, czy kontrolka widoczna ma byc od razu inicjowana wartościami.
 */
DelphiMaskEdit.prototype.registerControl = function(control, field, initialize) {
	this.control = control;
	this.field = field;
	
	if (initialize) {
		this.setText(this.field.value);
		this.control.value = this.controlText;
		this.field.value = this.getText();
	}
}

// Kalka z modułu kalendarza - dodaje zdarzenie do obiekta.
DelphiMaskEdit.prototype.addEvent = function(el, evname, func) {
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, true);
	} else {
		el["on" + evname] = func;
	}
};

DelphiMaskEdit.prototype.stopEvent = function(ev) {
	ev || (ev = window.event);
	if (document.all) {
		ev.cancelBubble = true;
		ev.returnValue = false;
	} else {
		ev.preventDefault();
		ev.stopPropagation();
		ev.cancelBubble = true;
		ev.returnValue = false;
	}
	return false;
};

/**
 * Obsługa zdarzenia naciśnięcia przycisku klawiatury.
 * @param Object event Zdarzenie
 * @return Bool Zwraca false aby przerwać wywołanie dalszych zdarzeń, z wyjątkiem backspace,entera i klawiszy specjalnych, które obsłuiguje gdzie indziej.
 */
DelphiMaskEdit.prototype._keyPressEvent = function(event) {
	var ns = ((document.layers || document.getElementById) && (!document.all));
	var ie = document.all;
	event || (event = window.event);
	var keyCode = (ie) ? event.keyCode : event.which;
	var key = String.fromCharCode(keyCode), K = event.keyCode;

						// backspace !!! :( obsługa będzie w keypress.
	if (keyCode == 0 || keyCode == 8 || keyCode == 13) return true;

	if ( !this.control ) return false;	// z całego bubble chcemy tylko wywołanie, w którym pojawia się maskEdit jako this

	var position = doGetCaretPosition(this.control);
	if ( this.insertCharacter(key,position) ) {
		this.control.value = this.controlText;
		this.field.value = this.getText();
		// Teraz - jeśli pozycja +1 jest stałym znakiem, a wciąż jest miejsce, to przesuwamy o 2.
		if ( this.validCharacterTab.charAt(position+1) == 'Z' && position+1 < this.fullMaskLength ) position++;
		doSetCaretPosition(this.control, position+1);
	}
	return false;
}

/**
 * Obsługa zdarzenia "keydown" na kontrolce. z powodu różnic między IE i Fx musimy to obsłużyć naokoło w taki dziwaczny sposób.
 * Tu robimy porządek z backspace i enterem oraz klawiszami specjalnymi.
 * @param Object event Zdarzenie
 * @return Bool True jeśli jest ok, false jeśli należy przerwać stos zdarzeń.
 */
// 
DelphiMaskEdit.prototype._keyDownEvent = function(event) {
	event || (event = window.event);
	var keyCode = event.keyCode;
	if ( !this.control ) return false;	// z całego bubble chcemy tylko wywołanie, w którym pojawia się maskEdit jako this
	var position = doGetCaretPosition(this.control);

	switch (keyCode) {
	case 8: // backspace
		if ( position > 0 ) {
			if ( this.insertCharacter(' ',position-1) ) {
				this.control.value = this.controlText;
				this.field.value = this.getText();
			}
			// Ale zawsze cofamy!
			doSetCaretPosition(this.control, position-1);
		}
		break;
	case 46: // delete
		if ( this.insertCharacter(' ', position) ) {
			this.control.value = this.controlText;
			this.field.Value = this.getText();
			doSetCaretPosition(this.control, position);
		}
		break;
	default: return true;
	}
	
	return false;
}

/**
 * Funkcja tworząca i rejestrująca nowy obiekt maski dla podanego pola edycyjnego [inputa].
 * Uwaga! Wartość rzeczywista będzie umieszczana w fieldName, który winien być Hidden, co więcej,
 * to z jego wartości zainicjujemy też zawartość controlName!
 * @param String controlName Nazwa kontrolki edycyjnej
 * @param String fieldName Nazwa elementu Hidden, który zawierać ma rzeczywistą wartość pola.
 * @param String mask Maska, jaka zainicjuje nam obiekt.
 */
function RegisterDelphiMaskEdit(controlName, fieldName, mask) {
	controlObj = document.getElementById(controlName);
	fieldObj = document.getElementById(fieldName);
	
	controlObj.maskEdit = new DelphiMaskEdit(mask);
	controlObj.maskEdit.registerControl(controlObj, fieldObj, true);
	// controlObj.maskEdit.addEvent(controlObj, "keypress", controlObj.maskEdit._keyEvent);
	// Powyższe z niepojętych przyczyn nie działa tak jakem bym sobie tego życzył, dlatego obsługa onKeyPress przerzucona została
	// do kontrolki, w której należy podać argument onKeyPress="return this.maskEdit._keyEvent(event);"
}


