/*
 * 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.resolution.typeinference;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.InitializerDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.VariableDeclarationExpr;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.visitor.GenericVisitor;
import com.github.javaparser.ast.visitor.GenericVisitorAdapter;
import java.util.List;

/**
 * Consider Control Flow to determine which statements are reachable.
 *
 * Except for the special treatment of while, do, and for statements whose condition expression has the constant value
 * true, the values of expressions are not taken into account in the flow analysis.
 *
 * See JLS 14.21
 *
 * @author Federico Tomassetti
 */
public class ControlFlowLogic {

    private static ControlFlowLogic instance = new ControlFlowLogic();

    public static ControlFlowLogic getInstance() {
        return instance;
    }

    private ControlFlowLogic() {}

    /**
     * A break statement with no label attempts to transfer control to the innermost enclosing switch, while, do, or
     * for statement of the immediately enclosing method or initializer; this statement, which is called the break
     * target, then immediately completes normally.
     *
     *
     * A break statement with label Identifier attempts to transfer control to the enclosing labeled statement (§14.7)
     * that has the same Identifier as its label; this statement, which is called the break target, then immediately
     * completes normally. In this case, the break target need not be a switch, while, do, or for statement.
     */
    public Statement breakTarget(BreakStmt breakStmt) {
        throw new UnsupportedOperationException();
    }

    /**
     * A reachable break statement exits a statement if, within the break target, either there are no try statements
     * whose try blocks contain the break statement, or there are try statements whose try blocks contain the break
     * statement and all finally clauses of those try statements can complete normally.
     */
    public boolean exitTheStatement(BreakStmt breakStmt) {
        if (!isReachable(breakStmt)) {
            return false;
        }
        Statement breakTarget = breakTarget(breakStmt);
        for (TryStmt tryStmt : containedTryStmts(breakTarget)) {
            if (contains(tryStmt.getTryBlock(), breakStmt)) {
                if (!tryStmt.getFinallyBlock().isPresent()
                        && !canCompleteNormally(tryStmt.getFinallyBlock().get())) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean continueADoStatement(ContinueStmt continueStmt, DoStmt doStmt) {
        for (TryStmt tryStmt : containedTryStmts(continueStmt)) {
            if (contains(tryStmt.getTryBlock(), continueStmt)) {
                if (!tryStmt.getFinallyBlock().isPresent()
                        && !canCompleteNormally(tryStmt.getFinallyBlock().get())) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean contains(Statement container, Statement contained) {
        throw new UnsupportedOperationException();
    }

    private List<TryStmt> containedTryStmts(Statement statement) {
        throw new UnsupportedOperationException();
    }

    private <P extends Node> boolean parentIs(Node node, Class<P> parentClass) {
        if (node.getParentNode().isPresent()) {
            return parentClass.isInstance(node.getParentNode().get());
        }
        return false;
    }

    // See JLS 14.21
    public boolean canCompleteNormally(Statement statement) {
        if (!isReachable(statement)) {
            return false;
        }
        GenericVisitor<Boolean, Void> visitor = new GenericVisitorAdapter<Boolean, Void>() {
            @Override
            public Boolean visit(BlockStmt n, Void arg) {
                // An empty block that is not a switch block can complete normally iff it is reachable
                if (n.isEmpty() && !parentIs(statement, SwitchStmt.class)) {
                    return isReachable(statement);
                }
                // A non-empty block that is not a switch block can complete normally iff the last statement in
                // it can complete normally.
                if (!n.isEmpty() && !parentIs(statement, SwitchStmt.class)) {
                    return canCompleteNormally(n.getStatement(n.getStatements().size() - 1));
                }
                throw new UnsupportedOperationException();
            }

            @Override
            public Boolean visit(LabeledStmt n, Void arg) {
                // A labeled statement can complete normally if at least one of the following is true:
                // – The contained statement can complete normally.
                // – There is a reachable break statement that exits the labeled statement.
                throw new UnsupportedOperationException();
            }

            @Override
            public Boolean visit(EmptyStmt n, Void arg) {
                // An empty statement can complete normally iff it is reachable.
                return isReachable(n);
            }

            @Override
            public Boolean visit(LocalClassDeclarationStmt n, Void arg) {
                // A local class declaration statement can complete normally if it is reachable.
                return isReachable(n);
            }

            @Override
            public Boolean visit(LocalRecordDeclarationStmt n, Void arg) {
                // A local record declaration statement can complete normally if it is reachable.
                return isReachable(n);
            }

            @Override
            public Boolean visit(IfStmt n, Void arg) {
                if (n.getElseStmt().isPresent()) {
                    // An if-then-else statement can complete normally iff the then-statement can
                    // complete normally or the else-statement can complete normally.
                    return canCompleteNormally(n.getThenStmt())
                            || canCompleteNormally(n.getElseStmt().get());
                }
                return isReachable(n);
            }

            @Override
            public Boolean visit(AssertStmt n, Void arg) {
                // An assert statement can complete normally iff it is reachable.
                return isReachable(n);
            }

            @Override
            public Boolean visit(ExpressionStmt n, Void arg) {
                // A local variable declaration statement can complete normally iff it is reachable.
                if (n.getExpression() instanceof VariableDeclarationExpr) {
                    VariableDeclarationExpr expr = (VariableDeclarationExpr) n.getExpression();
                    return isReachable(n);
                }
                // An expression statement can complete normally iff it is reachable.
                return isReachable(n);
            }
        };
        return statement.accept(visitor, null);
    }

    private boolean isReachableBecauseOfPosition(Statement statement) {
        // The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.

        // Every other statement S in a non-empty block that is not a switch block is reachable iff the statement
        // preceding S can complete normally.

        // The contained statement of a Labelled Statement is reachable iff the labeled statement is reachable.

        // The then-statement of an if-then statement is reachable iff the if-then statement is reachable.

        // The then-statement of an if-then-else  statement is reachable iff the if-then-else statement is reachable.
        // The else-statement is reachable iff the if-then-else statement is reachable.

        throw new UnsupportedOperationException();
    }

    public boolean isReachable(Statement statement) {

        GenericVisitor<Boolean, Void> visitor = new GenericVisitorAdapter<Boolean, Void>() {
            @Override
            public Boolean visit(BlockStmt n, Void arg) {
                // The block that is the body of a constructor, method, instance initializer, or static initializer is
                // reachable
                if (statement.getParentNode().isPresent()) {
                    if (statement.getParentNode().get() instanceof ConstructorDeclaration) {
                        return true;
                    }
                    if (statement.getParentNode().get() instanceof MethodDeclaration) {
                        return true;
                    }
                    if (statement.getParentNode().get() instanceof InitializerDeclaration) {
                        return true;
                    }
                }
                return isReachableBecauseOfPosition(statement);
            }

            @Override
            public Boolean visit(LocalClassDeclarationStmt n, Void arg) {
                return super.visit(n, arg);
            }

            @Override
            public Boolean visit(LocalRecordDeclarationStmt n, Void arg) {
                return super.visit(n, arg);
            }
        };
        return statement.accept(visitor, null);

        //
        //
        //
        //        A switch statement can complete normally iff at least one of the following is
        // true:
        // – The switch block is empty or contains only switch labels.
        // – The last statement in the switch block can complete normally.
        // – There is at least one switch label after the last switch block statement group. – The switch block does not
        // contain a default label.
        // – There is a reachable break statement that exits the switch statement.
        //        BLOCKS AND STATEMENTS Unreachable Statements 14.21
        //
        // A switch block is reachable iff its switch statement is reachable.
        //
        //        A statement in a switch block is reachable iff its switch statement is reachable
        // and at least one of the following is true:
        // – It bears a case or default label.
        // – There is a statement preceding it in the switch block and that preceding statement can complete normally.
        //
        // A while statement can complete normally iff at least one of the following is true:
        // – Thewhilestatementisreachableandtheconditionexpressionisnotaconstant
        // expression (§15.28) with value true.
        // – There is a reachable break statement that exits the while statement.
        //        The contained statement is reachable iff the while statement is reachable and the condition expression
        // is not a constant expression whose value is false.
        //
        //        A do statement can complete normally iff at least one of the following is true:
        // – The contained statement can complete normally and the condition expression
        // is not a constant expression (§15.28) with value true.
        // – The do statement contains a reachable continue statement with no label, and the do statement is the
        // innermost while, do, or for statement that contains that continue statement, and the continue statement
        // continues that do statement, and the condition expression is not a constant expression with value true.
        // – The do statement contains a reachable continue statement with a label L, and the do statement has label L,
        // and the continue statement continues that do statement, and the condition expression is not a constant
        // expression with value true.
        // – There is a reachable break statement that exits the do statement.
        //        The contained statement is reachable iff the do statement is reachable.
        //
        //        A basic for statement can complete normally iff at least one of the following
        // is true:
        // – The for statement is reachable, there is a condition expression, and the
        // condition expression is not a constant expression (§15.28) with value true.
        // – There is a reachable break statement that exits the for statement.
        //        The contained statement is reachable iff the for statement is reachable and the condition expression
        // is not a constant expression whose value is false.
        //
        // • An enhanced for statement can complete normally iff it is reachable.
        //
        // • A break, continue, return, or throw statement cannot complete normally.
        //
        // • A synchronized statement can complete normally iff the contained statement can complete normally.
        //        The contained statement is reachable iff the synchronized statement is reachable.
        //
        // • A try statement can complete normally iff both of the following are true:
        // – The try block can complete normally or any catch block can complete
        // normally.
        // – Ifthetrystatementhasafinallyblock,thenthefinallyblockcancomplete normally.
        //
        // • The try block is reachable iff the try statement is reachable.
        //
        // • A catch block C is reachable iff both of the following are true:
        // – Either the type of C's parameter is an unchecked exception type or Exception or a superclass of Exception,
        // or some expression or throw statement in the try block is reachable and can throw a checked exception whose
        // type is assignable to the type of C's parameter. (An expression is reachable iff the innermost statement
        // containing it is reachable.)
        // See §15.6 for normal and abrupt completion of expressions.
        // – There is no earlier catch block A in the try statement such that the type of C's
        // parameter is the same as or a subclass of the type of A's parameter.
        // • The Block of a catch block is reachable iff the catch block is reachable.
        // • If a finally block is present, it is reachable iff the try statement is reachable.
        //        One might expect the if statement to be handled in the following manner:
        // • An if-then statement can complete normally iff at least one of the following is true:
        // – The if-then statement is reachable and the condition expression is not a constant expression whose value is
        // true.
        // – The then-statement can complete normally.
        //        The then-statement is reachable iff the if-then statement is reachable and the
        // condition expression is not a constant expression whose value is false.
        // • An if-then-else statement can complete normally iff the then-statement can complete normally or the
        // else-statement can complete normally.
        //        BLOCKS AND STATEMENTS Unreachable Statements 14.21
        // The then-statement is reachable iff the if-then-else statement is reachable and the condition expression is
        // not a constant expression whose value is false.
        //        The else-statement is reachable iff the if-then-else statement is reachable and the condition
        // expression is not a constant expression whose value is true.
        //        This approach would be consistent with the treatment of other control structures. However, in order to
        // allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules
        // differ.

        // FIXME
        // throw new UnsupportedOperationException();
    }
}
