/*
 * 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 com.google.common.base.Predicates;
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableClassToInstanceMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.UPlaceholderExpression.PlaceholderParamIdent;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.OfKind;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.util.List;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;
import java.util.function.Predicate;

/**
 * Representation of a {@code Refaster} placeholder method, which can represent an arbitrary
 * operation on a specific set of expressions.
 *
 * @author lowasser@google.com (Louis Wasserman)
 */
record PlaceholderMethod(
    StringName name,
    UType returnType,
    ImmutableMap<UVariableDecl, ImmutableClassToInstanceMap<Annotation>> annotatedParameters,
    Matcher<ExpressionTree> matcher,
    ImmutableClassToInstanceMap<Annotation> annotations)
    implements Serializable {
  static PlaceholderMethod create(
      CharSequence name,
      UType returnType,
      ImmutableMap<UVariableDecl, ImmutableClassToInstanceMap<Annotation>> parameters,
      ClassToInstanceMap<Annotation> annotations) {
    boolean allowsIdentity = annotations.getInstance(Placeholder.class).allowsIdentity();
    Class<? extends Matcher<? super ExpressionTree>> matchesClass =
        annotations.containsKey(Matches.class)
            ? UTemplater.getValue(annotations.getInstance(Matches.class))
            : null;
    Class<? extends Matcher<? super ExpressionTree>> notMatchesClass =
        annotations.containsKey(NotMatches.class)
            ? UTemplater.getValue(annotations.getInstance(NotMatches.class))
            : null;
    Predicate<Kind> allowedKinds =
        annotations.containsKey(OfKind.class)
            ? Predicates.<Tree.Kind>in(Arrays.asList(annotations.getInstance(OfKind.class).value()))
            : Predicates.<Tree.Kind>alwaysTrue();
    class PlaceholderMatcher implements Matcher<ExpressionTree> {

      @Override
      public boolean matches(ExpressionTree t, VisitorState state) {
        try {
          return (allowsIdentity || !(t instanceof PlaceholderParamIdent))
              && (matchesClass == null || matchesClass.newInstance().matches(t, state))
              && (notMatchesClass == null || !notMatchesClass.newInstance().matches(t, state))
              && allowedKinds.test(t.getKind());
        } catch (ReflectiveOperationException e) {
          throw new RuntimeException(e);
        }
      }
    }
    return new PlaceholderMethod(
        StringName.of(name),
        returnType,
        parameters,
        new PlaceholderMatcher(),
        ImmutableClassToInstanceMap.<Annotation, Annotation>copyOf(annotations));
  }

  ImmutableSet<UVariableDecl> parameters() {
    return annotatedParameters().keySet();
  }

  /** Parameters which must be referenced in any tree matched to this placeholder. */
  Set<UVariableDecl> requiredParameters() {
    return Maps.filterValues(
            annotatedParameters(),
            (ImmutableClassToInstanceMap<Annotation> annotations) ->
                !annotations.containsKey(MayOptionallyUse.class))
        .keySet();
  }

  PlaceholderExpressionKey exprKey() {
    return new PlaceholderExpressionKey(name().contents(), this);
  }

  PlaceholderBlockKey blockKey() {
    return new PlaceholderBlockKey(name().contents(), this);
  }

  static final class PlaceholderExpressionKey extends Bindings.Key<JCExpression>
      implements Comparable<PlaceholderExpressionKey> {
    final PlaceholderMethod method;

    private PlaceholderExpressionKey(String str, PlaceholderMethod method) {
      super(str);
      this.method = method;
    }

    @Override
    public int compareTo(PlaceholderExpressionKey o) {
      return getIdentifier().compareTo(o.getIdentifier());
    }
  }

  static final class PlaceholderBlockKey extends Bindings.Key<List<JCStatement>> {
    final PlaceholderMethod method;

    private PlaceholderBlockKey(String str, PlaceholderMethod method) {
      super(str);
      this.method = method;
    }
  }
}
