/*
 * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
 * Copyright (C) 2011, 2013-2024 The JavaParser Team.
 *
 * This file is part of JavaParser.
 *
 * JavaParser can be used either under the terms of
 * a) 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.
 * b) the terms of the Apache License
 *
 * You should have received a copy of both licenses in LICENCE.LGPL and
 * LICENCE.APACHE. Please refer to those files for details.
 *
 * JavaParser 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.
 */

package com.github.javaparser.symbolsolver.javassistmodel;

import static org.junit.jupiter.api.Assertions.*;

import com.github.javaparser.ast.Node;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.declarations.AssociableToAST;
import com.github.javaparser.resolution.declarations.ResolvedAnnotationDeclarationTest;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration;
import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclarationTest;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class JavassistAnnotationDeclarationTest extends AbstractTypeDeclarationTest
        implements ResolvedAnnotationDeclarationTest {

    @Override
    public JavassistAnnotationDeclaration createValue() {
        try {
            TypeSolver typeSolver = new ReflectionTypeSolver();
            CtClass clazz = ClassPool.getDefault().getCtClass("java.lang.Override");
            return new JavassistAnnotationDeclaration(clazz, typeSolver);
        } catch (NotFoundException e) {
            throw new RuntimeException("Unexpected error.", e);
        }
    }

    @Override
    public Optional<Node> getWrappedDeclaration(AssociableToAST associableToAST) {
        return Optional.empty();
    }

    @Override
    public boolean isFunctionalInterface(AbstractTypeDeclaration typeDeclaration) {
        return false;
    }

    @Disabled(value = "This feature is not yet implemented. See https://github.com/javaparser/javaparser/issues/1841")
    @Test
    @Override
    public void containerTypeCantBeNull() {
        super.containerTypeCantBeNull();
    }

    @Disabled(value = "This feature is not yet implemented. See https://github.com/javaparser/javaparser/issues/1838")
    @Test
    @Override
    public void getDeclaredMethodsCantBeNull() {
        super.getDeclaredMethodsCantBeNull();
    }

    @Test
    void getAncestors_shouldReturnAnnotation() throws NotFoundException {
        TypeSolver typeSolver = new ReflectionTypeSolver();
        CtClass clazz = ClassPool.getDefault().getCtClass("java.lang.Override");
        JavassistAnnotationDeclaration overrideAnnotation = new JavassistAnnotationDeclaration(clazz, typeSolver);

        List<ResolvedReferenceType> ancestors = overrideAnnotation.getAncestors();
        assertEquals(2, ancestors.size());
        assertEquals(Object.class.getCanonicalName(), ancestors.get(0).getQualifiedName());
        assertEquals(Annotation.class.getCanonicalName(), ancestors.get(1).getQualifiedName());
    }

    @Test
    void internalTypes_shouldMatchNestedTypes() {
        TypeSolver typeSolver = new ReflectionTypeSolver();

        ClassPool classPool = new ClassPool(true);
        CtClass fooAnnotation = classPool.makeAnnotation("com.example.Foo");
        CtClass barClass = fooAnnotation.makeNestedClass("Bar", true);
        CtClass bazClass = barClass.makeNestedClass("Baz", true);
        JavassistAnnotationDeclaration fooClassDeclaration =
                new JavassistAnnotationDeclaration(fooAnnotation, typeSolver);

        List<ResolvedReferenceTypeDeclaration> innerTypes = new ArrayList<>(fooClassDeclaration.internalTypes());
        assertEquals(1, innerTypes.size());
        assertEquals("com.example.Foo.Bar", innerTypes.get(0).getQualifiedName());
    }

    @Test
    void isAnnotationNotInheritable() {
        TypeSolver typeSolver = new ReflectionTypeSolver();

        ClassPool classPool = new ClassPool(true);
        CtClass annotation = classPool.makeAnnotation("com.example.Foo");

        JavassistAnnotationDeclaration fooAnnotationDeclaration =
                new JavassistAnnotationDeclaration(annotation, typeSolver);
        // An annotation that does not declare an @Inherited annotation is not inheritable
        assertFalse(fooAnnotationDeclaration.isInheritable());
    }

    @Test
    void isAnnotationInheritable() {
        TypeSolver typeSolver = new ReflectionTypeSolver();

        ClassPool classPool = new ClassPool(true);
        CtClass ctClass = classPool.makeAnnotation("com.example.Foo");
        addAnnotation(ctClass, "java.lang.annotation.Inherited");

        JavassistAnnotationDeclaration fooAnnotationDeclaration =
                new JavassistAnnotationDeclaration(ctClass, typeSolver);
        // An annotation that declares an @Inherited annotation is inheritable
        assertTrue(fooAnnotationDeclaration.isInheritable());
    }

    private void addAnnotation(CtClass ctClass, String annotationName) {
        ConstPool constpool = ctClass.getClassFile().getConstPool();

        AnnotationsAttribute annotationsAttribute =
                new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        annotationsAttribute.setAnnotation(createAnnotation(annotationName, constpool));

        ctClass.getClassFile().addAttribute(annotationsAttribute);
    }

    private javassist.bytecode.annotation.Annotation createAnnotation(String annotationName, ConstPool constpool) {
        return new javassist.bytecode.annotation.Annotation(annotationName, constpool);
    }
}
