package org.checkerframework.checker.i18nformatter;

import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
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.javacutil.AnnotationBuilder;

/**
 * The transfer function for the Internationalization Format String Checker.
 *
 * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
 */
public class I18nFormatterTransfer extends CFTransfer {

  public I18nFormatterTransfer(CFAnalysis analysis) {
    super(analysis);
  }

  @Override
  public TransferResult<CFValue, CFStore> visitMethodInvocation(
      MethodInvocationNode node, TransferInput<CFValue, CFStore> in) {
    I18nFormatterAnnotatedTypeFactory atypeFactory =
        (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory();
    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in);
    I18nFormatterTreeUtil tu = atypeFactory.treeUtil;

    // If hasFormat is called, make sure that the format string is annotated correctly
    if (tu.isHasFormatCall(node, atypeFactory)) {
      CFStore thenStore = result.getRegularStore();
      CFStore elseStore = thenStore.copy();
      ConditionalTransferResult<CFValue, CFStore> newResult =
          new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
      Result<I18nConversionCategory[]> cats = tu.getHasFormatCallCategories(node);
      if (cats.value() == null) {
        tu.failure(cats, "i18nformat.indirect.arguments");
      } else {
        JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0));
        AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value());
        thenStore.insertValue(firstParam, anno);
      }
      return newResult;
    }

    // If isFormat is called, annotate the format string with I18nInvalidFormat
    if (tu.isIsFormatCall(node, atypeFactory)) {
      CFStore thenStore = result.getRegularStore();
      CFStore elseStore = thenStore.copy();
      ConditionalTransferResult<CFValue, CFStore> newResult =
          new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
      JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0));
      AnnotationBuilder builder = new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class);
      // No need to set a value of @I18nInvalidFormat
      builder.setValue("value", "");
      elseStore.insertValue(firstParam, builder.build());
      return newResult;
    }

    // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the
    // getString() method is called, this will check if the given key exist in the translation
    // file and annotate the result string with the correct format annotation according to the
    // corresponding key's value.
    if (tu.isMakeFormatCall(node, atypeFactory)) {
      Result<I18nConversionCategory[]> cats = tu.makeFormatCallCategories(node, atypeFactory);
      if (cats.value() == null) {
        tu.failure(cats, "i18nformat.key.not.found");
      } else {
        AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value());
        return recreateTransferResult(anno, result);
      }
    }

    return result;
  }
}
