/*
 * Copyright 2006-2011 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.codegen.lib.internal.impl;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.seasar.codegen.lib.Condition;
import org.seasar.codegen.lib.annotation.SqlFileEncoding;
import org.seasar.dao.BeanMetaData;
import org.seasar.dao.SqlCommand;
import org.seasar.dao.annotation.tiger.util.AnnotationUtil;
import org.seasar.dao.impl.BeanArrayMetaDataResultSetHandler;
import org.seasar.dao.impl.BeanListMetaDataResultSetHandler;
import org.seasar.dao.impl.BeanMetaDataResultSetHandler;
import org.seasar.dao.impl.DaoMetaDataImpl;
import org.seasar.dao.impl.RelationRowCreatorImpl;
import org.seasar.dao.impl.RowCreatorImpl;
import org.seasar.extension.jdbc.ResultSetHandler;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.Disposable;
import org.seasar.framework.util.DisposableUtil;

/**
 * S2Dao-CodeGen用のDaoMetaDataです。
 * 
 * @author azusa
 * 
 */
@SuppressWarnings("unchecked")
public class CodeGenDaoMetaDataImpl extends DaoMetaDataImpl implements Disposable {

    /**
     * 関連先まで取得するメソッドの名称のサフィックスです。
     */
    public static final String RELATION_METHOD_SUFFIX = "WithRelation";

    /**
     * コストラクタです。
     * 
     * <a href="http://s2container.seasar.org/2.4/s2-framework/ja/apidocs/org/seasar/framework/util/DisposableUtil.html"
     * >DisposableUtil</a>に登録します。
     * 
     */
    public CodeGenDaoMetaDataImpl() {
        DisposableUtil.add(this);
    }

    protected Map beanMetaDataCache = new HashMap();

    /**
     * SQLファイルのエンコーディングです。
     */
    protected String encoding;

    protected void setupMethod(Method method) {

        {
            Class[] paramerClasses = method.getParameterTypes();
            if (method.getName().endsWith(RELATION_METHOD_SUFFIX)) {
                setupFindMethod(method);
                return;
            }
            for (Class clazz : paramerClasses) {
                if (Condition.class.isAssignableFrom(clazz)) {
                    setupFindMethod(method);
                    return;
                }
            }
        }

        super.setupMethod(method);
    }

    protected void setupFindMethod(Method method) {
        sqlCommands.put(method.getName(), getFindSqlCommand(method));

    }

    /**
     * find～メソッドと名前がWithRelationで終わるメソッドのためのSqlCommandを作成します。
     * 
     * @param method
     *            Daoのメソッド
     * @return SqlCommand
     */
    protected SqlCommand getFindSqlCommand(Method method) {
        BeanMetaData beanMetaData = getBeanMetaData(daoAnnotationReader.getBeanClass(method));
        ResultSetHandler resultSetHandler = createResultSetHandlerFromReturnType(method, beanMetaData);
        String baseSql = createBaseSQL(method, beanMetaData);

        FindSqlCommand command = new FindSqlCommand(dataSource, statementFactory, resultSetHandler, resultSetFactory,
                pagingSqlRewriter, baseSql);
        command.setArgNames(daoAnnotationReader.getArgNames(method));
        command.setArgTypes(method.getParameterTypes());
        return command;
    }

    protected String createBaseSQL(Method method, BeanMetaData beanMetaData) {
        String baseSql;
        String query = daoAnnotationReader.getQuery(method);
        if (query == null) {
            baseSql = dbms.getAutoSelectSql(beanMetaData);
        } else {
            if (startsWithOrderBy(query)) {
                baseSql = dbms.getAutoSelectSql(beanMetaData) + " " + query;
            } else {
                baseSql = dbms.getAutoSelectSql(beanMetaData) + " WHERE " + query;
            }
        }
        return baseSql;
    }

    protected ResultSetHandler createResultSetHandlerFromReturnType(Method method, BeanMetaData beanMetaData) {
        Class retType = method.getReturnType();

        if (ClassUtil.isAssignableFrom(List.class, retType)) {
            return new BeanListMetaDataResultSetHandler(beanMetaData, new RowCreatorImpl(),
                    new RelationRowCreatorImpl());

        } else if (retType.isArray()) {
            return new BeanArrayMetaDataResultSetHandler(beanMetaData, new RowCreatorImpl(),
                    new RelationRowCreatorImpl());

        } else {
            return new BeanMetaDataResultSetHandler.RestrictBeanMetaDataResultSetHandler(beanMetaData,
                    new RowCreatorImpl(), new RelationRowCreatorImpl());

        }
    }

    protected BeanMetaData getBeanMetaData(Class beanClass) {
        BeanMetaData ret = (BeanMetaData) beanMetaDataCache.get(beanClass);
        if (ret == null) {
            BeanMetaData newBeanMetaData = beanMetaDataFactory.createBeanMetaData(beanClass);
            beanMetaDataCache.put(beanClass, newBeanMetaData);
            return newBeanMetaData;
        } else {
            return ret;
        }
    }

    public void dispose() {
        beanMetaDataCache.clear();
    }

    @Override
    protected void setupMethodBySqlFile(Class arg0, Method arg1) {
        super.setupMethodBySqlFile(arg0, arg1);
    }

    @Override
    protected String getSqlFileEncoding() {
        if (encoding != null) {
            return encoding;
        }
        SqlFileEncoding encoding = AnnotationUtil.getAnnotation(daoClass, SqlFileEncoding.class);
        if (encoding != null) {
            this.encoding = encoding.value();
            return encoding != null ? encoding.value() : super.getSqlFileEncoding();
        } else {
            return super.getSqlFileEncoding();
        }
    }

}
