package project.svc.generic;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

import common.db.JdbcSource;
import common.sql.QueryUtil;
import common.sql.SelectQuery;
import common.sql.Selector;
import core.config.Factory;
import online.model.UniModel;
import online.model.UniModelImpl;
import project.base.QueryAbstract;

/**
 * クエリサービス
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class QueryService extends QueryAbstract implements SelectQuery {

	/** クエリファイル */
	private String query = null;
	/** 汎用モデル（条件） */
	private UniModel param = null;
	/** 汎用モデル（結果） */
	private final UniModel model = new UniModelImpl();

	/**
	 * クエリファイル設定
	 * @param val クエリファイル
	 */
	public void setQueryFile(final String val) {
		this.query = val;
	}

	/**
	 * 汎用モデル設定
	 * @param val 汎用モデル
	 */
	public void setUniModel(final UniModel val) {
		this.param = new UniModelImpl(val);
	}

	/**
	 * 汎用モデル取得
	 * @return 汎用モデル
	 */
	public UniModel getResultModel() {
		return this.model;
	}

	/**
	 * @see common.sql.SelectQuery#callback(java.sql.ResultSet)
	 */
	@Override
	public boolean callback(final ResultSet rs) throws SQLException {
		this.model.putAll(initModel(rs));

		var ret = false;
		final var rsmd = rs.getMetaData();
		while (rs.next()) {
			ret = true;
			for (var i = 1; i <= rsmd.getColumnCount(); i++) {
				addValue(this.model, rs, i);
			}
		}
		return ret;
	}

	/**
	 * 汎用モデル初期化
	 *
	 * @param rs 結果セット
	 * @return 汎用モデル
	 * @throws SQLException SQL例外
	 */
	private UniModel initModel(final ResultSet rs) throws SQLException {
		final var ret = new UniModelImpl();

		final var rsmd = rs.getMetaData();
		for (var i = 1; i <= rsmd.getColumnCount(); i++) {
			ret.noValue(toCamelCase(rsmd.getColumnLabel(i)));
		}

		return ret;
	}

	/**
	 * 値追加
	 * @param um 汎用モデル
	 * @param rs 結果セット
	 * @param i カラム位置
	 * @throws SQLException SQL例外
	 */
	private void addValue(final UniModel um,
			final ResultSet rs, final int i) throws SQLException {
		var added = false;
		final var rsmd = rs.getMetaData();
		final var key = toCamelCase(rsmd.getColumnLabel(i));
		switch (rsmd.getColumnType(i)) {
			case java.sql.Types.DATE:
				um.addValue(key, rs.getDate(i));
				added = true;
				break;
			case java.sql.Types.TIME:
				um.addValue(key, rs.getTime(i));
				added = true;
				break;
			case java.sql.Types.TIMESTAMP:
				um.addValue(key, rs.getTimestamp(i));
				added = true;
				break;
			case java.sql.Types.CHAR:
			case java.sql.Types.VARCHAR:
				um.addValue(key, rs.getString(i));
				added = true;
				break;
			case java.sql.Types.NUMERIC:
			case java.sql.Types.DECIMAL:
				um.addValue(key, rs.getBigDecimal(i));
				added = true;
				break;
			default:
				break;
		}

		if (added) {
			return;
		}

		switch (rsmd.getColumnType(i)) {
			case java.sql.Types.SMALLINT:
			case java.sql.Types.INTEGER:
				if (rs.getObject(i) == null) {
					um.addValue(key, Integer.class.cast(null));
				} else {
					um.addValue(key, rs.getInt(i));
				}
				break;
			case java.sql.Types.BIGINT:
				if (rs.getObject(i) == null) {
					um.addValue(key, Long.class.cast(null));
				} else {
					um.addValue(key, rs.getLong(i));
				}
				break;
			case java.sql.Types.DOUBLE:
			case java.sql.Types.FLOAT:
				if (rs.getObject(i) == null) {
					um.addValue(key, Double.class.cast(null));
				} else {
					um.addValue(key, rs.getDouble(i));
				}
				break;
			default:
				um.addValue(key, rs.getString(i));
				break;
		}
	}

	/**
	 * @see common.sql.SelectQuery#makeParam()
	 */
	@Override
	public Map<String, Object> makeParam() {
		return toParamMap(this.param.toMap());
	}

	/**
	 * @see common.sql.SelectQuery#makeQuery()
	 */
	@Override
	public String makeQuery() {
		return QueryUtil.getSqlFromFile(this.query);
	}

	/**
	 * 検索処理
	 * @return レコードが存在した場合 true を返す。
	 */
	public boolean search() {
		try (var conn = JdbcSource.getConnection(getSchema())) {
			final var sc = Factory.create(Selector.class);
			sc.setConnection(conn);
			sc.setFetchSize(getFetchSize());
			return sc.search(this);
		}
	}

	/**
	 * 存在確認処理
	 * @return レコードが存在した場合 true を返す。
	 */
	public boolean exist() {
		try (var conn = JdbcSource.getConnection(getSchema())) {
			final var sc = Factory.create(Selector.class);
			sc.setConnection(conn);
			sc.setFetchSize(1);
			sc.setMaxRows(1);
			return sc.search(this);
		}
	}
}
