/*
 * Paraselene
 * Copyright (c) 2009  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.tag.form;



import java.text.*;
import java.util.*;
import java.util.regex.*;
import java.math.*;
import paraselene.*;
import paraselene.tag.*;

/**
 * 入力部品基底クラス。
 */
public abstract class Control extends Tag {
	enum Status {
		SELECTED("selected"),
		DISABLED("disabled"),
		READONLY("readonly"),
		CHECKED("checked");

		private static final long serialVersionUID = 2L;
		private String str;
		private Attribute attr;
		private Status( String s ) {
			str = s;
			attr = new Attribute( s );
		}

		boolean isSet( Tag t ) {
			return t.getAttribute( str ) != null;
		}

		void set( Tag t, boolean flag ) {
			try {
				if ( flag ) {
					t.setAttribute( attr );
				}
				else {
					t.removeAttribute( str );
				}
			}
			catch( Exception e ) {}
		}
	}

	private String title;
	private Object	obj = null;

	/**
	 * 見出しの設定。
	 * @param t 見出し。
	 */
	public void setTitle( String t ) {
		title = t;
	}

	/**
	 * 見出しの取得。
	 * @return 見出し。
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * ユーザーデータ設定。<br>
	 * このオブジェクトは{@link paraselene.tag.Tag#getReplica}で引き継ぎません。注意して下さい。
	 * @param user_date 紐づけるデータ。
	 */
	public void setUserData( Object user_date ) {
		obj = user_date;
	}

	/**
	 * ユーザーデータ取得。
	 * @return 紐づけたデータ。
	 */
	public Object getUserData() {
		return obj;
	}

	public Control( String s, boolean f ) {
		super( s, f );
	}

	/**
	 * 無効であるか？
	 * @return true:無効、false:有効。
	 */
	public boolean isDisabled() {
		return Status.DISABLED.isSet( this );
	}

	/**
	 * 無効設定。
	 * @param flag true:無効、false:有効。
	 */
	public void setDisabled( boolean flag ) {
		Status.DISABLED.set( this, flag );
	}

	/**
	 * 前後の空白の除去。
	 * @param cont トリム対象コントロール。
	 */
	public static void trim( Control ... cont ) {
		for ( int i = 0; i < cont.length; i++ ) {
			if ( cont[i].isEmptyValue() )	continue;
			cont[i].setValueString( cont[i].getValueString().trim() );
		}
	}

	/**
	 * 文字種。
	 */
	public enum CharGroup {
		/**
		 * 半角数字。マイナス記号や小数点を含みません。
		 */
		NUMBER( "\\p{Digit}" ),
		/**
		 * 半角アルファベット。
		 */
		ALPHA( "\\p{Alpha}" ),
		/**
		 * 半角英数字。
		 */
		ALNUM( "\\p{Alnum}" ),
		/**
		 * 7bitアスキーコード全て。半角カナは含みません。
		 * スペース等半角記号を含みます。
		 */
		ASCII( "\\p{ASCII}" );

		private static final long serialVersionUID = 1L;
		Pattern	pattern;
		private CharGroup( String s ) {
			pattern = Pattern.compile( ".*[^" + s +"]+.*" );
		}
	}

	/**
	 * 入力文字種の検証。CharGroup.ASCII以外は半角スペースを違反とみなします。
	 * 必要であれば、事前にtrim()して下さい。
	 * 値がnullまたは空文字列の場合は検証をスキップします。
	 * @param mes ControlException用メッセージ。
	 * @param cg 許可する文字種。
	 * @param cont 検証コントロール。
	 * @exception ControlException cg以外の文字種が使われている。
	 */
	public static void checkPattern( String mes, CharGroup cg, Control ... cont )
	throws ControlException {
		checkPattern( mes, cg, null, null, cont );
	}

	/**
	 * 入力文字種の検証。CharGroup.ASCII以外は半角スペースを違反とみなします。
	 * 必要であれば、事前にtrim()して下さい。
	 * 値がnullまたは空文字列の場合は検証をスキップします。<br>
	 * includeやexcludeに、cgの制約を変更するための文字を指定する事が可能です。
	 * この２つのパラメータがどちらもnullであると、もう１つのcheckPatternと
	 * 同じ検査になります。
	 * @param mes ControlException用メッセージ。
	 * @param cg 許可する文字種。
	 * @param include cgに違反するが許可したい文字。指定しない場合はnull。
	 * @param exclude cgに合致するが違反にしたい文字。指定しない場合はnull。
	 * @param cont 検証コントロール。
	 * @exception ControlException 条件に違反している。
	 */
	public static void checkPattern( String mes, CharGroup cg, char[] include, char[] exclude, Control ... cont )
	throws ControlException {
		ArrayList<Control>	list = new ArrayList<Control>();
		for ( int i = 0; i < cont.length; i++ ) {
			if ( cont[i].isEmptyValue() )	continue;
			String	str = cont[i].getValueString();
			if ( include != null ) {
				StringBuilder	buf = new StringBuilder();
				char[]	ch = str.toCharArray();
				for ( int k = 0; k < ch.length; k++ ) {
					boolean	flag = true;
					for ( int j = 0; j < include.length; j++ ) {
						if ( ch[k] == include[j] ) {
							flag = false;
							break;
						}
					}
					if ( flag )	buf = buf.append( ch[k] );
				}
				str = buf.toString();
			}
			Matcher	m = cg.pattern.matcher( str );
			if ( m.matches() ) {
				list.add( cont[i] );
				continue;
			}
			if ( exclude != null ) {
				char[]	ch = str.toCharArray();
				boolean	flag = true;
				for ( int k = 0; k < ch.length; k++ ) {
					for ( int j = 0; j < exclude.length; j++ ) {
						if ( ch[k] == exclude[j] ) {
							flag = false;
							break;
						}
					}
					if ( !flag )	break;
				}
				if ( !flag ) {
					list.add( cont[i] );
				}
			}
		}
		if ( list.size() > 0 ) {
			throw new ControlException( mes, list.toArray( new Control[0] ) );
		}
	}

	/**
	 * 未入力の検証。空白は入力ありと判定されます。
	 * 必要であれば、事前にtrim()して下さい。
	 * @param mes ControlException用メッセージ。
	 * @param cont 検証コントロール。
	 * @exception ControlException 未入力項目が存在する。
	 */
	public static void checkNull( String mes, Control ... cont ) throws ControlException {
		ArrayList<Control>	list = new ArrayList<Control>();
		for ( int i = 0; i < cont.length; i++ ) {
			if ( cont[i].isEmptyValue() ) {
				list.add( cont[i] );
			}
		}
		if ( list.size() > 0 ) {
			throw new ControlException( mes, list.toArray( new Control[0] ) );
		}
	}

	/**
	 * 日付の取得。
	 * @param mes ControlException用メッセージ。
	 * @param year 西暦。
	 * @param month 月。1～12。
	 * @param day 日。1～31。
	 * @return 日付。
	 * @exception ControlException 不正な日付が指定された。どれかがnullであった。
	 */
	public static java.sql.Date toDate( String mes, Control year, Control month, Control day ) throws ControlException {
		int	y = 0, m = 0, d = 0;
		try {
			y = Integer.parseInt( year.getValueString() );
		}
		catch( Exception e ) {
			throw new ControlException( mes, year );
		}
		try {
			m = Integer.parseInt( month.getValueString() ) - 1;
		}
		catch( Exception e ) {
			throw new ControlException( mes, month );
		}
		try {
			d = Integer.parseInt( day.getValueString() );
		}
		catch( Exception e ) {
			throw new ControlException( mes, day );
		}
		GregorianCalendar	c = new GregorianCalendar( y, m, d );
		int	cy = c.get( Calendar.YEAR );
		int	cm = c.get( Calendar.MONTH );
		int	cd = c.get( Calendar.DAY_OF_MONTH );
		if ( y != cy ) {
			throw new ControlException( mes, year );
		}
		if ( m != cm ) {
			throw new ControlException( mes, month );
		}
		if ( d != cd ) {
			throw new ControlException( mes, day );
		}
		return new java.sql.Date( c.getTime().getTime() );
	}

	/**
	 * 日付の取得。
	 * @param mes ControlException用メッセージ。
	 * @param format 日付文字列フォーマット。
	 * ex) new SimpleDateFormat( "yyyy/MM/dd" )
	 * @return 日付。
	 * @exception ControlException 日付フォーマットが不正な場合。
	 * 値がnullの場合は、例外ではなくnullリターンします。
	 */
	public java.sql.Date toDate( String mes, SimpleDateFormat format )
	throws ControlException {
		String	val = getValueString();
		if ( val == null )	return null;
		try {
			return new java.sql.Date( format.parse( val ).getTime() );
		}
		catch( ParseException e ) {
			throw new ControlException( mes, this );
		}
	}

	/**
	 * 整数値の取得。
	 * @param mes ControlException用メッセージ。
	 * @param radix 基数。
	 * @param min 最小値。nullを指定すると、最小値チェックを行いません。
	 * @param max 最大値。nullを指定すると、最大値チェックを行いません。
	 * @return 整数値。値が無い場合はnull。
	 * @exception ControlException 次の何れかで発生します。<br>
	 * 設定文字列が基数表現に違反する文字がある場合。<br>
	 * 数値が最小値を下回る、または最大値を上回る場合。一致は正常となります。<br>
	 * nullは例外ではなく、nullをリターンします。
	 */
	public BigInteger toInteger( String mes, int radix, BigInteger min, BigInteger max ) throws ControlException {
		String	src = getValueString();
		if ( src == null )	return null;
		src = src.trim();
		BigInteger	ret = null;
		try {
			ret = new BigInteger( src, radix );
		}
		catch( Exception e ) {
			throw new ControlException( mes, this );
		}
		if ( min != null ) {
			if ( ret.compareTo( min ) == -1 ) {
				throw new ControlException( mes, this );
			}
		}
		if ( max != null ) {
			if ( ret.compareTo( max ) == 1 ) {
				throw new ControlException( mes, this );
			}
		}
		return ret;
	}

	/**
	 * 整数値の取得。toInteger(mes,10,min,max)を返します。
	 * @param mes ControlException用メッセージ。
	 * @param min 最小値。nullを指定すると、最小値チェックを行いません。
	 * @param max 最大値。nullを指定すると、最大値チェックを行いません。
	 * @return 整数値。値が無い場合はnull。
	 * @exception ControlException 次の何れかで発生します。<br>
	 * 設定文字列が基数表現に違反する文字がある場合。<br>
	 * 数値が最小値を下回る、または最大値を上回る場合。一致は正常となります。<br>
	 * nullは例外ではなく、nullをリターンします。
	 */
	public BigInteger toInteger( String mes, BigInteger min, BigInteger max ) throws ControlException {
		return toInteger( mes, 10, min, max );
	}

	/**
	 * 小数値の取得。
	 * @param mes ControlException用メッセージ。
	 * @param place 小数点以下の桁数の最大。
	 * @param min 最小値。nullを指定すると、最小値チェックを行いません。
	 * @param max 最大値。nullを指定すると、最大値チェックを行いません。
	 * @return 小数値。値が無い場合はnull。
	 * @exception ControlException 次の何れかで発生します。<br>
	 * 設定文字列が小数表現に違反する文字がある場合。<br>
	 * 小数点以下の桁数がplaceを上回っている場合。<br>
	 * 数値が最小値を下回る、または最大値を上回る場合。一致は正常となります。<br>
	 * nullは例外ではなく、nullをリターンします。
	 */
	public BigDecimal toDecimal( String mes, int place, BigDecimal min, BigDecimal max ) throws ControlException {
		String	src = getValueString();
		if ( src == null )	return null;
		src = src.trim();
		BigDecimal	ret = null;
		try {
			ret = new BigDecimal( src );
		}
		catch( Exception e ) {
			throw new ControlException( mes, this );
		}
		String[]	div = src.split( "\\." );
		if ( div.length > 1 ) {
			if ( div[1].length() > place ) {
				throw new ControlException( mes, this );
			}
		}
		if ( min != null ) {
			if ( ret.compareTo( min ) == -1 ) {
				throw new ControlException( mes, this );
			}
		}
		if ( max != null ) {
			if ( ret.compareTo( max ) == 1 ) {
				throw new ControlException( mes, this );
			}
		}
		return ret;
	}

	/**
	 * 入力文字数チェック。
	 * null値は文字列長0として検査します。
	 * 文字数がmin_lenより下回ったり、max_lenを上回ったりすると例外が発生します。
	 * @param mes ControlException用メッセージ。
	 * @param min_len 長さ。バイト数ではありません。文字数です。
	 * この長さに等しい場合は正常判定になります。
	 * @param max_len 長さ。バイト数ではありません。文字数です。
	 * この長さに等しい場合は正常判定になります。
	 * @exception ControlException 文字数がlenを上回った場合。
	 */
	public void checkLength( String mes, int min_len, int max_len ) throws ControlException {
		checkMaxLength( mes, max_len );
		checkMinLength( mes, min_len );
	}

	/**
	 * 入力文字数チェック。指定サイズを上回ると例外が発生します。
	 * null値は文字列長0として検査します。
	 * @param mes ControlException用メッセージ。
	 * @param max_len 長さ。バイト数ではありません。文字数です。
	 * この長さに等しい場合は正常判定になります。
	 * @exception ControlException 文字数がmax_lenを上回った場合。
	 */
	public void checkMaxLength( String mes, int max_len ) throws ControlException {
		String	str = getValueString();
		if ( str == null )	str = "";
		if ( str.length() > max_len ) {
			throw new ControlException( mes, this );
		}
	}

	/**
	 * 入力文字数チェック。指定サイズを下回ると例外が発生します。
	 * null値は文字列長0として検査します。
	 * @param mes ControlException用メッセージ。
	 * @param min_len 長さ。バイト数ではありません。文字数です。
	 * この長さに等しい場合は正常判定になります。
	 * @exception ControlException 文字数がmin_lenを下回った場合。
	 */
	public void checkMinLength( String mes, int min_len ) throws ControlException {
		String	str = getValueString();
		if ( str == null )	str = "";
		if ( str.length() < min_len ) {
			throw new ControlException( mes, this );
		}
	}
}

