/*
 * Copyright (c)  2006-2009 Maskat Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/**
 * LiveValidation プラグイン用のカスタムLiveValidationクラスです。
 *
 * 独自拡張を行う場合は本クラスを継承してください。 
 */
maskat.lang.Class.declare(
	"maskat.validator.livevalidation.custom.CustomLiveValidation")
	.extend("LiveValidation", {
	
	_static: {
	
		/**
		 * 複数項目の入力値の一括検証を行います。
		 *
		 * @param {Array} validations
		 *         検証対象となるLiveValidationオブジェクトの配列
		 * @return 検証結果
		 * @type boolean
		 */
		massValidate: function (validations) {
			// massValidate は各クラスで実装すること。
			return LiveValidation.massValidate(validations);
		}
	},
	
	/**
	 * このクラスのコンストラクタです。
	 * 
	 * @param {HTMLElement} element 検証対象のコントロール要素
	 * @param {Object} optionsObj コンストラクタ引数
	 * @see LiveValidation#initialize(element, optionsObj)
	 */
	initialize: function(element, optionsObj){
		
		this.element = element;
		
		// コンストラクタ引数にマスカット部品の情報があればセットする。
		if (optionsObj.layout) {
			this.layout = optionsObj.layout;
			this.component = optionsObj.component;
		}

		// 適用するスタイルシートのクラス名を設定
		this.setStyleClass();

		// 検証ルール
		this.validations = [];
		
		// HTML要素の種別
		this.elementType = this.getElementType();
		
		// LiveValidationの初期化オプションの設定
		var options = optionsObj || {};
		this.setLvOptions(options);
		
		// 検証成功時/エラー時の後処理の設定
		this.setOnValidFunction(options);
		this.setOnInvalidFunction(options);
		
		// 検証実行のトリガーとなるイベントの設定
		this.setValidationEvents();
	},
	
	/**
	 * 適用するスタイルシートのクラス名を設定します。
	 */
	setStyleClass: function() {
		this.validClass = 'LV_valid';
	    this.invalidClass = 'LV_invalid';
	    this.messageClass = 'LV_validation_message';
	    this.validFieldClass = 'LV_valid_field';
	    this.invalidFieldClass = 'LV_invalid_field';
	},

	/**
	 * LiveValidation の動作オプションを設定します。
	 *
	 * @param {Object} optionObj LiveValidationの動作オプション
	 *                             (省略可能　default:{})
	 */
	setLvOptions: function(optionsObj) {
		var options = optionsObj || {};
		this.validMessage = options.validMessage || 'Thankyou!';
		this.insertAfterWhatNode = options.insertAfterWhatNode || this.element;
		this.onlyOnBlur =  options.onlyOnBlur || false;
		this.wait = options.wait || 500;
		this.onlyOnSubmit = options.onlyOnSubmit || false;
	},

	
	/**
	 * メッセージ領域を生成します。
	 *
	 * @return メッセージ領域
	 * @type HTMLElement
	 */
	createMessageArea: function() {
		var span = document.createElement('span');
		span.innerHTML = this.getMessage();
		return span;
	},
	
	/**
	 * メッセージ文字列を取得します。
	 *
	 * 改行文字(\n)が設定されている場合はbrタグに変換します。
	 *
	 * @return 改行コードの処理を行ったメッセージ文字列
	 * @type string
	 */
	getMessage: function() {
		return this.message.replace(/\\n/g, "<br />");
	},
	
	/**
	 * 入力値検証対象のHTMLElementにイベントハンドラを追加します。
	 *
	 * HTMLElementに紐づいている既存のハンドラがあれば、LiveValidation用のハンドラと
	 * 共存させます。
	 *
	 * @param eventType {string} - イベント種別(DOMレベル0)
	 * @param handler {function} - 設定したいイベントハンドラ 
	 */
	addEventHandler: function (eventType, handler) {
		var self = this;
		var originalHandler = this.element[eventType];
		this.element[eventType] = function(event) {
			try {
				handler(event);
			} catch (e) {
				// バリデーション実行時にエラーが発生した場合は
				var message = e.getMessages ? e.getMessages().join('\n') :
					e.message;
				var logger = maskat.log.LogFactory.getLog(
					"maskat.validator.livevalidation"); 
				logger.error(message);
			}
			
			if (typeof(originalHandler) == 'function') {
				originalHandler(event);
			}
			// Rialto側でCLASSを上書きしてしまう不具合に対応
			self.addFieldClass();
		}
	},
	
	/**
	 * 検証成功時の後処理を設定します。
	 *
	 * @param {Object} options オプションオブジェクト
	 */
	setOnValidFunction : function(options) {
		this.onValid = options.onValid || function () {
			this.removeMessage();
			this.addFieldClass();
		};
	},
	
	/**
	 * 検証エラー時の後処理を設定します。
	 *
	 * @param {Object} options オプションオブジェクト
	 */
	setOnInvalidFunction: function(options) {
		this.onInvalid = options.onInvalid || function () {
			this.insertMessage ( this.createMessageArea() );
			this.addFieldClass();
		};
	},
		
	/**
	 *　入力値検証を実行するイベントの設定をします。
	 */
	setValidationEvents: function() {
		var self = this;
		var KEY_CODE_TAB = 9;
		var KEY_CODE_SHIFT = 16;
		
		if(!this.onlyOnSubmit){
			this.addEventHandler("onfocus", function(e) {
				self.doOnFocus();
			});
		
			switch(this.elementType){
			case LiveValidation.CHECKBOX:
				this.addEventHandler("onclick", function(e) {
					self.validate();
				});
			case LiveValidation.SELECT:
				this.addEventHandler("onchange", function(e) {
					self.validate();
				});
				break;
			default:
				if(!this.onlyOnBlur) {
					this.addEventHandler("onkeyup", function(e) {
						var theEvent = e ? e : window.event;
						switch (theEvent.keyCode) {
						case KEY_CODE_TAB:
						case KEY_CODE_SHIFT:
							// Tabキーの場合は入力値検証を行わない。
							return true;
						default:
							break;
						}
						self.deferValidation(); 
					});
				}
				this.addEventHandler("onblur", function(e) {
					self.doOnBlur();
				});
				break;
			}
		}
	},
	
	/**
	 * 入力値検証を実行します。
	 * 
	 * @return 検証結果
	 * @see LiveValidation#validate
	 */
	validate : function() {
		try {
			var isValid = this.doValidations();
			if(isValid){
				this.onValid();
				return true;
			}else{
	  			this.onInvalid(); 
	  			return false;
			}
		} catch (e) {
			if (this.layout) {
				throw new maskat.lang.Error(
					"LIVEVALIDATION_COMPONENT_VALIDATION_ERROR",
					{ layout: this.layout, widget: this.component }, e);
			} else {
				throw new maskat.lang.Error(
					"LIVEVALIDATION_COMPONENT_VALIDATION_ERROR_HTML",
					{ id: this.element.id }, e);
			}
		}
	}
});
