package info.persistent.pushbot;

import com.google.appengine.api.xmpp.JID;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;

import info.persistent.pushbot.data.Subscription;
import info.persistent.pushbot.util.Feeds;
import info.persistent.pushbot.util.Persistence;
import info.persistent.pushbot.util.Xmpp;

import org.apache.commons.lang3.StringEscapeUtils;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class PushSubscriberServlet extends HttpServlet {
  private static final Logger logger =
      Logger.getLogger(PushSubscriberServlet.class.getName());
  
  private static final int MAX_ENTRIES_TO_DISPLAY = 3;

  /** Subscription verifications arrive via GETs */
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setStatus(200);
    resp.setContentType("text/plain");
    resp.getOutputStream().print(req.getParameter("hub.challenge"));
    resp.getOutputStream().flush();

    JID user = new JID(req.getPathInfo().substring(1));

    if (req.getParameter("hub.mode").equals("subscribe")) {
      Xmpp.sendMessage(user, "Subscribed to " + req.getParameter("hub.topic"));
    } else if (req.getParameter("hub.mode").equals("unsubscribe")) {
      Xmpp.sendMessage(
          user, "Unsubscribed from " + req.getParameter("hub.topic"));      
    }
  }

  /** Actual notifications arrive via POSTs */
  @SuppressWarnings("unchecked")
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setStatus(204);

    SyndFeed feed = Feeds.parseFeed(req.getInputStream());
    if (feed == null) {
      return;
    }
    
    List<SyndEntry> entries = feed.getEntries();
    
    if (entries.isEmpty()) {
      return;
    }    
    
    JID user = new JID(req.getPathInfo().substring(1));
    // TODO(mihaip): this is potentially incorrect if a feed gets redirected and
    // its self URL changes since the time the subscription was created
    List<URL> feedUrls = Feeds.getLinkUrl(feed, Feeds.SELF_RELATION); 
    if (!feedUrls.isEmpty()) {
      URL feedUrl = feedUrls.get(0);
      List<Subscription> subscriptions =
        Subscription.getSubscriptionsForUserAndFeedUrl(user, feedUrl);
      
      if (!subscriptions.isEmpty()) {
        final Subscription subscription = subscriptions.get(0);
        Set<String> seenEntryIds = subscription.getSeenEntryIds();
        List<String> newEntryIds = Lists.newArrayList();
        List<SyndEntry> filteredEntries = Lists.newArrayList();
        for (SyndEntry entry : entries) {
          String entryId = Feeds.getEntryId(entry);
          if (seenEntryIds.contains(entryId)) {
            logger.info("Filtering out already seen entry from " + feedUrl);
            continue;
          }
          filteredEntries.add(entry);
          newEntryIds.add(entryId);
        }
        
        if (!filteredEntries.isEmpty()) {
          subscription.addSeenEntryIds(newEntryIds);
          Persistence.withManager(new Persistence.Closure() {
            @Override public void run(PersistenceManager manager) {
              manager.makePersistent(subscription);
            }
          });
        } else {
          return;
        }
        
        entries = filteredEntries;
      } else {
        logger.warning("Got notification for feed without subscription " + feedUrl);
      }
    }
    
 
    // If subscribing to a previously unseen URL, the hub might report a bunch
    // of entries as new, so we sort them by published date and only show the
    // first few
    Collections.sort(entries, new Comparator<SyndEntry>() {
      @Override public int compare(SyndEntry o1, SyndEntry o2) {
        if (o1.getPublishedDate() == null) {
          return 1;
        }
        if (o2.getPublishedDate() == null) {
          return -1;
        }
        return o2.getPublishedDate().compareTo(o1.getPublishedDate());
      }
    });
    
    List<SyndEntry> displayEntries;
    if (entries.size() > MAX_ENTRIES_TO_DISPLAY) {
      displayEntries = entries.subList(0, MAX_ENTRIES_TO_DISPLAY);
    } else {
      displayEntries = entries;
    }
    
    StringBuilder message = new StringBuilder("Update from ")
        .append(StringEscapeUtils.unescapeHtml4(feed.getTitle())).append(":");
    for (SyndEntry displayEntry : displayEntries) {
      String title = displayEntry.getTitle();
      if (Strings.isNullOrEmpty(title)) {
        title = "(title unknown)";
      } else {
        title = StringEscapeUtils.unescapeHtml4(title);
      }
      String link = displayEntry.getLink();
      if (Strings.isNullOrEmpty(link)) {
        link = "<no link>";
      }
      
      message.append("\n  ").append(title).append(": ").append(link);
    }
    
    if (displayEntries.size() != entries.size()) {
      message.append("\n  (and ")
        .append(entries.size() - displayEntries.size()).append(" more)");
    }
    
    Xmpp.sendMessage(user, message.toString());
  }
}
