package com.topie.core.hibernate;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;

import java.util.List;
import java.util.Map;

import com.topie.core.util.BeanUtils;
import com.topie.core.util.ReflectUtils;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SessionFactory;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import org.springframework.transaction.annotation.Transactional;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * 提供基础功能方法的hibernate基类.
 * 
 * @author Lingo
 */
public class HibernateGenericDao extends HibernateBasicDao {
    /** default constructor. */
    public HibernateGenericDao() {
    }

    /**
     * constructor.
     * 
     * @param sessionFactory
     *            SessionFactory
     */
    public HibernateGenericDao(SessionFactory sessionFactory) {
        super(sessionFactory);
    }

    // ============================================================================================
    // createQuery
    // ============================================================================================

    /**
     * 生成一个Query.
     * 
     * @param hql
     *            HQL语句
     * @param values
     *            参数
     * @return Query
     */
    public Query createQuery(String hql, Object... values) {
        Assert.hasText(hql, "hql cannot be null");

        Query query = this.getSession().createQuery(hql);

        for (int i = 0; i < values.length; i++) {
            query.setParameter(i, values[i]);
        }

        return query;
    }

    /**
     * create query by hql and map.
     * 
     * @param hql
     *            String
     * @param map
     *            Map
     * @return Query
     */
    public Query createQuery(String hql, Map<String, Object> map) {
        Assert.hasText(hql, "hql cannot be null");

        Query query = this.getSession().createQuery(hql);

        if (map != null) {
            query.setProperties(map);
        }

        return query;
    }

    // ============================================================================================
    // createCriteria
    // ============================================================================================
    /**
     * 根据entityClass生成对应类型的Criteria.
     * 
     * @param entityClass
     *            实体类型
     * @param criterions
     *            条件
     * @return Criteria
     */
    public Criteria createCriteria(Class entityClass, Criterion... criterions) {
        Criteria criteria = this.getSession().createCriteria(entityClass);

        for (Criterion c : criterions) {
            criteria.add(c);
        }

        return criteria;
    }

    /**
     * 根据entityClass，生成带排序的Criteria.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            类型
     * @param orderBy
     *            排序字段名
     * @param isAsc
     *            是否正序
     * @param criterions
     *            条件
     * @return Criteria
     */
    public <T> Criteria createCriteria(Class<T> entityClass, String orderBy,
            boolean isAsc, Criterion... criterions) {
        if (StringUtils.hasText(orderBy)) {
            Criteria criteria = createCriteria(entityClass, criterions);

            if (isAsc) {
                criteria.addOrder(Order.asc(orderBy));
            } else {
                criteria.addOrder(Order.desc(orderBy));
            }

            return criteria;
        } else {
            return createCriteria(entityClass, criterions);
        }
    }

    // ============================================================================================
    // find Criteria
    // ============================================================================================
    /**
     * find by critrions.
     * 
     * @param entityClass
     *            Class
     * @param criterions
     *            Criterion...
     * @param <T>
     *            generic
     * @return List
     */
    @Transactional(readOnly = true)
    public <T> List<T> find(Class<T> entityClass, Criterion... criterions) {
        return this.createCriteria(entityClass, criterions).list();
    }

    /**
     * find unique by critrions.
     * 
     * @param entityClass
     *            Class
     * @param criterions
     *            Criterion...
     * @param <T>
     *            generic
     * @return T
     */
    @Transactional(readOnly = true)
    public <T> T findUnique(Class<T> entityClass, Criterion... criterions) {
        return (T) createCriteria(entityClass, criterions).uniqueResult();
    }

    // ============================================================================================
    // find Query
    // ============================================================================================
    /**
     * find by hql and values.
     * 
     * @param hql
     *            String
     * @param values
     *            Object
     * @return List
     */
    @Transactional(readOnly = true)
    public List find(String hql, Object... values) {
        return this.createQuery(hql, values).list();
    }

    /**
     * find by hql and map.
     * 
     * @param hql
     *            String
     * @param map
     *            Map
     * @return List
     */
    @Transactional(readOnly = true)
    public List find(String hql, Map<String, Object> map) {
        return this.createQuery(hql, map).list();
    }

    /**
     * 查询唯一记录.
     * 
     * @param <T>
     *            实体类型
     * @param hql
     *            HQL字符串
     * @param values
     *            参数
     * @return 实例
     */
    @Transactional(readOnly = true)
    public <T> T findUnique(String hql, Object... values) {
        return (T) this.createQuery(hql, values).setMaxResults(1)
                .uniqueResult();
    }

    /**
     * 查询唯一记录.
     * 
     * @param <T>
     *            实体类型
     * @param hql
     *            HQL字符串
     * @param map
     *            Map
     * @return 实例
     */
    @Transactional(readOnly = true)
    public <T> T findUnique(String hql, Map<String, Object> map) {
        return (T) this.createQuery(hql, map).setMaxResults(1).uniqueResult();
    }

    // ============================================================================================
    // findBy
    // ============================================================================================
    /**
     * 根据name，value进行查询.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param name
     *            字段名
     * @param value
     *            参数值
     * @return 查询结果
     */
    @Transactional(readOnly = true)
    public <T> List<T> findBy(Class<T> entityClass, String name, Object value) {
        Assert.hasText(name, "property name cannot be null");

        return this.createCriteria(entityClass, Restrictions.eq(name, value))
                .list();
    }

    /**
     * 根据name,value进行模糊查询.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param name
     *            字段名
     * @param value
     *            用来做模糊查询的字段值
     * @return 查询结果
     */
    @Transactional(readOnly = true)
    public <T> List<T> findByLike(Class<T> entityClass, String name,
            Object value) {
        Assert.hasText(name, "property name cannot be null");

        return this.createCriteria(entityClass, Restrictions.like(name, value))
                .list();
    }

    /**
     * find by ids.
     * 
     * @param entityClass
     *            Class
     * @param ids
     *            List
     * @param <T>
     *            generic
     * @return List
     */
    @Transactional(readOnly = true)
    public <T> List<T> findByIds(Class<T> entityClass, List ids) {
        Assert.notEmpty(ids);

        String idName = this.getIdName(entityClass);
        Criterion criterion = Restrictions.in(idName, ids);

        return this.find(entityClass, criterion);
    }

    /**
     * 查询唯一记录.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param name
     *            字段名
     * @param value
     *            字段值
     * @return 实例
     */
    @Transactional(readOnly = true)
    public <T> T findUniqueBy(Class<T> entityClass, String name, Object value) {
        return (T) this
                .createCriteria(entityClass, Restrictions.eq(name, value))
                .setMaxResults(1).uniqueResult();
    }

    // ============================================================================================
    // isUnique
    // ============================================================================================
    /**
     * 判断对象某些属性的值在数据库中是否唯一.
     * 
     * @param entityClass
     *            实体类型
     * @param entity
     *            实体对象
     * @param uniquePropertyNames
     *            在POJO里不能重复的属性列表,以逗号分割 如"name,loginid,password"
     * @param <T>
     *            实体类泛型
     * @return 如果唯一返回true，否则返回false
     */
    @Transactional(readOnly = true)
    public <T> boolean isUnique(Class<T> entityClass, T entity,
            String uniquePropertyNames) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        Assert.hasText(uniquePropertyNames);

        Criteria criteria = createCriteria(entityClass).setProjection(
                Projections.rowCount());
        String[] nameList = uniquePropertyNames.split(",");

        // 循环加入唯一列
        for (String name : nameList) {
            String getterMethodName = ReflectUtils.getGetterMethodName(entity,
                    name);
            criteria.add(Restrictions.eq(name,
                    BeanUtils.invokeMethod(entity, getterMethodName)));
        }

        // 以下代码为了如果是update的情况,排除entity自身.
        String idName = getIdName(entityClass);

        // 取得entity的主键值
        Serializable id = getId(entityClass, entity);

        // 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
        if (id != null) {
            criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
        }

        Object result = criteria.uniqueResult();

        return HibernateUtils.getNumber(result) == 0;
    }

    // ============================================================================================
    // getCount
    // ============================================================================================
    /**
     * 获得总记录数.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @return 总数
     */
    @Transactional(readOnly = true)
    public <T> Integer getCount(Class<T> entityClass) {
        return this.getCount(this.createCriteria(entityClass));
    }

    /**
     * 获得总记录数.
     * 
     * @param criteria
     *            条件
     * @return 总数
     */
    @Transactional(readOnly = true)
    public Integer getCount(Criteria criteria) {
        Object result = criteria.setProjection(Projections.rowCount())
                .uniqueResult();

        return HibernateUtils.getNumber(result);
    }

    /**
     * 获得总记录数.
     * 
     * @param hql
     *            HQL字符串
     * @param values
     *            参数
     * @return 总数
     */
    @Transactional(readOnly = true)
    public Integer getCount(String hql, Object... values) {
        Object result = createQuery(hql, values).uniqueResult();

        return HibernateUtils.getNumber(result);
    }

    /**
     * 获得总记录数.
     * 
     * @param hql
     *            HQL字符串
     * @param map
     *            Map
     * @return 总数
     */
    @Transactional(readOnly = true)
    public Integer getCount(String hql, Map<String, Object> map) {
        Object result = createQuery(hql, map).uniqueResult();

        return HibernateUtils.getNumber(result);
    }

    // ============================================================================================
    // batchUpdate
    // ============================================================================================
    /**
     * batch update by hql and objects.
     * 
     * @param hql
     *            String
     * @param values
     *            Object
     * @return int
     */
    @Transactional
    public int batchUpdate(String hql, Object... values) {
        return this.createQuery(hql, values).executeUpdate();
    }

    /**
     * batch update by hql and map.
     * 
     * @param hql
     *            String
     * @param map
     *            Map
     * @return int
     */
    @Transactional
    public int batchUpdate(String hql, Map<String, Object> map) {
        return this.createQuery(hql, map).executeUpdate();
    }
}
