/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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.kie.spring.persistence;

import java.util.HashMap;
import java.util.Map;

import org.drools.persistence.api.TransactionManager;
import org.drools.persistence.api.TransactionSynchronization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class KieSpringTransactionManager
        implements
        TransactionManager {

    public static final String RESOURCE_CONTAINER = "org.kie.resources";

    Logger logger = LoggerFactory.getLogger(getClass());
    private AbstractPlatformTransactionManager ptm;

    TransactionDefinition td = new DefaultTransactionDefinition();
    TransactionStatus currentTransaction = null;

    public KieSpringTransactionManager(AbstractPlatformTransactionManager ptm) {
        this.ptm = ptm;
    }

    public boolean begin() {
        try {
            if (getStatus() == TransactionManager.STATUS_NO_TRANSACTION) {
                // If there is no transaction then start one, we will commit within the same Command
                // it seems in spring calling getTransaction is enough to begin a new transaction
                currentTransaction = this.ptm.getTransaction(td);
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            logger.warn("Unable to begin transaction",
                    e);
            throw new RuntimeException("Unable to begin transaction",
                    e);
        }
    }

    public void commit(boolean transactionOwner) {
        if (transactionOwner) {
            try {
                // if we didn't begin this transaction, then do nothing
                this.ptm.commit(currentTransaction);
                currentTransaction = null;
                if (TransactionSynchronizationManager.hasResource(KieSpringTransactionManager.RESOURCE_CONTAINER)) {
                    TransactionSynchronizationManager.unbindResource(KieSpringTransactionManager.RESOURCE_CONTAINER);
                }
            } catch (Exception e) {
                logger.warn("Unable to commit transaction",
                        e);
                throw new RuntimeException("Unable to commit transaction",
                        e);
            }
        }
    }

    public void rollback(boolean transactionOwner) {
        try {
            if (transactionOwner) {
                this.ptm.rollback(currentTransaction);
                currentTransaction = null;
                if (TransactionSynchronizationManager.hasResource(KieSpringTransactionManager.RESOURCE_CONTAINER)) {
                    TransactionSynchronizationManager.unbindResource(KieSpringTransactionManager.RESOURCE_CONTAINER);
                }
            }
        } catch (Exception e) {
            logger.warn("Unable to rollback transaction",
                    e);
            throw new RuntimeException("Unable to rollback transaction",
                    e);
        }
    }

    /**
     * Borrowed from Seam
     */
    public int getStatus() {
        if (ptm == null) {
            return TransactionManager.STATUS_NO_TRANSACTION;
        }

        logger.debug("Current TX name (According to TransactionSynchronizationManager) : " + TransactionSynchronizationManager.getCurrentTransactionName());
        if (TransactionSynchronizationManager.isActualTransactionActive()) {
            TransactionStatus transaction = null;
            try {
                if (currentTransaction == null) {
                    transaction = ptm.getTransaction(td);
                    if (transaction.isNewTransaction()) {
                        return TransactionManager.STATUS_COMMITTED;
                    }
                } else {
                    transaction = currentTransaction;
                }
                logger.debug("Current TX: " + transaction);
                // If SynchronizationManager thinks it has an active transaction but
                // our transaction is a new one
                // then we must be in the middle of committing
                if (transaction.isCompleted()) {
                    if (transaction.isRollbackOnly()) {
                        return TransactionManager.STATUS_ROLLEDBACK;
                    }
                    return TransactionManager.STATUS_COMMITTED;
                } else {
                    // Using the commented-out code in means that if rollback with this manager,
                    //  I always have to catch and check the exception 
                    //  because ROLLEDBACK can mean both "rolled back" and "rollback only".
                    // if ( transaction.isRollbackOnly() ) {
                    //     return TransactionManager.STATUS_ROLLEDBACK;
                    // }

                    return TransactionManager.STATUS_ACTIVE;
                }
            } finally {
                if (currentTransaction == null) {
                    ptm.commit(transaction);
                }
            }
        }
        return TransactionManager.STATUS_NO_TRANSACTION;
    }

    public void registerTransactionSynchronization(TransactionSynchronization ts) {
        TransactionSynchronizationManager.registerSynchronization(new SpringTransactionSynchronizationAdapter(ts));
    }

    @Override
    public void putResource(Object key, Object resource) {
        Map<Object, Object> resources = (Map<Object, Object>) TransactionSynchronizationManager.getResource(RESOURCE_CONTAINER);
        if (resources == null) {
            resources = new HashMap<Object, Object>();
            TransactionSynchronizationManager.bindResource(RESOURCE_CONTAINER, resources);
        }
        resources.put(key, resource);
    }

    @Override
    public Object getResource(Object key) {
        Map<Object, Object> resources = (Map<Object, Object>) TransactionSynchronizationManager.getResource(RESOURCE_CONTAINER);
        if (resources == null) {
            return null;
        }
        return resources.get(key);
    }
}
