package com.commafeed.backend.feed;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.time.DateUtils;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.model.Feed;

@Singleton
public class FeedQueues {

	private final FeedDAO feedDAO;
	private final CommaFeedConfiguration config;

	private Queue<FeedRefreshContext> addQueue = new ConcurrentLinkedQueue<>();
	private Queue<FeedRefreshContext> takeQueue = new ConcurrentLinkedQueue<>();
	private Queue<Feed> giveBackQueue = new ConcurrentLinkedQueue<>();

	private Meter refill;

	@Inject
	public FeedQueues(FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
		this.config = config;
		this.feedDAO = feedDAO;

		refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
		metrics.register(MetricRegistry.name(getClass(), "addQueue"), new Gauge<Integer>() {
			@Override
			public Integer getValue() {
				return addQueue.size();
			}
		});
		metrics.register(MetricRegistry.name(getClass(), "takeQueue"), new Gauge<Integer>() {
			@Override
			public Integer getValue() {
				return takeQueue.size();
			}
		});
		metrics.register(MetricRegistry.name(getClass(), "giveBackQueue"), new Gauge<Integer>() {
			@Override
			public Integer getValue() {
				return giveBackQueue.size();
			}
		});
	}

	/**
	 * take a feed from the refresh queue
	 */
	public synchronized FeedRefreshContext take() {
		FeedRefreshContext context = takeQueue.poll();

		if (context == null) {
			refill();
			context = takeQueue.poll();
		}
		return context;
	}

	/**
	 * add a feed to the refresh queue
	 */
	public void add(Feed feed, boolean urgent) {
		int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
		if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
			boolean alreadyQueued = addQueue.stream().anyMatch(c -> c.getFeed().getId().equals(feed.getId()));
			if (!alreadyQueued) {
				addQueue.add(new FeedRefreshContext(feed, urgent));
			}
		}
	}

	/**
	 * refills the refresh queue and empties the giveBack queue while at it
	 */
	private void refill() {
		refill.mark();

		List<FeedRefreshContext> contexts = new ArrayList<>();
		int batchSize = Math.min(100, 3 * config.getApplicationSettings().getBackgroundThreads());

		// add feeds we got from the add() method
		int addQueueSize = addQueue.size();
		for (int i = 0; i < Math.min(batchSize, addQueueSize); i++) {
			contexts.add(addQueue.poll());
		}

		// add feeds that are up to refresh from the database
		int count = batchSize - contexts.size();
		if (count > 0) {
			List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
			for (Feed feed : feeds) {
				contexts.add(new FeedRefreshContext(feed, false));
			}
		}

		// set the disabledDate as we use it in feedDAO to decide what to refresh next. We also use a map to remove
		// duplicates.
		Map<Long, FeedRefreshContext> map = new LinkedHashMap<>();
		for (FeedRefreshContext context : contexts) {
			Feed feed = context.getFeed();
			feed.setDisabledUntil(DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()));
			map.put(feed.getId(), context);
		}

		// refill the queue
		takeQueue.addAll(map.values());

		// add feeds from the giveBack queue to the map, overriding duplicates
		int giveBackQueueSize = giveBackQueue.size();
		for (int i = 0; i < giveBackQueueSize; i++) {
			Feed feed = giveBackQueue.poll();
			map.put(feed.getId(), new FeedRefreshContext(feed, false));
		}

		// update all feeds in the database
		List<Feed> feeds = new ArrayList<>();
		for (FeedRefreshContext context : map.values()) {
			feeds.add(context.getFeed());
		}
		feedDAO.merge(feeds);
	}

	/**
	 * give a feed back, updating it to the database during the next refill()
	 */
	public void giveBack(Feed feed) {
		String normalized = FeedUtils.normalizeURL(feed.getUrl());
		feed.setNormalizedUrl(normalized);
		feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized));
		feed.setLastUpdated(new Date());
		giveBackQueue.add(feed);
	}

	private Date getLastLoginThreshold() {
		if (config.getApplicationSettings().getHeavyLoad()) {
			return DateUtils.addDays(new Date(), -30);
		} else {
			return null;
		}
	}

}

@@ -241,16 +241,53 @@
 dQueue;%0D
+%0Aimport java.util.stream.Collectors;%0D
 %0A%0D%0Aimpor
@@ -4242,118 +4242,81 @@
 s = 
-new ArrayList%3C%3E();%0D%0A%09%09for (FeedRefreshContext context : map.values()) %7B%0D%0A%09%09%09feeds.add(context.getFeed());%0D%0A%09%09%7D
+map.values().stream().map(c -%3E c.getFeed()).collect(Collectors.toList());
 %0D%0A%09%09

rewrite using lambda