/*******************************************************************************
 * Copyright (C) 2010, Dariusz Luksza <dariusz@luksza.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Mathias Kinzler (SAP AG) - improve UI responsiveness
 *******************************************************************************/
package org.eclipse.egit.ui.internal.dialogs;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE.SharedImages;

/**
 * Builds the nodes for a file tree
 *
 */
public class FileTreeContentProvider implements ITreeContentProvider {
	private final Repository repository;

	private final List<Node> rootNodes = new ArrayList<Node>();

	private List<String> input;

	private Mode mode = Mode.REPO_RELATIVE_PATHS;

	private List<IPath> projectPaths;

	private Image folderImage = PlatformUI.getWorkbench().getSharedImages()
			.getImage(ISharedImages.IMG_OBJ_FOLDER);

	private Image fileImage = PlatformUI.getWorkbench().getSharedImages()
			.getImage(ISharedImages.IMG_OBJ_FILE);

	private Image projectImage = PlatformUI.getWorkbench().getSharedImages()
			.getImage(SharedImages.IMG_OBJ_PROJECT);

	/**
	 * Describe how to show the paths
	 *
	 */
	public enum Mode {
		/**
		 * The paths are shown relative to the repository (default)
		 */
		REPO_RELATIVE_PATHS,
		/**
		 * The paths are shown as full file system paths
		 */
		FULL_PATHS,
		/**
		 * The paths are shown as resource paths (relative to the workspace)
		 */
		RESOURCE_PATHS;
	}

	/**
	 * The node
	 */
	public abstract class Node {
		private final String myName;

		private final Node myParent;

		/**
		 * Child list
		 */
		protected final List<Node> myChildren = new ArrayList<Node>();

		Node(Node parent, String name) {
			myParent = parent;
			myName = name;
			if (parent != null)
				parent.addChild(this);
		}

		/**
		 * @return a boolean
		 */
		public abstract boolean hasChildren();

		/**
		 * @return an image
		 */
		public abstract Image getImage();

		/**
		 * @return the name
		 */
		public String getName() {
			return myName;
		}

		/**
		 * @return the parent
		 */
		public Node getParent() {
			return myParent;
		}

		private void addChild(Node child) {
			myChildren.add(child);
		}

		/**
		 * @return the children or null
		 */
		public List<Node> getChildren() {
			if (myChildren != null)
				return myChildren;
			return null;
		}

		@Override
		public String toString() {
			return myName;
		}
	}

	private class FolderNode extends Node {
		FolderNode(Node parent, String name) {
			super(parent, name);
		}

		@Override
		public boolean hasChildren() {
			return true;
		}

		@Override
		public Image getImage() {
			if (mode == Mode.RESOURCE_PATHS
					&& getParent() instanceof VirtualNode)
				return projectImage;
			return folderImage;
		}

	}

	private class NoResourceNode extends Node {
		NoResourceNode(Node parent, String name) {
			super(parent, name);
		}

		@Override
		public boolean hasChildren() {
			return true;
		}

		@Override
		public Image getImage() {
			return folderImage;
		}

	}

	class FileNode extends Node {
		FileNode(Node parent, String name) {
			super(parent, name);
		}

		@Override
		public boolean hasChildren() {
			return false;
		}

		@Override
		public Image getImage() {
			return fileImage;
		}
	}

	private class VirtualNode extends FolderNode {
		VirtualNode() {
			super(null, ""); //$NON-NLS-1$
		}

		@Override
		public boolean hasChildren() {
			return !myChildren.isEmpty();
		}
	}

	/**
	 * @param repository
	 */
	public FileTreeContentProvider(Repository repository) {
		this.repository = repository;
	}

	/**
	 * @param mode
	 */
	public void setMode(Mode mode) {
		this.mode = mode;
	}

	public Object[] getChildren(Object parent) {
		return ((Node) parent).getChildren().toArray();
	}

	public Object getParent(Object child) {
		return ((Node) child).getParent();
	}

	public boolean hasChildren(Object parent) {
		return ((Node) parent).hasChildren();
	}

	public Object[] getElements(Object arg0) {
		return rootNodes.toArray();
	}

	public void dispose() {
		// nothing to dispose
	}

	@SuppressWarnings("unchecked")
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		rootNodes.clear();

		input = (List<String>) newInput;
		if (input == null)
			return;

		VirtualNode virtualNode = new VirtualNode();
		Node parentNode = virtualNode;
		NoResourceNode noResourceNode = null;

		IPath repositoryRoot = new Path(repository.getWorkTree()
				.getAbsolutePath());

		for (String pathString : input) {
			IPath path;
			if (mode == Mode.FULL_PATHS)
				path = repositoryRoot.append(pathString);
			else if (mode == Mode.RESOURCE_PATHS)
				path = resolveResourcePath(repositoryRoot.append(pathString));
			else
				path = new Path(pathString);
			if (path == null) {
				path = repositoryRoot.append(pathString);
				if (noResourceNode == null) {
					noResourceNode = new NoResourceNode(
							virtualNode,
							UIText.FileTreeContentProvider_NonWorkspaceResourcesNode);
				}
				parentNode = noResourceNode;
			} else
				parentNode = virtualNode;
			for (int i = 0; i < path.segmentCount(); i++) {
				String segment = path.segment(i);
				Node foundNode = null;
				for (Node node : parentNode.getChildren()) {
					if (node.getName().equals(segment)) {
						foundNode = node;
						break;
					}
				}
				if (foundNode == null) {
					if (i < path.segmentCount() - 1)
						parentNode = new FolderNode(parentNode, segment);
					else
						new FileNode(parentNode, segment);
				} else {
					parentNode = foundNode;
				}
			}
		}

		rootNodes.addAll(virtualNode.getChildren());
	}

	private IPath resolveResourcePath(IPath fullPath) {
		for (IPath projectPath : getProjectPaths()) {
			if (projectPath.isPrefixOf(fullPath)) {
				return fullPath
						.removeFirstSegments(projectPath.segmentCount() - 1);
			}
		}
		return null;
	}

	private List<IPath> getProjectPaths() {
		if (projectPaths == null) {
			projectPaths = new ArrayList<IPath>();
			for (IProject project : ResourcesPlugin.getWorkspace().getRoot()
					.getProjects()) {
				RepositoryMapping mapping = RepositoryMapping
						.getMapping(project);
				if (mapping != null
						&& mapping.getRepository().equals(repository)) {
					projectPaths.add(project.getLocation());
				}
			}
		}
		return projectPaths;
	}
}
