// NpgsqlTransactionCallbacks.cs
//
// Author:
//  Josh Cooley <jbnpgsql@tuxinthebox.net>
//
// Copyright (C) 2007, The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

using System;
using System.Data;
using System.Reflection;

namespace Npgsql
{
    internal interface INpgsqlTransactionCallbacks : IDisposable
    {
        string GetName();
        void PrepareTransaction();
        void CommitTransaction();
        void RollbackTransaction();
    }

    internal class NpgsqlTransactionCallbacks : MarshalByRefObject, INpgsqlTransactionCallbacks
    {
        private NpgsqlConnection _connection;
        private readonly string _connectionString;
        private bool _closeConnectionRequired;
        private bool _prepared;
        private readonly string _txName = Guid.NewGuid().ToString();

        private static readonly String CLASSNAME = MethodBase.GetCurrentMethod().DeclaringType.Name;

        public NpgsqlTransactionCallbacks(NpgsqlConnection connection)
        {
            _connection = connection;
            _connectionString = _connection.ConnectionString;
            _connection.Disposed += new EventHandler(_connection_Disposed);
        }

        private void _connection_Disposed(object sender, EventArgs e)
        {
            // TODO: what happens if this is called from another thread?
            // connections should not be shared across threads while in a transaction
            _connection.Disposed -= new EventHandler(_connection_Disposed);
            _connection = null;
        }

        private NpgsqlConnection GetConnection()
        {
            if (_connection == null || (_connection.FullState & ConnectionState.Open) != ConnectionState.Open)
            {
                _connection = new NpgsqlConnection(_connectionString);
                _connection.Open();
                _closeConnectionRequired = true;
                return _connection;
            }
            else
            {
                return _connection;
            }
        }

        #region INpgsqlTransactionCallbacks Members

        public string GetName()
        {
            return _txName;
        }

        public void CommitTransaction()
        {
            NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CommitTransaction");
            NpgsqlConnection connection = GetConnection();

            if (_prepared)
            {
                NpgsqlCommand.ExecuteBlind(connection.Connector, string.Format("COMMIT PREPARED '{0}'", _txName));
            }
            else
            {
                NpgsqlCommand.ExecuteBlind(connection.Connector, NpgsqlQuery.CommitTransaction);
            }
        }

        public void PrepareTransaction()
        {
            if (!_prepared)
            {
                NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "PrepareTransaction");
                NpgsqlConnection connection = GetConnection();
                NpgsqlCommand.ExecuteBlind(connection.Connector, string.Format("PREPARE TRANSACTION '{0}'", _txName));
                _prepared = true;
            }
        }

        public void RollbackTransaction()
        {
            NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "RollbackTransaction");
            NpgsqlConnection connection = GetConnection();

            if (_prepared)
            {
                NpgsqlCommand.ExecuteBlind(connection.Connector, string.Format("ROLLBACK PREPARED '{0}'", _txName));
            }
            else
            {
                NpgsqlCommand.ExecuteBlind(connection.Connector, NpgsqlQuery.RollbackTransaction);
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_closeConnectionRequired)
            {
                _connection.Close();
            }
            _closeConnectionRequired = false;
        }

        #endregion
    }
}
