/*
 * Copyright 2014-2015 Groupon, Inc
 * Copyright 2014-2015 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you 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.killbill.billing.entitlement.api;

import javax.inject.Inject;

import org.killbill.billing.ErrorCode;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApiException;
import org.killbill.billing.entitlement.plugin.api.OnFailureEntitlementResult;
import org.killbill.billing.entitlement.plugin.api.OnSuccessEntitlementResult;
import org.killbill.billing.entitlement.plugin.api.PriorEntitlementResult;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EntitlementPluginExecution {

    private static final Logger log = LoggerFactory.getLogger(EntitlementPluginExecution.class);

    private final EntitlementApi entitlementApi;
    private final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry;

    public interface WithEntitlementPlugin<T> {
        T doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException;
    }


    @Inject
    public EntitlementPluginExecution(final EntitlementApi entitlementApi, final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry) {
        this.entitlementApi = entitlementApi;
        this.pluginRegistry = pluginRegistry;
    }

    public <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementContext pluginContext) throws EntitlementApiException {

        try {
            final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
            if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
                throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED);
            }
            final EntitlementContext updatedPluginContext = new DefaultEntitlementContext(pluginContext, priorEntitlementResult);
            try {
                T result = callback.doCall(entitlementApi, updatedPluginContext);
                executePluginOnSuccessCalls(updatedPluginContext);
                return result;
            } catch (final EntitlementApiException e) {
                executePluginOnFailureCalls(updatedPluginContext);
                throw e;
            }
        } catch (final EntitlementPluginApiException e) {
            throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
        }
    }

    private PriorEntitlementResult executePluginPriorCalls(final EntitlementContext entitlementContextArg) throws EntitlementPluginApiException {

        // Return as soon as the first plugin aborts, or the last result for the last plugin
        PriorEntitlementResult prevResult = null;

        EntitlementContext currentContext = entitlementContextArg;
        for (final String pluginName : pluginRegistry.getAllServices()) {
            final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
            if (plugin == null) {
                // First call to plugin, we log warn, if plugin is not registered
                log.warn("Skipping unknown entitlement control plugin {} when fetching results", pluginName);
                continue;
            }
            prevResult = plugin.priorCall(currentContext, currentContext.getPluginProperties());
            if (prevResult.isAborted()) {
                break;
            }
            currentContext = new DefaultEntitlementContext(currentContext, prevResult);
        }
        return prevResult;
    }

    private OnSuccessEntitlementResult executePluginOnSuccessCalls(final EntitlementContext context) throws EntitlementPluginApiException {
        for (final String pluginName : pluginRegistry.getAllServices()) {
            final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
            if (plugin != null) {
                plugin.onSuccessCall(context, context.getPluginProperties());
            }
        }
        return null;
    }

    private OnFailureEntitlementResult executePluginOnFailureCalls(final EntitlementContext context) throws EntitlementPluginApiException {
        for (final String pluginName : pluginRegistry.getAllServices()) {
            final EntitlementPluginApi plugin = pluginRegistry.getServiceForName(pluginName);
            if (plugin != null) {
                plugin.onFailureCall(context, context.getPluginProperties());
            }
        }
        return null;
    }
}
