/**
 * Copyright 2011-2015 John Ericksen
 *
 * 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.androidtransfuse.analysis;

import com.sun.codemodel.JBlock;
import com.sun.codemodel.JExpr;
import org.androidtransfuse.adapter.ASTMethod;
import org.androidtransfuse.adapter.ASTType;
import org.androidtransfuse.adapter.PackageClass;
import org.androidtransfuse.adapter.element.ASTElementFactory;
import org.androidtransfuse.adapter.element.ASTTypeBuilderVisitor;
import org.androidtransfuse.analysis.repository.InjectionNodeBuilderRepository;
import org.androidtransfuse.analysis.repository.InjectionNodeBuilderRepositoryFactory;
import org.androidtransfuse.annotations.Service;
import org.androidtransfuse.experiment.*;
import org.androidtransfuse.experiment.generators.IntentFactoryGenerator;
import org.androidtransfuse.experiment.generators.ObservesExpressionGenerator;
import org.androidtransfuse.experiment.generators.OnCreateInjectionGenerator;
import org.androidtransfuse.experiment.generators.ServiceManifestEntryGenerator;
import org.androidtransfuse.gen.GeneratorFactory;
import org.androidtransfuse.gen.componentBuilder.ListenerRegistrationGenerator;
import org.androidtransfuse.gen.variableBuilder.InjectionBindingBuilder;
import org.androidtransfuse.intentFactory.ServiceIntentFactoryStrategy;
import org.androidtransfuse.model.MethodDescriptor;
import org.androidtransfuse.util.AndroidLiterals;

import javax.inject.Inject;
import javax.lang.model.type.TypeMirror;

import static org.androidtransfuse.util.TypeMirrorUtil.getTypeMirror;

/**
 * Service related Analysis
 *
 * @author John Ericksen
 */
public class ServiceAnalysis implements Analysis<ComponentDescriptor> {

    private final InjectionNodeBuilderRepositoryFactory injectionNodeBuilderRepositoryFactory;
    private final AnalysisContextFactory analysisContextFactory;
    private final ASTElementFactory astElementFactory;
    private final InjectionBindingBuilder injectionBindingBuilder;
    private final ASTTypeBuilderVisitor astTypeBuilderVisitor;
    private final GeneratorFactory generatorFactory;
    private final ListenerRegistrationGenerator.ListerRegistrationGeneratorFactory listenerRegistrationGeneratorFactory;
    private final ObservesExpressionGenerator.ObservesExpressionGeneratorFactory observesExpressionDecoratorFactory;
    private final ServiceManifestEntryGenerator serviceManifestEntryGenerator;
    private final OnCreateInjectionGenerator.InjectionGeneratorFactory onCreateInjectionGeneratorFactory;
    private final ScopesGeneration.ScopesGenerationFactory scopesGenerationFactory;
    private final ComponentAnalysis componentAnalysis;
    private final IntentFactoryGenerator intentFactoryGenerator;

    @Inject
    public ServiceAnalysis(InjectionNodeBuilderRepositoryFactory injectionNodeBuilderRepositoryFactory,
                           AnalysisContextFactory analysisContextFactory,
                           ASTElementFactory astElementFactory,
                           InjectionBindingBuilder injectionBindingBuilder,
                           ASTTypeBuilderVisitor astTypeBuilderVisitor,
                           GeneratorFactory generatorFactory,
                           ListenerRegistrationGenerator.ListerRegistrationGeneratorFactory listenerRegistrationGeneratorFactory,
                           ObservesExpressionGenerator.ObservesExpressionGeneratorFactory observesExpressionDecoratorFactory,
                           ServiceManifestEntryGenerator serviceManifestEntryGenerator,
                           OnCreateInjectionGenerator.InjectionGeneratorFactory onCreateInjectionGeneratorFactory,
                           ScopesGeneration.ScopesGenerationFactory scopesGenerationFactory,
                           ComponentAnalysis componentAnalysis,
                           IntentFactoryGenerator intentFactoryGenerator) {
        this.injectionNodeBuilderRepositoryFactory = injectionNodeBuilderRepositoryFactory;
        this.analysisContextFactory = analysisContextFactory;
        this.astElementFactory = astElementFactory;
        this.injectionBindingBuilder = injectionBindingBuilder;
        this.astTypeBuilderVisitor = astTypeBuilderVisitor;
        this.generatorFactory = generatorFactory;
        this.listenerRegistrationGeneratorFactory = listenerRegistrationGeneratorFactory;
        this.observesExpressionDecoratorFactory = observesExpressionDecoratorFactory;
        this.serviceManifestEntryGenerator = serviceManifestEntryGenerator;
        this.onCreateInjectionGeneratorFactory = onCreateInjectionGeneratorFactory;
        this.scopesGenerationFactory = scopesGenerationFactory;
        this.componentAnalysis = componentAnalysis;
        this.intentFactoryGenerator = intentFactoryGenerator;
    }

    public ComponentDescriptor analyze(ASTType input) {

        Service serviceAnnotation = input.getAnnotation(Service.class);
        PackageClass serviceClassName;
        ComponentDescriptor serviceDescriptor = null;

        if (input.inherits(AndroidLiterals.SERVICE)) {
            //vanilla Android Service
            PackageClass packageClass = input.getPackageClass();
            serviceClassName = componentAnalysis.buildComponentPackageClass(input, packageClass.getClassName(), "Service");

            serviceDescriptor = new ComponentDescriptor(input, null, packageClass);
        } else {
            //generated Android Service
            serviceClassName = componentAnalysis.buildComponentPackageClass(input, serviceAnnotation.name(), "Service");

            TypeMirror type = getTypeMirror(serviceAnnotation, "type");

            ASTType serviceType = type == null || type.toString().equals("java.lang.Object") ? AndroidLiterals.SERVICE : type.accept(astTypeBuilderVisitor, null);

            AnalysisContext context = analysisContextFactory.buildAnalysisContext(buildVariableBuilderMap(serviceType));

            serviceDescriptor = new ComponentDescriptor(input, serviceType, serviceClassName, context);

            componentAnalysis.setupGenerators(serviceDescriptor, serviceType, Service.class);

            serviceDescriptor.getGenerators().add(onCreateInjectionGeneratorFactory.build(getASTMethod("onCreate"), input));
            serviceDescriptor.getGenerators().add(scopesGenerationFactory.build(getASTMethod("onCreate")));
            serviceDescriptor.getGenerators().add(new OnBindGenerator());
            serviceDescriptor.getGenerators().add(listenerRegistrationGeneratorFactory.build(getASTMethod("onCreate")));
            serviceDescriptor.getGenerators().add(observesExpressionDecoratorFactory.build(
                    getASTMethod("onCreate"),
                    getASTMethod("onDestroy"),
                    getASTMethod("onCreate"),
                    getASTMethod("onDestroy")
            ));
            serviceDescriptor.getGenerators().add(generatorFactory.buildStrategyGenerator(ServiceIntentFactoryStrategy.class));
        }

        serviceDescriptor.getGenerators().add(serviceManifestEntryGenerator);
        serviceDescriptor.getGenerators().add(intentFactoryGenerator);

        return serviceDescriptor;
    }

    private ASTMethod getASTMethod(String methodName, ASTType... args) {
        return getASTMethod(AndroidLiterals.SERVICE, methodName, args);
    }

    private ASTMethod getASTMethod(ASTType type, String methodName, ASTType... args) {
        return astElementFactory.findMethod(type, methodName, args);
    }

    private InjectionNodeBuilderRepository buildVariableBuilderMap(ASTType serviceType) {

        InjectionNodeBuilderRepository injectionNodeBuilderRepository = componentAnalysis.setupInjectionNodeBuilderRepository(serviceType, Service.class);

        injectionNodeBuilderRepository.putType(AndroidLiterals.CONTEXT, injectionBindingBuilder.buildThis(AndroidLiterals.CONTEXT));

        injectionNodeBuilderRepository.putType(AndroidLiterals.SERVICE, injectionBindingBuilder.buildThis(AndroidLiterals.SERVICE));

        while(!serviceType.equals(AndroidLiterals.SERVICE) && serviceType.inherits(AndroidLiterals.SERVICE)){
            injectionNodeBuilderRepository.putType(serviceType, injectionBindingBuilder.buildThis(serviceType));
            serviceType = serviceType.getSuperClass();
        }

        injectionNodeBuilderRepository.addRepository(injectionNodeBuilderRepositoryFactory.buildModuleConfiguration());

        return injectionNodeBuilderRepository;

    }

    private final class OnBindGenerator implements Generation {
        @Override
        public void schedule(ComponentBuilder builder, ComponentDescriptor descriptor) {
            builder.add(getASTMethod("onBind", AndroidLiterals.INTENT), GenerationPhase.INIT, new ComponentMethodGenerator() {
                @Override
                public void generate(MethodDescriptor methodDescriptor, JBlock block) {
                    block._return(JExpr._null());
                }
            });
        }

        @Override
        public String getName() {
            return "Service OnBind Generator";
        }
    }
}