package org.jboss.windup.config.phase;

import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.RuleProvider;
import org.jboss.windup.config.metadata.MetadataBuilder;
import org.jboss.windup.config.metadata.RuleProviderMetadata;
import org.jboss.windup.graph.GraphContext;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.config.Rule;

/**
 * Provides a shorthand for stating the order of execution of {@link Rule}s within Windup. When a {@link AbstractRuleProvider} specifies a
 * {@link RulePhase}, the results of calling {@link RulePhase#getExecuteAfter()} and {@link RulePhase#getExecuteBefore()} on the rule will appended to
 * the results of {@link RuleProviderMetadata#getExecuteAfter()} and {@link RulePhase#getExecuteBefore()}.
 *
 * In this way, most {@link AbstractRuleProvider}s will be able to simply specify a {@link RulePhase} in order to determine their approximate
 * placement within the execution lifecycle.
 * 
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
public abstract class RulePhase extends AbstractRuleProvider
{
    private final Class<? extends RulePhase> thisPhase;
    private Class<? extends RulePhase> previousPhase;
    private Class<? extends RulePhase> nextPhase;

    public RulePhase(Class<? extends RulePhase> implementationType)
    {
        super(implementationType, implementationType.getSimpleName());
        this.thisPhase = implementationType;
    }

    /**
     * This will always return an empty {@link Configuration} as these are just barriers that do not provide additional
     * {@link Rule}s.
     */
    @Override
    public final Configuration getConfiguration(GraphContext context)
    {
        return ConfigurationBuilder.begin();
    }

    @Override
    public final RuleProviderMetadata getMetadata()
    {
        return MetadataBuilder.forProvider(thisPhase, thisPhase.getSimpleName())

                    /**
                     * We set {@link DependentPhase} because it has no executeBefore/executeAfter conditions of its own.
                     * The phase should provide it's own dependencies and not a recursive call to "getPhase()", so
                     * returning a value that has no dependencies of its own is necessary here.
                     */
                    .setPhase(DependentPhase.class)
                    .addExecuteAfter(_getExecuteAfter())
                    .addExecuteBefore(_getExecuteBefore());
    }

    private Class<? extends RuleProvider> _getExecuteBefore()
    {
        return nextPhase == null ? getExecuteBefore() : nextPhase;
    }

    private Class<? extends RuleProvider> _getExecuteAfter()
    {
        return previousPhase == null ? getExecuteAfter() : previousPhase;
    }

    /**
     * This allows the {@link RulePhase} implementation to specify the {@link RulePhase} implementation that it should
     * be executed after.
     */
    public abstract Class<? extends RulePhase> getExecuteAfter();

    /**
     * This allows the {@link RulePhase} implementation to specify the {@link RulePhase} implementation that it should
     * be executed before.
     */
    public abstract Class<? extends RulePhase> getExecuteBefore();

    /**
     * This allows the sorter to link {@link RulePhase}s to each other, without them having to explicitly specify both
     * the executeAfter and executeBefore.
     */
    public final void setExecuteAfter(Class<? extends RulePhase> previousPhase)
    {
        this.previousPhase = previousPhase;
    }

    /**
     * This allows the sorter to link {@link RulePhase}s to each other, without them having to explicitly specify both
     * the executeAfter and executeBefore.
     */
    public final void setExecuteBefore(Class<? extends RulePhase> nextPhase)
    {
        this.nextPhase = nextPhase;
    }
}
