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

import com.apobates.forum.attention.ImageDirectoryEnum;
import com.apobates.forum.attention.core.UploadImagePathConvert;
import com.apobates.forum.core.ImageIOMeta;
import com.apobates.forum.core.dao.BoardCarouselIndexDao;
import com.apobates.forum.core.dao.BoardDao;
import com.apobates.forum.core.dao.BoardGroupDao;
import com.apobates.forum.core.dao.TopicCarouselDao;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardCarouselIndex;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.TopicCarousel;
import com.apobates.forum.core.entity.TopicCarouselSlide;
import com.apobates.forum.core.entity.proxy.BoardCarouselIndexReplica;
import com.apobates.forum.core.entity.proxy.TopicCarouselReplica;
import com.apobates.forum.core.service.TopicCarouselService;
import com.apobates.forum.core.service.TopicCarouselSlideService;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.lang.TriFunction;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Consumer;
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.stereotype.Service;

/**
 *
 * @author xiaofanku
 * @since 20200614
 */
@Service
public class TopicCarouselServiceImpl implements TopicCarouselService{
    @Autowired
    private TopicCarouselDao topicCarouselDao;
    @Autowired
    private TopicCarouselSlideService topicCarouselSlideService;
    @Autowired
    private BoardCarouselIndexDao boardCarouselIndexDao;
    @Autowired
    private BoardGroupDao boardGroupDao;
    @Autowired
    private BoardDao boardDao;
    private final static Logger logger = LoggerFactory.getLogger(TopicCarouselServiceImpl.class);
    
    @Override
    public Stream<TopicCarousel> getAll() {
        return topicCarouselDao.findAll();
    }
    
    @Override
    public Stream<BoardCarouselIndexReplica> getAllRelative(final int topicCarouselId) {
        List<BoardCarouselIndex> bcis = boardCarouselIndexDao.findAllByCarousel(topicCarouselId).collect(Collectors.toList());
        //20200710
        final Map<Integer,Long> allQueryParam = Commons.collectMap(bcis.stream(), BoardCarouselIndex::getVolumesId, BoardCarouselIndex::getBoardId);
        //
        final TriFunction<BoardGroup, Board, BoardCarouselIndex, BoardCarouselIndexReplica> action = (bg, b, bci) ->{
            BoardCarouselIndexReplica bcir = Optional.ofNullable(bci).map(BoardCarouselIndexReplica.copyCarouselIndex).orElseGet(BoardCarouselIndexReplica::new);
            if(null != b){
                bcir.setBoard(b);
            }
            if (null !=bg && bg.getId() > 0) {
                bcir.setVolumes(bg);
            }
            return bcir;
        };
        CompletableFuture<Map<Long, Board>> boardMap =  CompletableFuture.supplyAsync(()->boardDao.findAllById(allQueryParam.values())).thenApply(boards -> boards.collect(Collectors.toMap(Board::getId, Function.identity()))).completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS);
        CompletableFuture<Map<Integer, BoardGroup>> boardGroupMap = CompletableFuture.supplyAsync(()->boardGroupDao.findAllById(allQueryParam.keySet())).thenApply(boardGroups->boardGroups.collect(Collectors.toMap(BoardGroup::getId, Function.identity()))).completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS);
        return boardMap.thenCombine(boardGroupMap, (bmap, bgmap)->{
            return bcis.parallelStream().map(bci->action.apply(bgmap.get(bci.getVolumesId()), bmap.get(bci.getBoardId()), bci));
        }).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public Optional<TopicCarousel> get(int id) {
        if (id > 0) {
            return topicCarouselDao.findOne(id);
        }
        return Optional.empty();
    }
    
    @Override
    public int create(String title, String summary, boolean status) {
        TopicCarousel tc = new TopicCarousel(title, summary, status);
        topicCarouselDao.save(tc);
        if (tc.getId() < 1) {
            return 0; //.failure("轮播图创建失败");
        }
        return tc.getId();
    }
    
    @Override
    public Optional<TopicCarouselReplica> get(final String title) {
        BiFunction<Optional<TopicCarousel>, Stream<TopicCarouselSlide>, Optional<TopicCarouselReplica>> action = (otc, tcss)->{
            Optional<TopicCarouselReplica> tmp=otc.map(TopicCarouselReplica.copyCarousel);
            tmp.ifPresent(tcr->tcr.setSlides(tcss));
            return tmp;
        };
        return getTopicCarouselByTitleAsync(title).thenCompose(otc->carouselSlideCacheAsync(otc).thenApply((Stream<TopicCarouselSlide> tss)->action.apply(otc, tss))).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public Optional<TopicCarouselReplica> get(final String title, final ImageIOMeta imageIO) {
        return foldCarouselSlideAsync(getTopicCarouselByTitleAsync(title), imageIO);
    }
    
    @Override
    public Optional<TopicCarouselReplica> get(final int boardGroupId, final long boardId, final ImageIOMeta imageIO) {
        return foldCarouselSlideAsync(CompletableFuture.supplyAsync(()->boardCarouselIndexDao.findOneByBoard(boardGroupId, boardId)), imageIO);
    }
    
    @Override
    public Optional<Boolean> edit(int id, String title, String summary, boolean status) {
        Optional<TopicCarousel> data = get(id);
        if (!data.isPresent()) {
            return Optional.empty();
        }
        TopicCarousel tc = data.get();
        tc.setTitle(title);
        tc.setSummary(summary);
        tc.setStatus(status);
        return topicCarouselDao.edit(tc);
    }
    
    @Override
    public Optional<Boolean> remove(int id) {
        return topicCarouselDao.editStatus(id, false);
    }
    
    @Override
    public Optional<Boolean> bindBoard(int boardGroupId, long boardId, int topicCarouselId, LocalDateTime expireDateTime) {
        if (null == expireDateTime) {
            return Optional.empty();
        }
        if (boardGroupId < 1 || topicCarouselId < 1) {
            return Optional.empty();
        }
        BoardCarouselIndex bci = new BoardCarouselIndex();
        bci.setBoardId(boardId);
        bci.setVolumesId(boardGroupId);
        bci.setCarouselId(topicCarouselId);
        bci.setExpireDateTime(expireDateTime);
        bci.setEntryDateTime(LocalDateTime.now());
        try {
            boardCarouselIndexDao.save(bci);
            return Optional.of(true);
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("轮播图关联版块失败", e);
            }
        }
        return Optional.empty();
    }
    
    @Override
    public Optional<Boolean> remove(int topicCarouselId, int boardGroupId, long boardId) {
        return boardCarouselIndexDao.deleteBoardCarouselIndex(topicCarouselId, boardGroupId, boardId);
    }
    
    private Optional<TopicCarouselReplica> foldCarouselSlideAsync(final CompletableFuture<Optional<TopicCarousel>> carousel, final ImageIOMeta imageIO){
        Objects.requireNonNull(imageIO);
        final UploadImagePathConvert uipc = new UploadImagePathConvert(imageIO.getImageBucketDomain(), imageIO.getUploadImageDirectName());
        BiFunction<Optional<TopicCarousel>, Stream<TopicCarouselSlide>, Optional<TopicCarouselReplica>> action = (otc, tcss)->{
            Optional<TopicCarouselReplica> tmp=otc.map(TopicCarouselReplica.copyCarousel);
            tmp.ifPresent(tcr->tcr.setSlides(tcss));
            return tmp;
        };
        final Consumer<TopicCarouselSlide> decodeCarouselSlideURL = (TopicCarouselSlide tcs)->{
                try{
                    String imagePath = uipc.decode(tcs.getImageAddr(), ImageDirectoryEnum.DEFAT, null).map(URL::toString).orElse("/static/img/140x140.png");
                    tcs.setImageAddr(imagePath);
                }catch(MalformedURLException e){}
        };
        return carousel.thenCompose(otc->carouselSlideCacheAsync(otc).thenApply((Stream<TopicCarouselSlide> tss)->{
            tss.peek(tcs->decodeCarouselSlideURL.accept(tcs));
            return action.apply(otc, tss);
        })).orTimeout(1, TimeUnit.SECONDS).join();
    }
    private CompletableFuture<Stream<TopicCarouselSlide>> carouselSlideCacheAsync(final Optional<TopicCarousel> topicCarousel){
        if(!topicCarousel.isPresent()){
            return CompletableFuture.supplyAsync(()->Stream.empty());
        }
        return CompletableFuture.supplyAsync(()->topicCarouselSlideService.getAllUsedByCarousel(topicCarousel.get().getId())).completeOnTimeout(Stream.empty(), 1, TimeUnit.SECONDS);
    }
    private CompletableFuture<Optional<TopicCarousel>> getTopicCarouselByTitleAsync(final String title){
        return CompletableFuture.supplyAsync(()->topicCarouselDao.findOne(title)).completeOnTimeout(Optional.empty(), 1, TimeUnit.SECONDS);
    }
}