/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.myorg.feedreader;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.actions.DeleteAction;
import org.openide.actions.OpenAction;
import org.openide.cookies.InstanceCookie;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.Repository;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.BeanNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;

public class RssNode extends FilterNode {
    
    /** Declaring the children of the root RSS node */
    public RssNode(Node folderNode) {
        super(folderNode, new RssFolderChildren(folderNode));
    }
    
    /** Declaring the Add Feed action and Add Folder action */
    public Action[] getActions(boolean popup) {
        DataFolder df = (DataFolder)getLookup().lookup(DataFolder.class);
        return new Action[] {
            new AddRssAction(df),
            new AddFolderAction(df)
        };
    }
    
    /** Getting the root node */
    public static class RootRssNode extends RssNode {
        public RootRssNode() throws DataObjectNotFoundException {
            super(DataObject.find(
                    Repository.getDefault().getDefaultFileSystem()
                    .getRoot().getFileObject("RssFeeds")).getNodeDelegate());
        }
        
        public String getDisplayName() {
            return NbBundle.getMessage(RssNode.class, "FN_title");
        }
    }
    
    /** Getting the children of the root node */
    private static class RssFolderChildren extends FilterNode.Children {
        RssFolderChildren(Node rssFolderNode) {
            super(rssFolderNode);
        }
        
        protected Node[] createNodes(Object key) {
            Node n = (Node) key;
            if (n.getLookup().lookup(DataFolder.class) != null) {
                return new Node[] {new RssNode(n)};
            } else {
                Feed feed = getFeed(n);
                if (feed != null) {
                    try {
                        return new Node[] {new OneFeedNode(n, feed.getSyndFeed())};
                    } catch (IOException ioe) {
                        ErrorManager.getDefault().notify(ioe);
                    }
                }
            }
            // best effort
            return new Node[] {new FilterNode(n)};
        }
    }
    
    /** Getting the feed node and wrapping it in a FilterNode */
    private static class OneFeedNode extends FilterNode {
        
        OneFeedNode(Node feedFileNode, SyndFeed feed) {
            super(feedFileNode,
                    new FeedChildren(feed),
                    new ProxyLookup(new Lookup[] {
                Lookups.fixed(new Object[] {feed}),
                feedFileNode.getLookup()
            }));
        }
        
        public String getDisplayName() {
            SyndFeed feed = (SyndFeed) getLookup().lookup(SyndFeed.class);
            return feed.getTitle();
        }
        
        public Image getIcon(int type) {
            return Utilities.loadImage("org/myorg/feedreader/rss16.gif");
        }
        
        public Image getOpenedIcon(int type) {
            return getIcon(type);
        }
        
        public Action[] getActions(boolean context) {
            return new Action[] { SystemAction.get(DeleteAction.class) };
        }
        
    }
    
    /** Defining the children of a feed node */
    private static class FeedChildren extends Children.Keys {
        private final SyndFeed feed;
        public FeedChildren(SyndFeed feed) {
            this.feed = feed;
        }
        
        protected void addNotify() {
            setKeys(feed.getEntries());
        }
        
        public Node[] createNodes(Object key) {
            try {
                return new Node[] { new EntryBeanNode((SyndEntry) key) };
            } catch (IntrospectionException ex) {
                assert false : ex;
                return new Node[0];
            }
        }
    }
    
    /** Wrapping the children in a FilterNode */
    private static class EntryBeanNode extends FilterNode {
        
        private final SyndEntry entry;
        
        public EntryBeanNode(SyndEntry entry) throws IntrospectionException {
            super(new BeanNode(entry), Children.LEAF, Lookups.fixed(new Object[] { entry, new EntryOpenCookie(entry) }));
            this.entry = entry;
        }
        
        /** Using HtmlDisplayName ensures any HTML in RSS entry titles are properly handled, escaped, entities resolved, etc. */
        public String getHtmlDisplayName() {
            return entry.getTitle();
        }
        
        /** Making a tooltip out of the entry's description */
        public String getShortDescription() {
            StringBuffer sb = new StringBuffer();
            sb.append("Author: " + entry.getAuthor() + "; ");
            if (entry.getPublishedDate() != null) {
                sb.append("Published: ").append(entry.getPublishedDate().toString());
            }
            return sb.toString();
        }
        
        /** Providing the Open action on a feed entry */
        public Action[] getActions(boolean popup) {
            return new Action[] { SystemAction.get(OpenAction.class) };
        }
        
        public Action getPreferredAction() {
            return (SystemAction) getActions(false) [0];
        }
        
    }
    
    /** Specifying what should happen when the user invokes the Open action */
    private static class EntryOpenCookie implements OpenCookie {
        
        private final SyndEntry entry;
        
        EntryOpenCookie(SyndEntry entry) {
            this.entry = entry;
        }
        
        public void open() {
            BrowserTopComponent btc = BrowserTopComponent.getBrowserComponent(entry.getTitle());
            btc.open();
            btc.requestActive();
            btc.setPage(entry.getUri());
        }
        
    }
    
    /** Looking up a feed */
    private static Feed getFeed(Node node) {
        InstanceCookie ck = (InstanceCookie) node.getCookie(InstanceCookie.class);
        if (ck == null) {
            throw new IllegalStateException("Bogus file in feeds folder: " + node.getLookup().lookup(FileObject.class));
        }
        try {
            return (Feed) ck.instanceCreate();
        } catch (ClassNotFoundException ex) {
            ErrorManager.getDefault().notify(ex);
        } catch (IOException ex) {
            ErrorManager.getDefault().notify(ex);
        }
        return null;
    }
    
    /** An action for adding a folder to organize feeds into groups */
    private static class AddFolderAction extends AbstractAction {
        
        private final DataFolder folder;
        
        public AddFolderAction(DataFolder df) {
            super(NbBundle.getMessage(RssNode.class, "FN_addfolderbutton"));
            folder = df;
        }
        
        public void actionPerformed(ActionEvent ae) {
            NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
                    NbBundle.getMessage(RssNode.class, "FN_askfolder_msg"),
                    NbBundle.getMessage(RssNode.class, "FN_askfolder_title"),
                    NotifyDescriptor.OK_CANCEL_OPTION,
                    NotifyDescriptor.PLAIN_MESSAGE);
            
            Object result = DialogDisplayer.getDefault().notify(nd);
            
            if (result.equals(NotifyDescriptor.OK_OPTION)) {
                final String folderString = nd.getInputText();
                try {
                    DataFolder.create(folder, folderString);
                } catch (IOException ex) {
                    ErrorManager.getDefault().notify(ex);
                }
            }
        }
    }
    
    /** An action for adding a feed */
    private static class AddRssAction extends AbstractAction {
        
        private final DataFolder folder;
        
        public AddRssAction(DataFolder df) {
            super(NbBundle.getMessage(RssNode.class, "FN_addbutton"));
            folder = df;
        }
        
        public void actionPerformed(ActionEvent ae) {
            NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
                    NbBundle.getMessage(RssNode.class, "FN_askurl_msg"),
                    NbBundle.getMessage(RssNode.class, "FN_askurl_title"),
                    NotifyDescriptor.OK_CANCEL_OPTION,
                    NotifyDescriptor.PLAIN_MESSAGE);
            
            Object result = DialogDisplayer.getDefault().notify(nd);
            
            if (result.equals(NotifyDescriptor.OK_OPTION)) {
                String urlString = nd.getInputText();
                URL url;
                try {
                    url = new URL(urlString);
                } catch (MalformedURLException e) {
                    String message = NbBundle.getMessage(RssNode.class, "FN_askurl_err", urlString);
                    ErrorManager.getDefault().annotate(e, ErrorManager.ERROR, null, message, null, null);
                    ErrorManager.getDefault().notify(e);
                    return;
                }
                try {
                    checkConnection(url);
                } catch (IOException e) {
                    String message = NbBundle.getMessage(RssNode.class, "FN_cannotConnect_err", urlString);
                    ErrorManager.getDefault().annotate(e, ErrorManager.ERROR, null, message, null, null);
                    ErrorManager.getDefault().notify(e);
                    return;
                }
                Feed f = new Feed(url);
                FileObject fld = folder.getPrimaryFile();
                String baseName = "RssFeed";
                int ix = 1;
                while (fld.getFileObject(baseName + ix, "ser") != null) {
                    ix++;
                }
                try {
                    FileObject writeTo = fld.createData(baseName + ix, "ser");
                    FileLock lock = writeTo.lock();
                    try {
                        ObjectOutputStream str = new ObjectOutputStream(writeTo.getOutputStream(lock));
                        try {
                            str.writeObject(f);
                        } finally {
                            str.close();
                        }
                    } finally {
                        lock.releaseLock();
                    }
                } catch (IOException ioe) {
                    ErrorManager.getDefault().notify(ioe);
                }
            }
        }
        
        private static void checkConnection(final URL url) throws IOException {
            InputStream is = url.openStream();
            is.close();
        }
        
    }
    
}
