/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.servicemix.components.rss;

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.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;

import org.apache.servicemix.components.util.OutBinding;
import org.apache.servicemix.expression.Expression;
import org.apache.servicemix.expression.ExpressionHelper;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;

import javax.jbi.JBIException;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * This component generates an RSS/Atom Feed from the JBI messages it receives.
 * 
 * @version $Revision: 2153 $
 */
public class FeedWriter extends OutBinding {

    private String feedType = "atom_0.3";
    private SyndFeed cachedFeed;
    private String title;
    private String link = "http://servicemix.org/RSS";
    private Expression entryTitle;
    private Expression entryValue;
    private String feedDescription = "This feed is auto-generated by ServiceMix";
    private int maximumEntryCount = 200;
    private File feedFile;
    private String contentType = "text/plain";
    private SourceTransformer sourceTransformer = new SourceTransformer();
    private boolean loadOnStartup = true;

    public synchronized SyndFeed getCachedFeed() throws IllegalArgumentException, FeedException, IOException {
        if (cachedFeed == null) {
            cachedFeed = loadOrCreateFeed();
        }
        return cachedFeed;
    }

    public void setCachedFeed(SyndFeed feed) {
        this.cachedFeed = feed;
    }

    public String getFeedType() {
        return feedType;
    }

    public void setFeedType(String feedType) {
        this.feedType = feedType;
    }

    public String getFeedDescription() {
        return feedDescription;
    }

    public void setFeedDescription(String feedDescription) {
        this.feedDescription = feedDescription;
    }

    public String getTitle() {
        if (title == null) {
            title = "ServiceMix feed for service: " + getService();
        }
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public Expression getEntryTitle() {
        return entryTitle;
    }

    public void setEntryTitle(Expression title) {
        this.entryTitle = title;
    }

    public Expression getEntryValue() {
        return entryValue;
    }

    public void setEntryValue(Expression entryValue) {
        this.entryValue = entryValue;
    }

    public int getMaximumEntryCount() {
        return maximumEntryCount;
    }

    public void setMaximumEntryCount(int maximumEntryCount) {
        this.maximumEntryCount = maximumEntryCount;
    }

    public File getFeedFile() {
        if (feedFile == null) {
            throw new IllegalArgumentException("You must set the 'feedFile' property");
        }
        return feedFile;
    }

    public void setFeedFile(File feedFile) {
        this.feedFile = feedFile;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public SourceTransformer getSourceTransformer() {
        return sourceTransformer;
    }

    public void setSourceTransformer(SourceTransformer sourceTransformer) {
        this.sourceTransformer = sourceTransformer;
    }

    public boolean isLoadOnStartup() {
        return loadOnStartup;
    }

    public void setLoadOnStartup(boolean loadOnStartup) {
        this.loadOnStartup = loadOnStartup;
    }

    // Implementation methods
    // -------------------------------------------------------------------------

    protected void init() throws JBIException {
        super.init();

        // enforce validation
        File file = getFeedFile();
        if (file.exists() && file.isDirectory()) {
            throw new IllegalArgumentException("Cannot generate the cachedFeed as the cachedFeed file is a directory: " + file);
        }
    }

    protected void process(MessageExchange exchange, NormalizedMessage message) throws Exception {
        SyndFeed feed = getCachedFeed();
        synchronized (feed) {
            addMessageToFeed(feed, exchange, message);
            removeExpiredEntries(feed);
            writeFeed(feed, exchange, message);
        }
        done(exchange);
    }

    protected void addMessageToFeed(SyndFeed feed, MessageExchange exchange, NormalizedMessage message) throws TransformerException, MessagingException {
        List entries = feed.getEntries();
        SyndEntry entry = createEntry(exchange, message);
        SyndContent description = createEntryContent(exchange, message);
        entry.setDescription(description);
        
        // TODO this line really should work but for some reason it doesn't
        // entries.add(0, entry);
        
        List temp = new ArrayList();
        temp.add(entry);
        temp.addAll(entries);
        feed.setEntries(temp);
    }

    protected SyndEntry createEntry(MessageExchange exchange, NormalizedMessage message) throws MessagingException {
        SyndEntry entry = new SyndEntryImpl();

        entry.setTitle(ExpressionHelper.asString(getEntryTitle(), exchange, message, "ServiceMix Feed"));
        entry.setLink(getLink());
        entry.setPublishedDate(new Date());
        return entry;
    }

    protected SyndContent createEntryContent(MessageExchange exchange, NormalizedMessage message) throws TransformerException, MessagingException {
        SyndContent description = new SyndContentImpl();
        description.setType(contentType);
        Source content = message.getContent();

        String value = ExpressionHelper.asString(getEntryValue(), exchange, message, null);
        if (value == null && content != null) {
            value = getSourceTransformer().toString(content);
            value = encodeContent(value);
        }
        if (value != null) {
            description.setValue(value);
        }
        return description;
    }

    /**
     * Encodes the content so its valid XML when used inside an entry
     */
    protected String encodeContent(String value) {
        return "<![CDATA[" + value + "]]>";
    }

    protected void writeFeed(SyndFeed feed, MessageExchange messageExchange, NormalizedMessage message) throws IOException, FeedException {
        Writer writer = new FileWriter(feedFile);
        SyndFeedOutput output = new SyndFeedOutput();
        output.output(feed, writer);
        writer.close();
    }

    /**
     * Removes any old entires no longer required in the cachedFeed
     * 
     * @param feed
     */
    protected void removeExpiredEntries(SyndFeed feed) {
        // lets limit the count
        if (maximumEntryCount > 0) {
            List entries = feed.getEntries();
            int size = entries.size();
            if (size > maximumEntryCount) {
                entries.subList(maximumEntryCount, size).clear();
            }
        }
    }

    protected SyndFeed loadOrCreateFeed() throws IllegalArgumentException, FeedException, IOException {
        if (isLoadOnStartup()) {
            File file = getFeedFile();
            if (file.exists() && file.isFile()) {
                SyndFeedInput input = new SyndFeedInput();
                XmlReader xmlReader = new XmlReader(file);
                return input.build(xmlReader);
            }
        }
        return createFeed();
    }

    protected SyndFeed createFeed() {
        SyndFeed feed = new SyndFeedImpl();
        feed.setFeedType(feedType);

        feed.setTitle(getTitle());
        feed.setLink(getLink());
        feed.setDescription(getFeedDescription());
        return feed;
    }

}
