// file      : odb/transaction.hxx
// license   : GNU GPL v2; see accompanying LICENSE file

#ifndef ODB_TRANSACTION_HXX
#define ODB_TRANSACTION_HXX

#include <odb/pre.hxx>

#include <memory>  // std::unique_ptr
#include <vector>
#include <cstddef> // std::size_t

#include <odb/forward.hxx>

#include <odb/details/export.hxx>

namespace odb
{
  class transaction_impl;

  class LIBODB_EXPORT transaction
  {
  public:
    typedef odb::database database_type;
    typedef odb::connection connection_type;

    // If the second argument is false, then this transaction is not made
    // the current transaction of the thread.
    //
    explicit
    transaction (std::unique_ptr<transaction_impl>, bool make_current = true);

    // Create a finalized transaction instance which can later be initialized
    // with reset().
    //
    transaction ();

    // The transaction is neither move-constructible/assignable nor copy-
    // constructible/assignable.
    //
    // Note that we could probably support move-construction with some effort
    // (and thus allow returning transactions from functions). The tricky
    // aspect here is the current transaction semantics: if the moved-from
    // transaction is current, then it feels correct to make the new instance
    // current instead. (BTW, an alternative approach would be to provide a
    // detach() or release() function which transfers the underlying _impl
    // pointer. But then we will have the issue with callbacks).
    //
    transaction (transaction&&) = delete;
    transaction (const transaction&) = delete;
    transaction& operator= (transaction&&) = delete;
    transaction& operator= (const transaction&) = delete;

    // Unless the transaction has already been finalized (explicitly
    // committed or rolled back), the destructor will roll it back.
    //
    ~transaction ();

    // Unless the current transaction has already been finalized (explicitly
    // committed or rolled back), reset will roll it back. If the second
    // argument is false, then this transaction is not made the current
    // transaction of the thread.
    //
    void
    reset (std::unique_ptr<transaction_impl>, bool make_current = true);

    void
    commit ();

    void
    rollback ();

    // Return the database this transaction is on.
    //
    database_type&
    database ();

    // Return the connection this transaction is on.
    //
    // The second version verifies the connection is to the specified
    // database. For database implementations that support attaching multiple
    // databases it may also select the connection corresponding to the
    // specified database.
    //
    connection_type&
    connection ();

    connection_type&
    connection (database_type&);

    bool
    finalized () const {return finalized_;}

  public:
    // Return true if there is a transaction in effect.
    //
    static bool
    has_current ();

    // Return current transaction or throw if there is no transaction
    // in effect.
    //
    static transaction&
    current ();

    // Set the current thread's transaction.
    //
    static void
    current (transaction&);

    // Revert to the no transaction in effect state for the current thread.
    //
    static void
    reset_current ();

    // SQL statement tracing.
    //
  public:
    typedef odb::tracer tracer_type;

    void
    tracer (tracer_type&);

    void
    tracer (tracer_type*);

    tracer_type*
    tracer () const;

    // Post-commit/rollback callbacks.
    //
  public:
    static const unsigned short event_commit = 0x01;
    static const unsigned short event_rollback = 0x02;
    static const unsigned short event_all = event_commit | event_rollback;

    typedef void (*callback_type) (
      unsigned short event, void* key, unsigned long long data);

    // Register a post-commit/rollback callback. The data argument
    // can be used to store any user data that does not exceed 8
    // bytes and doesn't require alignment greater than unsigned
    // long long, such as an old value that needs to be restored
    // in case of a rollback.
    //
    // The state argument can be used to indicate to the caller
    // that the callback has been unregistered because the
    // transaction has terminated. In this case the transaction
    // resets the passed pointer to 0.
    //
    // Note that the order in which the callbacks are called is
    // unspecified.
    //
    void
    callback_register (callback_type,
                       void* key,
                       unsigned short event = event_all,
                       unsigned long long data = 0,
                       transaction** state = 0);

    // Unregister a post-commit/rollback callback. Note that this is a
    // potentially slow operation. You also don't need to unregister
    // a callback that has been called or auto-reset using the state
    // argument passed to register(). This function does nothing if
    // the key is not found.
    //
    void
    callback_unregister (void* key);

    // Update the event, data, and state values for a callback. Note
    // that just like unregister(), this is a potentially slow operation.
    //
    void
    callback_update (void* key,
                     unsigned short event,
                     unsigned long long data = 0,
                     transaction** state = 0);

  public:
    transaction_impl&
    implementation ();

  protected:
    friend struct rollback_guard;

    std::size_t
    callback_find (void* key);

    void
    callback_call (unsigned short event);

  protected:
    bool finalized_;
    std::unique_ptr<transaction_impl> impl_;

    // Callbacks.
    //
    struct callback_data
    {
      unsigned short event;
      callback_type func;
      void* key;
      unsigned long long data;
      transaction** state;
    };

    // Slots for the first 20 callback are pre-allocated on the stack.
    // For the rest they are allocated dynamically as needed.
    //
    // Note, if you change stack_callback_count, make sure you also
    // update the common/transaction/callback test accordingly.
    //
    static const std::size_t stack_callback_count = 20;
    static const std::size_t max_callback_count = ~(std::size_t (0));

    callback_data stack_callbacks_[stack_callback_count];
    std::vector<callback_data> dyn_callbacks_;

    // When a callback is unregistered, the free slot from the stack is
    // added to the linked list of free slots which is organized by
    // re-using the key data member to store the slot's index (we cannot
    // store a pointer because std::vector may move slots on expansion).
    // The value equal to max_callback_count indicates no free slots are
    // available.
    //
    std::size_t free_callback_;

    // Total number of used slots, both registered and in the free list.
    //
    std::size_t callback_count_;
  };

  class LIBODB_EXPORT transaction_impl
  {
  public:
    typedef odb::tracer tracer_type;
    typedef odb::database database_type;
    typedef odb::connection connection_type;

    virtual
    ~transaction_impl ();

    virtual void
    start () = 0;

    virtual void
    commit () = 0;

    virtual void
    rollback () = 0;

    database_type&
    database ()
    {
      return database_;
    }

    virtual connection_type&
    connection (database_type*);

    virtual void
    tracer (tracer_type*);

    virtual tracer_type*
    tracer () const;

  protected:
    transaction_impl (database_type& db)
        : database_ (db), connection_ (0)
    {
    }

    transaction_impl (database_type& db, connection_type& c)
        : database_ (db), connection_ (&c)
    {
    }

  protected:
    database_type& database_;
    connection_type* connection_;
  };
}

#include <odb/transaction.ixx>

#include <odb/post.hxx>

#endif // ODB_TRANSACTION_HXX
