/*
 * 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.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import woolpack.utils.Switchable;
import woolpack.utils.UtilsConstants;

/**
 * 定数と静的メソッドの集まり。
 * 
 * @author nakamura
 * 
 */
public final class VisitorConstants {

	/**
	 * null オブジェクト役の{@link Acceptable}。
	 */
	public static final Acceptable<Object> NULL = new Acceptable<Object>() {
		public void accept(final Visitor visitor, final Object v) {
		}
	};

	/**
	 * {@link Acceptable#accept(Visitor, Object)}引数の V を
	 * {@link Visitor#visit(Object)}に渡す{@link Acceptable}。
	 */
	public static final Acceptable<Object> ECHO = new Acceptable<Object>() {
		public void accept(final Visitor visitor, final Object v) {
			visitor.visit(v);
		}
	};

	/**
	 * {@link Acceptable#accept(Visitor, Object)}引数の{@link Iterable}の値を順次取り出して
	 * {@link Visitor#visit(Object)}に渡す{@link Acceptable}。
	 */
	public static final Acceptable<Iterable> ITERABLE
	= new Acceptable<Iterable>() {
		public void accept(final Visitor visitor, final Iterable v) {
			for (final Object child : v) {
				visitor.visit(child);
			}
		}
	};

	/**
	 * {@link Acceptable#accept(Visitor, Object)}引数の{@link Map}のキーを順次取り出して
	 * キーと値を{@link Visitor#visit(Object)}に渡す{@link Acceptable}。
	 */
	public static final Acceptable<Map> MAP = new Acceptable<Map>() {
		public void accept(final Visitor visitor, final Map v) {
			for (final Object entryObject : v.entrySet()) {
				final Entry entry = (Entry) entryObject;
				visitor.visit(entry.getKey());
				visitor.visit(entry.getValue());
			}
		}
	};

	/**
	 * {@link Acceptable#accept(Visitor, Object)}引数の V のゲッターメソッドを順次取り出して
	 * プロパティ名と値を{@link Visitor#visit(Object)}に渡す{@link Acceptable}。
	 */
	public static final Acceptable<Object> REFLECTION
	= new Acceptable<Object>() {
		public void accept(final Visitor visitor, final Object v) {
			final List<PropertyDescriptor> methodList = getGetterList(v
					.getClass());
			for (final PropertyDescriptor property : methodList) {
				final Object value = get(v, property.getReadMethod());
				visitor.visit(property.getName());
				visitor.visit(value);
			}
		}
	};

	private VisitorConstants() {
	} // カバレージがここを通過してはいけない

	/**
	 * object に対して method を引数無しで実行しその結果を返す。
	 * 
	 * @param object
	 *            作用対象。
	 * @param method
	 *            実行するメソッド。
	 * @return method を実行した後の返却値。
	 * @throws IllegalArgumentException メソッド実行で発生した場合。
	 * @throws IllegalStateException
	 *  ({@link IllegalAccessException},
	 *  {@link InvocationTargetException})メソッド実行で発生した場合。
	 */
	public static Object get(final Object object, final Method method) {
		try {
			return method.invoke(object, new Object[0]);
		} catch (final IllegalAccessException e) {
			throw new IllegalStateException(e);
		} catch (final InvocationTargetException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * クラスのゲッターメソッド一覧を返す。
	 * 
	 * @param clazz
	 *            調査対象クラス。
	 * @return ゲッターメソッド一覧。
	 * @throws IllegalArgumentException (
	 *             {@link IntrospectionException})clazz の解析に失敗した場合。
	 */
	public static List<PropertyDescriptor> getGetterList(final Class clazz) {
		final BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(clazz);
		} catch (final IntrospectionException e) {
			throw new IllegalArgumentException(e);
		}
		final List<PropertyDescriptor> list = new ArrayList<PropertyDescriptor>(
				beanInfo.getPropertyDescriptors().length);
		for (final PropertyDescriptor p : beanInfo.getPropertyDescriptors()) {
			final Method m = p.getReadMethod();
			if (m == null) {
				continue;
			}
			final String key = p.getName();
			if ("class".equals(key)) {
				continue;
			}
			list.add(p);
		}
		return list;
	}

	/**
	 * コンストラクタ引数とゲッターメソッド一覧が一致する場合にコンストラクタの引数の順序で並べたゲッターメソッド一覧を返す。
	 * コンストラクタが複数存在する場合はゲッターメソッド一覧と一致するものを検索する。
	 * 
	 * @param clazz
	 *            調査対象クラス。
	 * @return ゲッターメソッド一覧。
	 * @throws IllegalArgumentException
	 *             コンストラクタの引数内で同一の型のものが存在するまたはゲッターメソッド一覧と一致するコンストラクタが存在しない場合。
	 */
	public static List<PropertyDescriptor> getConstructorGetterList(
			final Class clazz) {
		final List<PropertyDescriptor> list = getGetterList(clazz);
		final Map<Class, PropertyDescriptor> map = UtilsConstants
				.unoverwritableMap(new HashMap<Class, PropertyDescriptor>());
		for (final PropertyDescriptor p : list) {
			map.put(p.getPropertyType(), p);
		}
		final Constructor[] array = clazz.getConstructors();
		for (final Constructor constructor : array) {
			final List<Class> classList = Arrays.asList(constructor
					.getParameterTypes());
			if (classList.containsAll(map.keySet())
					&& map.keySet().containsAll(classList)) {
				final List<PropertyDescriptor> newList
				= new ArrayList<PropertyDescriptor>();
				for (final Class propertyClass : classList) {
					newList.add(map.get(propertyClass));
				}
				return newList;
			}
		}
		throw new IllegalArgumentException("not match constructor and parameter.");
	}

	/**
	 * {@link Acceptable#accept(Visitor, Object)}
	 * 引数の{@link Switchable}のキーを順次取り出して
	 * キーと値を{@link Visitor#visit(Object)}に渡す{@link Acceptable}を返す。
	 * 
	 * @param <K>
	 *            {@link Switchable}のキーの型。
	 * @param <V>
	 *            {@link Switchable}の値の型。
	 * @param defaultKey
	 *            {@link Switchable#getDefault()}のキー。
	 * @return {@link Switchable}のキーを順次取り出してキーと値を
	 * {@link Visitor}に委譲する{@link Acceptable}。
	 */
	public static <K, V> Acceptable<Switchable<K, V>> switchableAcceptable(
			final Object defaultKey) {
		return new Acceptable<Switchable<K, V>>() {
			public void accept(
					final Visitor visitor,
					final Switchable<K, V> v) {
				for (final K key : v.keys()) {
					visitor.visit(key);
					visitor.visit(v.get(key));
				}
				if (defaultKey != null && v.getDefault() != null) {
					visitor.visit(defaultKey);
					visitor.visit(v.getDefault());
				}
			}
		};
	}

	/**
	 * 引数を{@link Visitor#visit(Object)}に渡す{@link Acceptable}を返す。
	 * 
	 * @param <V>
	 *            汎用型。
	 * @param object
	 *            {@link Visitor#visit(Object)}に渡す値。
	 * @return object を引数として{@link Visitor#visit(Object)}
	 * を呼び出す{@link Acceptable}。
	 */
	public static <V> Acceptable<V> visitAcceptable(final Object object) {
		return new Acceptable<V>() {
			public void accept(final Visitor visitor, final V v) {
				visitor.visit(object);
			}
		};
	}

	/**
	 * 順次{@link Acceptable#accept(Visitor, Object)}を呼び出す{@link Acceptable}を返す。
	 * 
	 * @param acceptables
	 *            {@link Acceptable}の配列。
	 * @return 引数に順次{@link Acceptable#accept(Visitor, Object)}
	 * を呼び出す{@link Acceptable}。
	 */
	public static Acceptable serialAcceptable(
			final Acceptable... acceptables) {
		return new Acceptable() {
			public void accept(final Visitor visitor, final Object v) {
				for (final Acceptable acceptable : acceptables) {
					acceptable.accept(visitor, v);
				}
			}
		};
	}
}
