/*
 * Copyright (c) 2010, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

package org.contikios.cooja.dialogs;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import org.apache.log4j.Logger;

import org.contikios.cooja.COOJAProject;
import org.contikios.cooja.Cooja;
import org.contikios.cooja.ProjectConfig;

/**
 * This dialog allows a user to manage Cooja extensions: extensions to COOJA that 
 * provide new functionality such as radio mediums, plugins, and mote types.
 *
 * @author Fredrik Osterlind
 */
public class ProjectDirectoriesDialog extends JDialog {
	private static final long serialVersionUID = 1896348946753376556L;
	private static Logger logger = Logger.getLogger(ProjectDirectoriesDialog.class);

	private Cooja gui;

	private JTable table = null;
	private JTextArea projectInfo = new JTextArea("Extension information:");
	private DirectoryTreePanel treePanel = null;

	private ArrayList<COOJAProject> currentProjects = new ArrayList<COOJAProject>();
	private COOJAProject[] returnedProjects = null;

	/**
	 * Shows a blocking configuration dialog.
	 * Returns a list of new COOJA project directories, or null if canceled by the user. 
	 *  
	 * @param parent Parent container
	 * @param gui COOJA
	 * @param currentProjects Current projects
	 * @return New COOJA projects, or null
	 */
	public static COOJAProject[] showDialog(Container parent, Cooja gui, COOJAProject[] currentProjects) {
		if (Cooja.isVisualizedInApplet()) {
			return null;
		}

		ProjectDirectoriesDialog dialog = new ProjectDirectoriesDialog((Window) parent, currentProjects);
		dialog.gui = gui;
		dialog.setLocationRelativeTo(parent);
		dialog.setVisible(true);
		return dialog.returnedProjects;
	}

	private ProjectDirectoriesDialog(Container parent, COOJAProject[] projects) {
		super(
				parent instanceof Dialog?(Dialog)parent:
					parent instanceof Window?(Window)parent:
						(Frame)parent, "Cooja extensions", ModalityType.APPLICATION_MODAL);

		table = new JTable(new AbstractTableModel() {
			private static final long serialVersionUID = 591599455927509191L;
			public int getColumnCount() {
				return 2;
			}
			public int getRowCount() {
				return currentProjects.size();
			}
			public Object getValueAt(int rowIndex, int columnIndex) {
				if (columnIndex == 0) {
					return rowIndex+1;
				}

				COOJAProject p = currentProjects.get(rowIndex);
				if (!p.directoryExists()) {
					return p + "  (not found)";
				}
				if (!p.configExists()) {
					return p + "  (no config)";
				}
				if (!p.configRead()) {
					return p + "  (config error)";
				}
				return p;
			}
		});
    table.setFillsViewportHeight(true);
		table.setTableHeader(null);
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
				if (table.getSelectedRow() < 0) {
					return;
				}
				selectTreeProject(currentProjects.get(table.getSelectedRow()));
				showProjectInfo(currentProjects.get(table.getSelectedRow()));
			}
		});
		table.getColumnModel().getColumn(0).setPreferredWidth(30);
		table.getColumnModel().getColumn(0).setMaxWidth(30);
		table.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
			private static final long serialVersionUID = 7224219223448831880L;
			public Component getTableCellRendererComponent(JTable table,
					Object value, boolean isSelected, boolean hasFocus, int row,
					int column) {
				if (currentProjects.get(row).hasError()) {
					setBackground(Color.RED);
				} else {
					setBackground(table.getBackground());
				}
				return super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
						row, column);
			}
		});

		/* Add current extensions */
		for (COOJAProject project : projects) {
			addProjectDir(project);
		}

		Box mainPane = Box.createVerticalBox();
		Box buttonPane = Box.createHorizontalBox();
		JPanel sortPane;
		JButton button;

		/* Lower buttons */
		{
			buttonPane.setBorder(BorderFactory.createEmptyBorder(0,3,3,3));
			buttonPane.add(Box.createHorizontalGlue());

			button = new JButton("View config");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					try {
						/* Default config */
						ProjectConfig config = new ProjectConfig(true);

						/* Merge configs */
						for (COOJAProject project : getProjects()) {
							config.appendConfig(project.config);
						}

						ConfigViewer.showDialog(ProjectDirectoriesDialog.this, config);
					} catch (Exception ex) {
						logger.fatal("Error when merging config: " + ex.getMessage(), ex);
						return;
					}
				}
			});
			buttonPane.add(button);
			buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));

			button = new JButton("Cancel");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ProjectDirectoriesDialog.this.returnedProjects = null;
					dispose();
				}
			});
			buttonPane.add(button);

			buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));

			button = new JButton("Apply for session");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ProjectDirectoriesDialog.this.returnedProjects = currentProjects.toArray(new COOJAProject[0]);
					dispose();
				}
			});
			buttonPane.add(button);

			buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
			
			button = new JButton("Save");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Object[] options = { "Ok", "Cancel" };

					String newDefaultProjectDirs = "";
					for (COOJAProject p: currentProjects) {
						if (newDefaultProjectDirs != "") {
							newDefaultProjectDirs += ";";
						}

						newDefaultProjectDirs += gui.createPortablePath(p.dir, false).getPath();
					}
					newDefaultProjectDirs = newDefaultProjectDirs.replace('\\', '/');

					String question = "External tools setting DEFAULT_PROJECTDIRS will change from:\n"
						+ Cooja.getExternalToolsSetting("DEFAULT_PROJECTDIRS", "").replace(';', '\n')
						+ "\n\n to:\n\n"
						+ newDefaultProjectDirs.replace(';', '\n');
					String title = "Change external tools settings?";
					int answer = JOptionPane.showOptionDialog(ProjectDirectoriesDialog.this, question, title,
							JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null,
							options, options[0]);

					if (answer != JOptionPane.YES_OPTION) {
						return;
					}

					Cooja.setExternalToolsSetting("DEFAULT_PROJECTDIRS", newDefaultProjectDirs);
					dispose();
				}
			});
			buttonPane.add(button);

			this.getRootPane().setDefaultButton(button);
		}

		/* Center: Tree and list*/
		{
			treePanel = new DirectoryTreePanel(this);

			sortPane = new JPanel(new BorderLayout());
			button = new JButton("Move up");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					int selectedIndex = table.getSelectedRow();
					if (selectedIndex <= 0) {
						return;
					}
					COOJAProject project = currentProjects.get(selectedIndex);
					removeProjectDir(project);
					addProjectDir(project, selectedIndex - 1);
					table.getSelectionModel().setSelectionInterval(selectedIndex - 1, selectedIndex - 1);
				}
			});
			sortPane.add(BorderLayout.NORTH, button);
			
			button = new JButton("Move down");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					int selectedIndex = table.getSelectedRow();
					if (selectedIndex < 0) {
						return;
					}
					if (selectedIndex >= currentProjects.size() - 1) {
						return;
					}
					COOJAProject project = currentProjects.get(selectedIndex);
					removeProjectDir(project);
					addProjectDir(project, selectedIndex + 1);
					table.getSelectionModel().setSelectionInterval(selectedIndex + 1, selectedIndex + 1);
				}
			});
			sortPane.add(BorderLayout.SOUTH, button);

			{
				button = new JButton("Remove");
				
				button.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						int selectedIndex = table.getSelectedRow();
						if (selectedIndex < 0) {
							return;
						}
						if (selectedIndex >= currentProjects.size()) {
							return;
						}
						COOJAProject project = currentProjects.get(selectedIndex);

						String s1 = "Remove";
						String s2 = "Cancel";
						Object[] options = { s1, s2 };
						int n = JOptionPane.showOptionDialog(Cooja.getTopParentContainer(),
								"Remove Cooja project?\n" + project,
								"Remove Cooja project?", JOptionPane.YES_NO_OPTION,
								JOptionPane.WARNING_MESSAGE, null, options, s1);
						if (n != JOptionPane.YES_OPTION) {
							return;
						}
						removeProjectDir(project);
					}
				});
				JPanel p = new JPanel(new BorderLayout());
				p.add(BorderLayout.SOUTH, button);
				sortPane.add(BorderLayout.CENTER, p);
			}

			JPanel tableAndSort = new JPanel(new BorderLayout());
			JScrollPane scroll = new JScrollPane(table);
			tableAndSort.add(BorderLayout.CENTER, scroll);
			tableAndSort.add(BorderLayout.EAST, sortPane);

			final JSplitPane projectPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
			projectPane.setTopComponent(tableAndSort);
			projectInfo.setEditable(false);
			projectPane.setBottomComponent(new JScrollPane(projectInfo));

			final JSplitPane listPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
			listPane.setLeftComponent(treePanel);
			listPane.setRightComponent(projectPane);

			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					projectPane.setDividerLocation(0.6);
					listPane.setDividerLocation(0.5);
				}
			});
			mainPane.add(listPane);
		}

		JPanel topPanel = new JPanel(new BorderLayout());
		/*		topPanel.add(BorderLayout.CENTER, new JLabel("A Cooja project depends on a cooja.config file, and extends Cooja with radio mediums, mote types, plugins etc."));
				topPanel.setBackground(Color.WHITE);*/
		getContentPane().add(BorderLayout.NORTH, topPanel);
		getContentPane().add(BorderLayout.CENTER, mainPane);
		getContentPane().add(BorderLayout.SOUTH, buttonPane);
		setSize(700, 500);
	}

	protected void showProjectInfo(COOJAProject project) {
		projectInfo.setText("");
		if (project.getDescription() != null) {
			projectInfo.append("-- " + project.getDescription() + " --\n\n");
		}
		
		projectInfo.append("Directory: " + project.dir.getAbsolutePath() + 
				(project.directoryExists()?"":": NOT FOUND") + "\n");
		if (!project.directoryExists()) {
			return;
		}
		projectInfo.append("Configuration: " + project.configFile.getAbsolutePath() + 
				(project.configExists()?"":": NOT FOUND") + "\n");
		if (!project.configExists()) {
			return;
		}
		if (!project.configRead()) {
		        projectInfo.append("Parsing: " +
					   (project.configRead()?"OK":"FAILED") + "\n\n");
			return;
		}
		
		if (project.getConfigPlugins() != null) {
			projectInfo.append("Plugins: " + Arrays.toString(project.getConfigPlugins()) + "\n");
		}
		if (project.getConfigJARs() != null) {
			String[] jars = project.getConfigJARs();
			projectInfo.append("JARs: " + Arrays.toString(jars) + "\n");
			for (String jar: jars) {
				File jarFile = Cooja.findJarFile(project.dir, jar);
				if (jarFile == null) {
					projectInfo.append("\tError: " + jar + " could not be found.\n");
				} else if (!jarFile.exists()) {
					projectInfo.append("\tError: " + jarFile.getAbsolutePath() + " could not be found.\n");
				} else {
					projectInfo.append("\t" + jarFile.getAbsolutePath() + " found\n");
				}
			}
		}
		if (project.getConfigMoteTypes() != null) {
			projectInfo.append("Mote types: " + Arrays.toString(project.getConfigMoteTypes()) + "\n");
		}
		if (project.getConfigRadioMediums() != null) {
			projectInfo.append("Radio mediums: " + Arrays.toString(project.getConfigRadioMediums()) + "\n");
		}
		if (project.getConfigMoteInterfaces() != null) {
			projectInfo.append("Cooja mote interfaces: " + Arrays.toString(project.getConfigMoteInterfaces()) + "\n");
		}
		if (project.getConfigCSources() != null) {
			projectInfo.append("Cooja mote C sources: " + Arrays.toString(project.getConfigCSources()) + "\n");
		}
	}

	public COOJAProject[] getProjects() {
		return currentProjects.toArray(new COOJAProject[0]);
	}
	protected void addProjectDir(COOJAProject project) {
		currentProjects.add(project);
		((AbstractTableModel)table.getModel()).fireTableDataChanged();
	}
	protected void addProjectDir(File dir) {
		currentProjects.add(new COOJAProject(dir));
		((AbstractTableModel)table.getModel()).fireTableDataChanged();
	}
	protected void addProjectDir(COOJAProject project, int index) {
		currentProjects.add(index, project);
		((AbstractTableModel)table.getModel()).fireTableDataChanged();
	}
	protected void removeProjectDir(int index) {
		currentProjects.remove(index);
		((AbstractTableModel)table.getModel()).fireTableDataChanged();
	}
	protected void removeProjectDir(File dir) {
		COOJAProject ps[] = getProjects();
		for (COOJAProject p: ps) {
			if (p.dir.equals(dir)) {
				removeProjectDir(p);
			}
		}
	}
	protected void removeProjectDir(COOJAProject project) {
		currentProjects.remove(project);
		((AbstractTableModel)table.getModel()).fireTableDataChanged();
		repaint();
	}
	private int getProjectListIndex(COOJAProject project) {
		return currentProjects.indexOf(project);
	}
	public void selectListProject(File dir) {
		/* Check if project exists */
		for (COOJAProject p: currentProjects) {
			if (dir.equals(p.dir)) {
				int i = getProjectListIndex(p);
				if (i >= 0) {
					table.getSelectionModel().setSelectionInterval(i, i);
				}
				return;
			}
		}

	}
	public void selectTreeProject(COOJAProject project) {
		treePanel.selectProject(project.dir);
	}
}

/**
 * Shows a directory tree, and allows for selecting directories with a cooja.config file.  
 * 
 * @author Fredrik Osterlind
 */
class DirectoryTreePanel extends JPanel {
	private static final long serialVersionUID = -6852893350326771136L;
	private static Logger logger = Logger.getLogger(DirectoryTreePanel.class);

	private ProjectDirectoriesDialog parent;
	private JTree tree;
	private DefaultMutableTreeNode treeRoot;
	public DirectoryTreePanel(ProjectDirectoriesDialog parent) {
		super(new BorderLayout());
		this.parent = parent;

		/* Build directory tree */
		treeRoot = new DefaultMutableTreeNode("My Computer");
		tree = new JTree(treeRoot);
		tree.setRootVisible(false);
		tree.setShowsRootHandles(true);
		tree.expandRow(0);
		tree.setCellRenderer(new DefaultTreeCellRenderer() {
			private static final long serialVersionUID = 280434957859560569L;
			private Icon unselectedIcon = new CheckboxIcon(null);
			private Icon selectedIcon = new CheckboxIcon(new Color(0, 255, 0, 128));
			private Icon errorIcon = new CheckboxIcon(new Color(255, 0, 0, 128));
			private Font boldFont = null;
			private Font normalFont = null;
			public Component getTreeCellRendererComponent(JTree tree,
					Object value, boolean sel, boolean expanded, boolean leaf,
					int row, boolean hasFocus) {
				super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
						row, hasFocus);
				if (value instanceof DefaultMutableTreeNode) {
					value = ((DefaultMutableTreeNode) value).getUserObject();
				}
				if (!(value instanceof TreeDirectory)) {
					return this;
				}
				TreeDirectory td = (TreeDirectory) value;

				if (boldFont == null) {
					normalFont = getFont();
					boldFont = getFont().deriveFont( Font.BOLD );
				}

				/* Style */
				setFont(normalFont);
				if (td.isProject()) {
					if (td.containsConfig()) {
						setIcon(selectedIcon);
					} else {
						/* Error: no cooja.config */
						setIcon(errorIcon);
						setFont(boldFont);
					}
				} else if (td.containsConfig()) {
					setIcon(unselectedIcon);
				} else if (td.subtreeContainsProject()) {
					setFont(boldFont);
				}

				return this;
			}
			class CheckboxIcon implements Icon {
				Icon icon;
				Color color;
				public CheckboxIcon(Color color) {
					this.icon = (Icon) UIManager.get("CheckBox.icon");
					this.color = color;
				}
				public int getIconHeight() {
					if (icon == null) {
						return 18;
					}
					return icon.getIconHeight();
				}
				public int getIconWidth() {
					if (icon == null) {
						return 18;
					}
					return icon.getIconWidth();
				}
				public void paintIcon(Component c, Graphics g, int x, int y) {
					if (icon != null) {
						try {
							icon.paintIcon(c, g, x, y);
						} catch (Exception e) {
							icon = null;
						}
					}
					if (icon == null) {
						g.setColor(Color.WHITE);
						g.fillRect(x+1, y+1, 16, 16);
						g.setColor(Color.BLACK);
						g.drawRect(x+1, y+1, 16, 16);
					}
					if (color != null) {
						g.setColor(color);
						g.fillRect(x, y, getIconWidth(), getIconHeight());
					}
				}
			}
		});
		tree.setModel(new COOJAProjectTreeModel(treeRoot));
		tree.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
				if (selPath == null) {
					return;
				}
				if (e.getClickCount() != 1) {
					return;
				}
				Object o = selPath.getLastPathComponent();
				if (!(o instanceof DefaultMutableTreeNode)) {
					return;
				}
				if (!(((DefaultMutableTreeNode) o).getUserObject() instanceof TreeDirectory)) {
					return;
				}
				TreeDirectory pd = (TreeDirectory) ((DefaultMutableTreeNode) o).getUserObject();
				Rectangle r = tree.getPathBounds(selPath);
				int delta = e.getX() - r.x;
				if (delta > 18 /* XXX Icon width */) {
					return;
				}

				if (pd.isProject()) {
					/* Remove project */
					DirectoryTreePanel.this.parent.removeProjectDir(pd.dir);
					DirectoryTreePanel.this.parent.repaint();
				} else if (pd.containsConfig()) {
					/* Add project */
					DirectoryTreePanel.this.parent.addProjectDir(pd.dir);
					DirectoryTreePanel.this.parent.repaint();
				}
			}
		});
		tree.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				TreePath selPath = e.getPath();
				if (selPath == null) {
					return;
				}
				Object o = selPath.getLastPathComponent();
				if (!(o instanceof DefaultMutableTreeNode)) {
					return;
				}
				if (!(((DefaultMutableTreeNode) o).getUserObject() instanceof TreeDirectory)) {
					return;
				}
				TreeDirectory pd = (TreeDirectory) ((DefaultMutableTreeNode) o).getUserObject();
				if (pd.isProject()) {
					DirectoryTreePanel.this.parent.selectListProject(pd.dir);
				}
			}
		});

		/* Try expand current COOJA projects */
		for (COOJAProject project: parent.getProjects()) {
			if (!project.dir.exists()) {
				logger.fatal("Project directory not found: " + project.dir);
				continue;
			}
			try {
				String projectCanonical = project.dir.getCanonicalPath();
				TreePath tp = new TreePath(tree.getModel().getRoot());
				tp = buildTreePath(projectCanonical, treeRoot, tp, tree);
				/*logger.info("Expanding: " + tp);*/
				if (tp != null) {
					tree.expandPath(tp.getParentPath());
				}
			} catch (IOException ex) {
				logger.warn("Error when expanding projects: " + ex.getMessage());
			}
		}
		add(BorderLayout.CENTER, new JScrollPane(tree));
	}

	public void selectProject(File dir) {
		/* Expand view */
		try {
			String projectCanonical = dir.getCanonicalPath();
			TreePath tp = new TreePath(tree.getModel().getRoot());
			tp = buildTreePath(projectCanonical, treeRoot, tp, tree);
			/*logger.info("Expanding: " + tp);*/
			if (tp != null) {
				tree.setSelectionPath(tp);
				tree.scrollPathToVisible(tp);
			}
		} catch (IOException ex) {
			logger.warn("Error when expanding projects: " + ex.getMessage());
		}
	}

	private static TreePath buildTreePath(String projectCanonical, DefaultMutableTreeNode parent, TreePath tp, JTree tree)
	throws IOException {
		/* Force filesystem listing */
		tree.getModel().getChildCount(parent);

		for (int i=0; i < tree.getModel().getChildCount(parent); i++) {
			DefaultMutableTreeNode child = (DefaultMutableTreeNode) tree.getModel().getChild(parent, i);
			Object userObject = child.getUserObject();
			if (!(userObject instanceof TreeDirectory)) {
				logger.fatal("Bad tree element: " + userObject.getClass());
				continue;
			}
			TreeDirectory td = (TreeDirectory) userObject;
			String treeCanonical = td.dir.getCanonicalPath();

			projectCanonical = projectCanonical.replace('\\', '/');
			if (!projectCanonical.endsWith("/")) {
				projectCanonical += "/";
			}
			treeCanonical = treeCanonical.replace('\\', '/');
			if (!treeCanonical.endsWith("/")) {
				treeCanonical += "/";
			}

			if (projectCanonical.startsWith(treeCanonical)) {
				tp = tp.pathByAddingChild(child);
				if (projectCanonical.equals(treeCanonical)) {
					return tp;
				}

				return buildTreePath(projectCanonical, child, tp, tree);
			}
		}
		return null;
	}

	private class TreeDirectory {
		File dir = null;
		File[] subdirs = null;

		public TreeDirectory(File file) {
			this.dir = file;
		}

		boolean isProject() {
			for (COOJAProject project: parent.getProjects()) {
				if (project.dir.equals(dir)) {
					return true;
				}
			}
			return false;
		}
		boolean containsConfig() {
			return new File(dir, Cooja.PROJECT_CONFIG_FILENAME).exists();
		}
		boolean subtreeContainsProject() {
			try {
				String dirCanonical = dir.getCanonicalPath();
				for (COOJAProject project: parent.getProjects()) {
					if (!project.dir.exists()) {
						continue;
					}
					String projectCanonical = project.dir.getCanonicalPath();
					if (projectCanonical.startsWith(dirCanonical)) {
						return true;
					}
				}
			} catch (IOException ex) {
			}
			return false;
		}
		public String toString() {
			if (dir.getName() == null || dir.getName().equals("")) {
				return dir.getAbsolutePath();
			}
			return dir.getName();
		}
	}
	private class COOJAProjectTreeModel extends DefaultTreeModel {
		private static final long serialVersionUID = -4673855124090194313L;

		private DefaultMutableTreeNode computerNode;

		public COOJAProjectTreeModel(DefaultMutableTreeNode computerNode) {
			super(computerNode);
			this.computerNode = computerNode;

			/* List roots */
			File[] devices = File.listRoots();
			if (devices == null) {
				logger.fatal("Could not list filesystem");
				return;
			}
			for (File device: devices) {
				DefaultMutableTreeNode deviceNode = new DefaultMutableTreeNode(new TreeDirectory(device));
				computerNode.add(deviceNode);
			}
		}
		public Object getRoot() {
			return computerNode.getUserObject();
		}
		public boolean isLeaf(Object node) {  
			if ((node instanceof DefaultMutableTreeNode)) {
				node = ((DefaultMutableTreeNode)node).getUserObject();
			}
			if (!(node instanceof TreeDirectory)) {
				/* Computer node */
				return false;
			}
			TreeDirectory td = ((TreeDirectory)node);

			return td.dir.isFile();
		}
		public int getChildCount(Object parent) {
			if ((parent instanceof DefaultMutableTreeNode)) {
				parent = ((DefaultMutableTreeNode)parent).getUserObject();
			}
			if (!(parent instanceof TreeDirectory)) {
				/* Computer node */
				return computerNode.getChildCount();
			}
			TreeDirectory td = ((TreeDirectory)parent);

			File[] children;
			if (td.subdirs != null) {
				children = td.subdirs;
			} else {
				children = getDirectoryList(td.dir);
				td.subdirs = children;
			}
			if (children == null) {
				return 0;
			}
			return children.length;
		}
		public Object getChild(Object parent, int index) {
			if ((parent instanceof DefaultMutableTreeNode)) {
				parent = ((DefaultMutableTreeNode)parent).getUserObject();
			}
			if (!(parent instanceof TreeDirectory)) {
				/* Computer node */
				return computerNode.getChildAt(index);
			}
			TreeDirectory td = ((TreeDirectory)parent);

			File[] children;
			if (td.subdirs != null) {
				children = td.subdirs;
			} else {
				children = getDirectoryList(td.dir);
				td.subdirs = children;
			}
			if ((children == null) || (index >= children.length)) {
				return null;
			}
			return new DefaultMutableTreeNode(new TreeDirectory(children[index]));
		}
		public int getIndexOfChild(Object parent, Object child) {
			if ((parent instanceof DefaultMutableTreeNode)) {
				parent = ((DefaultMutableTreeNode)parent).getUserObject();
			}
			if (!(parent instanceof TreeDirectory)) {
				/* Computer node */
				for(int i=0; i < computerNode.getChildCount(); i++) {
					if (computerNode.getChildAt(i).equals(child)) {
						return i;
					}
				}
			}
			TreeDirectory td = ((TreeDirectory)parent);

			File[] children;
			if (td.subdirs != null) {
				children = td.subdirs;
			} else {
				children = getDirectoryList(td.dir);
				td.subdirs = children;
			}
			if (children == null) {
				return -1;
			}
			if (child instanceof DefaultMutableTreeNode) {
				child = ((DefaultMutableTreeNode)child).getUserObject();
			}
			File subDir = ((TreeDirectory)child).dir;
			for(int i = 0; i < children.length; i++) {
				if (subDir.equals(children[i])) {
					return i;
				}
			}
			return -1;
		}

		public void valueForPathChanged(TreePath path, Object newvalue) {}
		public void addTreeModelListener(TreeModelListener l) {}
		public void removeTreeModelListener(TreeModelListener l) {}

		private final FileFilter DIRECTORIES = new FileFilter() {
			public boolean accept(File file) {
				if (!file.isDirectory()) {
					return false;
				}
				if (file.getName().startsWith(".")) {
					return false;
				}
				return true;
			}
		};
		private File[] getDirectoryList(File parent) {
			File[] dirs = parent.listFiles(DIRECTORIES);
			Arrays.sort(dirs);
			return dirs;
		}
	}
}

/**
 * Modal frame that shows all keys with their respective values of a given class
 * configuration.
 *
 * @author Fredrik Osterlind
 */
class ConfigViewer extends JDialog {
	private static final long serialVersionUID = 6900340477602324582L;
	public static void showDialog(Frame parentFrame, ProjectConfig config) {
		ConfigViewer myDialog = new ConfigViewer(parentFrame, config);
		myDialog.setAlwaysOnTop(true);
		myDialog.setSize(700, 300);
		myDialog.setLocationRelativeTo(parentFrame);
		myDialog.setVisible(true);
	}

	public static void showDialog(Dialog parentDialog, ProjectConfig config) {
		ConfigViewer myDialog = new ConfigViewer(parentDialog, config);
		myDialog.setAlwaysOnTop(true);
		myDialog.setSize(700, 300);
		myDialog.setLocationRelativeTo(parentDialog);
		myDialog.setVisible(true);
	}

	private ConfigViewer(Dialog dialog, ProjectConfig config) {
		super(dialog, "Merged project configuration", true);
		init(config);
	}

	private ConfigViewer(Frame frame, ProjectConfig config) {
		super(frame, "Merged project configuration", true);
		init(config);
	}

	private void init(ProjectConfig config) {
		JPanel configPane = new JPanel(new BorderLayout());
		JLabel label;
		JButton button;

		/* Control */
		JPanel buttonPane = new JPanel();
		buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
		buttonPane.add(Box.createHorizontalGlue());

		button = new JButton("Close");
		button.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				dispose();
			}
		});
		buttonPane.add(button);

		/* Config */
		JPanel keyPane = new JPanel();
		keyPane.setBackground(Color.WHITE);
		keyPane.setLayout(new BoxLayout(keyPane, BoxLayout.Y_AXIS));
		keyPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
		configPane.add(keyPane, BorderLayout.WEST);

		JPanel valuePane = new JPanel();
		valuePane.setBackground(Color.WHITE);
		valuePane.setLayout(new BoxLayout(valuePane, BoxLayout.Y_AXIS));
		configPane.add(valuePane, BorderLayout.EAST);

		label = new JLabel("KEY");
		label.setForeground(Color.RED);
		keyPane.add(label);
		label = new JLabel("VALUE");
		label.setForeground(Color.RED);
		valuePane.add(label);

		Enumeration<String> allPropertyNames = config.getPropertyNames();
		while (allPropertyNames.hasMoreElements()) {
			String propertyName = allPropertyNames.nextElement();

			keyPane.add(new JLabel(propertyName));
			if (config.getStringValue(propertyName).equals("")) {
				valuePane.add(new JLabel(" "));
			} else {
				valuePane.add(new JLabel(config.getStringValue(propertyName)));
			}
		}

		Container contentPane = getContentPane();
		configPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		configPane.setBackground(Color.WHITE);
		contentPane.add(new JScrollPane(configPane), BorderLayout.CENTER);
		contentPane.add(buttonPane, BorderLayout.SOUTH);
		pack();
	}
}
