package org.ccpit.base.dao;

import org.ccpit.base.controller.Page;
import org.ccpit.base.controller.PageRequest;
import org.ccpit.base.utils.HtmlEscapeUtil;
import org.ccpit.base.utils.SqlEscapeUtil;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate4.HibernateTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Repository
public class BaseDao<T> implements IDao<T> {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    /**
     * the Entity class
     */
    private Class entityClass;

    /**
     * Save an entity
     *
     * @param t
     */
    @Override
    public boolean save(T t) {
        new HtmlEscapeUtil<T>().escape(t);
        new SqlEscapeUtil<T>().escape(t);
        boolean success;
        try {
            hibernateTemplate.saveOrUpdate(t);
            success = true;
        } catch (Exception e) {
            success = false;
            throw new RuntimeException("save failed", e);
        }
        return success;
    }

    public boolean update(T entity) {
        try {
            new HtmlEscapeUtil<T>().escape(entity);
            new SqlEscapeUtil<T>().escape(entity);
            hibernateTemplate.merge(entity);
            return true;
        } catch (Exception e) {
            throw new RuntimeException("orm-entity-005：更新实体失败", e);
        }
    }

    public int update(String[] propertyNames,
                      Object[] propertyValues, String whereFilter) {
        StringBuffer hql = new StringBuffer("update " + getEntityClass().getName() + " set ");

        try {
            if ((propertyNames.length != propertyValues.length)
                    && (propertyNames.length > 0)) {
                throw new Exception("属性名及值个数不相等");
            }

            for (int i = 0; i < propertyNames.length - 1; i++) {
                hql.append(propertyNames[i]);
                hql.append(" = ? , ");
            }

            hql.append(propertyNames[(propertyNames.length - 1)]);
            hql.append(" = ? ");

            if ((whereFilter != null) && (!whereFilter.equals(""))) {
                hql.append(" where " + whereFilter + " ");
            }

            return hibernateTemplate.bulkUpdate(hql.toString(),
                    propertyValues);
        } catch (Exception ex) {
            throw new RuntimeException("orm-entity-006：删除记录失败，hql为："
                    + hql.toString(), ex);
        }
    }

    /**
     * Delete an entity
     *
     * @param t
     */
    @Override
    public boolean delete(T t) {
        boolean success;
        try {
            hibernateTemplate.delete(t);
            success = true;
        } catch (Exception e) {
            success = false;
            throw new RuntimeException("save failed", e);
        }
        return success;
    }

    /**
     * Delete an entity
     *
     * @param t
     */
    @Override
    public boolean deleteById(long id) {
        boolean success = false;
        try {
            T t = queryById(id);
            if (t != null) {
                hibernateTemplate.delete(t);
                success = true;
            }
        } catch (Exception e) {
            success = false;
            throw new RuntimeException("save failed", e);
        }
        return success;
    }

    /**
     * Save all the entity
     *
     * @param ts
     */
    @Override
    public boolean saveAll(List<T> ts) {
        if (ts == null)
            return false;
        boolean success;
        try {
            for (int i = 0; i < ts.size(); i++) {
                hibernateTemplate.saveOrUpdate(ts.get(i));
            }
            success = true;
        } catch (Exception e) {
            success = false;
            throw new RuntimeException("save failed", e);
        }
        return success;
    }

    /**
     * delete the entity list
     *
     * @param ts
     */
    @Override
    public boolean deleteAll(List<T> ts) {
        if (ts == null)
            return false;
        boolean success;
        try {
            for (int i = 0; i < ts.size(); i++) {
                hibernateTemplate.delete(ts.get(i));
            }
            success = true;
        } catch (Exception e) {
            success = false;
            throw new RuntimeException("save failed", e);
        }
        return success;

    }

    /**
     * Query the entity by ID, return unique entity or null.
     *
     * @param id
     * @return
     */
    public T queryById(Object id) {
        List<T> list = queryByEqualField("id", id);
        if (list != null && !list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    /**
     * Query the entity by one field
     *
     * @param fieldName the field name
     * @param value     the field value
     * @return
     */
    public List<T> queryByEqualField(String fieldName, Object value) {
        return (List<T>) hibernateTemplate.find("from " + getEntityClass().getName() + " where " + fieldName + " = ?", value);
    }

    /**
     * query
     *
     * @param hql
     * @param params
     * @return
     */
    @Override
    public List<T> query(final String hql, final Object... params) {
        return (List<T>) hibernateTemplate.find(hql, params);
    }

    @Override
    public List<T> queryAll() {
        return (List<T>) hibernateTemplate.find("from " + getEntityClass().getName());
    }

    /**
     * Get the hibernateTemplete
     *
     * @return
     */
    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }


    /**
     * get Current session
     *
     * @return session
     */
    public Session currentSession() {
        return hibernateTemplate.getSessionFactory().getCurrentSession();
    }

    /**
     * Get the entity Class
     *
     * @return
     */
    @Override
    public Class getEntityClass() {
        if (entityClass == null)
            getGenericClass();
        return entityClass;
    }

    /**
     * init get the entity class by the genericClass
     */
    private void getGenericClass() {
        Class<T> entityClass = null;
        Type t = getClass().getGenericSuperclass();
        if (t == null)
            throw new IllegalArgumentException("Dao class need to complete the Generic Type");
        if (t instanceof ParameterizedType) {
            Type[] p = ((ParameterizedType) t).getActualTypeArguments();
            entityClass = (Class<T>) p[0];
        }
        this.entityClass = entityClass;
    }

    private SessionFactory getSessionFactory() {
        return getHibernateTemplate().getSessionFactory();
    }

    public boolean hasExsits(Object entity) {
        Class entityClass = entity.getClass();
        String entityName = getSessionFactory()
                .getClassMetadata(entityClass).getEntityName();
        String idProperty = getSessionFactory()
                .getClassMetadata(entityClass).getIdentifierPropertyName();
        Object obj = invokeMethod(entity, idProperty);
        String hqlString = "from " + entityName + " where " + idProperty + "=?";
        Object[] objArr = new Object[1];
        objArr[0] = obj;
        List list = getHibernateTemplate().find(hqlString, objArr);
        boolean result = (list != null) && (!list.isEmpty());
        return result;
    }

    /**
     * Reflect invoke method
     *
     * @param owner
     * @param methodName
     * @return
     */
    private Object invokeMethod(Object owner, String methodName) {
        Class ownerClass = owner.getClass();

        methodName = methodName.substring(0, 1).toUpperCase()
                + methodName.substring(1);
        Method method = null;
        try {
            method = ownerClass.getMethod("get" + methodName, new Class[0]);
            return method.invoke(owner, new Object[0]);
        } catch (SecurityException e) {
            throw new RuntimeException("orm-entity-001:遇安全问题。调用:"
                    + ownerClass.getName() + "对象的" + methodName + "方法出错。", e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("orm-entity-002:无该方法。调用:"
                    + ownerClass.getName() + "对象的" + methodName + "方法出错。", e);
        } catch (Exception e) {
            throw new RuntimeException("orm-entity-003:其它参数、调用错误。调用:"
                    + ownerClass.getName() + "对象的" + methodName + "方法出错。", e);
        }
    }

    public boolean hasExsits(String hql) {
        List list = getHibernateTemplate().find(hql);
        boolean result = (list != null) && (!list.isEmpty());
        return result;
    }

    public boolean hasExsits(String propertyName, String propertyValue) {
        List list = getHibernateTemplate().find("from " + getEntityClass().getName() + " where ? in('?')", new Object[]{propertyName, propertyValue});
        boolean result = (list != null) && (!list.isEmpty());
        return result;
    }

    @Override
    public Page findPage(PageRequest pageRequest, String hql, Object[] values) {
        Assert.notNull(pageRequest, "pageRequest对象不能为空");

        Page page = new Page(pageRequest);

        if (pageRequest.isCountTotal()) {
            long totalCount = countTotalResult(hql, values);
            page.setTotal(totalCount);
        }

        if (pageRequest.isOrderBySetted()) {
            hql = setOrderParameterToHql(hql, pageRequest);
        }

        Query q = createQuery(hql, values);
        q.setFirstResult(pageRequest.getOffset());
        q.setMaxResults(pageRequest.getPageSize());
        q.setCacheable(true);

        List result = q.list();
        page.setRows(result);
        return page;
    }

    protected String setOrderParameterToHql(String hql, PageRequest pageRequest) {
        StringBuilder builder = new StringBuilder(hql);
        builder.append(" order by ");
        builder.append(pageRequest.getOrderBy());
        return builder.toString();
    }

    protected long countTotalResult(String hql, Object[] values) {
        String countHql = prepareCountHql(hql);
        try {
            Long count = (Long) findUnique(countHql, values);
            return count.longValue();
        } catch (Exception e) {
            throw new RuntimeException("orm-entity-003：Hql无法获得记录总数，hql为："
                    + countHql, e);
        }
    }

    private String prepareCountHql(String orgHql) {
        String countHql = "select count (1) "
                + removeFetch(removeSelect(removeOrders(orgHql)));
        return countHql;
    }

    private String removeFetch(String hql) {
        Pattern p = Pattern.compile("fetch\\s+", 2);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    private static String removeSelect(String hql) {
        int beginPos = hql.toLowerCase().indexOf("from");
        return hql.substring(beginPos);
    }

    private static String removeOrders(String hql) {
        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", 2);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    public T findUnique(String hql, Object[] values) {
        T obj = (T) createQuery(hql, values).uniqueResult();
        return obj;
    }

    protected Query createQuery(String queryString, Object[] values) {
        Query query = currentSession().createQuery(queryString);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
        query.setCacheable(true);
        return query;
    }

    private ClassMetadata getClassMetadata(String entitySimpleName) {
        Map map = getSessionFactory().getAllClassMetadata();
        Set keySet = map.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            String keyString = (String) iterator.next();
            String shortKeyString = ((ClassMetadata) map.get(keyString))
                    .getMappedClass().getSimpleName();
            if (shortKeyString.equals(entitySimpleName)) {
                return (ClassMetadata) map.get(keyString);
            }
        }
        return null;
    }

    public int delete(String propertyName, String propertyValues) {
        StringBuffer hql = new StringBuffer("delete from " + getEntityClass().getName() + " ");
        try {
            if ((StringUtils.hasText(propertyName))
                    && (StringUtils.hasText(propertyValues))) {
                if (propertyValues.startsWith("'")) {
                    hql.append(" where " + propertyName + " in (");
                    hql.append(propertyValues);
                    hql.append(")");
                } else {
                    hql.append(" where " + propertyName + " in ('");
                    hql.append(propertyValues.replaceAll(",", "','"));
                    hql.append("')");
                }
            } else {
                return 0;
            }
            return currentSession().createQuery(hql.toString()).executeUpdate();
        } catch (Exception ex) {
            throw new RuntimeException("orm-entity-006：删除记录失败，hql为："
                    + hql.toString(), ex);
        }
    }

    public int deleteByFilter(String entityName, String whereFilter) {
        StringBuffer hql = new StringBuffer("delete from " + entityName + " ");
        try {
            if (StringUtils.hasText(whereFilter)) {
                hql.append(" where " + whereFilter + " ");
            } else {
                return 0;
            }
            return currentSession().createQuery(hql.toString()).executeUpdate();
        } catch (Exception ex) {
            throw new RuntimeException("orm-entity-006：删除记录失败，hql为："
                    + hql.toString(), ex);
        }
    }

    public Object get(long id) {
        Object entity = getHibernateTemplate().get(entityClass, id);
        return entity;
    }

    public List<T> getAll() {
        List<T> list = getHibernateTemplate().loadAll(entityClass);
        return list;
    }

    public String getIdName(Class entityClass) {
        String idName = getSessionFactory().getClassMetadata(entityClass).getIdentifierPropertyName();
        return idName;
    }

    public void flush() {
        currentSession().flush();
    }

    public void clear() {
        currentSession().clear();
    }

    public List batchExecute(String[] hql) {
        List list = new ArrayList();
        for (int i = 0; i < hql.length; i++) {
            List temp = getHibernateTemplate().find(hql[i]);
            list.add(temp);
        }
        return list;
    }

    public int[] batchUpdate(String[] hql) {
        int[] arr = new int[hql.length];
        for (int i = 0; i < hql.length; i++) {
            int temp = getHibernateTemplate().bulkUpdate(hql[i]);
            arr[i] = temp;
        }
        return arr;
    }

    public int execute(String hql) {
        int result = currentSession().createQuery(hql).executeUpdate();
        return result;
    }

}
