/*******************************************************************************
* Copyright (c) 2006 IONA Technologies PLC
* 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:
*     IONA Technologies PLC - initial API and implementation
*******************************************************************************/
package org.eclipse.stp.sc.common.views;

import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import java.lang.reflect.Method;

import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.common.annotations.ScAnnotationSupportUtils;

/**
 * content provider for the <code>TreeViewer</code> of the <code>AnnotationView</code>.
 * this class is not meant to be reused outside this context
 */
class AnnotationViewerContentProvider implements ITreeContentProvider {
    
    private static final LoggingProxy LOG = LoggingProxy.getlogger(AnnotationViewerContentProvider.class);
    
    private AnnotationView parent;
    
    public AnnotationViewerContentProvider(AnnotationView aParent) {
    	parent = aParent;
    }

    public Object[] getChildren(Object node) {
        if (node instanceof Class) {
            return ((Class)node).getDeclaredMethods();
        }
        else if (node instanceof Method) {
        	Method method = (Method)node;
        	Class returnType = method.getReturnType();
        	if (returnType.isArray()) {
        		Class annoClass = method.getDeclaringClass();
        		Annotation annoNode = parent.annotationNodesMap.get(annoClass.getSimpleName());
        		if (annoNode == null) {
        			annoNode = parent.annotationNodesMap.get(annoClass.getName());
        		}
        		if (annoNode != null) {
        			if (annoNode instanceof NormalAnnotation) {
        				MemberValuePair mvpOne = null;
           				for (Object obj : ((NormalAnnotation)annoNode).values()) {
           					MemberValuePair mvp = (MemberValuePair)obj;
           					if (mvp.getName().getIdentifier().equals(method.getName())) {
           						mvpOne = mvp;
           						break;
           					}
           				}
           				if (mvpOne != null) {
           					ArrayInitializer arrayInit = (ArrayInitializer)mvpOne.getValue();
           					int number = arrayInit.expressions().size();
           					Object[] children = new Object[number + 1];
           					for (int i = 0; i < children.length; i++) {
           						children[i] = new Object[2];
           					}
           					Arrays.sort(children, new Comparator<Object>() {
           						public int compare(Object o1, Object o2) {
           							return o1.toString().compareTo(o2.toString());
           						}
           					});
           					for (int i = 0; i < number; i++) {
           						Object[] pair = (Object[])children[i];
           						pair[0] = returnType.getComponentType().getCanonicalName() + ":Existing" + i;
           						pair[1] = arrayInit.expressions().get(i);
           						
           					}
           					Object[] pair = (Object[])children[number];
           					pair[0] = returnType.getComponentType().getCanonicalName() + ":ClickToAdd";
           					pair[1] = null;
           					return children;
           				}
        			}
        		} else {
   					Object[] pair = new Object[2];
   					pair[0] = returnType.getComponentType().getCanonicalName() + ":ClickToAdd";
   					pair[1] = null;
   					Object[] children = new Object[1];
   					children[0] = pair;
   					return children;
        		}
        	}
        }
        else if (node instanceof Object[]) {
        	Object[] pair = (Object[])node;
        	if (pair[0] instanceof String) {
        		String str = (String)pair[0];
        		NormalAnnotation anno = (NormalAnnotation)pair[1];
        		String className = str.substring(0, str.indexOf(":"));
        		try {
        			Method[] methods = Class.forName(className).getDeclaredMethods();
   					Arrays.sort(methods, new Comparator<Method>() {
   						public int compare(Method m1, Method m2) {
   							return m1.toString().compareTo(m2.toString());
   						}
   					});
        			Object[] children = new Object[methods.length];
        			for (int i = 0; i < methods.length; i++) {
        				children[i] = new Object[2];
        			}
   					Arrays.sort(children, new Comparator<Object>() {
   						public int compare(Object o1, Object o2) {
   							return o1.toString().compareTo(o2.toString());
   						}
   					});
        			for (int i = 0; i < methods.length; i++) {
        				Method method = methods[i];
        				Object[] sub = (Object[])children[i];
        				sub[0] = method;
        				sub[1] = null;
        				if (anno != null) {
        					for (Object obj : anno.values()) {
        						MemberValuePair mvp2 = (MemberValuePair)obj;
        						if (mvp2.getName().getIdentifier().equals(method.getName())) {
        							sub[1] = mvp2;
        							break;
        						}
        					}
        				}
        			}
        			return children;
        		} catch (ClassNotFoundException e) {
        			return null;
        		}
        	} else if (pair[0] instanceof Method) {
        		return null;
        	}
        }

        return null;
    }

    public Object getParent(Object arg0) {
        return null;
    }

    public boolean hasChildren(Object arg0) {
    	if (arg0 instanceof Class) {
    		return true;
    	} else if (arg0 instanceof Method) {
    		if (((Method)arg0).getReturnType().isArray()) {
    			return true;
    		}
    	} else if (arg0 instanceof Object[]) {
    		Object[] pair = (Object[])arg0;
    		if (pair[0] instanceof String) {
    			return true;
    		}
    	}
        return false;
    }

    /**
     * identify the base nodes of the tree based on the type of java element selected
     */
    public Object[] getElements(Object input) {
    	try {
    		if (input instanceof IMethod) {
    			IMethod method = (IMethod)input;
    			IProject project = method.getUnderlyingResource().getProject();
    			List<Class> list = ScAnnotationSupportUtils.getAvailableAnnotationsForMethod(project);
    			LOG.debug("retrieved methods annotations: " + list);
    			return list.toArray();
    		}

    		if (input instanceof IType) {
    			IType type = (IType)input;
    			IProject project = type.getUnderlyingResource().getProject();
    			if (type.isAnnotation()) {
    				return ScAnnotationSupportUtils.getAvailableAnnotationsForAnno(project).toArray();
    			}
    			if (type.isClass()) {
    				return ScAnnotationSupportUtils.getAvailableAnnotationsForClass(project).toArray();
    			}
    			if (type.isInterface()) {
    				return ScAnnotationSupportUtils.getAvailableAnnotationsForInterface(project).toArray();
    			}
    		}
        
    		if (input instanceof SingleVariableDeclaration) {
    			SingleVariableDeclaration var = (SingleVariableDeclaration)input;
    			CompilationUnit root = (CompilationUnit)var.getRoot();
    			IProject project = root.getJavaElement().getCorrespondingResource().getProject();
    			return ScAnnotationSupportUtils.getAvailableAnnotationsForParam(project).toArray();
    		}
        
    		if (input instanceof IField) {
    			IField field = (IField)input;
    			IProject project = field.getUnderlyingResource().getProject();
    			return ScAnnotationSupportUtils.getAvailableAnnotationsForField(project).toArray();
    		}
    	} catch (Exception e) {
    		e.printStackTrace();
    	}

        return null;
    }

    public void dispose() {
    }

    public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
    }
}
