/*
 * Copyright (C) 2020 The Dagger 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 dagger.hilt.android.internal.testing;

import static dagger.hilt.internal.Preconditions.checkNotNull;
import static dagger.hilt.internal.Preconditions.checkState;

import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import dagger.hilt.internal.GeneratedComponentManager;
import java.lang.annotation.Annotation;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * A Junit {@code TestRule} that's installed in all Hilt tests.
 *
 * <p>This rule enforces that a Hilt TestRule has run. The Dagger component will not be created
 * without this test rule.
 */
public final class MarkThatRulesRanRule implements TestRule {
  private static final String HILT_ANDROID_APP = "dagger.hilt.android.HiltAndroidApp";
  private static final String HILT_ANDROID_TEST = "dagger.hilt.android.testing.HiltAndroidTest";

  private final Context context = ApplicationProvider.getApplicationContext();
  private final Object testInstance;
  private final boolean autoAddModule;

  public MarkThatRulesRanRule(Object testInstance) {
    this.autoAddModule = true;
    this.testInstance = checkNotNull(testInstance);
    checkState(
        hasAnnotation(testInstance, HILT_ANDROID_TEST),
        "Expected %s to be annotated with @HiltAndroidTest.",
        testInstance.getClass().getName());
    checkState(
        context instanceof GeneratedComponentManager,
        "Hilt test, %s, must use a Hilt test application but found %s. To fix, configure the test "
            + "to use HiltTestApplication or a custom Hilt test application generated with "
            + "@CustomTestApplication.",
        testInstance.getClass().getName(),
        context.getClass().getName());
    checkState(
        !hasAnnotation(context, HILT_ANDROID_APP),
        "Hilt test, %s, cannot use a @HiltAndroidApp application but found %s. To fix, configure "
            + "the test to use HiltTestApplication or a custom Hilt test application generated "
            +  "with @CustomTestApplication.",
        testInstance.getClass().getName(),
        context.getClass().getName());
  }

  public void inject() {
    getTestApplicationComponentManager().inject();
  }

  @Override
  public Statement apply(final Statement base, Description description) {
    checkState(
        description.getTestClass().isInstance(testInstance),
        "HiltAndroidRule was constructed with an argument that was not an instance of the test"
            + " class");
    return new Statement() {
      @Override
      public void evaluate() throws Throwable {

        TestApplicationComponentManager componentManager = getTestApplicationComponentManager();
        // This check is required to check that state hasn't been set before this rule runs. This
        // prevents cases like setting state in Application.onCreate for Gradle emulator tests that
        // will get cleared after running the first test case.
        componentManager.checkStateIsCleared();
        componentManager.setAutoAddModule(autoAddModule);
        if (testInstance != null) {
          componentManager.setTestInstance(testInstance);
        }
        componentManager.setHasHiltTestRule(description);
        base.evaluate();
        componentManager.clearState();
      }
    };
  }

  private TestApplicationComponentManager getTestApplicationComponentManager() {
    checkState(
        context instanceof TestApplicationComponentManagerHolder,
        "The context is not an instance of TestApplicationComponentManagerHolder: %s",
        context);
    Object componentManager = ((TestApplicationComponentManagerHolder) context).componentManager();
    checkState(
        componentManager instanceof TestApplicationComponentManager,
        "Expected TestApplicationComponentManagerHolder to return an instance of"
            + "TestApplicationComponentManager");
    return (TestApplicationComponentManager) componentManager;
  }

  private static boolean hasAnnotation(Object obj, String annotationName) {
    for (Annotation annotation : obj.getClass().getAnnotations()) {
      if (annotation.annotationType().getName().contentEquals(annotationName)) {
        return true;
      }
    }
    return false;
  }
}

@@ -3310,24 +3310,40 @@
 tManager();%0A
+        try %7B%0A  
         // T
@@ -3436,16 +3436,18 @@
         
+  
 // preve
@@ -3528,14 +3528,11 @@
 ests
- that%0A
+%0A  
     
@@ -3537,16 +3537,21 @@
       //
+ that
  will ge
@@ -3595,32 +3595,34 @@
 t case.%0A        
+  
 componentManager
@@ -3641,24 +3641,26 @@
 sCleared();%0A
+  
         comp
@@ -3709,24 +3709,26 @@
 e);%0A        
+  
 if (testInst
@@ -3739,24 +3739,26 @@
  != null) %7B%0A
+  
           co
@@ -3807,26 +3807,30 @@
 e);%0A        
-%7D%0A
+  %7D%0A  
         comp
@@ -3883,16 +3883,18 @@
         
+  
 base.eva
@@ -3898,24 +3898,46 @@
 evaluate();%0A
+        %7D finally %7B%0A  
         comp
@@ -3959,24 +3959,34 @@
 earState();%0A
+        %7D%0A
       %7D%0A    

Add try-finally around MarkThatRulesRanRule's evaluate() to ensure we clear state even if there's a failure.