package project.jndi.factory;

import java.util.Collections;
import java.util.Hashtable;
import java.util.Objects;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import javax.sql.XADataSource;

import org.seasar.extension.dbcp.impl.ConnectionPoolImpl;
import org.seasar.extension.dbcp.impl.DataSourceImpl;
import org.seasar.extension.dbcp.impl.XADataSourceImpl;

import common.db.JdbcSource;
import common.db.jdbc.JdbcSession;
import common.db.jdbc.provider.S2DataSourceProvider;
import core.config.Factory;
import core.util.BooleanUtil;
import core.util.NumberUtil;

/**
 * S2DBCPファクトリ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class S2DbCpDataSourceFactory implements ObjectFactory {

	/**
	 * @see ObjectFactory#getObjectInstance(java.lang.Object,
	 * javax.naming.Name, javax.naming.Context, java.util.Hashtable)
	 */
	@Override
	public Object getObjectInstance(final Object obj, final Name name,
			final Context ctx, final Hashtable<?, ?> env) throws Exception {
		return getDataSource(obj, name.toString());
	}

	/**
	 * DataSource取得
	 * @param obj Object
	 * @param lookup Lookup名
	 * @return DataSource
	 * @throws ReflectiveOperationException 例外
	 */
	private DataSource getDataSource(
			final Object obj, final String lookup) throws ReflectiveOperationException {
		// ドライバ取得
		final var xa = getXaInstance(Reference.class.cast(obj), toResourceName(lookup));

		// コネクションプール作成
		final var cp = new ConnectionPoolImpl();

		// パラメタ情報設定
		setParameters(Reference.class.cast(obj), cp, xa);

		// トランザクションマネージャ設定
		cp.setTransactionManager(S2DataSourceProvider.getTransactionManager());
		cp.setXADataSource(xa);

		return new DataSourceImpl(cp);
	}

	/**
	 * XAインスタンス取得
	 * @param ref リファレンス
	 * @param name リソース名
	 * @return XAインスタンス
	 * @throws ReflectiveOperationException 例外
	 */
	private XADataSource getXaInstance(
			final Reference ref, final String name) throws ReflectiveOperationException {
		// ドライバ取得
		final var driver = getDriverInstance(ref);
		if (XADataSource.class.isInstance(driver)) {
			return XADataSource.class.cast(driver);
		}

		final var jc = Factory.create(JdbcSession.class);
		final var ret = new XADataSourceImpl();
		ret.setUser(jc.getUser(name));
		ret.setPassword(jc.getPassword(name));
		ret.setURL(jc.getUrl(name));
		return ret;
	}

	/**
	 * 接続名変換
	 * @param name 名称
	 * @return 接続名
	 */
	private String toResourceName(final String name) {
		if (JdbcSource.hasResource(name)) {
			return name;
		}
		return null;
	}

	/**
	 * ドライバインスタンス取得
	 *
	 * @param ref リファレンス
	 * @return ドライバインスタンス
	 * @throws ReflectiveOperationException 例外
	 */
	private Object getDriverInstance(final Reference ref) throws ReflectiveOperationException {
		final var clazz = "DriverClassName";

		for (final var addr : Collections.list(ref.getAll())) {
			if (clazz.equalsIgnoreCase(addr.getType())) {
				final var cl = Objects.requireNonNullElseGet(
					Thread.currentThread().getContextClassLoader(),
					() -> this.getClass().getClassLoader());
				final var content = Objects.toString(addr.getContent(), null);
				if (content != null) {
					return cl.loadClass(content).getDeclaredConstructor().newInstance();
				}
			}
		}

		return null;
	}

	/**
	 * パラメタ情報設定
	 *
	 * @param ref リファレンス
	 * @param objs 設定対象オブジェクト
	 * @throws ReflectiveOperationException 例外
	 */
	private void setParameters(
			final Reference ref, final Object... objs) throws ReflectiveOperationException {
		for (final var addr : Collections.list(ref.getAll())) {
			for (final var obj : objs) {
				final var content = Objects.toString(addr.getContent(), null);
				if (content != null) {
					setParameter(obj, addr.getType(), content);
				}
			}
		}
	}

	/**
	 * 値設定処理
	 *
	 * @param obj 対象オブジェクト
	 * @param type 対象項目
	 * @param cont 値
	 * @throws ReflectiveOperationException 例外
	 */
	private void setParameter(final Object obj, final String type,
			final String cont) throws ReflectiveOperationException {
		final var name = "set" + type;
		for (final var mt : obj.getClass().getMethods()) {
			if (name.equalsIgnoreCase(mt.getName())) {
				final var prms = mt.getParameterTypes();
				if (prms.length == 1) {
					if (String.class.equals(prms[0])) {
						mt.invoke(obj, cont);
					} else if (int.class.equals(prms[0]) || Integer.class.equals(prms[0])) {
						mt.invoke(obj, NumberUtil.toInteger(cont));
					} else if (boolean.class.equals(prms[0]) || Boolean.class.equals(prms[0])) {
						mt.invoke(obj, BooleanUtil.toBoolean(cont));
					}
					break;
				}
			}
		}
	}
}
