/*
 * 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 woolpack;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import woolpack.dom.DomConstants;
import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;
import woolpack.html.ToNode;
import woolpack.utils.OGE;
import woolpack.utils.StringReaderFactory;
import woolpack.utils.UtilsConstants;

/**
 * テスト用の静的メソッドの集まり。
 * @author nakamura
 *
 */
public class TestUtils {

	private static final String FORM_HEAD = "<HTML><BODY><FORM>";
	private static final String FORM_TAIL = "</FORM></BODY></HTML>";
	private static final String BODY_HEAD = "<HTML><BODY>";
	private static final String BODY_TAIL = "</FORM></BODY>";

	private TestUtils(){
	}
	
	/**
	 * {@link DomContext#getNode()}を標準出力に出力する。
	 * @param context 出力対象。
	 * @throws NullPointerException 引数または{@link DomContext#getNode()}が null の場合。
	 */
	public static void print(final DomContext context){
		DomConstants.write(context.getNode(), new OutputStreamWriter(System.out));
	}
	
	/**
	 * ノードを生成する{@link DomExpression}を返す。
	 * @param s HTML-FORM の子ノードの HTML 表現。
	 * @return ノードを生成する{@link DomExpression}。
	 */
	public static DomExpression getToNodeForm(final String s){
		return getToNode(FORM_HEAD + s + FORM_TAIL);
	}
	
	/**
	 * ノードを生成する{@link DomExpression}を返す。
	 * @param s HTML-BODYの子ノードの HTML 表現。
	 * @return ノードを生成する{@link DomExpression}。
	 */
	public static DomExpression getToNodeBody(final String s){
		return getToNode(BODY_HEAD + s + BODY_TAIL);
	}
	
	/**
	 * ノードを生成する{@link DomExpression}を返す。
	 * @param s
	 * @return ノードを生成する{@link DomExpression}。
	 */
	public static DomExpression getToNode(final String s){
		return new ToNode(new StringReaderFactory(s));
	}

	/**
	 * DOM ノードを比較する。
	 * @param context 比較先。
	 * @param expected 比較元。
	 * @return 比較元と比較先が同一内容を表すなら true。それ以外は false。
	 */
	public static boolean equalsForm(final DomContext context, final String expected){
		return equals(context, FORM_HEAD + expected + FORM_TAIL);
	}

	/**
	 * DOM ノードを比較する。
	 * @param context 比較先。
	 * @param expected 比較元。
	 * @return 比較元と比較先が同一内容を表すなら true。それ以外は false。
	 */
	public static boolean equalsBody(final DomContext context, final String expected){
		return equals(context, BODY_HEAD + expected + BODY_TAIL);
	}
	
	/**
	 * DOM ノードを比較する。
	 * @param context 比較先。
	 * @param expected 比較元。
	 * @return 比較元と比較先が同一内容を表すなら true。それ以外は false。
	 */
	public static boolean equals(final DomContext context, final String expected){
		final DomContext c = new DomContext();
		getToNode(expected).interpret(c);
		context.getNode().normalize();
		return equals(c.getNode(), context.getNode());
	}
	
	/**
	 * DOM ノードを比較する。
	 * @param node0 比較元。
	 * @param node1 比較先。
	 * @return 比較元と比較先が同一内容を表すなら true。それ以外は false。
	 */
	public static boolean equals(final Node node0, final Node node1){
		if(node0 == null){
			return (node1 == null);
		}
		if(node1 == null){
			return false;
		}
		if(node0.getNodeType() != node1.getNodeType()){
			return false;
		}
		if(node0.getNodeType() == Node.TEXT_NODE || node0.getNodeType() == Node.COMMENT_NODE){
			return node0.getNodeValue().equals(node1.getNodeValue());
		}
		if(node0.getNodeType() == Node.ATTRIBUTE_NODE){
			return node0.getNodeName().equals(node1.getNodeName()) && node0.getNodeValue().equals(node1.getNodeValue());
		}
		if(node0.getNodeType() == Node.DOCUMENT_NODE){
			return equals(((Document)node0).getDocumentElement(), ((Document)node1).getDocumentElement());
		}
		if(!node0.getNodeName().equals(node1.getNodeName())){
			return false;
		}
		
		final Element e0 = (Element)node0;
		final Element e1 = (Element)node1;
		final NamedNodeMap map0 = e0.getAttributes();
		final NamedNodeMap map1 = e1.getAttributes();
		if(map0.getLength() != map1.getLength()){
			return false;
		}
		for(int i=0; i<map0.getLength(); i++){
			if(!equals(map0.item(i), map1.item(i))){
				return false;
			}
		}
		
		Node child0 = node0.getFirstChild();
		Node child1 = node1.getFirstChild();
		while(child0 != null || child1 != null){
			if(!equals(child0, child1)){
				return false;
			}
			child0 = child0.getNextSibling();
			child1 = child1.getNextSibling();
		}
		return true;
	}

	public static String toString(final Reader reader) throws IOException{
		try{
			final StringBuilder sb = new StringBuilder();
			int i = 0;
			while((i = reader.read()) >= 0){
				sb.append((char)i);
			}
			return sb.toString();
		}finally{
			reader.close();
		}
	}

	/**
	 * 構造化されたコレクション構造の比較を行う。
	 * @param a OGNL表現。
	 * @param b 比較対象。
	 * @return 比較結果。
	 */
	public static boolean equals(final String a, final Object b){
		return equals(new OGE(a).getValue(null), b);
	}

	/**
	 * 構造化されたコレクション構造の比較を行う。
	 * @param a
	 * @param b
	 * @return 比較結果。
	 */
	public static boolean equals(final Object a, final Object b){
		if(a == null){
			return b == null;
		}else{
			if(b == null){
				return false;
			}
		}
		if((a instanceof LinkedHashMap) && (b instanceof LinkedHashMap)){
			final LinkedHashMap aMap = (LinkedHashMap)a;
			final LinkedHashMap bMap = (LinkedHashMap)b;
			if(!equalsMap(aMap, bMap)){
				return false;
			}
			final Iterator aIterator = aMap.keySet().iterator();
			final Iterator bIterator = bMap.keySet().iterator();
			while(aIterator.hasNext()){
				if(!equals(aIterator.next(), bIterator.next())){
					return false;
				}
			}
			return true;
		}
		if(a instanceof Map){
			if(b instanceof Map){
				final Map aMap = (Map)a;
				final Map bMap = (Map)b;
				return equalsMap(aMap, bMap);
			}else{
				return false;
			}
		}else{
			if(b instanceof Map){
				return false;
			}
		}
		if((a instanceof List || a.getClass().isArray()) && (b instanceof List || a.getClass().isArray())){
			final List aList = UtilsConstants.toList(a);
			final List bList = UtilsConstants.toList(b);
			if(aList.size() != bList.size()){
				return false;
			}
			final int length = aList.size();
			for(int i=0; i<length; i++){
				if(!equals(aList.get(i), bList.get(i))){
					return false;
				}
			}
			return true;
		}
		if((a instanceof Collection) && (b instanceof Collection)){
			return equalsCollection((Collection)a, (Collection)b);
		}
		return a.equals(b);
	}
	
	private static boolean equalsMap(final Map a, final Map b){
		if(!equalsCollection(a.keySet(), b.keySet())){
			return false;
		}
		for(final Object key:a.keySet()){
			if(!equals(a.get(key), b.get(key))){
				return false;
			}
		}
		return true;
	}
	
	private static boolean equalsCollection(final Collection a, final Collection b){
		return containsAll(a, b) && containsAll(b, a);
	}
	
	private static boolean containsAll(final Collection a, final Collection b){
		return a.containsAll(b);
	}
}
