/*
 * Copyright (C) 2012  Taiki Sugawara
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.buzztaiki.jenova;

import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

@SupportedAnnotationTypes("com.github.buzztaiki.jenova.Jenova")
public class AnnotationProcessor extends AbstractProcessor {
    private Context context;
    private TreeMaker maker;
    private JavacElements elems;

    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        if (!(processingEnv instanceof JavacProcessingEnvironment)) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Jenova requires javac v1.6 or greater.");
            return;
        }
        context = ((JavacProcessingEnvironment)processingEnv).getContext();
        if (context != null) {
            maker = TreeMaker.instance(context);
            elems = JavacElements.instance(context);
        }
    }

    @Override
    public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (context == null) return false;

        for (Element elem : roundEnv.getRootElements()) {
            final JCTree.JCCompilationUnit unit = toUnit(elem);
            if (unit == null) continue;
            if (unit.sourcefile.getKind() != JavaFileObject.Kind.SOURCE) continue;
            unit.accept(new TreeTranslator() {
                @Override public <T extends JCTree> T translate(T tree) {
                    // printTree(tree);
                    if (tree instanceof JCTree.JCNewClass) {
                        JCTree.JCNewClass nc = (JCTree.JCNewClass)tree;
                        if (nc.getIdentifier().toString().startsWith("fn<")) {
                            return (T)translateFn(nc);
                        }
                    }
                    return super.translate(tree);
                }
            });
        }
        return false;
    }

    private void printTree(JCTree tree) {
        if (tree == null) return;
        System.out.format("##%s:%s%n", tree.getClass(), tree);
    }

    private JCTree.JCCompilationUnit toUnit(Element element) {
        TreePath path = Trees.instance(processingEnv).getPath(element);
        return (path == null) ? null : (JCTree.JCCompilationUnit)path.getCompilationUnit();
    }

    private JCTree translateFn(JCTree.JCNewClass fn) {
        JCTree.JCTypeApply ta = (JCTree.JCTypeApply)fn.getIdentifier();
        List<JCTree.JCExpression> typeArgs = ta.getTypeArguments();

        JCTree.JCClassDecl body = fn.getClassBody();
        List<JCTree> members = body.getMembers();
        JCTree.JCBlock initBlock = (JCTree.JCBlock)(members.get(0));
        JCTree.JCMethodDecl method = maker.MethodDef(
            maker.Modifiers(Flags.PUBLIC),
            elems.getName("apply"),
            typeArgs.get(1),
            List.<JCTree.JCTypeParameter>nil(),
            List.of(arg("_", typeArgs.get(0))),
            List.<JCTree.JCExpression>nil(),
            initBlock,
            null);
        JCTree.JCClassDecl newBody = maker.ClassDef(
            body.getModifiers(),
            body.getSimpleName(),
            body.getTypeParameters(),
            body.getExtendsClause(), // TODO: compile failed when java7
            body.getImplementsClause(),
            List.<JCTree>of(method));
        JCTree.JCNewClass newNewClass = maker.NewClass(
            fn.getEnclosingExpression(),
            fn.getTypeArguments(),
            maker.TypeApply(
                maker.QualIdent(elems.getTypeElement("com.google.common.base.Function")),
                typeArgs),
            fn.getArguments(),
            newBody);
        return newNewClass;
    }

    private JCTree.JCVariableDecl arg(String name,  JCTree.JCExpression vartype) {
        return maker.VarDef(maker.Modifiers(0), elems.getName(name), vartype, null);
    }
}

@@ -3318,129 +3318,27 @@
 lic 
-%3CT extends JCTree%3E T translate(T tree) %7B%0A                    // printTree(tree);%0A                    if (tree instanceof 
+void visitNewClass(
 JCTr
@@ -3350,16 +3350,21 @@
 NewClass
+ tree
 ) %7B%0A    
@@ -3383,53 +3383,27 @@
     
-    JCTree.JCNewClass nc = (JCTree.JC
+super.visit
 NewClass
 )tre
@@ -3402,13 +3402,14 @@
 lass
-)
+(
 tree
+)
 ;%0A  
@@ -3430,18 +3430,16 @@
     
-  
-  
 if (
-nc
+tree
 .get
@@ -3511,22 +3511,17 @@
     
-    return (T)
+result = 
 tran
@@ -3528,18 +3528,20 @@
 slateFn(
-nc
+tree
 );%0A     
@@ -3559,85 +3559,9 @@
     
-    %7D%0A                    %7D%0A                    return super.translate(tree);
+%7D
 %0A   

Use visitNewClass instead of translate.