content: /*******************************************************************************
   * Copyright (C) 2021, 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.common.Symbols.ANNOTATION_SYMBOLS;
  import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.CONDITIONAL__PREDICATE;
  import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.PROCEDURE;
  import static com._1c.g5.v8.dt.mcore.McorePackage.Literals.NAMED_ELEMENT__NAME;
  
  import java.text.MessageFormat;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  import java.util.TreeSet;
  
  import org.eclipse.core.runtime.IProgressMonitor;
  
  import com._1c.g5.v8.dt.bsl.model.BinaryExpression;
  import com._1c.g5.v8.dt.bsl.model.BinaryOperation;
  import com._1c.g5.v8.dt.bsl.model.Conditional;
  import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess;
  import com._1c.g5.v8.dt.bsl.model.Expression;
  import com._1c.g5.v8.dt.bsl.model.FeatureAccess;
  import com._1c.g5.v8.dt.bsl.model.IfStatement;
  import com._1c.g5.v8.dt.bsl.model.Invocation;
  import com._1c.g5.v8.dt.bsl.model.ModuleType;
  import com._1c.g5.v8.dt.bsl.model.Pragma;
  import com._1c.g5.v8.dt.bsl.model.Procedure;
  import com._1c.g5.v8.dt.bsl.model.ReturnStatement;
  import com._1c.g5.v8.dt.bsl.model.Statement;
  import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
  import com._1c.g5.v8.dt.common.StringUtils;
  import com._1c.g5.v8.dt.lcore.util.CaseInsensitiveString;
  import com.e1c.g5.v8.dt.check.CheckComplexity;
  import com.e1c.g5.v8.dt.check.ICheckParameters;
  import com.e1c.g5.v8.dt.check.components.BasicCheck;
  import com.e1c.g5.v8.dt.check.settings.IssueSeverity;
  import com.e1c.g5.v8.dt.check.settings.IssueType;
  import com.e1c.v8codestyle.check.StandardCheckExtension;
  import com.e1c.v8codestyle.internal.bsl.BslPlugin;
  
  /**
   * Check event handlers OnWrite, BeforeWrite and BeforeDelete that has the checking of DataExchange.Load with return.
   *
   * @author Dmitriy Marmyshev
   * @author Victor Golubev
   *
   */
  public class EventDataExchangeLoadCheck
      extends BasicCheck
  {
      private static final String CHECK_ID = "data-exchange-load"; //$NON-NLS-1$
  
      private static final String PARAM_FUNCTION_LIST = "dataExchangeLoadFunctionList"; //$NON-NLS-1$
  
      private static final String DEFAULT_FUNCTION_LIST = ""; //$NON-NLS-1$
  
      private static final String PARAM_CHECK_AT_BEGINNING = "checkAtBeginning"; //$NON-NLS-1$
  
      private static final String DEFAULT_CHECK_AT_BEGINNING = Boolean.toString(Boolean.FALSE);
  
      private static final String DOT = "."; //$NON-NLS-1$
  
      private static final Collection<String> DEFAULT_NAMES;
  
      static
      {
          Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
          names.add("ПриЗаписи"); //$NON-NLS-1$
          names.add("OnWrite"); //$NON-NLS-1$
          names.add("ПередЗаписью"); //$NON-NLS-1$
          names.add("BeforeWrite"); //$NON-NLS-1$
          names.add("ПередУдалением"); //$NON-NLS-1$
          names.add("BeforeDelete"); //$NON-NLS-1$
          DEFAULT_NAMES = names;
      }
      private static final Collection<String> СALL_FOR_CHECK = Set.of("DataExchange.Load", //$NON-NLS-1$
          "ОбменДанными.Загрузка"); //$NON-NLS-1$
  
      @Override
      public String getCheckId()
      {
          return CHECK_ID;
      }
  
      @Override
      protected void configureCheck(CheckConfigurer builder)
      {
          builder.title(Messages.EventDataExchangeLoadCheck_title)
              .description(Messages.EventDataExchangeLoadCheck_description)
              .complexity(CheckComplexity.NORMAL)
              .severity(IssueSeverity.MAJOR)
              .issueType(IssueType.PORTABILITY)
              .extension(new StandardCheckExtension(773, getCheckId(), BslPlugin.PLUGIN_ID))
              .extension(ModuleTypeFilter.onlyTypes(ModuleType.OBJECT_MODULE, ModuleType.RECORDSET_MODULE))
              .module()
              .checkedObjectType(PROCEDURE)
              .parameter(PARAM_CHECK_AT_BEGINNING, Boolean.class, DEFAULT_CHECK_AT_BEGINNING,
                  Messages.EventDataExchangeLoadCheck_Check_at_the_beginning_of_event_handler)
              .parameter(PARAM_FUNCTION_LIST, String.class, DEFAULT_FUNCTION_LIST,
                  Messages.EventDataExchangeLoadCheck_Function_list_that_checks_DataExchange_Load);
      }
  
      @Override
      protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
          IProgressMonitor monitor)
      {
          Procedure procedure = (Procedure)object;
  
          if (!isNecessaryEventHandler(procedure))
          {
              return;
          }
  
          List<Statement> statements = procedure.allStatements();
          if (statements.isEmpty())
          {
              return;
          }
  
          final boolean checkAtBeginning = parameters.getBoolean(PARAM_CHECK_AT_BEGINNING);
  
          Set<String> checkCalls = getCheckCalls(parameters);
          for (Statement methodStatement : statements)
          {
              if (monitor.isCanceled())
              {
                  return;
              }
  
              if (methodStatement instanceof IfStatement
                  && isDataExchangeLoadChecking((IfStatement)methodStatement, checkCalls))
              {
                  if (hasReturnStatement((IfStatement)methodStatement, monitor))
                  {
                      return;
                  }
  
                  resultAceptor.addIssue(Messages.EventDataExchangeLoadCheck_No_return_in__DataExchange_Load__checking,
                      ((IfStatement)methodStatement).getIfPart(), CONDITIONAL__PREDICATE);
  
                  return;
              }
              if (checkAtBeginning)
              {
                  break;
              }
          }
  
          String message = MessageFormat.format(
              Messages.EventDataExchangeLoadCheck_Mandatory_checking_of_DataExchangeLoad_is_absent_in_event_handler_0,
              procedure.getName());
  
          resultAceptor.addIssue(message, NAMED_ELEMENT__NAME);
  
      }
  
      private boolean isNecessaryEventHandler(Procedure procedure)
      {
          if (!procedure.isEvent())
          {
              return false;
          }
  
          for (Pragma pragma : procedure.getPragmas())
          {
              if (ANNOTATION_SYMBOLS.contains(new CaseInsensitiveString(pragma.getSymbol()))
                  && StringUtils.isNotEmpty(pragma.getValue())
                  && DEFAULT_NAMES.contains(pragma.getValue().replace("\"", ""))) //$NON-NLS-1$ //$NON-NLS-2$
              {
                  return true;
              }
          }
          return DEFAULT_NAMES.contains(procedure.getName());
      }
  
      private boolean isDataExchangeLoadChecking(IfStatement statementMethod, Set<String> checkCalls)
      {
          Conditional conditional = statementMethod.getIfPart();
          Expression predicate = conditional.getPredicate();
          if (predicate instanceof DynamicFeatureAccess)
          {
              return checkDynamicFeatureAccess((DynamicFeatureAccess)predicate, checkCalls);
          }
          else if (predicate instanceof Invocation)
          {
              FeatureAccess expression = ((Invocation)predicate).getMethodAccess();
              if (expression instanceof DynamicFeatureAccess
                  && checkDynamicFeatureAccess((DynamicFeatureAccess)expression, checkCalls))
              {
                  return true;
              }
          }
          else if (predicate instanceof BinaryExpression)
          {
              Map<Expression, BinaryOperation> operand =
                  getMapOperandOperator((BinaryExpression)predicate, BinaryOperation.OR);
              for (Entry<Expression, BinaryOperation> temp : operand.entrySet())
              {
                  Expression expression = temp.getKey();
                  if (expression instanceof Invocation)
                  {
                      expression = ((Invocation)expression).getMethodAccess();
                  }
                  if (expression instanceof DynamicFeatureAccess
                      && checkDynamicFeatureAccess((DynamicFeatureAccess)expression, checkCalls))
                  {
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      private boolean hasReturnStatement(IfStatement statement, IProgressMonitor monitor)
      {
          Conditional conditional = statement.getIfPart();
  
          List<Statement> statementsIf = conditional.getStatements();
          for (Statement statementControlIf : statementsIf)
          {
              if (monitor.isCanceled())
              {
                  return false;
              }
  
              if (statementControlIf instanceof ReturnStatement)
              {
                  return true;
              }
          }
  
          return false;
      }
  
      private Set<String> getCheckCalls(ICheckParameters parameters)
      {
          Set<String> checkCalls = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
          checkCalls.addAll(СALL_FOR_CHECK);
          String functionList = parameters.getString(PARAM_FUNCTION_LIST);
          if (functionList != null && !functionList.isBlank())
          {
              String[] functions = functionList.replace("(", "").replace(")", "").replace(" ", "").split(","); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
              checkCalls.addAll(Set.of(functions));
          }
  
          return Set.copyOf(checkCalls);
      }
  
      private Map<Expression, BinaryOperation> getMapOperandOperator(BinaryExpression binaryExpression,
          BinaryOperation operation)
      {
          Map<Expression, BinaryOperation> mapOperatorOperand = new HashMap<>();
  
          Expression binaryLeft = binaryExpression.getLeft();
          BinaryOperation tempOperation = binaryExpression.getOperation();
          Expression temp = binaryExpression.getRight();
  
          mapOperatorOperand.put(temp, tempOperation);
  
          BinaryExpression currentExpression = null;
          if (binaryLeft instanceof BinaryExpression)
          {
              currentExpression = (BinaryExpression)binaryLeft;
          }
          else
          {
              mapOperatorOperand.put(binaryLeft, tempOperation);
              currentExpression = null;
          }
  
          while (currentExpression != null && currentExpression.getOperation().equals(operation))
          {
              binaryLeft = currentExpression.getLeft();
              temp = currentExpression.getRight();
              tempOperation = currentExpression.getOperation();
              mapOperatorOperand.put(temp, tempOperation);
              if (binaryLeft instanceof BinaryExpression)
              {
                  currentExpression = (BinaryExpression)binaryLeft;
              }
              else
              {
                  currentExpression = null;
              }
          }
  
          mapOperatorOperand.put(binaryLeft, tempOperation);
          return mapOperatorOperand;
  
      }
  
      private boolean checkDynamicFeatureAccess(DynamicFeatureAccess dynamicFeatureAccess, Set<String> checkCalls)
      {
          String methodForCheck = getMethodForCheck(dynamicFeatureAccess);
          return methodForCheck != null && checkCalls.contains(methodForCheck);
      }
  
      private String getMethodForCheck(DynamicFeatureAccess dynamicFeatureAccess)
      {
          StringBuilder builder = new StringBuilder(dynamicFeatureAccess.getName());
          builder.insert(0, DOT);
          Expression expression = dynamicFeatureAccess.getSource();
  
          while (expression instanceof DynamicFeatureAccess)
          {
              DynamicFeatureAccess current = (DynamicFeatureAccess)expression;
              builder.insert(0, current.getName());
              builder.insert(0, DOT);
              expression = current.getSource();
          }
          if (expression instanceof StaticFeatureAccess)
          {
              StaticFeatureAccess staticFeatureAccess = (StaticFeatureAccess)expression;
              builder.insert(0, staticFeatureAccess.getName());
              return builder.toString();
          }
          return null;
      }
  
  }
  