/*
 * Copyright (C) 2015-2016 Federico Tomassetti
 * Copyright (C) 2017-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;

import static com.github.javaparser.Providers.provider;
import static org.junit.jupiter.api.Assertions.*;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParseStart;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * @see <a href="https://github.com/javaparser/javaparser/issues/2162">https://github.com/javaparser/javaparser/issues/2162</a>
 */
public class Issue2162Test extends AbstractSymbolResolutionTest {

    private JavaParser javaParser;
    private CompilationUnit cu;
    private TypeSolver typeSolver;
    private ParserConfiguration configuration;
    private List<MethodDeclaration> classMethods;
    private List<MethodCallExpr> methodCallExprs;

    @BeforeEach
    public void setUp() {
        typeSolver = new CombinedTypeSolver(new ReflectionTypeSolver());
        configuration = new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(typeSolver));

        javaParser = new JavaParser(configuration);

        // language=JAVA
        String src = "" + "import java.awt.*;\n"
                + "\n"
                + "abstract class Screen <V extends Component> {\n"
                + "    abstract V getView();\n"
                + "}\n"
                + "\n"
                + "class D extends Component {\n"
                + "    void getTest() {\n"
                + "    }\n"
                + "}\n"
                + "\n"
                + "class B extends Screen<D> {\n"
                + "    @Override\n"
                + "    D getView() {\n"
                + "        return new D();\n"
                + "    }\n"
                + "}\n"
                + "\n"
                + "class Run {\n"
                + "    public static void main(String[] args) {\n"
                + "        B b1 = new B();\n"
                + "        b1.getView(); // b1.getView() -> B#getView(), overriding Screen#getView() -> returns object of type D.\n"
                + "        \n"
                + "        // Note that if `b2.getView` is parsed as Screen#getView (as B extends Screen), it will return type `V extends Component` thus will fail to locate the method `Component#getTest()` \n"
                + "        B b2 = new B();\n"
                + "        b2.getView().getTest(); // b2.getView() -> returns object of type D, per above // D#getTest returns void.\n"
                + "        \n"
                + "        // This part is expected to fail as D#getView does not exist (where D is of type `V extends Component`)\n"
                + "        B b3 = new B();\n"
                + "        b3.getView().getView(); // b3.getView() -> returns object of type D, per above // D#getView doesn't exist, thus resolution will fail.\n"
                + "    }\n"
                + "}\n"
                + "";

        ParseResult<CompilationUnit> parseResult = javaParser.parse(ParseStart.COMPILATION_UNIT, provider(src));

        //        parseResult.getProblems().forEach(problem -> System.out.println("problem.getVerboseMessage() = " +
        // problem.getVerboseMessage()));

        assertTrue(parseResult.isSuccessful());
        assertEquals(
                0, parseResult.getProblems().size(), "Expected zero errors when attempting to parse the input code.");
        assertTrue(parseResult.getResult().isPresent(), "Must have a parse result to run this test.");

        this.cu = parseResult.getResult().get();

        classMethods = this.cu.getClassByName("Run").get().getMethods();
        assertEquals(1, classMethods.size(), "Expected only one class with this matching name.");

        methodCallExprs = classMethods.get(0).findAll(MethodCallExpr.class);
        assertTrue(methodCallExprs.size() > 0, "Expected more than one method call.");
    }

    @Test
    public void doTest_withJavaParserFacade_explicit() {
        JavaParserFacade javaParserFacade = JavaParserFacade.get(this.typeSolver);

        //        assertEquals(5, methodCallExprs.size(), "Unexpected number of method calls -- has the test code been
        // updated, without also updating this test case?");

        // b1.getView()
        assertEquals(
                "D",
                javaParserFacade
                        .solve(methodCallExprs.get(0))
                        .getCorrespondingDeclaration()
                        .getReturnType()
                        .describe());

        // b2.getView()
        assertEquals(
                "D",
                javaParserFacade
                        .solve(methodCallExprs.get(2))
                        .getCorrespondingDeclaration()
                        .getReturnType()
                        .describe());
        // b2.getView().getTest()
        assertEquals(
                "void",
                javaParserFacade
                        .solve(methodCallExprs.get(1))
                        .getCorrespondingDeclaration()
                        .getReturnType()
                        .describe());

        // b3.getView()
        assertEquals(
                "D",
                javaParserFacade
                        .solve(methodCallExprs.get(4))
                        .getCorrespondingDeclaration()
                        .getReturnType()
                        .describe());
        assertThrows(
                UnsolvedSymbolException.class,
                () -> {
                    // b3.getView().getView() -- causing error
                    assertEquals(
                            "V",
                            javaParserFacade
                                    .solve(methodCallExprs.get(3))
                                    .getCorrespondingDeclaration()
                                    .getReturnType()
                                    .describe());
                },
                "Exected this resolution to fail due to the chained methods -- `getView()` shouldn't exist on the return value from the first call to `getView()`.");
    }
}
