package eu.dnetlib.functionality.alert.alerter.atom;

import java.net.URI;
import java.util.SortedSet;

import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import eu.dnetlib.domain.functionality.ObjectPage;
import eu.dnetlib.functionality.alert.alerter.AlerterException;
import eu.dnetlib.functionality.alert.alerter.atom.dao.AtomAlerterDAO;
import eu.dnetlib.functionality.alert.alerter.atom.dao.AtomAlerterDAOException;
import eu.dnetlib.functionality.alert.alerter.atom.domain.AtomEntry;
import eu.dnetlib.functionality.alert.alerter.atom.domain.AtomFeed;

public class AtomAlerterTransactionHelper {
	private static final Logger logger = Logger.getLogger(AtomAlerterTransactionHelper.class);
	
	private AtomAlerterDAO dao;
	
	/**
	 * Set the DAO.
	 * @param dao the DAO to use to persist feeds and entries
	 */
	public void setDao(final AtomAlerterDAO dao) {
		this.dao = dao;
	}

	/**
	 * Retrieve a page of feeds.
	 * @param pageNumber the number of the page to retrieve
	 * @param pageSize the size of the page to retrieve
	 * @return an object page containing feeds
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlerterException.class)
	public ObjectPage<AtomFeed> getFeeds(final int pageNumber, final int pageSize) throws AlerterException {
		try {
			final ObjectPage<AtomFeed> feeds = new ObjectPage<AtomFeed>(pageNumber, pageSize, dao.countFeeds(), dao.getFeeds(pageSize, pageNumber * pageSize));
			logger.info("Retrieved feeds " + feeds);
			return feeds;
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error retrieving feeds (page: " + pageNumber + ", page size: " + pageSize, e);
		}
	}
	
	/**
	 * Retrieve a feed.
	 * @param id the URN that uniquely identifies the feed to retrieve
	 * @return the feed specified or null if no such feed exists
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlerterException.class)
	public AtomFeed getFeed(final URI id) throws AlerterException {
		try {
			final AtomFeed feed = dao.getFeed(id);
			logger.info((feed == null) ? ("Feed " + id + " does not exist") : ("Retrieved feed " + id));
			return feed;
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error retrieving feed " + id, e);
		}
	}
	
	/**
	 * Add a new feed.
	 * @param feed the feed to add
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlerterException.class)
	public void addFeed(final AtomFeed feed) throws AlerterException {
		try {
			dao.saveFeed(feed);
			logger.info("Added feed " + feed);
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error adding feed " + feed, e);
		}
	}
	
	/**
	 * Remove a feed.
	 * @param id the URN that uniquely identifies the feed to remove
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlerterException.class)
	public void removeFeed(final URI id) throws AlerterException {
		try {
			dao.deleteFeed(id);
			logger.info("Removed feed " + id);
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error removing feed " + id, e);
		}
	}
	
	/**
	 * Retrieve some entries of a feed.
	 * @param feed the URN that uniquely identifies the feed of which entries to retrieve
	 * @param limit the maximum number of entries to retrieve
	 * @param offset the offset to start at
	 * @return a sorted set containing entries
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlerterException.class)
	public SortedSet<AtomEntry> getEntries(final URI feed, final int limit, final int offset) throws AlerterException {
		try {
			final SortedSet<AtomEntry> entries = dao.getEntries(feed, limit, offset);
			logger.info("Retrieved " + entries.size() + " entries of feed " + feed + " (limit: " + limit + ", offset: " + offset + ")");
			return entries;
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error retrieving entries of feed " + feed + " (limit: " + limit + ", offset: " + offset + ")");
		}
	}
	
	/**
	 * Add a new entry.
	 * @param entry the entry to add
	 * @throws AlerterException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlerterException.class)
	public void addEntry(final AtomEntry entry) throws AlerterException {
		try {
			final AtomFeed feed = dao.getFeed(entry.getFeed());
			if (entry.getUpdated().after(feed.getUpdated())) {
				feed.setUpdated(entry.getUpdated());
				dao.saveFeed(feed);
			}
			dao.saveEntry(entry);
			logger.info("Added entry " + entry);
		} catch (final AtomAlerterDAOException e) {
			throw new AlerterException("error adding entry " + entry);
		}
	}
}
