/*******************************************************************************
 * Copyright (c) 2003, 2012 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.common.componentcore.internal.impl;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.wst.common.componentcore.internal.ComponentResource;
import org.eclipse.wst.common.componentcore.internal.ComponentcorePackage;
import org.eclipse.wst.common.componentcore.internal.StructureEdit;
import org.eclipse.wst.common.componentcore.internal.WorkbenchComponent;
import org.eclipse.wst.common.componentcore.internal.util.IPathProvider;

public class ResourceTreeNode {
	
	public static final int CREATE_NONE = 0x0;
	/** 
	 * Type constant (bit mask value 1) which identifies creating child nodes.
	 *
	 */
	public static final int CREATE_TREENODE_IFNEC = 0x1;

	/**
	 * Type constant (bit mask value 2) which identifies always creating a virtual resource.
	 *
	 */
	public static final int CREATE_RESOURCE_ALWAYS = 0x2;

	private final Set moduleResources = Collections.synchronizedSet(new LinkedHashSet());	
	private final Map children = Collections.synchronizedMap(new LinkedHashMap());
	private final Map transientChildResources = Collections.synchronizedMap(new LinkedHashMap());
	private static final ComponentResource[] NO_MODULE_RESOURCES = new ComponentResource[]{};
	private IPathProvider pathProvider;
//	private ResourceTreeNode parent;
	private String pathSegment;

	public ResourceTreeNode(String aPathSegment, ResourceTreeNode parent, IPathProvider aPathProvider) {
		pathSegment = aPathSegment;
		pathProvider = aPathProvider;
	}

	public ResourceTreeNode addChild(ResourceTreeNode aChild) {
		children.put(aChild.getPathSegment(), aChild);
		return aChild;
	}

	public ResourceTreeNode addChild(ComponentResource aModuleResource) {
		ResourceTreeNode newChild = findChild(getPathProvider().getPath(aModuleResource), CREATE_TREENODE_IFNEC);
		if(newChild != null) {
			newChild.addModuleResource(aModuleResource);
			return newChild;
		}
		return null;
	}

	public ResourceTreeNode removeChild(ResourceTreeNode aChild) {
		return (ResourceTreeNode) children.remove(aChild.getPathSegment());
	}

	public ResourceTreeNode removeChild(ComponentResource aModuleResource) { 
		ResourceTreeNode containingChild = findChild(getPathProvider().getPath(aModuleResource), CREATE_NONE);
		if(containingChild != null) {
			containingChild.removeResource(aModuleResource);
			if(containingChild.hasModuleResources())
				return containingChild;
			return removeChild(containingChild);
		}
		return null;
	}

	public ResourceTreeNode removeChild(IPath targetPath, ComponentResource aModuleResource) { 
		ResourceTreeNode containingChild = findChild(targetPath, CREATE_NONE);
		if(containingChild != null) {
			containingChild.removeResource(aModuleResource);
			if(containingChild.hasModuleResources())
				return containingChild;
			return removeChild(containingChild);
		}
		return null;			
	}
	
	public void removeResource(ComponentResource aResource) {
		moduleResources.remove(aResource);
	}

	public ResourceTreeNode findChild(IPath aPath) {
		return findChild(aPath, CREATE_TREENODE_IFNEC);
	}

	public ResourceTreeNode findChild(IPath aPath, int creationFlags) {
		if(aPath == null)
			return null;
		ResourceTreeNode child = this;
		if (aPath.segmentCount() > 0) {
			child = findChild(aPath.segment(0), creationFlags);
			if (child == null)
				return null;
			if (aPath.segmentCount() == 1)
				return child;
			child = child.findChild(aPath.removeFirstSegments(1), creationFlags);

		}
		return child;
	}

	public ResourceTreeNode findChild(String aPathSegment) {
		if (aPathSegment == null || aPathSegment.length() == 0)
			return this;
		return findChild(aPathSegment, CREATE_NONE);
	}

	public ResourceTreeNode findChild(String aPathSegment, int creationFlags) {
		boolean toCreateChildIfNecessary = (creationFlags & CREATE_TREENODE_IFNEC) == CREATE_TREENODE_IFNEC;
		ResourceTreeNode childNode = (ResourceTreeNode) children.get(aPathSegment);
		if (childNode == null && toCreateChildIfNecessary)
			childNode = addChild(aPathSegment);
		return childNode;
	}

	public ComponentResource[] findModuleResources(IPath aPath, int creationFlags) {

		Set foundModuleResources = findModuleResourcesSet(aPath, aPath, creationFlags);
		if (foundModuleResources.size() == 0)
			return NO_MODULE_RESOURCES;
		return (ComponentResource[]) foundModuleResources.toArray(new ComponentResource[foundModuleResources.size()]);
	}
	public boolean exists(IPath aPath, int creationFlags) {

		Set foundModuleResources = findModuleResourcesSet(aPath, aPath, creationFlags);
		if (foundModuleResources.size() == 0) {
			if (true) {
				ResourceTreeNode child = findChild(aPath.segment(0), creationFlags);
				if (child != null)
					return true;
			}
			return false;
		}
		return true;
	}

	public boolean hasModuleResources() {
		return moduleResources.size() > 0;
	}

	public ComponentResource[] getModuleResources() {
		return (ComponentResource[]) moduleResources.toArray(new ComponentResource[moduleResources.size()]);
	}

	private Set findModuleResourcesSet(IPath aFullPath, IPath aPath, int creationFlags) {

		if (aPath.segmentCount() == 0) {
			Set resources = aggregateResources(new LinkedHashSet());
			return resources;
		}
		ResourceTreeNode child = findChild(aPath.segment(0), creationFlags);
		if (child == null)
			return findMatchingVirtualPathsSet(aFullPath, aPath, creationFlags);
		Set foundResources = new LinkedHashSet();
		foundResources.addAll(child.findModuleResourcesSet(aFullPath, aPath.removeFirstSegments(1), creationFlags));
		foundResources.addAll(findMatchingVirtualPathsSet(aFullPath, aPath, creationFlags));
		return foundResources;
	}

	private Set findMatchingVirtualPathsSet(IPath aFullPath, IPath aPath, int creationFlags) {
		boolean toCreateResourceAlways = (creationFlags & CREATE_RESOURCE_ALWAYS) == CREATE_RESOURCE_ALWAYS;
		if (hasModuleResources()) {
			ComponentResource moduleResource = null;
			IResource eclipseResource = null;
			IContainer eclipseContainer = null;
			Set resultSet = new LinkedHashSet();
			for (Iterator resourceIter = moduleResources.iterator(); resourceIter.hasNext();) {
				moduleResource = (ComponentResource) resourceIter.next();
				if(moduleResource.getRuntimePath() != null && moduleResource.eResource() != null) {
					eclipseResource = StructureEdit.getEclipseResource(moduleResource);
					
					if (eclipseResource != null && (eclipseResource.getType() == IResource.FOLDER || eclipseResource.getType() == IResource.PROJECT)) {
						eclipseContainer = (IContainer) eclipseResource;
				 
						IPath runtimeURI = moduleResource.getRuntimePath().append(aPath);
						IPath srcPath = eclipseContainer.getProjectRelativePath().append(aPath);
						
						// check for existing subpath in tree
						ComponentResource newResource = findExistingComponentResource(moduleResource.getComponent(), runtimeURI, srcPath);
						
						// add new resource if null
						if(newResource == null) {
							// flesh out the tree
							IResource eclipseRes = eclipseContainer.findMember(aPath);
							if ((toCreateResourceAlways) || (eclipseRes != null)) {
								newResource = (ComponentResource)transientChildResources.get(srcPath);
								if (newResource == null) {
								newResource = ComponentcorePackage.eINSTANCE.getComponentcoreFactory().createComponentResource();
								// Not setting the parent on this transient child resource
								// newResource.setComponent(moduleResource.getComponent());
								newResource.setRuntimePath(runtimeURI);
								newResource.setSourcePath(srcPath);
								if (eclipseRes != null)
									newResource.setOwningProject(eclipseRes.getProject());
								transientChildResources.put(srcPath,newResource);
								}
								resultSet.add(newResource);
							}
						}
					}
		
				}
			}
			return resultSet.size() > 0 ? resultSet : Collections.EMPTY_SET;
		}
		return Collections.EMPTY_SET;
	}

	private ComponentResource findExistingComponentResource(WorkbenchComponent component, IPath runtimeURI, IPath srcPath) { 
		List resources = component.getResources();
		for (Iterator iter = resources.iterator(); iter.hasNext();) {
			ComponentResource element = (ComponentResource) iter.next();
			if(runtimeURI.equals(element.getRuntimePath()) && srcPath.equals(element.getSourcePath()))
				return element;
			
		}
		return null;
	}

	private Set aggregateResources(Set anAggregationSet) {
		if (hasModuleResources())
			anAggregationSet.addAll(moduleResources);
		ResourceTreeNode childNode = null;
		for (Iterator childrenIterator = children.values().iterator(); childrenIterator.hasNext();) {
			childNode = (ResourceTreeNode) childrenIterator.next();
			childNode.aggregateResources(anAggregationSet);
		}
		return anAggregationSet;
	}

	public int childrenCount() {
		return children.size();
	}

	public String getPathSegment() {
		return pathSegment;
	}

	protected ResourceTreeNode addChild(String aPathSegment) {
		ResourceTreeNode newChild = null;
		if ((newChild = (ResourceTreeNode) children.get(aPathSegment)) == null) {
			newChild = new ResourceTreeNode(aPathSegment, this, pathProvider);
			children.put(newChild.getPathSegment(), newChild);
		}
		return newChild;
	}

	protected ResourceTreeNode removeChild(String aPathSegment) {
		return (ResourceTreeNode) children.remove(aPathSegment);
	}

	/* package */void addModuleResource(ComponentResource aModuleResource) {
		moduleResources.add(aModuleResource);
	}

	/* package */IPathProvider getPathProvider() {
		return pathProvider;
	}
	
	public Map getChildren() {
		return children;
	}
}
