package org.jboss.windup.rules.apps.javaee.rules;

import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.ast.java.data.TypeReferenceLocation;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.operation.Iteration;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.config.phase.MigrationRulesPhase;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.rules.apps.java.condition.JavaClass;
import org.jboss.windup.rules.apps.java.model.AbstractJavaSourceModel;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.model.JavaSourceFileModel;
import org.jboss.windup.rules.apps.java.scan.ast.JavaTypeReferenceModel;
import org.jboss.windup.rules.apps.java.service.JavaClassService;
import org.jboss.windup.rules.apps.javaee.model.RMIServiceModel;
import org.jboss.windup.rules.apps.javaee.service.RMIServiceModelService;
import org.jboss.windup.util.Logging;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;

/**
 * Scans for classes with JAX-WS related annotations, and adds JAX-WS related metadata for these.
 *
 * @author <a href="mailto:bradsdavis@gmail.com">Brad Davis</a>
 */
@RuleMetadata(phase = MigrationRulesPhase.class)
public class DiscoverRmiRuleProvider extends AbstractRuleProvider
{
    private static final Logger LOG = Logging.get(DiscoverRmiRuleProvider.class);
    private static final String RMI_INHERITANCE = "rmiInheritance";

    @Override
    public Configuration getConfiguration(GraphContext context)
    {
        String ruleIDPrefix = getClass().getSimpleName();
        return ConfigurationBuilder
        .begin()
        .addRule()
        .when(JavaClass
                    .references("java.rmi.Remote")
                    .at(TypeReferenceLocation.IMPORT)
                    .as(RMI_INHERITANCE))
        .perform(Iteration.over(RMI_INHERITANCE).perform(new AbstractIterationOperation<JavaTypeReferenceModel>()
        {
            @Override
            public void perform(GraphRewrite event, EvaluationContext context, JavaTypeReferenceModel payload)
            {
                extractMetadata(event, payload);
            }
        }).endIteration())
        .withId(ruleIDPrefix + "_RMIInheritanceRule");
    }

    private void extractMetadata(GraphRewrite event, JavaTypeReferenceModel typeReference)
    {
        // get the rmi interface class from the graph
        JavaClassModel javaClassModel = getJavaClass(typeReference);

        if (!isRemoteInterface(javaClassModel))
        {
            LOG.warning("Is not remote: " + javaClassModel.getQualifiedName());
            return;
        }

        LOG.info("Processing: " + typeReference);
        // Make sure we create a source report for the interface source
        typeReference.getFile().setGenerateSourceReport(true);

        RMIServiceModelService rmiService = new RMIServiceModelService(event.getGraphContext());

        if (javaClassModel != null)
        {
            RMIServiceModel rmiServiceModel = rmiService.getOrCreate(typeReference.getFile().getApplication(), javaClassModel);

            // Create the source report for the RMI Implementation.
            JavaClassService javaClassService = new JavaClassService(event.getGraphContext());

            if (rmiServiceModel != null && rmiServiceModel.getImplementationClass() != null) {
                for (JavaSourceFileModel source : javaClassService.getJavaSource(rmiServiceModel.getImplementationClass().getQualifiedName()))
                {
                    source.setGenerateSourceReport(true);
                }
            }
        }
    }

    public boolean isRemoteInterface(JavaClassModel jcm)
    {
        if (!jcm.isInterface())
            return false;

        LOG.info("Class: " + jcm.getQualifiedName());
        for (JavaClassModel im : jcm.getInterfaces())
        {
            if (StringUtils.equals("java.rmi.Remote", im.getQualifiedName()))
            {
                return true;
            }
            LOG.info(" - Implements: " + im.getQualifiedName());
        }
        if (jcm.getExtends() != null)
        {
            LOG.info(" - Extends: " + jcm.getExtends().getQualifiedName());
            return (StringUtils.equals("java.rmi.Remote", jcm.getExtends().getQualifiedName()));
        }
        return false;
    }

    private JavaClassModel getJavaClass(JavaTypeReferenceModel javaTypeReference)
    {
        JavaClassModel result = null;
        AbstractJavaSourceModel javaSource = javaTypeReference.getFile();

        for (JavaClassModel javaClassModel : javaSource.getJavaClasses())
        {
            // there can be only one public one, and the annotated class should be public
            if (javaClassModel.isPublic() != null && javaClassModel.isPublic())
            {
                result = javaClassModel;
                break;
            }
        }

        if (result == null)
        {
            // no public classes found, so try to find any class (even non-public ones)
            result = javaSource.getJavaClasses().iterator().next();
        }
        return result;
    }

    @Override
    public String toString()
    {
        return "DiscoverEJBAnnotatedClasses";
    }
}