/*
 * Copyright 2014 the original author or authors.
 *
 * 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.gradle.platform.base.internal.registry;

import org.gradle.model.collection.CollectionBuilder;
import org.gradle.model.internal.core.ModelReference;
import org.gradle.model.internal.core.ModelType;
import org.gradle.model.internal.inspect.AbstractAnnotationDrivenMethodRuleDefinitionHandler;
import org.gradle.model.internal.inspect.MethodRuleDefinition;
import org.gradle.platform.base.InvalidComponentModelException;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.List;

public abstract class AbstractAnnotationDrivenMethodComponentRuleDefinitionHandler<T extends Annotation> extends AbstractAnnotationDrivenMethodRuleDefinitionHandler<T> {
    protected <R> void assertIsVoidMethod(MethodRuleDefinition<R> ruleDefinition) {
        if (!ModelType.of(Void.TYPE).equals(ruleDefinition.getReturnType())) {
            throw new InvalidComponentModelException(String.format("%s method must not have a return value.", annotationType.getSimpleName()));
        }
    }

    protected <R, V> void visitCollectionBuilderSubject(RuleMethodDataCollector dataCollector, MethodRuleDefinition<R> ruleDefinition, Class<V> typeParameter) {
        if (ruleDefinition.getReferences().size() == 0) {
            throw new InvalidComponentModelException(String.format("%s method must have a parameter of type '%s'.", annotationType.getSimpleName(), CollectionBuilder.class.getName()));
        }

        ModelType<?> builder = ruleDefinition.getReferences().get(0).getType();

        if (!ModelType.of(CollectionBuilder.class).isAssignableFrom(builder)) {
            throw new InvalidComponentModelException(String.format("%s method first parameter must be of type '%s'.", annotationType.getSimpleName(), CollectionBuilder.class.getName()));
        }
        if (builder.getTypeVariables().size() != 1) {
            throw new InvalidComponentModelException(String.format("Parameter of type '%s' must declare a type parameter extending '%s'.", CollectionBuilder.class.getSimpleName(), typeParameter.getSimpleName()));
        }
        ModelType<?> subType = builder.getTypeVariables().get(0);

        if (subType.isWildcard()) {
            throw new InvalidComponentModelException(String.format("%s type '%s' cannot be a wildcard type (i.e. cannot use ? super, ? extends etc.).", typeParameter.getName(), subType.toString()));
        }
        dataCollector.parameterTypes.put(typeParameter, subType.getConcreteClass());
    }

    protected class RuleMethodDataCollector {
        HashMap<Class<?>, Class<?>> parameterTypes = new HashMap<Class<?>, Class<?>>();

        @SuppressWarnings("unchecked")
        public <S, R extends S> Class<R> getParameterType(Class<S> baseClass) {
            return (Class<R>) parameterTypes.get(baseClass);
        }
    }

    protected <R, S> void visitDependency(RuleMethodDataCollector dataCollector, MethodRuleDefinition<R> ruleDefinition, ModelType<S> expectedDependency) {
        List<ModelReference<?>> references = ruleDefinition.getReferences();
        ModelType<? extends S> dependency = null;
        for (ModelReference<?> reference : references) {
            ModelType<? extends S> newDependency = expectedDependency.asSubclass(reference.getType());
            if (newDependency != null) {
                if (dependency != null) {
                    throw new InvalidComponentModelException(String.format("%s method must have one parameter extending %s. Found multiple parameter extending %s.", annotationType.getSimpleName(),
                            expectedDependency.getConcreteClass().getSimpleName(),
                            expectedDependency.getConcreteClass().getSimpleName()));

                }
                dependency = newDependency;
            }
        }

        if (dependency == null) {
            throw new InvalidComponentModelException(String.format("%s method must have one parameter extending %s. Found no parameter extending %s.", annotationType.getSimpleName(),
                    expectedDependency.getConcreteClass().getSimpleName(),
                    expectedDependency.getConcreteClass().getSimpleName()));
        }
        dataCollector.parameterTypes.put(expectedDependency.getConcreteClass(), dependency.getConcreteClass());
    }
}

@@ -1078,24 +1078,72 @@
 Annotation;%0A
+import java.security.InvalidParameterException;%0A
 import java.
@@ -1156,16 +1156,16 @@
 ashMap;%0A
-
 import j
@@ -3182,16 +3182,24 @@
         
+private 
 HashMap%3C
@@ -3452,32 +3452,418 @@
 lass);%0A        %7D
+%0A%0A        public %3CS%3E void put(Class%3CS%3E baseClass, Class%3C? extends S%3E concreteClass) %7B%0A            if(!baseClass.isAssignableFrom(concreteClass))%7B%0A                throw new InvalidParameterException(String.format(%22Class %25s must be assignable from Class %25s%22, baseClass.getName(), concreteClass.getName()));%0A            %7D%0A            parameterTypes.put(baseClass, concreteClass);%0A        %7D
 %0A    %7D%0A%0A    prot
@@ -5204,32 +5204,32 @@
 ()));%0A        %7D%0A
+
         dataColl
@@ -5230,39 +5230,24 @@
 taCollector.
-parameterTypes.
 put(expected

check correct types in RuleMethodDataCollector