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


import java.util.*;
import java.io.*;
import javax.servlet.http.*;

import paraselene.*;
import paraselene.supervisor.*;
import paraselene.tag.*;

class FormSeq {
	private volatile int seq = 0;
	String getSeq() {
		synchronized( this ) {
			seq++;
			if ( seq < 0 )	seq = 0;
			return Integer.toString( seq, Character.MAX_RADIX );
		}
	}
}

/**
 * FORMタグ。
 */
public class Form extends Tag {
	private static final long serialVersionUID = 2L;
	/**
	 * 予約済みHIDDEN項目のname。paraselene$form
	 */
	public static final String FORM_ID = "paraselene$form";
	/**
	 * マルチパート。
	 */
	public static final String MULTI_PART = "multipart/form-data";

	private static FormSeq	form_seq = new FormSeq();
	private	String	seq;
	private HashMap<String, HashMap<String,Control>>	control = new HashMap<String, HashMap<String,Control>>();

	/**
	 * コントロールインスタンスが存在するか？
	 * @param cont 検証するインスタンス。
	 * @return true:持っている、false:持っていない。
	 * 但し、name属性を持たないコントロールでは常にfalseを返す。
	 */
	public boolean isContained( Control cont ) {
		String	name = cont.getNameAttribute();
		if ( name == null )	return false;
		HashMap<String,Control>	map = control.get( Page.getIndexName( name ) );
		if ( map == null )	return false;
		return map.get( name ) == cont;
	}

	/**
	 * コンストラクタ。
	 */
	public Form() {
		super( "form", false );
		seq = form_seq.getSeq();
		Input	id = Input.createHidden( FORM_ID, seq );
		addHTMLPart( id );
	}

	protected Tag newReplica() {
		return new Form();
	}

	/**
	 * 削除要素の検証。
	 * @param tag 削除要素。
	 */
	public synchronized void checkTag4rm( Tag tag ) {
		Tag[]	in_tag = tag.getTagArray();
		for ( int i = 0; i < in_tag.length; i++ ) {
			checkTag4rm( in_tag[i] );
		}

		if ( !(tag instanceof Control) )	return;
		Control	ctl = (Control)tag;
		String	name = ctl.getNameAttribute();
		if ( name == null )	return;
		String	idx = Page.getIndexName( name );
		HashMap<String,Control>	map = control.get( idx );
		if ( map == null ) return;
		map.remove( name );
		if ( map.size() == 0 ) {
			control.remove( idx );
		}
	}

	/**
	 * フォームIDの取得。ライブラリ内部でフォームの検出に使用します。<br>
	 * プログラマは使用しません。
	 * @return ID。
	 */
	public String getID() {
		return seq;
	}

	/**
	 * 全てのコントロールの取得。
	 * @return コントロール配列。存在しない場合、0個の配列を返します。
	 */
	public synchronized Control[] getAllControl() {
		ArrayList<Control>	list = new ArrayList<Control>();
		for ( String	all_k: control.keySet() ) {
			HashMap<String,Control>	map = control.get( all_k );
			for ( String map_k:	map.keySet() ) {
				list.add( map.get( map_k ) );
			}
		}
		return list.toArray( new Control[0] );
	}

	/**
	 * 全てのコントロールの取得。
	 * @param name コントロールのname属性。
	 * @return コントロール配列。存在しない場合、0個の配列を返します。
	 */
	public synchronized Control[] getAllControl( String name ) {
		ArrayList<Control>  list = new ArrayList<Control>();
		HashMap<String,Control>	map = control.get( Page.getIndexName( name ) );
		if ( map != null ) {
			for ( String map_k: map.keySet() ) {
				list.add( map.get( map_k ) );
			}
		}
		return list.toArray( new Control[0] );
	}

	/**
	 * コントロールの取得。配列化されたコントロールは取得できません。
	 * @param name コントロールのname属性。
	 * @return コントロール。無ければnull。
	 */
	public Control getControl( String name ) {
		Control[]	ctl = getAllControl( name );
		if ( ctl.length != 1 )	return null;
		return ctl[0];
	}

	/**
	 * 追加要素の検証。
	 * @param tag 追加要素。
	 */
	public synchronized void checkTag( Tag tag ) {
		Tag[]	in_tag = tag.getTagArray();
		for ( int i = 0; i < in_tag.length; i++ ) {
			checkTag( in_tag[i] );
		}

		if ( !(tag instanceof Control) )	return;
		Control	ctl = (Control)tag;
		String	name = ctl.getNameAttribute();
		if ( name == null )	return;
		String	idx = Page.getIndexName( name );
		HashMap<String,Control>	map = control.get( idx );
		if ( map == null ) {
			map = new HashMap<String,Control>();
			control.put( idx, map );
		}
		map.put( name, ctl );

		if ( !(ctl instanceof Input) )	return;
		Input	inp = (Input)ctl;
		Attribute	attr = inp.getAttribute( Input.Type.TYPE );
		if ( attr == null )	return;
		String	val = attr.getString();
		if ( val == null )	return;
		if ( "file".equals( val ) ) {
			try {
				setAttribute( new Attribute( "enctype", MULTI_PART ) );
			}
			catch( Exception e ) {}
			return;
		}
	}

	/**
	 * リクエストの反映。
	 * @param req リクエスト。
	 * @return 変更のあったコントロール。
	 */
	public synchronized ArrayList<Control> reflect( RequestParameter req ) {
		Control[]	cont = getAllControl();
		ArrayList<Control>	set = new ArrayList<Control>();
		HashMap<String, CheckBox>	after = new HashMap<String, CheckBox>();
		for ( int i = 0; i < cont.length; i++ ) {
			String	name = cont[i].getNameAttribute();
			if ( name == null )	continue;
			CheckBox	cbox = null;
			if ( cont[i] instanceof CheckBox ) {
				cbox = (CheckBox)cont[i];
				if ( cbox.isRadio() ) {
					after.put( Page.getIndexName( name ), cbox );
					continue;
				}
			}
			Button	button = null;
			if ( cont[i] instanceof Button ) {
				button = (Button)cont[i];
				button.setPushed( false );
			}
			RequestItem	item = req.getItem( name );
			if ( item == null ) {
				if ( cbox != null ) {
					if ( !cbox.isDisabled() ) {
						if ( cbox.isChecked() ) {
							set.add( cbox );
							cbox.setChecked( false );
						}
					}
				}
				else if ( button == null ) {
					if ( !cont[i].isDisabled() ) {
						String	old = cont[i].getValueString();
						if ( old != null ) {
							if ( !old.isEmpty() )	set.add( cbox );
						}
						cont[i].setValueString( null );
					}
				}
				continue;
			}
			if ( button != null ) {
				button.setPushed( true );
				set.add( button );
			}
			else if ( cbox != null ) {
				if ( !cbox.isChecked() ) {
					set.add( cbox );
					cbox.setChecked( true );
				}
			}
			else if ( cont[i] instanceof UploadFile ) {
				UploadFile	uf = (UploadFile)cont[i];
				set.add( uf );
				uf.setRequest( item );
			}
			else if ( cont[i] instanceof Select ) {
				Select	sel = (Select)cont[i];
				if ( !sel.isSameSet( item.getAllValue() ) ) {
					set.add( sel );
					sel.setValueString( null );
					int	item_cnt = item.getCount();
					for ( int j = 0; j < item_cnt; j++ ) {
						cont[i].setValueString( item.getValue( j ) );
					}
				}
			}
			else {
				String	old = cont[i].getValueString();
				if ( old == null )	old = "";
				int	item_cnt = item.getCount();
				for ( int j = 0; j < item_cnt; j++ ) {
					String	val = item.getValue( j );
					if ( val == null )	val = "";
					if ( val.equals( old ) )	continue;
					set.add( cont[i] );
					cont[i].setValueString( val );
				}
			}
		}
		for ( String k:	after.keySet() ) {
			CheckBox	cbox = getChecked( k );
			RequestItem	item = req.getItem( k );
			if ( item == null ) {
				if ( cbox != null ) {
					set.add( cbox );
					CheckBox.setRadio( this, k, null );
				}
				continue;
			}
			int	item_cnt = item.getCount();
			String	old = CheckBox.getRadio( this, k );
			for ( int j = 0; j < item_cnt; j++ ) {
				String	val = item.getValue( j );
				if ( val.equals( old ) )	continue;
				CheckBox.setRadio( this, k, item.getValue( j ) );
			}
			CheckBox	now = getChecked( k );
			if ( now != cbox ) {
				set.add( now );
			}
		}
		return set;
	}

	private CheckBox getChecked( String name ) {
		Control[]	tag = getAllControl( name );
		for ( int i = 0; i < tag.length; i++ ) {
			if ( !(tag[i] instanceof CheckBox) )	continue;
			CheckBox	cbox = (CheckBox)tag[i];
			if ( !cbox.isRadio() )	continue;
			if ( cbox.isChecked() )	return cbox;
		}
		return null;
	}
}

