/*
 * Copyright 2005, Sixth and Red River Software
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.sixrr.stockmetrics.utils;

import com.intellij.psi.*;

public class ControlFlowUtils {
    private ControlFlowUtils() {
        super();
    }

    public static boolean statementMayCompleteNormally(PsiStatement statement) {
        if (statement == null) {
            return true;
        }
        if (statement instanceof PsiBreakStatement ||
                statement instanceof PsiContinueStatement ||
                statement instanceof PsiReturnStatement ||
                statement instanceof PsiThrowStatement) {
            return false;
        } else if (statement instanceof PsiExpressionListStatement ||
                statement instanceof PsiExpressionStatement ||
                statement instanceof PsiEmptyStatement ||
                statement instanceof PsiAssertStatement ||
                statement instanceof PsiDeclarationStatement) {
            return true;
        } else if (statement instanceof PsiForStatement) {
            return forStatementMayReturnNormally((PsiForStatement) statement);
        } else if (statement instanceof PsiForeachStatement) {
            return true;
        } else if (statement instanceof PsiWhileStatement) {
            return whileStatementMayReturnNormally((PsiWhileStatement) statement);
        } else if (statement instanceof PsiDoWhileStatement) {
            return doWhileStatementMayReturnNormally((PsiDoWhileStatement) statement);
        } else if (statement instanceof PsiSynchronizedStatement) {
            final PsiCodeBlock body = ((PsiSynchronizedStatement) statement).getBody();
            return codeBlockMayCompleteNormally(body);
        } else if (statement instanceof PsiBlockStatement) {
            final PsiCodeBlock codeBlock = ((PsiBlockStatement) statement).getCodeBlock();
            return codeBlockMayCompleteNormally(codeBlock);
        } else if (statement instanceof PsiLabeledStatement) {
            return labeledStatementMayCompleteNormally((PsiLabeledStatement) statement);
        } else if (statement instanceof PsiIfStatement) {
            return ifStatementMayReturnNormally((PsiIfStatement) statement);
        } else if (statement instanceof PsiTryStatement) {
            return tryStatementMayReturnNormally((PsiTryStatement) statement);
        } else if (statement instanceof PsiSwitchStatement) {
            return switchStatementMayReturnNormally((PsiSwitchStatement) statement);
        } else   // unknown statement type
        {
            return true;
        }
    }

    private static boolean doWhileStatementMayReturnNormally(PsiDoWhileStatement loopStatement) {
        final PsiExpression test = loopStatement.getCondition();
        final PsiStatement body = loopStatement.getBody();
        return statementMayCompleteNormally(body) && !BoolUtils.isTrue(test)
                || statementIsBreakTarget(loopStatement);
    }

    private static boolean whileStatementMayReturnNormally(PsiWhileStatement loopStatement) {
        final PsiExpression test = loopStatement.getCondition();
        return !BoolUtils.isTrue(test)
                || statementIsBreakTarget(loopStatement);
    }

    private static boolean forStatementMayReturnNormally(PsiForStatement loopStatement) {
        final PsiExpression test = loopStatement.getCondition();

        if (statementIsBreakTarget(loopStatement)) {
            return true;
        }
        if (test == null) {
            return false;
        }
        return !BoolUtils.isTrue(test);
    }

    private static boolean switchStatementMayReturnNormally(PsiSwitchStatement switchStatement) {
        if (statementIsBreakTarget(switchStatement)) {
            return true;
        }
        final PsiCodeBlock body = switchStatement.getBody();
        if (body == null) {
            return true;
        }
        final PsiStatement[] statements = body.getStatements();
        return statementMayCompleteNormally(statements[statements.length - 1]);
    }

    private static boolean tryStatementMayReturnNormally(PsiTryStatement tryStatement) {
        final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
        if (finallyBlock != null) {
            if (!codeBlockMayCompleteNormally(finallyBlock)) {
                return false;
            }
        }
        final PsiCodeBlock tryBlock = tryStatement.getTryBlock();
        if (codeBlockMayCompleteNormally(tryBlock)) {
            return true;
        }
        final PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks();
        for (final PsiCodeBlock catchBlock : catchBlocks) {
            if (codeBlockMayCompleteNormally(catchBlock)) {
                return true;
            }
        }
        return false;
    }

    private static boolean ifStatementMayReturnNormally(PsiIfStatement ifStatement) {
        final PsiStatement thenBranch = ifStatement.getThenBranch();
        if (statementMayCompleteNormally(thenBranch)) {
            return true;
        }
        final PsiStatement elseBranch = ifStatement.getElseBranch();
        return elseBranch == null ||
                statementMayCompleteNormally(elseBranch);
    }

    private static boolean labeledStatementMayCompleteNormally(PsiLabeledStatement labeledStatement) {
        final PsiStatement statement = labeledStatement.getStatement();
        return statementMayCompleteNormally(statement) ||
                statementIsBreakTarget(statement);
    }

    private static boolean codeBlockMayCompleteNormally(PsiCodeBlock block) {
        if (block == null) {
            return true;
        }
        final PsiStatement[] statements = block.getStatements();
        for (final PsiStatement statement : statements) {
            if (!statementMayCompleteNormally(statement)) {
                return false;
            }
        }
        return true;
    }

    private static boolean statementIsBreakTarget(PsiStatement statement) {
        final BreakFinder breakFinder = new BreakFinder(statement);
        statement.accept(breakFinder);
        return breakFinder.breakFound();
    }

    private static class BreakFinder extends JavaRecursiveElementVisitor {
        private boolean m_found = false;
        private final PsiStatement m_target;

        private BreakFinder(PsiStatement target) {
            super();
            m_target = target;
        }

        private boolean breakFound() {
            return m_found;
        }

        @Override
        public void visitBreakStatement(PsiBreakStatement breakStatement) {
            super.visitBreakStatement(breakStatement);
            final PsiStatement exitedStatement = breakStatement.findExitedStatement();
            if (m_target.equals(exitedStatement)) {
                m_found = true;
            }
        }
    }
}

