package org.multiverse.api;

import org.multiverse.api.callables.*;
import org.multiverse.api.collections.*;
import org.multiverse.api.exceptions.*;
import org.multiverse.api.lifecycle.*;
import org.multiverse.api.references.*;
import java.util.*;
import java.util.concurrent.Callable;

import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.TxnThreadLocal.getRequiredThreadLocalTxn;

/**
 * A utility class with convenience methods to access the {@link org.multiverse.api.Stm} or
 * {@link Txn}. These methods can be imported using the static import for a less
 * ugly syntax.
 *
 * @author Peter Veentjer.
 */
public final class StmUtils {

    private final static TxnRefFactory refFactory
        = getGlobalStmInstance().getDefaultRefFactory();
    private final static TxnExecutor defaultTxnExecutor
        = getGlobalStmInstance().getDefaultTxnExecutor();
    private final static OrElseBlock orelseBlock
        = getGlobalStmInstance().newOrElseBlock();
    private final static TxnCollectionsFactory txnCollectionsFactory
        = getGlobalStmInstance().getDefaultTxnCollectionFactory();

    /**
     * Creates a new committed TxnList based on a double linked list.
     *
     * @return the created TxnList.
     */
    public static <E> TxnList<E> newTxnLinkedList(){
        return txnCollectionsFactory.newLinkedList();
    }

    /**
     * Creates a new committed unbound TxnStack.
     *
     * @return the created TxnStack.
     */
    public static <E> TxnStack<E> newTxnStack(){
        return txnCollectionsFactory.newStack();
    }

    /**
     * Creates a new committed bound TxnStack.
     *
     * @param capacity the maximum capacity of the stack. Integer.MAX_VALUE indicates that there is no bound.
     * @return the create TxnStack
     * @throws IllegalArgumentException if capacity smaller than 0.
     */
    public static <E> TxnStack<E> newTxnStack(int capacity){
        return txnCollectionsFactory.newStack(capacity);
    }

    /**
     * Creates a new committed unbound TxnQueue.
     *
     * @return the created TxnQueue.
     */
    public static <E> TxnQueue<E> newTxnQueue(){
        return txnCollectionsFactory.newQueue();
    }

    /**
     * Creates a new committed bound TxnQueue.
     *
     * @param capacity the maximum capacity of the queue. Integer.MAX_VALUE indicates that there is no bound.
     * @return the created TxnQueue
     * @throws IllegalArgumentException if capacity smaller than 0.
     */
    public static <E> TxnQueue<E> newTxnQueue(int capacity){
        return txnCollectionsFactory.newQueue(capacity);
    }

    /**
     * Creates a new committed unbound TxnDeque.
     *
     * @return the created TxnDeque
     */
    public static <E> TxnDeque<E> newTxnDeque(){
        return txnCollectionsFactory.newDeque();
    }

    /**
     * Creates a new committed bound TxnDeque.
     *
     * @param capacity the maximum capacity of the deque. Integer.MAX_VALUE indicates that there is no bound.
     * @return the created TxnDeque.
     * @throws IllegalArgumentException if capacity is smaller than 0.
     */
    public static <E> TxnDeque<E> newTxnDeque(int capacity){
        return txnCollectionsFactory.newDeque(capacity);
    }

    /**
     * Creates a new committed TxnSet that is based on a 'hashtable'.
     *
     * @return the created TxnSet.
     */
    public static <E> TxnSet<E> newTxnHashSet(){
        return txnCollectionsFactory.newHashSet();
    }

    /**
     * Creates a new committed TxnMap.
     *
     * @return the created TxnMap
     */
    public static <K, V> TxnMap<K, V> newTxnHashMap(){
        return txnCollectionsFactory.newHashMap();
    }

    public static void atomic(final Runnable runnable){
        if(runnable == null){
            throw new NullPointerException("runnable can't be null");
        }

        atomic(new TxnVoidCallable(){
            @Override
            public void call(Txn txn) throws Exception {
                runnable.run();
            }
        });
    }

    public static <E> E atomic(final Callable<E> callable){
        if(callable == null){
            throw new NullPointerException("callable can't be null");
        }

        return atomic(new TxnCallable<E>() {
            @Override
            public E call(Txn txn) throws Exception {
               return callable.call();
            }
        });
    }

#foreach($callable in $callables)
    /**
     * Executes the callable transactionally on the GlobalStmInstance using the default TxnExecutor. If a
     * Transaction already is active on the TxnThreadLocal, this transaction will lift on that
     * transaction (so the propagation level is Requires) and will not commit this transaction.
     *
     * <p>This method doesn't throw a checked exception, but if the callable does, it is wrapped inside an
     * InvisibleCheckedException.
     *
     * <p>If you want to get most out of performance, it is best to make use of a customized {@link TxnExecutor} instead
     * of relying on the default TxnExecutor that will always provide the most expensive transaction available.
     *
     * @param callable The callable {@link ${callable.name}} to execute.
#if(${callable.type} ne 'void')
     * @return the result of the execution
#end
     * @throws NullPointerException if callable is null.
     * @throws org.multiverse.api.exceptions.InvisibleCheckedException
     *                                  if the callable throws a checked exception.
     */
    public static ${callable.typeParameter} ${callable.type} atomic(${callable.name}${callable.typeParameter} callable){
#if(${callable.type} eq 'void')
        defaultTxnExecutor.execute(callable);
#else
        return defaultTxnExecutor.execute(callable);
#end
    }

   /**
    * Executes the callable transactionally on the GlobalStmInstance using the default TxnExecutor. If a
    * Transaction already is active on the TxnThreadLocal, this transaction will lift on that
    * transaction (so the propagation level is Requires) and will not commit this transaction.
    *
    * <p>If you want to get most out of performance, it is best to make use of a customized {@link TxnExecutor} instead
    * of relying on the default TxnExecutor that will always provide the most expensive transaction available.
    *
    * @param callable The {@link ${callable.name}} to execute.
#if(${callable.type} ne 'void')
    * @return the result of the execution
#end
    * @throws NullPointerException if callable is null.
    * @throws Exception is the callable throws an Exception
    */
   public static ${callable.typeParameter} ${callable.type} atomicChecked(${callable.name}${callable.typeParameter} callable) throws Exception{
#if(${callable.type} eq 'void')
       defaultTxnExecutor.executeChecked(callable);
#else
       return defaultTxnExecutor.executeChecked(callable);
#end
   }

   /**
    * Executes the either block, or in case of a retry, the orelse block is executed.
    *
    * If in the execution of the callable a checked exception is thrown, the exception
    * is wrapped in a InvisibleCheckedException. The original exception can be retrieved by calling the
    * getCause method.
    *
    * @param either the either block
    * @param orelse the orelse block.
#if(${callable.type} ne 'void')
    * @return the result of the execution.
#end
    * @throws NullPointerException if callable is null.
    * @throws org.multiverse.api.exceptions.InvisibleCheckedException if a checked exception is thrown by the callable.
    */
    public static ${callable.typeParameter} ${callable.type} atomic(${callable.name}${callable.typeParameter} either, ${callable.name}${callable.typeParameter} orelse){
#if(${callable.type} ne 'void')
        return orelseBlock.execute(either,orelse);
#else
        orelseBlock.execute(either,orelse);
#end
    }

    /**
    * Executes the either block, or in case of a retry, the orelse block is executed.
    *
    * @param either the either block
    * @param orelse the orelse block
#if(${callable.type} ne 'void')
    * @return the result of the execution.
#end
    * @throws NullPointerException if callable is null.
    * @throws Exception if the execute call fails.
    */
    public static ${callable.typeParameter} ${callable.type} atomicChecked(${callable.name}${callable.typeParameter} either, ${callable.name}${callable.typeParameter} orelse)throws Exception{
#if(${callable.type} ne 'void')
        return orelseBlock.executeChecked(either,orelse);
#else
        orelseBlock.executeChecked(either,orelse);
#end
    }

#end
    /**
     * Creates a committed {@link TxnInteger} with the provided value using the {@link GlobalStmInstance}.
     *
     * @param value the initial value of the TnxInteger
     * @return the created TnxInteger.
     */
    public static TxnInteger newTxnInteger(int value) {
        return refFactory.newTxnInteger(value);
    }

    /**
     * Creates a committed {@link TxnInteger} with 0 as initial value using the {@link GlobalStmInstance}.
     *
     * @return the created TxnInteger.
     */
    public static TxnInteger newTxnInteger() {
        return refFactory.newTxnInteger(0);
    }

    /**
     * Creates a committed {@link TxnLong} with 0 as initial value using the {@link GlobalStmInstance}.
     *
     * @return the created TnxLong.
     */
    public static TxnLong newTxnLong() {
        return refFactory.newTxnLong(0);
    }

    /**
     * Creates a committed {@link TxnLong} with the provided value using the {@link GlobalStmInstance}.
     *
     * @param value the initial value of the TxnLong.
     * @return the created TnxLong.
     */
    public static TxnLong newTxnLong(long value) {
        return refFactory.newTxnLong(value);
    }

    /**
     * Creates a committed {@link TxnDouble} with 0 as initial value using the {@link GlobalStmInstance}.
     *
     * @return the created TxnDouble.
     */
    public static TxnDouble newTxnDouble() {
        return refFactory.newTxnDouble(0);
    }

    /**
     * Creates a committed {@link TxnDouble} with the provided value using the {@link GlobalStmInstance}.
     *
     * @param value the initial value.
     * @return the created TnxDouble.
     */
    public static TxnDouble newTxnDouble(double value) {
        return refFactory.newTxnDouble(value);
    }

    /**
     * Creates a committed {@link TxnBoolean} with false as initial value using the {@link GlobalStmInstance}.
     *
     * @return the created TxnBoolean.
     */
    public static TxnBoolean newTxnBoolean() {
        return refFactory.newTxnBoolean(false);
    }

    /**
     * Creates a committed {@link TxnBoolean} with the provided value using the {@link GlobalStmInstance}.
     *
     * @param value the initial value
     * @return the created TxnBoolean.
     */
    public static TxnBoolean newTxnBoolean(boolean value) {
        return refFactory.newTxnBoolean(value);
    }

    /**
     * Creates a committed {@link TxnRef} with null as initial value using the {@link GlobalStmInstance}.
     *
     * @param <E> the type of the TxnRef.
     * @return the created Ref.
     */
    public static <E> TxnRef<E> newTxnRef() {
        return refFactory.newTxnRef(null);
    }

    /**
     * Creates a committed {@link TxnRef} with the provided value using the {@link GlobalStmInstance}.
     *
     * @param value the initial value of the TxnRef.
     * @param <E>   the type of the TxnRef.
     * @return the created TxnRef.
     */
    public static <E> TxnRef<E> newTxnRef(E value) {
        return refFactory.newTxnRef(value);
    }

    /**
     * Does a retry. This behavior is needed for blocking transactions; transaction that wait for a state change
     * to happen on certain datastructures, e.g. an item to come available on a transactional blocking queue.
     *
     * <p>Under the hood the retry throws an Retry that will be caught up the callstack
     * (by the {@link TxnExecutor} for example). The Retry should not be caught by user code in almost all cases.
     */
    public static void retry() {
        Txn txn = getRequiredThreadLocalTxn();
        txn.retry();
    }

    /**
     * Prepares the Transaction in the TxnThreadLocal transaction.
     *
     * <p>For more information see {@link Txn#prepare()}.
     *
     * @throws TxnMandatoryException if no active transaction is found.
     * @throws IllegalTransactionStateException if the active transaction is not in the correct
     *                                           state for this operation.
     * @throws ControlFlowError
     */
    public static void prepare() {
        Txn txn = getRequiredThreadLocalTxn();
        txn.prepare();
    }

    /**
     * Aborts the Transaction in the TxnThreadLocal transaction.
     *
     * <p>For more information see {@link Txn#abort()}.
     *
     * @throws TxnMandatoryException if no active transaction is found.
     * @throws IllegalTransactionStateException if the active transaction is not in the correct
     *                                           state for this operation.
     * @throws ControlFlowError
     */
    public static void abort() {
        Txn txn = getRequiredThreadLocalTxn();
        txn.abort();
    }

    /**
     * Commits the Transaction in the TxnThreadLocal transaction.
     *
     * <p>For more information see {@link Txn#commit()}.
     *
     * @throws TxnMandatoryException if no active transaction is found.
     * @throws IllegalTransactionStateException if the active transaction is not in the correct
     * state for this operation.
     * @throws ControlFlowError
     */
     public static void commit() {
         Txn txn = getRequiredThreadLocalTxn();
         txn.commit();
     }

     /**
      * Scheduled an deferred or compensating task on the {@link Txn} in the TxnThreadLocal. This task is
      * executed after the transaction commits or aborts.
      *
      * @param task the deferred task to execute.
      * @throws NullPointerException if task is null.
      * @throws org.multiverse.api.exceptions.TxnMandatoryException
                        if no transaction is set on the {@link org.multiverse.api.TxnThreadLocal}.
      * @throws org.multiverse.api.exceptions.IllegalTxnStateException
      *                 if the transaction is not in the correct state to accept a compensating or deferred task.
      */
      public static void scheduleCompensatingOrDeferredTask(final Runnable task) {
            if (task == null) {
                throw new NullPointerException();
            }

            Txn txn = getRequiredThreadLocalTxn();
            txn.register(new TxnListener() {
                @Override
                public void notify(Txn txn, TxnEvent event) {
                    if (event == TxnEvent.PostCommit || event == TxnEvent.PostAbort) {
                        task.run();
                    }
                }
            });
       }

       /**
        * Scheduled an deferred task on the {@link Txn} in the {@link TxnThreadLocal}. This task is executed after
        * the transaction commits and one of the use cases is starting transactions.
        *
        * @param task the deferred task to execute.
        * @throws NullPointerException if task is null.
        * @throws org.multiverse.api.exceptions.TxnMandatoryException
        *                   if no transaction is set on the {@link org.multiverse.api.TxnThreadLocal}.
        * @throws org.multiverse.api.exceptions.IllegalTxnStateException
        *                   if the transaction is not in the correct state to accept a deferred task.
        */
       public static void scheduleDeferredTask(final Runnable task) {
            if (task == null) {
                throw new NullPointerException();
            }

            Txn txn = getRequiredThreadLocalTxn();
            txn.register(new TxnListener() {
                @Override
                public void notify(Txn txn, TxnEvent event) {
                    if (event == TxnEvent.PostCommit) {
                        task.run();
                    }
                }
            });
       }

       /**
        * Scheduled a compensating task on the {@link Txn} in the {@link TxnThreadLocal}. This task is executed after
        * the transaction aborts and one of the use cases is cleaning up non transaction resources like the file system.
        *
        * @param task the deferred task to execute.
        * @throws NullPointerException if task is null.
        * @throws org.multiverse.api.exceptions.TxnMandatoryException
        *                       if no transaction is set on the {@link org.multiverse.api.TxnThreadLocal}.
        * @throws org.multiverse.api.exceptions.IllegalTxnStateException
        *                       if the transaction is not in the correct state to accept a compensating task.
        */
       public static void scheduleCompensatingTask(final Runnable task) {
            if (task == null) {
                throw new NullPointerException();
            }

            Txn txn = getRequiredThreadLocalTxn();
            txn.register(new TxnListener() {
                @Override
                public void notify(Txn txn, TxnEvent event) {
                    if (event == TxnEvent.PostAbort) {
                        task.run();
                    }
                }
            });
       }

       //we don want instances
       private StmUtils() {
       }
}
