/*
 * 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.NullFn;
import woolpack.fn.ThrowFn;
import woolpack.utils.BeanUtils;
import woolpack.utils.BuildableLinkedHashMap;
import woolpack.utils.Utils;
import woolpack.validator.MessageValidator;
import woolpack.xml.XmlUtils;

/**
 * 本パッケージの一般的な応用を集めたユーティリティです。
 * 本クラスで定義されている{@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>>, Void>> CHECK_NOT_EMPTY = Collections.unmodifiableMap(
			new BuildableLinkedHashMap<Object, Fn<Visitor<List<Object>>, Void>>()
			.map(null, new ThrowFn<Visitor<List<Object>>, Void>(new IllegalStateException()))
			.map(String.class, new Fn<Visitor<List<Object>>, Void>() {
				public Void exec(final Visitor<List<Object>> visitor) {
					if ("".equals(visitor.getElement())) {
						throw new IllegalStateException();
					}
					return null;
				}
			})
			.map(Object.class, new Fn<Visitor<List<Object>>, Void>() {
				public Void exec(final Visitor<List<Object>> visitor) {
					final List<Object> list = visitor.getSubContext();
					if (list != null) {
						list.add(visitor.getElement());
						VisitorUtils.COMPLEX_ACCEPTOR.exec(visitor);
						list.remove(list.size() - 1);
					} else {
						VisitorUtils.COMPLEX_ACCEPTOR.exec(visitor);
					}
					return null;
				}
			})
	);
	/**
	 * 構造化されたエレメントを Javascript 表現に変換するための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 * エレメント役がBeanである場合、パッケージ名を取り除いたクラス名の new 宣言に変換します。
	 * エレメント役が匿名クラスの場合は内部変数をを検索しないので、変換したJS宣言が無意味である可能性があります。
	 */
	public static final Map<Object, Fn<Visitor<StringBuilder>, Void>> JS = Collections.unmodifiableMap(
			new BuildableLinkedHashMap<Object, Fn<Visitor<StringBuilder>, Void>>()
			.map(null, new Fn<Visitor<StringBuilder>, Void>() {
				public Void exec(final Visitor<StringBuilder> visitor) {
					visitor.getSubContext().append("null");
					return null;
				}
			})
			.map(SimpleDateFormat.class, new Fn<Visitor<StringBuilder>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> visitor) {
					visitor.visit(null);
					return null;
				}
			})
			.map(Pattern.class, new Fn<Visitor<StringBuilder>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				private char[] escapeArray = {'\n', '\r', '\t', '\"', '\''};
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> visitor) {
					visitor.getSubContext().append(visitor.getElement());
					return null;
				}
			})
			.map(Boolean.class)
			.map(Map.class, new Fn<Visitor<StringBuilder>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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>, Void>() {
				public Void exec(final Visitor<StringBuilder> 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;
				}
			})
	);
	/**
	 * 構造化されたエレメントを HTML 表現に変換するための
	 * {@link Visitor#setMap(Map)}に設定する値です。
	 */
	public static final Map<Object, Fn<Visitor<Node>, Void>> DOC = Collections.unmodifiableMap(
			new BuildableLinkedHashMap<Object, Fn<Visitor<Node>, Void>>()
			.map(String.class, new Fn<Visitor<Node>, Void>() {
				public Void exec(final Visitor<Node> 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>, Void>() {
				public Void exec(final Visitor<Node> visitor) {
					visitor.visit("null");
					return null;
				}
			})
			.map(SimpleDateFormat.class, new Fn<Visitor<Node>, Void>() {
				public Void exec(final Visitor<Node> 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>, Void>() {
				public Void exec(final Visitor<Node> 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>, Void>() {
				public Void exec(final Visitor<Node> visitor) {
					final Pattern pattern = (Pattern) visitor.getElement();
					visitor.visit("RegExp");
					visitor.visit(Utils.map("pattern", pattern.pattern()));
					return null;
				}
			})
			.map(Map.class, new ElementAcceptor("dl", new Fn<Visitor<Node>, Void>() {
				private final Fn<Visitor<Node>, Void> dt = new ElementAcceptor("dt");
				private final Fn<Visitor<Node>, Void> dd = new ElementAcceptor("dd");
				public Void exec(final Visitor<Node> visitor) {
					final Map map = (Map) visitor.getElement();
					for (final Object entryObject : map.entrySet()) {
						final Entry entry = (Entry) entryObject;
						VisitorUtils.localElement(entry.getKey(), visitor, dt);
						VisitorUtils.localElement(entry.getValue(), visitor, dd);
					}
					return null;
				}
			}))
			.map(Iterable.class, new ElementAcceptor("ol", new Fn<Visitor<Node>, Void>() {
				private final Fn<Visitor<Node>, Void> li = new ElementAcceptor("li");
				public Void exec(final Visitor<Node> visitor) {
					final Iterable iterable = (Iterable) visitor.getElement();
					for (final Object e : iterable) {
						VisitorUtils.localElement(e, visitor, li);
					}
					return null;
				}
			}))
			.map(Object[].class, new ElementAcceptor("ol", new Fn<Visitor<Node>, Void>() {
				private final Fn<Visitor<Node>, Void> li = new ElementAcceptor("li");
				public Void exec(final Visitor<Node> visitor) {
					final int length = Array.getLength(visitor.getElement());
					for (int i = 0; i < length; i++) {
						VisitorUtils.localElement(Array.get(visitor.getElement(), i), visitor, 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>, Void>() {
				public Void exec(final Visitor<Node> 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)}に設定する{@link Map}です。
	 */
	public static final Map<Object, Fn<? super Visitor<Collection<String>>, Void>> MESSAGE = Collections.unmodifiableMap(
			new BuildableLinkedHashMap<Object, Fn<? super Visitor<Collection<String>>, Void>>()
			.map(String.class, new NullFn<Visitor<Collection<String>>, Void>())
			.map(MessageValidator.class, new Fn<Visitor<Collection<String>>, Void>() {
				public Void exec(final Visitor<Collection<String>> visitor) {
					final MessageValidator v = (MessageValidator) visitor.getElement();
					visitor.getSubContext().add(v.getMessage());
					return null;
				}
			})
			.map(Object.class, VisitorUtils.COMPLEX_ACCEPTOR));

	private VisitorAppUtils() {
	}
}
