// Keep somewhat in sync with
// ../defaultsPersist/Driver.java and ../PersistUtil.

import com.sun.tools.classfile.Annotation;
import com.sun.tools.classfile.ClassFile;
import java.io.PrintStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class Driver {

  private static final PrintStream out = System.out;

  // The argument is in the format expected by Class.forName().
  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      throw new IllegalArgumentException("Usage: java Driver <test-name>");
    }
    String name = args[0];
    Class<?> clazz = Class.forName(name);
    new Driver().runDriver(clazz.newInstance());
  }

  protected void runDriver(Object object) throws Exception {
    int passed = 0, failed = 0;
    Class<?> clazz = object.getClass();
    out.println("Tests for " + clazz.getName());

    // Find methods
    for (Method method : clazz.getMethods()) {
      List<String> expected = expectedOf(method);
      if (expected == null) {
        continue;
      }
      if (method.getReturnType() != String.class) {
        throw new IllegalArgumentException("Test method needs to return a string: " + method);
      }
      String testClass = PersistUtil.testClassOf(method);

      try {
        String compact = (String) method.invoke(object);
        String fullFile = PersistUtil.wrap(compact);
        ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass);
        List<Annotation> actual = ReferenceInfoUtil.extendedAnnotationsOf(cf);
        String diagnostic =
            String.join(
                "; ",
                "Tests for " + clazz.getName(),
                "compact=" + compact,
                "fullFile=" + fullFile,
                "testClass=" + testClass);
        ReferenceInfoUtil.compare(expected, actual, cf, diagnostic);
        out.println("PASSED:  " + method.getName());
        ++passed;
      } catch (Throwable e) {
        out.println("FAILED:  " + method.getName());
        out.println("    " + e);
        ++failed;
      }
    }

    out.println();
    int total = passed + failed;
    out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED");

    out.flush();

    if (failed != 0) {
      throw new RuntimeException(failed + " tests failed");
    }
  }

  private List<String> expectedOf(Method m) {
    ADescription ta = m.getAnnotation(ADescription.class);
    ADescriptions tas = m.getAnnotation(ADescriptions.class);

    if (ta == null && tas == null) {
      return null;
    }

    List<String> result = new ArrayList<>();

    if (ta != null) {
      result.add(expectedOf(ta));
    }

    if (tas != null) {
      for (ADescription a : tas.value()) {
        result.add(expectedOf(a));
      }
    }

    return result;
  }

  private String expectedOf(ADescription d) {
    return d.annotation();
  }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ADescription {
  String annotation();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ADescriptions {
  ADescription[] value() default {};
}
