package com.carrotsearch.randomizedtesting;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import com.carrotsearch.randomizedtesting.FilterExpressionParser.IContext;
import com.carrotsearch.randomizedtesting.FilterExpressionParser.Node;
import com.carrotsearch.randomizedtesting.RandomizedRunner.TestCandidate;
import com.carrotsearch.randomizedtesting.annotations.TestGroup;

/**
 * Evaluates enabled/ disabled state for a given test group.
 */
class GroupEvaluator {
  private static class TestGroupInfo {
    final TestGroup group;
    final String name;
    final String sysProperty;
    final boolean enabled;

    TestGroupInfo(Class<? extends Annotation> annType) {
      group = annType.getAnnotation(TestGroup.class); 
      name = TestGroup.Utilities.getGroupName(annType); 
      sysProperty = TestGroup.Utilities.getSysProperty(annType);

      boolean enabled;
      try {
        enabled = RandomizedTest.systemPropertyAsBoolean(sysProperty, group.enabled());
      } catch (IllegalArgumentException e) {
        // Ignore malformed system property, disable the group if malformed though.
        enabled = false;
      }
      this.enabled = enabled;
    }
  }

  private final HashMap<Class<? extends Annotation>, TestGroupInfo> testGroups;
  private final Node filter;
  private String filterExpression;

  GroupEvaluator(List<TestCandidate> testCandidates) {
    testGroups = collectGroups(testCandidates);

    filterExpression = System.getProperty(SysGlobals.SYSPROP_TESTFILTER());
    if (filterExpression != null && filterExpression.trim().isEmpty()) {
      filterExpression = null;
    }

    filter = filterExpression != null ? new FilterExpressionParser().parse(filterExpression) : null;
  }

  private HashMap<Class<? extends Annotation>, TestGroupInfo> collectGroups(List<TestCandidate> testCandidates) {
    final HashMap<Class<? extends Annotation>, TestGroupInfo> groups = new HashMap<Class<? extends Annotation>, TestGroupInfo>();

    // Collect all groups declared on methods and instance classes.
    HashSet<Class<?>> clazzes = new HashSet<Class<?>>();
    HashSet<Annotation> annotations = new HashSet<Annotation>();
    for (TestCandidate c : testCandidates) {
      final Class<?> testClass = c.instanceProvider.getTestClass();
      if (!clazzes.contains(testClass)) {
        clazzes.add(testClass);
        annotations.addAll(Arrays.asList(testClass.getAnnotations()));
      }
      annotations.addAll(Arrays.asList(c.method.getAnnotations()));
    }

    // Get TestGroup annotated annotations. 
    for (Annotation ann : annotations) {
      Class<? extends Annotation> annType = ann.annotationType();
      if (!groups.containsKey(ann) && annType.isAnnotationPresent(TestGroup.class)) {
        groups.put(annType, new TestGroupInfo(annType));
      }
    }

    return groups;
  }

  void appendGroupFilteringOptions(ReproduceErrorMessageBuilder builder) {
    for (TestGroupInfo info : testGroups.values()) {
      if (System.getProperty(info.sysProperty) != null) {
        builder.appendOpt(info.sysProperty, System.getProperty(info.sysProperty));
      }
    }

    if (hasFilteringExpression()) {
      builder.appendOpt(SysGlobals.SYSPROP_TESTFILTER(), filterExpression);
    }
  }

  public boolean hasFilteringExpression() {
    return filterExpression != null;
  }  

  String isTestIgnored(AnnotatedElement... elements) {
    final Map<String, Annotation> annotations = new HashMap<String,Annotation>();

    for (AnnotatedElement element : elements) {
      for (Annotation ann : element.getAnnotations()) {
        Class<? extends Annotation> annType = ann.annotationType();
        if (annType.isAnnotationPresent(TestGroup.class)) {
          if (!testGroups.containsKey(annType)) {
            testGroups.put(annType, new TestGroupInfo(annType));
          }
          annotations.put(testGroups.get(annType).name, ann);
        }
      }
    }

    String defaultState = null;
    for (Annotation ann : annotations.values()) {
      TestGroupInfo g = testGroups.get(ann.annotationType());
      if (!g.enabled) {
        defaultState = "'" + g.name + "' test group is disabled (" + toString(ann) + ")";
        break;
      }
    }

    if (hasFilteringExpression()) {
      final String defaultStateCopy = defaultState;
      boolean enabled = filter.evaluate(new IContext() {
        @Override
        public boolean defaultValue() {
          return defaultStateCopy == null;
        }

        @Override
        public boolean hasGroup(String value) {
          if (value.startsWith("@")) value = value.substring(1);
          for (Annotation ann : annotations.values()) {
            if (value.equalsIgnoreCase(testGroups.get(ann.annotationType()).name)) {
              return true;
            }
          }
          return false;
        }
      });
      return enabled ? null : "Test filter condition: " + filterExpression;
    } else {
      return defaultState;
    }
  }
  
  private String toString(Annotation ann) {
    if (ann == null) return "@null?";
    return ann.toString().replace(
        ann.annotationType().getName(), 
        ann.annotationType().getSimpleName());
  }
}
