package autodagger.compiler;

import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.AnnotationSpec;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import autodagger.AutoSubcomponent;
import autodagger.compiler.utils.AutoComponentClassNameUtil;
import dagger.Subcomponent;
import processorworkflow.AbstractComposer;
import processorworkflow.AbstractProcessing;
import processorworkflow.Errors;
import processorworkflow.ProcessingBuilder;

/**
 * @author Lukasz Piliszczuk - [email protected]
 */
public class SubcomponentProcessing extends AbstractProcessing<SubcomponentSpec, State> {

    /**
     * Build all extractors first, then build all builders, because
     * we want to gather all addsTo that can be related to other annoted auto subcomponents
     */
    private final Set<SubcomponentExtractor> extractors = new HashSet<>();

    public SubcomponentProcessing(Elements elements, Types types, Errors errors, State state) {
        super(elements, types, errors, state);
    }

    @Override
    public Set<Class<? extends Annotation>> supportedAnnotations() {
        Set set = ImmutableSet.of(AutoSubcomponent.class, Subcomponent.class);
        return set;
    }

    @Override
    protected void processElements(Set<? extends Element> annotationElements) {
        super.processElements(annotationElements);
        if (errors.hasErrors()) {
            return;
        }

        processExtractors();
    }

    @Override
    public boolean processElement(Element element, Errors.ElementErrors elementErrors) {
        SubcomponentExtractor extractor = new SubcomponentExtractor(element, types, elements, errors);
        if (errors.hasErrors()) {
            return false;
        }

        extractors.add(extractor);

        if (!extractor.getModulesTypeMirrors().isEmpty()) {
            state.addSubcomponentModule(element.asType(), extractor.getModulesTypeMirrors());
        }

        return true;
    }

    private void processExtractors() {
        for (SubcomponentExtractor extractor : extractors) {
            SubcomponentSpec spec = new Builder(extractor, errors).build();
            if (errors.hasErrors()) {
                return;
            }

            specs.add(spec);
        }
    }

    @Override
    public AbstractComposer<SubcomponentSpec> createComposer() {
        return new SubcomponentComposer(specs);
    }

    private class Builder extends ProcessingBuilder<SubcomponentExtractor, SubcomponentSpec> {

        public Builder(SubcomponentExtractor extractor, Errors errors) {
            super(extractor, errors);
        }

        @Override
        protected SubcomponentSpec build() {
            SubcomponentSpec subcomponentSpec = new SubcomponentSpec(AutoComponentClassNameUtil.getComponentClassName(extractor.getElement()));

            if (extractor.getScopeAnnotationTypeMirror() != null) {
                subcomponentSpec.setScopeAnnotationSpec(AnnotationSpec.get(extractor.getScopeAnnotationTypeMirror()));
            }

            // modules
            subcomponentSpec.setModulesTypeNames(ProcessingUtil.getTypeNames(extractor.getModulesTypeMirrors()));

            // superinterfaces
            subcomponentSpec.setSuperinterfacesTypeNames(ProcessingUtil.getTypeNames(extractor.getSuperinterfacesTypeMirrors()));

            // exposed
            subcomponentSpec.setExposeSpecs(ProcessingUtil.getAdditions(extractor.getElement(), state.getExposeExtractors()));

            // injector
            subcomponentSpec.setInjectorSpecs(ProcessingUtil.getAdditions(extractor.getElement(), state.getInjectorExtractors()));

            return subcomponentSpec;
        }
    }
}