/*
 * Copyright (C) 2015 Julio Vilmar Gesser and Mike DeHaan
 *
 * This file is part of antlr-java-parser.
 *
 * antlr-java-parser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * antlr-java-parser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with antlr-java-parser.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.github.antlrjavaparser.adapter;

import com.github.antlrjavaparser.ASTHelper;
import com.github.antlrjavaparser.Java7Lexer;
import com.github.antlrjavaparser.Java7Parser;
import com.github.antlrjavaparser.api.BlockComment;
import com.github.antlrjavaparser.api.Comment;
import com.github.antlrjavaparser.api.LineComment;
import com.github.antlrjavaparser.api.Node;
import com.github.antlrjavaparser.api.body.AnnotationMemberDeclaration;
import com.github.antlrjavaparser.api.body.BodyDeclaration;
import com.github.antlrjavaparser.api.body.CatchParameter;
import com.github.antlrjavaparser.api.body.ConstructorDeclaration;
import com.github.antlrjavaparser.api.body.FieldDeclaration;
import com.github.antlrjavaparser.api.body.JavadocComment;
import com.github.antlrjavaparser.api.body.MethodDeclaration;
import com.github.antlrjavaparser.api.body.ModifierSet;
import com.github.antlrjavaparser.api.body.Parameter;
import com.github.antlrjavaparser.api.body.Resource;
import com.github.antlrjavaparser.api.body.TypeDeclaration;
import com.github.antlrjavaparser.api.expr.AnnotationExpr;
import com.github.antlrjavaparser.api.expr.BinaryExpr;
import com.github.antlrjavaparser.api.expr.Expression;
import com.github.antlrjavaparser.api.expr.NameExpr;
import com.github.antlrjavaparser.api.type.ClassOrInterfaceType;
import com.github.antlrjavaparser.api.type.ReferenceType;
import com.github.antlrjavaparser.api.type.Type;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Mike De Haan
 * Date: 12/4/12
 * Time: 7:07 PM
 */
public final class AdapterUtil {
    private AdapterUtil() {

    }

    public static String dottedIdentifier(List<TerminalNode> terminalNodeList) {
        String identifier = "";

        for (int i = 0; i < terminalNodeList.size(); i++) {
            identifier += (i > 0 ? "." : "") + terminalNodeList.get(i).getText();
        }

        return identifier;
    }

    public static NameExpr qualifiedName(List<TerminalNode> terminalNodeList) {
        String qualifiedName = dottedIdentifier(terminalNodeList);
        return ASTHelper.createNameExpr(qualifiedName);
    }

    public static void setVariableModifiers(Java7Parser.VariableModifiersContext context, Resource resource, AdapterParameters adapterParameters) {
        List<AnnotationExpr> annotations = getAnnotations(context, adapterParameters);

        int modifiers = 0;
        if (hasModifier(context.FINAL())) {
            modifiers |= ModifierSet.FINAL;
        }

        resource.setAnnotations(annotations);
        resource.setModifiers(modifiers);
    }

    public static void setVariableModifiers(Java7Parser.VariableModifiersContext context, Parameter parameter, AdapterParameters adapterParameters) {
        List<AnnotationExpr> annotations = getAnnotations(context, adapterParameters);

        int modifiers = 0;
        if (hasModifier(context.FINAL())) {
            modifiers |= ModifierSet.FINAL;
        }

        parameter.setAnnotations(annotations);
        parameter.setModifiers(modifiers);
    }

    public static void setVariableModifiers(Java7Parser.VariableModifiersContext context, CatchParameter parameter, AdapterParameters adapterParameters) {
        /*
        variableModifiers
        :   annotation* FINAL? annotation*
        ;
        */
        int modifiers = 0;
        List<AnnotationExpr> annotations = getAnnotations(context, adapterParameters);

        if (hasModifier(context.FINAL())) {
            modifiers |= ModifierSet.FINAL;
        }

        parameter.setAnnotations(annotations);
        parameter.setModifiers(modifiers);
    }

    private static List<AnnotationExpr> getAnnotations(Java7Parser.VariableModifiersContext context, AdapterParameters adapterParameters) {
        List<AnnotationExpr> annotations = new LinkedList<AnnotationExpr>();
        for (Java7Parser.AnnotationContext annotationContext : context.annotation()) {
            AnnotationExpr annotationExpr = Adapters.getAnnotationContextAdapter().adapt(annotationContext, adapterParameters);
            annotations.add(annotationExpr);
        }
        return annotations;
    }

    private static void setModifiersByType(BodyDeclaration bodyDeclaration, int modifiers) {
        if (bodyDeclaration instanceof TypeDeclaration) {
            ((TypeDeclaration)bodyDeclaration).setModifiers(modifiers);
        } else if (bodyDeclaration instanceof MethodDeclaration) {
            ((MethodDeclaration)bodyDeclaration).setModifiers(modifiers);
        } else if (bodyDeclaration instanceof ConstructorDeclaration) {
            ((ConstructorDeclaration)bodyDeclaration).setModifiers(modifiers);
        } else if (bodyDeclaration instanceof FieldDeclaration) {
            ((FieldDeclaration)bodyDeclaration).setModifiers(modifiers);
        } else if (bodyDeclaration instanceof AnnotationMemberDeclaration) {
            ((AnnotationMemberDeclaration)bodyDeclaration).setModifiers(modifiers);
        } else {
            throw new RuntimeException("Unknown type to set modifiers: " + bodyDeclaration.getClass().getName());
        }
    }

    public static void setModifiers(Java7Parser.ModifiersContext modifiersContext, BodyDeclaration typeDeclaration, AdapterParameters adapterParameters) {
        if (modifiersContext != null && modifiersContext.modifier() != null) {
            setModifiers(modifiersContext.modifier(), typeDeclaration, adapterParameters);
        }
    }

    public static void setModifiers(List<Java7Parser.ModifierContext> modifierList, BodyDeclaration typeDeclaration, AdapterParameters adapterParameters) {
        int modifiers = 0;
        List<AnnotationExpr> annotations = new LinkedList<AnnotationExpr>();
        for (Java7Parser.ModifierContext modifierContext : modifierList) {
            if (hasModifier(modifierContext.PUBLIC())) {
                modifiers |= ModifierSet.PUBLIC;
            }

            if (hasModifier(modifierContext.PROTECTED())) {
                modifiers |= ModifierSet.PROTECTED;
            }

            if (hasModifier(modifierContext.PRIVATE())) {
                modifiers |= ModifierSet.PRIVATE;
            }

            if (hasModifier(modifierContext.ABSTRACT())) {
                modifiers |= ModifierSet.ABSTRACT;
            }

            if (hasModifier(modifierContext.STATIC())) {
                modifiers |= ModifierSet.STATIC;
            }

            if (hasModifier(modifierContext.FINAL())) {
                modifiers |= ModifierSet.FINAL;
            }

            if (hasModifier(modifierContext.STRICTFP())) {
                modifiers |= ModifierSet.STRICTFP;
            }

            if (hasModifier(modifierContext.TRANSIENT())) {
                modifiers |= ModifierSet.TRANSIENT;
            }

            if (modifierContext.annotation() != null) {
                AnnotationExpr annotationExpr = Adapters.getAnnotationContextAdapter().adapt(modifierContext.annotation(), adapterParameters);
                annotations.add(annotationExpr);
            }

            // Any comments that were not trapped by an annotation should be captured here
            AdapterUtil.setComments(typeDeclaration, modifierContext, adapterParameters);
        }

        typeDeclaration.setAnnotations(annotations);

        setModifiersByType(typeDeclaration, modifiers);
    }

    private static boolean hasModifier(TerminalNode modifier) {
        return modifier != null;
    }

    public static List<ClassOrInterfaceType> convertTypeList(List<Type> typeList) {

        if (typeList == null) {
            return null;
            //throw new RuntimeException("typeList cannot be null");
        }

        List<ClassOrInterfaceType> classOrInterfaceTypeList = new LinkedList<ClassOrInterfaceType>();
        for (Type type : typeList) {

            if (type instanceof ReferenceType) {
                ReferenceType referenceType = (ReferenceType)type;
                classOrInterfaceTypeList.add((ClassOrInterfaceType)referenceType.getType());
            } else {
                classOrInterfaceTypeList.add((ClassOrInterfaceType)type);
            }
        }

        return classOrInterfaceTypeList;
    }

    /**
     *
     * @param adapter
     * @param contextList
     * @param operator
     * @param <C> Context Type
     * @return Expression
     */
    public static <C> Expression handleExpression(Adapter<Expression, C> adapter, List<C> contextList, BinaryExpr.Operator operator, AdapterParameters adapterParameters) {
        Expression expression = adapter.adapt(contextList.get(0), adapterParameters);
        AdapterUtil.setComments(expression, (ParserRuleContext)contextList.get(0), adapterParameters);

        // This expression represents more than one consecutive OR expression
        if (contextList.size() > 1) {
            BinaryExpr root = new BinaryExpr();

            root.setLeft(expression);
            root.setOperator(operator);
            BinaryExpr currentExpression = root;

            for (int i = 1; i < contextList.size(); i++) {
                Expression rightExpression = adapter.adapt(contextList.get(i), adapterParameters);
                AdapterUtil.setComments(rightExpression, (ParserRuleContext)contextList.get(i), adapterParameters);
                currentExpression.setRight(rightExpression);

                // On the last one, do not create a tail.
                if (i < contextList.size() - 1) {
                    BinaryExpr binaryExpr = new BinaryExpr();
                    binaryExpr.setLeft(currentExpression);
                    binaryExpr.setOperator(operator);
                    currentExpression = binaryExpr;
                }
            }

            return currentExpression;
        }

        return expression;
    }

    public static <T> List<T> singleElementList(T element) {
        List<T> newElementList = new LinkedList<T>();
        newElementList.add(element);
        return newElementList;
    }

    /**
     * If there are no statements within a block, we need a special method to grab any comments that
     * might exist between braces.
     *
     * @param node
     * @param parserRuleContext
     * @param adapterParameters
     */
    public static void setInternalComments(Node node, ParserRuleContext parserRuleContext, AdapterParameters adapterParameters) {
        BufferedTokenStream tokens = adapterParameters.getTokens();

        if (node == null || parserRuleContext == null || tokens == null) {
            throw new IllegalArgumentException("Parameters must not be null");
        }

        Token startToken = parserRuleContext.getStart();
        Token stopToken = parserRuleContext.getStop();

        List<Token> commentTokens;
        List<Comment> internalCommentList = new LinkedList<Comment>();

        // Checking to the right of the start token will check inside the statement
        commentTokens = tokens.getHiddenTokensToRight(startToken.getTokenIndex(), Java7Lexer.COMMENTS);
        if (commentTokens != null) {
            for (Token commentToken : commentTokens) {

                // Skip already claimed comments (prevents comment repeats)
                if (adapterParameters.isCommentTokenClaimed(commentToken.getTokenIndex())) {
                    continue;
                } else {
                    // Claim it
                    adapterParameters.claimCommentToken(commentToken.getTokenIndex());
                }

                if (commentToken.getText().startsWith("/**")) {
                    JavadocComment javadocComment = new JavadocComment(commentToken.getText());
                    internalCommentList.add(javadocComment);
                } else if (commentToken.getText().startsWith("/*")) {
                    BlockComment blockComment = new BlockComment(commentToken.getText());
                    internalCommentList.add(blockComment);
                } else if (commentToken.getText().startsWith("//")) {
                    LineComment lineComment = new LineComment(commentToken.getText());
                    internalCommentList.add(lineComment);
                }
            }
        }

        if (internalCommentList.size() > 0) {
            if (node.getInternalComments() != null) {
                node.getInternalComments().addAll(internalCommentList);
            } else {
                node.setInternalComments(internalCommentList);
            }
        }
    }

    public static void setPosition(Node node, ParserRuleContext ctx) {
        int beginLine = ctx.getStart().getLine();
        int beginColumn = ctx.getStart().getCharPositionInLine();
        int endLine = ctx.getStop().getLine();
        int endTokenLength = ctx.getStop().getStopIndex() - ctx.getStop().getStartIndex();
        int endColumn = ctx.getStop().getCharPositionInLine() + endTokenLength;

        node.setBeginLine(beginLine);
        node.setBeginColumn(beginColumn);
        node.setEndLine(endLine);
        node.setEndColumn(endColumn);

        node.setBeginIndex(ctx.getStart().getStartIndex());
        node.setEndIndex(ctx.getStop().getStopIndex());
    }

    public static void setComments(Node node, ParserRuleContext parserRuleContext, AdapterParameters adapterParameters) {
        BufferedTokenStream tokens = adapterParameters.getTokens();

        if (node == null || parserRuleContext == null || tokens == null) {
            // Just return
            return;
        }

        Token startToken = parserRuleContext.getStart();
        Token stopToken = parserRuleContext.getStop();

        List<Token> commentTokens;
        List<Comment> beginCommentList = new LinkedList<Comment>();
        List<Comment> endCommentList = new LinkedList<Comment>();

        commentTokens = tokens.getHiddenTokensToLeft(startToken.getTokenIndex(), Java7Lexer.COMMENTS);
        if (commentTokens != null) {
            for (Token commentToken : commentTokens) {

                // Skip already claimed comments (prevents comment repeats)
                if (adapterParameters.isCommentTokenClaimed(commentToken.getTokenIndex())) {
                    continue;
                } else {
                    // Claim it
                    adapterParameters.claimCommentToken(commentToken.getTokenIndex());
                }

                if (commentToken.getText().trim().startsWith("/**")) {
                    JavadocComment javadocComment = new JavadocComment(commentToken.getText());
                    beginCommentList.add(javadocComment);
                } else if (commentToken.getText().trim().startsWith("/*")) {
                    BlockComment blockComment = new BlockComment(commentToken.getText());
                    beginCommentList.add(blockComment);
                } else if (commentToken.getText().trim().startsWith("//")) {
                    LineComment lineComment = new LineComment(commentToken.getText());
                    beginCommentList.add(lineComment);
                }
            }
        }

        commentTokens = tokens.getHiddenTokensToRight(stopToken.getTokenIndex(), Java7Lexer.COMMENTS);
        if (commentTokens != null) {
            for (Token commentToken : commentTokens) {

                if (commentToken.getLine() == stopToken.getLine()) {

                    // Skip already claimed comments (prevents comment repeats)
                    if (adapterParameters.isCommentTokenClaimed(commentToken.getTokenIndex())) {
                        continue;
                    } else {
                        // Claim it
                        adapterParameters.claimCommentToken(commentToken.getTokenIndex());
                    }

                    if (commentToken.getText().trim().startsWith("/**")) {
                        JavadocComment javadocComment = new JavadocComment(commentToken.getText());
                        endCommentList.add(javadocComment);
                    } else if (commentToken.getText().trim().startsWith("/*")) {
                        BlockComment blockComment = new BlockComment(commentToken.getText());
                        endCommentList.add(blockComment);
                    } else if (commentToken.getText().trim().startsWith("//")) {
                        LineComment lineComment = new LineComment(commentToken.getText());
                        endCommentList.add(lineComment);
                    }
                }
            }
        }

        if (beginCommentList.size() > 0) {
            if (node.getBeginComments() != null) {
                node.getBeginComments().addAll(beginCommentList);
            } else {
                node.setBeginComments(beginCommentList);
            }
        }

        if (endCommentList.size() > 0) {
            if (node.getEndComments() != null) {
                node.getEndComments().addAll(endCommentList);
            } else {
                node.setEndComments(endCommentList);
            }
        }
    }
}
