package com.yeejoin.amos.fas.core.util.query;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.predicate.InPredicate;
import org.springframework.data.jpa.domain.Specification;

import com.yeejoin.amos.fas.core.enums.QueryOperatorEnum;
import com.yeejoin.amos.fas.core.util.DaoCriteria;

/**
 * 基础Specification
 * 
 * @author as-youjun
 *
 * @param <T>
 */
public class BaseQuerySpecification<T> implements Specification<T> {

	private List<DaoCriteria> daoCriterias;

	private Map<String, List<DaoCriteria>> criterias;

	private Map<String, List<String>> orderbys;

	public BaseQuerySpecification(List<DaoCriteria> daoCriterias) {
		this.daoCriterias = daoCriterias;
	}

	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
		if (daoCriterias == null) {
			return builder.and(new Predicate[] {});
		}

		criterias = classify(daoCriterias);
		List<Predicate> conditions = new ArrayList<Predicate>();

		for (String key : criterias.keySet()) {
			List<DaoCriteria> daoCriterias = criterias.get(key);
			if (daoCriterias.size() > 1) {
				List<Predicate> sameConditions = new ArrayList<Predicate>();
				for (DaoCriteria daoCriteria : daoCriterias) {
					Predicate predicate = createPredicate(root, query, builder, daoCriteria);
					if (null == predicate) {
						throw new IllegalArgumentException("查询条件生成错误！");
					} else {
						sameConditions.add(predicate);
					}
				}
				Predicate[] predicates = new Predicate[sameConditions.size()];
				conditions.add(builder.or(sameConditions.toArray(predicates)));
			} else {
				for (DaoCriteria daoCriteria : daoCriterias) {
					Predicate predicate = createPredicate(root, query, builder, daoCriteria);
					if (null != predicate) {
						conditions.add(predicate);
					}
				}
			}
		}

		Predicate[] predicates = new Predicate[conditions.size()];
		query.where(builder.and(conditions.toArray(predicates)));

		List<Order> orders = new ArrayList<>();
		orderbys.keySet().forEach(key -> {
			if (key.toLowerCase().equals("asc") && !orderbys.get(key).isEmpty()) {
				orderbys.get(key).forEach(propertyName -> {
					orders.add(builder.asc(root.get(propertyName)));
				});
			} else if (key.toLowerCase().equals("desc") && !orderbys.get(key).isEmpty()) {
				orderbys.get(key).forEach(propertyName -> {
					orders.add(builder.desc(root.get(propertyName)));
				});
			}
		});

		query.orderBy(orders);

		return query.getRestriction();
	}

	/**
	 * 根据同类条件进行分组
	 * 
	 * @param criteraList
	 * @return
	 */
	private Map<String, List<DaoCriteria>> classify(List<DaoCriteria> criteraList) {
		Map<String, List<DaoCriteria>> criteraType = new HashMap<String, List<DaoCriteria>>();
		orderbys = new HashMap<>();
		orderbys.put("asc", new ArrayList<String>());
		orderbys.put("desc", new ArrayList<String>());
		if (criteraList != null && criteraList.size() > 0) {
			for (DaoCriteria daoCriteria : criteraList) {
				if (daoCriteria.getOperator().equals(QueryOperatorEnum.ORDER_BY.getName())) {
					orderbys.get(daoCriteria.getValue().toString().toLowerCase()).add(daoCriteria.getPropertyName());
				} else {
					List<DaoCriteria> temp = criteraType.get(daoCriteria.getPropertyName());
					if (temp == null) {
						temp = new ArrayList<DaoCriteria>();
						criteraType.put(daoCriteria.getPropertyName(), temp);
					}
					temp.add(daoCriteria);
				}
			}
		}
		return criteraType;
	}

	/**
	 * 根据条件类型，创建查询条件
	 * 
	 * @param root
	 * @param query
	 * @param builder
	 * @param daoCriteria
	 * @return
	 */
	private Predicate createPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder,
			DaoCriteria daoCriteria) {
		String name = daoCriteria.getPropertyName();
		Object value = daoCriteria.getValue();
		String operator = daoCriteria.getOperator();

		if (operator.equals(QueryOperatorEnum.EQUAL.getName())) {
			if (value instanceof Boolean) {
				return builder.equal(root.get(name), value);
			}
			return builder.equal(root.get(name), value);
		} else if (operator.equals(QueryOperatorEnum.LIKE.getName())) {
			return builder.like(root.get(name).as(String.class), value.toString());
		} else if (operator.equals(QueryOperatorEnum.NOT_IN.getName())) {
			return builder.not(
					new InPredicate<Object>((CriteriaBuilderImpl) builder, root.get(name).as(String.class), value));
		} else if (operator.equals(QueryOperatorEnum.BIGGER.getName())) {
			return builder.greaterThan(root.get(name).as(String.class), value.toString());
		} else if (operator.equals(QueryOperatorEnum.BIGGER_EQUAL.getName())) {
			return builder.greaterThanOrEqualTo(root.get(name).as(String.class), value.toString());
		} else if (operator.equals(QueryOperatorEnum.LESS.getName())) {
			return builder.lessThan(root.get(name).as(String.class), value.toString());
		} else if (operator.equals(QueryOperatorEnum.LESS_EQUAL.getName())) {
			return builder.lessThanOrEqualTo(root.get(name).as(String.class), value.toString());
		} else if (operator.equals(QueryOperatorEnum.NOT_EQUAL.getName())) {
			return builder.notEqual(root.get(name).as(String.class), value);
		} else if (operator.equals(QueryOperatorEnum.IN.getName())) {
			if (value instanceof ArrayList<?>) {
				return builder.and(
						new InPredicate<Object>((CriteriaBuilderImpl) builder, root.get(name), ((ArrayList) value).toArray()));
			}
			return builder.and(
					new InPredicate<Object>((CriteriaBuilderImpl) builder, root.get(name), value));
		} else {
			return null;
		}
	}

	@SuppressWarnings("all")
	private Class clasz(Object o) {
		if (o instanceof Date) {
			return Date.class;
		} else if (o instanceof Integer) {
			return Integer.class;
		} else if (o instanceof Long) {
			return Long.class;
		} else if (o instanceof Double) {
			return Double.class;
		} else if (o instanceof Float) {
			return Float.class;
		} else if (o instanceof Short) {
			return Short.class;
		} else if (o instanceof Character) {
			return Character.class;
		} else if (o instanceof BigDecimal) {
			return Double.class;
		} else if (o instanceof Byte) {
			return Byte.class;
		} else if (o instanceof Byte) {
			return Byte.class;
		} else {
			return String.class;
		}
	}
}
