/*
 * 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.crud;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import woolpack.utils.CheckUtils;
import woolpack.utils.PropertyUtils;

/**
 * {@link ExpressionFactory}と{@link Executable}を使用する{@link QueryFactory}。
 * @author nakamura
 *
 */
public class QueryFactoryImpl implements QueryFactory {
	private final ExpressionFactory factory;
	private final Executable executable;
	private final int maxRecodes;
	private final String startPositionKey;
	private final String countKey;
	
	/**
	 * コンストラクタ。
	 * @param factory {@link Expression}のファクトリ。
	 * @param executable データベースにアクセスするクラス。
	 * @param maxRecode 取得する最大レコード数。
	 * @param startPositionKey {@link ResultSet}から取得する開始位置のキー。
	 * @param recodeCountKey {@link ResultSet}から取得する件数のキー。
	 * @throws NullPointerException 引数のいずれかが null の場合。
	 */
	public QueryFactoryImpl(
			final ExpressionFactory factory,
			final Executable executable,
			final int maxRecode,
			final String startPositionKey,
			final String recodeCountKey) {
		CheckUtils.checkNotNull(executable);
		CheckUtils.checkNotNull(factory);
		this.executable = executable;
		this.factory = factory;
		this.maxRecodes = maxRecode;
		this.startPositionKey = startPositionKey;
		this.countKey = recodeCountKey;
	}

	public Query newInstance(final String id, final Map<String, List<Object>> map0) {
		final Expression expression = factory.newInstance(id, map0);
		return new Query() {
			private List<String> resultColumnNameList;
			public List<Map<String, Object>> select(final Map<String, List<Object>> map1) {
				final List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
				executable.execute(expression, new Fetchable() {
					public void fetch(final Statement statement)  {
						final int startPosition;
						if (startPositionKey == null) {
							startPosition = 1;
						} else {
							final List<Object> value = map1.get(startPositionKey);
							if (value == null) {
								startPosition = 1;
							} else {
								startPosition = Integer.valueOf(value.get(0).toString()) + 1;
							}
						}
						final int endPosition;
						if (countKey == null) {
							endPosition = maxRecodes;
						} else {
							final List<Object> value = map1.get(countKey);
							if (value == null) {
								endPosition = maxRecodes;
							} else {
								final int tmpCount = Integer.valueOf(value.get(0).toString());
								endPosition = Math.min(maxRecodes, startPosition + tmpCount - 1);
							}
						}
						try {
							final ResultSet rs = statement.getResultSet();
							try {
								if (resultColumnNameList == null) {
									final ResultSetMetaData md = rs.getMetaData();
									final int size = md.getColumnCount();
									final List<String> list = new ArrayList<String>(size);
									for (int i = 0; i < size; i++) {
										list.add(PropertyUtils.toJavaName(md.getColumnName(i + 1)));
									}
									resultColumnNameList = list;
								}
								int count = 0;
								while (rs.next()) {
									count++;
									if (count < startPosition) {
										continue;
									}
									final Map<String, Object> bean = new HashMap<String, Object>();
									for (int i = 0; i < resultColumnNameList.size(); i++) {
										bean.put(resultColumnNameList.get(i), rs.getObject(i + 1));
									}
									list.add(bean);
									if (endPosition <= count) {
										break;
									}
								}
							} finally {
								rs.close();
							}
						} catch (final SQLException e) {
							throw new IllegalStateException(e);
						}
					}
				}, map1);
				return list;
			}
		};
	}
}
