/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.domain.common;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.hibernate.query.criteria.internal.OrderImpl;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import com.crunii.micro.common.utils.ConvertUtil;
import com.crunii.micro.common.utils.FieldUtil;
import com.crunii.micro.common.dto.OrderProp;

/**
 * @author 田平 create 2018年9月17日下午5:32:36
 */
public class BaseRepoImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepo<T, ID> {

	protected final EntityManager entityManager;

	@Override
	public T getOne(ID id) {
		Optional<T> op = super.findById(id);
		return op.isPresent() ? op.get() : null;
	}

	// 父类没有不带参数的构造方法，这里手动构造父类
	public BaseRepoImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
		super(entityInformation, entityManager);
		this.entityManager = entityManager;
	}

	@Override
	public void evict(T entity) {
		entityManager.detach(entity);
	}

	@Override
	public T getUniqueByProps(Map<String, Object> props) {
		List<T> list = findByProps(props);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	@Override
	public T getUniqueByStrProps(Map<String, String> props) {
		List<T> list = findByStrProps(props);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	@Override
	public T getUniqueByProp(String prop, Object value) {
		List<T> list = findByProp(prop, value);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	@Override
	public List<T> findByStrProps(Map<String, String> props, OrderProp... os) {

		Map<String, Object> real = new LinkedHashMap<String, Object>();
		for (Entry<String, String> entry : props.entrySet()) {
			Field field = FieldUtil.getFields(getDomainClass(), entry.getKey());
			if (field.getType() == String.class) {
				real.put(entry.getKey(), entry.getValue());
			} else {
				real.put(entry.getKey(), ConvertUtil.convert(entry.getValue(), field.getType()));
			}
		}
		return findByProps(real, os);
	}

	@Override
	public List<T> findByProps(Map<String, Object> props, OrderProp... os) {
		if (props == null || props.size() == 0) {
			throw new RuntimeException("props not allowd null or size 0!");
		}
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		Class<T> cl = getDomainClass();
		CriteriaQuery<T> c = cb.createQuery(cl);
		Root<T> root = c.from(cl);
		c.select(root);
		Predicate[] preds = new Predicate[props.size()];
		int index = 0;
		for (Entry<String, Object> entry : props.entrySet()) {
			Path<?> path = root.get(entry.getKey());
			if (path != null) {
				preds[index] = cb.equal(path, entry.getValue());
				index++;
			}
		}
		c.where(preds);
		if (os != null) {
			Order[] orders = new Order[os.length];
			for (int i = 0; i < os.length; i++) {
				orders[i] = new OrderImpl(root.get(os[i].getProp()), os[i].isAscending());
			}
			c.orderBy(orders);
		}
		TypedQuery<T> q = entityManager.createQuery(c);
		return q.getResultList();
	}

	@Override
	public List<T> findByProp(String prop, Object value, OrderProp... os) {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		Class<T> cl = getDomainClass();
		CriteriaQuery<T> c = cb.createQuery(cl);
		Root<T> root = c.from(cl);
		c.select(root);
		// Field field = FieldUtil.getFields(cl, prop);
		// ParameterExpression<?> param = cb.parameter(field.getType(), prop);
		c.where(cb.equal(root.get(prop), value));
		if (os != null) {
			Order[] orders = new Order[os.length];
			for (int i = 0; i < os.length; i++) {
				orders[i] = new OrderImpl(root.get(os[i].getProp()), os[i].isAscending());
			}
			c.orderBy(orders);
		}
		TypedQuery<T> q = entityManager.createQuery(c);
		return q.getResultList();
	}

	@Override
	public List<T> queryByHql(String hql, Map<String, Object> props) {
		Class<T> cl = getDomainClass();
		TypedQuery<T> query = entityManager.createQuery(hql, cl);
		query.getParameters().forEach((c) -> {
			query.setParameter(c.getName(), props.get(c.getName()));
		});
		return query.getResultList();
	}
}
