/**
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the latest version of the GNU Lesser General
 * Public License as published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program (LICENSE.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.jamwiki.servlets;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.SyndFeedOutput;
import org.apache.commons.lang.StringUtils;
import org.jamwiki.Environment;
import org.jamwiki.WikiBase;
import org.jamwiki.model.RecentChange;
import org.jamwiki.utils.Pagination;
import org.jamwiki.utils.Utilities;
import org.jamwiki.utils.WikiLogger;
import org.jamwiki.utils.WikiUtil;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

/**
 * Provides an RSS or Atom feed for recent changes.
 *
 * Feed generation can be influenced through following request parameters:
 * <ul>
 * <li><code>feedType</code>: RSS or Atom (see
 * {@link #setDefaultFeedType(String)}) </li>
 * <li><code>minorEdits</code>: set to 'true' to include minor edits </li>
 * <li><code>num</code>: number of entries to return (see
 * {@link WikiUtil#buildPagination(HttpServletRequest)})</li>
 * </ul>
 *
 * @author Rainer Schmitz
 * @since 22.12.2006
 */
public class RecentChangesFeedServlet extends AbstractController {

	private static final WikiLogger logger = WikiLogger.getLogger(RecentChangesFeedServlet.class.getName());
	private static final String MIME_TYPE = "application/xml";
	private static final String FEED_ENCODING = "UTF-8";
	private static final String DEFAULT_FEED_TYPE = "rss_2.0";
	private static final String FEED_TYPE = "feedType";
	private static final String MINOR_EDITS = "minorEdits";
	private static final String LINK_TO_VERSION = "linkToVersion";
	private String defaultFeedType = DEFAULT_FEED_TYPE;
	private boolean defaultIncludeMinorEdits = false;
	private boolean defaultLinkToVersion = false;
	private String feedUrlPrefix = "";

	/**
	 * Sets the default feed type.
	 *
	 * Valid values are rss_0.92, rss_0.93, rss_0.94, rss_1.0, rss_2.0,
	 * atom_0.3, and atom_1.0.
	 *
	 * This value is used if no feed type is given in the request (parameter
	 * 'feedType'). Default is rss_2.0.
	 *
	 * @param defaultFeedType
	 *            The defaultFeedType to set.
	 */
	public void setDefaultFeedType(String defaultFeedType) {
		this.defaultFeedType = defaultFeedType;
	}

	/**
	 * Sets whether minor edits are included in generated feed.
	 *
	 * This value can be overriden by the request parameter 'minorEdits'.
	 * Default is false.
	 *
	 * @param includeMinorEdits
	 *            <code>true</code> if minor edits shall be included in feed.
	 */
	public void setDefaultIncludeMinorEdits(boolean includeMinorEdits) {
		this.defaultIncludeMinorEdits = includeMinorEdits;
	}

	/**
	 * Sets whether feed entry link should link to the changed or to the current
	 * version of the changed entry.
	 *
	 * This value can be overriden by the request parameter 'linkToVersion'.
	 * Default is false.
	 *
	 * @param feedUrlPrefix feed URL prefix to set; may not be null.
	 */
	public void setFeedUrlPrefix(String feedUrlPrefix) {
		if (feedUrlPrefix == null) {
			throw new IllegalArgumentException("Feed URL prefix may not be null");
		}
		this.feedUrlPrefix = feedUrlPrefix;
	}

	/**
	 * Prefix to use in feed and feed entry links.
	 *
	 * This is useful in portal environments to prefix the feed URL with the portal URL.
	 *
	 * @param linkToVersion
	 *            <code>true</code> if link should point to edited version.
	 */
	public void setDefaultLinkToVersion(boolean linkToVersion) {
		this.defaultLinkToVersion = linkToVersion;
	}

	/**
	 * Handle the servlet request, generating appropriate output.
	 *
	 * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			String feedType = ServletRequestUtils.getStringParameter(request, FEED_TYPE, defaultFeedType);
			logger.finer("Serving xml feed of type " + feedType);
			SyndFeed feed = getFeed(request);
			feed.setFeedType(feedType);
			response.setContentType(MIME_TYPE);
			response.setCharacterEncoding(FEED_ENCODING);
			SyndFeedOutput output = new SyndFeedOutput();
			output.output(feed, response.getWriter());
		} catch (Exception e) {
			logger.severe("Could not generate feed: " + e.getMessage(), e);
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not generate feed: "
					+ e.getMessage());
		}
		return null;
	}

	/**
	 * TODO cache feed to avoid high load caused by RSS aggregators
	 *
	 * @throws Exception
	 */
	private SyndFeed getFeed(HttpServletRequest request) throws Exception {
		List<RecentChange> changes = getChanges(request);
		SyndFeed feed = new SyndFeedImpl();
		feed.setEncoding(FEED_ENCODING);
		feed.setTitle(Environment.getValue(Environment.PROP_RSS_TITLE));
		StringBuffer requestURL = request.getRequestURL();
		String feedURL = feedUrlPrefix + requestURL.substring(0, requestURL.length() - WikiUtil.getTopicFromURI(request).length());
		feed.setLink(feedURL);
		feed.setDescription("List of the last " + changes.size() + " changed wiki pages.");
		boolean includeMinorEdits = ServletRequestUtils.getBooleanParameter(request, MINOR_EDITS,
				defaultIncludeMinorEdits);
		boolean linkToVersion = ServletRequestUtils.getBooleanParameter(request, LINK_TO_VERSION, defaultLinkToVersion);
		feed.setEntries(getFeedEntries(changes, includeMinorEdits, linkToVersion, feedURL));
		return feed;
	}

	/**
	 *
	 */
	private List<SyndEntry> getFeedEntries(List<RecentChange> changes, boolean includeMinorEdits, boolean linkToVersion, String feedURL) {
		List<SyndEntry> entries = new ArrayList<SyndEntry>();
		for (RecentChange change : changes) {
			// FIXME - add support for log item changes
			if (!StringUtils.isBlank(change.getTopicName()) && (includeMinorEdits || !change.getMinor())) {
				entries.add(getFeedEntry(change, linkToVersion, feedURL));
			}
		}
		return entries;
	}

	/**
	 *
	 */
	private SyndEntry getFeedEntry(RecentChange change, boolean linkToVersion, String feedURL) {
		SyndContent description;
		SyndEntry entry = new SyndEntryImpl();
		entry.setTitle(change.getTopicName());
		entry.setAuthor(change.getAuthorName());
		entry.setPublishedDate(change.getChangeDate());
		description = new SyndContentImpl();
		description.setType("text/plain");
		StringBuffer descr = new StringBuffer();
		if (!StringUtils.isBlank(change.getChangeComment())) {
			descr.append(change.getChangeComment());
		}
		if (change.isDelete()) {
			descr.append(" (deleted)");
		} else {
			if (linkToVersion) {
				try {
				String url = feedURL + URLEncoder.encode("Special:History?topicVersionId=" + change.getTopicVersionId() + "&topic="
						+ Utilities.encodeAndEscapeTopicName(change.getTopicName()), "UTF-8");
					entry.setLink(url);
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else {
				entry.setLink(feedURL + Utilities.encodeAndEscapeTopicName(change.getTopicName()));
			}
		}
		if (change.isUndelete()) {
			descr.append(" (undeleted)");
		}
		if (change.getMinor()) {
			descr.append(" (minor)");
		}
		description.setValue(descr.toString());
		entry.setDescription(description);
		// URI is used as GUID in RSS 2.0 and should therefore contain the
		// version id
		entry.setUri(feedURL + Utilities.encodeAndEscapeTopicName(change.getTopicName()) + "#" + change.getTopicVersionId());
		return entry;
	}

	/**
	 *
	 */
	private List<RecentChange> getChanges(HttpServletRequest request) throws Exception {
		String virtualWiki = WikiUtil.getVirtualWikiFromURI(request);
		Pagination pagination = WikiUtil.buildPagination(request);
		return WikiBase.getDataHandler().getRecentChanges(virtualWiki, pagination, true);
	}
}
