package org.eclipse.bpel.validator.factory;


import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.xml.namespace.QName;

import org.eclipse.bpel.model.adapters.AdapterRegistry;
import org.eclipse.bpel.validator.Activator;
import org.eclipse.bpel.validator.IBPELMarker;
import org.eclipse.bpel.validator.helpers.DOMNodeAdapter;
import org.eclipse.bpel.validator.model.INode;
import org.eclipse.bpel.validator.model.IProblem;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.w3c.dom.Element;


/**
 * An adapter factory.  
 *  
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Sep 19, 2006
 *
 */

@SuppressWarnings({"boxing","nls","restriction"})

public class AdapterFactory implements IAdapterFactory {

	/** debug variance in behavior */
	static public boolean DEBUG = false;
		
	/** We adapt to these types ... */	
	
	Class<?> [] adapterList = { 
			IMarker.class , 
			IResource.class , 
			INode.class ,
			IMessage.class };
	
	
	/**
	 * Get the adapter for the requested adaptable object.
	 * 
	 * @param adaptableObject adaptable object.
	 * @param adapterType the adapter type
	 * @return the adapter or null 
	 */
	
	@SuppressWarnings("unchecked")
	public Object getAdapter (Object adaptableObject, Class adapterType) {
		
		if (adaptableObject instanceof EObject) {
			Object adapter = AdapterRegistry.INSTANCE.adapt(adaptableObject, adapterType,false);
			if (adapter != null) {
				return adapter;
			}
		}
		
		if (adapterType == IMarker.class) {
			
			if (adaptableObject instanceof IProblem) {				
				return adapt_IProblem2IMarker ( (IProblem) adaptableObject );
			}
			
		} else if (adapterType == IResource.class) {
			
			if (adaptableObject instanceof EObject) {
				return adapt_EObject2IResource ( (EObject) adaptableObject );
			}
			if (adaptableObject instanceof Element) {
				return adapt_Element2IResource( (Element) adaptableObject );
			}
			
		} else if (adapterType == INode.class ) {
						
			if (adaptableObject instanceof Element) {
				Element elm = (Element) adaptableObject;
				
				Object adapter = elm.getUserData(DOMNodeAdapter.KEY);
				if (adapter instanceof DOMNodeAdapter) {
					return adapterType.cast(adapter);
				}
				adapter = new DOMNodeAdapter( elm );
				elm.setUserData(DOMNodeAdapter.KEY, adapter, null );
				return adapterType.cast(adapter);																				
			}
			
		} else if (adapterType == IMessage.class) {
			
			if (adaptableObject instanceof IProblem) {
				return adapt_IProblem2IMessage( (IProblem) adaptableObject );
			}
			
		}
		return null;
	}

	
	/**
	 * Return an IResource adapter for the EObject. 
	 * 
	 * @param eObj
	 * @return the IResource to which we will be creating markers. 
	 */
	
	IResource adapt_EObject2IResource (EObject eObj) {
		if (eObj == null) {
			return null;
		}
		
		Resource r = eObj.eResource();
		if (r == null) {
			return null;
		}
		return getFileFromURI ( r.getURI() );		
	}

	IResource adapt_Element2IResource ( Element elm ) {
		
		Element top = elm.getOwnerDocument().getDocumentElement();
		IResource result = null;
		if (top != null) {
			// Find the EObject reference to the emf model in the hierarchy of
			// the
			EObject eObj = (EObject) top.getUserData("emf.model");

			result = adapt_EObject2IResource(eObj);
		}
		return result;
	}

	/**
	 * Return an IMarker which is derived from IProblem. 
	 *  
	 * @param problem the problem to adapt (convert to) IMarker
	 * @return an appropriate IMarker
	 */
	  

	IMarker adapt_IProblem2IMarker (IProblem problem ) {
				
		INode node = (INode) problem.getAttribute( IProblem.NODE );					
		IResource resource = (IResource) getAdapter(node.nodeValue(), IResource.class );
		
		Map<String,Object> props = new HashMap<String,Object>();
		
		//message && fix  		
		String msg  = problem.getAttribute(IProblem.MESSAGE);
		String rule = problem.getAttribute(IProblem.RULE);
				
		props.put("bpel.validation.rule", rule);
		
		if (DEBUG) {
			Throwable t = problem.getAttribute(IProblem.EXCEPTION);
			String emsg = msg;
			emsg += " (rule=";
			emsg += rule;			
			
			if (t != null) {
				emsg += "; stack=";
				
				int count = 0;
				for(StackTraceElement e : t.getStackTrace()) {
					emsg += "[" + count + "]";
					emsg += e.getClassName() + ".";
					emsg += e.getMethodName() + "@" + e.getLineNumber();
					count += 1;
					if (count > 2) {
						break;
					}
					emsg += "/";
				}
			}
			
			emsg += ")";			
			props.put(IMarker.MESSAGE, emsg);
			
		} else {
			props.put(IMarker.MESSAGE, msg );
		}
		
		// Path to object in the BPEL model.
		
		// Location of the marker in the source
		int lineNo = problem.getAttribute(IProblem.LINE_NUMBER,-1);
		props.put(IMarker.LINE_NUMBER, lineNo );
		props.put(IMarker.CHAR_END, problem.getAttribute(IProblem.CHAR_END, -1));
		props.put(IMarker.CHAR_START, problem.getAttribute(IProblem.CHAR_START,-1));
		
		// Location
		if (lineNo < 0) {
			props.put(IMarker.LOCATION, problem.getAttribute(IProblem.LOCATION));
		}
		
		// type of marker
		switch (problem.getAttribute(IProblem.SEVERITY,IProblem.SEVERITY_ERROR )) {
		case IProblem.SEVERITY_WARNING : 				
			props.put(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);
			break;
		case IProblem.SEVERITY_INFO :
			props.put(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
			break;
		default :
			props.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
			break;
		}			
		
		// the pointer in the model object space to the object on which this marker is produced. 
		props.put( IBPELMarker.ADDRESS_MODEL , problem.getAttribute( IProblem.ADDRESS_MODEL ));
		// the pointer in the DOM object space to the object on which this marker is produced.			
		props.put( IBPELMarker.ADDRESS_XPATH, problem.getAttribute( IProblem.ADDRESS_XPATH ));
		
		// href.context refers to the problem context, expressed  
		// in the model object space context. 
		Object context = problem.getAttribute(IProblem.CONTEXT );
		if( context instanceof QName){
			QName qname = (QName)context;
			props.put( "href.context", qname.getLocalPart());	
		}
		else{
			props.put( "href.context", problem.getAttribute(IProblem.CONTEXT ));
		}
		
		
		// do not save this marker
		// props.put( IMarker.TRANSIENT, true);
			
		
		IMarker marker = null;
		try {
			marker = resource.createMarker( IBPELMarker.ID );
			marker.setAttributes( props );
		} catch (Exception ex) {
			// can't create marker ... ?
			Activator.log(ex);
			return null;
		}
			
		
		// return the marker.
		return marker;				
	}

	
	/**
	 * Return an IMessage which is derived from IProblem. 
	 *  
	 * @param problem the problem to adapt (convert to) IMarker
	 * @return an appropriate IMarker
	 */
	  

	
	IMessage adapt_IProblem2IMessage (IProblem problem ) {										
		// IResource resource = (IResource) getAdapter(node.nodeValue(), IResource.class );			
		return new Message ( problem );		
	}
	
	
	
	static class Message implements IMessage {

		static String GENERAL = "general";
		
		IProblem fProblem;

		protected String fMarkerId;
		
		Message ( IProblem problem ) {
			fProblem = problem;
		}
		
		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getAttribute(java.lang.String)
		 */
		
		public Object getAttribute(String attributeName) {
			return fProblem.getAttribute(attributeName);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getAttributes()
		 */
		public HashMap getAttributes() {
			return (HashMap) fProblem.getAttributes();
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getBundleName()
		 */
		public String getBundleName() {
			return fProblem.getAttribute(IProblem.BUNDLE_NAME);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getGroupName()
		 */
		public String getGroupName() {
			return GENERAL;
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getId()
		 */
		public String getId() {
			return fProblem.getAttribute(IProblem.MESSAGE_ID);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getLength()
		 */
		public int getLength() {
			int offset = fProblem.getAttribute( IProblem.CHAR_START );
			int offsetEnd = fProblem.getAttribute( IProblem.CHAR_END );
			int length = offsetEnd - offset + 1;
			if (length < 0) {
				return IMessage.OFFSET_UNSET; 
			}
			return length ;
		}

		/** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getLineNumber()
		 */
		public int getLineNumber() {
			return fProblem.getAttribute(IProblem.LINE_NUMBER);			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getMarkerId()
		 */
		public String getMarkerId() {
			return fMarkerId;
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getOffset()
		 */
		public int getOffset() {
			int offset = fProblem.getAttribute( IProblem.CHAR_START );
			if (offset < 0) {
				return IMessage.OFFSET_UNSET; 
			}
			return offset;
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getParams()
		 */
		public String[] getParams() {
			return (String[]) fProblem.getAttribute(IProblem.MESSAGE_ARGS);
		}

		/**non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getSeverity()
		 */
		public int getSeverity() {
			int severity = fProblem.getAttribute(IProblem.SEVERITY);
			switch (severity) {
			case IProblem.SEVERITY_ERROR : 
				return IMessage.HIGH_SEVERITY;
			case IProblem.SEVERITY_WARNING :
			case IProblem.SEVERITY_INFO :
				return IMessage.LOW_SEVERITY;			
			}
			
			return IMessage.LOW_SEVERITY;
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getTargetObject()
		 */
		public Object getTargetObject() {
			INode node = fProblem.getAttribute(IProblem.NODE);
			if (node == null) {
				return null;
			}			
			return node;
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getText()
		 */
		public String getText() {
			return fProblem.getAttribute(IProblem.MESSAGE);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getText(java.lang.ClassLoader)
		 */
		public String getText(ClassLoader classLoader) {
			return fProblem.getAttribute(IProblem.MESSAGE);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getText(java.util.Locale)
		 */
		public String getText(Locale locale) {
			return fProblem.getAttribute(IProblem.MESSAGE);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#getText(java.util.Locale, java.lang.ClassLoader)
		 */
		public String getText(Locale locale, ClassLoader classLoader) {
			return fProblem.getAttribute(IProblem.MESSAGE);
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setAttribute(java.lang.String, java.lang.Object)
		 */
		public void setAttribute(String attributeName, Object value) {
			// TODO Auto-generated method stub
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setBundleName(java.lang.String)
		 */
		public void setBundleName(String bundleName) {
			 
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setGroupName(java.lang.String)
		 */
		public void setGroupName(String name) {
			
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setId(java.lang.String)
		 */
		public void setId(String newId) {
			
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setLength(int)
		 */
		public void setLength(int length) {
			
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setLineNo(int)
		 */
		public void setLineNo(int lineNumber) {
			
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setMarkerId(java.lang.String)
		 */
		public void setMarkerId(String markerId) {
			fMarkerId = markerId;
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setOffset(int)
		 */
		public void setOffset(int offset) {
			// TODO Auto-generated method stub
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setParams(java.lang.String[])
		 */
		public void setParams(String[] newParams) {
			// TODO Auto-generated method stub
			
		}

		/*** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setSeverity(int)
		 */
		public void setSeverity(int newSeverity) {
			// TODO Auto-generated method stub
			
		}

		/** (non-Javadoc)
		 * @see org.eclipse.wst.validation.internal.provisional.core.IMessage#setTargetObject(java.lang.Object)
		 */
		public void setTargetObject(Object obj) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
		
	
	/**
	 * Return the adapter list. These are all the interfaces or classes to which
	 * we can adapt objects passed to this factory.
	 * 
	 * @return a list of classes that we can adapt to.
	 */
	
	public Class<?>[] getAdapterList() {
		return adapterList;
	}

	
	
	IFile getFileFromURI (URI uri) {
		if (uri.isFile()) {
			return getFileFromDeviceURI(uri);
		}
		return getFileFromPlatformURI(uri);
	}
	
	IFile getFileFromDeviceURI(URI uri) {
		String device = uri.device();
		StringBuilder path = new StringBuilder();
		for(String segment : uri.segments()) {
			path.append("/").append(segment);
		}		
		return ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(device, path.toString()));
	}
	
	IFile getFileFromPlatformURI (URI uri) {
		String [] segs  = uri.segments();
		IPath path = null;
		// start at 1 to skip resource
		for (int i = 1; i< segs.length; i++) {
			if (path == null) {
				path = new Path(segs[i]);
			} else {
				path = path.append(segs[i]);
			}
		}
		return ResourcesPlugin.getWorkspace().getRoot().getFile(path);
	}

}
