import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedList;
import java.util.regex.Pattern;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.ChangedCharSetException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.parser.DTD;
import javax.swing.text.html.parser.Parser;
import javax.swing.text.html.parser.TagElement;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeSelectionModel;

public class Linker extends JFrame {
    protected LinkParser parser;
    protected JTree tree;
    protected JScrollPane treeView;
    protected Set shownURLs;
    protected NodeExpander expander;
    
    protected class NodeExpander extends Thread {
	protected boolean halted;
	protected DefaultMutableTreeNode node;

	public NodeExpander(String str) {
	    super(str);
	    halted = false;
	}
	
	public void setNode(DefaultMutableTreeNode node) {
	    this.node = node;
	}

	public void run() {
	    System.out.println("Expander running");
	    Object nodeInfo = node.getUserObject();
	    if (nodeInfo instanceof URL) {
		URL url = (URL)nodeInfo;
		if (url != null) {
		    URL aux = url;
		    url = null;
		    if (aux.getProtocol().equals("http")) {
			try {
			    Reader in =
				new BufferedReader(new InputStreamReader(aux.openStream()));
			    parser.parse(aux, in);
			    Runnable update = new Runnable() {
				    public void run() {
					expandNodes(node);
				    }
				};
			    SwingUtilities.invokeLater(update);
			} catch (IOException ie) {
			    System.err.println("Cannot process " + aux);
			}
		    } else {
			System.out.println("Non-HTTP url");
		    }
	    }
		System.out.println("Expander stopping");
	    }
	}
    }

    public void expandNodes(DefaultMutableTreeNode node) {
	List children = parser.getChildURLs();
	Iterator it = children.iterator();
	while (it.hasNext()) {
	    URL child = (URL)it.next();
	    DefaultMutableTreeNode childNode = null;
	    if (shownURLs.contains(child)) {
		System.out.println("Jo on " + child);
	    } else {
		childNode =
		    new DefaultMutableTreeNode(child);
		shownURLs.add(child);
		node.add(childNode);
	    }
	}
    }

    public Linker(DTD dtd, String rootName) throws MalformedURLException {
	super("Linker " + dtd);
	shownURLs = new HashSet();
	parser = new LinkParser(dtd);	
	expander = new NodeExpander("Node expander");
	URL root = new URL(rootName);
	shownURLs.add(root);
	DefaultMutableTreeNode top = new DefaultMutableTreeNode(root);
	tree = new JTree(top);
        tree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                                   tree.getLastSelectedPathComponent();

                if (node == null) return;
                Object nodeInfo = node.getUserObject();
		if (nodeInfo instanceof URL) {
		    URL nodeURL = (URL)nodeInfo;
		    if (node.isLeaf()) {
			System.out.println("Leaf node " + nodeURL + ", expanding");
			expander.setNode(node);
			expander.start();
		    } else {
			System.out.println("Non-leaf node " + nodeURL);
		    }
		} else {
		    System.out.println("Non-URL " + nodeInfo);
		}
            }
        });

	treeView = new JScrollPane(tree);
	treeView.setMinimumSize(new Dimension(100,100));
	treeView.setPreferredSize(new Dimension(100,100));
	getContentPane().add(treeView, BorderLayout.CENTER);
	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	pack();
	setVisible(true);
    }

    protected void expand(String urlString) {
	
    }

    public LinkParser getParser() {
	return parser;
    }

    public static void main(String[] args) {
 	try {
	    Linker linker = new Linker(DTD.getDTD("HTML 4.01"), args[0]);

// 	    LinkParser parser = linker.getParser();

// 	    URL url = new URL(args[0]);
// 	    Pattern pattern = Pattern.compile("\\s*(y|yes|Y|Yes|YES)\\s*");
// 	    BufferedReader tty = 
// 		new BufferedReader(new InputStreamReader(System.in));
// 	    while (url != null) {
// 		if (url.getProtocol().equals("http")) {
// 		    System.out.print(url.toExternalForm() + " proceed (y/n) ");
// 		    if (pattern.matcher(tty.readLine()).matches()) {
// 			System.out.println("Processing: " + url.toExternalForm());
// 			Reader in =
// 			    new BufferedReader(new InputStreamReader(url.openStream()));
// 			parser.parse(url, in);
// 		    }
// 		} else {
// 		    System.out.println("Skipping URL " + url);
// 		}
// 		url = parser.nextURL();
// 	    }
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

}

// Protocol: First call parse(URL, Reader) with some url and reader you've
// created.  Then get the next URL by calling nextURL, open it, and call
// parse(URL, Reader) again.
class LinkParser extends Parser {
    protected Set allURLs;
    protected List newURLs;
    protected List childURLs;
    protected URL currentURL;

    public LinkParser(DTD dtd) {
	super(dtd);
	clearURLs();
    }

    public void clearURLs() {
	allURLs = new HashSet();
	newURLs = new LinkedList();
    }

    public void parse(URL url, Reader in) throws IOException {
	childURLs = new LinkedList();
	currentURL = url;
	if (!allURLs.contains(url)) {
	    allURLs.add(url);
	}
	parse(in);
// 	System.out.println("newURLs = ");
// 	Iterator it = newURLs.iterator();
// 	while(it.hasNext()) {
// 	    System.out.println("  " + it.next());
// 	}
    }

    protected void addURL(URL url) {
	if (!allURLs.contains(url)) {
	    allURLs.add(url);
	    newURLs.add(newURLs.size(), url);
	}
    }

    protected void handleEmptyTag(TagElement tag) {
	HTML.Tag ht = tag.getHTMLTag();
	SimpleAttributeSet as = getAttributes();
	Object href = as.getAttribute(HTML.Attribute.HREF);
	if (href != null) {
	    URL newURL = null;
	    try {
		newURL = new URL(currentURL, (String)href);
	    } catch(Exception e) {
		System.err.println("Cannot create url " + currentURL + ", " + href);
	    }
	    if (newURL != null) {
		childURLs.add(childURLs.size(), newURL);
		addURL(newURL);
	    }
	}
    }

    public URL nextURL() {
	if (!newURLs.isEmpty()) {
	    Object first = newURLs.get(0);
	    newURLs.remove(first);
	    return (URL)first;
	} else {
	    return null;
	}
    }

    public List getChildURLs() {
	return childURLs;
    }

}
