/*
 * Copyright 2009 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 com.google.gwt.junit;

import com.google.gwt.dev.cfg.BindingProperty;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.Properties;
import com.google.gwt.junit.JUnitShell.Strategy;
import com.google.gwt.junit.client.WithProperties;
import com.google.gwt.junit.client.WithProperties.Property;

import junit.framework.TestCase;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

import javax.lang.model.SourceVersion;

/**
 * A {@link JUnitShell.Strategy} that will alter the module the tests are run
 * in by defining module properties as requested by annotations on the tests.
 */
public class PropertyDefiningStrategy implements Strategy {
  private TestCase testCase;
  private Set<Property> properties;

  public PropertyDefiningStrategy(TestCase testCase) {
    this.testCase = testCase;
  }

  @Override
  public String getSyntheticModuleExtension() {
    String extension = "JUnit";
    computePropertiesMap();
    if (properties.size() > 0) {
      StringBuilder sb = new StringBuilder();
      for (Property property : properties) {
        sb.append(".").append(property.name()).append(
            "_").append(property.value());
      }
      extension += sb.toString();
    }
    return extension;
  }

  @Override
  public void processModule(ModuleDef module) {
    computePropertiesMap();
    if (properties.size() > 0) {
      Properties props = module.getProperties();
      for (Property property : properties) {
        BindingProperty binding = props.createBinding(property.name());
        if (!binding.isDefinedValue(property.value())) {
          binding.addDefinedValue(binding.getRootCondition(), property.value());
        }
        binding.setValues(
            binding.getRootCondition(), property.value());
      }
    }
  }

  private Property checkProperty(Property property) {
    String[] tokens = (property.name() + ". ").split("\\.");
    for (int i = 0; i < tokens.length - 1; i++) {
      if (!SourceVersion.isIdentifier(tokens[i])) {
        throw new AssertionError(
            "Property name invalid: " + property.name());
      }
    }

    if (!SourceVersion.isIdentifier(property.value())) {
      throw new AssertionError(
          "Property value invalid: " + property.value());
    }

    return property;
  }

  private void computePropertiesMap() {
    if (properties == null) {
      Set<Property> props = new TreeSet<Property>(
          new Comparator<Property>() {
        public int compare(Property p1, Property p2) {
          int r = p1.name().compareTo(p2.name());
          if (r == 0) {
            r = p1.value().compareTo(p2.value());
          }
          return r;
        }
      });
      try {
        String name = testCase.getName();
        if (name != null) {
          Method testMethod = testCase.getClass().getMethod(testCase.getName());
          if (testMethod.isAnnotationPresent(WithProperties.class)) {
            WithProperties annotation =
              testMethod.getAnnotation(WithProperties.class);
            for (Property property : annotation.value()) {
              props.add(checkProperty(property));
            }
          }
        }
      } catch (SecurityException e) {
        // should not happen
        e.printStackTrace();
      } catch (NoSuchMethodException e) {
        // should not happen
        e.printStackTrace();
      }
      properties = props;
    }
  }
}
