package com.mossle.core.hibernate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mossle.core.query.MatchType;
import com.mossle.core.query.PropertyFilter;
import com.mossle.core.util.BeanUtils;

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

import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Restrictions;

import org.hibernate.internal.CriteriaImpl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.util.Assert;

/**
 * hibernate utils.
 * 
 * @author Lingo
 */
public class HibernateUtils {
    /** logger. */
    private static Logger logger = LoggerFactory
            .getLogger(HibernateUtils.class);

    /** protected constructor. */
    protected HibernateUtils() {
    }

    /**
     * get number for count.
     * 
     * @param result
     *            Object
     * @return Integer
     */
    public static Integer getNumber(Object result) {
        if (result == null) {
            return 0;
        } else {
            return ((Number) result).intValue();
        }
    }

    /**
     * 去除hql的select 子句，未考虑union的情况,用于pagedQuery.
     * 
     * @param hql
     *            HQL字符串
     * @return 删除select语句后的字符串
     * @see HibernatePagingDao#pagedQuery(String,int,int,Object[])
     */
    public static String removeSelect(String hql) {
        Assert.hasText(hql);

        if (hql.toLowerCase(Locale.ENGLISH).indexOf("distinct") != -1) {
            logger.warn(
                    "there is a distinct in paged query hql : [{}], this maybe cause an unexpected result",
                    hql);
        }

        int beginPos = hql.toLowerCase(Locale.CHINA).indexOf("from");
        Assert.isTrue(beginPos != -1, " hql : " + hql
                + " must has a keyword 'from'");

        return hql.substring(beginPos);
    }

    /**
     * 去除hql的order by 子句，用于pagedQuery.
     * 
     * @param hql
     *            HQL字符串
     * @return 删除排序语句后的字符串
     * @see HibernatePagingDao#pagedQuery(String,int,int,Object[])
     */
    public static String removeOrders(String hql) {
        Assert.hasText(hql);

        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*",
                Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            m.appendReplacement(sb, "");
        }

        m.appendTail(sb);

        return sb.toString();
    }

    /**
     * distinct.
     * 
     * @param query
     *            Query
     * @return Query
     */
    public static Query distinct(Query query) {
        query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);

        return query;
    }

    /**
     * distinct.
     * 
     * @param criteria
     *            Criteria
     * @return Criteria
     */
    public static Criteria distinct(Criteria criteria) {
        criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);

        return criteria;
    }

    /**
     * find projection from criteria.
     * 
     * @param criteria
     *            Criteria
     * @return Projection
     */
    public static Projection findProjection(Criteria criteria) {
        if (criteria instanceof CriteriaImpl) {
            return ((CriteriaImpl) criteria).getProjection();
        } else {
            throw new IllegalArgumentException(criteria
                    + " is not a CriteriaImpl");
        }
    }

    /**
     * find order entries.
     * 
     * @param criteria
     *            Criteria
     * @return List
     */
    public static List findOrderEntries(Criteria criteria) {
        return (List) BeanUtils.safeGetFieldValue(criteria, "orderEntries");
    }

    /**
     * set order entries.
     * 
     * @param criteria
     *            Criteria
     * @param orderEntries
     *            List
     */
    public static void setOrderEntries(Criteria criteria, List orderEntries) {
        BeanUtils.safeSetFieldValue(criteria, "orderEntries", orderEntries);
    }

    /**
     * 按属性条件参数创建Criterion,辅助函数.
     * 
     * @param propertyName
     *            String
     * @param propertyValue
     *            Object
     * @param matchType
     *            MatchType
     * @return Criterion
     */
    public static Criterion buildCriterion(String propertyName,
            Object propertyValue, MatchType matchType) {
        Assert.hasText(propertyName, "propertyName不能为空");

        Criterion criterion = null;

        // 根据MatchType构造criterion
        switch (matchType) {
        case EQ:
            criterion = Restrictions.eq(propertyName, propertyValue);

            break;

        case NOT:
            criterion = Restrictions.ne(propertyName, propertyValue);

            break;

        case LIKE:
            criterion = Restrictions.like(propertyName, (String) propertyValue,
                    MatchMode.ANYWHERE);

            break;

        case LE:
            criterion = Restrictions.le(propertyName, propertyValue);

            break;

        case LT:
            criterion = Restrictions.lt(propertyName, propertyValue);

            break;

        case GE:
            criterion = Restrictions.ge(propertyName, propertyValue);

            break;

        case GT:
            criterion = Restrictions.gt(propertyName, propertyValue);

            break;

        case IN:
            criterion = Restrictions.in(propertyName,
                    (Collection) propertyValue);

            break;

        case INL:
            criterion = Restrictions.isNull(propertyName);

            break;

        case NNL:
            criterion = Restrictions.isNotNull(propertyName);

            break;

        default:
            criterion = Restrictions.eq(propertyName, propertyValue);

            break;
        }

        return criterion;
    }

    /**
     * 按属性条件列表创建Criterion数组,辅助函数.
     * 
     * @param filters
     *            List
     * @return Criterion[]
     */
    public static Criterion[] buildCriterion(List<PropertyFilter> filters) {
        List<Criterion> criterionList = new ArrayList<Criterion>();

        for (PropertyFilter filter : filters) {
            // 只有一个属性需要比较的情况.
            if (!filter.hasMultiProperties()) {
                Criterion criterion = buildCriterion(filter.getPropertyName(),
                        filter.getMatchValue(), filter.getMatchType());
                criterionList.add(criterion);
            } else {
                // 包含多个属性需要比较的情况,进行or处理.
                Disjunction disjunction = Restrictions.disjunction();

                for (String param : filter.getPropertyNames()) {
                    Criterion criterion = buildCriterion(param,
                            filter.getMatchValue(), filter.getMatchType());
                    disjunction.add(criterion);
                }

                criterionList.add(disjunction);
            }
        }

        return criterionList.toArray(new Criterion[criterionList.size()]);
    }

    public static void buildQuery(StringBuilder buff,
            PropertyFilter propertyFilter) {
        if (buff.toString().toLowerCase().indexOf("where") == -1) {
            buff.append(" where ");
        } else {
            buff.append(" and ");
        }

        buff.append(propertyFilter.getPropertyName());

        switch (propertyFilter.getMatchType()) {
        case EQ:
            buff.append("=:");

            break;

        case NOT:
            buff.append("<>:");

            break;

        case LIKE:
            buff.append(" LIKE:");

            break;

        case LE:
            buff.append("<=:");

            break;

        case LT:
            buff.append("<:");

            break;

        case GE:
            buff.append(">=:");

            break;

        case GT:
            buff.append(">:");

            break;

        case IN:
            buff.append("IN :");

            break;

        case INL:
            buff.append(" IS NULL");

            break;

        case NNL:
            buff.append(" IS NOT NULL");

            break;

        default:
            buff.append("=:");

            break;
        }

        buff.append(propertyFilter.getPropertyName().replaceAll("\\.", "_"));
    }
}
