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

import java.util.Map;
import java.util.logging.Logger;
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.GraphOperation;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.config.phase.ArchiveExtractionPhase;
import org.jboss.windup.config.phase.ArchiveMetadataExtractionPhase;
import org.jboss.windup.config.phase.DependentPhase;
import org.jboss.windup.config.phase.DiscoverProjectStructurePhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.rules.apps.java.archives.config.ArchiveIdentificationConfigLoadingRuleProvider;
import org.jboss.windup.rules.apps.java.archives.model.ArchiveCoordinateModel;
import org.jboss.windup.rules.apps.java.archives.model.IdentifiedArchiveModel;
import org.jboss.windup.rules.apps.java.condition.SourceMode;
import org.jboss.windup.rules.apps.java.scan.provider.DiscoverMavenHierarchyRuleProvider;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.WindupException;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;


/**
 * Creates a stub of Maven project structure, including pom.xml's and the proper directory structure and dependencies,
 * based on the project structure determined by prior Windup rules (nested deployments) and the libraries included in them.
 *
 * @author <a href="http://ondra.zizka.cz/">Ondrej Zizka, ozizka at seznam.cz</a>
 */
@RuleMetadata(after = {
    ArchiveMetadataExtractionPhase.class,
    ArchiveIdentificationConfigLoadingRuleProvider.class,
    ArchiveExtractionPhase.class,
    DiscoverMavenHierarchyRuleProvider.class,
    DiscoverProjectStructurePhase.class
}, phase = DependentPhase.class)
public class MavenizeRuleProvider extends AbstractRuleProvider
{
    private static final Logger LOG = Logging.get(MavenizeRuleProvider.class);
    public static final MavenCoord JBOSS_PARENT = new MavenCoord("org.jboss", "jboss-parent", "20");
    public static final MavenCoord JBOSS_BOM_JAVAEE6_WITH_ALL = new MavenCoord("org.jboss.bom", "jboss-javaee-6.0-with-all", "1.0.7.Final");
    public static final MavenCoord JBOSS_BOM_JAVAEE7_WITH_ALL = new MavenCoord("org.jboss.bom", "wildfly-javaee7-with-tools", "10.0.1.Final");

    // @formatter:off
    @Override
    public Configuration getConfiguration(GraphContext grCtx)
    {
        ConditionBuilder applicationProjectModels = Query.fromType(WindupConfigurationModel.class);

        return ConfigurationBuilder.begin()
        // Create the BOM frame
        .addRule()
        .perform(new GraphOperation() {
            public void perform(GraphRewrite event, EvaluationContext context) {
                if (!isPerformMavenization(event.getGraphContext()))
                    return;

                GlobalBomModel bom = event.getGraphContext().getFramed().addVertex(null, GlobalBomModel.class);
                ArchiveCoordinateModel jbossParent = event.getGraphContext().getFramed().addVertex(null, ArchiveCoordinateModel.class);
                copyTo(JBOSS_PARENT, jbossParent);
                bom.setParent(jbossParent);
            }
        })
        .withId("Mavenize-BOM-data-collection")

        // For each IdentifiedArchive, add it to the global BOM.
        .addRule()
        .when(Query.fromType(IdentifiedArchiveModel.class))
        .perform(new MavenizePutNewerVersionToGlobalBomOperation())
        .withId("Mavenize-BOM-file-creation")

        // For each application given to Windup as input, mavenize it.
        .addRule()
        .when(applicationProjectModels, SourceMode.isDisabled())
        .perform(new MavenizeApplicationOperation())
        .withId("Mavenize-projects-mavenization")
        ;
    }
    // @formatter:on


    /**
     * This operation puts the given IdentifiedArchiveModel to the global BOM frame.
     * If there's already one of such G:A:P, then the newer version is used.
     * Eventual version collisions are overridden in pom.xml's.
     */
    class MavenizePutNewerVersionToGlobalBomOperation extends AbstractIterationOperation<IdentifiedArchiveModel>
    {
        @Override
        public void perform(GraphRewrite event, EvaluationContext context, IdentifiedArchiveModel archive)
        {
            if (!isPerformMavenization(event.getGraphContext()))
                return;

            if (archive.getCoordinate() == null)
            {
                LOG.info("Warning: archive.getCoordinate() is null: " + archive.toPrettyString());
                return;
            }

            LOG.info("Adding to global BOM: " + archive.getCoordinate().toPrettyString());
            // BOM
            GraphService<GlobalBomModel> bomServ = new GraphService<>(event.getGraphContext(), GlobalBomModel.class);
            GlobalBomModel bom = bomServ.getUnique();

            // Check for an existing coord, add the new one
            bom.addNewerDependency(archive.getCoordinate());
        }
    }

    /**
     * Create a stub of Maven project structure, including pom.xml's and the proper directory structure and dependencies,
     * based on the project structure determined by prior Windup rules (nested deployments) and the libraries included in them.
     */
    private class MavenizeApplicationOperation extends AbstractIterationOperation<WindupConfigurationModel>
    {
        public MavenizeApplicationOperation()
        {
        }

        @Override
        public void perform(GraphRewrite event, EvaluationContext evalContext, WindupConfigurationModel config)
        {
            if (!isPerformMavenization(event.getGraphContext()))
                return;

            for (FileModel inputPath : config.getInputPaths())
            {
                ProjectModel projectModel = inputPath.getProjectModel();
                if (projectModel == null)
                    throw new WindupException("Error, no project found in: " + inputPath.getFilePath());

                new MavenizationService(event.getGraphContext()).mavenizeApp(projectModel);
            }
        }
    }


    private static void copyTo(MavenCoord from, ArchiveCoordinateModel to)
    {
        to.setArtifactId(from.getArtifactId());
        to.setGroupId(from.getGroupId());
        to.setVersion(from.getVersion());
        to.setClassifier(from.getClassifier());
        to.setPackaging(from.getPackaging());
    }

    /**
     * @return the value of the option with given name. null if the value was null.
     * @throws IllegalStateException if the value is not Boolean.
     */
    public static Boolean getBooleanOption(GraphContext graphContext, String name)
    {
        Map<String, Object> options = graphContext.getOptionMap();
        final Object value = options.get(name);
        if ( value != null && !(value instanceof Boolean))
            throw new IllegalStateException("Option value expected to be Boolean, but was: " + value.getClass());
        return (Boolean) options.get(name);
    }

    /**
     * @return the boolean value of the option with given name. Given default_ value if the value was null.
     * @throws IllegalStateException if the value is not Boolean.
     */
    public static boolean getBooleanOption(GraphContext graphContext, String name, boolean default_)
    {
        Boolean val = getBooleanOption(graphContext, name);
        if (val == null)
            return default_;
        return val;
    }

    private static boolean isPerformMavenization(GraphContext graphContext)
    {
        return getBooleanOption(graphContext, MavenizeOption.NAME, false);
    }
}
