/*
 * Copyright 2014 Groupon, Inc
 * Copyright 2014 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.payment.core.sm;

import javax.inject.Inject;

import org.killbill.automaton.MissingEntryException;
import org.killbill.automaton.Operation;
import org.killbill.automaton.StateMachine;
import org.killbill.automaton.StateMachineConfig;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.caching.StateMachineConfigCache;

/**
 * This class needs to know about the payment state machine xml file. All the knowledge about the xml file is encapsulated here.
 */
public class PaymentStateMachineHelper {

    private static final String BIG_BANG_STATE_MACHINE_NAME = "BIG_BANG";
    private static final String AUTHORIZE_STATE_MACHINE_NAME = "AUTHORIZE";
    private static final String CAPTURE_STATE_MACHINE_NAME = "CAPTURE";
    private static final String PURCHASE_STATE_MACHINE_NAME = "PURCHASE";
    private static final String REFUND_STATE_MACHINE_NAME = "REFUND";
    private static final String CREDIT_STATE_MACHINE_NAME = "CREDIT";
    private static final String VOID_STATE_MACHINE_NAME = "VOID";
    private static final String CHARGEBACK_STATE_MACHINE_NAME = "CHARGEBACK";

    private static final String BIG_BANG_INIT = "BIG_BANG_INIT";

    private static final String AUTHORIZE_SUCCESS = "AUTH_SUCCESS";
    private static final String CAPTURE_SUCCESS = "CAPTURE_SUCCESS";
    private static final String PURCHASE_SUCCESS = "PURCHASE_SUCCESS";
    private static final String REFUND_SUCCESS = "REFUND_SUCCESS";
    private static final String CREDIT_SUCCESS = "CREDIT_SUCCESS";
    private static final String VOID_SUCCESS = "VOID_SUCCESS";
    private static final String CHARGEBACK_SUCCESS = "CHARGEBACK_SUCCESS";

    private static final String AUTHORIZE_PENDING = "AUTH_PENDING";
    private static final String CAPTURE_PENDING = "CAPTURE_PENDING";
    private static final String PURCHASE_PENDING = "PURCHASE_PENDING";
    private static final String REFUND_PENDING = "REFUND_PENDING";
    private static final String CREDIT_PENDING = "CREDIT_PENDING";
    private static final String VOID_PENDING = "VOID_PENDING";
    private static final String CHARGEBACK_PENDING = "CHARGEBACK_PENDING";

    private static final String AUTHORIZE_FAILED = "AUTH_FAILED";
    private static final String CAPTURE_FAILED = "CAPTURE_FAILED";
    private static final String PURCHASE_FAILED = "PURCHASE_FAILED";
    private static final String REFUND_FAILED = "REFUND_FAILED";
    private static final String CREDIT_FAILED = "CREDIT_FAILED";
    private static final String VOID_FAILED = "VOID_FAILED";
    private static final String CHARGEBACK_FAILED = "CHARGEBACK_FAILED";

    private static final String AUTH_ERRORED = "AUTH_ERRORED";
    private static final String CAPTURE_ERRORED = "CAPTURE_ERRORED";
    private static final String PURCHASE_ERRORED = "PURCHASE_ERRORED";
    private static final String REFUND_ERRORED = "REFUND_ERRORED";
    private static final String CREDIT_ERRORED = "CREDIT_ERRORED";
    private static final String VOID_ERRORED = "VOID_ERRORED";
    private static final String CHARGEBACK_ERRORED = "CHARGEBACK_ERRORED";

    private final StateMachineConfigCache stateMachineConfigCache;

    public static final String[] STATE_NAMES = {AUTH_ERRORED,
                                                AUTHORIZE_FAILED,
                                                AUTHORIZE_PENDING,
                                                AUTHORIZE_SUCCESS,
                                                CAPTURE_ERRORED,
                                                CAPTURE_FAILED,
                                                CAPTURE_PENDING,
                                                CAPTURE_SUCCESS,
                                                CHARGEBACK_ERRORED,
                                                CHARGEBACK_FAILED,
                                                CHARGEBACK_PENDING,
                                                CHARGEBACK_SUCCESS,
                                                CREDIT_ERRORED,
                                                CREDIT_FAILED,
                                                CREDIT_PENDING,
                                                CREDIT_SUCCESS,
                                                PURCHASE_ERRORED,
                                                PURCHASE_FAILED,
                                                PURCHASE_PENDING,
                                                PURCHASE_SUCCESS,
                                                REFUND_ERRORED,
                                                REFUND_FAILED,
                                                REFUND_PENDING,
                                                REFUND_SUCCESS,
                                                VOID_ERRORED,
                                                VOID_FAILED,
                                                VOID_PENDING,
                                                VOID_SUCCESS};

    @Inject
    public PaymentStateMachineHelper(final StateMachineConfigCache stateMachineConfigCache) {
        this.stateMachineConfigCache = stateMachineConfigCache;
    }

    public String getInitStateNameForTransaction() {
        return BIG_BANG_INIT;
    }

    public String getSuccessfulStateForTransaction(final TransactionType transactionType) {
        switch (transactionType) {
            case AUTHORIZE:
                return AUTHORIZE_SUCCESS;
            case CAPTURE:
                return CAPTURE_SUCCESS;
            case PURCHASE:
                return PURCHASE_SUCCESS;
            case REFUND:
                return REFUND_SUCCESS;
            case CREDIT:
                return CREDIT_SUCCESS;
            case VOID:
                return VOID_SUCCESS;
            case CHARGEBACK:
                return CHARGEBACK_SUCCESS;
            default:
                throw new IllegalStateException("Unsupported transaction type " + transactionType);
        }
    }

    public String getPendingStateForTransaction(final TransactionType transactionType) {
        switch (transactionType) {
            case AUTHORIZE:
                return AUTHORIZE_PENDING;
            case CAPTURE:
                return CAPTURE_PENDING;
            case PURCHASE:
                return PURCHASE_PENDING;
            case REFUND:
                return REFUND_PENDING;
            case CREDIT:
                return CREDIT_PENDING;
            case VOID:
                return VOID_PENDING;
            case CHARGEBACK:
                return CHARGEBACK_PENDING;
            default:
                throw new IllegalStateException("No PENDING state for transaction type " + transactionType);
        }
    }

    public String getErroredStateForTransaction(final TransactionType transactionType) {
        switch (transactionType) {
            case AUTHORIZE:
                return AUTH_ERRORED;
            case CAPTURE:
                return CAPTURE_ERRORED;
            case PURCHASE:
                return PURCHASE_ERRORED;
            case REFUND:
                return REFUND_ERRORED;
            case CREDIT:
                return CREDIT_ERRORED;
            case VOID:
                return VOID_ERRORED;
            case CHARGEBACK:
                return CHARGEBACK_ERRORED;
            default:
                throw new IllegalStateException("Unsupported transaction type " + transactionType);
        }
    }

    public String getFailureStateForTransaction(final TransactionType transactionType) {
        switch (transactionType) {
            case AUTHORIZE:
                return AUTHORIZE_FAILED;
            case CAPTURE:
                return CAPTURE_FAILED;
            case PURCHASE:
                return PURCHASE_FAILED;
            case REFUND:
                return REFUND_FAILED;
            case CREDIT:
                return CREDIT_FAILED;
            case VOID:
                return VOID_FAILED;
            case CHARGEBACK:
                return CHARGEBACK_FAILED;
            default:
                throw new IllegalStateException("Unsupported transaction type " + transactionType);
        }
    }

    public StateMachineConfig getStateMachineConfig(final String pluginName, final InternalCallContext internalCallContext) throws PaymentApiException {
        return stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, internalCallContext);
    }

    public Operation getOperationForTransaction(final StateMachineConfig stateMachineConfig, final TransactionType transactionType) throws MissingEntryException {
        final StateMachine stateMachine = getStateMachineForTransaction(stateMachineConfig, transactionType);
        // Only one operation defined, this is the current PaymentStates.xml model
        return stateMachine.getOperations()[0];
    }

    private StateMachine getStateMachineForTransaction(final StateMachineConfig stateMachineConfig, final TransactionType transactionType) throws MissingEntryException {
        switch (transactionType) {
            case AUTHORIZE:
                return stateMachineConfig.getStateMachine(AUTHORIZE_STATE_MACHINE_NAME);
            case CAPTURE:
                return stateMachineConfig.getStateMachine(CAPTURE_STATE_MACHINE_NAME);
            case PURCHASE:
                return stateMachineConfig.getStateMachine(PURCHASE_STATE_MACHINE_NAME);
            case REFUND:
                return stateMachineConfig.getStateMachine(REFUND_STATE_MACHINE_NAME);
            case CREDIT:
                return stateMachineConfig.getStateMachine(CREDIT_STATE_MACHINE_NAME);
            case VOID:
                return stateMachineConfig.getStateMachine(VOID_STATE_MACHINE_NAME);
            case CHARGEBACK:
                return stateMachineConfig.getStateMachine(CHARGEBACK_STATE_MACHINE_NAME);
            default:
                throw new IllegalStateException("Unsupported transaction type " + transactionType + " for null payment id");
        }
    }

    // A better way would be to change the xml to add attributes to the state (e.g isTerminal, isSuccess, isInit,...)
    public boolean isSuccessState(final String stateName) {
        return stateName.endsWith("SUCCESS") || stateName.startsWith("CHARGEBACK");
    }
}
