package project.base;

import java.io.Serializable;
import java.util.Objects;

import org.apache.logging.log4j.LogManager;

import common.db.JdbcSource;
import common.db.dao.DaoConstraintException;
import common.db.dao.DaoLockException;
import common.db.dao.DaoUtil;
import core.config.Factory;

/**
 * 更新サービス作成用親
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public abstract class UpdateAbstract extends QueryAbstract {

	/** バージョン */
	private String versionName = "Version";
	/** 作成者ID */
	private String creatorName = "InsertId";
	/** 作成日時 */
	private String createdName = "InsertDatetime";
	/** 更新者ID */
	private String updaterName = "UpdateId";
	/** 更新日時 */
	private String updatedName = "UpdateDatetime";

	/**
	 * バージョン名設定
	 * @param val バージョン名
	 */
	protected void setVersionName(final String val) {
		this.versionName = val;
	}

	/**
	 * 作成者ID名設定
	 * @param val 作成者ID名
	 */
	protected void setCreatorName(final String val) {
		this.creatorName = val;
	}

	/**
	 * 作成日時名設定
	 * @param val 作成日時
	 */
	protected void setCreatedName(final String val) {
		this.createdName = val;
	}

	/**
	 * 更新者ID名設定
	 * @param val 更新者ID
	 */
	protected void setUpdaterName(final String val) {
		this.updaterName = val;
	}

	/**
	 * 更新日時名設定
	 * @param val 更新日時名
	 */
	protected void setUpdatedName(final String val) {
		this.updatedName = val;
	}

	/**
	 * シーケンス取得
	 * @param name シーケンス名
	 * @return シーケンス
	 */
	protected long getSequence(final String name) {
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.sequence(name);
		}
	}

	/**
	 * 排他検索処理
	 * @param <T> ジェネリックス
	 *
	 * @param cls クラス
	 * @param id ID
	 * @return レコードが不在の場合 null を返す。
	 */
	protected <T> T find(final Class<T> cls, final Serializable id) {
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.findById(cls, id);
		}
	}

	/**
	 * 排他検索処理
	 * @param <T> ジェネリックス
	 *
	 * @param cls クラス
	 * @param id ID
	 * @return レコードが不在の場合 null を返す。
	 */
	protected <T> T findWithLock(final Class<T> cls, final Serializable id) {
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.findByIdWithLock(cls, id);
		} catch (final DaoLockException ex) {
			LogManager.getLogger().warn(ex.getMessage());
			return null;
		}
	}

	/**
	 * 作成処理
	 *
	 * @param obj 保存オブジェクト
	 * @return 作成された場合 true を返す。
	 */
	protected boolean insert(final Serializable obj) {
		setVersion(obj, 1);
		setUpdateInfo(obj);
		setCreateInfo(obj);
		try (var dao = JdbcSource.getDao(getSchema())) {
			dao.insert(obj);
			return true;
		} catch (final DaoConstraintException ex) {
			LogManager.getLogger().warn(ex.getMessage());
			return false;
		}
	}

	/**
	 * 更新処理
	 *
	 * @param obj 更新オブジェクト
	 * @return 他で更新されていた場合 false を返す。
	 */
	protected boolean update(final Serializable obj) {
		increment(obj);
		setUpdateInfo(obj);
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.update(obj);
		} catch (final DaoConstraintException ex) {
			if (!ex.isNoWait()) {
				throw ex;
			}
			LogManager.getLogger().warn(ex.getMessage());
			return false;
		}
	}

	/**
	 * 物理削除処理
	 *
	 * @param obj 削除オブジェクト
	 * @return 他で更新されていた場合 false を返す。
	 */
	protected boolean delete(final Serializable obj) {
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.delete(obj);
		}
	}

	/**
	 * 論理削除処理
	 *
	 * @param obj 削除オブジェクト
	 * @return 他で更新されていた場合 false を返す。
	 */
	protected boolean invisible(final Serializable obj) {
		setVersion(obj, 0);
		setUpdateInfo(obj);
		try (var dao = JdbcSource.getDao(getSchema())) {
			return dao.update(obj);
		} catch (final DaoConstraintException ex) {
			if (!ex.isNoWait()) {
				throw ex;
			}
			LogManager.getLogger().warn(ex.getMessage());
			return false;
		}
	}

	/**
	 * カウントアップ
	 * @param obj 更新オブジェクト
	 */
	protected void increment(final Serializable obj) {
		final Number num = DaoUtil.getValue(obj, this.versionName);
		if (num.intValue() == 999999) {
			setVersion(obj, 1);
		} else {
			setVersion(obj, num.intValue() + 1);
		}
	}

	/**
	 * 更新可能確認
	 * @param obj 更新オブジェクト
	 * @param ver バージョン
	 * @return 更新可能の場合 true を返す。
	 */
	protected boolean isUpdatable(final Serializable obj, final Number ver) {
		if (obj != null) {
			final Number val = DaoUtil.getValue(obj, this.versionName);
			if (val == null && ver == null) {
				return true;
			} else if (val != null && ver != null) {
				final int v = val.intValue();
				return ver.intValue() == v;
			}
		}
		return false;
	}

	/**
	 * 作成情報設定
	 *
	 * @param obj オブジェクト
	 */
	protected void setCreateInfo(final Object obj) {
		var uid = getUid();
		if (uid == null) {
			uid = this.getClass().getSimpleName();
		}
		setObject(obj, this.creatorName, uid);
		setObject(obj, this.createdName, getDateTime());
	}

	/**
	 * 更新情報設定
	 *
	 * @param obj オブジェクト
	 */
	protected void setUpdateInfo(final Object obj) {
		final var uid = Objects.requireNonNullElseGet(
			getUid(), () -> this.getClass().getSimpleName());
		setObject(obj, this.updaterName, uid);
		setObject(obj, this.updatedName, getDateTime());
	}

	/**
	 * バージョン設定
	 * @param obj オブジェクト
	 * @param val バージョン番号
	 */
	protected void setVersion(final Object obj, final int val) {
		setObject(obj, this.versionName, val);
	}

	/**
	 * 値設定
	 *
	 * @param obj オブジェクト
	 * @param m 項目名
	 * @param val 値
	 */
	protected static void setObject(final Object obj, final String m, final Object val) {
		final var cls = obj.getClass();
		final var get = Factory.getMethod(cls, "get" + m);
		if (get != null) {
			final var set = Factory.getMethod(cls, "set" + m, get.getReturnType());
			Factory.invoke(obj, set, val);
		}
	}
}
