/*******************************************************************************
 * Copyright (C) 2022, 1C-Soft LLC and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     1C-Soft LLC - initial API and implementation
 *******************************************************************************/
package com.e1c.v8codestyle.bsl.check;

import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.INVOCATION;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.xtext.EcoreUtil2;

import com._1c.g5.v8.dt.bsl.model.FeatureAccess;
import com._1c.g5.v8.dt.bsl.model.Invocation;
import com._1c.g5.v8.dt.bsl.model.Method;
import com._1c.g5.v8.dt.bsl.model.Statement;
import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.TryExceptStatement;
import com.e1c.g5.v8.dt.check.CheckComplexity;
import com.e1c.g5.v8.dt.check.ICheckParameters;
import com.e1c.g5.v8.dt.check.settings.IssueSeverity;
import com.e1c.g5.v8.dt.check.settings.IssueType;

/**
 * Rollback transaction must be in a try-catch,
 * there is no begin transaction for rollback transaction,
 * there should be no executable code between exception and rollback transaction,
 * there is no commit transaction for begin transaction.
 *
 * @author Artem Iliukhin
 */
public final class RollbackTransactionCheck
    extends AbstractTransactionCheck
{

    private static final String CHECK_ID = "rollback-transaction"; //$NON-NLS-1$

    @Override
    public String getCheckId()
    {
        return CHECK_ID;
    }

    @Override
    protected void configureCheck(CheckConfigurer builder)
    {
        builder.title(Messages.RollbackTransactionCheck_Transactions_is_broken)
            .description(Messages.RollbackTransactionCheck_Transactions_is_broken_des)
            .complexity(CheckComplexity.NORMAL)
            .severity(IssueSeverity.MINOR)
            .issueType(IssueType.WARNING)
            .disable()
            //.extension(new StandardCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID))
            .module()
            .checkedObjectType(INVOCATION);
    }

    @Override
    protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
        IProgressMonitor monitor)
    {
        Invocation inv = (Invocation)object;
        FeatureAccess featureAccess = inv.getMethodAccess();
        if (featureAccess instanceof StaticFeatureAccess)
        {
            String nameFeature = featureAccess.getName();
            if (!(ROLLBACK_TRANSACTION_RU.equalsIgnoreCase(nameFeature)
                || ROLLBACK_TRANSACTION.equalsIgnoreCase(nameFeature)))
            {
                return;
            }

            TryExceptStatement tryExceptStatement = EcoreUtil2.getContainerOfType(inv, TryExceptStatement.class);
            if (tryExceptStatement == null)
            {
                resultAceptor.addIssue(Messages.RollbackTransactionCheck_Rollback_transaction_must_be_in_try_catch,
                    inv);
            }

            if (monitor.isCanceled())
            {
                return;
            }

            Method method = EcoreUtil2.getContainerOfType(inv, Method.class);
            if (method == null)
            {
                return;
            }

            List<Invocation> invocations = EcoreUtil2.getAllContentsOfType(method, Invocation.class);
            for (Invocation invocation : invocations)
            {
                String invocName = invocation.getMethodAccess().getName();
                if (BEGIN_TRANSACTION_RU.equals(invocName) || BEGIN_TRANSACTION.equals(invocName))
                {
                    if (monitor.isCanceled())
                    {
                        return;
                    }
                    Statement statement = getStatementFromInvoc(invocation);
                    Statement nextStatement = null;
                    if (statement != null)
                    {
                        nextStatement = getNextStatement(statement);
                    }
                    if (nextStatement instanceof TryExceptStatement)
                    {
                        analyzeTryExcept(invocation, (TryExceptStatement)nextStatement, resultAceptor);
                    }

                    break;
                }
                else if (ROLLBACK_TRANSACTION_RU.equals(invocName) || ROLLBACK_TRANSACTION.equals(invocName))
                {
                    resultAceptor.addIssue(
                        Messages.RollbackTransactionCheck_No_begin_transaction_for_rollback_transaction, invocation);
                }
            }
        }
    }

    private void analyzeTryExcept(Invocation beginTrans, TryExceptStatement tryExceptStatement,
        ResultAcceptor resultAceptor)
    {
        List<Statement> tryStatements = getAllStatement(tryExceptStatement.getTryStatements());
        if (!tryStatements.isEmpty())
        {
            analyzeTryPart(beginTrans, tryStatements, resultAceptor);
        }

        List<Statement> exceptStatement = getAllStatement(tryExceptStatement.getExceptStatements());
        if (!exceptStatement.isEmpty())
        {
            analyzeExceptPart(exceptStatement, resultAceptor);
        }
    }

    private void analyzeTryPart(Invocation beginTrans, List<Statement> tryStatements, ResultAcceptor resultAceptor)
    {
        boolean invocThereIs = false;
        for (Statement statement : tryStatements)
        {
            if (isCommitStatement(statement))
            {
                invocThereIs = true;
                break;
            }

        }

        if (!invocThereIs)
        {
            resultAceptor.addIssue(Messages.RollbackTransactionCheck_No_commit_transaction_for_begin_transaction,
                beginTrans);
        }
    }

    private void analyzeExceptPart(List<Statement> exceptStatement, ResultAcceptor resultAceptor)
    {
        boolean invocThereIs = false;
        for (Statement statement : exceptStatement)
        {
            if (isRollbackStatement(statement))
            {
                invocThereIs = true;
                break;
            }

        }

        if (invocThereIs)
        {
            Statement firstStatement = exceptStatement.get(0);
            if (!isRollbackStatement(firstStatement))
            {
                resultAceptor.addIssue(
                    Messages.RollbackTransactionCheck_Should_be_no_executable_code_between_exception_and_rollback,
                    firstStatement);
            }
        }
    }
}
