/**
 * 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.experiment.generators;

import com.sun.codemodel.*;
import org.androidtransfuse.TransfuseAnalysisException;
import org.androidtransfuse.adapter.ASTMethod;
import org.androidtransfuse.adapter.ASTParameter;
import org.androidtransfuse.adapter.ASTType;
import org.androidtransfuse.adapter.element.ASTElementFactory;
import org.androidtransfuse.adapter.element.ASTTypeBuilderVisitor;
import org.androidtransfuse.analysis.AnalysisContext;
import org.androidtransfuse.analysis.InjectionPointFactory;
import org.androidtransfuse.annotations.FragmentLayoutHandler;
import org.androidtransfuse.annotations.Layout;
import org.androidtransfuse.experiment.*;
import org.androidtransfuse.gen.ClassGenerationUtil;
import org.androidtransfuse.gen.InjectionFragmentGenerator;
import org.androidtransfuse.gen.InstantiationStrategyFactory;
import org.androidtransfuse.gen.UniqueVariableNamer;
import org.androidtransfuse.gen.variableBuilder.InjectionBindingBuilder;
import org.androidtransfuse.layout.FragmentLayoutHandlerDelegate;
import org.androidtransfuse.model.InjectionNode;
import org.androidtransfuse.model.MethodDescriptor;
import org.androidtransfuse.model.TypedExpression;
import org.androidtransfuse.model.r.RResourceReferenceBuilder;
import org.androidtransfuse.util.AndroidLiterals;
import org.androidtransfuse.validation.Validator;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.lang.model.type.TypeMirror;
import java.util.Map;

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

/**
 * @author John Ericksen
 */
public class FragmentLayoutGenerator implements Generation {

    private final RResourceReferenceBuilder rResourceReferenceBuilder;
    private final ASTElementFactory astElementFactory;
    private final UniqueVariableNamer namer;
    private final ClassGenerationUtil generationUtil;
    private final InjectionBindingBuilder injectionBindingBuilder;
    private final InjectionFragmentGenerator injectionFragmentGenerator;
    private final InstantiationStrategyFactory instantiationStrategyFactory;
    private final InjectionPointFactory injectionPointFactory;
    private final Provider<ASTTypeBuilderVisitor> astTypeBuilderVisitorProvider;
    private final Validator validator;

    @Inject
    public FragmentLayoutGenerator(RResourceReferenceBuilder rResourceReferenceBuilder,
                                   ASTElementFactory astElementFactory,
                                   UniqueVariableNamer namer,
                                   ClassGenerationUtil generationUtil,
                                   InjectionBindingBuilder injectionBindingBuilder,
                                   InjectionFragmentGenerator injectionFragmentGenerator,
                                   InstantiationStrategyFactory instantiationStrategyFactory,
                                   InjectionPointFactory injectionPointFactory,
                                   Provider<ASTTypeBuilderVisitor> astTypeBuilderVisitorProvider,
                                   Validator validator) {
        this.rResourceReferenceBuilder = rResourceReferenceBuilder;
        this.astElementFactory = astElementFactory;
        this.namer = namer;
        this.generationUtil = generationUtil;
        this.injectionBindingBuilder = injectionBindingBuilder;
        this.injectionFragmentGenerator = injectionFragmentGenerator;
        this.instantiationStrategyFactory = instantiationStrategyFactory;
        this.injectionPointFactory = injectionPointFactory;
        this.astTypeBuilderVisitorProvider = astTypeBuilderVisitorProvider;
        this.validator = validator;
    }

    @Override
    public String getName() {
        return "Fragment Layout Generator";
    }

    @Override
    public void schedule(final ComponentBuilder builder, final ComponentDescriptor descriptor) {

        final InjectionNode layoutHandlerInjectionNode;
        ASTType target = descriptor.getTarget();

        if(target.isAnnotated(FragmentLayoutHandler.class)) {
            FragmentLayoutHandler layoutHandlerAnnotation = target.getAnnotation(FragmentLayoutHandler.class);
            layoutHandlerInjectionNode = buildLayoutHandlerInjectionNode(layoutHandlerAnnotation, builder.getAnalysisContext());
        }
        else {
            layoutHandlerInjectionNode = null;
        }

        final ASTMethod onCreateViewMethod = astElementFactory.findMethod(AndroidLiterals.FRAGMENT, "onCreateView", AndroidLiterals.LAYOUT_INFLATER, AndroidLiterals.VIEW_GROUP, AndroidLiterals.BUNDLE);
        builder.add(onCreateViewMethod, GenerationPhase.POSTSCOPES, new ComponentMethodGenerator() {
            @Override
            public void generate(MethodDescriptor methodDescriptor, JBlock block) {
                ASTType target = descriptor.getTarget();
                final JVar viewDeclaration = builder.getDefinedClass().field(JMod.PRIVATE, generationUtil.ref(AndroidLiterals.VIEW), namer.generateName(AndroidLiterals.VIEW));
                builder.getAnalysisContext().getInjectionNodeBuilders().putType(AndroidLiterals.VIEW, injectionBindingBuilder.buildExpression(new TypedExpression(AndroidLiterals.VIEW, viewDeclaration)));

                JBlock isNullconditionalBlock = block._if(viewDeclaration.eq(JExpr._null()))._then();

                methodDescriptor.pushBody(isNullconditionalBlock);

                boolean handled = false;

                if (target.isAnnotated(Layout.class)) {
                    Layout layoutAnnotation = target.getAnnotation(Layout.class);

                    Integer layout = layoutAnnotation == null ? null : layoutAnnotation.value();

                    isNullconditionalBlock.assign(viewDeclaration, methodDescriptor.getExpression(AndroidLiterals.LAYOUT_INFLATER).getExpression()
                            .invoke("inflate")
                            .arg(rResourceReferenceBuilder.buildReference(layout))
                            .arg(methodDescriptor.getExpression(AndroidLiterals.VIEW_GROUP).getExpression())
                            .arg(JExpr.lit(false)));

                    handled = true;
                }

                if (layoutHandlerInjectionNode != null) {
                    if(handled){
                        validator.error("Layout is already defined with @Layout")
                                .element(target)
                                .annotation(target.getASTAnnotation(FragmentLayoutHandler.class))
                                .build();
                    }
                    try {
                        Map<InjectionNode, TypedExpression> expressionMap = injectionFragmentGenerator.buildFragment(isNullconditionalBlock,
                                instantiationStrategyFactory.buildMethodStrategy(isNullconditionalBlock, builder.getScopes()),
                                builder.getDefinedClass(),
                                layoutHandlerInjectionNode,
                                builder.getScopes());

                        //FragmentLayoutHandlerDelegate.invokeLayout(layoutInflater, viewGroup)
                        JExpression layoutHandlerDelegate = expressionMap.get(layoutHandlerInjectionNode).getExpression();

                        isNullconditionalBlock.invoke(layoutHandlerDelegate, FragmentLayoutHandlerDelegate.INVOKE_LAYOUT_METHOD)
                            .arg(methodDescriptor.getParameter(methodDescriptor.getASTMethod().getParameters().get(0)).getExpression())
                            .arg(methodDescriptor.getParameter(methodDescriptor.getASTMethod().getParameters().get(1)).getExpression());


                    } catch (JClassAlreadyExistsException e) {
                        throw new TransfuseAnalysisException("Class Already Exists ", e);
                    }


                    handled = true;
                }

                if (!handled) {
                    JInvocation onCreateView = JExpr._super().invoke("onCreateView");

                    for (ASTParameter astParameter : methodDescriptor.getASTMethod().getParameters()) {
                        onCreateView.arg(methodDescriptor.getExpression(astParameter.getASTType()).getExpression());
                    }

                    isNullconditionalBlock.assign(viewDeclaration, onCreateView);
                }

                builder.add(onCreateViewMethod, GenerationPhase.RETURN, new ComponentMethodGenerator() {
                    @Override
                    public void generate(MethodDescriptor methodDescriptor, JBlock block) {
                        methodDescriptor.popBody();
                        methodDescriptor.getBody()._return(viewDeclaration);
                    }
                });
            }
        });
    }

    private InjectionNode buildLayoutHandlerInjectionNode(final FragmentLayoutHandler layoutHandlerAnnotation, AnalysisContext context) {
        if (layoutHandlerAnnotation != null) {
            TypeMirror layoutHandlerType = getTypeMirror(layoutHandlerAnnotation, "value");

            if (layoutHandlerType != null) {
                ASTType layoutHandlerASTType = layoutHandlerType.accept(astTypeBuilderVisitorProvider.get(), null);
                return injectionPointFactory.buildInjectionNode(layoutHandlerASTType, context);
            }
        }
        return null;
    }
}
