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

import java.io.File;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openqa.selenium.By;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.apobates.forum.core.api.ImageIOMeta;
import com.apobates.forum.core.api.TagRelateTopic;
import com.apobates.forum.core.api.TopicFileCache;
import com.apobates.forum.core.api.TopicFileCacheHandler;
import com.apobates.forum.core.api.dao.AlbumDao;
import com.apobates.forum.core.api.dao.BoardDao;
import com.apobates.forum.core.api.dao.BoardStatsDao;
import com.apobates.forum.core.api.dao.PostsDao;
import com.apobates.forum.core.api.dao.TopicActionCollectionDao;
import com.apobates.forum.core.api.dao.TopicCategoryDao;
import com.apobates.forum.core.api.dao.TopicConfigDao;
import com.apobates.forum.core.api.dao.TopicDao;
import com.apobates.forum.core.api.dao.TopicStatsDao;
import com.apobates.forum.core.api.dao.TopicTagDao;
import com.apobates.forum.core.api.service.TopicService;
import com.apobates.forum.core.entity.Album;
import com.apobates.forum.core.entity.AlbumPicture;
import com.apobates.forum.core.entity.Board;
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.TopicCategory;
import com.apobates.forum.core.entity.TopicConfig;
import com.apobates.forum.core.entity.TopicStats;
import com.apobates.forum.core.entity.TopicTag;
import com.apobates.forum.core.impl.event.ForumEventPublisher;
import com.apobates.forum.core.impl.event.PlugTopicPublishEvent;
import com.apobates.forum.core.impl.event.TopicMoveEvent;
import com.apobates.forum.core.impl.event.TopicPublishEvent;
import com.apobates.forum.core.plug.AbstractPlugTopic;
import com.apobates.forum.core.plug.PlugTopic;
import com.apobates.forum.core.tag.AbstractTagRelateQueryExecutor;
import com.apobates.forum.core.tag.RelateWordStats;
import com.apobates.forum.core.tag.TagExtraction;
import com.apobates.forum.core.tag.TagRelateResult;
import com.apobates.forum.core.tag.nlp.TagNLPExtraction;
import com.apobates.forum.core.tag.nlp.WordNatureEnum;
import com.apobates.forum.core.tag.nlp.relate.TagRelateDistance;
import com.apobates.forum.decorater.ForumEncoder;
import com.apobates.forum.decorater.Posts.ForumPostsDecorator;
import com.apobates.forum.event.elderly.ActionDescriptor;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.DateTimeUtils;
import com.apobates.forum.utils.lang.TriFunction;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;
import com.apobates.forum.utils.selenium.WebDriverCaptureScreen;

@Service
@CacheConfig(cacheNames = "topicCache")
public class TopicServiceImpl implements TopicService{
	@Autowired
	private TopicDao topicDao;
	@Autowired
	private TopicStatsDao topicStatsDao;
	@Autowired
	private BoardStatsDao boardStatsDao;
	@Autowired
	private PostsDao postsDao;
	@Autowired
	private TopicConfigDao topicConfigDao;
	@Autowired
	private TopicActionCollectionDao topicActionCollectionDao;
	@Autowired
	private BoardDao boardDao;
	@Autowired
	private TopicTagDao topicTagDao;
	@Autowired
	private TopicCategoryDao topicCategoryDao;
	@Autowired
	private AlbumDao albumDao;
	@Autowired
	private ForumEventPublisher forumEventPublisher;
	private final static Logger logger = LoggerFactory.getLogger(TopicServiceImpl.class);
	
	@Cacheable(key="#id", unless="#result==null")
	@Override
	public Optional<Topic> get(long id) { //[TC]T1
		logger.info("[TopicSRV]get one arg:"+id);
		if(id>0){
			return topicDao.findOne(id);
		}
		return Optional.empty();
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_TOP)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> editTop(long id, ActionEventCulpritor culpritor) { //[TC]T1-R1
		return topicDao.editTop(id, true);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_BEST)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> editGoods(long id, ActionEventCulpritor culpritor) { //[TC]T1-R2
		return topicDao.editGoods(id, true);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_LOCK)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> lock(long id, ActionEventCulpritor culpritor) { //[TC]T1-R3
		return topicDao.editStatus(id, ForumEntityStatusEnum.LOCKED);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_UNLOCK)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> releaseLock(long id, ActionEventCulpritor culpritor) { //[TC]T1-R4
		//锁定以前是什么
		return topicDao.editStatus(id, ForumEntityStatusEnum.ACTIVE);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_DEL)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> remove(long id, ActionEventCulpritor culpritor) { //[TC]T1-R5
		return topicDao.editStatus(id, ForumEntityStatusEnum.DELETE);
	}
	
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> removeTop(long id, ActionEventCulpritor culpritor) { //[TC]T1-R6
		return topicDao.editTop(id, false);
	}
	
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> removeGoods(long id, ActionEventCulpritor culpritor) { //[TC]T1-R7
		return topicDao.editGoods(id, false);
	}
	
	@CacheEvict(key = "#id")
	@Override
	public Optional<Boolean> edit(long id, String title, String content, String[] keywords, ImageIOMeta imageIO, ActionEventCulpritor culpritor) { // [TC]T1-R8
		Topic topic = get(id).orElseThrow(() -> new IllegalArgumentException("指定的话题不存在或暂时无法访问"));
		// 无HTML tag,Emoji安全
		final String encodeTitle = new ForumEncoder(title).noneHtmlTag().parseEmoji().getContent(); // ST1
		final ForumEncoder postsEncode = new ForumEncoder(content).encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
		// 话题编辑时在有图->无图|无图->有图切换时错误
		String modifyTopicContent = postsEncode.parseEmoji().getContent(); // ST2
		// 回复像册
		Album album = null;
		List<AlbumPicture> aps = postsEncode.extractUploadImageLink().stream()
				.map(imgLinkDigest -> new AlbumPicture(imgLinkDigest.getUrl(), imgLinkDigest.getAnchor(), imgLinkDigest.getRanking()))
				.collect(Collectors.toList());
		if (null != aps && !aps.isEmpty()) { // 重新创建像册|重新设置话题关联的像册
			// 是否已经存在像册了
			Optional<Album> topicAlbum = albumDao.findOneByTopic(id); // ST3
			if (!topicAlbum.isPresent()) {
				Posts oneFloorPosts = new Posts();
				oneFloorPosts.setVolumesId(topic.getVolumesId());
				oneFloorPosts.setBoardId(topic.getBoardId());
				oneFloorPosts.setMemberId(topic.getMemberId());
				oneFloorPosts.setMemberNickname(topic.getMemberNickname());
				album = Album.createAlbum(aps, oneFloorPosts, id, encodeTitle).orElse(null);
			} else {
				Album existTopicAlbum = topicAlbum.get();
				existTopicAlbum.getPictures().addAll(aps);
				album = existTopicAlbum;
			}
		}
		// 标签编辑
		Set<TopicTag> tags = buildTopicTag(id, title, content, keywords);// ST4
		return modifyTopic(topic.getId(), encodeTitle, modifyTopicContent, album, tags, culpritor.getMemberId(), culpritor.getMemberNickname());
	}

	@ActionDescriptor(action = ForumActionEnum.TOPIC_MOVE)
	@CacheEvict(key = "#id")
	@Override
	public Optional<Boolean> move(long id, long boardId, long targetBoardId, ActionEventCulpritor culpritor) throws IllegalStateException { // [TC]T1-R9
		logger.info("[MTV][1]话题: " + id + ", 现属版块: " + boardId + ", 移动至版块: " + targetBoardId);
		Topic topic = Optional.ofNullable(get(id, boardId)).orElseThrow(() -> new IllegalStateException("话题不存在或暂时无法访问"));
		//
		Board targetBoardObj = boardDao.findOne(targetBoardId).orElseThrow(() -> new IllegalArgumentException("目标版块不存在或暂时无法访问"));
		logger.info("[MTV][3]话题开始移动");

		int affect = topicDao.moveTopic(id, targetBoardObj.getId(), targetBoardObj.getVolumesId());
		if (affect >= 1) {
			// 统计
			logger.info("[MTV][11.1]话题移动成功,开始平衡统计");
			// 原版块话题-1,新版块话题+1
			// 原版块回复-d.successValue,新版块回复+d.successValue
			boardStatsDao.balanceTopicPosts(targetBoardId, boardId, affect); // 1话题的内容
			forumEventPublisher.publishMoveTopicEvent(new TopicMoveEvent(this, topic, topic.getBoard(), targetBoardObj, culpritor.getMemberNickname()));
			return Optional.of(true);
		}
		throw new IllegalStateException("话题移动操作失败");
	}
	
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_CONFIG_EDIT)
	@CacheEvict(key="'topic_config_'+#id")
	@Override
	public Optional<Boolean> editTopicConfig(long id, TopicConfig updateConfig, long configId, ActionEventCulpritor culpritor)throws IllegalStateException { //[TC]T2-R1
		TopicConfig tc = topicConfigDao.findOne(configId).orElseThrow(()->new IllegalStateException("话题配置文件不存在"));
		tc.setPrivacy(updateConfig.isPrivacy());
		tc.setReply(updateConfig.isReply());
		tc.setNotify(updateConfig.isNotify());
		tc.setReadMinScore(updateConfig.getReadMinScore());
		tc.setReadLowMemberGroup(updateConfig.getReadLowMemberGroup());
		tc.setReadLowMemberRole(updateConfig.getReadLowMemberRole());
		tc.setReadLowMemberLevel(updateConfig.getReadLowMemberLevel());
		
		tc.setWriteMinScore(updateConfig.getWriteMinScore());
		tc.setWriteLowMemberGroup(updateConfig.getWriteLowMemberGroup());
		tc.setWriteLowMemberRole(updateConfig.getWriteLowMemberRole());
		tc.setWriteLowMemberLevel(updateConfig.getWriteLowMemberLevel());
		tc.setAtomPoster(updateConfig.isAtomPoster());
		
		return topicConfigDao.edit(tc);
	}
	
	@Override
	public Topic get(long id, long board, int boardGroupId) {
		return topicDao.findOneForIndex(id);
	}
	
	@Override
	public List<Topic> getRecentRelateContent(int size) { //11
		Stream<Topic> data = topicDao.findAllOfRecent(size).filter(t -> t.isNormal());
		return associatedBoardAndStatsAsync(data.collect(Collectors.toList())).collect(Collectors.toList());
	}

	@ActionDescriptor(action=ForumActionEnum.TOPIC_LIKED)
	@Override
	public Optional<Boolean> like(long id, ActionEventCulpritor culpritor)throws IllegalStateException { //[TC]T1-R6
		//不能重复点赞
		boolean isLiked = isLiked(id, culpritor.getMemberId());
		if(!isLiked){
			throw new IllegalStateException("已经点过赞了");
		}
		return topicStatsDao.plusLikes(id, culpritor.getMemberId());
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_LIKED_CANCEL)
	@Override
	public Optional<Boolean> removeLike(long id, ActionEventCulpritor culpritor)throws IllegalStateException { //[TC]T1-R7
		//不能重复取消赞
		Long data = topicActionCollectionDao.countMemberAction(culpritor.getMemberId(), id, ForumActionEnum.TOPIC_LIKED_CANCEL);
		if(data!=null && data>0){
			throw new IllegalStateException("点赞已经取消");
		}
		return topicStatsDao.negateLikes(id, culpritor.getMemberId());
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_FAVORITE)
	@Override
	public Optional<Boolean> favorite(long id, ActionEventCulpritor culpritor)throws IllegalStateException {
		//更新话题的收藏统计|重复收藏
		boolean isFavorited = isFavorited(id, culpritor.getMemberId());
		if(!isFavorited){
			throw new IllegalStateException("收藏记录已经存在");
		}
		return topicStatsDao.plusFavorites(id, culpritor.getMemberId());
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_FAVORITE_CANCEL)
	@Override
	public Optional<Boolean> removeFavorite(long id, ActionEventCulpritor culpritor)throws IllegalStateException {
		//更新话题的收藏统计|重复收藏
		Long data = topicActionCollectionDao.countMemberAction(culpritor.getMemberId(), id, ForumActionEnum.TOPIC_FAVORITE_CANCEL);
		if(data!=null && data>0){
			throw new IllegalStateException("已经取消收藏");
		}
		return topicStatsDao.negateFavorites(id, culpritor.getMemberId());
	}

	@ActionDescriptor(action=ForumActionEnum.TOPIC_BROWSE)
	@Override
	public Optional<Boolean> browse(long id, ActionEventCulpritor culpritor)throws IllegalStateException {
		//更新话题的收藏统计
		//是否达到火贴的标准了
		return topicStatsDao.updateDisplaies(id);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_PUBLISH)
	@Override
	public long create(int volumesId, long boardId, String title, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) {
		return create(volumesId, boardId, 0, title, content, imageIO, culpritor);
	}
	
	@ActionDescriptor(action = ForumActionEnum.TOPIC_PUBLISH, isRedress=false)
	@Override
	public long create(int volumesId, long boardId, int categoryId, String title, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) {
		// 编码回复内容
		final ForumEncoder postsEncode = new ForumEncoder(content).encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
		// -------------------------------------------------------------------------
		// 计算TopicCategory
		TopicCategory tc = TopicCategory.empty();
		if (categoryId > 0) {
			tc = topicCategoryDao.findOne(categoryId).orElse(TopicCategory.empty());
		}
		// 无HTML tag,Emoji安全
		final String encodeTitle = new ForumEncoder(title).noneHtmlTag().parseEmoji().getContent();
		Topic topic = new Topic(encodeTitle, culpritor.getMemberId(), culpritor.getMemberNickname(), volumesId, boardId, culpritor.getIpAddr());
		topic.setTopicCategoryName(tc.getNames());
		topic.setTopicCategoryValue(tc.getValue());
		topic.setSummary(postsEncode.noneHtmlTag().parseEmoji().getContent(200));
		// 标签(原始数据：title,content)
		Set<TopicTag> tages = buildTopicTag(-2L, title, content); // 需要回填topic.id
		// 话题内容
		Posts oneFloorPosts = new Posts(postsEncode.relaxedHtmlTag().parseEmoji().getContent(), culpritor.getMemberId(), culpritor.getMemberNickname(), 0L, volumesId, boardId, culpritor.getIpAddr(), false); // 需要回填topic.id
		// 话题像册
		Album album = null;
		List<AlbumPicture> aps = postsEncode.extractUploadImageLink().stream()
				.map(imgLinkDigest -> new AlbumPicture(imgLinkDigest.getUrl(), imgLinkDigest.getAnchor(), imgLinkDigest.getRanking()))
				.collect(Collectors.toList());
		if (null != aps && !aps.isEmpty()) {
			album = Album.createAlbum(aps, oneFloorPosts, -1L, encodeTitle).orElse(null); // 需要回填topic.id
		}
		// 最后一步
		Optional<Topic> tr = storeReplicaTopic(topic, oneFloorPosts, album, tages, null, culpritor.getUserAgent());
		tr.ifPresent(this::generalTopicEvent);
		return tr.map(Topic::getId).orElse(0L);
	}
	//从标题和内容中汲取标签[20200109]
	private Set<TopicTag> buildTopicTag(long topicId, String title, String content, String... keywords){
		TagExtraction tagExtraction = new TagNLPExtraction(10, WordNatureEnum.Noun,WordNatureEnum.Name,WordNatureEnum.Region,WordNatureEnum.Organization,WordNatureEnum.EFB);
		//构造关建词
		Map<String,Integer> tages = tagExtraction.extract(title, content, keywords);
		if(null==tages || tages.isEmpty()){
			return Collections.emptySet();
		}
		//完成TopicTag
		return tages.entrySet().stream().map(entry->new TopicTag(entry.getKey(), entry.getValue(), topicId)).collect(Collectors.toSet());
	}
	
	@Override
	public long create(int volumesId, long boardId, String categoryName, String categoryValue, String title, String content, String[] keywords, ImageIOMeta imageIO, ActionEventCulpritor culpritor) {
		int boardGroupId = (volumesId > 0) ? volumesId : 0;
		// 编码回复内容
		final ForumEncoder postsEncode = new ForumEncoder(content).encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
		// 无HTML tag,Emoji安全
		final String encodeTitle = new ForumEncoder(title).noneHtmlTag().parseEmoji().getContent();
		Topic topic = new Topic(encodeTitle, culpritor.getMemberId(), culpritor.getMemberNickname(), boardGroupId, boardId, culpritor.getIpAddr());
		topic.setTopicCategoryName(categoryName);
		topic.setTopicCategoryValue(categoryValue);
		topic.setSummary(postsEncode.noneHtmlTag().parseEmoji().getContent(200));
		// 标签
		Set<TopicTag> tages = buildTopicTag(-2L, title, content, keywords); // 需要回填topic.id
		// 话题内容
		Posts oneFloorPosts = new Posts(postsEncode.relaxedHtmlTag().parseEmoji().getContent(), culpritor.getMemberId(), culpritor.getMemberNickname(), 0L, volumesId, boardId, culpritor.getIpAddr(), false); // 需要回填topic.id
		// 话题像册
		Album album = null;
		List<AlbumPicture> aps = postsEncode.extractUploadImageLink().stream()
				.map(imgLinkDigest -> new AlbumPicture(imgLinkDigest.getUrl(), imgLinkDigest.getAnchor(), imgLinkDigest.getRanking()))
				.collect(Collectors.toList());
		if (null != aps && !aps.isEmpty()) {
			album = Album.createAlbum(aps, oneFloorPosts, -1L, encodeTitle).orElse(null); // 需要回填topic.id
		}
		// 最后一步
		Optional<Topic> tr = storeReplicaTopic(topic, oneFloorPosts, album, tages, null, culpritor.getUserAgent());
		tr.ifPresent(this::generalTopicEvent);
		return tr.map(Topic::getId).orElse(0L);
	}

	@Override
	public long createTermArticle(int sectionId, long termId, String title, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) {
		// 编码回复内容
		final ForumEncoder postsEncode = new ForumEncoder(content).encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
		// -------------------------------------------------------------------------
		// 无HTML tag,Emoji安全
		final String encodeTitle = new ForumEncoder(title).noneHtmlTag().parseEmoji().getContent();
		Topic article = Topic.buildArticle(encodeTitle, culpritor.getMemberId(), culpritor.getMemberNickname(), sectionId, termId, culpritor.getIpAddr());
		article.setSummary(postsEncode.noneHtmlTag().parseEmoji().getContent(200));
		// 文章内容
		Posts oneFloorPosts = new Posts(postsEncode.relaxedHtmlTag().parseEmoji().getContent(), culpritor.getMemberId(), culpritor.getMemberNickname(), 0L, sectionId, termId, culpritor.getIpAddr(), false); // 需要回填topic.id
		// 文章像册
		Album album = null;
		List<AlbumPicture> aps = postsEncode.extractUploadImageLink().stream()
				.map(imgLinkDigest -> new AlbumPicture(imgLinkDigest.getUrl(), imgLinkDigest.getAnchor(), imgLinkDigest.getRanking()))
				.collect(Collectors.toList());
		if (null != aps && !aps.isEmpty()) {
			album = Album.createAlbum(aps, oneFloorPosts, -1L, encodeTitle).orElse(null); // 需要回填topic.id
		}
		// 文章配置
		TopicConfig config = TopicConfig.originConfig(-1L); // 需要回填topic.id
		// 最后一步
		Optional<Topic> tr = storeReplicaTopic(article, oneFloorPosts, album, null, config, culpritor.getUserAgent());
		tr.ifPresent(this::generalTopicEvent);
		return tr.map(Topic::getId).orElse(0L);
	}
	
	@Override
	public Page<Topic> getAll(long boardId, Pageable pageable) { //1
		Page<Topic> data = topicDao.findAllByBoard(boardId, pageable);
		//
		final long total = data.getTotalElements();
		final Stream<Topic> result = associationTopicStatsAsync(data.getResult().collect(Collectors.toList()));
		//
		return new Page<Topic>(){
			@Override
			public long getTotalElements() {
				return total;
			}
			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}

	@Override
	public Page<Topic> getAll(long boardId, String categoryValue, Pageable pageable) {
		if(!Commons.isNotBlank(categoryValue)){
			return getAll(boardId, pageable);
		}
		Page<Topic> data = topicDao.findAllByBoard(boardId, categoryValue, pageable);
		//
		final long total = data.getTotalElements();
		final Stream<Topic> result = associationTopicStatsAsync(data.getResult().collect(Collectors.toList()));
		//
		return new Page<Topic>(){
			@Override
			public long getTotalElements() {
				return total;
			}
			@Override
			public Stream<Topic> getResult() {
				return result;
			}
		};
	}

	@Override
	public Stream<Topic> getRecentForBoard(long boardId, int size) {
		return topicDao.findAllByBoard(boardId, size);
	}

	@Override
	public Stream<Topic> getRecentForBoardIgnoreStatus(long boardId, int size) {
		return topicDao.findAllByBoardIgnoreStatus(boardId, size);
	}

	@Override
	public Stream<Topic> getTopForBoard(long boardId) {
		return associationTopicStatsAsync(topicDao.findAllByBoardOfTop(boardId).collect(Collectors.toList()));
	}

	@Override
	public Page<Topic> getAllForMember(long memberId, Pageable pageable) { //2
		Page<Topic> rs = topicDao.findAllByMember(memberId, pageable);
		final Stream<Topic> data = associationTopicStatsAsync(rs.getResult().collect(Collectors.toList()));
		
		return new Page<Topic>(){
			@Override
			public long getTotalElements() {
				return rs.getTotalElements();
			}
			@Override
			public Stream<Topic> getResult() {
				return data;
			}
			
		};
	}

	@Override
	public long countAllForMember(long memberId) {
		return topicDao.findAllByMemberCount(memberId);
	}

	@Override
	public Stream<Topic> getRecentForMember(long memberId, int size) { // 3
		Stream<Topic> data = topicDao.findAllByMember(memberId, size);
		return associatedBoardAsync(data.collect(Collectors.toList()));
	}

	@Override
	public Stream<Topic> getRecent(int size) {
		return topicDao.findAllOfRecent(size);
	}
	
	@Override
	public Stream<Topic> getRecentIgnoreCondition(int size) {
		return topicDao.findAllOfRecentIgnoreCondition(size);
	}

	@Override
	public Topic getTopicStats(final long id) { //4
		return queryTopicStatic(id).orElse(null);
	}

	@Override
	public Topic get(final long id, final long boardId) {
		Optional<Topic> opt = get(id);
		opt.ifPresent(t->{
			t.setBoard(boardDao.findOne(boardId).orElse(null));
		});
		return opt.orElse(null);
	}
	@Override
	public Topic getTopicConfig(final long id) { //10
		Optional<Topic> opt = get(id);
		opt.ifPresent(t->{
			t.setConfigure(topicConfigDao.findOneByTopic(id).orElse(null));
		});
		return opt.orElse(null);
	}
	@Override
	public Topic getTopicContentAndStats(final long id, Function<Long, Optional<Member>> topicAuthorFun) {
		Optional<Topic> topic = queryTopicStatic(id);
		topic.ifPresent(t->{
			Optional<Posts> oneFloor = postsDao.findOneByOneFloor(id);
			oneFloor.ifPresent(p->{
				if(null != topicAuthorFun){
					p.setMember(topicAuthorFun.apply(p.getMemberId()).orElse(null));
				}
				t.setContent(p);
			});
		});
		return topic.orElse(null);
	}
	
	@Override
	public Topic getTopicContent(final long id, Function<Long, Optional<Member>> topicAuthorFun) {
		Optional<Topic> topic = get(id);
		topic.ifPresent(t->{
			Optional<Posts> oneFloor = postsDao.findOneByOneFloor(id);
			oneFloor.ifPresent(p->{
				if(null != topicAuthorFun){
					p.setMember(topicAuthorFun.apply(p.getMemberId()).orElse(null));
				}
				t.setContent(p);
			});
		});
		return topic.orElse(null);
	}
	
	@Override
	public Topic getTopicContentAndStats(final long id, final ImageIOMeta imageIO) {
		Optional<Topic> opt = queryTopicStatic(id);
		Function<Posts, String> fun = p->{
			ForumPostsDecorator pd = ForumPostsDecorator.init(p)
					.decorateSmileImage(imageIO.getImageBucketDomain(), imageIO.getSmileyDirectName())
					.decorateUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName(), true, null);
			return pd.getContent();
		};
		return decorateTopicContent(opt, ()-> postsDao.findOneByOneFloor(id).orElse(Posts.empty("内容不存在或暂时无法访问")), fun);
	}

	@Override
	public Topic getTopicContentAndStatsForRSS(final long id, final ImageIOMeta imageIO, final String dictionaryFilePath) {
		Optional<Topic> opt = queryTopicStatic(id);
		Function<Posts, String> fun = p->{
			ForumPostsDecorator pd = ForumPostsDecorator.init(p)
					.block("作者被禁止发言或内容自动屏蔽")
					.sensitiveWord(dictionaryFilePath)
					.decorateSmileImage(imageIO.getImageBucketDomain(), imageIO.getSmileyDirectName())
					.decorateUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName(), true, null)
					.modify("回复最近由管理人员进行过编辑");
			return pd.getContent();
		};
		return decorateTopicContent(opt, ()-> postsDao.findOneByOneFloor(id).orElse(Posts.empty("内容不存在或暂时无法访问")), fun);
	}
	
	@Override
	public Topic getTermArticleContent(final long id, final ImageIOMeta imageIO) {
		Optional<Topic> opt = get(id);
		Function<Posts, String> fun = p->{
			ForumPostsDecorator pd = ForumPostsDecorator.init(p)
					.block("内容自动屏蔽")
					.decorateSmileImage(imageIO.getImageBucketDomain(), imageIO.getSmileyDirectName())
					.decorateUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName(), true, null);
			return pd.getContent();
		};
		return decorateTopicContent(opt, ()-> postsDao.findOneByOneFloor(id).orElse(Posts.empty("内容不存在或暂时无法访问")), fun);
	}
	
	@Override
	public Topic getFirstArticleForTerm(long termId, final ImageIOMeta imageIO) {
		Optional<Topic> opt = topicDao.findOneArticleForTerm(termId);
		opt.ifPresent(t->{
			Posts oneFloor = postsDao.findOneByOneFloor(t.getId()).orElse(Posts.empty("内容不存在或暂时无法访问"));
			//-------------------------------解码表情
			ForumPostsDecorator pd = ForumPostsDecorator.init(oneFloor)
											.decorateSmileImage(imageIO.getImageBucketDomain(), imageIO.getSmileyDirectName())
											.decorateUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName(), true, null);
			oneFloor.setContent(pd.getContent());
			//----------------------------------
			t.setContent(oneFloor);
		});
		return opt.orElse(null);
	}
	@Override
	public Page<Topic> getAllForMemberReply(long memberId, Pageable pageable) { //5
		Page<Topic> rs = topicDao.findAllByMemberReply(memberId, pageable);
		final Stream<Topic> data = associationTopicStatsAsync(rs.getResult().collect(Collectors.toList()));
		return new Page<Topic>(){
			@Override
			public long getTotalElements() {
				return rs.getTotalElements();
			}
			@Override
			public Stream<Topic> getResult() {
				return data;
			}
		};
	}

	@Override
	public long countAllForMemberReply(long memberId) {
		return topicDao.findAllByMemberReplyCount(memberId);
	}

	@Override
	public Stream<Topic> getAllForMemberReply(long memberId, int size) { // 7
		Stream<Topic> data = topicDao.findAllByMemberReply(memberId, size);
		return associatedBoardAsync(data.collect(Collectors.toList()));
	}

	@Override
	public Stream<Topic> getAllForMemberPopular(long memberId, int size) {
		Stream<Topic> data = topicDao.findAllByMemberPopular(memberId, size);
		return associatedBoardAndStatsAsync(data.collect(Collectors.toList()));
	}

	@Override
	public Stream<Topic> getRecent(int boardGroupId, int size) { //8
		Stream<Topic> data = topicDao.findAllOfRecent(boardGroupId, size);
		return associationTopicStatsAsync(data.collect(Collectors.toList()));
	}

	@Override
	public List<Topic> getRecentByUnixStamp(long boardId, int prevUnixStamp) { //9
		LocalDateTime prevDate = DateTimeUtils.getDateTimeByUnixTimestamp(prevUnixStamp);
		return associationTopicStatsAsync(topicDao.findAllRecentByBoard(boardId, prevDate).collect(Collectors.toList())).collect(Collectors.toList());
	}

	@Override
	public Stream<Topic> getGoodsForBoardGroup(int boardGroupId, int size) {
		return topicDao.findAllByBoardGroupOfGoods(boardGroupId, size);
	}

	@Override
	public Stream<Topic> getMaxReplyForBoardGroup(int boardGroupId, int size) {
		return topicDao.findAllByBoardGroupByMaxReply(boardGroupId, size).stream();
	}

	@Override
	public Stream<Topic> getHot(int size) {
		return topicDao.findAllByMaxReply(size).stream();
	}

	@Override
	public Map<Long, Long> statsBoardTopicesForToday() {
		LocalDateTime start = DateTimeUtils.getTodayEarlyMorning();
		return statsBoardTopicesForDate(start, LocalDateTime.now());
	}

	@Override
	public Map<Long, Long> statsBoardTopicesForDate(LocalDateTime start, LocalDateTime finish) {
		return topicDao.statsBoardTopicSize(start, finish);
	}

	@Override
	public Stream<Topic> getRecentReply(int size) {
		return topicDao.findAllByReplyDate(size);
	}

	@Override
	public TreeMap<String, Long> groupTopicesForDate(LocalDateTime start, LocalDateTime finish) {
		return topicDao.groupTopicSize(start, finish);
	}

	@Override
	public EnumMap<ForumEntityStatusEnum, Long> groupTopicesForStatus() {
		return topicDao.groupTopicesForStatus();
	}

	@Override
	public Map<String, Long> groupTopicesForCategory() {
		return topicDao.groupTopicesForCategory();
	}

	@Override
	public Map<Board, Long> groupTopicesForBoard() {
		Map<Long,Long> rs = topicDao.groupTopicesForBoard();
		if(rs==null || rs.isEmpty()){
			return Collections.emptyMap();
		}
		Map<Long, Board> data = boardDao.findAllById(rs.keySet()).collect(Collectors.toMap(Board::getId, Function.identity()));
		
		Map<Board, Long> result = new HashMap<>();
		for(Entry<Long,Long> entry : rs.entrySet()){
			Board tmp = data.get(entry.getKey());
			if(tmp == null){
				continue;
			}
			result.put(tmp, entry.getValue());
		}
		return result;
	}
	
	@Override
	public Stream<Topic> getAllByBoard(long boardId, ForumEntityStatusEnum status) {
		return topicDao.findAllByBoard(boardId, status);
	}

	@Override
	public Stream<TagRelateTopic> getRelateTopic(long id, int size) {
		// ST1找到需要计算的集合
		logger.info("[QR]Step 1 get Topic tages");
		List<TopicTag> tages = topicTagDao.findAllRelateTopic(id, size * 100).collect(Collectors.toList());
		if (tages.isEmpty()) {
			logger.info("[QR]Step 1.1 Topic tages is empty");
			return Stream.empty();
		}
		Map<Boolean, List<TopicTag>> rs = tages.stream().collect(Collectors.partitioningBy(tt -> tt.getTopicId() == id));// 分区:true为话题的标签,false为计算的源
		if (rs.get(false).isEmpty()) {
			logger.info("[QR]Step 2.1 Tag Relate source is empty");
			return Stream.empty();
		}
		// ST2计算距离.以决定哪个在前哪个在后
		logger.info("[QR]Step 3 Relate Tag Executor");
		List<RelateWordStats> sourceRS = rs.get(false).parallelStream().map(tt -> new RelateWordStats(tt.getTopicId(), tt.getNames(), tt.getRates())).collect(Collectors.toList());
		AbstractTagRelateQueryExecutor trqe = new TagRelateDistance(rs.get(true).stream().collect(Collectors.toMap(TopicTag::getNames, TopicTag::getRates))).load(sourceRS);
		// ST3排序返回结果
		logger.info("[QR]Step 4 Relate Topice expect size: " + size);
		Map<Long, TagRelateResult> relateTopices = trqe.getResult(size).collect(Collectors.toMap(TagRelateResult::getTopic, Function.identity()));
		if (relateTopices.isEmpty()) {
			logger.info("[QR]Step 4.1 Relate Topice is empty");
			return Stream.empty();
		}
		BiFunction<Topic, TagRelateResult, TagRelateTopic> bi = (t, trr) -> {
			return new TagRelateTopic(t, trr.getRanking(), trr.getSimilarity());
		};
		return topicDao.findAllById(relateTopices.keySet()).stream().map(topic -> bi.apply(topic, relateTopices.get(topic.getId())));
	}

	@Override
	public Page<Topic> getAllForTag(List<String> tagNames, Pageable pageable) {
		return topicDao.findAllByTag(tagNames, pageable);
	}
	
	@ActionDescriptor(action=ForumActionEnum.TOPIC_SHARE)
	@Override
	public Optional<File> getPoster(long id, String posterURL, TopicFileCache tfc, ActionEventCulpritor culpritor)throws IllegalStateException {
		Topic topic = get(id).orElse(Topic.empty(id));
		if(!Commons.isNotBlank(topic.getTitle())){
			throw new IllegalStateException("操作参数解析失败");
		}
		File posterCacheFile = tfc.getFile("poster.png", topic.getConnect(), new TopicFileCacheHandler(){
			@Override
			public Optional<File> create() {
				try{
					WebDriverCaptureScreen wdc = new WebDriverCaptureScreen(posterURL);
					File tmp = wdc.shot(By.id("poster"));
					return Optional.ofNullable(tmp);
				}catch(Exception e){
					return Optional.empty();
				}
			}
		});
		if(null == posterCacheFile){
			throw new IllegalStateException("海报文件计算失败");
		}
		return Optional.of(posterCacheFile);
	}

	@Override
	public boolean isLiked(long id, long memberId) {
		return topicActionCollectionDao.isLiked(id, memberId).orElse(false);
	}

	@Override
	public boolean isFavorited(long id, long memberId) {
		return topicActionCollectionDao.isFavorited(id, memberId).orElse(false);
	}
	/*子栏目中的文章*/
	@Override
	public Stream<Topic> getRecentTermArticle(int size) {
		return topicDao.findAllOfRecentTermArticle(size);
	}

	@Override
	public Page<Topic> getTermArticle(long termId, Pageable pageable) {
		return topicDao.findAllTermArticle(termId, pageable);
	}
	
	@Override
	public Optional<Boolean> removeTermArticle(long id, long termId, ActionEventCulpritor culpritor) {
		return topicDao.removeTermArticle(id, termId);
	}

	@Override
	public Map<String, Topic> getPrevNextTermArticle(long termId, long articleId) {
		Topic prevTopic = topicDao.findPrevTermArticle(termId, articleId).orElse(null);
		Topic nextTopic = topicDao.findNextTermArticle(termId, articleId).orElse(null);
		//
		Map<String, Topic> data = new HashMap<>();
		if(prevTopic != null){
			data.put("PREV", prevTopic);
		}
		if(nextTopic != null){
			data.put("NEXT", nextTopic);
		}
		return data;
	}

	@Override
	public Optional<Boolean> editTermArticle(long articleId, String title, String content, ImageIOMeta imageIO, ActionEventCulpritor culpritor) {
		Topic topic = get(articleId).orElseThrow(() -> new IllegalArgumentException("指定的文章不存在或暂时无法访问"));
		// 编辑标题: 无HTML tag,Emoji安全
		final String encodeTitle = new ForumEncoder(title).noneHtmlTag().parseEmoji().getContent(); // ST1
		final ForumEncoder postsEncode = new ForumEncoder(content).encodeUploadImage(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
		// 编辑话题内容
		String modifyTopicContent = postsEncode.parseEmoji().getContent(); // ST2
		// 回复像册
		Album album = null;
		List<AlbumPicture> aps = postsEncode.extractUploadImageLink().stream()
				.map(imgLinkDigest -> new AlbumPicture(imgLinkDigest.getUrl(), imgLinkDigest.getAnchor(), imgLinkDigest.getRanking()))
				.collect(Collectors.toList());
		if (null != aps && !aps.isEmpty()) { // 重新创建像册|重新设置话题关联的像册
			// 是否忆经存在像册了
			Optional<Album> topicAlbum = albumDao.findOneByTopic(articleId); // ST3
			if (!topicAlbum.isPresent()) {
				Posts oneFloorPosts = new Posts();
				oneFloorPosts.setVolumesId(topic.getVolumesId());
				oneFloorPosts.setBoardId(topic.getBoardId());
				oneFloorPosts.setMemberId(topic.getMemberId());
				oneFloorPosts.setMemberNickname(topic.getMemberNickname());
				album = Album.createAlbum(aps, oneFloorPosts, articleId, encodeTitle).orElse(null);
			} else {
				Album existTopicAlbum = topicAlbum.get();
				existTopicAlbum.getPictures().addAll(aps);
				album = existTopicAlbum; // ST5
			}
		}
		return modifyTopic(topic.getId(), encodeTitle, modifyTopicContent, album, null, culpritor.getMemberId(), culpritor.getMemberNickname());// ("文章编辑失败");
	}

	@Override
	public Optional<Topic> plug(AbstractPlugTopic topicBuildPlug) {
		final ActionEventCulpritor culpritor = topicBuildPlug.getCulpritor();
		String topicAuthor = (culpritor.getMemberId() > 0) ? culpritor.getMemberNickname() : String.format("Guest#%d", Commons.ipHashcode(culpritor.getIpAddr()));
		long topicAuthorId = (culpritor.getMemberId() > 0) ? culpritor.getMemberId() : 0L;
		Topic pt = new Topic(topicBuildPlug.getTitle(), topicAuthorId, topicAuthor, topicBuildPlug.getVolumes(), topicBuildPlug.getBoard(), culpritor.getIpAddr(), topicBuildPlug.getCategory().getNames(), topicBuildPlug.getCategory().getValue());
		pt.setSuggest(topicBuildPlug.isSuggest());
		// 话题内容
		// 需要回填topic.id
		Posts oneFloorPosts = new Posts(topicBuildPlug.getContent(), culpritor.getMemberId(), topicAuthor, 0L, pt.getVolumesId(), pt.getBoardId(), culpritor.getIpAddr(), false);
		// 话题配置
		TopicConfig config = topicBuildPlug.getConfig(); // 需要回填topic.id
		// 最后一步
		Optional<Topic> tr = storeReplicaTopic(pt, oneFloorPosts, null, null, config, culpritor.getUserAgent());
		// 发布事件
		final Object eventTarget = this;
		final ForumActionEnum action = topicBuildPlug.getAction();
		tr.ifPresent((Topic tr1) -> {
			forumEventPublisher.publishPlugTopicEvent(new PlugTopicPublishEvent(eventTarget, tr1, action, culpritor));
		});
		return tr;
	}

	@Override
	public Optional<Topic> plug(PlugTopic topicBuildPlug, final ForumActionEnum action, final ActionEventCulpritor culpritor) {
		String topicAuthor = (culpritor.getMemberId() > 0) ? culpritor.getMemberNickname() : String.format("Guest#%d", Commons.ipHashcode(culpritor.getIpAddr()));
		long topicAuthorId = (culpritor.getMemberId() > 0) ? culpritor.getMemberId() : 0L;
		Topic pt = Topic.buildArticle(topicBuildPlug.getTitle(), topicAuthorId, topicAuthor, topicBuildPlug.getVolumes(), topicBuildPlug.getBoard(), culpritor.getIpAddr());
		final ForumEncoder postsEncode = new ForumEncoder(topicBuildPlug.getContent());
		pt.setSummary(postsEncode.noneHtmlTag().parseEmoji().getContent(200));
		pt.setSuggest(topicBuildPlug.isSuggest());
		pt.setTopicCategoryName(topicBuildPlug.getCategory().getNames());
		pt.setTopicCategoryValue(topicBuildPlug.getCategory().getValue());
		// 话题内容
		// 需要回填topic.id
		Posts oneFloorPosts = new Posts(postsEncode.relaxedHtmlTag().parseEmoji().getContent(), culpritor.getMemberId(), topicAuthor, 0L, pt.getVolumesId(), pt.getBoardId(), culpritor.getIpAddr(), false);
		// 话题配置
		TopicConfig config = topicBuildPlug.getConfig(); // 需要回填topic.id
		// 最后一步
		Optional<Topic> tr = storeReplicaTopic(pt, oneFloorPosts, null, null, config, culpritor.getUserAgent());
		// 发布事件
		final Object eventTarget = this;
		tr.ifPresent((Topic tr1) -> {
			forumEventPublisher.publishPlugTopicEvent(new PlugTopicPublishEvent(eventTarget, tr1, action, culpritor));
		});
		return tr;
	}
	
    //[异步]级联话题统计 && 所属的版块
    private Stream<Topic> associatedBoardAndStatsAsync(final List<Topic> topices){
        //20200710
        //Key=话题的Id,Value=版块的Id
        final Map<Long, Long> allQueryParam = Commons.collectMap(topices.stream(), Topic::getId, Topic::getBoardId);
        if(null == allQueryParam || allQueryParam.isEmpty()){
            return Stream.empty();
        }
        final TriFunction<Topic, TopicStats, Board, Topic> action = (t, ts, b) ->{
            Optional<Topic> opt = Optional.ofNullable(t);
            if(null != b){
                opt.ifPresent(topt->topt.setBoard(b));
            }else{
                opt.ifPresent(topt->topt.setBoard(Board.empty(t.getBoardId())));
            }
            if(null != ts){
                opt.ifPresent(topt->topt.setStats(ts));
            }else{
            	opt.ifPresent(topt->topt.setStats(TopicStats.empty(t.getId(), t.getMemberId(), t.getMemberNickname())));
            }
            return opt.orElse(null);
        };
        CompletableFuture<Map<Long, TopicStats>> topicStats = CompletableFuture.supplyAsync(()->topicStatsDao.findAllByTopicId(allQueryParam.keySet()))
                                                .thenCompose((Stream<TopicStats> ts)->
                                                         CompletableFuture.supplyAsync(()->
                                                                ts.collect(Collectors.toMap(TopicStats::getTopicId, Function.identity()))));
        CompletableFuture<Map<Long, Board>> boards = CompletableFuture.supplyAsync(()->boardDao.findAllById(allQueryParam.values()))
                                                .thenCompose((Stream<Board> bs)->
                                                        CompletableFuture.supplyAsync(()->
                                                                bs.collect(Collectors.toMap(Board::getId, Function.identity()))));
        return topicStats.thenCombine(boards, (topicStatsMap, boardsMap)->{
            return topices.parallelStream().map(topic->action.apply(topic, topicStatsMap.get(topic.getId()), boardsMap.get(topic.getBoardId()))).filter(Objects::nonNull);
        }).join();
    }
    
    //[异步]级联话题统计
    private Stream<Topic> associationTopicStatsAsync(final List<Topic> topices) {
        if (null == topices || topices.isEmpty()) {
            return Stream.empty();
        }
        BiFunction<Topic, TopicStats, Topic> bi = (t, ts)->{
            Optional<Topic> opt = Optional.ofNullable(t);
            if(null != ts){
                opt.ifPresent(tto->tto.setStats(ts));
            }else{
                opt.ifPresent(tto->tto.setStats(TopicStats.empty(t.getId(), t.getMemberId(), t.getMemberNickname())));
            }
            return opt.orElse(null);
        };
        Map<Long, TopicStats> topicStatsMap = CompletableFuture.supplyAsync(()->topices.stream().map(Topic::getId).collect(Collectors.toSet()))
                                .thenCompose(topicIdSet-> CompletableFuture.supplyAsync(()->topicStatsDao.findAllByTopicId(topicIdSet).collect(Collectors.toMap(TopicStats::getTopicId, Function.identity())))).join();
        Comparator<Topic> comparator = (topic, otherTopic) -> topic.getRankingDateTime().compareTo(otherTopic.getRankingDateTime());
        return topices.parallelStream().sorted(comparator.reversed()).map(topic->bi.apply(topic, topicStatsMap.get(topic.getId()))).filter(Objects::nonNull);
    }
    
    //[异步]级联话题所属的版块
    private Stream<Topic> associatedBoardAsync(final List<Topic> topices){
        if (null == topices || topices.isEmpty()) {
            return Stream.empty();
        }
        BiFunction<Topic, Board, Topic> bi = (t, b)->{
            Optional<Topic> opt = Optional.ofNullable(t);
            if(null != b){
                opt.ifPresent(tto->tto.setBoard(b));
            }else{
                opt.ifPresent(tto->tto.setBoard(Board.empty(t.getBoardId())));
            }
            return opt.orElse(null);
        };
        Map<Long, Board> boards = CompletableFuture.supplyAsync(()->topices.stream().map(Topic::getBoardId).collect(Collectors.toSet())).thenCompose(boardIdSet-> CompletableFuture.supplyAsync(()->boardDao.findAllById(boardIdSet).collect(Collectors.toMap(Board::getId, Function.identity())))).join();
        return topices.parallelStream().map(topic->bi.apply(topic, boards.get(topic.getBoardId()))).filter(Objects::nonNull);
    }
    
	private Optional<Topic> storeReplicaTopic(Topic topic, Posts content, Album album, Set<TopicTag> tages, TopicConfig config, String userAgent) {
		try {
			Topic tr = topicDao.pushTopic(topic, content, album).orElseGet(Topic::new);
			if (tr.getId() > 0) {
				final long topicId = tr.getId();
				if (null != tages && !tages.isEmpty()) {
					Set<TopicTag> tts = tages.stream().peek(tt -> tt.setTopicId(topicId)).collect(Collectors.toSet());
					tr.setTages(tts);
				}
				if (null != config) {
					config.setTopicId(topicId);
				}
				tr.setConfigure(config);
				tr.setUserAgent(userAgent);
				return Optional.of(tr);
			}
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("发布话题失败", e);
			}
		}
		return Optional.empty();
	}

	private Optional<Boolean> modifyTopic(long topicId, String title, String content, Album album, Set<TopicTag> tags, long modifyMember, String modifyMemberNickname) {
		try {
			topicDao.modifyTopic(topicId, title, content, album, tags, modifyMember, modifyMemberNickname);
			return Optional.of(true);
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("编辑话题失败", e);
			}
		}
		return Optional.empty();
	}

	// 话题发布事件方法
	private void generalTopicEvent(Topic tr) {
		forumEventPublisher.publishTopicEvent(new TopicPublishEvent(this, tr, tr.getUserAgent()));
	}
	
	private Optional<Topic> queryTopicStatic(final long id){
		Optional<Topic> opt = get(id);
		opt.ifPresent(t->{
			t.setStats(topicStatsDao.findOneByTopic(id).orElse(null));
		});
		return opt;
	}
	
	private Topic decorateTopicContent(Optional<Topic> srcTopic, Supplier<Posts> contentQuery, Function<Posts, String> action){
		srcTopic.ifPresent(t->{
			Posts oneFloor = contentQuery.get();
			oneFloor.setContent(action.apply(oneFloor));
			t.setContent(oneFloor);
		});
		return srcTopic.orElse(null);
	}
}
