package org.jctools.queues.unpadded;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import org.jctools.queues.util.JCToolsGenerator;

import java.io.File;
import java.io.FileWriter;

import static org.jctools.queues.util.GeneratorUtils.buildGenerator;
import static org.jctools.queues.util.GeneratorUtils.cleanupPaddingComments;
import static org.jctools.queues.util.GeneratorUtils.formatMultilineJavadoc;
import static org.jctools.queues.util.GeneratorUtils.removePaddingFields;
import static org.jctools.queues.util.GeneratorUtils.runJCToolsGenerator;

public class JavaParsingUnpaddedQueueGenerator extends VoidVisitorAdapter<Void> implements JCToolsGenerator {

    protected final String sourceFileName;

    public static void main(String[] args) throws Exception {
        runJCToolsGenerator(JavaParsingUnpaddedQueueGenerator.class, args);
    }

    public JavaParsingUnpaddedQueueGenerator(String sourceFileName) {
        this.sourceFileName = sourceFileName;
    }


    @Override
    public void cleanupComments(CompilationUnit cu) {
        cleanupPaddingComments(cu);
    }

    @Override
    public void organiseImports(CompilationUnit cu) {
        cu.addImport(new ImportDeclaration("org.jctools.queues", false, true));
    }

    @Override
    public String translateQueueName(String qName) {
        if (qName.contains("LinkedQueue") || qName.contains("LinkedArrayQueue")) {
            return qName.replace("Linked", "LinkedUnpadded");
        }

        if (qName.contains("ArrayQueue")) {
            return qName.replace("ArrayQueue", "UnpaddedArrayQueue");
        }

        throw new IllegalArgumentException("Unexpected queue name: " + qName);

    }

    @Override
    public void visit(PackageDeclaration n, Void arg) {
        super.visit(n, arg);
        // Change the package of the output
        n.setName("org.jctools.queues.unpadded");
    }

    @Override
    public void visit(ClassOrInterfaceDeclaration node, Void arg) {
        super.visit(node, arg);
        String nameAsString = node.getNameAsString();
        if (!nameAsString.contains("Queue"))
            return;
        // fixup inheritance
        replaceParentClasses(node);
        // fixup name
        node.setName(translateQueueName(nameAsString));

        node.setJavadocComment(formatMultilineJavadoc(0,
            "NOTE: This class was automatically generated by "
                + this.getClass().getName(),
            "which can found in the jctools-build module. The original source file is " + sourceFileName + ".")
            + node.getJavadocComment().orElse(new JavadocComment("")).getContent());

        removePaddingFields(node);
    }

    @Override
    public void visit(MethodCallExpr n, Void arg)
    {
        super.visit(n, arg);
        if (!n.getName().getIdentifier().equals("fieldOffset")) {
            return;
        }
        for (Expression argument : n.getArguments())
        {
            if (argument.isClassExpr()) {
                ClassExpr classExpr = argument.asClassExpr();
                String type = classExpr.getTypeAsString();
                classExpr.setType(translateQueueName(type));
            }
        }

    }

    @Override
    public void visit(ConstructorDeclaration n, Void arg) {
        super.visit(n, arg);
        // Update the ctor to match the class name
        String nameAsString = n.getNameAsString();
        if (nameAsString.contains("Queue"))
            n.setName(translateQueueName(nameAsString));
    }

    /**
     * Searches all extended or implemented super classes or interfaces for
     * special classes that differ with the atomics version and replaces them
     * with the appropriate class.
     */
    protected void replaceParentClasses(ClassOrInterfaceDeclaration n) {
        for (ClassOrInterfaceType parent : n.getExtendedTypes()) {
            String parentNameAsString = parent.getNameAsString();
            switch (parentNameAsString) {
                case "AbstractQueue":
                    // ignore the JDK parent
                    break;
                default:
                    // Padded super classes are to be renamed and thus so does the
                    // class we must extend.
                    parent.setName(translateQueueName(parentNameAsString));
            }
        }
    }
}
