package org.multiverse.api;

import org.multiverse.api.lifecycle.TxnListener;

/**
 * The unit of work for {@link Stm}. The transaction make sure that changes on {@link TxnObject} instances are:
 * <ol>
 * <li>Atomic: all or nothing gets committed (Failure atomicity)</li>
 * <li>Consistent : </li>
 * <li>Isolated: a transaction is executed isolated from other transactions. Meaning that a transaction won't see changed made by
 * transactions executed concurrently, but it will see changes made by transaction completed before. It depends on the
 * {@link IsolationLevel} or {@link LockMode} used how strict the isolation is.</li>
 * </ol>
 *
 * <h3>Thread-safety</h3>
 *
 * <p>A Txn is not thread-safe (just like a Hibernate Session is not thread-safe to use). It can be
 * handed over from thread to thread, but one needs to be really careful with the {@link TxnThreadLocal} or other
 * thread specific state like the stackframe of a method (this is an issue when instrumentation is used since the stackframe
 * is likely to be enhanced to include the Txn as a local variable.
 *
 * <h3>TxnListener</h3>
 *
 * <p>It is possible to listen to a Txn when it aborts/prepares/commits/starts. There are 2 different flavors of
 * listeners:
 * <ol>
 * <li>normal listeners: are registered during the execution of a transaction using the
 * {@link Txn#register(org.multiverse.api.lifecycle.TxnListener)} method. If the transactions aborts/commits
 * these listeners are removed. So if the transaction is retried, the listeners need to be registered (this is easy since
 * the logic inside the transactional closure that did the register, is executed again.
 * </li>
 * <li>permanent listeners: are registered once and will always remain. It can be done on the
 * TxnExecutor level using the {@link TxnFactoryBuilder#addPermanentListener(org.multiverse.api.lifecycle.TxnListener)}
 * or it can be done on the Stm level. Permanent listeners are suited for products that want to integrate with Multiverse and always
 * execute some logic at important transaction events. Registration of permanent can also be done on the {@link Stm} level. See
 * the implementations for more details. Permanent listeners are always executed after the normal listeners.
 * </li>
 * </ol>
 *
 * <h3>Storing transaction references</h3>
 *
 * <p>Txn instances should not be stored since they are likely to be pooled by the STM. So it could be that the same
 * transaction instance is re-used to execute a completely unrelated piece of logic, and it can also be that different instances
 * are used to execute the same logic.
 *
 * @author Peter Veentjer.
 */
public interface Txn {

    /**
     * Returns the TxnConfig used by this Txn.
     *
     * <p>Because the Txn can be reused, the TxnConfig used by this Txn doesn't need to be constant.
     *
     * @return the TxnConfig.
     */
    TxnConfig getConfig();

    /**
     * Returns the status of this Txn.
     *
     * @return the status of this Txn.
     */
    TxnStatus getStatus();

    /**
     * Gets the current attempt (so the number of tries this transaction already had). Value will
     * always be equal or larger than 1 (the first attempt returns 1). The maximum number of attempts for retrying is determined based
     * on the  {@link TxnConfig#getMaxRetries()}
     *
     * @return the current attempt.
     */
    int getAttempt();

    /**
     * Gets the remaining timeout in nanoseconds. Long.MAX_VALUE indicates that no timeout is used.
     *
     * <p>The remaining timeout only is decreased if a transaction blocks on a retry or when doing
     * a backoff.
     *
     * @return the remaining timeout.
     */
    long getRemainingTimeoutNs();

    /**
     * Commits this Txn. If the Txn is:
     * <ol>
     * <li>active: it is prepared for commit and then committed</li>
     * <li>prepared: it is committed. Once it is prepared, the commit is guaranteed to
     * succeed.</li>
     * <li>aborted: a DeadTxnException is thrown</li>
     * <li>committed: the call is ignored</li>
     * </ol>
     *
     * <p>Txn will always be aborted if the commit does not succeed.
     *
     * <p>Commit will not throw a {@link org.multiverse.api.exceptions.ReadWriteConflict} after the transaction is prepared.
     * So if prepared successfully, a commit will always succeed.
     *
     * <p>If there are TxnListeners (either normal ones or permanent ones) and they thrown a {@link RuntimeException}
     * or {@link Error}, this will be re-thrown. If a listener fails after the prepare/commit  the transaction still is
     * committed.
     *
     * @throws org.multiverse.api.exceptions.ReadWriteConflict
     *          if the commit failed. Check the class hierarchy of the ReadWriteConflict for more information.
     * @throws org.multiverse.api.exceptions.IllegalTxnStateException
     *          if the Txn is not in the correct
     *          state for this operation.
     */
    void commit();

    /**
     * Prepares this transaction to be committed. It can lock resources to make sure that no conflicting changes are
     * made after the transaction has been prepared. If the transaction already is prepared, the call is ignored.  If
     * the prepare fails, the transaction automatically is aborted. Once a transaction is prepared, the commit will
     * always succeed.
     *
     * <p>It is very important that the transaction eventually commits or aborts, if it doesn't no other transaction
     * reading/writing the committed resources, can't commit.
     *
     * @throws org.multiverse.api.exceptions.ReadWriteConflict
     *          if the transaction can't be prepared.
     * @throws org.multiverse.api.exceptions.DeadTxnException
     *          if the transaction already is committed or aborted.
     */
    void prepare();

    /**
     * Aborts this Txn. This means that the changes made in this transaction are not committed. It depends on
     * the implementation if this operation is simple (ditching objects for example), or if changes need to be rolled
     * back. If an exception is thrown while executing the abort, the transaction is still aborted. And example of
     * such a situation is a pre-abort task that fails. So the transaction always is aborted (unless it is committed).
     *
     * <p>If the Txn already is aborted, the call is ignored.
     *
     * @throws org.multiverse.api.exceptions.IllegalTxnStateException
     *          if the Txn is not in the correct state for this operation.
     */
    void abort();

    /**
     * Retries the transaction. This call doesn't block, but if all goes well a {@link org.multiverse.api.exceptions.RetryError}
     * is thrown which is caught by the {@link TxnExecutor}.
     *
     * @throws org.multiverse.api.exceptions.TxnExecutionException
     *          if the transaction is not in a legal state for
     *          this operation.
     * @throws org.multiverse.api.exceptions.ControlFlowError
     *
     */
    void retry();

    /**
     * Signals that the only possible outcome of the Txn is one that aborts. When the transaction prepares or
     * commits it checks if the transaction is marked as abort only. If so, it will automatically aborted and an
     * {@link org.multiverse.api.exceptions.AbortOnlyException} is thrown.
     *
     * <p>This method is not threadsafe, so can only be called by the thread that used the transaction.
     *
     * @throws org.multiverse.api.exceptions.IllegalTxnStateException
     *          if the transaction is not active.
     * @throws org.multiverse.api.exceptions.ControlFlowError
     *
     */
    void setAbortOnly();

    /**
     * Checks if this Txn is abort only (so will always fail when committing or preparing).
     *
     * <p>This method is not threadsafe, so can only be called by the thread that used the transaction.
     *
     * @return true if abort only, false otherwise.
     * @throws org.multiverse.api.exceptions.DeadTxnException
     *          if the transaction is committed/aborted.
     */
    boolean isAbortOnly();

    /**
     * Registers a TxnListener. Every time a transaction is retried, the listener needs to
     * be registered again if you want the task to be executed again. If you want a permanent listener, have
     * a look at the {@link TxnFactoryBuilder#addPermanentListener(org.multiverse.api.lifecycle.TxnListener)}.
     *
     * <p>If a TxnListener is added more than once, it is executed more than once. No checks
     * are made. The permanent listeners are executed in the order they are added.
     *
     * <p>If a TxnListener throws an Error/RuntimeException and the transaction still is alive,
     * it is aborted. For compensating and deferred actions this is not an issue, but for the PrePrepare state
     * or the state it could since the transaction is aborted.
     *
     * @param listener the listener to add.
     * @throws NullPointerException if listener is null. If the transaction is still alive, it is aborted.
     * @throws org.multiverse.api.exceptions.IllegalTxnStateException
     *                              if the transaction is not in the correct
     *                              state (e.g. aborted or committed).
     * @throws org.multiverse.api.exceptions.ControlFlowError
     *
     */
    void register(TxnListener listener);
}
