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

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.w3c.dom.Node;

import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.utils.BeanUtils;
import woolpack.utils.Utils;
import woolpack.validator.MessageValidator;
import woolpack.xml.XmlUtils;

/**
 * visitorパッケージの一般的な応用を集めたユーティリティです。
 * 本クラスで定義されている{@link Map}型の値を
 * {@link Map#putAll(Map)}でコピーして編集したものを
 * {@link Visitor#setMap(Map)}で扱うこともできます。
 * 
 * @author nakamura
 *
 */
public final class VisitorAppUtils {
	/**
	 * 構造化されたエレメントに null も空文字列も存在しないことをチェックするための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 */
	public static final Map<Object, Fn<Visitor<List<Object>, RuntimeException>, Void, RuntimeException>> CHECK_NOT_EMPTY = Collections.unmodifiableMap(
			Utils.<Object, Fn<Visitor<List<Object>, RuntimeException>, Void, RuntimeException>>
			linkedMap(null, FnUtils.<Visitor<List<Object>, RuntimeException>, Void, RuntimeException>throwing(new IllegalStateException()))
			.map(String.class, new Fn<Visitor<List<Object>, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<List<Object>, RuntimeException> visitor) {
					if ("".equals(visitor.getElement())) {
						throw new IllegalStateException();
					}
					return null;
				}
			})
			.map(Object.class, new Fn<Visitor<List<Object>, RuntimeException>, Void, RuntimeException>() {
				private final Fn<Visitor<?, RuntimeException>, Void, RuntimeException> complexAcceptor = new ComplexAcceptor<RuntimeException>();
				public Void exec(final Visitor<List<Object>, RuntimeException> visitor) {
					final List<Object> list = visitor.getSubContext();
					if (list != null) {
						list.add(visitor.getElement());
						complexAcceptor.exec(visitor);
						list.remove(list.size() - 1);
					} else {
						complexAcceptor.exec(visitor);
					}
					return null;
				}
			})
	);
	
	/**
	 * 構造化されたエレメントを Javascript 表現に変換するための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 * エレメント役がBeanである場合、パッケージ名を取り除いたクラス名の new 宣言に変換します。
	 * エレメント役が匿名クラスの場合は内部変数をを検索しないので、変換したJS宣言が無意味である可能性があります。
	 */
	public static final Map<Object, Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>> JS = Collections.unmodifiableMap(
			Utils.<Object, Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>>
			linkedMap(null, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					visitor.getSubContext().append("null");
					return null;
				}
			})
			.map(SimpleDateFormat.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final SimpleDateFormat format = (SimpleDateFormat) visitor.getElement();
					final StringBuilder sb = visitor.getSubContext();
					sb.append("new SimpleDateFormat(");
					visitor.visit(format.toPattern());
					sb.append(')');
					return null;
				}
			})
			.map(DecimalFormat.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final DecimalFormat format = (DecimalFormat) visitor.getElement();
					final StringBuilder sb = visitor.getSubContext();
					sb.append("new DecimalFormat(");
					visitor.visit(format.toPattern());
					sb.append(')');
					return null;
				}
			})
			.map(Class.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					visitor.visit(null);
					return null;
				}
			})
			.map(Pattern.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final Pattern pattern = (Pattern) visitor.getElement();
					final StringBuilder sb = visitor.getSubContext();
					sb.append("new RegExp(");
					visitor.visit(pattern.pattern());
					sb.append(",\"g\")");
					return null;
				}
			})
			.map(String.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				private char[] escapeArray = {'\n', '\r', '\t', '\"', '\''};
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final String s = visitor.getElement().toString();
					final StringBuilder sb = visitor.getSubContext();
					sb.append("\"");
					
					int i = sb.length();
					sb.append(s);
					int max = sb.length();
					while (i < max) {
						for (final char c : escapeArray) {
							if (c == sb.charAt(i)) {
								sb.insert(i, '\\');
								i++;
								max++;
								break;
							}
						}
						i++;
					}
					
					sb.append("\"");
					return null;
				}
			})
			.map(Character.class)
			.map(Number.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					visitor.getSubContext().append(visitor.getElement());
					return null;
				}
			})
			.map(Boolean.class)
			.map(Map.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final Map map = (Map) visitor.getElement();
					final StringBuilder sb = visitor.getSubContext();
					sb.append('{');
					boolean flag = true;
					for (final Object entryObject : map.entrySet()) {
						final Entry entry = (Entry) entryObject;
						if (flag) {
							flag = false;
						} else {
							sb.append(',');
						}
						visitor.visit(entry.getKey());
						sb.append(':');
						visitor.visit(entry.getValue());
					}
					sb.append('}');
					return null;
				}
			})
			.map(Iterable.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final Iterable iterable = (Iterable) visitor.getElement();
					final StringBuilder sb = visitor.getSubContext();
					sb.append('[');
					boolean flag = true;
					for (final Object e : iterable) {
						if (flag) {
							flag = false;
						} else {
							sb.append(',');
						}
						visitor.visit(e);
					}
					sb.append(']');
					return null;
				}
			})
			.map(Object[].class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final int length = Array.getLength(visitor.getElement());
					final StringBuilder sb = visitor.getSubContext();
					sb.append('[');
					boolean flag = true;
					for (int i = 0; i < length; i++) {
						if (flag) {
							flag = false;
						} else {
							sb.append(',');
						}
						visitor.visit(Array.get(visitor.getElement(), i));
					}
					sb.append(']');
					return null;
				}
			})
			.map(char[].class)
			.map(byte[].class)
			.map(short[].class)
			.map(int[].class)
			.map(long[].class)
			.map(float[].class)
			.map(double[].class)
			.map(boolean[].class)
			.map(Object.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final StringBuilder sb = visitor.getSubContext();
					sb.append("new ");
					sb.append(BeanUtils.getLocalClassName(visitor.getElement().getClass()));
					sb.append('(');
					boolean flag = true;
					final List<PropertyDescriptor> methodList = BeanUtils.getConstructorGetterList(visitor.getElement());
					for (final PropertyDescriptor property : methodList) {
						final Object value = BeanUtils.get(visitor.getElement(), property.getReadMethod());
						if (flag) {
							flag = false;
						} else {
							sb.append(',');
						}
						visitor.visit(value);
					}
					sb.append(')');
					return null;
				}
			})
	);

	/**
	 * 構造化されたエレメントをダンプ情報に変換するための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 */
	public static final Map<Object, Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>> DUMP = Collections.unmodifiableMap(
			Utils.<Object, Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>>
			linkedMap(JS)
			.map(Object.class, new Fn<Visitor<StringBuilder, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<StringBuilder, RuntimeException> visitor) {
					final StringBuilder sb = visitor.getSubContext();
					sb.append(BeanUtils.getLocalClassName(visitor.getElement().getClass()));
					sb.append('(');
					boolean flag = true;
					final List<PropertyDescriptor> methodList = BeanUtils.getGetterList(visitor.getElement().getClass());
					for (final PropertyDescriptor property : methodList) {
						final Object value = BeanUtils.get(visitor.getElement(), property.getReadMethod());
						if (flag) {
							flag = false;
						} else {
							sb.append(',');
						}
						visitor.visit(property.getName());
						sb.append(':');
						visitor.visit(value);
					}
					sb.append(')');
					return null;
				}
			}));
	
	/**
	 * 構造化されたエレメントを HTML 表現に変換するための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 */
	public static final Map<Object, Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>> DOC = Collections.unmodifiableMap(
			Utils.<Object, Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>>
			linkedMap(String.class, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final Node node = visitor.getSubContext();
					final String s = visitor.getElement().toString();
					if (!"".equals(s)) {
						node.appendChild(XmlUtils.getDocumentNode(node).createTextNode(s));
					}
					return null;
				}
			})
			.map(Character.class)
			.map(Number.class)
			.map(Boolean.class)
			.map(null, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					visitor.visit("null");
					return null;
				}
			})
			.map(SimpleDateFormat.class, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final SimpleDateFormat format = (SimpleDateFormat) visitor.getElement();
					visitor.visit("SimpleDateFormat");
					visitor.visit(Utils.map("pattern", format.toPattern()));
					return null;
				}
			})
			.map(DecimalFormat.class, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final DecimalFormat format = (DecimalFormat) visitor.getElement();
					visitor.visit("DecimalFormat");
					visitor.visit(Utils.map("pattern", format.toPattern()));
					return null;
				}
			})
			.map(Pattern.class, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final Pattern pattern = (Pattern) visitor.getElement();
					visitor.visit("RegExp");
					visitor.visit(Utils.map("pattern", pattern.pattern()));
					return null;
				}
			})
			.map(Map.class, new ElementAcceptor<RuntimeException>("dl", new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				private final Fn<Visitor<Node, RuntimeException>, Void, RuntimeException> dt = new ElementAcceptor<RuntimeException>("dt");
				private final Fn<Visitor<Node, RuntimeException>, Void, RuntimeException> dd = new ElementAcceptor<RuntimeException>("dd");
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final Map map = (Map) visitor.getElement();
					for (final Object entryObject : map.entrySet()) {
						final Entry entry = (Entry) entryObject;
						visitor.visit(entry.getKey(), dt);
						visitor.visit(entry.getValue(), dd);
					}
					return null;
				}
			}))
			.map(Iterable.class, new ElementAcceptor<RuntimeException>("ol", new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				private final Fn<Visitor<Node, RuntimeException>, Void, RuntimeException> li = new ElementAcceptor<RuntimeException>("li");
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final Iterable iterable = (Iterable) visitor.getElement();
					for (final Object e : iterable) {
						visitor.visit(e, li);
					}
					return null;
				}
			}))
			.map(Object[].class, new ElementAcceptor<RuntimeException>("ol", new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				private final Fn<Visitor<Node, RuntimeException>, Void, RuntimeException> li = new ElementAcceptor<RuntimeException>("li");
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					final int length = Array.getLength(visitor.getElement());
					for (int i = 0; i < length; i++) {
						visitor.visit(Array.get(visitor.getElement(), i), li);
					}
					return null;
				}
			}))
			.map(char[].class)
			.map(byte[].class)
			.map(short[].class)
			.map(int[].class)
			.map(long[].class)
			.map(float[].class)
			.map(double[].class)
			.map(boolean[].class)
			.map(Object.class, new Fn<Visitor<Node, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Node, RuntimeException> visitor) {
					visitor.visit(BeanUtils.getLocalClassName(visitor.getElement().getClass()));
					final Map<Object, Object> map = new HashMap<Object, Object>();
					final List<PropertyDescriptor> methodList = BeanUtils.getConstructorGetterList(visitor.getElement());
					for (final PropertyDescriptor property : methodList) {
						final Object value = BeanUtils.get(visitor.getElement(), property.getReadMethod());
						map.put(property.getName(), value);
					}
					visitor.visit(map);
					return null;
				}
			})
	);
	
	/**
	 * 構造化されたエレメントに含まれる{@link MessageValidator#getMessage()}を収集するための、
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 */
	public static final Map<Object, Fn<? super Visitor<Collection<String>, RuntimeException>, Void, RuntimeException>> MESSAGE = Collections.unmodifiableMap(
			Utils.<Object, Fn<? super Visitor<Collection<String>, RuntimeException>, Void, RuntimeException>>
			linkedMap(String.class, FnUtils.<Visitor<Collection<String>, RuntimeException>, Void>fix(null))
			.map(MessageValidator.class, new Fn<Visitor<Collection<String>, RuntimeException>, Void, RuntimeException>() {
				public Void exec(final Visitor<Collection<String>, RuntimeException> visitor) {
					final MessageValidator v = (MessageValidator) visitor.getElement();
					visitor.getSubContext().add(v.getMessage());
					return null;
				}
			})
			.map(Object.class, new ComplexAcceptor<RuntimeException>()));

	private VisitorAppUtils() {
	}
}
