/*******************************************************************************
 * Copyright (c) 2004, 2009 Mylyn project committers 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
 *******************************************************************************/
package org.eclipse.mylyn.internal.cdt.ui;

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

import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.IBinary;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IFunction;
import org.eclipse.cdt.core.model.IInclude;
import org.eclipse.cdt.core.model.IMethod;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CElementGrouping;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.context.core.AbstractContextStructureBridge;
import org.eclipse.mylyn.context.core.ContextCore;
import org.eclipse.mylyn.context.core.IInteractionElement;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.views.markers.internal.ConcreteMarker;

/**
 * @author Mik Kersten
 * @author Jeff Johnston
 */
public class CDTStructureBridge extends AbstractContextStructureBridge {

	public final static String CONTENT_TYPE = "c/c++"; //$NON-NLS-1$

	public final static int C_SOURCEROOT = 1000;

	public CDTStructureBridge() {
	}

	@Override
	public String getContentType() {
		return CONTENT_TYPE;
	}

	@Override
	public Object getAdaptedParent(Object object) {
		if (object instanceof IFile) {
			IFile file = (IFile) object;
			return CoreModel.getDefault().create(file.getParent());
		} else {
			return super.getAdaptedParent(object);
		}
	}

	@Override
	public String getParentHandle(String handle) {
		ICElement cElement = (ICElement) getObjectForHandle(handle);
		if (cElement != null && cElement.getParent() != null) {
			return getHandleForElement(cElement.getParent());
		} else {
			return null;
		}
	}

	@Override
	public List<String> getChildHandles(String handle) {
		Object object = getObjectForHandle(handle);
		if (object instanceof ICElement) {
			ICElement element = (ICElement) object;
			if (element instanceof IParent) {
				IParent parent = (IParent) element;
				ICElement[] children;
				try {
					children = parent.getChildren();
					List<String> childHandles = new ArrayList<String>();
					for (ICElement element2 : children) {
						String childHandle = getHandleIdentifier(element2);
						if (childHandle != null) {
							childHandles.add(childHandle);
						}
					}
					AbstractContextStructureBridge parentBridge = ContextCore.getStructureBridge(parentContentType);
					if (parentBridge != null && ContextCore.CONTENT_TYPE_RESOURCE.equals(parentBridge.getContentType())) {
						// TODO: Make sure line below is correct
						if (element.getElementType() < ICElement.C_NAMESPACE) {
							List<String> resourceChildren = parentBridge.getChildHandles(handle);
							if (!resourceChildren.isEmpty()) {
								childHandles.addAll(resourceChildren);
							}
						}
					}

					return childHandles;
				} catch (CModelException e) {
					// ignore these, usually indicate no-existent element
				}
			}
		}
		return Collections.emptyList();
	}

	public static ICElement getElementForHandle(String handle) {
		return CoreModel.create(handle);
	}

	@Override
	public Object getObjectForHandle(String handle) {
		return getElementForHandle(handle);
	}

	// In the CDT, an ICElement does not have a handle interface like a
	// JavaElement.  So, to find an element again, we save its CProject name,
	// plus its path, plus its element name.  The CProject name allows us
	// to find the CProject.  The path allows us to find the ITranslationUnit.
	// The element name (optional) allows us to find an element within the
	// TranslationUnit.
	public static String getHandleForElement(ICElement element) {
		String handle = element.getHandleIdentifier();
		if (handle != null && !(element instanceof ICProject)) {
			ICProject cProject = element.getCProject();
			if (cProject != null) {
				String projectHandle = getHandleForElement(cProject);
				if (handle.equals(projectHandle)) {
					// see bug 328300
					return null;
				}
			}
		}
		return handle;
	}

	/**
	 * Uses resource-compatible path for projects
	 */
	@Override
	public String getHandleIdentifier(Object object) {
		if (object instanceof ICElement) {
			return getHandleForElement((ICElement) object);
		} else {
			if (object instanceof IAdaptable) {
				Object adapter = ((IAdaptable) object).getAdapter(ICElement.class);
				if (adapter instanceof ICElement) {
					return getHandleForElement((ICElement) adapter);
				}
			}
		}
		return null;
	}

	@Override
	public String getLabel(Object object) {
		if (object instanceof ICElement) {
			return ((ICElement) object).getElementName();
		} else {
			return ""; //$NON-NLS-1$
		}
	}

	@Override
	public boolean canBeLandmark(String handle) {
		ICElement element = (ICElement) getObjectForHandle(handle);
		if ((element instanceof IMethod || element instanceof IFunction) && element.exists()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * TODO: figure out if the non ICElement stuff is needed
	 */
	@Override
	public boolean acceptsObject(Object object) {
		if (object instanceof IResource) {
			object = ((IResource) object).getAdapter(ICElement.class);
		}

		boolean accepts = (object instanceof ICElement && !(object instanceof IBinary))
				|| object instanceof IWorkingSet || object instanceof CElementGrouping;

		return accepts;
	}

	@Override
	public boolean canFilter(Object object) {
		if (object instanceof IWorkingSet) {
			try {
				IWorkingSet workingSet = (IWorkingSet) object;
				IAdaptable[] elements = workingSet.getElements();
				for (IAdaptable adaptable : elements) {
					IInteractionElement element = ContextCore.getContextManager().getElement(
							getHandleIdentifier(adaptable));
					if (element.getInterest().isInteresting()) {
						return false;
					}
				}
			} catch (Exception e) {
				return false;
			}
		} else if (object instanceof CElementGrouping) {
			try {
				CElementGrouping grouping = (CElementGrouping) object;
				Object[] elements = grouping.getChildren(grouping);
				if (elements != null) {
					for (Object adaptable : elements) {
						IInteractionElement element = ContextCore.getContextManager().getElement(
								getHandleIdentifier(adaptable));
						if (element != null && element.getInterest().isInteresting()) {
							return false;
						}
					}
				}
			} catch (Exception e) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean isDocument(String handle) {
		ICElement element = (ICElement) getObjectForHandle(handle);
		return element instanceof ITranslationUnit;
	}

	@Override
	public String getHandleForOffsetInObject(Object object, int offset) {
		IMarker marker;
		int charStart = 0;
		if (object instanceof ConcreteMarker) {
			marker = ((ConcreteMarker) object).getMarker();
		}
		if (object instanceof IMarker) {
			marker = (IMarker) object;
		} else {
			return null;
		}

		Object attribute = marker.getAttribute(IMarker.CHAR_START, 0);
		if (attribute instanceof Integer) {
			charStart = ((Integer) attribute).intValue();
		}

		try {
			ITranslationUnit translationUnit = null;
			IResource resource = marker.getResource();
			if (resource instanceof IFile) {
				IFile file = (IFile) resource;
				if (CoreModel.isValidTranslationUnitName(null, file.getName())) {
					ICElement element = CoreModel.getDefault().create(file);
					translationUnit = CoreModel.getDefault().createTranslationUnitFrom(element.getCProject(),
							element.getPath());
				} else {
					return null;
				}
			}
			if (translationUnit != null) {
				ICElement cElement = translationUnit.getElementAtOffset(charStart);
				if (cElement != null) {
					if (cElement instanceof IInclude) {
						cElement = cElement.getParent().getParent();
					}
					return cElement.getElementName();
				} else {
					return null;
				}
			} else {
				return null;
			}
		} catch (CModelException ex) {
			if (ex.doesNotExist()) {
				StatusHandler.fail(ex.getStatus());
			}
			return null;
		}
	}

	@Override
	public String getContentType(String elementHandle) {
		return getContentType();
	}

	/**
	 * Some copying from:
	 * 
	 * @see org.eclipse.jdt.ui.ProblemsLabelDecorator
	 */
	public boolean containsProblem(IInteractionElement node) {
		try {
			ICElement element = (ICElement) getObjectForHandle(node.getHandleIdentifier());
			switch (element.getElementType()) {
			case ICElement.C_PROJECT:
			case ICElement.C_CCONTAINER:
			case ICElement.C_VCONTAINER:
				return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_INFINITE, null);
			case ICElement.C_UNIT:
				return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_ONE, null);
			case ICElement.C_USING:
			case ICElement.C_NAMESPACE:
			case ICElement.C_INCLUDE:
			case ICElement.C_TYPEDEF:
			case ICElement.C_MACRO:
			case ICElement.C_FUNCTION:
			case ICElement.C_METHOD:
			case ICElement.C_FIELD:
			case ICElement.C_VARIABLE_LOCAL:
			case ICElement.C_CLASS:
				ITranslationUnit cu = (ITranslationUnit) element.getAncestor(ICElement.C_UNIT);
				if (cu != null) {
					return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_ONE, null);
				}
			}
		} catch (CoreException e) {
			// ignore
		}
		return false;
	}

	private boolean getErrorTicksFromMarkers(IResource res, int depth, ISourceReference sourceElement)
			throws CoreException {
		if (res == null || !res.isAccessible()) {
			return false;
		}
		IMarker[] markers = res.findMarkers(IMarker.PROBLEM, true, depth);
		if (markers != null) {
			for (IMarker curr : markers) {
				if (sourceElement == null) {
					int priority = curr.getAttribute(IMarker.SEVERITY, -1);
					if (priority == IMarker.SEVERITY_ERROR) {
						return true;
					}
				}
			}
		}
		return false;
	}
}
