/*
 * HelpTOCPanel.java - Help table of contents
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 1999, 2004 Slava Pestov
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.help;

//{{{ Imports
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;

import org.gjt.sp.util.ThreadUtilities;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import org.gjt.sp.jedit.browser.FileCellRenderer; // for icons
import org.gjt.sp.jedit.*;
import org.gjt.sp.util.EnhancedTreeCellRenderer;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.StandardUtilities;
import org.gjt.sp.util.XMLUtilities;

import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
//}}}

public class HelpTOCPanel extends JPanel
{
	//{{{ HelpTOCPanel constructor
	public HelpTOCPanel(HelpViewerInterface helpViewer)
	{
		super(new BorderLayout());

		this.helpViewer = helpViewer;
		nodes = new HashMap<String,DefaultMutableTreeNode>();

		toc = new TOCTree();

		// looks bad with the OS X L&F, apparently...
		if(!OperatingSystem.isMacOSLF())
			toc.putClientProperty("JTree.lineStyle", "Angled");

		toc.setRowHeight(0);
		toc.setCellRenderer(new TOCCellRenderer());
		toc.setEditable(false);
		toc.setShowsRootHandles(true);

		add(BorderLayout.CENTER,new JScrollPane(toc));

		load();
	} //}}}

	//{{{ selectNode() method
	public void selectNode(String shortURL)
	{
		if(tocModel == null)
			return;

		final DefaultMutableTreeNode node =
			nodes.get(shortURL);

		if(node == null)
			return;

		EventQueue.invokeLater(new Runnable()
		{
			public void run()
			{
				TreePath path = new TreePath(tocModel.getPathToRoot(node));
				toc.expandPath(path);
				toc.setSelectionPath(path);
				toc.scrollPathToVisible(path);
			}
		});
	} //}}}

	//{{{ load() method
	public void load()
	{
		DefaultTreeModel empty = new DefaultTreeModel(
			new DefaultMutableTreeNode(
			jEdit.getProperty("helpviewer.toc.loading")));
		toc.setModel(empty);
		toc.setRootVisible(true);

		ThreadUtilities.runInBackground(new Runnable()
		{
			public void run()
			{
				createTOC();
				tocModel.reload(tocRoot);
				toc.setModel(tocModel);
				toc.setRootVisible(false);
				for(int i = 0; i <tocRoot.getChildCount(); i++)
				{
					DefaultMutableTreeNode node =
						(DefaultMutableTreeNode)
						tocRoot.getChildAt(i);
					toc.expandPath(new TreePath(
						node.getPath()));
				}
				if(helpViewer.getShortURL() != null)
					selectNode(helpViewer.getShortURL());
			}
		});
	} //}}}

	//{{{ Private members
	private HelpViewerInterface helpViewer;
	private DefaultTreeModel tocModel;
	private DefaultMutableTreeNode tocRoot;
	private JTree toc;
	private Map<String, DefaultMutableTreeNode> nodes;

	//{{{ createNode() method
	private DefaultMutableTreeNode createNode(String href, String title)
	{
		DefaultMutableTreeNode node = new DefaultMutableTreeNode(
			new HelpNode(href,title),true);
		nodes.put(href,node);
		return node;
	} //}}}

	//{{{ createTOC() method
	private void createTOC()
	{
		EditPlugin[] plugins = jEdit.getPlugins();
		Arrays.sort(plugins,new PluginCompare());
		tocRoot = new DefaultMutableTreeNode();

		tocRoot.add(createNode("welcome.html",
			jEdit.getProperty("helpviewer.toc.welcome")));

		tocRoot.add(createNode("README.txt",
			jEdit.getProperty("helpviewer.toc.readme")));
		tocRoot.add(createNode("CHANGES.txt",
			jEdit.getProperty("helpviewer.toc.changes")));
		tocRoot.add(createNode("TODO.txt",
			jEdit.getProperty("helpviewer.toc.todo")));
		tocRoot.add(createNode("COPYING.txt",
			jEdit.getProperty("helpviewer.toc.copying")));
		tocRoot.add(createNode("COPYING.DOC.txt",
			jEdit.getProperty("helpviewer.toc.copying-doc")));
		tocRoot.add(createNode("Apache.LICENSE.txt",
			jEdit.getProperty("helpviewer.toc.copying-apache")));
		tocRoot.add(createNode("COPYING.PLUGINS.txt",
			jEdit.getProperty("helpviewer.toc.copying-plugins")));

		loadTOC(tocRoot,"whatsnew/toc.xml");
		loadTOC(tocRoot,"users-guide/toc.xml");
		loadTOC(tocRoot,"FAQ/toc.xml");


		DefaultMutableTreeNode pluginTree = new DefaultMutableTreeNode(
			jEdit.getProperty("helpviewer.toc.plugins"),true);

		for (EditPlugin plugin : plugins)
		{
			String name = plugin.getClassName();

			String docs = jEdit.getProperty("plugin." + name + ".docs");
			String label = jEdit.getProperty("plugin." + name + ".name");
			if (label != null && docs != null)
			{
				String path = plugin.getPluginJAR().getClassLoader().getResourceAsPath(docs);
				pluginTree.add(createNode(path, label));
			}
		}

		if(pluginTree.getChildCount() != 0)
			tocRoot.add(pluginTree);
		else
		{
			// so that HelpViewer constructor doesn't try to expand
			pluginTree = null;
		}
		loadTOC(tocRoot,"api/toc.xml");
		tocModel = new DefaultTreeModel(tocRoot);
	} //}}}

	//{{{ loadTOC() method
	private void loadTOC(DefaultMutableTreeNode root, String path)
	{
		TOCHandler h = new TOCHandler(root,MiscUtilities.getParentOfPath(path));
		try
		{
			XMLUtilities.parseXML(
				new URL(helpViewer.getBaseURL()
					+ '/' + path).openStream(), h);
		}
		catch(FileNotFoundException e)
		{
			/* it is acceptable only for the API TOC :
			   the user can choose not to install them
			 */
			if("api/toc.xml".equals(path))
			{
				Log.log(Log.NOTICE,this,
					"The API docs for jEdit will not be available (reinstall jEdit if you want them)");
				root.add(
					createNode("http://www.jedit.org/api/overview-summary.html",
						jEdit.getProperty("helpviewer.toc.online-apidocs")));
			}
			else
			{
				Log.log(Log.ERROR,this,e);
			}
		}
		catch(IOException e)
		{
			Log.log(Log.ERROR,this,e);
		}
	} //}}}

	//}}}

	//{{{ HelpNode class
	static class HelpNode
	{
		String href, title;

		//{{{ HelpNode constructor
		HelpNode(String href, String title)
		{
			this.href = href;
			this.title = title;
		} //}}}

		//{{{ toString() method
		public String toString()
		{
			return title;
		} //}}}
	} //}}}

	//{{{ TOCHandler class
	class TOCHandler extends DefaultHandler
	{
		String dir;

		//{{{ TOCHandler constructor
		TOCHandler(DefaultMutableTreeNode root, String dir)
		{
			nodes = new Stack<DefaultMutableTreeNode>();
			node = root;
			this.dir = dir;
		} //}}}

		//{{{ characters() method
		public void characters(char[] c, int off, int len)
		{
			if(tag.equals("TITLE"))
			{
				boolean firstNonWhitespace = false;
				for(int i = 0; i < len; i++)
				{
					char ch = c[off + i];
					if (!firstNonWhitespace && Character.isWhitespace(ch)) continue;
					firstNonWhitespace = true;
					title.append(ch);
				}
			}


		} //}}}

		//{{{ startElement() method
		public void startElement(String uri, String localName,
					 String name, Attributes attrs)
		{
			tag = name;
			if (name.equals("ENTRY"))
				href = attrs.getValue("HREF");
		} //}}}

		//{{{ endElement() method
		public void endElement(String uri, String localName, String name)
		{
			if(name == null)
				return;

			if(name.equals("TITLE"))
			{
				DefaultMutableTreeNode newNode = createNode(
					dir + href,title.toString());
				node.add(newNode);
				nodes.push(node);
				node = newNode;
				title.setLength(0);
			}
			else if(name.equals("ENTRY"))
			{
				node = nodes.pop();
				href = null;
			}
		} //}}}

		//{{{ Private members
		private String tag;
		private StringBuilder title = new StringBuilder();
		private String href;
		private DefaultMutableTreeNode node;
		private Stack<DefaultMutableTreeNode> nodes;
		//}}}
	} //}}}

	//{{{ TOCTree class
	class TOCTree extends JTree
	{
		//{{{ TOCTree constructor
		TOCTree()
		{
			ToolTipManager.sharedInstance().registerComponent(this);
			selectionModel.setSelectionMode(SINGLE_TREE_SELECTION);
		} //}}}

		//{{{ getToolTipText() method
		public final String getToolTipText(MouseEvent evt)
		{
			TreePath path = getPathForLocation(evt.getX(), evt.getY());
			if(path != null)
			{
				Rectangle cellRect = getPathBounds(path);
				if(cellRect != null && !cellRectIsVisible(cellRect))
					return path.getLastPathComponent().toString();
			}
			return null;
		} //}}}

		//{{{ getToolTipLocation() method
		/* public final Point getToolTipLocation(MouseEvent evt)
		{
			TreePath path = getPathForLocation(evt.getX(), evt.getY());
			if(path != null)
			{
				Rectangle cellRect = getPathBounds(path);
				if(cellRect != null && !cellRectIsVisible(cellRect))
				{
					return new Point(cellRect.x + 14, cellRect.y);
				}
			}
			return null;
		} */ //}}}

		//{{{ processKeyEvent() method
		public void processKeyEvent(KeyEvent evt)
		{
			if ((KeyEvent.KEY_PRESSED == evt.getID()) &&
			    (KeyEvent.VK_ENTER == evt.getKeyCode()))
			{
				TreePath path = getSelectionPath();
				if(path != null)
				{
					Object obj = ((DefaultMutableTreeNode)
						path.getLastPathComponent())
						.getUserObject();
					if(!(obj instanceof HelpNode))
					{
						this.expandPath(path);
						return;
					}

					HelpNode node = (HelpNode)obj;
					helpViewer.gotoURL(node.href,true,0);
				}
				evt.consume();
			}
			else
			{
				super.processKeyEvent(evt);
			}
		} //}}}

		//{{{ processMouseEvent() method
		protected void processMouseEvent(MouseEvent evt)
		{
			//ToolTipManager ttm = ToolTipManager.sharedInstance();

			switch(evt.getID())
			{
			/* case MouseEvent.MOUSE_ENTERED:
				toolTipInitialDelay = ttm.getInitialDelay();
				toolTipReshowDelay = ttm.getReshowDelay();
				ttm.setInitialDelay(200);
				ttm.setReshowDelay(0);
				super.processMouseEvent(evt);
				break;
			case MouseEvent.MOUSE_EXITED:
				ttm.setInitialDelay(toolTipInitialDelay);
				ttm.setReshowDelay(toolTipReshowDelay);
				super.processMouseEvent(evt);
				break; */
			case MouseEvent.MOUSE_CLICKED:
				TreePath path = getPathForLocation(evt.getX(),evt.getY());
				if(path != null)
				{
					if(!isPathSelected(path))
						setSelectionPath(path);

					Object obj = ((DefaultMutableTreeNode)
						path.getLastPathComponent())
						.getUserObject();
					if(!(obj instanceof HelpNode))
					{
						this.expandPath(path);
						return;
					}

					HelpNode node = (HelpNode)obj;

					helpViewer.gotoURL(node.href,true,0);
				}

				super.processMouseEvent(evt);
				break;
			default:
				super.processMouseEvent(evt);
				break;
			}
		} //}}}

		//{{{ cellRectIsVisible() method
		private boolean cellRectIsVisible(Rectangle cellRect)
		{
			Rectangle vr = TOCTree.this.getVisibleRect();
			return vr.contains(cellRect.x,cellRect.y) &&
				vr.contains(cellRect.x + cellRect.width,
				cellRect.y + cellRect.height);
		} //}}}
	} //}}}

	//{{{ TOCCellRenderer class
	static class TOCCellRenderer extends EnhancedTreeCellRenderer
	{
		@Override
		protected TreeCellRenderer newInstance()
		{
			return new TOCCellRenderer();
		}

		@Override
		protected void configureTreeCellRendererComponent(JTree tree,
			Object value, boolean sel, boolean expanded,
			boolean leaf, int row, boolean focus)
		{
			setIcon(leaf ? FileCellRenderer.fileIcon
				: (expanded ? FileCellRenderer.openDirIcon
				: FileCellRenderer.dirIcon));
			setBorder(border);
		}

		EmptyBorder border = new EmptyBorder(1,0,1,1);
	} //}}}

	//{{{ PluginCompare class
	static class PluginCompare implements Comparator<EditPlugin>
	{
		public int compare(EditPlugin p1, EditPlugin p2)
		{
			return StandardUtilities.compareStrings(
				jEdit.getProperty("plugin." + p1.getClassName() + ".name"),
				jEdit.getProperty("plugin." + p2.getClassName() + ".name"),
				true);
		}
	} //}}}
}
