package com.apobates.forum.core.impl.dao;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.apobates.forum.core.api.dao.TopicDao;
import com.apobates.forum.core.api.dao.TopicTagDao;
import com.apobates.forum.core.entity.Album;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.ForumEntityStatusEnum;
import com.apobates.forum.core.entity.Posts;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.core.entity.TopicTag;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;

@Repository
public class TopicDaoImpl implements TopicDao{
	@PersistenceContext
	private EntityManager entityManager;
	@Autowired
	private TopicTagDao topicTagDao;
	private final static Logger logger = LoggerFactory.getLogger(TopicDaoImpl.class);

	@Override
	public Page<Topic> findAll(Pageable pageable) {
		return emptyResult();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public void save(Topic entity) {
		entityManager.persist(entity);
	}

	@Override
	public Optional<Topic> findOne(Long primaryKey) {
		return Optional.ofNullable(entityManager.find(Topic.class, primaryKey));
	}

	@Override
	public Optional<Boolean> edit(Topic updateEntity) {
		return Optional.empty();
	}

	@Override
	public Stream<Topic> findAll() {
		return Stream.empty();
	}

	@Override
	public long count() {
		return -1L;
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editStatus(long id, ForumEntityStatusEnum status) {
		try {
			int affect = entityManager.createQuery("UPDATE Topic t SET t.status = ?1 WHERE t.id = ?2").setParameter(1, status).setParameter(2, id).executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editTitle(long id, String updateTopicTitle) {
		try {
			int affect = entityManager.createQuery("UPDATE Topic t SET t.title = ?1 WHERE t.id = ?2").setParameter(1, updateTopicTitle).setParameter(2, id).executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editTop(long id, boolean topValue) {
		try {
			int affect = entityManager.createQuery("UPDATE Topic t SET t.tops = ?1 WHERE t.id = ?2").setParameter(1, topValue).setParameter(2, id).executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editGoods(long id, boolean goodsValue) {
		try {
			int affect = entityManager.createQuery("UPDATE Topic t SET t.goods = ?1 WHERE t.id = ?2").setParameter(1, goodsValue).setParameter(2, id).executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public boolean removeAlbum(long id) {
		int affectTopicAlbum = entityManager.createQuery("UPDATE Topic t SET t.albumId = ?1 WHERE t.id = ?2").setParameter(1, 0).setParameter(2, id).executeUpdate();
		Album album = null; 
		try{
			album = entityManager.createQuery("SELECT a FROM Album a WHERE a.topicId = ?1", Album.class).setParameter(1, id).getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug("[TopicDao][removeAlbum]获取话题的像册时失败", e);
			}
		}
		if(album!=null){
			entityManager.remove(album);
			entityManager.flush();
		}
		return affectTopicAlbum == 1;
	}

	//最后回复的日期排
	@Override
	public Page<Topic> findAllByBoard(long boardId, Pageable pageable) {
		final long total = findAllByBoardCount(boardId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Topic> query = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 AND t.article = ?4 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardId)
				.setParameter(2, false)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setParameter(4, false);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	private long findAllByBoardCount(long boardId) {
		try {
			return entityManager.createQuery("SELECT COUNT(t) FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 AND t.article = ?4", Long.class)
					.setParameter(1, boardId)
					.setParameter(2, false)
					.setParameter(3, ForumEntityStatusEnum.DELETE)
					.setParameter(4, false)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByBoardCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	
	@Override
	public Page<Topic> findAllByBoard(long boardId, String categoryValue, Pageable pageable) {
		final long total = findAllByBoardCount(boardId, categoryValue);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Topic> query = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 AND t.topicCategoryValue = ?4 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardId)
				.setParameter(2, false)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setParameter(4, categoryValue);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	private long findAllByBoardCount(long boardId, String categoryValue) {
		try {
			return entityManager.createQuery("SELECT COUNT(t) FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 AND t.topicCategoryValue = ?4", Long.class)
					.setParameter(1, boardId)
					.setParameter(2, false)
					.setParameter(3, ForumEntityStatusEnum.DELETE)
					.setParameter(4, categoryValue)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByBoardCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	@Override
	public Stream<Topic> findAllByBoard(long boardId, int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 AND t.suggest = ?4 AND t.summary IS NOT NULL ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardId)
				.setParameter(2, false)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setParameter(4, true)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Stream<Topic> findAllByBoardIgnoreStatus(long boardId, int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 ORDER BY t.entryDateTime DESC", Topic.class)
				.setParameter(1, boardId)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Stream<Topic> findAllByBoardOfTop(long boardId) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.tops = ?2 AND t.status != ?3 ORDER BY t.id DESC", Topic.class)
				.setParameter(1, boardId)
				.setParameter(2, true)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.getResultStream();
	}

	@Override
	public Page<Topic> findAllByMember(long memberId, Pageable pageable) {
		final long total = findAllByMemberCount(memberId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Topic> query = entityManager.createQuery("SELECT t FROM Topic t WHERE t.memberId = ?1 ORDER BY t.rankingDateTime DESC", Topic.class).setParameter(1, memberId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	@Override
	public long findAllByMemberCount(long memberId) {
		try {
			return entityManager.createQuery("SELECT COUNT(t) FROM Topic t WHERE t.memberId = ?1", Long.class).setParameter(1, memberId).getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByMemberCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	@Override
	public Stream<Topic> findAllByMember(long memberId, int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.memberId = ?1 AND t.status != ?2 AND t.suggest = ?3 AND t.summary IS NOT NULL ORDER BY t.entryDateTime DESC", Topic.class)
				.setParameter(1, memberId)
				.setParameter(2, ForumEntityStatusEnum.DELETE)
				.setParameter(3, true)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Stream<Topic> findAllOfRecent(int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId != ?1 AND t.status != ?2 AND t.suggest = ?3 AND t.article = ?4 AND t.summary IS NOT NULL ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, 1)
				.setParameter(2, ForumEntityStatusEnum.DELETE)
				.setParameter(3, true)
				.setParameter(4, false)
				.setMaxResults(size)
				.getResultStream();
	}
	

	
	@Override
	public Stream<Topic> findAllOfRecentIgnoreCondition(int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.suggest = ?1 AND t.article = ?2 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, true)
				.setParameter(2, false)
				.setMaxResults(size)
				.getResultStream();
	}
	//IS BAD
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public int moveTopic(long id, long targetBoardId, int targetBoardGroupId) throws IllegalStateException{
		//话题
		int affectTopic=0;
		try {
			affectTopic = entityManager.createQuery("UPDATE Topic t SET t.boardId = ?1, t.volumesId = ?2 WHERE t.id = ?3")
					.setParameter(1, targetBoardId)
					.setParameter(2, targetBoardGroupId)
					.setParameter(3, id)
					.executeUpdate();
		}catch(Exception e) {
			logger.info("[MTV][4]更新话题到目标版块失败, 异常: "+e.getMessage());
			throw new  IllegalStateException(e.getMessage());
		}
		if(affectTopic == 0) {
			logger.info("[MTV][5]更新话题到目标版块影响的记录0");
			return -1;
		}
		//话题统计
		int affectTopicStats = 0;
		try{
			affectTopicStats = entityManager.createQuery("UPDATE TopicStats ts SET ts.boardId = ?1, ts.volumesId = ?2 WHERE ts.topicId = ?3")
					.setParameter(1, targetBoardId)
					.setParameter(2, targetBoardGroupId)
					.setParameter(3, id)
					.executeUpdate();
		}catch(Exception e) {
			logger.info("[MTV][6]更新话题统计为目标版块失败, 异常: "+e.getMessage());
			throw new  IllegalStateException(e.getMessage());
		}
		if(affectTopicStats == 0) {
			logger.info("[MTV][7]更新话题统计为目标版块影响的记录0");
			return -2;
		}
		//话题回复
		int affectPosts = 0;
		try {
			affectPosts = entityManager.createQuery("UPDATE Posts p SET p.boardId = ?1, p.volumesId = ?2 WHERE p.topicId = ?3")
					.setParameter(1, targetBoardId)
					.setParameter(2, targetBoardGroupId)
					.setParameter(3, id)
					.executeUpdate();
		}catch(Exception e) {
			logger.info("[MTV][8]更新话题的回复为目标版块失败, 异常: "+e.getMessage());
			throw new  IllegalStateException(e.getMessage());
		}
		if(affectPosts == 0) {
			logger.info("[MTV][9]更新话题回复为目标版块影响的记录0");
			return -3;
		}
		//话题的像册
		try {
			entityManager.createQuery("UPDATE Album pa SET pa.boardId = ?1, pa.volumesId = ?2 WHERE pa.topicId = ?3")
			.setParameter(1, targetBoardId)
			.setParameter(2, targetBoardGroupId)
			.setParameter(3, id)
			.executeUpdate();
		}catch(Exception e) {
			logger.info("[MTV][10]更新话题像册为目标版块失败, 异常: "+e.getMessage());
			throw new  IllegalStateException(e.getMessage());
		}
		return affectPosts;
	}

	@Override
	public Page<Topic> findAllByMemberReply(long memberId, Pageable pageable) {
		final long total = findAllByMemberReplyCount(memberId);
		if (total == 0) {
			return emptyResult();
		}
		final String SQL = "SELECT t.* FROM apo_topic AS t WHERE t.MEMBERID != ?1 AND EXISTS(SELECT 1 FROM apo_topic_posts AS tp WHERE tp.MEMBERID = ?2 AND tp.TOPICID = t.ID) ORDER BY t.RANKINGDATETIME DESC";
		Query query = entityManager.createNativeQuery(SQL, Topic.class).setParameter(1, memberId).setParameter(2, memberId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		@SuppressWarnings("unchecked")
		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	@Override
	public long findAllByMemberReplyCount(long memberId) {
		try {
			final String SQL = "SELECT COUNT(*) FROM apo_topic AS t WHERE t.MEMBERID != ?1 AND EXISTS(SELECT 1 FROM apo_topic_posts AS tp WHERE tp.MEMBERID = ?2 AND tp.TOPICID = t.ID)";
			Object rs = entityManager.createNativeQuery(SQL)
					.setParameter(1, memberId)
					.setParameter(2, memberId)
					.getSingleResult();
			if (rs instanceof Long) {
				return (Long) rs;
			}
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByMemberReplyCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	@SuppressWarnings("unchecked")
	@Override
	public Stream<Topic> findAllByMemberReply(long memberId, int size) { //不需要判断suggest
		final String SQL="SELECT t.* FROM apo_topic AS t WHERE t.summary IS NOT NULL AND t.MEMBERID != ?1 AND EXISTS(SELECT 1 FROM apo_topic_posts AS tp WHERE tp.MEMBERID = ?2 AND tp.TOPICID = t.ID) ORDER BY t.RANKINGDATETIME DESC"; 
		return entityManager.createNativeQuery(SQL, Topic.class).setParameter(1, memberId).setParameter(2, memberId).setMaxResults(size).getResultStream();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Stream<Topic> findAllByMemberPopular(long memberId, int size) {
		final String SQL="SELECT t.* FROM apo_topic AS t JOIN apo_topic_stats AS ts ON t.ID = ts.TOPICID WHERE t.MEMBERID = ?1 AND ts.DISPLAIES > 1 ORDER BY ts.DISPLAIES DESC";
		return entityManager.createNativeQuery(SQL, Topic.class).setParameter(1, memberId).setMaxResults(size).getResultStream();
	}

	@Override
	public Topic findOneForIndex(long id) {
		Topic t = entityManager.find(Topic.class, id);
		if(t==null) {
			return null;
		}
		if(t.getBoardId()>0) {
			try {
				Board b = entityManager.createQuery("SELECT b FROM Board b WHERE b.id = ?1", Board.class).setParameter(1, t.getBoardId()).getSingleResult();
				if(b!=null) {
					t.setBoard(b);
				}
			}catch(javax.persistence.NoResultException e) {}
		}
		if(t.getVolumesId()>0) {
			try {
				BoardGroup bg = entityManager.createQuery("SELECT bg FROM BoardGroup bg WHERE bg.id = ?1", BoardGroup.class).setParameter(1, t.getVolumesId()).getSingleResult();
				if(bg!=null) {
					t.setVolumes(bg);
				}
			}catch(javax.persistence.NoResultException e) {}
		}else {
			t.setVolumes(BoardGroup.getDefault());
		}
		return t;
	}

	@Override
	public Stream<Topic> findAllOfRecent(int boardGroupId, int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.volumesId = ?1 AND t.status != ?2 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardGroupId)
				.setParameter(2, ForumEntityStatusEnum.DELETE)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Stream<Topic> findAllRecentByBoard(long boardId, LocalDateTime prevDate) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.entryDateTime BETWEEN ?2 AND ?3 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardId)
				.setParameter(2, prevDate)
				.setParameter(3, LocalDateTime.now())
				.getResultStream();
	}

	@Override
	public Stream<Topic> findAllByBoardGroupOfGoods(int boardGroupId, int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.volumesId = ?1 AND t.status != ?2 AND t.goods = ?3 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, boardGroupId)
				.setParameter(2, ForumEntityStatusEnum.DELETE)
				.setParameter(3, true)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public List<Topic> findAllByBoardGroupByMaxReply(int boardGroupId, int size) {
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createNativeQuery("SELECT ts.TOPICID,ts.POSTSES FROM apo_topic_stats AS ts WHERE ts.VOLUMESID = ?1 AND ts.POSTSES > ?2 ORDER BY POSTSES DESC")
								.setParameter(1, boardGroupId)
								.setParameter(2, 1)
								.setMaxResults(size)
								.getResultList();
		Map<Long,Long> data = new HashMap<>();
		for(Object[] columns : result){
			data.put((Long)columns[0], (Long)columns[1]);
		}
		if(data.isEmpty()){
			return Collections.emptyList();
		}
		List<Topic> rs = entityManager.createQuery("SELECT t FROM Topic t WHERE t.id IN ?1 AND t.status != ?2 ORDER BY t.rankingDateTime DESC", Topic.class)
							.setParameter(1, data.keySet())
							.setParameter(2, ForumEntityStatusEnum.DELETE)
							.getResultList();
		for(Topic t : rs){
			t.setRanking(data.get(t.getId()).intValue());
		}
		return rs;
	}

	@Override
	public List<Topic> findAllByMaxReply(int size) {
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createNativeQuery("SELECT ts.TOPICID,ts.POSTSES FROM apo_topic_stats AS ts WHERE ts.POSTSES > ?1 ORDER BY POSTSES DESC")
				.setParameter(1, 1)
				.setMaxResults(size)
				.getResultList();
		Map<Long, Long> data = new HashMap<>();
		for (Object[] columns : result) {
			data.put((Long) columns[0], (Long) columns[1]);
		}
		if (data.isEmpty()) {
			return Collections.emptyList();
		}
		List<Topic> rs = entityManager.createQuery("SELECT t FROM Topic t WHERE t.id IN ?1 AND t.status != ?2 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, data.keySet())
				.setParameter(2, ForumEntityStatusEnum.DELETE)
				.getResultList();
		for (Topic t : rs) {
			t.setRanking(data.get(t.getId()).intValue());
		}
		return rs;
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editAlbum(long id, long albumId) {
		int affectRows = entityManager.createQuery("UPDATE Topic t SET t.albumId = ?1 WHERE t.id = ?2").setParameter(1, albumId).setParameter(2, id).executeUpdate();
		return affectRows == 1?Optional.of(true):Optional.empty();
	}

	@Override
	public List<Topic> findAllById(Collection<Long> idList) {
		if(idList == null || idList.isEmpty()){
			return Collections.emptyList();
		}
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.id IN ?1", Topic.class).setParameter(1, idList).getResultList();
	}

	@Override
	public Map<Long, Long> statsBoardTopicSize(LocalDateTime start, LocalDateTime finish) {
		final String SQL="SELECT t.boardId, COUNT(t) FROM Topic t WHERE t.entryDateTime BETWEEN ?1 AND ?2 GROUP BY t.boardId";
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery(SQL).setParameter(1, start).setParameter(2, finish).getResultList();
		//
		Map<Long, Long> data = new HashMap<>();
		for(Object[] columns : result){
			Long k = 0L;
			try{
				k = ((BigDecimal)columns[0]).longValue();
			}catch(java.lang.ClassCastException e){
				k = (Long)columns[0];
			}
			Long v = 0L;
			try{
				v = ((BigDecimal)columns[1]).longValue();
			}catch(java.lang.ClassCastException e){
				v = (Long)columns[1];
			}
			if(k==null || k == 0){
				continue;
			}
			if(v == null){
				continue;
			}
			data.put(k, v);
			
		}
		return data;
	}

	@Override
	public long statsBoardTopicSize(long boardId, LocalDateTime start, LocalDateTime finish) {
		final String SQL="SELECT COUNT(t) FROM Topic t WHERE t.entryDateTime BETWEEN ?1 AND ?2 AND t.boardId = ?3";
		try{
			return entityManager.createQuery(SQL, Long.class).setParameter(1, start).setParameter(2, finish).setParameter(3, boardId).getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug("[statsBoardTopicSize][TopicDao]", e);
			}
		}
		return 0L;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> updateRankingDate(long id, LocalDateTime updateDateTime) {
		int affectRows = entityManager.createQuery("UPDATE Topic t SET t.rankingDateTime = ?1 WHERE t.id = ?2").setParameter(1, updateDateTime).setParameter(2, id).executeUpdate();
		return affectRows == 1?Optional.of(true):Optional.empty();
	}

	@Override
	public TreeMap<String, Long> groupTopicSize(LocalDateTime start, LocalDateTime finish) {
		final String SQL="SELECT FUNCTION('DATE', t.entryDateTime), COUNT(t) FROM Topic t WHERE t.entryDateTime BETWEEN ?1 AND ?2 GROUP BY FUNCTION('DATE', t.entryDateTime)";
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery(SQL).setParameter(1, start).setParameter(2, finish).getResultList();
		//
		TreeMap<String, Long> data = new TreeMap<>();
		for(Object[] columns : result){
			String k = null;
			try{
				k = (String)columns[0];
			}catch(java.lang.ClassCastException e){
				k = columns[0].toString();
			}
			Long v = 0L;
			try{
				v = ((BigDecimal)columns[1]).longValue();
			}catch(java.lang.ClassCastException e){
				v = (Long)columns[1];
			}
			if(k==null || v == null){
				continue;
			}
			data.put(k, v);
		}
		return data;
	}

	@Override
	public EnumMap<ForumEntityStatusEnum, Long> groupTopicesForStatus() {
		final String SQL="SELECT t.status, COUNT(t) FROM Topic t GROUP BY t.status";
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery(SQL).getResultList();
		//
		EnumMap<ForumEntityStatusEnum, Long> data = new EnumMap<>(ForumEntityStatusEnum.class);
		for(Object[] columns : result){
			ForumEntityStatusEnum k = null;
			try{
				k = (ForumEntityStatusEnum)columns[0];
			}catch(java.lang.ClassCastException e){}
			Long v = 0L;
			try{
				v = ((BigDecimal)columns[1]).longValue();
			}catch(java.lang.ClassCastException e){
				v = (Long)columns[1];
			}
			if(k==null || v == null){
				continue;
			}
			data.put(k, v);
			
		}
		return data;
	}

	@Override
	public Map<String, Long> groupTopicesForCategory() {
		final String SQL="SELECT t.topicCategoryName, COUNT(t) FROM Topic t GROUP BY t.topicCategoryName";
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery(SQL).getResultList();
		//
		Map<String, Long> data = new HashMap<>();
		for(Object[] columns : result){
			String k = null;
			try{
				k = (String)columns[0];
			}catch(java.lang.ClassCastException e){}
			Long v = 0L;
			try{
				v = ((BigDecimal)columns[1]).longValue();
			}catch(java.lang.ClassCastException e){
				v = (Long)columns[1];
			}
			if(k==null || v == null){
				continue;
			}
			data.put(k, v);
			
		}
		return data;
	}

	@Override
	public Map<Long, Long> groupTopicesForBoard() {
		final String SQL="SELECT t.boardId, COUNT(t) FROM Topic t GROUP BY t.boardId";
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery(SQL).getResultList();
		//
		Map<Long, Long> data = new HashMap<>();
		for(Object[] columns : result){
			Long k = 0L;
			try{
				k = ((BigDecimal)columns[0]).longValue();
			}catch(java.lang.ClassCastException e){
				k = (Long)columns[0];
			}
			Long v = 0L;
			try{
				v = ((BigDecimal)columns[1]).longValue();
			}catch(java.lang.ClassCastException e){
				v = (Long)columns[1];
			}
			if(k==null || k==0){
				continue;
			}
			if(v == null){
				continue;
			}
			data.put(k, v);
			
		}
		return data;
	}

	@Override
	public Stream<Topic> findAllByReplyDate(int size) {
		return entityManager.createQuery("SELECT t FROM Topic t ORDER BY t.rankingDateTime DESC", Topic.class).setMaxResults(size).getResultStream();
	}

	@Override
	public Stream<Topic> findAllByBoard(long boardId, ForumEntityStatusEnum status) {
		return  entityManager.createQuery("SELECT t FROM Topic t WHERE t.status = ?1 AND t.boardId = ?2 ORDER BY t.entryDateTime ASC", Topic.class)
				.setParameter(1, status)
				.setParameter(2, boardId)
				.getResultStream();
	}

	@Override
	public Page<Topic> findAllByTag(Collection<String> tagNames, Pageable pageable) {
		if(null == tagNames || tagNames.isEmpty()){
			return emptyResult();
		}
		//删除掉HTML标签
		List<String> params = tagNames.stream().map(s->Commons.htmlPurifier(s)).collect(Collectors.toList());
		//转换成SQL参数形式
		Optional<String> tagNameParams = Commons.quoteSQLParameters(params);
		if(!tagNameParams.isPresent()){
			logger.error("转换参数时出错了");
			return emptyResult();
		}
		final long total = findAllByTagCount(tagNameParams.get());
		if (total == 0) {
			return emptyResult();
		}
		final String SQL="SELECT t.* FROM apo_topic AS t WHERE t.SUGGEST = ?1 AND t.ARTICLE = ?2 AND t.STATUS != ?3 AND EXISTS(SELECT 1 FROM apo_topic_tag AS tt WHERE tt.`NAMES` IN("+tagNameParams.get()+") AND t.ID = tt.TOPICID) ORDER BY t.RANKINGDATETIME DESC";
		Query query = entityManager.createNativeQuery(SQL, Topic.class)
				.setParameter(1, 1)
				.setParameter(2, 0)
				.setParameter(3, "DELETE");
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		@SuppressWarnings("unchecked")
		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	private long findAllByTagCount(String tagNameParams){
		if(null == tagNameParams){
			return 0L;
		}
		try {
			return entityManager.createQuery("SELECT COUNT(tt) FROM TopicTag tt WHERE tt.names IN("+tagNameParams+")", Long.class).getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByTagCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	
	/*子栏目中的文章*/
	@Override
	public Stream<Topic> findAllOfRecentTermArticle(int size) {
		return entityManager.createQuery("SELECT t FROM Topic t WHERE t.suggest = ?1 AND t.article = ?2 ORDER BY t.rankingDateTime DESC", Topic.class)
				.setParameter(1, false)
				.setParameter(2, true)
				.setMaxResults(size)
				.getResultStream();
	}
	@Override
	public Page<Topic> findAllTermArticle(long termId, Pageable pageable) {
		final long total = findAllByArticleCount(termId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Topic> query = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.suggest = ?2 AND t.status != ?3 AND t.article = ?4 ORDER BY t.entryDateTime DESC", Topic.class)
				.setParameter(1, termId)
				.setParameter(2, false)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setParameter(4, true);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Topic> result = query.getResultStream();
		return new Page<Topic>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}
	private long findAllByArticleCount(long termId) {
		try {
			return entityManager.createQuery("SELECT COUNT(t) FROM Topic t WHERE t.boardId = ?1 AND t.suggest = ?2 AND t.status != ?3 AND t.article = ?4", Long.class)
					.setParameter(1, termId)
					.setParameter(2, false)
					.setParameter(3, ForumEntityStatusEnum.DELETE)
					.setParameter(4, true)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByArticleCount][TopicDao]", e);
			}
		}
		return 0L;
	}
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> removeTermArticle(long id, long termId) {
		try{
			int affect = entityManager.createQuery("UPDATE Topic t SET t.status = ?1 WHERE t.id = ?2 AND t.boardId = ?3 AND t.suggest = ?4").setParameter(1, ForumEntityStatusEnum.DELETE).setParameter(2, id).setParameter(3, termId).setParameter(4, false).executeUpdate();
			return affect == 1?Optional.of(true):Optional.empty();
		}catch(Exception e){
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}
	@Override
	public Optional<Topic> findOneArticleForTerm(long termId) {
		Topic topic=null;
		try{
			topic = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 ORDER BY t.entryDateTime ASC", Topic.class)
					.setParameter(1, termId)
					.setMaxResults(1)
					.getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.ofNullable(topic);
	}
	@Override
	public Optional<Topic> findNextTermArticle(long termId, long articleId) {
		Topic topic=null;
		try{
			topic = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.id > ?2 AND t.status != ?3 ORDER BY t.id ASC", Topic.class)
				.setParameter(1, termId)
				.setParameter(2, articleId)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setMaxResults(1)
				.getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.ofNullable(topic);
	}

	@Override
	public Optional<Topic> findPrevTermArticle(long termId, long articleId) {
		Topic topic=null;
		try{
			topic = entityManager.createQuery("SELECT t FROM Topic t WHERE t.boardId = ?1 AND t.id < ?2 AND t.status != ?3 ORDER BY t.id DESC", Topic.class)
				.setParameter(1, termId)
				.setParameter(2, articleId)
				.setParameter(3, ForumEntityStatusEnum.DELETE)
				.setMaxResults(1)
				.getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.ofNullable(topic);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Topic> pushTopic(Topic entity, Posts posts, Album album) {
		entityManager.persist(entity);
		entityManager.flush();
		if (entity.getId() <= 0) {
			return Optional.empty();
		}
		Topic tr = Optional.of(entity).orElseGet(Topic::new);
		if (null != posts) {
			posts.setTopicId(entity.getId());
			entityManager.persist(posts);
			tr.setContent(posts);
		}
		if (null != album) {
			album.setTopicId(entity.getId());
			entityManager.persist(album);
			entityManager.flush();
			if (album.getId() > 0) {
				editAlbum(entity.getId(), album.getId());
			}
			tr.setAlbum(album);
		}
		return Optional.of(tr);
	}
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void modifyTopic(long topicId, String title, String content, Album album, Set<TopicTag> tags, long modifyMember, String modifyMemberNickname) {
        int affect = entityManager.createQuery("UPDATE Topic t SET t.title = ?1 WHERE t.id = ?2").setParameter(1, title).setParameter(2, topicId).executeUpdate();
        boolean symbol = false;
        if(affect == 1){
            affect = entityManager.createQuery("UPDATE Posts p SET p.content = ?1, p.modifyMemberId = ?2, p.modifyMemberNickname = ?3, p.modifyDateTime = ?4 WHERE p.topicId = ?5 AND p.reply = ?6")
                    .setParameter(1, content)
                    .setParameter(2, modifyMember)
                    .setParameter(3, modifyMemberNickname)
                    .setParameter(4, LocalDateTime.now())
                    .setParameter(5, topicId)
                    .setParameter(6, false)
                    .executeUpdate();
            symbol = affect == 1;
        }
        if(symbol && null!=album){
            //原来没有像册
            if(album.getId() == 0){
                entityManager.persist(album);
            }else{
                entityManager.merge(album);
            }
            entityManager.flush();
            symbol = editAlbum(topicId, album.getId()).orElse(false);
        }
        if(symbol && null!=tags && !tags.isEmpty()){
            topicTagDao.batchSave(topicId, tags);
        }
    }
}
