package pl.tomaszdziurko.jvm_bloggers.blog_posts;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.rometools.rome.feed.synd.SyndContentImpl;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndEntryImpl;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndFeedImpl;
import com.rometools.rome.feed.synd.SyndLink;
import com.rometools.rome.feed.synd.SyndLinkImpl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import pl.tomaszdziurko.jvm_bloggers.blog_posts.domain.BlogPost;
import pl.tomaszdziurko.jvm_bloggers.blog_posts.domain.BlogPostRepository;
import pl.tomaszdziurko.jvm_bloggers.utils.NowProvider;
import pl.tomaszdziurko.jvm_bloggers.utils.UriUtmComponentsBuilder;
import pl.tomaszdziurko.jvm_bloggers.utils.Validators;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static pl.tomaszdziurko.jvm_bloggers.utils.DateTimeUtilities.toDate;

@Service
@CacheConfig(cacheNames = AggregatedRssFeedProducer.RSS_CACHE)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class AggregatedRssFeedProducer {

    public static final String RSS_CACHE = "Aggregated RSS feed cache";
    @VisibleForTesting
    static final String FEED_DESCRIPTION = "JVMBloggers aggregated feed";
    @VisibleForTesting
    static final String FEED_TITLE = "JVMBloggers";
    @VisibleForTesting
    static final String FEED_TYPE = "atom_1.0";

    private static final String SELF_REL = "self";
    private static final String UTM_MEDIUM = "RSS";
    private static final String UTM_CAMPAIGN = "RSS";
    @VisibleForTesting
    static final Set<String> INCLUDE_ALL_AUTHORS_SET = ImmutableSet.of(StringUtils.EMPTY);

    private final BlogPostRepository blogPostRepository;
    private final NowProvider nowProvider;

    /**
     * Generates aggregated RSS feed for all or given <tt>limit</tt> of approved blog posts.
     *
     * @param feedUrl Value of the <tt>{@literal <link rel=self/>}</tt> element
     *     of the generated feed. It identifies the URL of the web site associated
     *     with the generated feed. Cannot be <tt>null</tt> nor empty.
     *
     * @param limit Upper limit of RSS entries count in the generated feed. If equal or less
     *     than zero then all approved blog posts will be generated.
     *
     * @param excludedAuthors RSS entries for given authors will be excluded
     *     from the generated feed (may be <code>null</code>)
     *
     * @return Aggregated RSS feed for approved blog posts ordered by publication date
     */
    @Cacheable
    public SyndFeed getRss(String feedUrl, int limit, Set<String> excludedAuthors) {

        Preconditions.checkArgument(
            StringUtils.isNotBlank(feedUrl), "feedUrl parameter cannot be blank");

        StopWatch stopWatch = null;
        if (log.isDebugEnabled()) {
            stopWatch = new StopWatch();
            log.debug("Building aggregated RSS feed...");
            stopWatch.start();
        }

        final Pageable pageRequest = new PageRequest(0, limit > 0 ? limit : Integer.MAX_VALUE);
        if (CollectionUtils.isEmpty(excludedAuthors)) {
            excludedAuthors = INCLUDE_ALL_AUTHORS_SET;
        }
        final List<BlogPost> approvedPosts =
            blogPostRepository.findByApprovedTrueAndBlogAuthorNotInOrderByPublishedDateDesc(
                pageRequest, excludedAuthors
                );
        final List<SyndEntry> feedItems = approvedPosts.stream()
            .filter(it -> Validators.isUrlValid(it.getUrl()))
            .map(this::toRssEntry)
            .collect(Collectors.toList());
        final SyndFeed feed = buildFeed(feedItems, feedUrl);

        if (log.isDebugEnabled()) {
            stopWatch.stop();
            log.debug("Total {} feed entries produced in {}ms", feedItems.size(),
                stopWatch.getTotalTimeMillis());
        }
        return feed;
    }

    private SyndEntry toRssEntry(BlogPost post) {
        final SyndEntry rssEntry = new SyndEntryImpl();
        rssEntry.setTitle(post.getTitle());
        rssEntry.setLink(addUtmComponents(post.getUrl()));
        rssEntry.setAuthor(post.getBlog().getAuthor());
        rssEntry.setPublishedDate(toDate(post.getPublishedDate()));
        rssEntry.setUri(post.getUid());

        final String description = post.getDescription();
        if (isNotBlank(description)) {
            final SyndContentImpl descriptionContent = new SyndContentImpl();
            descriptionContent.setValue(description);
            rssEntry.setDescription(descriptionContent);
        }

        return rssEntry;
    }

    private String addUtmComponents(String url) {
        return UriUtmComponentsBuilder.fromHttpUrl(url)
            .withSource(UriUtmComponentsBuilder.DEFAULT_UTM_SOURCE)
            .withMedium(UTM_MEDIUM)
            .withCampaign(UTM_CAMPAIGN)
            .build();
    }

    private SyndFeed buildFeed(final List<SyndEntry> feedItems, String requestedUrlString) {
        final SyndLink feedLink = new SyndLinkImpl();
        feedLink.setRel(SELF_REL);
        feedLink.setHref(requestedUrlString);
        final SyndFeed feed = new SyndFeedImpl();
        feed.setUri(FEED_TITLE);
        feed.setTitle(FEED_TITLE);
        feed.setFeedType(FEED_TYPE);
        feed.setDescription(FEED_DESCRIPTION);
        feed.setLinks(Arrays.asList(feedLink));
        feed.setPublishedDate(toDate(nowProvider.now()));
        feed.setEntries(feedItems);
        return feed;
    }

}

@@ -1961,16 +1961,24 @@
 IPTION =
+%0A       
  %22JVMBlo
@@ -1998,19 +1998,258 @@
 ted feed
+. You can customize your rss results by using parameters%22%0A        + %22%60limit%60 and 'excludedAuthors%60 (comma delimeted names) parameters. %22%0A        + %22Example: http://jvm-bloggers.com/pl/rss?limit=5&excludedAuthors=Tomasz Dziurko Adam Warski
 %22;%0A
+%0A
     @Vis

Add info about available parameters in RSS