package org.multiverse.api;

import org.multiverse.api.lifecycle.TxnListener;

/**
 * A Builder for creating a {@link TxnFactory} and {@link TxnExecutor}. This builder provides full control
 * on transaction settings.
 *
 * <p>Since the {@link Txn} and {@link TxnExecutor} are very closely integrated, both of them are created
 * by this TxnFactoryBuilder.
 *
 * <p>Instances of this class are considered immutable, so when you call one of the modifying methods, make sure
 * that you use the resulting TxnFactoryBuilder. Normally with the builder implementation the same
 * instance is returned. In this case this isn't true because a new instance is returned every time.
 *
 * @author Peter Veentjer
 * @see TxnFactory
 * @see TxnConfig
 */
public interface TxnFactoryBuilder {

    /**
     * Returns the {@link TxnConfig} used by this TxnFactoryBuilder.
     *
     * @return the used TxnConfig.
     */
    TxnConfig getConfig();

    /**
     * Sets if the {@link org.multiverse.api.exceptions.ControlFlowError} is reused. Normally you don't want to reuse them
     * because they can be expensive to create (especially the stacktrace) and they could be created very often. But for
     * debugging purposes it can be quite annoying because you want to see the stacktrace.
     *
     * @param reused true if ControlFlowErrors should be reused.
     * @return the updated TxnFactoryBuilder.
     * @see TxnConfig#isControlFlowErrorsReused()
     */
    TxnFactoryBuilder setControlFlowErrorsReused(boolean reused);

    /**
     * Sets the {@link Txn} familyname. If an {@link TxnExecutor} is used inside a method, a useful familyname could
     * be the full name of the class and the method.
     * <p/>
     * The transaction familyName is useful debugging purposes, but has not other meaning.
     *
     * @param familyName the familyName of the transaction.
     * @return the updated TxnFactoryBuilder
     * @throws NullPointerException if familyName is null.
     * @see TxnConfig#getFamilyName()
     */
    TxnFactoryBuilder setFamilyName(String familyName);

    /**
     * Sets the {@link org.multiverse.api.PropagationLevel} used. With the PropagationLevel you have control
     * on how the transaction deals with transaction nesting. The default is {@link PropagationLevel#Requires}
     * which automatically starts a transaction is one is missing, or lifts on a transaction if available.
     *
     * @param propagationLevel the new PropagationLevel
     * @return the updated TxnFactoryBuilder
     * @throws NullPointerException if propagationLevel is null.
     * @see TxnConfig#getPropagationLevel()
     * @see PropagationLevel
     */
    TxnFactoryBuilder setPropagationLevel(PropagationLevel propagationLevel);

    /**
     * Sets the {@link Txn} {@link LockMode} for all reads. If a LockMode is set higher than {@link LockMode#None}, this transaction
     * will locks all reads (and writes since a read is needed for a write) and the transaction automatically becomes
     * serialized.
     *
     * @param lockMode the LockMode to set.
     * @return the updated TxnFactoryBuilder.
     * @throws NullPointerException if lockMode is null.
     * @see TxnConfig#getReadLockMode()
     * @see LockMode
     */
    TxnFactoryBuilder setReadLockMode(LockMode lockMode);

    /**
     * Sets the {@link Txn} {@link LockMode} for all writes. For a write, always a read needs to be done, so if the read LockMode is
     *
     * <p>Freshly constructed objects that are not committed, automatically are locked with {@link LockMode#Exclusive}.
     *
     * <p>If the write LockMode is set after the read LockMode and the write LockMode is lower than the read LockMode,
     * an {@code IllegalTxnFactoryException} will be thrown when a {@link TxnFactory} is created.
     *
     * <p>If the write LockMode is set before the read LockMode and the write LockMode is lower than the read LockMode,
     * the write LockMode automatically is upgraded to that of the read LockMode. This makes setting the readLock
     * mode less of a nuisance.
     *
     * @param lockMode the LockMode to set.
     * @return the updated TxnFactoryBuilder.
     * @throws NullPointerException if lockMode is null.
     * @see TxnConfig#getWriteLockMode()
     * @see LockMode
     */
    TxnFactoryBuilder setWriteLockMode(LockMode lockMode);

    /**
     * Adds a permanent {@link Txn} {@link org.multiverse.api.lifecycle.TxnListener}. All permanent listeners are always executed after all normal
     * listeners are executed. If the same listener is added multiple times, it will be executed multiple times.
     *
     * <p>This method is very useful for integrating Multiverse in other JVM based environments because with this
     * approach you have a callback when transaction aborts/commit and can add your own logic. See the
     * {@link org.multiverse.api.lifecycle.TxnListener} for more information about normal vs permanent listeners.
     *
     * @param listener the permanent listener to add.
     * @return the updated TxnFactoryBuilder.
     * @throws NullPointerException if listener is null.
     * @see TxnConfig#getPermanentListeners()
     */
    TxnFactoryBuilder addPermanentListener(TxnListener listener);

    /**
     * Sets the {@link Txn} {@link TraceLevel}. With tracing it is possible to see what is happening inside a transaction.
     *
     * @param traceLevel the new traceLevel.
     * @return the updated TxnFactoryBuilder.
     * @throws NullPointerException if traceLevel is null.
     * @see TxnConfig#getTraceLevel()
     * @see TraceLevel
     */
    TxnFactoryBuilder setTraceLevel(TraceLevel traceLevel);

    /**
     * Sets the timeout (the maximum time a {@link Txn} is allowed to block. Long.MAX_VALUE indicates that an
     * unbound timeout should be used.
     *
     * @param timeoutNs the timeout specified in nano seconds
     * @return the updated TxnFactoryBuilder
     * @see TxnConfig#getTimeoutNs()
     * @see Txn#getRemainingTimeoutNs()
     */
    TxnFactoryBuilder setTimeoutNs(long timeoutNs);

    /**
     * Sets if the {@link Txn} can be interrupted while doing blocking operations.
     *
     * @param interruptible if the transaction can be interrupted while doing blocking operations.
     * @return the updated TxnFactoryBuilder
     * @see TxnConfig#isInterruptible()
     */
    TxnFactoryBuilder setInterruptible(boolean interruptible);

    /**
     * Sets the {@link Txn} {@link BackoffPolicy}. Policy is used to backoff when a transaction conflicts with another {@link Txn}.
     * See the {@link BackoffPolicy} for more information.
     *
     * @param backoffPolicy the backoff policy to use.
     * @return the updated TxnFactoryBuilder
     * @throws NullPointerException if backoffPolicy is null.
     * @see TxnConfig#getBackoffPolicy()
     */
    TxnFactoryBuilder setBackoffPolicy(BackoffPolicy backoffPolicy);

    /**
     * Sets if the {@link Txn} dirty check is enabled. Dirty check is that something only needs to be written,
     * if there really is a change (else it will be interpreted as a read). If it is disabled, it will always write, and
     * this could prevent the aba isolation anomaly, but causes more conflicts so more contention. In most cases enabling
     * it is the best option.
     *
     * @param dirtyCheckEnabled true if dirty check should be executed, false otherwise.
     * @return the updated TxnFactoryBuilder.
     * @see TxnConfig#isDirtyCheckEnabled()
     */
    TxnFactoryBuilder setDirtyCheckEnabled(boolean dirtyCheckEnabled);

    /**
     * Sets the maximum number of spins that are allowed when a {@link Txn} can't be read/written/locked
     * because it is locked by another transaction.
     *
     * <p>Setting the value to a very high value, could lead to more an increased chance of a live locking.
     *
     * @param spinCount the maximum number of spins
     * @return the updated TxnFactoryBuilder.
     * @throws IllegalArgumentException if spinCount smaller than 0.
     * @see TxnConfig#getSpinCount()
     */
    TxnFactoryBuilder setSpinCount(int spinCount);

    /**
     * Sets the readonly property on a {@link Txn}. If a transaction is configured as readonly, no write operations
     * (also no construction of new transactional objects making use of that transaction) is allowed
     *
     * @param readonly true if the transaction should be readonly, false otherwise.
     * @return the updated TxnFactoryBuilder
     * @see TxnConfig#isReadonly()
     */
    TxnFactoryBuilder setReadonly(boolean readonly);

    /**
     * Sets if the {@link Txn} should automatically track all reads that have been done. This is needed for blocking
     * operations, but also for other features like writeskew detection.
     *
     * <p>Tracking reads puts more pressure on the transaction since it needs to store all reads, but it reduces the chance
     * of read conflicts, since once read from main memory, it can be retrieved from the transaction.
     *
     * The transaction is free to track reads even though this property is disabled.
     *
     * @param enabled true if read tracking enabled, false otherwise.
     * @return the updated TxnFactoryBuilder
     * @see TxnConfig#isReadTrackingEnabled()
     */
    TxnFactoryBuilder setReadTrackingEnabled(boolean enabled);

    /**
     * With the speculative configuration enabled, the {@link Stm} is allowed to determine optimal settings for
     * a {@link Txn}.
     *
     * <p>Some behavior like readonly or the need for tracking reads can be determined runtime. The system can start with
     * a readonly non readtracking transaction and upgrade to an update or a read tracking once a write or retry
     * happens.
     *
     * <p>It depends on the {@link Stm} implementation on which properties it is going to speculate.
     *
     * <p>Enabling it can cause a few unexpected 'retries' of transactions, but it can seriously improve performance.
     *
     * @param speculative indicates if speculative configuration should be enabled.
     * @return the updated TxnFactoryBuilder
     * @see TxnConfig#isSpeculative()
     */
    TxnFactoryBuilder setSpeculative(boolean speculative);

    /**
     * Sets the the maximum count a {@link Txn} can be retried. The default is 1000. Setting it to a very low value
     * could mean that a transaction can't complete. Setting it to a very high value could lead to live-locking.
     *
     * <p>If the speculative configuration mechanism is enabled ({@link #setSpeculative(boolean)}), a few retries
     * are done in the beginning to figure out the best settings.
     *
     * @param maxRetries the maximum number of times a transaction can be tried.
     * @return the updated TxnFactoryBuilder
     * @throws IllegalArgumentException if maxRetries smaller than 0.
     * @see TxnConfig#getMaxRetries()
     */
    TxnFactoryBuilder setMaxRetries(int maxRetries);

    /**
     * Sets the {@link IsolationLevel} on the {@link Txn}.
     *
     * <p>The {@link Txn} is free to upgraded to a higher {@link IsolationLevel}. This is essentially the same
     * behavior you get when Oracle is used, where a read uncommitted is upgraded to a read committed and a repeatable
     * read is upgraded to the Oracle version of serialized (so with the writeskew problem still there).
     *
     * @param isolationLevel the new IsolationLevel
     * @return the updated TxnFactoryBuilder
     * @throws NullPointerException if isolationLevel is null.
     * @see TxnConfig#getIsolationLevel()
     * @see IsolationLevel
     */
    TxnFactoryBuilder setIsolationLevel(IsolationLevel isolationLevel);

    /**
     * Sets if the {@link Txn} is allowed to do an explicit retry (needed for a blocking operation). One use case
     * for disallowing it, it when the transaction is used inside an actor, and you don't want that inside the logic
     * executed by the agent a blocking operations is done (e.g. taking an item of a blocking queue).
     *
     * @param blockingAllowed true if explicit retry is allowed, false otherwise.
     * @return the updated TxnFactoryBuilder
     */
    TxnFactoryBuilder setBlockingAllowed(boolean blockingAllowed);

    /**
     * Builds a new {@link TxnFactory}.
     *
     * @return the build TxnFactory.
     * @throws org.multiverse.api.exceptions.IllegalTxnFactoryException
     *          if the TxnFactory could not be build
     *          because the configuration was not correct.
     */
    TxnFactory newTransactionFactory();

    /**
     * Builds a new {@link TxnExecutor} optimized for executing transactions created by this TxnFactoryBuilder.
     *
     * @return the created TxnExecutor.
     * @throws org.multiverse.api.exceptions.IllegalTxnFactoryException
     *          if the TxnFactory could not be build
     *          because the configuration was not correct.
     */
    TxnExecutor newTxnExecutor();
}
