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

import com.apobates.forum.letterbox.dao.OutboxDao;
import com.apobates.forum.letterbox.entity.ForumLetter;
import com.apobates.forum.letterbox.entity.ForumLetterReceiver;
import com.apobates.forum.letterbox.entity.ForumLetterTypeEnum;
import com.apobates.forum.letterbox.entity.Outbox;
import com.apobates.forum.letterbox.entity.proxy.ForumLetterReplica;
import com.apobates.forum.letterbox.service.OutboxService;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 *
 * @author xiaofanku
 * @since 20200510
 */
@Service
public class OutboxServiceImpl implements OutboxService{
    @Autowired
    private OutboxDao outboxDao;
    
    @Override
    public Page<ForumLetterReplica> getSent(long memberId, Pageable pageable) {
        Page<ForumLetter> rs = outboxDao.findAllBySender(memberId, pageable);
        
        final Stream<ForumLetterReplica> result = associationLetterReceiversAsync(rs.getResult().collect(Collectors.toList()));
        return new Page<ForumLetterReplica>() {
            @Override
            public long getTotalElements() {
                return rs.getTotalElements();
            }
            
            @Override
            public Stream<ForumLetterReplica> getResult() {
                return result;
            }
        };
    }
    
    @Override
    public Page<ForumLetterReplica> getSent(long memberId, ForumLetterTypeEnum label, Pageable pageable) {
        Page<ForumLetter> rs = outboxDao.findAllBySenderAndType(memberId, label, pageable);
        
        final Stream<ForumLetterReplica> result = associationLetterReceiversAsync(rs.getResult().collect(Collectors.toList()));
        return new Page<ForumLetterReplica>() {
            @Override
            public long getTotalElements() {
                return rs.getTotalElements();
            }
            
            @Override
            public Stream<ForumLetterReplica> getResult() {
                return result;
            }
        };
    }
    
    @Override
    public long countSentByMember(long memberId) {
        return outboxDao.countBySender(memberId);
    }
    
    @Override
    public long countSentByMember(long memberId, ForumLetterTypeEnum label) {
        return outboxDao.countBySenderAndType(memberId, label);
    }
    //[异步]级联加载收件人
    private Stream<ForumLetterReplica> associationLetterReceiversAsync(final List<ForumLetter> data) {
        if (null == data || data.isEmpty()) {
            return Stream.empty();
        }
        BiFunction<ForumLetterReplica, List<Outbox>, ForumLetterReplica> bi = (flr, outboxs)->{
            ForumLetterReplica tmp = Optional.ofNullable(flr).orElseGet(ForumLetterReplica::new);
            try {
                Set<ForumLetterReceiver> trs = outboxs.stream().map(ob -> new ForumLetterReceiver(ob.getReceiver(), ob.getReceiverNickname())).collect(Collectors.toSet());
                tmp.setReceivers(trs);
            } catch (NullPointerException e) {
                tmp.getReceivers().add(ForumLetterReceiver.allMemberes());
            }
            return tmp;
        };
        final Map<Long, List<Outbox>> boxes = CompletableFuture.supplyAsync(()->data.stream().map(ForumLetter::getId).collect(Collectors.toSet()))
                                .thenCompose(letterIdSet-> CompletableFuture.supplyAsync(()->outboxDao.findAllByLetter(letterIdSet).collect(Collectors.groupingBy(Outbox::getLetter)))
                                                        .completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS))
                                .orTimeout(1, TimeUnit.SECONDS)
                                .join();
        return data.parallelStream().map(ForumLetterReplica.copyLetter).peek(flr->bi.apply(flr, boxes.get(flr.getId())));
    }
}