/*
 * Copyright 2016 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.component.internal;

import org.gradle.internal.reflect.DirectInstantiator;
import org.gradle.internal.reflect.ObjectInstantiationException;
import org.gradle.model.internal.core.MutableModelNode;
import org.gradle.platform.base.ComponentSpec;
import org.gradle.platform.base.ModelInstantiationException;
import org.gradle.platform.base.internal.ComponentSpecIdentifier;

public class DefaultComponentSpec extends AbstractComponentSpec {
    private static final ThreadLocal<ComponentInfo> NEXT_COMPONENT_INFO = new ThreadLocal<ComponentInfo>();

    public static <T extends DefaultComponentSpec> T create(Class<? extends ComponentSpec> publicType, Class<T> implementationType, ComponentSpecIdentifier identifier, MutableModelNode modelNode) {
        NEXT_COMPONENT_INFO.set(new ComponentInfo(identifier, modelNode, publicType));
        try {
            try {
                return DirectInstantiator.INSTANCE.newInstance(implementationType);
            } catch (ObjectInstantiationException e) {
                throw new ModelInstantiationException(String.format("Could not create component of type %s", publicType.getSimpleName()), e.getCause());
            }
        } finally {
            NEXT_COMPONENT_INFO.set(null);
        }
    }

    protected static ComponentInfo getInfo() {
        return NEXT_COMPONENT_INFO.get();
    }

    public DefaultComponentSpec() {
        this(getInfo());
    }

    public DefaultComponentSpec(ComponentInfo info) {
        super(validate(info).componentIdentifier, info.publicType);
    }

    private static ComponentInfo validate(ComponentInfo info) {
        if (info == null) {
            throw new ModelInstantiationException("Direct instantiation of a BaseComponentSpec is not permitted. Use a @ComponentType rule instead.");
        }
        return info;
    }

    protected static class ComponentInfo {
        public final ComponentSpecIdentifier componentIdentifier;
        public final MutableModelNode modelNode;
        public final Class<?> publicType;

        private ComponentInfo(
            ComponentSpecIdentifier componentIdentifier,
            MutableModelNode modelNode,
            Class<?> publicType
        ) {
            this.componentIdentifier = componentIdentifier;
            this.modelNode = modelNode;
            this.publicType = publicType;
        }
    }
}
