/*
 * Copyright 2014 The Error Prone Authors.
 *
 * 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.google.errorprone.refaster;

import static com.google.errorprone.refaster.Unifier.unifications;
import static com.google.errorprone.refaster.Unifier.unifyList;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.TreeVisitor;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import java.util.Optional;

/**
 * {@code UTree} representation of a {@code LambdaExpressionTree}.
 *
 * @author lowasser@google.com (Louis Wasserman)
 */
@AutoValue
abstract class ULambda extends UExpression implements LambdaExpressionTree {
  public static ULambda create(
      ParameterKind parameterKind, Iterable<UVariableDecl> parameters, UTree<?> body) {
    return new AutoValue_ULambda(parameterKind, ImmutableList.copyOf(parameters), body);
  }

  @Override
  public Kind getKind() {
    return Kind.LAMBDA_EXPRESSION;
  }

  @Override
  public <R, D> R accept(TreeVisitor<R, D> visitor, D data) {
    return visitor.visitLambdaExpression(this, data);
  }

  @Override
  public Choice<Unifier> visitLambdaExpression(LambdaExpressionTree node, Unifier unifier) {
    return unifyList(unifier, getParameters(), node.getParameters())
        .flatMap(unifications(getBody(), node.getBody()));
  }

  @Override
  public JCLambda inline(Inliner inliner) throws CouldNotResolveImportException {
    return inliner.maker().Lambda(inlineParams(inliner), inlineBody(inliner));
  }

  public List<JCVariableDecl> inlineParams(Inliner inliner) throws CouldNotResolveImportException {
    if (parameterKind() == ParameterKind.EXPLICIT) {
      return List.convert(JCVariableDecl.class, inliner.inlineList(getParameters()));
    }
    ListBuffer<JCVariableDecl> params = new ListBuffer<>();
    for (UVariableDecl param : getParameters()) {
      params.add(param.inlineImplicitType(inliner));
    }
    return params.toList();
  }

  JCTree inlineBody(Inliner inliner) throws CouldNotResolveImportException {
    if (getBody() instanceof UPlaceholderExpression body) {
      Optional<List<JCStatement>> blockBinding =
          inliner.getOptionalBinding(body.placeholder().blockKey());
      if (blockBinding.isPresent()) {
        // this lambda is of the form args -> blockPlaceholder();
        List<JCStatement> blockInlined =
            UPlaceholderExpression.copier(body.arguments(), inliner)
                .copy(blockBinding.get(), inliner);
        if (blockInlined.size() == 1) {
          if (blockInlined.getFirst() instanceof JCReturn jCReturn) {
            return jCReturn.getExpression();
          } else if (blockInlined.getFirst()
              instanceof JCExpressionStatement jCExpressionStatement) {
            return jCExpressionStatement.getExpression();
          }
        }
        return inliner.maker().Block(0, blockInlined);
      }
    }
    return getBody().inline(inliner);
  }

  abstract ParameterKind parameterKind();

  @Override
  public abstract ImmutableList<UVariableDecl> getParameters();

  @Override
  public abstract UTree<?> getBody();

  @Override
  public BodyKind getBodyKind() {
    return getBody() instanceof BlockTree ? BodyKind.STATEMENT : BodyKind.EXPRESSION;
  }
}
