/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * 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.vaadin.tests.tb3;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

/**
 * TestBench test runner which supports static @Parameters annotated methods
 * providing parameters for the corresponding setter.
 * <p>
 * {@code @Parameters public static Collection<String> getThemes() } creates one
 * permutation for each value returned by {@code getThemes()}. The value is
 * automatically assigned to the test instance using {@code setTheme(String)}
 * before invoking the test method
 *
 * @author Vaadin Ltd
 */
public class ParameterizedTB3Runner extends TB3Runner {

    public ParameterizedTB3Runner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> methods = super.computeTestMethods();

        Map<Method, Collection<String>> parameters = new LinkedHashMap<>();

        // Find all @Parameters methods and invoke them to find out permutations

        for (Method m : getTestClass().getJavaClass().getMethods()) {
            Parameters p = m.getAnnotation(Parameters.class);
            if (p == null) {
                continue;
            }

            if (!m.getName().startsWith("get") || !m.getName().endsWith("s")) {
                throw new IllegalStateException("Method " + m.getName()
                        + " is annotated with @Parameter but is not named getSomeThings() as it should");
            }

            if (m.getParameterTypes().length != 0) {
                throw new IllegalStateException("Method " + m.getName()
                        + " annotated with @Parameter should not have any arguments");
            }

            if (!Modifier.isStatic(m.getModifiers())) {
                throw new IllegalStateException("Method " + m.getName()
                        + " annotated with @Parameter must be static");
            }

            // getThemes -> setTheme
            String setter = "set" + m.getName().substring("get".length());
            setter = setter.substring(0, setter.length() - 1);
            // property = property.substring(0, 1).toLowerCase()
            // + property.substring(1);

            Method setterMethod;
            try {
                setterMethod = getTestClass().getJavaClass().getMethod(setter,
                        String.class);
            } catch (Exception e) {
                throw new IllegalStateException(
                        "No setter " + setter + " found in "
                                + getTestClass().getJavaClass().getName(),
                        e);
            }

            Collection<String> values;
            try {
                values = (Collection<String>) m.invoke(null);
                if (!values.isEmpty()) {
                    // Ignore any empty collections to allow e.g. integration
                    // tests to use "/demo" path by default without adding that
                    // to the screenshot name
                    parameters.put(setterMethod, values);
                }
            } catch (Exception e) {
                throw new IllegalStateException(
                        "The setter " + m.getName() + " could not be invoked",
                        e);
            }
        }

        // Add method permutations for all @Parameters
        for (Method setter : parameters.keySet()) {
            List<FrameworkMethod> newMethods = new ArrayList<>();
            for (FrameworkMethod m : methods) {

                if (!(m instanceof TBMethod)) {
                    System.err.println(
                            "Unknown method type: " + m.getClass().getName());
                    newMethods.add(m);
                    continue;
                }

                // testFoo
                // testBar
                // ->
                // testFoo[valo]
                // testFoo[runo]
                // testBar[valo]
                // testBar[runo]

                for (final String value : parameters.get(setter)) {
                    newMethods.add(new TBMethodWithBefore((TBMethod) m, setter,
                            value));
                }
            }
            // Update methods so next parameters will use all expanded methods
            methods = newMethods;
        }
        return methods;
    }

    public static class TBMethodWithBefore extends TBMethod {

        private Method setter;
        private String value;
        private TBMethod parent;

        public TBMethodWithBefore(TBMethod m, Method setter, String value) {
            super(m.getMethod(), m.getCapabilities());
            parent = m;
            this.setter = setter;
            this.value = value;
        }

        @Override
        public Object invokeExplosively(Object target, Object... params)
                throws Throwable {
            setter.invoke(target, value);
            return parent.invokeExplosively(target, params);
        }

        @Override
        public String getName() {
            return parent.getName() + "[" + value + "]";
        };

    }
}

@@ -821,16 +821,42 @@
 til.Map;
+%0Aimport java.util.Objects;
 %0A%0Aimport
@@ -6005,16 +6005,509 @@
     %7D;%0A%0A
+        @Override%0A        public boolean equals(Object obj) %7B%0A            if (!TBMethodWithBefore.class.isInstance(obj)) %7B%0A                return false;%0A            %7D%0A%0A            TBMethodWithBefore otherTbMethod = (TBMethodWithBefore) obj;%0A%0A            return super.equals(obj)%0A                    && Objects.equals(otherTbMethod.parent, parent)%0A                    && Objects.equals(otherTbMethod.setter, setter)%0A                    && Objects.equals(otherTbMethod.value, value);%0A        %7D%0A%0A
     %7D%0A%7D%0A

Fix parameterized runner to be compatible with TestBench 4.1