package common.db.dao.hibernate;

import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.resource.transaction.spi.TransactionStatus;

import common.db.dao.DaoConstraintException;
import common.db.dao.DaoLockException;
import common.db.dao.DaoPropertyException;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;

/**
 * バッチ用DAO
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class MainDao extends BaseDao {

	/** シーケンスクエリキャッシュ */
	private final ConcurrentMap<String, SequenceWork> sequence = new ConcurrentHashMap<>();

	/** セションオブジェクト */
	private Session session = null;

	/**
	 * コンストラクタ
	 *
	 * @param config コンフィグ
	 */
	protected MainDao(final Config config) {
		super(config);
	}

	/**
	 * コミット処理
	 */
	@Override
	public void commit() {
		if (this.session != null && TransactionStatus.ACTIVE.equals(
				this.session.getTransaction().getStatus())) {
			flush();
			try {
				this.session.getTransaction().commit();
				this.session.clear();
			} catch (final HibernateException ex) {
				ThrowableUtil.error(ex);
				throw new PhysicalException(ex);
			} finally {
				clearSequence();
			}
		}
	}

	/**
	 * ロールバック処理
	 * @return 処理結果
	 */
	@Override
	public boolean rollback() {
		if (this.session != null && TransactionStatus.ACTIVE.equals(
				this.session.getTransaction().getStatus())) {
			try {
				this.session.getTransaction().rollback();
				this.session.clear();
				return true;
			} catch (final HibernateException ex) {
				ThrowableUtil.error(ex);
			} finally {
				clearSequence();
			}
		}
		return false;
	}

	/**
	 * フラッシュ処理
	 *
	 * @return フラッシュされた場合 true を返す。対象なしの場合 false を返す。
	 */
	@Override
	public boolean flush() {
		try {
			if (this.session != null) {
				this.session.flush();
				this.session.clear();
				return true;
			}
		} catch (final LockAcquisitionException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoLockException(ex, isNoWait());
		} catch (final ConstraintViolationException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoConstraintException(ex, isNoWait());
		} catch (final PropertyValueException ex) {
			// not null例外
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoPropertyException(ex);
		} catch (final JDBCException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		} catch (final HibernateException ex) {
			if (!isUpdateFailed(ex)) {
				ThrowableUtil.error(ex);
				throw new PhysicalException(ex);
			}
			LogManager.getLogger().info(ex.getMessage());
		}
		return false;
	}

	/**
	 * セションクローズ
	 *
	 */
	@Override
	public void close() {
		// コミットまたはロールバック済みなら処理されない。
		rollback();

		if (this.session != null) {
			try {
				this.session.close();
			} catch (final HibernateException e) {
				LogManager.getLogger().warn(e.getMessage(), e);
			} finally {
				this.session = null;
				clearSequence();
			}
		}
	}

	/**
	 * @see common.db.dao.Dao#sequence(java.lang.String)
	 */
	@Override
	public long sequence(final String name) {
		final Supplier<SequenceWork> supplier = () -> {
			final var w = new SequenceWork(getSequenceNextValString(name));
			final var work = this.sequence.putIfAbsent(name, w);
			if (work != null) {
				w.close();
				return work;
			}
			return w;
		};

		final var sw = Objects.requireNonNullElseGet(this.sequence.get(name), supplier);
		beginTransaction();
		getSession().doWork(sw);
		return sw.getSequence();
	}

	/**
	 * シーケンスマップクリア
	 */
	private void clearSequence() {
		for (final var me : this.sequence.entrySet()) {
			me.getValue().close();
		}
		this.sequence.clear();
	}

	/**
	 * セッション取得
	 *
	 * @return セションオブジェクト
	 */
	@Override
	protected Session getSession() {
		if (this.session == null) {
			this.session = getSessionFactory().openSession();
		}

		return this.session;
	}

	/**
	 * トランザクション開始
	 *
	 * @return トランザクション
	 */
	@Override
	protected Transaction beginTransaction() {
		final var tran = getSession().getTransaction();
		if (!TransactionStatus.ACTIVE.equals(tran.getStatus())) {
			tran.begin();
		}
		return tran;
	}

	/**
	 * フラッシュ処理
	 *
	 */
	@Override
	protected void flushSession(final Serializable item) {
		return;
	}
}
