package common.db.dao.hibernate;

import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import org.hibernate.HibernateException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;

import common.db.dao.Dao;
import common.db.dao.DaoSession;
import common.transaction.XATransaction;
import core.config.Factory;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;

/**
 * DAOセション
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class DaoSessionImpl implements DaoSession {

	/** インスタンス */
	private static final AtomicReference<DaoSessionImpl> INSTANCE = new AtomicReference<>();

	/** 設定保存マップ */
	private final ConcurrentMap<String, Config> config = new ConcurrentHashMap<>();

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

	/**
	 * ファクトリメソッド
	 *
	 * @return インスタンス
	 */
	public static DaoSessionImpl getInstance() {
		if (INSTANCE.get() == null) {
			INSTANCE.compareAndSet(null, new DaoSessionImpl());
		}
		return INSTANCE.get();
	}

	/**
	 * DAO作成
	 *
	 * @param cname 設定名
	 * @return DAOオブジェクト
	 */
	@Override
	public Dao getDao(final String cname) {
		try {
			BaseDao bd = null;
			final Config cfg = getConfig(cname);
			final Properties prop = cfg.getConfiguration().getProperties();
			if (prop != null) {
				if (prop.getProperty(AvailableSettings.JTA_PLATFORM) != null) {
					final XATransaction xa = Factory.create(XATransaction.class);
					if (xa.isInTransaction()) {
						bd = new JtaDao(cfg);
					} else {
						bd = new MainDao(cfg);
					}
					bd.setNoWait(isNoWaitAccess(prop));
				} else {
					bd = new MainDao(cfg);
				}
			}
			return bd;
		} catch (final HibernateException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * 設定取得
	 *
	 * @param cname 設定名
	 * @return 設定
	 */
	private Config getConfig(final String cname) {
		final String key = toConfigName(cname);
		Config conf = this.config.get(key);
		if (conf == null) {
			final Configuration cfg = new Configuration().configure(key);
			conf = new Config(cfg, cfg.buildSessionFactory());
			if (this.config.putIfAbsent(key, conf) != null) {
				conf = this.config.get(key);
			}
		}
		return conf;
	}

	/**
	 * 設定ファイル名化
	 * @param cname 設定名
	 * @return 設定ファイル名
	 */
	private String toConfigName(final String cname) {
		String ret = cname;
		if (ret == null) {
			return "/hibernate.cfg.xml";
		}
		if (!ret.startsWith("/")) {
			ret = "/" + ret;
		}
		if (!ret.endsWith(".cfg.xml")) {
			ret = ret + ".cfg.xml";
		}
		return ret;
	}

	/**
	 * NO WAIT アクセス判断
	 * @param prop Properties
	 * @return NO WAIT アクセスの場合 true を返す。
	 */
	private boolean isNoWaitAccess(final Properties prop) {
		return Boolean.parseBoolean(prop.getProperty("no_wait_access"));
	}
}
