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

import com.apobates.forum.core.dao.BoardDao;
import com.apobates.forum.core.dao.BoardGroupDao;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.proxy.BoardGroupReplica;
import com.apobates.forum.core.service.BoardGroupService;
import com.apobates.forum.utils.Commons;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;

/**
 * 
 * @author xiaofanku
 * @since 20200512
 */
@Service
@CacheConfig(cacheNames = "boardgroupCache")
public class BoardGroupServiceImpl implements BoardGroupService{
    @Autowired
    private BoardGroupDao boardGroupDao;
    @Autowired
    private BoardDao boardDao;
    private final static Logger logger = LoggerFactory.getLogger(BoardGroupServiceImpl.class);
    
    @Cacheable(key = "#id", unless="#result==null")
    @Override
    public Optional<BoardGroup> get(int id) {
        if (id > 0) {
            return boardGroupDao.findOne(id);
        }
        return Optional.empty(); //("参数不合法或不被接受");
    }
    
    @CacheEvict(key = "#id")
    @Override
    public Optional<Boolean> edit(int id, BoardGroup updateEntity)throws IllegalArgumentException {
        BoardGroup bg = get(id).orElseThrow(()->new IllegalArgumentException("版块组(卷)不存在"));
        bg.setTitle(updateEntity.getTitle());
        bg.setDescription(updateEntity.getDescription());
        bg.setStatus(updateEntity.isStatus());
        bg.setRanking(updateEntity.getRanking());//@ADD 20200917
        if (Commons.isNotBlank(updateEntity.getImageAddr())) {
            bg.setImageAddr(updateEntity.getImageAddr());
        }
        return boardGroupDao.edit(bg);
    }
    
    @Override
    public List<BoardGroupReplica> getAllUsedAndBoard(final boolean containsDefault) {
        /*BiFunction<BoardGroup, Set<Board>, BoardGroupReplica> action = (bg, bs)->{
            BoardGroupReplica bgr = Optional.ofNullable(bg).map(BoardGroupReplica.copyBoardGroup).orElse(null);
            if(null == bgr){
                return null;
            }
            TreeSet<Board> tmp = bs.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Board::getRanking))));
            if (!tmp.isEmpty()) {
                bgr.pushBoards(tmp);
            }
            return bgr;
        };
        
        Map<Integer, Set<Board>> groupBoardData = boardDao.findUsed().collect(Collectors.groupingBy(Board::getVolumesId, Collectors.toSet()));
        List<BoardGroup> groupData = getAllUsed().collect(Collectors.toList());
        Stream<BoardGroup> result;
        if(containsDefault){
            result = Stream.concat(groupData.stream(), Stream.of(BoardGroup.defaultInstance(groupData.size()+1))); //排在最后
        }else{
            result = groupData.stream();
        }
        return result.parallel().map(bg->action.apply(bg, groupBoardData.getOrDefault(bg.getId(), Collections.emptySet()))).filter(Objects::nonNull).sorted(Comparator.comparingInt(BoardGroup::getRanking)).collect(Collectors.toList());*/
        return getAllUsedAndBoardAsync(containsDefault);
    }
    
    @Cacheable
    @Override
    public List<BoardGroup> getAllUsed() {
        return boardGroupDao.findUsed();
    }
    
    @Override
    public Optional<Boolean> editSection(int id, String title, String description, String directoryNames, boolean status)throws IllegalArgumentException {
        BoardGroup section = getOriginById(id).orElseThrow(()->new IllegalArgumentException("栏目不存在或暂时不可用"));
        section.setTitle(title);
        section.setDescription(description);
        section.setDirectoryNames(directoryNames);
        section.setStatus(status);
        return boardGroupDao.edit(section);
    }
    
    @Override
    public int buildOrigin(String title, String description, String directoryNames, boolean status)throws IllegalStateException,IllegalArgumentException {
        if (!checkOriginDirectNameUnique(directoryNames).orElse(false)) {
            throw new IllegalStateException("栏目目录名称唯一性检查失败");
        }
        BoardGroup bg = new BoardGroup();
        bg.setTitle(title);
        bg.setDescription(description);
        bg.setOrigin(true);
        bg.setDirectoryNames(directoryNames); //需要保证唯一
        bg.setStatus(status);
        bg.setEntryDateTime(LocalDateTime.now());
        bg.setRanking(0);
        //
        try {
            boardGroupDao.save(bg);
            if (bg.getId() > 0) {
                return bg.getId();
            }
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("栏目创建失败", e);
            }
        }
        return 0;
    }
    
    @Override
    public Optional<BoardGroup> create(String title, String description, String encodeImageAddr, boolean status, int ranking) {
        BoardGroup bg = new BoardGroup(title, description, encodeImageAddr, status, ranking);
        try {
            boardGroupDao.save(bg);
            if (bg.getId() > 0) {
                return Optional.of(bg);
            }
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("版块组创建失败", e);
            }
        }
        return Optional.empty();
    }
    
    @Override
    public Stream<BoardGroup> getAll() {
        return boardGroupDao.findAll();
    }
    
    @Override
    public Stream<BoardGroup> getAllContainsDefault() {
        Stream<BoardGroup> data = boardGroupDao.findAll();
        return Stream.concat(Stream.of(BoardGroup.defaultInstance()), data);
    }
    
    @Override
    public Stream<BoardGroup> getAllOrigin() {
        return boardGroupDao.findAllOrigin();
    }
    
    @Override
    public Stream<BoardGroup> getAllOriginById(Collection<Integer> idList) {
        if (null == idList || idList.isEmpty()) {
            return getAllOrigin();
        }
        return boardGroupDao.findAllOriginById(idList);
    }
    
    @Override
    public Optional<BoardGroup> getByBoardId(long boardId) {
        return boardGroupDao.findOneByBoard(boardId);
    }
    
    @Override
    public Optional<BoardGroup> getOriginByDirectoryNames(String directoryNames) {
        return boardGroupDao.findOneOriginByDirectoryNames(directoryNames);
    }
    
    @Override
    public Optional<BoardGroup> getOriginById(int sectionId) {
        if (sectionId > 0) {
            return boardGroupDao.findOneSectionById(sectionId);
        }
        return Optional.empty();
    }
    
    @Override
    public Optional<Boolean> checkOriginDirectNameUnique(String directoryNames)throws IllegalArgumentException,IllegalStateException {
        if (!Commons.isNotBlank(directoryNames)) {
            throw new IllegalArgumentException("栏目的目录名称不可用");
        }
        int originId = boardGroupDao.existsOriginDirectoryNames(directoryNames);
        if (originId > 0) {
            throw new IllegalStateException("栏目的目录名称已被使用");
        }
        return Optional.of(true);
    }
    
    @Override
    public Optional<Boolean> checkOriginDirectNameUnique(String directoryNames, int sectionId)throws IllegalArgumentException,IllegalStateException {
        if (sectionId <= 0) {
            return checkOriginDirectNameUnique(directoryNames);
        }
        //
        if (!Commons.isNotBlank(directoryNames)) {
            throw new IllegalArgumentException("栏目的目录名称不可用");
        }
        long count = boardGroupDao.existsOriginDirectoryNames(directoryNames, sectionId);
        if (count > 0){
            throw new IllegalStateException("栏目的目录名称已被使用");
        }
        return Optional.of(true);
    }
    
    @Override
    public Map<Integer, String> getAllById(Collection<Integer> idList) {
        if (null == idList || idList.isEmpty()) {
            return Collections.emptyMap();
        }
        //20200710
        Map<Integer, String> data = boardGroupDao.findAllById(idList).collect(Collectors.toMap(BoardGroup::getId, BoardGroup::getTitle));
        //
        BoardGroup defaultBg = BoardGroup.defaultInstance();
        data.put(defaultBg.getId(), defaultBg.getTitle());
        return data;
    }
    private List<BoardGroupReplica> getAllUsedAndBoardAsync(final boolean containsDefault) {
        BiFunction<BoardGroup, Set<Board>, BoardGroupReplica> action = (bg, bs)->{
            BoardGroupReplica bgr = Optional.ofNullable(bg).map(BoardGroupReplica.copyBoardGroup).orElse(null);
            if(null == bgr){
                return null;
            }
            TreeSet<Board> tmp = bs.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Board::getRanking))));
            if (!tmp.isEmpty()) {
                bgr.pushBoards(tmp);
            }
            return bgr;
        };
        CompletableFuture<Map<Integer, Set<Board>>> groupBoardData = CompletableFuture.supplyAsync(()->boardDao.findUsed()).thenApply(bs->bs.collect(Collectors.groupingBy(Board::getVolumesId, Collectors.toSet()))).completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS);
        CompletableFuture<Stream<BoardGroup>> groupData = CompletableFuture
                .supplyAsync(()->boardGroupDao.findUsed().stream().collect(Collectors.toMap(BoardGroup::getId, Function.identity())))
                .thenApply((Map<Integer, BoardGroup> bgmap)->{
                    if(!containsDefault){
                        return bgmap.values().stream();
                    }else{
                        return Stream.concat(bgmap.values().stream(), Stream.of(BoardGroup.defaultInstance(bgmap.keySet().size()+1)));
                    }
                }).completeOnTimeout(Stream.empty(), 1, TimeUnit.SECONDS);
        return groupData.thenCombine(groupBoardData, (boardGroupStream, boardSetMap)->{
            return boardGroupStream.parallel().map(bg->action.apply(bg, boardSetMap.getOrDefault(bg.getId(), Collections.emptySet()))).filter(Objects::nonNull).sorted(Comparator.comparingInt(BoardGroup::getRanking)).collect(Collectors.toList());
        }).orTimeout(1, TimeUnit.SECONDS).join();
    }
}