package common.db.jdbc.provider;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.TransactionManager;

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

import common.db.jdbc.JdbcDataSourceProvider;
import common.db.jdbc.JdbcSession;
import core.config.Env;
import core.config.Factory;

/**
 * DataSourceProvider
 * @author Tadashi Nakayama
 */
public final class S2DataSourceProvider implements JdbcDataSourceProvider {

	/** トランザクションマネージャ */
	private static final TransactionManager TM = new TransactionManagerImpl();

	/** 自身保持用 */
	private static final AtomicReference<S2DataSourceProvider> INSTANCE = new AtomicReference<>();

	/** DataSource保持マップ */
	private final ConcurrentMap<String, DataSource> datasource = new ConcurrentHashMap<>();

	/**
	 * コンストラクタ
	 */
	private S2DataSourceProvider() {
		if (INSTANCE.get() != null) {
			throw new AssertionError();
		}
	}

	/**
	 * インスタンス取得
	 * @return インスタンス
	 */
	public static S2DataSourceProvider getInstance() {
		if (INSTANCE.get() == null) {
			INSTANCE.compareAndSet(null, new S2DataSourceProvider());
		}
		return INSTANCE.get();
	}

	/**
	 * @see common.db.jdbc.JdbcDataSourceProvider#getDataSource(java.lang.String)
	 */
	@Override
	public DataSource getDataSource(final String name) {
		final Supplier<DataSource> supplier = () -> {
			// データソース作成
			final DataSource src = createDataSource(name);
			final DataSource ds = this.datasource.putIfAbsent(name, src);
			return (ds != null) ? ds : src;
		};
		return Objects.requireNonNullElseGet(this.datasource.get(name), supplier);
	}

	/**
	 * XAインスタンス取得
	 * @param name リソース名
	 * @return XAインスタンス
	 */
	private XADataSource getXaInstance(final String name) {
		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;
	}

	/**
	 * DataSource取得
	 * @param name 接続名
	 * @return DataSource
	 */
	private DataSource createDataSource(final String name) {

		// コネクションプール作成
		final var cp = new ConnectionPoolImpl();
		cp.setTransactionManager(TM);
		cp.setXADataSource(getXaInstance(name));
		cp.setTimeout(Env.getEnv(name + ".Timeout", 600));
		cp.setMaxPoolSize(Env.getEnv(name + ".MaxPoolSize", 10));
		cp.setMinPoolSize(Env.getEnv(name + ".MinPoolSize", 0));
		cp.setMaxWait(Env.getEnv(name + ".MaxWait", -1));

		return new DataSourceImpl(cp);
	}

	/**
	 * 実装トランザクションマネージャ取得
	 *
	 * @return 実装トランザクションマネージャ
	 */
	public static TransactionManager getTransactionManager() {
		return TM;
	}
}
