package org.checkerframework.checker.signature;

import javax.lang.model.element.ExecutableElement;
import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TypesUtils;

/** The transfer function for the Signature Checker. */
public class SignatureTransfer extends CFTransfer {

  /** The annotated type factory for this transfer function. */
  private final SignatureAnnotatedTypeFactory atypeFactory;

  /**
   * Create a new SignatureTransfer.
   *
   * @param analysis the analysis
   */
  public SignatureTransfer(CFAnalysis analysis) {
    super(analysis);
    atypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory();
  }

  @Override
  public TransferResult<CFValue, CFStore> visitMethodInvocation(
      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
    TransferResult<CFValue, CFStore> superResult = super.visitMethodInvocation(n, in);

    MethodAccessNode target = n.getTarget();
    ExecutableElement method = target.getMethod();
    Node receiver = target.getReceiver();
    if (TypesUtils.isString(receiver.getType()) && ElementUtils.matchesElement(method, "isEmpty")) {

      AnnotatedTypeMirror receiverAtm = atypeFactory.getAnnotatedType(receiver.getTree());
      if (receiverAtm.hasPrimaryAnnotation(CanonicalNameOrEmpty.class)) {

        CFStore thenStore = superResult.getRegularStore();
        CFStore elseStore = thenStore.copy();
        ConditionalTransferResult<CFValue, CFStore> result =
            new ConditionalTransferResult<>(superResult.getResultValue(), thenStore, elseStore);
        // The refined expression is the receiver of the method call.
        JavaExpression refinedExpr = JavaExpression.fromNode(receiver);

        elseStore.insertValue(refinedExpr, atypeFactory.CANONICAL_NAME);
        return result;
      }
    }
    return superResult;
  }
}
