/*
 * Copyright 2012 Netflix, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.netflix.recipes.rss.manager;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.netflix.client.ClientFactory;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.karyon.spi.HealthCheckHandler;
import com.netflix.niws.client.http.HttpClientRequest;
import com.netflix.niws.client.http.HttpClientResponse;
import com.netflix.niws.client.http.RestClient;
import com.netflix.recipes.rss.RSS;
import com.netflix.recipes.rss.RSSConstants;
import com.netflix.recipes.rss.RSSItem;
import com.netflix.recipes.rss.RSSStore;
import com.netflix.recipes.rss.Subscriptions;
import com.netflix.recipes.rss.impl.CassandraStoreImpl;
import com.netflix.recipes.rss.impl.InMemoryStoreImpl;
import com.netflix.recipes.rss.impl.RSSImpl;
import com.netflix.recipes.rss.impl.RSSItemImpl;
import com.netflix.recipes.rss.impl.SubscriptionsImpl;

/**
 * RSS Manager that
 *  1) Fetches content from RSS feeds using Ribbon
 *  2) Parses RSS feeds
 *  3) Persists feed urls into
 *      a) Cassandra using Astyanax (or)
 *      b) InMemoryStore
 */
public class RSSManager implements HealthCheckHandler {

    private RSSStore store;
    private static final Logger logger = LoggerFactory.getLogger(RSSManager.class);
    private static final RSSManager instance = new RSSManager();
    
    private RSSManager() {
        if (RSSConstants.RSS_STORE_CASSANDRA.equals(
                DynamicPropertyFactory.getInstance().getStringProperty(RSSConstants.RSS_STORE, RSSConstants.RSS_STORE_CASSANDRA).get())) {
            store = new CassandraStoreImpl();
        } else {
            store = new InMemoryStoreImpl();
        }
    }

    public static RSSManager getInstance() {
        return instance;
    }

    /**
     * Fetches the User subscriptions
     */
    public Subscriptions getSubscriptions(String userId) throws Exception {
        List<String> feedUrls = store.getSubscribedUrls(userId);
        List<RSS> feeds = new ArrayList<RSS>(feedUrls.size());
        for (String feedUrl: feedUrls) {
            RSS rss = RSSManager.getInstance().fetchRSSFeed(feedUrl);
            if (rss.getItems() != null && !rss.getItems().isEmpty()) {
                feeds.add(rss);
            }
        }

        return new SubscriptionsImpl(userId, feeds);
    }

    /**
     * Add subscription
     */
    public void addSubscription(String user, String decodedUrl) throws Exception {
        if (decodedUrl == null) throw new IllegalArgumentException("url cannot be null");
        store.subscribeUrl(user, decodedUrl);
    }

    /**
     * Delete subscription
     */
    public void deleteSubscription(String user, String decodedUrl) throws Exception {
        if (decodedUrl == null) throw new IllegalArgumentException("url cannot be null");
        store.unsubscribeUrl(user, decodedUrl);
    }

    /**
     * Fetch the RSS feed content using Ribbon
     */
    private RSS fetchRSSFeed(String url) {

        RestClient client = (RestClient) ClientFactory.getNamedClient(RSSConstants.MIDDLETIER_REST_CLIENT);
        HttpClientResponse response;
        String rssData = null;

        try {
            HttpClientRequest request = HttpClientRequest.newBuilder().setUri(new URI(url)).build();
            response = client.execute(request);

            if (response != null) {
                rssData  = IOUtils.toString(response.getRawEntity(), Charsets.UTF_8);
                logger.info("Status code for " + response.getRequestedURI() + " : " + response.getStatus());
            }
        } catch (URISyntaxException e) {
            logger.error("Exception occurred when setting the URI", e);
        } catch (Exception e) {
            logger.error("Exception occurred when executing the HTTP request", e);
        }

        return parseRSS(url, rssData);
    }

    /**
     * Parses the RSS feeds and return back a POJO
     */
    private RSS parseRSS(String url, String rss) {
        // Error case
        if (rss == null) return new RSSImpl();
        
        RSS rssItems = null;
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
        try {
            dbf.setFeature(FEATURE, true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            try {
                InputSource is = new InputSource(new StringReader(rss));
                Document dom = db.parse(is);
                Element docEle = dom.getDocumentElement();
                
                List<RSSItem> items = new ArrayList<RSSItem>();
                String title = docEle.getElementsByTagName("title").item(0).getTextContent();
                NodeList nl  = docEle.getElementsByTagName("item");
                if (nl != null && nl.getLength() > 0) {
                    for (int i = 0 ; i < nl.getLength(); i++) {
                        Element el = (Element) nl.item(i);
                        items.add(new RSSItemImpl(el.getElementsByTagName("title").item(0).getTextContent(), el.getElementsByTagName("link").item(0).getTextContent(), el.getElementsByTagName("description").item(0).getTextContent()));
                    }
                }
                rssItems = new RSSImpl(url, title, items);
                
            } catch (SAXException e) {
                logger.error("Exception occurred during parsing the RSS feed", e);
            } catch (IOException e) {
                logger.error("Exception occurred during fetching the RSS feed", e);
            }
        } catch (ParserConfigurationException e) {
            logger.error("Exception occurred during parsing the RSS feed", e);
        }

        if (rssItems == null) {
            rssItems = new RSSImpl();
        }
        return rssItems;
    }

    public int getStatus() {
        return store == null ? 500 : 200;
    }
}