/*******************************************************************************
 * Copyright 2Fi Business Solutions Ltd.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.core.dao;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import com.ffii.core.BaseEntity;
import com.ffii.core.User;
import com.ffii.core.utils.SecurityUtils;

import org.hibernate.query.Query;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;

/**
 * Implementation of the {@link IHibernateDao} interface.
 *
 * @author Patrick
 */
public abstract class HibernateDao<T extends BaseEntity<PK>, PK extends Serializable> extends HibernateDaoSupport
		implements IHibernateDao<T, PK> {

	private final Class<T> type;

	public HibernateDao(final Class<T> type) {
		this.type = type;
	}

	/**
	 * Automatically set the {@code createdBy}, {@code created}, {@code modifiedBy},
	 * and {@code modified} fields of the object instance.
	 *
	 * @param instance the object instance
	 * @param isNew    {@code true} if the instance is new, and need to set the
	 *                 {@code createdBy} and {@code created} fields
	 */
	private void autoSetCreateAndLastModifyFields(final T instance, boolean isNew) {
		// try to get the authenticated User ID from session (not available in Mapp)
		User user = SecurityUtils.getUser();
		Integer userId = null;
		Integer sysGroupId = null;
		if (user != null){
			userId = user.getId();
			sysGroupId = user.getSysGroupId();
		}

		Date now = new Date();

		if (isNew) {
			if (instance.getCreatedBy() == null && userId != null)
				instance.setCreatedBy(userId);
			if (userId != null)
				instance.setModifiedBy(userId);
			if (instance.getCreated() == null)
				instance.setCreated(now);
			if(instance.getSysGroupId() == null)
				instance.setSysGroupId(sysGroupId);
			instance.setModified(now);
		} else {
			if (userId != null)
				instance.setModifiedBy(userId);
			instance.setModified(now);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public PK save(final T instance) {
		autoSetCreateAndLastModifyFields(instance, true);
		return (PK) getHibernateTemplate().save(instance);
	}

	@SuppressWarnings("unchecked")
	@Override
	public PK saveAs(final T instance) {
		return (PK) getHibernateTemplate().save(instance);
	}

	@Override
	public void update(final T instance) {
		autoSetCreateAndLastModifyFields(instance, false);
		getHibernateTemplate().update(instance);
	}

	@Override
	public PK saveOrUpdate(final T instance) {
		Serializable id = instance.getId();
		if (id instanceof Long) {
			if (((Long) id).longValue() < 0L)
				id = null;
		} else if (id instanceof Integer) {
			if (((Integer) id).intValue() < 0)
				id = null;
		}
		if (id == null)
			return save(instance);
		else {
			update(instance);
			return (PK) instance.getId();
		}
	}

	@Override
	public boolean delete(final T instance) {
		try {
			getHibernateTemplate().delete(instance);
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public void deleteAll(List<T> instances) {
		getHibernateTemplate().deleteAll(instances);
	}

	@Override
	public void flush() {
		getHibernateTemplate().flush();
	}

	@Override
	public T find(final PK id) {
		return getHibernateTemplate().get(type, id);
	}

	@Override
	@SuppressWarnings("unchecked")
	public T findByQuery(final String queryString, final Serializable... args) {
		return getHibernateTemplate().execute(session -> {
			Query<T> query = session.createQuery(queryString);
			for (int i = 0; i < args.length; i++) {
				query.setParameter(i, args[i]);
			}
			query.setMaxResults(1);
			return query.uniqueResult();
		});
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> listByQuery(final String queryString, final Serializable... args) {
		return getHibernateTemplate().execute(session -> {
			Query<T> query = session.createQuery(queryString);
			for (int i = 0; i < args.length; i++) {
				query.setParameter(i, args[i]);
			}
			return query.list();
		});
	}

}
