// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.groovy.refactoring.convertToJava;

import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiLiteralUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.GrInspectionUtil;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.intentions.conversions.strings.ConvertGStringToStringIntention;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.*;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.formatter.GrControlStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrPropertySelection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.SubtypeConstraint;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.TypeConstraint;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrRangeType;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.ClosureSyntheticParameter;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureParameterEnhancer;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtilKt;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.refactoring.convertToJava.invocators.CustomMethodInvocator;

import static org.jetbrains.plugins.groovy.lang.psi.util.GroovyIndexPropertyUtil.advancedResolve;

/**
 * @author Maxim.Medvedev
 */
public class ExpressionGenerator extends Generator {
  private static final Logger LOG = Logger.getInstance(ExpressionGenerator.class);

  private final StringBuilder builder;
  private final GroovyPsiElementFactory factory;

  private final ExpressionContext context;
  private final @Nullable PsiType expectedType;

  public ExpressionGenerator(StringBuilder builder, ExpressionContext context, @Nullable PsiType expectedType) {
    this.builder = builder;
    this.context = context;
    this.expectedType = expectedType;
    this.factory = GroovyPsiElementFactory.getInstance(context.project);
  }

  public ExpressionGenerator(StringBuilder builder, ExpressionContext context) {
    this(builder, context, null);
  }

  @Override
  public StringBuilder getBuilder() {
    return builder;
  }

  @Override
  public ExpressionContext getContext() {
    return context;
  }

  @Override
  public void visitClosure(@NotNull GrClosableBlock closure) {
    new ClosureGenerator(builder, context).generate(closure);
  }


  @Override
  public void visitExpression(@NotNull GrExpression expression) {
    LOG.error("this method should not be invoked");
  }

  @Override
  public void visitMethodCall(@NotNull GrMethodCall call) {
    generateMethodCall(call);
  }

  private void generateMethodCall(GrMethodCall methodCallExpression) {
    final GrExpression invoked = methodCallExpression.getInvokedExpression();
    final GrExpression[] exprs = methodCallExpression.getExpressionArguments();
    final GrNamedArgument[] namedArgs = methodCallExpression.getNamedArguments();
    final GrClosableBlock[] clArgs = methodCallExpression.getClosureArguments();
    if (invoked instanceof GrReferenceExpression) {
      final GroovyResolveResult resolveResult = ((GrReferenceExpression)invoked).advancedResolve();
      final PsiElement resolved = resolveResult.getElement();
      if (resolved instanceof PsiMethod) {
        final GrExpression qualifier = ((GrReferenceExpression)invoked).getQualifier();//todo replace null-qualifier with this-reference

        invokeMethodOn(
          ((PsiMethod)resolved),
          qualifier,
          exprs, namedArgs, clArgs,
          resolveResult.getSubstitutor(),
          methodCallExpression
        );
        return;
      }
      else if (resolved == null) {
        final GrExpression qualifier = ((GrReferenceExpression)invoked).getQualifier();
        final GrExpression[] args =
          generateArgsForInvokeMethod(((GrReferenceExpression)invoked).getReferenceName(), exprs, namedArgs, clArgs, methodCallExpression);
        GenerationUtil.invokeMethodByName(qualifier, "invokeMethod", args, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY, this, methodCallExpression);
        return;
      }
    }

    GenerationUtil.invokeMethodByName(invoked, "call", exprs, namedArgs, clArgs, this, methodCallExpression);
  }

  private GrExpression[] generateArgsForInvokeMethod(String name,
                                                     GrExpression[] exprs,
                                                     GrNamedArgument[] namedArgs,
                                                     GrClosableBlock[] clArgs,
                                                     GroovyPsiElement psiContext) {
    GrExpression[] result = new GrExpression[2];
    result[0] = factory.createExpressionFromText("\"" + name + "\"");
    StringBuilder builder = new StringBuilder();
    builder.append('[');
    if (namedArgs.length > 0) {
      builder.append('[');
      for (GrNamedArgument namedArg : namedArgs) {
        builder.append(namedArg.getText()).append(',');
      }
      builder.delete(builder.length() - 1, builder.length());
      //builder.removeFromTheEnd(1);
      builder.append("],");
    }
    for (GrExpression expr : exprs) {
      builder.append(expr.getText()).append(',');
    }

    for (GrClosableBlock clArg : clArgs) {
      builder.append(clArg.getText()).append(',');
    }
    if (namedArgs.length + exprs.length + clArgs.length > 0) builder.delete(builder.length() - 1, builder.length());
    //if (namedArgs.length + exprs.length + clArgs.length > 0) builder.removeFromTheEnd(1);
    builder.append("] as Object[]");

    result[1] = factory.createExpressionFromText(builder.toString(), psiContext);
    return result;
  }

  @Override
  public void visitNewExpression(@NotNull GrNewExpression newExpression) {
    boolean hasFieldInitialization = hasFieldInitialization(newExpression);
    StringBuilder builder;

    final PsiType type = newExpression.getType();
    final String varName;
    if (hasFieldInitialization) {
      builder = new StringBuilder();
      varName = GenerationUtil.suggestVarName(type, newExpression, context);
      TypeWriter.writeType(builder, type, newExpression);
      builder.append(' ').append(varName).append(" = ");
    }
    else {
      varName = null;
      builder = this.builder;
    }

    final GrTypeElement typeElement = newExpression.getTypeElement();
    final GrArrayDeclaration arrayDeclaration = newExpression.getArrayDeclaration();
    final GrCodeReferenceElement referenceElement = newExpression.getReferenceElement();

    builder.append("new ");
    if (typeElement != null) {
      final PsiType builtIn = typeElement.getType();
      LOG.assertTrue(builtIn instanceof PsiPrimitiveType);
      final PsiType boxed = TypesUtil.boxPrimitiveType(builtIn, newExpression.getManager(), newExpression.getResolveScope());
      TypeWriter.writeTypeForNew(builder, boxed, newExpression);
    }
    else if (referenceElement != null) {
      GenerationUtil.writeCodeReferenceElement(builder, referenceElement);
    }

    final GrArgumentList argList = newExpression.getArgumentList();
    if (argList != null) {

      GrSignature signature = null;

      final GroovyResolveResult resolveResult = newExpression.advancedResolve();
      final PsiElement constructor = resolveResult.getElement();
      if (constructor instanceof PsiMethod) {
        signature = GrClosureSignatureUtil.createSignature((PsiMethod)constructor, resolveResult.getSubstitutor());
      }
      else if (referenceElement != null) {
        final GroovyResolveResult clazzResult = referenceElement.advancedResolve();
        final PsiElement clazz = clazzResult.getElement();
        if (clazz instanceof PsiClass && ((PsiClass)clazz).getConstructors().length == 0) {
          signature = GrClosureSignatureUtil.createSignature(PsiParameter.EMPTY_ARRAY, null);
        }
      }

      final GrNamedArgument[] namedArgs = hasFieldInitialization ? GrNamedArgument.EMPTY_ARRAY : argList.getNamedArguments();
      new ArgumentListGenerator(builder, context).generate(
        signature,
        argList.getExpressionArguments(),
        namedArgs,
        GrClosableBlock.EMPTY_ARRAY,
        newExpression
      );
    }

    final GrAnonymousClassDefinition anonymous = newExpression.getAnonymousClassDefinition();
    if (anonymous != null) {
      writeTypeBody(builder, anonymous);
    }

    if (arrayDeclaration != null) {
      final GrExpression[] boundExpressions = arrayDeclaration.getBoundExpressions();
      for (GrExpression boundExpression : boundExpressions) {
        builder.append('[');
        boundExpression.accept(this);
        builder.append(']');
      }
      if (boundExpressions.length == 0) {
        builder.append("[]");
      }
    }

    if (hasFieldInitialization) {
      builder.append(';');
      context.myStatements.add(builder.toString());
      final GrNamedArgument[] namedArguments = argList.getNamedArguments();
      for (GrNamedArgument namedArgument : namedArguments) {
        final String fieldName = namedArgument.getLabelName();
        if (fieldName == null) {
          //todo try to initialize field
          final GrArgumentLabel label = namedArgument.getLabel();
          LOG.info("cannot initialize field " + (label == null ? "<null>" : label.getText()));
        }
        else {
          final GroovyResolveResult resolveResult = referenceElement.advancedResolve();
          final PsiElement resolved = resolveResult.getElement();
          LOG.assertTrue(resolved instanceof PsiClass);
          initializeField(varName, type, ((PsiClass)resolved), resolveResult.getSubstitutor(), fieldName, namedArgument.getExpression());
        }
      }
    }
  }

  private void initializeField(String varName,
                               PsiType type,
                               PsiClass resolved,
                               PsiSubstitutor substitutor,
                               String fieldName,
                               GrExpression expression) {
    StringBuilder builder = new StringBuilder();
    final PsiMethod setter = GroovyPropertyUtils.findPropertySetter(resolved, fieldName, false, true);
    if (setter != null) {
      final GrVariableDeclaration var = factory.createVariableDeclaration(ArrayUtilRt.EMPTY_STRING_ARRAY, "", type, varName);
      final GrReferenceExpression caller = factory.createReferenceExpressionFromText(varName, var);
      invokeMethodOn(setter, caller, new GrExpression[]{expression}, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY, substitutor,
                     expression);
    }
    else {
      builder.append(varName).append('.').append(fieldName).append(" = ");
      expression.accept(new ExpressionGenerator(builder, context));
    }
    context.myStatements.add(builder.toString());
  }

  @Override
  public String toString() {
    return builder.toString();
  }

  private static boolean hasFieldInitialization(GrNewExpression newExpression) {
    final GrArgumentList argumentList = newExpression.getArgumentList();
    if (argumentList == null) return false;
    if (argumentList.getNamedArguments().length == 0) return false;

    final GrCodeReferenceElement refElement = newExpression.getReferenceElement();
    if (refElement == null) return false;
    final GroovyResolveResult resolveResult = newExpression.advancedResolve();
    final PsiElement constructor = resolveResult.getElement();
    if (constructor instanceof PsiMethod) {
      return ((PsiMethod)constructor).getParameterList().isEmpty();
    }

    final PsiElement resolved = refElement.resolve();
    return resolved instanceof PsiClass;
  }

  private void writeTypeBody(StringBuilder builder, GrAnonymousClassDefinition anonymous) {
    builder.append("{\n");
    final GeneratorClassNameProvider classNameProvider = new GeneratorClassNameProvider();
    final ClassItemGeneratorImpl classItemGenerator = new ClassItemGeneratorImpl(context.extend());
    new ClassGenerator(classNameProvider, classItemGenerator).writeMembers(builder, anonymous);
    builder.append('}');
  }

  @Override
  public void visitConditionalExpression(@NotNull GrConditionalExpression expression) {
    final GrExpression condition = expression.getCondition();
    final GrExpression thenBranch = expression.getThenBranch();
    final GrExpression elseBranch = expression.getElseBranch();

    final boolean elvis = expression instanceof GrElvisExpression;
    final String var;
    if (elvis) {
      var = createVarByInitializer(condition);
    }
    else {
      var = null;
    }
    final PsiType type = condition.getType();
    if (type == null || PsiTypes.booleanType().equals(TypesUtil.unboxPrimitiveTypeWrapper(type))) {
      if (elvis) {
        builder.append(var);
      }
      else {
        condition.accept(this);
      }
    }
    else {
      final GroovyResolveResult[] results = ResolveUtil.getMethodCandidates(type, "asBoolean", expression, PsiType.EMPTY_ARRAY);
      final GroovyResolveResult result = PsiImplUtil.extractUniqueResult(results);
      GenerationUtil.invokeMethodByResolveResult(
        elvis ? factory.createReferenceExpressionFromText(var, expression) : condition,
        result,
        "asBoolean",
        GrExpression.EMPTY_ARRAY, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY,
        this,
        expression
      );
    }

    builder.append('?');
    if (thenBranch != null) {
      if (elvis) {
        builder.append(var);
      }
      else {
        thenBranch.accept(this);
      }
    }

    builder.append(':');
    if (elseBranch != null) {
      elseBranch.accept(this);
    }
  }

  /**
   * x= expr ->
   * x = expr
   * x.set(expr)[: x.get()]
   * x+= expr ->
   * x+=expr
   * x= plus(x, expr)
   * x.set(plus(x, expr))[expr]
   * x[a] = 4 ->
   * x[a] = 4
   * x.putAt(a, 4) [4]
   */
  @Override
  public void visitAssignmentExpression(final @NotNull GrAssignmentExpression expression) {
    final GrExpression lValue = expression.getLValue();
    final GrExpression rValue = expression.getRValue();

    PsiElement realLValue = PsiUtil.skipParentheses(lValue, false);
    if (realLValue instanceof GrReferenceExpression && rValue != null) {
      GroovyResolveResult resolveResult = ((GrReferenceExpression)realLValue).advancedResolve();
      PsiElement resolved = resolveResult.getElement();
      if (resolved instanceof GrVariable && context.analyzedVars.toWrap((GrVariable)resolved)) {
        //write assignment to wrapped local var
        writeAssignmentWithRefSetter((GrExpression)realLValue, expression);
        return;
      }
      else if (resolved instanceof PsiMethod && resolveResult.isInvokedOnProperty()) {
        //write assignment via setter
        writeAssignmentWithSetter(((GrReferenceExpression)realLValue).getQualifier(), (PsiMethod)resolved, expression);
        return;
      }
      else if (resolved == null || resolved instanceof GrBindingVariable) {
        //write unresolved reference assignment via setter GroovyObject.setProperty(String name, Object value)
        final GrExpression qualifier = ((GrReferenceExpression)realLValue).getQualifier();
        final PsiType type = PsiImplUtil.getQualifierType((GrReferenceExpression)realLValue);

        final GrExpression[] args = {
          factory.createExpressionFromText("\"" + ((GrReferenceExpression)realLValue).getReferenceName() + "\""),
          getRValue(expression)
        };
        GroovyResolveResult[] candidates = type != null
                                           ? ResolveUtil.getMethodCandidates(type, "setProperty", expression, args[0].getType(), args[1].getType())
                                           : GroovyResolveResult.EMPTY_ARRAY;
        final PsiMethod method = PsiImplUtil.extractUniqueElement(candidates);
        if (method != null) {
          writeAssignmentWithSetter(qualifier, method, args, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY, PsiSubstitutor.EMPTY, expression);
          return;
        }
      }
    }
    else if (realLValue instanceof GrIndexProperty) {   //qualifier[args] = rValue
      //write assignment via qualifier.putAt(Args, Value) method
      final GroovyResolveResult result = advancedResolve(((GrIndexProperty)realLValue), false);
      final PsiElement resolved = result.getElement();
      if (resolved instanceof PsiMethod) {
        final GrExpression[] args = ((GrIndexProperty)realLValue).getArgumentList().getExpressionArguments();
        writeAssignmentWithSetter(((GrIndexProperty)realLValue).getInvokedExpression(), (PsiMethod)resolved,
                                  ArrayUtil.append(args, getRValue(expression)), GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY,
                                  result.getSubstitutor(), expression);
        return;
      }
    }

    final PsiType lType = GenerationUtil.getDeclaredType(lValue, context);

    if (!expression.isOperatorAssignment()) {
      //write simple assignment
      lValue.accept(this);
      builder.append(" = ");

      if (rValue != null) {
        final PsiType rType = GenerationUtil.getDeclaredType(rValue, context);
        GenerationUtil.wrapInCastIfNeeded(builder, GenerationUtil.getNotNullType(expression, lType), rType, expression, context, new StatementWriter() {
          @Override
          public void writeStatement(StringBuilder builder, ExpressionContext context) {
            rValue.accept(ExpressionGenerator.this);
          }
        });
      }
    }
    else {
      //write assignment such as +=, -=, etc
      final GroovyResolveResult resolveResult = PsiImplUtil.extractUniqueResult(PsiUtilKt.multiResolve(expression));
      final PsiElement resolved = resolveResult.getElement();

      if (resolved instanceof PsiMethod && !shouldNotReplaceOperatorWithMethod(lValue.getType(), rValue, expression.getOperationTokenType())) {
        lValue.accept(this);
        builder.append(" = ");

        final PsiType rType = GenerationUtil.getDeclaredType((PsiMethod)resolved, resolveResult.getSubstitutor(), context);
        GenerationUtil.wrapInCastIfNeeded(builder, GenerationUtil.getNotNullType(expression, lType),  rType, expression, context, new StatementWriter() {
          @Override
          public void writeStatement(StringBuilder builder, ExpressionContext context) {
            invokeMethodOn(
              ((PsiMethod)resolved),
              (GrExpression)lValue.copy(),
              rValue == null ? GrExpression.EMPTY_ARRAY : new GrExpression[]{rValue},
              GrNamedArgument.EMPTY_ARRAY,
              GrClosableBlock.EMPTY_ARRAY,
              resolveResult.getSubstitutor(),
              expression
            );
          }
        });
      }
      else {
        writeSimpleBinaryExpression(expression.getOperationToken(), lValue, rValue);
      }
    }
  }

  private void writeAssignmentWithRefSetter(GrExpression ref, GrAssignmentExpression expression) {
    GrExpression rValue = getRValue(expression);

    if (PsiUtil.isExpressionUsed(expression)) {
      LOG.assertTrue(context.getRefSetterName(expression) != null);
      builder.append(context.getRefSetterName(expression)).append('(');
      ref.accept(this);
      builder.append(", ");
    }
    else {
      ref.accept(this);
      builder.append(".set(");
    }
    if (rValue != null) {
      rValue.accept(this);
    }
    builder.append(')');
  }

  /**
   * returns rValue         for lValue =  expr
   * lValue+Rvalue  for lValue += rValue
   */
  private @Nullable GrExpression getRValue(GrAssignmentExpression expression) {
    GrExpression rValue = expression.getRValue();
    if (rValue == null) return null;

    GrExpression lValue = expression.getLValue();
    IElementType opToken = expression.getOperationTokenType();
    if (opToken == GroovyTokenTypes.mASSIGN) return rValue;
    Pair<String, IElementType> pair = GenerationUtil.getBinaryOperatorType(opToken);
    LOG.assertTrue(pair != null);

    return factory.createExpressionFromText(lValue.getText() + pair.getFirst() + rValue.getText(), expression);
  }

  private void writeAssignmentWithSetter(GrExpression qualifier, PsiMethod setter, GrAssignmentExpression assignment) {
    GrExpression rValue = getRValue(assignment);
    LOG.assertTrue(rValue != null);
    writeAssignmentWithSetter(qualifier, setter, new GrExpression[]{rValue}, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY, PsiSubstitutor.EMPTY, assignment);
  }

  private void writeAssignmentWithSetter(@Nullable GrExpression qualifier,
                                         @NotNull PsiMethod method,
                                         GrExpression @NotNull [] exprs,
                                         GrNamedArgument @NotNull [] namedArgs,
                                         GrClosableBlock @NotNull [] closures,
                                         @NotNull PsiSubstitutor substitutor,
                                         @NotNull GrAssignmentExpression assignment) {
    if (PsiUtil.isExpressionUsed(assignment)) {
      final GrExpression rValue = assignment.getRValue();

      //inline setter method invocation in case of tail statement and simple right value
      if (PsiUtil.isExpressionStatement(assignment) && PsiUtil.isReturnStatement(assignment) && rValue != null && isVarAccess(rValue)) {

        final StringBuilder assignmentBuffer = new StringBuilder();
        new ExpressionGenerator(assignmentBuffer, context).invokeMethodOn(method, qualifier, exprs, namedArgs, closures, substitutor, assignment);
        assignmentBuffer.append(';');
        context.myStatements.add(assignmentBuffer.toString());

        rValue.accept(this);
      }
      else {
        String setterName = context.getSetterName(method, assignment);
        GrExpression[] args;
        if (method.hasModifierProperty(PsiModifier.STATIC)) {
          args = exprs;
        }
        else {
          args = new GrExpression[exprs.length + 1];
          if (qualifier == null) {
            qualifier = factory.createExpressionFromText("this", assignment);
          }
          args[0] = qualifier;
          System.arraycopy(exprs, 0, args, 1, exprs.length);
        }
        GenerationUtil.invokeMethodByName(null, setterName, args, namedArgs, closures, this, assignment);
      }
    }
    else {
      invokeMethodOn(method, qualifier, exprs, namedArgs, closures, substitutor, assignment);
    }
  }

  private static boolean isVarAccess(@Nullable GrExpression expr) {
    if (expr instanceof GrReferenceExpression) {
      final PsiElement resolved = ((GrReferenceExpression)expr).resolve();
      if (resolved instanceof PsiVariable) {
        return true;
      }
    }
    return false;
  }

  @Override
  public void visitBinaryExpression(@NotNull GrBinaryExpression expression) {
    final GrExpression left = expression.getLeftOperand();
    GrExpression right = expression.getRightOperand();
    final PsiType ltype = left.getType();
    final PsiElement token = expression.getOperationToken();
    final IElementType op = expression.getOperationTokenType();

    if (op == GroovyTokenTypes.mREGEX_FIND) {
      builder.append(GroovyCommonClassNames.JAVA_UTIL_REGEX_PATTERN).append(".compile(");
      if (right != null) {
        right.accept(this);
      }
      builder.append(").matcher(");
      left.accept(this);
      builder.append(')');
      return;
    }
    if (op == GroovyTokenTypes.mREGEX_MATCH) {
      builder.append(GroovyCommonClassNames.JAVA_UTIL_REGEX_PATTERN).append(".matches(");
      if (right != null) {
        right.accept(this);
      }
      builder.append(", ");
      left.accept(this);
      builder.append(')');
      return;
    }
    if ((op == GroovyTokenTypes.mEQUAL || op == GroovyTokenTypes.mNOT_EQUAL) && (GrInspectionUtil.isNull(left) || right != null && GrInspectionUtil.isNull(right))) {
      writeSimpleBinaryExpression(token, left, right);
      return;
    }

    if (op == GroovyTokenTypes.kIN && right instanceof GrReferenceExpression && InheritanceUtil.isInheritor(right.getType(), CommonClassNames.JAVA_LANG_CLASS)) {
      final PsiType type = com.intellij.psi.util.PsiUtil.substituteTypeParameter(right.getType(), CommonClassNames.JAVA_LANG_CLASS, 0, true);
      writeInstanceof(left, type, expression);
      return;
    }

    if (shouldNotReplaceOperatorWithMethod(ltype, right, op)) {
      writeSimpleBinaryExpression(token, left, right);
      return;
    }

    final GroovyResolveResult resolveResult = PsiImplUtil.extractUniqueResult(PsiUtilKt.multiResolve(expression));
    final PsiElement resolved = resolveResult.getElement();
    if (resolved instanceof PsiMethod) {
      if (right == null) {
        right = factory.createExpressionFromText("null");
      }

      if (op == GroovyTokenTypes.mNOT_EQUAL && "equals".equals(((PsiMethod)resolved).getName())) {
        builder.append('!');
      }
      invokeMethodOn(
        ((PsiMethod)resolved),
        left,
        new GrExpression[]{right},
        GrNamedArgument.EMPTY_ARRAY,
        GrClosableBlock.EMPTY_ARRAY,
        resolveResult.getSubstitutor(),
        expression
      );
      if (op == GroovyTokenTypes.mGE) {
        builder.append(" >= 0");
      }
      else if (op == GroovyTokenTypes.mGT) {
        builder.append(" > 0");
      }
      else if (op == GroovyTokenTypes.mLT) {
        builder.append(" < 0");
      }
      else if (op == GroovyTokenTypes.mLE) builder.append(" <= 0");
    }
    else {
      writeSimpleBinaryExpression(token, left, right);
    }
  }

  private static boolean shouldNotReplaceOperatorWithMethod(@Nullable PsiType ltype, @Nullable GrExpression right, IElementType op) {
    if (!GenerationSettings.replaceOperatorsWithMethodsForNumbers) {

      //adding something to string
      if ((op == GroovyTokenTypes.mPLUS || op == GroovyTokenTypes.mPLUS_ASSIGN) && ltype != null && TypesUtil.isClassType(ltype, CommonClassNames.JAVA_LANG_STRING)) {
        return true;
      }

      //we think it is number operation if we don't know right argument
      if (TypesUtil.isNumericType(ltype) && (right == null || TypesUtil.isNumericType(right.getType()))) return true;
    }

    if (op == GroovyTokenTypes.mLNOT && isBooleanType(ltype)) {
      return true;
    }

    return false;
  }

  private static boolean isBooleanType(PsiType type) {
    return PsiTypes.booleanType().equals(type) || type != null && type.equalsToText(CommonClassNames.JAVA_LANG_BOOLEAN);
  }

  private void writeSimpleBinaryExpression(PsiElement opToken, GrExpression left, GrExpression right) {
    left.accept(this);
    builder.append(' ');
    builder.append(opToken.getText());
    if (right != null) {
      builder.append(' ');
      right.accept(this);
    }
  }

  @Override
  public void visitUnaryExpression(@NotNull GrUnaryExpression expression) {
    final boolean postfix = expression.isPostfix();

    final GroovyResolveResult resolveResult = expression.getReference().advancedResolve();
    final PsiElement resolved = resolveResult.getElement();
    final GrExpression operand = expression.getOperand();

    IElementType opType = expression.getOperationTokenType();

    if (resolved instanceof PsiMethod) {

      if (opType == GroovyTokenTypes.mINC || opType == GroovyTokenTypes.mDEC) {
        if (!postfix || expression.getParent() instanceof GrStatementOwner || expression.getParent() instanceof GrControlStatement) {
          if (generatePrefixIncDec((PsiMethod)resolved, operand, expression)) return;
        }
      }

      if (operand != null && shouldNotReplaceOperatorWithMethod(operand.getType(), null, expression.getOperationTokenType())) {
        writeSimpleUnary(operand, expression, this);
      }
      else {
        if (opType == GroovyTokenTypes.mLNOT) {
          builder.append('!');
        }
        invokeMethodOn(
          ((PsiMethod)resolved),
          operand,
          GrExpression.EMPTY_ARRAY,
          GrNamedArgument.EMPTY_ARRAY,
          GrClosableBlock.EMPTY_ARRAY,
          resolveResult.getSubstitutor(),
          expression
        );
      }
    }
    else if (operand != null) {
      if (postfix) {
        operand.accept(this);
        builder.append(expression.getOperationToken().getText());
      }
      else {
        builder.append(expression.getOperationToken().getText());
        operand.accept(this);
      }
    }
  }

  private boolean generatePrefixIncDec(PsiMethod method,
                                       GrExpression operand,
                                       GrUnaryExpression unary) {
    if (!(operand instanceof GrReferenceExpression)) return false;

    final GrExpression qualifier = ((GrReferenceExpression)operand).getQualifier();
    final GroovyReference rValueReference = ((GrReferenceExpression)operand).getRValueReference();
    final GroovyResolveResult resolveResult = rValueReference == null ? EmptyGroovyResolveResult.INSTANCE
                                                                      : rValueReference.advancedResolve();
    final PsiElement resolved = resolveResult.getElement();
    if (resolved instanceof PsiMethod getter && GroovyPropertyUtils.isSimplePropertyGetter(getter)) {
      final String propertyName = GroovyPropertyUtils.getPropertyNameByGetter(getter);
