/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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.
 */


function ClassCastException(message){
	this.message = message;
	this.isClassCastException = true;
}

function checkNumber(value){
	if(typeof(value) != "number"){
		throw new ClassCastException("not a number");
	}
}

function checkString(value){
	if(typeof(value) != "string"){
		throw new ClassCastException("not a string");
	}
}

function convertFormToListMap(form){
	var map = {};
	for(var i=0; i<form.elements.length; i++){
		var e = form.elements[i];
		if((e.type == "checkbox" || e.type == "radio") && !e.checked){
			continue;
		}
		var name = e.name;
		if((name == null) || (name.length == 0)){
			continue;
		}
		var value = e.value;
		var list = map[name];
		if(list == null){
			list = [];
			map[name] = list;
		}
		list.push(value);
	}
	return map;
}

function equals(o0, o1){
	if(o0 == null){
		return o1 == null;
	}
	if(o1 == null){
		return false;
	}
	{
		var o0type = typeof(o0);
		var o1type = typeof(o1);
		if(o0type == "number" || o0type == "string" || o0type == "boolean" ||
			o1type == "number" || o1type == "string" || o1type == "boolean"){
			return o0 == o1;
		}
	}
	if(o1.length && o1.length){
		// evaluate as list.
		if(o0.length != o1.length){
			return false;
		}
		for(var i=0; i<o0.length; i++){
			if(!equals(o0[i],o1[i])){
				return false;
			}
		}
		return true;
	}else{
		// evaluate as map.
		return containsMap(o0, o1) && containsMap(o1, o0);
	}
}

function containsMap(map0, map1){
	for(key in map0){
		var o0 = map0[key];
		var o1 = map1[key];
		if(!equals(o0, o1)){
			return false;
		}
	}
	return true;
}

function ValidatorContext(){
	this.id = null;
	this.inputMap = null;
	this.tmpKey = null;
	this.collectable = null;
	this.tmpIndex = 0;
	this.getTmpValue = function(){
		var list = this.inputMap[this.tmpKey];
		if(list == null || list.length <= this.tmpIndex){
			return null;
		}
		return list[this.tmpIndex];
	};
	this.setTmpValue = function(value){
		var list = this.inputMap[this.tmpKey];
		if(list == null){
			list = [];
			this.inputMap[this.tmpKey] = list;
		}
		if(list.length == 0 && this.tmpIndex == 0){
			list.push(value);
		}else{
			list[this.tmpIndex] = value;
		}
	};
	this.copy = function(){
		var v = new ValidatorContext();
		v.id = this.id;
		v.inputMap = this.inputMap;
		v.collectable = this.collectable;
		v.tmpKey = this.tmpKey;
		v.tmpIndex = this.tmpIndex;
		return v;
	}
	this.add = function(message){
		this.collectable.add(message, this);
	};
}

function SimpleMessageCollector(){
	this.messageList = [];
	this.add = function(message, context){
		this.messageList.push(message);
	};
}

function AddressedMessageCollector(){
	this.messageList = [];
	this.add = function(message, context){
		this.messageList.push(new AddressedMessage(context.tmpKey, context.tmpIndex, message, context.getTmpValue()));
	};
}

function AddressedMessage(key, index, message, value){
	this.key = key;
	this.index = index;
	this.message = message;
	this.value = value;
}

function DecimalFormat(){
	this.format = function(value){
		checkNumber(value);
		var v = null;
		v = "" + value;
		
		v = v.split('');
		v = v.reverse();
		v = v.join('');
		
		v = v.replace(new RegExp("(\\d\\d\\d)", "g"), "$1,");
		
		v = v.split('');
		v = v.reverse();
		v = v.join('');
		
		v = v.replace(new RegExp("^,"), "");
		
		return v;
	};
	this.parseObject = function(value){
		checkString(value);
		var v = null;
		v = value;
		v = v.replace(new RegExp("[^0-9]", "g"), "");
		v = v.replace(new RegExp("^0+([^0]+)$"), "$1");
		if(v.length == 0){
			return null;
		}
		v = parseInt(v);
		return v;
	};
}

function LimitedValueFormat(parseMap, defaultParsedValue, defaultFormattedValue){
	this.format = function(value){
		for(var key in parseMap){
			if(parseMap[key] == value){
				return key;
			}
		}
		if(defaultFormattedValue != null){
			return defaultFormattedValue;
		}
		return value;
	};
	this.parseObject = function(value){
		var o = parseMap[value];
		if(o != null){
			return o;
		}
		if(defaultParsedValue != null){
			return defaultParsedValue;
		}
		throw "ParseException";
	};
}

function BooleanFormat(trueString, falseString){
	this.format = function(value){
		return (value == true)?trueString:falseString;
	};
	this.parseObject = function(value){
		return (value == trueString);
	};
}

function SimpleDateFormat(pattern){
	this.format = function(value){
		var v = pattern;
		v = v.replace(new RegExp("yyyy"), this.unshiftZero(4, value.getFullYear()));
		v = v.replace(new RegExp("yy"), this.unshiftZero(4, value.getFullYear()).substring(2));
		v = v.replace(new RegExp("MM"), this.unshiftZero(2, value.getMonth() + 1));
		v = v.replace(new RegExp("dd"), this.unshiftZero(2, value.getDate()));
		v = v.replace(new RegExp("HH"), this.unshiftZero(2, value.getHours()));
		v = v.replace(new RegExp("mm"), this.unshiftZero(2, value.getMinutes()));
		v = v.replace(new RegExp("ss"), this.unshiftZero(2, value.getSeconds()));
		v = v.replace(new RegExp("SSS"), this.unshiftZero(3, value.getMilliseconds()));
		return v;
	};
	this.parseObject = function(value){
		var d = new Date();
		if(!this.setIf("yyyy", value, function(v){d.setFullYear(v);})){
			this.setIf("yy", value, function(v){d.setFullYear(((v < 2000)?1900:2000) + v);});
		}
		this.setIf("MM", value, function(v){d.setMonth(v-1);});
		this.setIf("dd", value, function(v){d.setDate(v);});
		this.setIf("HH", value, function(v){d.setHours(v);});
		this.setIf("mm", value, function(v){d.setMinutes(v);});
		this.setIf("ss", value, function(v){d.setSeconds(v);});
		this.setIf("SSS", value, function(v){d.setMilliseconds(v);});
		return d;
	};
	this.unshiftZero = function(digit, value){
		var v = "" + value;
		while(v.length < digit){
			v = "0" + v;
		}
		return v;
	}
	this.setIf = function(part, value, toFunction){
		var v = pattern.indexOf(part);
		if(v < 0){
			return false;
		}
		v = value.substr(v, part.length);
		v = v.replace(new RegExp("^0+([^0]+)$"), "$1");
		v = parseInt(v);
		toFunction(v);
		return true;
	};
}

function FixFormat(fixValue){
	this.format = function(value){
		return fixValue;
	};
	this.parseObject = function(value){
		return fixValue;
	};
}

function RegExpFormat(pattern, replacement){
	this.format = function(value){
		return this.convert(value);
	};
	this.parseObject = function(value){
		return this.convert(value);
	};
	this.convert = function(value){
		return value.replace(new RegExp(pattern, "g"), replacement);
	};
}

function ToCharacterFormat(){
	this.format = function(value){
		return value;
	};
	this.parseObject = function(value){
		return value.charAt(0);
	};
}

function BranchByIdValidator(switchable){
	this.interpret = function(context){
		var e = switchable[context.id];
		if(e == null){
			e = new TrueValidator();
		}
		return e.interpret(context);
	};
}

function BranchByNameIfExistsValidator(validatorIterable, switchable){
	this.interpret = function(context){
		var list = [];
		for(var key in switchable){
			if(context.inputMap[key] == null){
				continue;
			}
			var o = new Object();
			o.key = key;
			o.e = switchable[key];
			o.interpret = function(context2){
				var newContext = context2.copy();
				newContext.tmpKey = this.key;
				return this.e.interpret(newContext);
			};
			list.push(o);
		}
		return validatorIterable.interpret(context, list);
	};
}

function BranchByNameValidator(validatorIterable, switchable){
	this.interpret = function(context){
		var list = [];
		for(var key in switchable){
			var o = new Object();
			o.key = key;
			o.e = switchable[key];
			o.interpret = function(context2){
				var newContext = context2.copy();
				newContext.tmpKey = this.key;
				return this.e.interpret(newContext);
			};
			list.push(o);
		}
		return validatorIterable.interpret(context, list);
	};
}

function CountValidator(expression){
	this.count = 0;
	this.contextList = [];
	this.interpret = function(context){
		this.count++;
		this.contextList.unshift(context);
		return expression.interpret(context);
	};
}

function DefaultValueValidator(value){
	this.interpret = function(context){
		context.setTmpValue(value);
		return true;
	};
}

function EqualsValidator(value){
	this.interpret = function(context){
		return (value == context.getTmpValue());
	};
}

function FormatValidator(format){
	this.interpret = function(context){
		var v = context.getTmpValue();
		v = format.format(v);
		context.setTmpValue(v);
		return true;
	};
}

function IfNotValidator(ifExpression, falseExpression){
	this.interpret = function(context){
		return new IfValidator(new NotValidator(ifExpression), falseExpression, new TrueValidator()).interpret(context);
	};
}

function IfValidator(ifExpression, trueExpression, falseExpression){
	this.interpret = function(context){
		var flag = ifExpression.interpret(context);
		if(flag){
			return trueExpression.interpret(context);
		}else{
			return falseExpression.interpret(context);
		}
	};
}

function MaxLengthValidator(value){
	this.interpret = function(context){
		checkString(context.getTmpValue());
		return context.getTmpValue().length <= value;
	};
}

function MaxValidator(value){
	this.interpret = function(context){
		checkNumber(context.getTmpValue());
		return context.getTmpValue() <= value;
	};
}

function MessageValidator(value){
	this.interpret = function(context){
		context.collectable.add(value);
		return false;
	};
}

function MinLengthValidator(value){
	this.interpret = function(context){
		checkString(context.getTmpValue());
		return context.getTmpValue().length >= value;
	};
}

function MinValidator(value){
	this.interpret = function(context){
		checkNumber(context.getTmpValue());
		return context.getTmpValue() >= value;
	};
}

function NotValidator(expression){
	this.interpret = function(context){
		return !expression.interpret(context);
	};
}

function ParseValidator(format){
	this.interpret = function(context){
		var v = context.getTmpValue();
		v = format.parseObject(v);
		if(v == null){
			return false;
		}
		context.setTmpValue(v);
		return true;
	};
}

function RegExpIdValidator(value){
	this.pattern = new RegExp(value);
	this.interpret = function(context){
		return (context.id.match(this.pattern) != null);
	};
}

function RegExpValidator(value){
	this.pattern = new RegExp(value);
	this.interpret = function(context){
		return (context.getTmpValue().match(this.pattern) != null);
	};
}

function SerialValidator(validatorIterable, iterable){
	this.interpret = function(context){
		return validatorIterable.interpret(context, iterable);
	};
}

function TmpIndexValidator(tmpIndex, expression){
	this.interpret = function(context){
		var newContext = context.copy();
		newContext.tmpIndex = tmpIndex;
		return expression.interpret(newContext);
	};
}

function TmpKeyValidator(tmpKey, expression){
	this.interpret = function(context){
		var newContext = context.copy();
		newContext.tmpKey = tmpKey;
		return expression.interpret(newContext);
	};
}

function ValidNamesValidator(collection){
	this.interpret = function(context){
		loop:for(var key in context.inputMap){
			for(var i in collection){
				if(collection[i] == key){
					continue loop;
				}
			}
			return false;
		}
		return true;
	};
}

function ValidValuesValidator(collection){
	this.interpret = function(context){
		var value = context.getTmpValue();
		for(var i in collection){
			if(collection[i] == value){
				return true;
			}
		}
		return false;
	};
}

function ValueLoopValidator(validatorIterable, expression){
	this.interpret = function(context){
		var length = 0;
		{
			var list = context.inputMap[context.tmpKey];
			if(list == null){
				length = 1;
			}else{
				length = list.length;
			}
		}
		
		var list = [];
		for(var i=0; i<length; i++){
			var o = new Object();
			o.index = i;
			o.interpret = function(context2){
				var newContext = context2.copy();
				newContext.tmpIndex = this.index;
				return expression.interpret(newContext);
			};
			list.push(o);
		}
		return validatorIterable.interpret(context, list);
	};
}

function TrueValidator(){
	this.interpret = function(context){
		return true;
	};
}

function FalseValidator(){
	this.interpret = function(context){
		return false;
	};
}

function RequiredValidator(){
	this.interpret = function(context){
		var value = context.getTmpValue();
		return ((value != null && (!(typeof(value) == "string") || value.length > 0)) == true);
	};
}

function OrValidatorIterable(){
	this.interpret = function(context, iterable){
		var result = false;
		var empty = true;
		for(var i in iterable){
			var e = iterable[i];
			empty = false;
			result |= e.interpret(context);
		}
		return ((result || empty) == true);
	};
}

function OrOrValidatorIterable(){
	this.interpret = function(context, iterable){
		var empty = true;
		for(var i in iterable){
			var e = iterable[i];
			empty = false;
			if(e.interpret(context)){
				return true;
			}
		}
		return empty;
	};
}

function AndValidatorIterable(){
	this.interpret = function(context, iterable){
		var flag = true;
		for(var i in iterable){
			var e = iterable[i];
			flag &= e.interpret(context);
		}
		return (flag == true);
	};
}

function AndAndValidatorIterable(){
	this.interpret = function(context, iterable){
		for(var i in iterable){
			var e = iterable[i];
			var flag = e.interpret(context);
			if(!flag){
				return false;
			}
		}
		return true;
	};
}

function EqValidatorIterable(){
	this.interpret = function(context, iterable){
		var trueFlag = true;
		var falseFlag = true;
		for(var i in iterable){
			var e = iterable[i];
			var flag = e.interpret(context);
			trueFlag &= flag;
			falseFlag &= !flag;
		}
		return ((trueFlag || falseFlag) == true);
	};
}

function EqEqValidatorIterable(){
	this.interpret = function(context, iterable){
		var trueFlag = true;
		var falseFlag = true;
		for(var i in iterable){
			var e = iterable[i];
			var flag = e.interpret(context);
			trueFlag &= flag;
			falseFlag &= !flag;
			if(!(trueFlag || falseFlag)){
				return false;
			}
		}
		return true;
	};
}

function RetainNamesValidator(collection){
	this.interpret = function(context){
		loop:for(var key in context.inputMap){
			for(var i in collection){
				if(collection[i] == key){
					continue loop;
				}
			}
			context.inputMap[key] = undefined;
		}
		return true;
	};
}
