/*
 * @(#)XElementNS.java
 *
 * Copyright 2001 by Intelligent Technology Inc. All rights reserved.
 */
package jp.co.iti.fagot.fob.xml;

import java.awt.Color;

import java.util.Enumeration;
import java.util.Vector;
import java.util.Hashtable;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.dom.ElementNSImpl;

import jp.co.iti.fagot.fob.FobManager;
import jp.co.iti.fagot.fob.IFUndoElement;

import jp.co.iti.fagot.util.ZGraphicUtil;
import jp.co.iti.fagot.util.ZString;
import jp.co.iti.fagot.util.ZDebug;
/**
 * undoableエレメント
 *
 * @author  福島康治
 * @version 1.01 2001/03/22
 */
public class XElementNS extends ElementNSImpl {

	/**
	 * コンストラクタ
	 * @param doc   オーナドキュメント
	 * @param ns    ネームスペース
	 * @param name  タグ名
	 */
	public XElementNS(DocumentImpl doc, String ns, String name) throws DOMException {
	super(doc,ns,name);
	}

	/**
	 * デフォルトコンストラクタ
	 */
	public XElementNS() throws DOMException {
	super(null,null);
	}

	/**
	 * デフォルトコンストラクタとペアで利用する
	 * @param doc   オーナドキュメント
	 * @param ns    ネームスペース
	 * @param name  タグ名
	 */
	public void constructElement( DocumentImpl doc, String ns, String name) {
//	setOwnerDocument(doc) ;
	setDocument(doc);

	this.namespaceURI = ns;			// ElementNSImpl
	this.name = name;				// ElementImpl

//	needsSyncData(true);
	this.flags = (short) ( this.flags | SYNCDATA );	// NodeImpl.needsSyncData
	}

	/**
	 * ドキュメントの設定
	 * ElementImpl.setOwnerDocumentはプロテクトされているのでオーバライド
	 * @param doc   オーナドキュメント
	 */
	public void setDocument( DocumentImpl doc ) {
	this.ownerNode = doc;	// NodeImpl
	this.ownerDocument = doc;		// ParentNode

//	if (attributes != null) {
//		attributes.setOwnerDocument(doc);
//	}
	}

	//## オーバライド：ドキュメント操作 ###############################
	/**
	 * 挿入
	 */
    public Node insertBefore(Node newChild, Node refChild) throws DOMException {
	FobManager mgr = ((XDocument)getOwnerDocument()).getFobManager();
	mgr.push( new XUndoInsertElement(this,newChild,refChild) );
	return insertBeforeNoUndo(newChild,refChild);
	}

	/**
	 * 挿入(undoなし)
	 */
    public Node insertBeforeNoUndo(Node newChild, Node refChild) throws DOMException {
	return super.insertBefore(newChild,refChild);
	}

	/**
	 * 削除
	 */
    public Node removeChild(Node oldChild) throws DOMException {
	FobManager mgr = ((XDocument)getOwnerDocument()).getFobManager();
	Node refChild = oldChild.getNextSibling();
	mgr.push( new XUndoRemoveElement(this,oldChild,refChild) );
	return removeChildNoUndo(oldChild);
	}

	/**
	 * 削除(undoなし)
	 */
    public Node removeChildNoUndo(Node oldChild) throws DOMException {
	Node nd = super.removeChild(oldChild);
	if ( oldChild instanceof XElementNS ) {
		((XElementNS)oldChild).firePropertyChanged( "removed", null, null ) ;
	}
	return nd;
	}

	/**
	 * 置換
	 */
    public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
	ZDebug.trace("replace:"+oldChild.toString());
	return super.removeChild(oldChild);
	}

	//## 基本操作 ######################################################
	/**
	 * アトリビュートの設定
	 * @param szName   アトリビュート名
	 * @param newValue 値
	 */
	public void setAttribute(String szName,String newValue) {
	FobManager mgr = ((XDocument)getOwnerDocument()).getFobManager();
	String oldValue = getAttribute(szName);
//	ZDebug.trace("name;"+szName+" set:"+newValue+" old:"+oldValue+":");
	if ( !oldValue.equals(newValue) ) {
		mgr.push( new XUndoSetAttribute(this,szName,newValue,oldValue) );
		setAttributeNoUndo( szName, newValue );
	}
	}

	/**
	 * アトリビュートの設定(undoなし)
	 * @param szName  アトリビュート名
	 * @param szValue 値
	 */
	public void setAttributeNoUndo(String szName,String szValue) {
	String oldValue = getAttribute(szName);
//	if ( szName.equals("font-size") )ZDebug.trace("font-size:"+oldValue+":"+szValue);
	super.setAttribute( szName, szValue );
	firePropertyChanged( szName, oldValue, szValue ) ;
	}

	/**
	 * アトリビュートの削除
	 * @param szName  アトリビュート名
	 */
	public void removeAttribute(String szName) {
	FobManager mgr = ((XDocument)getOwnerDocument()).getFobManager();
	String oldValue = getAttribute(szName);
	if ( oldValue.length() != 0 ) {
		mgr.push( new XUndoSetAttribute(this,szName,"",oldValue) );
		removeAttributeNoUndo( szName );
	}
	}

	/**
	 * アトリビュートの削除(undoなし)
	 * @param szName  アトリビュート名
	 */
	public void removeAttributeNoUndo(String szName) {
	String oldValue = getAttribute(szName);
	super.removeAttribute( szName );
	firePropertyChanged( szName, oldValue, "" ) ;
	}

	//## 型変換 ########################################################
	/**
	 * アトリビュートの設定
	 * @param szName アトリビュート名
	 * @param value  値
	 */
	public void setBooleanAttr(String szName,boolean value) {
	setAttribute( szName, ((value)?"true":"false"));
	}

	/**
	 * アトリビュートの取得
	 * @param szName アトリビュート名
	 * @param defaultValue 値が設定されていない場合の戻り値
	 */
	public boolean getBooleanAttr(String szName,boolean defaultValue) {
	String szValue = getAttribute(szName);
	if ( szValue.equals("true") ) {
		return true;
	} else if ( szValue.equals("false") ) {
		return false;
	}
	return defaultValue;
	}

	/**
	 * アトリビュートの設定
	 * @param szName アトリビュート名
	 * @param value  値
	 */
	public void setByteAttr(String szName,byte value) {
	setAttribute( szName, Byte.toString(value));
	}

	/**
	 * アトリビュートの取得
	 * @param szName アトリビュート名
	 * @param defaultValue 値が設定されていない場合の戻り値
	 */
	public byte getByteAttr(String szName,byte defaultValue) {
	return ZString.parseByte(getAttribute(szName),defaultValue);
	}

	/**
	 * アトリビュートの設定
	 * @param szName アトリビュート名
	 * @param value  値
	 */
	public void setIntAttr(String szName,int value) {
	setAttribute( szName, Integer.toString(value));
	}

	/**
	 * アトリビュートの取得
	 * @param szName アトリビュート名
	 * @param defaultValue 値が設定されていない場合の戻り値
	 */
	public int getIntAttr(String szName,int defaultValue) {
	return ZString.parseInt(getAttribute(szName),defaultValue);
	}

	/**
	 * アトリビュートの設定
	 * @param szName アトリビュート名
	 * @param value  値
	 */
	public void setDoubleAttr(String szName,double value) {
	setAttribute( szName, Double.toString(value));
	}

	/**
	 * アトリビュートの取得
	 * @param szName アトリビュート名
	 * @param defaultValue 値が設定されていない場合の戻り値
	 */
	public double getDoubleAttr(String szName,double defaultValue) {
	return ZString.parseDouble(getAttribute(szName),defaultValue);
	}

	/**
	 * アトリビュートの取得
	 * @param szName アトリビュート名
	 * @param value  値
	 */
	public Color getColorAttr(String szName) {
	return ZGraphicUtil.stringToColor(getAttribute(szName));
	}

	//## エレメント検索系 #############################################
	/**
	 * エレメントの検索して、アトリビュートを取得
	 *
	 * @param tag   タグ名情報
	 * @param where 検索条件
	 * @param attr  必要なアトリビュート
	 */
	public String getChildElementAttribute(String tag,String where,String attr) {
	XElementNS elm = findChildElement(tag,where);
	if ( elm != null ) {
		return elm.getAttribute(attr);
	}
	return "";
	}

	/**
	 * エレメントの検索
	 *
	 * 【tagの指定】
	 * 検索条件をいろいろな形式で設定できるようにする
	 * タグの階層をたどる場合は、親タグ名.子タグ名の形式で階層を記述する
	 * タグ階層はスペースで区切って複数記述可能
	 * ルート階層からたどる場合は、先頭に.を記述する
	 *
	 * @param tag   タグ名情報
	 * @param where 検索条件
	 */
	protected XElementNS findElement(String tag,String where) {
	String[] tagInfo = ZString.tokenize(tag);

//	ZDebug.trace(getTagName()+".find:"+tag+":where="+where);

	// タグ名条件について
	for ( int nIndex=0 ; nIndex<tagInfo.length ; nIndex++ ) {
		boolean isAbsolute = false;
		if ( tagInfo[nIndex].startsWith(".") ) {
			// . から始まっていれば絶対パス
			tagInfo[nIndex] = tagInfo[nIndex].substring(1);
			isAbsolute=true;
		}

		if ( tagInfo[nIndex].equals(getTagName()) && isMatch( where ) ) {
			// もし、自分のタグ名と同じで、条件が合えば自分
			return this;
		} else if ( tagInfo[nIndex].startsWith(getTagName()) ) {
			// 自分のタグ名の子孫であれば子ども検索
			tagInfo[nIndex] = tagInfo[nIndex].substring( getTagName().length() );
		} else if ( isAbsolute ) {
			// 絶対パス指定で、自分のタグの子孫でなければ検索終了
			tagInfo[nIndex] = null;
		}
	}

	// 子供検索条件の作成
	String szTag = null;
	for ( int nIndex=0 ; nIndex<tagInfo.length ; nIndex++ ) {
		if ( tagInfo[nIndex] != null ) {
			if ( szTag == null ) {
				szTag = tagInfo[nIndex];
			} else {
				szTag += " "+tagInfo[nIndex];
			}
		}
	}
	if ( szTag != null ) {
		return findChildElement( szTag,where );
	}
	return null;
	}

	/**
	 * 子エレメントの検索
	 *
	 * @param tag   タグ名情報(子エレメントの情報)
	 * @param where 検索条件
	 */
	public XElementNS findChildElement(String tag,String where) {
	if ( where == null ) {
		// ハッシュテーブルから検索
		// 性能向上のため、変更されない情報(=子タグのノード)をハッシュテーブルに登録
		// where 条件がある場合は、対象ノードが変わる可能性があるので、登録しない
		XElementNS node = (XElementNS)mHash.get( tag );
		if ( node != null ) {
			return node;
		}
	}
	NodeList list = getChildNodes();
	for ( int nAt=0 ; nAt<list.getLength() ; nAt++ ) {
		Node child = list.item(nAt);
		if ( child instanceof XElementNS ) {
			XElementNS node = ((XElementNS)child).findElement(tag,where);
			if ( node != null ) {
				if ( where == null ) {
					// 性能向上のため、変更されない情報(=子タグのノード)をハッシュテーブルに登録
					mHash.put( tag, node );
				}
				return node;
			}
		}
	}
	return null;
	}

	/**
	 * エレメントの検索
	 *
	 * @param tag   タグ名情報(スペースで区切って複数記述可能)
	 */
	protected Vector findElements(String tag) {
	Vector children = new Vector();
	String[] tagInfo = ZString.tokenize(tag);

	// タグ名条件について
	for ( int nIndex=0 ; nIndex<tagInfo.length ; nIndex++ ) {
		boolean isAbsolute = false;
		if ( tagInfo[nIndex].startsWith(".") ) {
			// . から始まっていれば絶対パス
			tagInfo[nIndex] = tagInfo[nIndex].substring(1);
			isAbsolute=true;
		}

		if ( tagInfo[nIndex].equals(getTagName())) {
			// もし、自分のタグ名と同じで、条件が合えば自分
			children.add( this ) ;
		} else if ( tagInfo[nIndex].startsWith(getTagName()) ) {
			// 自分のタグ名の子孫であれば子ども検索
			tagInfo[nIndex] = tagInfo[nIndex].substring( getTagName().length() );
		} else if ( isAbsolute ) {
			// 絶対パス指定で、自分のタグの子孫でなければ検索終了
			tagInfo[nIndex] = null;
		}
	}

/*
	// 自分自身の判定
	for ( int nIndex=0; nIndex<tagInfo.length ; nIndex++ ) {
		if ( tagInfo[nIndex].equals(getTagName()) ) {
			children.add( this ) ;
		}
	}
*/
	// 子供検索条件の作成
	String szTag = null;
	for ( int nIndex=0 ; nIndex<tagInfo.length ; nIndex++ ) {
		if ( tagInfo[nIndex] != null ) {
			if ( szTag == null ) {
				szTag = tagInfo[nIndex];
			} else {
				szTag += " "+tagInfo[nIndex];
			}
		}
	}
	if ( szTag != null ) {
		children.addAll( findChildElements(szTag) );
	}
	return children;
	}

	/**
	 * 子エレメントの検索
	 *
	 * @param tag   タグ名情報(スペースで区切って複数記述可能)
	 */
	public Vector findChildElements(String tag) {
	Vector children = new Vector();
	NodeList list = getChildNodes();
	for ( int nAt=0 ; nAt<list.getLength() ; nAt++ ) {
		Node child = list.item(nAt);
		if ( child instanceof XElementNS ) {
			children.addAll(((XElementNS)child).findElements(tag));
		}
	}
	return children;
	}

	/**
	 * 検索条件にヒットするか
	 *
	 * 基本的にはSQLと同じ形式にする
	 * タグの階層をたどる場合は、親タグ名.子タグ名の形式で階層を記述する
	 *
	 * @param tag タグ名情報
	 * @param where   検索条件
	 */
	protected boolean isMatch(String where) {
	if ( where == null ) {
		return true;
	}
	// 条件検索
	boolean bMatch = false;
	String[] szMatch = ZString.tokenfirst(where,"=");
	if ( szMatch.length==2 ) {
		String szValue = getAttribute(szMatch[0]);
		if ( szValue != null && szValue.equalsIgnoreCase(szMatch[1]) ) {
//			ZDebug.trace("where:::"+where+":::    value:"+szValue+":::==:::"+szMatch[1]);
			bMatch = true;
		}
	}
	return bMatch;
	}

	/**
	 * 子エレメントの取得
	 * 存在しない場合は作成する
	 *
	 * @param tag   タグ名情報(子エレメントの情報)
	 */
	public XElementNS getChildElement(String tag,String where) {
	XElementNS node = findChildElement( tag, where );
	if ( node == null ) {
//		ZDebug.trace("createChildElement");
		node = createChildElement( tag );
		if ( where == null ) {
			// 性能向上のため、変更されない情報(=子タグのノード)をハッシュテーブルに登録
			mHash.put( tag, node );
		} else {
			// 初期アトリビュート設定
			String[] szMatch = ZString.tokenfirst(where,"=");
			node.setAttribute(szMatch[0],szMatch[1]);
		}
	}
	return node;
	}

	/**
	 * 子エレメントの作成
	 *
	 * @param tag   タグ名情報(子エレメントの情報)
	 */
	public XElementNS createChildElement(String tag) {
//	ZDebug.trace(tag);
	if ( tag.startsWith(".") ) {
		tag = tag.substring(1);
	}
	String[] childTag = ZString.tokenfirst(tag,".");

	XElementNS child = null;

	if ( childTag[1] != null ) {
		// タグ名が階層構造になっている場合
		child = findChildElement("."+childTag[0],null);
		if ( child == null ) {
			child = (XElementNS)getOwnerDocument().createElementNS(null,childTag[0]);
//			ZDebug.trace("CreateElement:"+getTagName()+"→"+childTag[0]);
			appendChild(child);
		}
		return child.createChildElement(childTag[1]);
	}

	child = (XElementNS)getOwnerDocument().createElementNS(null,childTag[0]);
	appendChild(child);
	return child;
	}

	//## プロパティチェンジリスナー ###################################
	/**
     * 状態変更リスナーの登録
     * @param lis リスナー
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener) {
	mListener.add(listener);
	}

	/**
     * 状態変更リスナーの削除
     * @param lis リスナー
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener) {
	mListener.remove(listener);
	}

	/**
     * 状態変更の通知
	 * @param evt インプットボックスイベント
	 */
	public void firePropertyChanged( String event, Object oldValue, Object newValue ) {
	// イベントの通知
	PropertyChangeEvent evt = new PropertyChangeEvent(this,event,oldValue,newValue);
	firePropertyChanged(evt);
	}

	/**
     * 状態変更の通知
	 * @param evt インプットボックスイベント
	 */
	public void firePropertyChanged( PropertyChangeEvent evt ) {
	if ( mLock ) {
		return;
	}
	mLock = true;
	Enumeration elm = mListener.elements();
	while ( elm.hasMoreElements() ) {
		PropertyChangeListener listener = (PropertyChangeListener)elm.nextElement();
		listener.propertyChange( evt );
	}
	mLock = false;
	}

	//## サブクラス：アンドゥオブジェクト（insert,remove共通）#########
	/**
	 * XElement用undoオブジェクト
	 */
	public class XUndoElement implements IFUndoElement {
	/**
	 * コンストラクタ
	 * @param org オリジナルオブジェクト
	 * @param obj 更新オブジェクト
	 * @param ref 参照オブジェクト
	 */
	public XUndoElement(XElementNS org, Object obj, Object ref) {
	mOrg = org ;
	mObj = obj ;
	mRef = ref ;
	}

	/**
	 * アンドゥ
	 */
	public void undo() {
	}

	/**
	 * リドゥ
	 */
	public void redo() {
	}

	/**
	 * オリジナルオブジェクトの取得
	 */
	public void getFobs( Vector items ) {
	items.add( mOrg );
	items.add( mObj );
	}

	//## アトリビュート################################################
	/**
	 * オリジナルオブジェクト
	 */
	protected XElementNS  mOrg = null ;
	
	/**
	 * 更新オブジェクト
	 */
	protected Object      mObj = null ;
	
	/**
	 * 参照オブジェクト
	 */
	protected Object      mRef = null ;
	}

	//## サブクラス：アンドゥオブジェクト（insert用）##################
	/**
	 * XElement用undoオブジェクト(insert用)
	 */
	public class XUndoInsertElement extends XUndoElement {
	/**
	 * コンストラクタ
	 * @param org オリジナルオブジェクト
	 * @param obj 更新オブジェクト
	 * @param ref 参照オブジェクト
	 */
	public XUndoInsertElement(XElementNS org, Node obj, Node ref) {
	super( org, obj, ref );
	}

	/**
	 * アンドゥ
	 */
	public void undo() {
	mOrg.removeChildNoUndo((Node)mObj);
	}

	/**
	 * リドゥ
	 */
	public void redo() {
	mOrg.insertBeforeNoUndo((Node)mObj,(Node)mRef);
	}
	}

	//## サブクラス：アンドゥオブジェクト（remove用）##################
	/**
	 * XElement用undoオブジェクト(remove用)
	 */
	public class XUndoRemoveElement extends XUndoElement {
	/**
	 * コンストラクタ
	 * @param org オリジナルオブジェクト
	 * @param obj 更新オブジェクト
	 * @param ref 参照オブジェクト
	 */
	public XUndoRemoveElement(XElementNS org, Node obj, Node ref) {
	super( org, obj, ref );
	}

	/**
	 * アンドゥ
	 */
	public void undo() {
	mOrg.insertBeforeNoUndo((Node)mObj,(Node)mRef);
	}

	/**
	 * リドゥ
	 */
	public void redo() {
	mOrg.removeChildNoUndo((Node)mObj);
	}
	}

	//## サブクラス：アンドゥオブジェクト（attribute用）###############
	/**
	 * XElement用undoオブジェクト(attribute用)
	 */
	public class XUndoSetAttribute extends XUndoElement {
	/**
	 * コンストラクタ
	 * @param org オリジナルオブジェクト
	 * @param name アトリビュート名
	 * @param obj 設定値
	 * @param ref 設定前の値
	 */
	public XUndoSetAttribute(XElementNS org, String name, String obj, String ref) {
	super( org, obj, ref );
	mName = name;
	}

	/**
	 * アンドゥ
	 */
	public void undo() {
	String str = (String)mRef;
	if ( str.length()==0 ) {
		mOrg.removeAttributeNoUndo( mName );
	} else {
		mOrg.setAttributeNoUndo( mName, str );
	}
	}

	/**
	 * リドゥ
	 */
	public void redo() {
	String str = (String)mObj;
	if ( str.length()==0 ) {
		mOrg.removeAttributeNoUndo( mName );
	} else {
		mOrg.setAttributeNoUndo( mName, str );
	}
	}
	
	//## アトリビュート################################################
	/**
	 * アトリビュート名
	 */
	protected String mName = null ;

	}

	//## アトリビュート################################################
    /**
     * プロパティファイヤーロック
     * イベントが再発生する可能性があるので、その場合はイベントを通知しないよう
     * 制限するためのフラグ
     */
	protected boolean 	mLock		= false;

    /**
     * プロパティチェンジリスナー
     */
	protected Vector	mListener	= new Vector();

    /**
     * ハッシュテーブル
     */
	protected Hashtable mHash	= new Hashtable();
}
