/*
 * 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.sql.DataSource;

import org.seasar.codegen.lib.BindValue;
import org.seasar.codegen.lib.Column;
import org.seasar.codegen.lib.Condition;
import org.seasar.codegen.lib.ConditionResult;
import org.seasar.codegen.lib.Order;
import org.seasar.codegen.lib.Query;
import org.seasar.codegen.lib.impl.BindValueImpl;
import org.seasar.dao.CommandContext;
import org.seasar.dao.impl.SelectDynamicCommand;
import org.seasar.dao.pager.PagingSqlRewriter;
import org.seasar.extension.jdbc.ResultSetFactory;
import org.seasar.extension.jdbc.ResultSetHandler;
import org.seasar.extension.jdbc.StatementFactory;
import org.seasar.extension.jdbc.impl.BasicSelectHandler;
import org.seasar.framework.util.StringUtil;

/**
 * S2Dao-CodeGenの検索系メソッドのためのSqlCommandです。
 * 
 * @author azusa
 * 
 */
public class FindSqlCommand extends SelectDynamicCommand {

    protected final PagingSqlRewriter sqlRewriter;

    protected final ResultSetHandler resultSetHandler;

    protected final ResultSetFactory resultSetFactory;

    protected final String baseSql;

    /**
     * コンストラクタです。
     * 
     * @param dataSource
     *            データソース
     * @param statementFactory
     *            StatementFactory
     * @param resultSetHandler
     *            ResultSetHandler
     * @param resultSetFactory
     *            ResultSetFactory
     * @param pagingSqlRewriter
     *            PagingSqlRewriter
     * @param baseSql
     *            ベースのSQL
     */
    public FindSqlCommand(DataSource dataSource, StatementFactory statementFactory, ResultSetHandler resultSetHandler,
            ResultSetFactory resultSetFactory, PagingSqlRewriter pagingSqlRewriter, String baseSql) {
        super(dataSource, statementFactory, resultSetHandler, resultSetFactory, pagingSqlRewriter);
        this.sqlRewriter = pagingSqlRewriter;
        this.resultSetHandler = resultSetHandler;
        this.resultSetFactory = resultSetFactory;
        this.baseSql = baseSql;
    }

    /**
     * コマンドを実行します。
     * 
     * @param args
     *            メソッド引数
     * @return 検索結果
     */
    @Override
    public Object execute(Object[] args) {

        String sql = baseSql;
        Condition condition = null;
        ConditionResult conditionResult = null;
        Object[] bindVariables = null;
        Class<?>[] bindVariableTypes = null;

        if (args.length != 0 && args[0] instanceof Condition) {
            // 検索系
            condition = (Condition) args[0];
            conditionResult = condition.getConditionResult();

            sql = new StringBuilder(baseSql).append(createSql(conditionResult)).toString();
            setSql(sql);
            bindVariables = createVariables(conditionResult);
            bindVariableTypes = createVariablesTypes(bindVariables);

        } else {
            // Relation系
            setSql(sql);
            CommandContext ctx = apply(args);
            bindVariables = ctx.getBindVariables();
            bindVariableTypes = ctx.getBindVariableTypes();
            sql = ctx.getSql();

        }

        String executingSql = sqlRewriter.rewrite(sql, bindVariables, bindVariableTypes);
        BasicSelectHandler selectHandler = new BasicSelectHandler(getDataSource(), executingSql, resultSetHandler,
                getStatementFactory(), resultSetFactory);
        injectDaoClass(selectHandler);
        selectHandler.setFetchSize(-1);
        return selectHandler.execute(bindVariables, bindVariableTypes);
    }

    protected StringBuilder createSql(ConditionResult conditionResult) {
        StringBuilder sb = new StringBuilder();
        // getQueryの結果を追加

        if (conditionResult.getBindValues().size() != 0 || !StringUtil.isEmpty(conditionResult.getQuery().getQuery())) {
            sb.append(" WHERE ");
        } else {
            sb.append(" ");
        }
        {
            Iterator<BindValue> i = conditionResult.getBindValues().iterator();
            while (i.hasNext()) {
                BindValue bindValue = i.next();
                String alias = bindValue.getColumn().getAlias();
                String column = bindValue.getColumn().getName();
                if (StringUtil.isEmpty(alias) == false) {
                    sb.append(alias).append(".").append(column);
                } else {
                    sb.append(column);
                }
                // IS NULLとかはoperatorの前にスペースいるから...
                sb.append(" ");
                sb.append(bindValue.getOperator().getOperator());
                Object[] args = bindValue.getArgs();
                switch (bindValue.getOperator()) {
                case EQUAL:
                case LESS:
                case LESSTHAN:
                case MORE:
                case MORETHAN:
                case LIKE:
                case NOT:
                    // 通常の検索条件
                    sb.append("?");
                    break;
                case IN:
                case NOTIN:
                    // IN と NOT IN
                    sb.append("(");
                    for (int k = 0; k < args.length; k++) {
                        if (k != 0) {
                            sb.append(",");
                        }
                        sb.append("?");
                    }
                    sb.append(")");
                case NOT_NULL:
                case NULL:
                    break;
                default:
                    break;
                }

                if (i.hasNext()) {
                    sb.append(" AND ");
                }

            }
            if (StringUtil.isNotEmpty(conditionResult.getQuery().getQuery())
                    && !conditionResult.getBindValues().isEmpty()) {
                sb.append(" AND ");
            }
            sb.append(conditionResult.getQuery().getQuery());

        }
        addOrder(conditionResult, sb);
        return sb;

    }

    protected Object[] createVariables(ConditionResult conditionResult) {
        List<BindValue> list = conditionResult.getBindValues();
        List<BindValue> actualList = new ArrayList<BindValue>(list);
        Query query = conditionResult.getQuery();
        if (query.getArgs().length != 0) {
            actualList.add(new BindValueImpl(query.getArgs()));
        }
        List<Object> result = new ArrayList<Object>();
        for (BindValue bindValue : actualList) {
            Object[] args = bindValue.getArgs();
            for (Object object : args) {
                result.add(object);
            }
        }
        return result.toArray();
    }

    protected Class<?>[] createVariablesTypes(Object[] bindVariables) {
        Class<?>[] bindVariablesTypes = new Class[bindVariables.length];
        for (int i = 0; i < bindVariables.length; i++) {
            bindVariablesTypes[i] = bindVariables[i].getClass();
        }
        return bindVariablesTypes;
    }

    private String addOrder(ConditionResult conditionResult, StringBuilder sb) {
        List<Order> orderList = conditionResult.getOrderList();
        Iterator<Order> i = orderList.iterator();
        if (i.hasNext()) {
            sb.append(" ");
            sb.append("ORDER BY ");
        }
        while (i.hasNext()) {

            Order order = i.next();
            Column column = order.getColumn();
            if (StringUtil.isEmpty(column.getAlias())) {
                sb.append(column.getName());
            } else {
                sb.append(column.getAlias());
                sb.append(".");
                sb.append(column.getName());
            }
            if (!order.isAsc()) {
                sb.append(" DESC ");
            }
            if (i.hasNext()) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

}
