/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package spechelper;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;

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

/**
 * 
 */
public class MethodSelector {

    public String obtainReplacement(String buffer) {
        IMethod method = selectMethod();
        // if user did cancel the selection
        if (method == null) {
            return null;
        }

        // see if we are already in a annotation:
        // if yes -> only dump the testtarget annotation, not the complete
        // TestInfo
        // (could not easily find this out with CompilationUnit, since inserting
        // a :
        // broke the AST - maybe use WorkingCopy and so on,
        // but for now: do it with simple String analysis
        boolean shortOnly = false;
        int annotPos = buffer.lastIndexOf("@TestInfo");
        // the latest annotation - count "(" ")" pairs - if not the same count
        // we assume to be in the annotation (H: code compiles fine)
        if (annotPos != -1) {
            String sub = buffer.substring(annotPos);
            // only consider the latest 6 lines for the annotation to occur
            // (6 = range within which the annotation @TestTarget
            // must occur, but out of range to reach the annotation from the
            // previous method - ah i'd prefer working with compilationUnit...
            String[] lines = sub.split("\n");
            for (int i = lines.length - 6; i < lines.length; i++) {
                String line = lines[i];
                if (line.contains("@TestTarget")) {
                    shortOnly = true;
                }
            }
        }

        return generateAnnotation(shortOnly, method);
    }


    private String generateAnnotation(boolean shortOnly, IMethod method) {
        String[] ptypes = method.getParameterTypes();
        String param = "";
        for (int i = 0; i < ptypes.length; i++) {
            String ptype = ptypes[i];
            String sig = Signature.toString(ptype);
            // kind of a hack: convert all Generic Type args to Object, or to
            // its bound Type
            if (sig.length() == 1) {
                ITypeParameter tps = method.getTypeParameter(sig);
                sig = "Object";

                if (tps != null && tps.exists()) {
                    try {
                        String[] bounds = tps.getBounds();
                        if (bounds.length > 0) {
                            sig = bounds[0];
                        }
                    } catch (JavaModelException e) {
                        e.printStackTrace();
                    }

                }
            }
            // omit type signature
            sig = sig.replaceAll("<.*>", "");
            param += (i > 0 ? ", " : "") + sig + ".class";
        }
        String IND = "    ";

        String targ = "@TestTarget(\n" + IND + "      methodName = \""
                + method.getElementName() + "\",\n" + IND
                + "      methodArgs = {" + param + "}\n" + IND + "    )\n";

        String s;
        if (shortOnly) {
            s = targ;
        } else {

            s = "@TestInfo(\n" + IND + "  status = TestStatus.TBR,\n" + IND
                    + "  notes = \"\",\n" + IND + "  targets = {\n" + IND
                    + "    " + targ + IND + "})";
        }
        return s;
    }

    private IMethod selectMethod() {
        IEditorPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                .getActivePage().getActiveEditor();
        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                .getShell();
        IEditorInput ei = part.getEditorInput();
        final ICompilationUnit cu = JavaPlugin.getDefault()
                .getWorkingCopyManager().getWorkingCopy(ei);
        // cu != null since we register only for java/javadoc completion
        // proposals
        ASTParser parser = ASTParser.newParser(AST.JLS3);
        parser.setSource(cu);
        parser.setResolveBindings(true);
        CompilationUnit unit = (CompilationUnit) parser.createAST(null);

        class MHolder {
            IMethod method;
        }
        final MHolder mholder = new MHolder();

        class FHolder {
            boolean foundClassAnnotation;
        }
        final FHolder fholder = new FHolder();

        unit.accept(new ASTVisitor() {
            public boolean visit(SingleMemberAnnotation node) {
                String name = node.getTypeName().getFullyQualifiedName();
                if (!name.equals("TestTargetClass")) {
                    return false;
                }
                fholder.foundClassAnnotation = true;
                Expression targetClassE = node.getValue();
                ITypeBinding ty = targetClassE.resolveTypeBinding();
                if (ty == null) {
                    return false;
                }
                ITypeBinding[] classTypes = ty.getTypeArguments();
                if (classTypes.length > 0) {
                    ITypeBinding tp = classTypes[0];
                    String qname = tp.getQualifiedName();
                    System.out.println("qname:" + qname);
                    IJavaProject myProject = cu.getJavaProject();
                    try {
                        IType myType = myProject.findType(qname);
                        if (myType != null) {
                            Shell parent = PlatformUI.getWorkbench()
                                    .getActiveWorkbenchWindow().getShell();
                            ElementListSelectionDialog dialog = new ElementListSelectionDialog(
                                    parent,
                                    new JavaElementLabelProvider(
                                            JavaElementLabelProvider.SHOW_PARAMETERS
                                                    | JavaElementLabelProvider.SHOW_OVERLAY_ICONS
                                                    | JavaElementLabelProvider.SHOW_RETURN_TYPE));
                            // restrict to public/protected methods only
                            IMethod[] allMeth = myType.getMethods();
                            List<IMethod> pubproMethods = new ArrayList<IMethod>();
                            for (int i = 0; i < allMeth.length; i++) {
                                IMethod method = allMeth[i];
                                if ((method.getFlags() & (Flags.AccPublic | Flags.AccProtected)) != 0) {
                                    pubproMethods.add(method);
                                }
                            }
                            IMethod[] res = pubproMethods
                                    .toArray(new IMethod[pubproMethods.size()]);
                            dialog.setIgnoreCase(true);
                            dialog.setBlockOnOpen(true);
                            dialog.setElements(res);//
                            dialog.setFilter("");
                            dialog.setTitle(qname);
                            if (dialog.open() != IDialogConstants.CANCEL_ID) {
                                Object[] types = dialog.getResult();
                                System.out.println("selected:" + types[0]);
                                IMethod method = (IMethod) types[0];
                                mholder.method = method;

                            } else {
                                // System.out.println("cancelled!!");
                            }
                        }
                    } catch (JavaModelException e) {
                        e.printStackTrace();
                    }
                }
                return true;
            }
        });
        if (!fholder.foundClassAnnotation) {
            MessageDialog.openInformation(shell, "Class Annotation missing",
                    "@TestTargetClass(...) is missing");
            return null;
        }
        return mholder.method;
    }
}