/*
 * 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.language.base.sources;

import org.gradle.api.Incubating;
import org.gradle.api.internal.file.SourceDirectorySetFactory;
import org.gradle.internal.reflect.DirectInstantiator;
import org.gradle.api.reflect.ObjectInstantiationException;
import org.gradle.language.base.LanguageSourceSet;
import org.gradle.language.base.internal.AbstractLanguageSourceSet;
import org.gradle.platform.base.ModelInstantiationException;
import org.gradle.platform.base.internal.ComponentSpecIdentifier;

/**
 * Base class that may be used for custom {@link LanguageSourceSet} implementations. However, it is generally better to use an
 * interface annotated with {@link org.gradle.model.Managed} and not use an implementation class at all.
 */
@Incubating
public class BaseLanguageSourceSet extends AbstractLanguageSourceSet {
    // This is here as a convenience for subclasses to create additional SourceDirectorySets
    protected final SourceDirectorySetFactory sourceDirectorySetFactory;

    private static final ThreadLocal<SourceSetInfo> NEXT_SOURCE_SET_INFO = new ThreadLocal<SourceSetInfo>();

    public static <T extends LanguageSourceSet> T create(Class<? extends LanguageSourceSet> publicType, Class<T> implementationType, ComponentSpecIdentifier componentId, SourceDirectorySetFactory sourceDirectorySetFactory) {
        NEXT_SOURCE_SET_INFO.set(new SourceSetInfo(componentId, publicType, sourceDirectorySetFactory));
        try {
            try {
                return DirectInstantiator.INSTANCE.newInstance(implementationType);
            } catch (ObjectInstantiationException e) {
                throw new ModelInstantiationException(String.format("Could not create LanguageSourceSet of type %s", publicType.getSimpleName()), e.getCause());
            }
        } finally {
            NEXT_SOURCE_SET_INFO.set(null);
        }
    }

    public BaseLanguageSourceSet() {
        this(NEXT_SOURCE_SET_INFO.get());
    }

    private BaseLanguageSourceSet(SourceSetInfo info) {
        super(validate(info).identifier, info.publicType, info.sourceDirectorySetFactory.create("source"));
        this.sourceDirectorySetFactory = info.sourceDirectorySetFactory;
    }

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

    private static class SourceSetInfo {
        private final ComponentSpecIdentifier identifier;
        private final Class<? extends LanguageSourceSet> publicType;
        final SourceDirectorySetFactory sourceDirectorySetFactory;

        private SourceSetInfo(ComponentSpecIdentifier identifier, Class<? extends LanguageSourceSet> publicType, SourceDirectorySetFactory sourceDirectorySetFactory) {
            this.identifier = identifier;
            this.publicType = publicType;
            this.sourceDirectorySetFactory = sourceDirectorySetFactory;
        }
    }
}
