/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.extension.jdbc.gen.internal.argtype;

import java.io.File;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.tiger.ReflectionUtil;

/**
 * {@link ArgumentType}を管理するクラスです。
 * 
 * @author taedium
 */
public class ArgumentTypeRegistry {

    /** クラスをキー、 {@link ArgumentType}のコンストラクタを値とするマップ */
    protected static Map<Class<?>, Constructor<? extends ArgumentType<?>>> argTypeMap = new ConcurrentHashMap<Class<?>, Constructor<? extends ArgumentType<?>>>();
    static {
        argTypeMap.put(Boolean.class, ReflectionUtil.getConstructor(
                BooleanType.class, null));
        argTypeMap.put(Character.class, ReflectionUtil.getConstructor(
                CharacterType.class, null));
        argTypeMap.put(String.class, ReflectionUtil.getConstructor(
                StringType.class, null));
        argTypeMap.put(File.class, ReflectionUtil.getConstructor(
                FileType.class, null));
        argTypeMap.put(Class.class, ReflectionUtil.getConstructor(
                ClassType.class, null));
    }

    /** コレクション型のクラスをキー、 コレクション型の{@link ArgumentType}のコンストラクタを値とするマップ */
    @SuppressWarnings("unchecked")
    protected static Map<Class<? extends Collection>, Constructor<? extends CollectionType>> collectionArgTypeMap = new ConcurrentHashMap<Class<? extends Collection>, Constructor<? extends CollectionType>>();
    static {
        collectionArgTypeMap.put(List.class, ReflectionUtil.getConstructor(
                ListType.class, ArgumentType.class));
        collectionArgTypeMap.put(Set.class, ReflectionUtil.getConstructor(
                SetType.class, ArgumentType.class));
        collectionArgTypeMap.put(Collection.class, ReflectionUtil
                .getConstructor(CollectionType.class, ArgumentType.class));
    }

    /**
     * {@link ArgumentType}を返します。
     * 
     * @param <T>
     *            引数の型
     * @param propertyDesc
     *            プロパティ記述
     * @return {@link ArgumentType}
     */
    public static <T> ArgumentType<T> getArgumentType(PropertyDesc propertyDesc) {
        Class<?> propertyType = propertyDesc.getPropertyType();
        if (propertyDesc.isParameterized()
                && Collection.class.isAssignableFrom(propertyType)) {
            Class<?> elementClass = propertyDesc.getElementClassOfCollection();
            return getCollectionArgumentType(propertyType, elementClass);
        }
        return getArgumentType(propertyType);
    }

    /**
     * {@link ArgumentType}を返します。
     * 
     * @param <T>
     *            引数の型
     * @param clazz
     *            引数のクラス
     * @return {@link ArgumentType}
     */
    @SuppressWarnings("unchecked")
    protected static <T> ArgumentType<T> getArgumentType(Class<?> clazz) {
        clazz = ClassUtil.getWrapperClassIfPrimitive(clazz);
        if (argTypeMap.containsKey(clazz)) {
            Constructor<? extends ArgumentType<?>> constructor = argTypeMap
                    .get(clazz);
            return (ArgumentType<T>) ReflectionUtil.newInstance(constructor,
                    null);
        }
        if (Number.class.isAssignableFrom(clazz)) {
            return new NumberType(clazz);
        }
        if (Enum.class.isAssignableFrom(clazz)) {
            return new EnumType(clazz);
        }
        return null;
    }

    /**
     * {@link ArgumentType}を返します。
     * 
     * @param <T>
     *            引数の型
     * @param collectioinClass
     *            コレクションのクラス
     * @param elementClass
     *            コレクションの要素のクラス
     * @return {@link ArgumentType}を返します。
     */
    @SuppressWarnings("unchecked")
    protected static <T> ArgumentType<T> getCollectionArgumentType(
            Class<?> collectioinClass, Class<?> elementClass) {
        ArgumentType<?> argumentType = getArgumentType(elementClass);
        if (collectionArgTypeMap.containsKey(collectioinClass)) {
            Constructor<? extends CollectionType> constructor = collectionArgTypeMap
                    .get(collectioinClass);
            return ReflectionUtil.newInstance(constructor, argumentType);
        }
        return null;
    }

    /**
     * {@link ArgumentType}を登録します。
     * 
     * @param clazz
     *            引数のクラス
     * @param argumentTypeClass
     *            引数の型を表すクラス
     */
    public static void registerArgumentType(Class<?> clazz,
            Class<? extends ArgumentType<?>> argumentTypeClass) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        if (argumentTypeClass == null) {
            throw new NullPointerException("argumentTypeClass");
        }
        Constructor<? extends ArgumentType<?>> constructor = ReflectionUtil
                .getConstructor(argumentTypeClass, null);
        argTypeMap.put(clazz, constructor);
    }

    /**
     * {@link ArgumentType}を削除します。
     * 
     * @param clazz
     *            引数のクラス
     */
    public static void deregisterArgumentType(Class<?> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        argTypeMap.remove(clazz);
    }

    /**
     * コレクション型の{@link ArgumentType}を登録します。
     * 
     * @param collectionClass
     *            コレクション型のクラス
     * @param argumentTypeClass
     *            引数の型を表すクラス
     */
    @SuppressWarnings("unchecked")
    public static void registerCollectionArgumentType(
            Class<? extends Collection> collectionClass,
            Class<? extends CollectionType> argumentTypeClass) {
        if (collectionClass == null) {
            throw new NullPointerException("collectionClass");
        }
        if (argumentTypeClass == null) {
            throw new NullPointerException("argumentTypeClass");
        }
        Constructor<? extends CollectionType> constructor = ReflectionUtil
                .getConstructor(argumentTypeClass, ArgumentType.class);
        collectionArgTypeMap.put(collectionClass, constructor);
    }

    /**
     * コレクション型の{@link ArgumentType}を削除します。
     * 
     * @param collectionClass
     *            コレクション型の引数のクラス
     */
    @SuppressWarnings("unchecked")
    public static void deregisterCollectionArgumentType(
            Class<? extends Collection> collectionClass) {
        if (collectionClass == null) {
            throw new NullPointerException("collectionClass");
        }
        collectionArgTypeMap.remove(collectionClass);
    }
}
