/*
 * 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.
 */

// package utils
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 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){
			throw "parse error";
		}
		var v = parseInt(v);
		if(v == null){
			throw "parse error";
		}
		return v;
	};
}
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 yyyy = -1;
		var MM = -1;
		var dd = -1;
		var HH = 0;
		var mm = 0;
		var ss = 0;
		var SSS = 0;
		if(!this.setIf("yyyy", value, function(v){yyyy = v;})){
			this.setIf("yy", value, function(v){yyyy = ((v < 2000)?1900:2000) + v;});
		}
		this.setIf("MM", value, function(v){MM = v-1;});
		this.setIf("dd", value, function(v){dd = v;});
		this.setIf("HH", value, function(v){HH = v;});
		this.setIf("mm", value, function(v){mm = v;});
		this.setIf("ss", value, function(v){ss = v;});
		this.setIf("SSS", value, function(v){SSS = v;});
		var d = new Date(yyyy, MM, dd, HH, mm, ss, SSS);
		if (isNaN(d)) {
			throw "parse error";
		}
		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);
		if(v == null){
			throw "parse error";
		}
		toFunction(v);
		return true;
	};
}

// package fn
function CastFn(dummy){this.exec = function(c){return c;};}
function Delegator(fn){this.exec = function(c){return fn.exec(c);};}
function EchoFn(){this.exec = function(c){return c;};}
function ExecFn(fn){this.exec = function(c){return fn.exec(c).exec(c);};}
function FixFn(v){this.exec = function(c){return v;};}
function IfFn(ifFn, trueFn, falseFn){
	this.exec = function(c){
		if (ifFn.exec(c)) {
			return trueFn.exec(c);
		} else {
			return falseFn.exec(c);
		}
	};
}
function JoinFn(fn0, fn1){this.exec = function(c){return fn1.exec(fn0.exec(c));};}
function NullFn(){this.exec = function(c){return null;};}
function SeqFn(iterable){
	this.exec = function(c){
		var r = null;
		for(var i in iterable){
			r = iterable[i].exec(c);
		}
		return r;
	};
}
function SwitchFn(map, defaultFn){
	this.exec = function(c){
		var r = map[c];
		if (r == null) {
			return defaultFn.exec(c);
		} else {
			return r;
		}
	};
}
function ThrowFn(e){this.exec = function(c){throw e;};}
function TryFn(tryFn, catchFn, finallyFn){
	this.exec = function(c){
		try {
			return tryFn.exec(c);
		} catch(e) {
			return catchFn.exec(c);
		} finally {
			finallyFn.exec(c);
		}
	};
}
function RecodeFn(fn, name, nameList, contextList, returnList){
	this.exec = function(c){
		if (nameList != null) {
			nameList.push(name);
		}
		if (contextList != null) {
			contextList.push(c);
		}
		var r = null;
		if (fn != null) {
			r = fn.exec(c);
		}
		if (returnList != null) {
			returnList.push(r);
		}
		return r;
	};
}

// package boolean
function BooleanStateAndFactory(){
	this.exec = function(c, iterable){
		var flag = true;
		for(var i in iterable){
			var fn = iterable[i];
			flag &= fn.exec(c);
		}
		return (flag == true);
	};
}
function BooleanStateAndAndFactory(){
	this.exec = function(c, iterable){
		for(var i in iterable){
			var fn = iterable[i];
			var flag = fn.exec(c);
			if(!flag){
				return false;
			}
		}
		return true;
	};
}
function BooleanStateEqFactory(){
	this.exec = function(c, iterable){
		var trueFlag = true;
		var falseFlag = true;
		for(var i in iterable){
			var fn = iterable[i];
			var flag = fn.exec(c);
			trueFlag &= flag;
			falseFlag &= !flag;
		}
		return ((trueFlag || falseFlag) == true);
	};
}
function BooleanStateEqEqFactory(){
	this.exec = function(c, iterable){
		var trueFlag = true;
		var falseFlag = true;
		for(var i in iterable){
			var fn = iterable[i];
			var flag = fn.exec(c);
			trueFlag &= flag;
			falseFlag &= !flag;
			if(!(trueFlag || falseFlag)){
				return false;
			}
		}
		return true;
	};
}
function BooleanStateOrFactory(){
	this.exec = function(c, iterable){
		var result = false;
		for(var i in iterable){
			var fn = iterable[i];
			result |= fn.exec(c);
		}
		return (result == true);
	};
}
function BooleanStateOrOrFactory(){
	this.exec = function(c, iterable){
		for(var i in iterable){
			var fn = iterable[i];
			if(fn.exec(c)){
				return true;
			}
		}
		return false;
	};
}
function BooleanConverter(booleanOperator, iterable){this.exec = function(c){return c != null && c != false;};}
function BoolSeq(booleanOperator, iterable){this.exec = function(c){return booleanOperator.exec(c, iterable);};}
// CompareFn support numeric compare
function CompareFn(v){this.exec = function(c){return v - c;};}
function ContainsChecker(iterable){
	this.exec = function(c){
		for(var i in iterable){
			var o = iterable[i];
			if (o == c) {
				return true;
			}
		}
		return false;
	};
}
function ContainsAllChecker(iterable){
	this.checker = new ContainsChecker(iterable);
	this.exec = function(c){
		for(var i in c){
			var o = c[i];
			if (this.checker.exec(o) == false) {
				return false;
			}
		}
		return true;
	};
}
function EqualsChecker(v){this.exec = function(c){return c == v;};}
function MaxChecker(v){this.exec = function(c){return c <= v;};}
function MaxLengthChecker(v){this.exec = function(c){checkString(c);return c.length <= v;};}
function MinChecker(v){this.exec = function(c){return v <= c;};}
function MinLengthChecker(v){this.exec = function(c){checkString(c);return v <= c.length;};}
function NotDelegator(fn){this.exec = function(c){return fn.exec(c) == false;};}
function NotEmptyChecker(){this.exec = function(c){return (c != null && (!(typeof(c) == "string") || c.length > 0)) == true;};}
function NotFn(){this.exec = function(c){return c == false;};}
// ObjectMatcher don't support Class checking
function ObjectMatcher(v){this.exec = function(c){return c == v;};}
function RegExpChecker(pattern){this.exec = function(c){return (c.match(pattern) != null);};}

// package convert
function CloneFormatFactory(format){this.exec = function(c){return format;};}
function FormatConverter(factory){this.exec = function(c){return factory.exec(null).format(c);};}
function KeySetGetter(){
	this.exec = function(c){
		var list = [];
		for(var key in c){
			list.push(key);
		}
		return list;
	};
}
// unsupport NetMaskConverter
function ParseConverter(factory){this.exec = function(c){return factory.exec(null).parseObject(c);};}
function RegExpConverter(pattern, replacement){this.exec = function(c){return c.replace(pattern, replacement);};}
function ToBigDecimalConverter(){this.exec = function(c){return c;};}
function ToBigIntegerConverter(){this.exec = function(c){return c;};}
function ToByteConverter(){this.exec = function(c){return c;};}
function ToDoubleConverter(){this.exec = function(c){return c;};}
function ToFloatConverter(){this.exec = function(c){return c;};}
function ToIntegerConverter(){this.exec = function(c){return c;};}
function ToLongConverter(){this.exec = function(c){return c;};}
function ToShortConverter(){this.exec = function(c){return c;};}
function ToSqlDateConverter(){this.exec = function(c){return c;};}
function ToStringConverter(){
	this.exec = function(c){
		return "" + c;
	};
}
function ToTimestampConverter(){this.exec = function(c){return c;};}
function TrysFn(iterable){
	this.exec = function(c){
		var t = null;
		for (var i in iterable) {
			var fn = iterable[i];
			try {
				return fn.exec(c);
			} catch (e) {
				t = e;
			}
		}
		throw t;
	};
}
function RetainKeys(collection){
	this.exec = function(c){
		loop:for(var key in c){
			for(var i in collection){
				if(collection[i] == key){
					continue loop;
				}
			}
			c[key] = undefined;
		}
		return true;
	};
}

// package id
function IdConverter(fn){this.exec = function(c){c.id = fn.exec(c.id);return null;};}
function IdGetter(fn){this.exec = function(c){return c.id;};}
function LocalId(fn){
	this.exec = function(c){
		var id = c.id;
		try {
			return fn.exec(c);
		} finally {
			c.id = id;
		}
	};
}

// package validator
function ValidatorContext(){
	this.id = null;
	this.inputMap = null;
	this.key = null;
	this.collectable = null;
	this.index = 0;
	this.getValue = function(){
		var list = this.inputMap[this.key];
		if(list == null || list.length <= this.index){
			return null;
		}
		return list[this.index];
	};
	this.setValue = function(value){
		var list = this.inputMap[this.key];
		if(list == null){
			list = [];
			this.inputMap[this.key] = list;
		}
		if(list.length == 0 && this.index == 0){
			list.push(value);
		}else{
			list[this.index] = value;
		}
	};
	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.key, context.index, message, context.getValue()));
	};
}
function AddressedMessage(key, index, message, value){
	this.key = key;
	this.index = index;
	this.message = message;
	this.value = value;
}
function ConvertValidator(fn){this.exec = function(c){c.setValue(fn.exec(c.getValue()));return true;};}
function LocalIndexValidator(index, fn){
	this.exec = function(c){
		var base = c.index;
		try {
			c.index = index;
			return fn.exec(c);
		} finally {
			c.index = base;
		}
	};
}
function LocalKeyValidator(key, fn){
	this.exec = function(c){
		var base = c.key;
		try {
			c.key = key;
			return fn.exec(c);
		} finally {
			c.key = base;
		}
	};
}
function MapGetter(){
	this.exec = function(c){
		return c.inputMap;
	};
}
function MessageValidator(value){
	this.exec = function(c){
		c.collectable.add(value);
		return false;
	};
}
function NameBranch(booleanOperator, map){
	this.exec = function(c){
		var iterable = [];
		for(var key in map){
			iterable.push(new LocalKeyValidator(key, map[key]));
		}
		return booleanOperator.exec(c, iterable);
	};
}
function NameBranchIfExists(booleanOperator, map){
	this.exec = function(c){
		var iterable = [];
		for(var key in map){
			if(c.inputMap[key] == null){
				continue;
			}
			iterable.push(new LocalKeyValidator(key, map[key]));
		}
		return booleanOperator.exec(c, iterable);
	};
}
function ValueGetter(){
	this.exec = function(c){
		return c.getValue();
	};
}
function ValueLoopValidator(booleanOperator, fn){
	this.exec = function(c){
		var length = 0;
		{
			var list = c.inputMap[c.key];
			if(list == null){
				length = 1;
			}else{
				length = list.length;
			}
		}
		var iterable = [];
		for(var i=0; i<length; i++){
			iterable.push(new LocalIndexValidator(i, fn));
		}
		return booleanOperator.exec(c, iterable);
	};
}
function ValuesGetter(){
	this.exec = function(c){
		return c.inputMap[c.key];
	};
}
