/*
 * Copyright 2010 Google Inc.
 *
 * 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 org.hibernate.jsr303.tck.util;

import static com.google.gwt.thirdparty.guava.common.base.Predicates.and;
import static com.google.gwt.thirdparty.guava.common.base.Predicates.not;
import static com.google.gwt.thirdparty.guava.common.base.Predicates.or;
import static com.google.gwt.thirdparty.guava.common.collect.ImmutableList.copyOf;
import static com.google.gwt.thirdparty.guava.common.collect.Iterables.filter;

import com.google.gwt.junit.tools.GWTTestSuite;
import com.google.gwt.thirdparty.guava.common.base.Predicate;

import junit.framework.Test;

import org.hibernate.jsr303.tck.util.client.Failing;
import org.hibernate.jsr303.tck.util.client.NonTckTest;
import org.hibernate.jsr303.tck.util.client.NotSupported;
import org.hibernate.jsr303.tck.util.client.TestNotCompatible;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Vector;

/**
 * Wrapper for {@link GWTTestSuite} to prevent selected methods from running.
 *
 * Copied code from {@link junit.framework.TestSuite} and modified to exclude
 * test methods with select annotations.
 */
public class TckTestSuiteWrapper extends GWTTestSuite {

  private static final Predicate<Method> HAS_FAILING = createHasAnnotationPredicate(Failing.class);
  private static final Predicate<Method> HAS_NON_TCK_TEST = createHasAnnotationPredicate(NonTckTest.class);
  private static final Predicate<Method> HAS_NOT_SUPPORTED = createHasAnnotationPredicate(NotSupported.class);
  private static final Predicate<Method> HAS_TEST_NOT_COMPATIBLE = createHasAnnotationPredicate(TestNotCompatible.class);

  private static final Predicate<Method> INCLUDE_FAILING = createHasProperty(Failing.INCLUDE);
  private static final Predicate<Method> INCLUDE_NOT_SUPPORTED = createHasProperty(NotSupported.INCLUDE);
  private static final Predicate<Method> INCLUDE_TEST_NOT_COMPATIBLE = createHasProperty(TestNotCompatible.INCLUDE);
  private static final Predicate<Method> EXCLUDE_NON_TCK_TEST = createHasProperty(NonTckTest.EXCLUDE);

  @SuppressWarnings("unchecked")
  private static final Predicate<Method> METHOD_FILTER = 
      and(
         or(INCLUDE_NOT_SUPPORTED, not(HAS_NOT_SUPPORTED)),
         or(INCLUDE_TEST_NOT_COMPATIBLE, not(HAS_TEST_NOT_COMPATIBLE)),
         or(INCLUDE_FAILING, not(HAS_FAILING)),
         not(and(EXCLUDE_NON_TCK_TEST, HAS_NON_TCK_TEST))
      );

  public static Predicate<Method> createHasAnnotationPredicate(
      final Class<? extends Annotation> annotationClass) {
    return new Predicate<Method>() {

      @Override
      public boolean apply(Method method) {
        return method.getAnnotation(annotationClass) != null;
      }
    };
  }

  private static <T> Predicate<T> createHasProperty(final String property) {
    return new Predicate<T>() {
      @Override
      public boolean apply(T notUsed) {
        String value = System.getProperty(property);
        return Boolean.parseBoolean(value);
      }
    };
  }

  public TckTestSuiteWrapper(String name) {
    super(name);
  }

  /**
   * Adds the tests from the given class to the suite.
   */
  @Override
  public void addTestSuite(Class theClass) {

    String fName = theClass.getName();
    try {
      getTestConstructor(theClass); // Avoid generating multiple error messages
    } catch (NoSuchMethodException e) {
      addTest(warning("Class " + theClass.getName()
          + " has no public constructor TestCase(String name) or TestCase()"));
      return;
    }

    if (!Modifier.isPublic(theClass.getModifiers())) {
      addTest(warning("Class " + theClass.getName() + " is not public"));
      return;
    }

    Class<?> superClass = theClass;
    Vector<String> names = new Vector<String>();
    while (Test.class.isAssignableFrom(superClass)) {
      for (Method method : filter(copyOf(superClass.getDeclaredMethods()),
          METHOD_FILTER)) {
        addTestMethod(method, names, theClass);
      }
      superClass = superClass.getSuperclass();
    }
    if (testCount() == 0)
      addTest(warning("No tests found in " + theClass.getName()));
  }

  private void addTestMethod(Method m, Vector<String> names, Class<?> theClass) {
    String name = m.getName();
    if (names.contains(name))
      return;
    if (!isPublicTestMethod(m)) {
      if (isTestMethod(m))
        addTest(warning("Test method isn't public: " + m.getName()));
      return;
    }
    names.addElement(name);
    addTest(createTest(theClass, name));
  }

  private boolean isPublicTestMethod(Method m) {
    return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
  }

  private boolean isTestMethod(Method m) {
    String name = m.getName();
    Class[] parameters = m.getParameterTypes();
    Class returnType = m.getReturnType();
    return parameters.length == 0 && name.startsWith("test")
        && returnType.equals(Void.TYPE);
  }
}
