package com.sourcegraph.javagraph;

import org.apache.commons.lang3.StringUtils;

import javax.lang.model.element.*;
import javax.lang.model.util.ElementKindVisitor8;
import java.util.ArrayList;
import java.util.List;

public class ElementPath {
    private final List<String> components = new ArrayList<>(5);

    public static ElementPath get(Element e) {
        return new Visitor().visit(e, new ElementPath());
    }

    @Override
    public String toString() {
        return StringUtils.join(components, ".");
    }

    public void unshift(String name) {
        components.add(0, name);
    }

    private static class Visitor extends
            ElementKindVisitor8<ElementPath, ElementPath> {
        @Override
        public ElementPath visitPackage(PackageElement e, ElementPath p) {
            p.unshift(e.getQualifiedName().toString());
            return p;
        }

        @Override
        public ElementPath visitType(TypeElement e, ElementPath p) {
            String name = e.getSimpleName().toString();
            Element enclosing = e.getEnclosingElement();
            // TODO(sqs): handle multiple anonymous names at same level
            if (name.isEmpty())
                name = "anon";

            // Except for top-level package scope, a type and a variable with
            // the same name may exist in the same scope. We must disambiguate
            // them.
            name += ":type";

            p.unshift(name);
            if (enclosing != null && enclosing.getKind() != ElementKind.OTHER) {
                return visit(enclosing, p);
            } else {
                return p;
            }
        }

        @Override
        public ElementPath visitVariable(VariableElement e, ElementPath p) {
            String name = e.getSimpleName().toString();
            p.unshift(name);
            return visit(e.getEnclosingElement(), p);
        }

        @Override
        public ElementPath visitExecutableAsMethod(ExecutableElement e,
                                                   ElementPath p) {
            String methodName = e.getSimpleName().toString();
            final List<String> params = getParameters(e);
            String name = methodName;
            if (!params.isEmpty())
                name += ":" + StringUtils.join(params, ":");
            p.unshift(name);
            return visit(e.getEnclosingElement(), p);
        }

        @Override
        public ElementPath visitExecutableAsConstructor(ExecutableElement e,
                                                        ElementPath p) {
            final List<String> params = getParameters(e);
            String name = e.getEnclosingElement().getSimpleName().toString()
                    + "/:init";
            if (!params.isEmpty())
                name += ":" + StringUtils.join(params, ":");
            p.unshift(name);
            return visit(e.getEnclosingElement().getEnclosingElement(), p);
        }

        private List<String> getParameters(ExecutableElement e) {
            final List<String> result = new ArrayList<String>();
            for (VariableElement ve : e.getParameters()) {
                result.add(ve.asType().toString().replaceAll("\\.", "\\$"));
            }
            return result;
        }
    }
}

@@ -1887,32 +1887,375 @@
  p);%0A        %7D%0A%0A
+        @Override%0A        public ElementPath visitUnknown(Element e, ElementPath p) %7B%0A            System.err.println(%22Element visitor: unknown element %22 + e.getSimpleName().toString() + %22 of type %22 + e.getKind().toString());%0A            p.unshift(e.getSimpleName().toString());%0A            return visit(e.getEnclosingElement(), p);%0A        %7D%0A%0A
         @Overrid

handle visiting unknown elems (lambdas, etc.)