/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.jboss.windup.config;

import org.jboss.forge.furnace.proxy.Proxies;
import org.jboss.forge.furnace.util.Annotations;
import org.jboss.windup.config.loader.RuleLoaderContext;
import org.jboss.windup.config.metadata.MetadataBuilder;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.metadata.RuleMetadataType;
import org.jboss.windup.config.metadata.RuleProviderMetadata;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.context.Context;
import org.ocpsoft.rewrite.context.ContextBase;

/**
 * {@link AbstractRuleProvider} provides metadata, and a list of {@link Rule} objects that are then evaluated by the {@link RuleSubset} during Windup
 * execution.
 *
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 * @author <a href="mailto:zizka@seznam.cz">Ondrej Zizka, I</a>
 */
public abstract class AbstractRuleProvider extends ContextBase implements RuleProvider {
    private int executionIndex;
    private RuleProviderMetadata metadata;

    public AbstractRuleProvider() {
        /*
         * In the case of a proxy, the no-args constructor will be called. This is the case even if the provider
         * itself would normally have a metadata param passed in.
         *
         * Once completed, the getMetadata() method will be proxied correctly, so this is ok. Just allow it to pass
         * in this case.
         */
        if (Proxies.isForgeProxy(this) && !Annotations.isAnnotationPresent(getClass(), RuleMetadata.class))
            return;

        if (!Annotations.isAnnotationPresent(getClass(), RuleMetadata.class)) {
            throw new IllegalStateException(getClass().getName() + " must either "
                    + "be abstract, or specify @" + RuleMetadata.class.getName()
                    + ", or call a super() constructor and provide " + RuleProviderMetadata.class.getName());
        }
        this.metadata = MetadataBuilder.forProvider(getClass());
    }

    /**
     * Create a new {@link AbstractRuleProvider} instance using the given {@link RuleProviderMetadata}.
     */
    public AbstractRuleProvider(RuleProviderMetadata metadata) {
        this.metadata = metadata;
    }

    /**
     * Create a new {@link AbstractRuleProvider} instance using the given parameters to construct default {@link RuleProviderMetadata}.
     */
    public AbstractRuleProvider(Class<? extends RuleProvider> implementationType, String id) {
        this.metadata = MetadataBuilder.forProvider(implementationType, id);
    }

    @Override
    public RuleProviderMetadata getMetadata() {
        return metadata;
    }

    @Override
    public boolean handles(Object payload) {
        return payload instanceof RuleLoaderContext;
    }

    /**
     * Specify additional meta-data to individual {@link Rule} instances originating from the corresponding {@link RuleProvider} instance.
     */
    public static void enhanceRuleMetadata(RuleProvider provider, Rule rule) {
        if (rule instanceof Context) {
            Context context = (Context) rule;
            if (!context.containsKey(RuleMetadataType.ORIGIN))
                context.put(RuleMetadataType.ORIGIN, provider.getMetadata().getOrigin());
            if (!context.containsKey(RuleMetadataType.RULE_PROVIDER))
                context.put(RuleMetadataType.RULE_PROVIDER, provider);
            if (!context.containsKey(RuleMetadataType.TAGS))
                context.put(RuleMetadataType.TAGS, provider.getMetadata().getTags());
        }
    }

    @Override
    public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof AbstractRuleProvider) {
            AbstractRuleProvider that = (AbstractRuleProvider) other;
            result = this.getMetadata().equals(that.getMetadata());
        }
        return result;
    }

    @Override
    public int hashCode() {
        return getMetadata().hashCode();
    }

    /**
     * For internal use only.
     */
    public final int getExecutionIndex() {
        return executionIndex;
    }

    /**
     * For internal use only.
     */
    public final void setExecutionIndex(int executionIndex) {
        this.executionIndex = executionIndex;
    }

    /**
     * The "priority" of the {@link RuleProvider} instance. This is not presently used by Windup.
     */
    @Override
    public final int priority() {
        return 0;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(metadata.getID());

        if (!metadata.getID().equals(metadata.getOrigin())) {
            builder.append(" from ").append(metadata.getOrigin());
        }

        return builder.toString();
    }
}
