/*******************************************************************************
 * Copyright (C) 2013 Robin Stocker <robin@nibor.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
 *******************************************************************************/
package org.eclipse.egit.ui.internal.synchronize.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.synchronize.GitCommitsModelCache.Change;
import org.eclipse.jgit.lib.Repository;

/**
 * For building trees of directory and file nodes out of a flat list of changes.
 */
class TreeBuilder {

	/**
	 * This interface enables creating the right instances of
	 * {@link GitModelBlob} for files.
	 */
	interface FileModelFactory {
		/**
		 * Creates proper instance of {@link GitModelBlob} for file nodes
		 *
		 * @param parent
		 *            parent object
		 * @param repo
		 *            repository associated with file that will be created
		 * @param change
		 *            change associated with file that will be created
		 * @param fullPath
		 *            absolute path
		 * @return instance of {@link GitModelBlob}
		 */
		GitModelBlob createFileModel(GitModelObjectContainer parent,
				Repository repo, Change change, IPath fullPath);

		/**
		 * Distinguish working tree from changed/staged tree
		 *
		 * @return {@code true} when this tree is working tree, {@code false}
		 *         when it is a cached tree
		 */
		boolean isWorkingTree();
	}

	/**
	 * Interface for creating the desired instances of {@link GitModelTree}.
	 */
	interface TreeModelFactory {
		GitModelTree createTreeModel(GitModelObjectContainer parent,
				IPath fullPath, int kind);
	}

	/**
	 *
	 * @param root
	 *            the root node of the tree to build, which will become the
	 *            parent of the first level of children
	 * @param repo
	 * @param changes
	 * @param fileFactory
	 * @param treeFactory
	 * @return the children of the root nodes
	 */
	public static GitModelObject[] build(final GitModelObjectContainer root,
			final Repository repo, final Map<String, Change> changes,
			final FileModelFactory fileFactory,
			final TreeModelFactory treeFactory) {

		if (changes == null || changes.isEmpty())
			return new GitModelObject[] {};

		final IPath rootPath = new Path(repo.getWorkTree()
				.getAbsolutePath());
		final List<GitModelObject> rootChildren = new ArrayList<GitModelObject>();

		final Map<IPath, Node> nodes = new HashMap<IPath, Node>();

		for (Map.Entry<String, Change> entry : changes.entrySet()) {
			String repoRelativePath = entry.getKey();
			Change change = entry.getValue();

			GitModelObjectContainer parent = root;
			List<GitModelObject> children = rootChildren;
			IPath path = rootPath;

			String[] segments = repoRelativePath.split("/"); //$NON-NLS-1$

			for (int i = 0; i < segments.length; i++) {
				path = path.append(segments[i]);

				// Changes represent files, so the last segment is the file name
				boolean fileNode = (i == segments.length - 1);
				if (!fileNode) {
					Node node = nodes.get(path);
					if (node == null) {
						GitModelTree tree = treeFactory.createTreeModel(parent,
								path, change.getKind());
						node = new Node(tree);
						nodes.put(path, node);
						children.add(tree);
					}
					parent = node.tree;
					children = node.children;
				} else {
					GitModelBlob file = fileFactory.createFileModel(parent,
							repo, change, path);
					children.add(file);
				}
			}
		}

		for (Node object : nodes.values()) {
			GitModelTree tree = object.tree;
			tree.setChildren(object.children);
		}

		return rootChildren.toArray(new GitModelObject[rootChildren.size()]);
	}

	private static class Node {
		private final GitModelTree tree;

		private final List<GitModelObject> children = new ArrayList<GitModelObject>();

		public Node(GitModelTree tree) {
			this.tree = tree;
		}
	}
}
