/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.openjpa.jta;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAResource;

/**
 * A transaction for testing.
 *
 * @author Pinaki Poddar
 *
 */
public class SimpleTransaction implements Transaction {
    private final Set<Synchronization> synchs = new HashSet<>();
    private Throwable rollbackCause;
    private volatile int status = Status.STATUS_UNKNOWN;

    /**
     * Commits this transaction.
     */
    @Override
    public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException,
            SecurityException, SystemException {
        if (status == Status.STATUS_MARKED_ROLLBACK)
            throw new RuntimeException(this + " can not commit. Marked for rollback");
        status = Status.STATUS_COMMITTING;
        List<Throwable> errors = new ArrayList<>();
        for (Synchronization synch : synchs) {
            try {
                synch.beforeCompletion();
            } catch (Throwable t) {
                errors.add(t);
            }
        }
        // do nothing
        status = errors.isEmpty() ? Status.STATUS_COMMITTED : Status.STATUS_ROLLEDBACK;
        for (Synchronization synch : synchs) {
            try {
                synch.afterCompletion(status);
            } catch (Throwable t) {
                errors.add(t);
            }
        }
        status = errors.isEmpty() ? Status.STATUS_COMMITTED : Status.STATUS_UNKNOWN;
    }

    /**
     * Not implemented.
     * Raises UnsupportedOperationException.
     */
    @Override
    public boolean delistResource(XAResource arg0, int arg1) throws IllegalStateException, SystemException {
        throw new UnsupportedOperationException();
    }

    /**
     * Not implemented.
     * Raises UnsupportedOperationException.
     */
    @Override
    public boolean enlistResource(XAResource arg0) throws IllegalStateException, RollbackException, SystemException {
        return false;
    }

    /**
     * Gets the status of this transaction.
     * Raises UnsupportedOperationException.
     */
    @Override
    public int getStatus() throws SystemException {
        return status;
    }

    void setStatus(int newStatus) throws SystemException {
        status = newStatus;
    }


    /**
     * Registers the given synchronization element.
     */
    @Override
    public void registerSynchronization(Synchronization synch) throws IllegalStateException, RollbackException,
            SystemException {
        synchs.add(synch);
    }

    /**
     * Rolls back this transaction.
     */
    @Override
    public void rollback() throws IllegalStateException, SystemException {
        status = Status.STATUS_ROLLING_BACK;
        List<Throwable> errors = new ArrayList<>();
        for (Synchronization synch : synchs) {
            try {
                synch.beforeCompletion();
            } catch (Throwable t) {
                errors.add(t);
            }
        }

        for (Synchronization synch : synchs) {
            try {
                synch.afterCompletion(Status.STATUS_ROLLEDBACK);
            } catch (Throwable t) {
                errors.add(t);
            }
        }
        status = errors.isEmpty() ? Status.STATUS_ROLLEDBACK : Status.STATUS_UNKNOWN;
        if (!errors.isEmpty())
            throw new RuntimeException(errors.get(0));
    }

    /**
     * Marks this transaction for rollback only.
     */
    @Override
    public void setRollbackOnly() throws IllegalStateException, SystemException {
        setRollbackOnly(null);
    }

    /**
     * Marks this transaction for rollback only with the given underlying cause.
     */
    void setRollbackOnly(Throwable cause) throws IllegalStateException, SystemException {
        rollbackCause = cause;
        status = Status.STATUS_MARKED_ROLLBACK;
    }

    Throwable getRollbackCause() {
        return rollbackCause;
    }

    @Override
    public String toString() {
        return "TXN:"+hashCode() +"["+statusCode()+"]";
    }

    String statusCode() {
        switch (status) {
        case Status.STATUS_ACTIVE          : return "active";
        case Status.STATUS_COMMITTED       : return "committed";
        case Status.STATUS_COMMITTING      : return "committing";
        case Status.STATUS_MARKED_ROLLBACK : return "marked rollback";
        case Status.STATUS_NO_TRANSACTION  : return "none";
        case Status.STATUS_PREPARED        : return "prepared";
        case Status.STATUS_PREPARING       : return "preparing";
        case Status.STATUS_ROLLEDBACK      : return "rolled back";
        case Status.STATUS_ROLLING_BACK    : return "rolling back";
        case Status.STATUS_UNKNOWN         : return "unknown";
        default                            : return "error";
        }
    }

}
