//------------------------------------------------------------------------------
// <copyright file="OdbcTransaction.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
// <owner current="true" primary="false">[....]</owner>
//------------------------------------------------------------------------------

using System;
using System.Data;
using System.Data.Common;
using System.Threading;

namespace System.Data.Odbc
{
    public sealed class OdbcTransaction : DbTransaction {
        private OdbcConnection _connection;
        private IsolationLevel _isolevel = IsolationLevel.Unspecified;
        private OdbcConnectionHandle _handle;

        internal OdbcTransaction(OdbcConnection connection, IsolationLevel isolevel, OdbcConnectionHandle handle) {
            OdbcConnection.VerifyExecutePermission();
            
            _connection = connection;
            _isolevel   = isolevel;
            _handle = handle;
        }

        new public OdbcConnection Connection { // MDAC 66655
            get {
                return _connection;
            }
        }

        override protected DbConnection DbConnection { // MDAC 66655
            get {
                return Connection;
            }
        }

        override public IsolationLevel IsolationLevel {
            get {
                OdbcConnection connection = _connection;
                if (null == connection ) {
                    throw ADP.TransactionZombied(this);
                }

                //We need to query for the case where the user didn't set the isolevel
                //BeginTransaction(), but we should also query to see if the driver
                //"rolled" the level to a higher supported one...
                if(IsolationLevel.Unspecified == _isolevel) {
                    //Get the isolation level
                    int sql_iso= connection .GetConnectAttr(ODBC32.SQL_ATTR.TXN_ISOLATION, ODBC32.HANDLER.THROW);
                    switch((ODBC32.SQL_TRANSACTION)sql_iso) {
                    case ODBC32.SQL_TRANSACTION.READ_UNCOMMITTED:
                        _isolevel = IsolationLevel.ReadUncommitted;
                        break;
                    case ODBC32.SQL_TRANSACTION.READ_COMMITTED:
                        _isolevel = IsolationLevel.ReadCommitted;
                        break;
                    case ODBC32.SQL_TRANSACTION.REPEATABLE_READ:
                        _isolevel = IsolationLevel.RepeatableRead;
                        break;
                    case ODBC32.SQL_TRANSACTION.SERIALIZABLE:
                        _isolevel = IsolationLevel.Serializable;
                        break;
                    case ODBC32.SQL_TRANSACTION.SNAPSHOT:
                        _isolevel = IsolationLevel.Snapshot;
                        break;
                    default:
                        throw ODBC.NoMappingForSqlTransactionLevel(sql_iso);
                    };
                }
                return _isolevel;
            }
        }

        override public void Commit() {
            OdbcConnection.ExecutePermission.Demand(); // MDAC 81476

            OdbcConnection connection = _connection;
            if (null == connection) {
                throw ADP.TransactionZombied(this);
            }

            connection.CheckState(ADP.CommitTransaction); // MDAC 68289

            //Note: SQLEndTran success if not actually in a transaction, so we have to throw
            //since the IDbTransaciton spec indicates this is an error for the managed packages
            if(null == _handle) {
                throw ODBC.NotInTransaction();
            }
            
            ODBC32.RetCode retcode = _handle.CompleteTransaction(ODBC32.SQL_COMMIT);
            if (retcode == ODBC32.RetCode.ERROR)  {
                //If an error has occurred, we will throw an exception in HandleError,
                //and leave the transaction active for the user to retry
                connection.HandleError(_handle, retcode);
            }

            //Transaction is complete...
            connection.LocalTransaction = null;
            _connection = null;
            _handle = null;
            
        }

        protected override void Dispose(bool disposing) {
            if (disposing) {
                OdbcConnectionHandle handle = _handle;
                _handle = null;
                if (null != handle){
                    try{
                        ODBC32.RetCode retcode = handle.CompleteTransaction(ODBC32.SQL_ROLLBACK);
                        if (retcode == ODBC32.RetCode.ERROR) {
                            //don't throw an exception here, but trace it so it can be logged
                            if (_connection != null) {
                                Exception e = _connection.HandleErrorNoThrow(handle, retcode);
                                ADP.TraceExceptionWithoutRethrow(e);                               
                            }
                        }
                    }
                    catch (Exception e){
                        // 
                        if (!ADP.IsCatchableExceptionType(e)) {
                            throw;
                        }
                    }
                }
                if (_connection != null) {
                    if (_connection.IsOpen) {
                        _connection.LocalTransaction = null;
                    }
                }
                _connection = null;
                _isolevel = IsolationLevel.Unspecified;
            }
            base.Dispose(disposing);
        }

        override public void Rollback() {
            OdbcConnection connection = _connection;
            if (null == connection) {
                throw ADP.TransactionZombied(this);
            }
            connection.CheckState(ADP.RollbackTransaction); // MDAC 68289

            //Note: SQLEndTran success if not actually in a transaction, so we have to throw
            //since the IDbTransaciton spec indicates this is an error for the managed packages
            if(null == _handle) {
                throw ODBC.NotInTransaction();
            }

            ODBC32.RetCode retcode = _handle.CompleteTransaction(ODBC32.SQL_ROLLBACK);
            if (retcode == ODBC32.RetCode.ERROR) {
                //If an error has occurred, we will throw an exception in HandleError,
                //and leave the transaction active for the user to retry
                connection.HandleError(_handle, retcode);
            }
            connection.LocalTransaction = null;
            _connection = null;
            _handle = null;
        }
    }
}

