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

namespace Microsoft.SqlServer.Server {

    using System.Data.Sql;
    using System.Data.SqlClient;
    using System.Diagnostics;
 
    internal class SmiEventSink_Default : SmiEventSink {

        private SmiEventSink    _parent;     // next level up, which we'll defer to if we don't need to handle the event.

        private SqlErrorCollection _errors;
        private SqlErrorCollection _warnings;

        private SqlErrorCollection Errors {
            get {
                if ( null == _errors ) {
                    _errors = new SqlErrorCollection( );
                }

                return _errors;
            }
        }

        internal bool HasMessages {
            get {
                SmiEventSink_Default parent = (SmiEventSink_Default)_parent;
                if (null != parent) {
                    return parent.HasMessages;
                }
                else {
                    bool result = (null != _errors || null != _warnings);
                    return result;
                }
            }
        }

        virtual internal string ServerVersion {
            get {
                return null;
            }
        }

        internal SmiEventSink Parent {
            get {
                return _parent;
            }
            set {
                _parent = value;
            }
        }

        private SqlErrorCollection Warnings {
            get {
                if ( null == _warnings ) {
                    _warnings = new SqlErrorCollection( );
                }

                return _warnings;
            }
        }

        protected virtual void DispatchMessages(bool ignoreNonFatalMessages) {
            // virtual because we want a default implementation in the cases
            // where we don't have a connection to process stuff, but we want to
            // provide the connection the ability to fire info messages when it
            // hooks up.
            SmiEventSink_Default parent = (SmiEventSink_Default)_parent;
            if (null != parent) {
                parent.DispatchMessages(ignoreNonFatalMessages);
            }
            else {
                SqlException errors = ProcessMessages(true, ignoreNonFatalMessages);   // ignore warnings, because there's no place to send them...
                if (null != errors) {
                    throw errors;
                }
            }
        }

        protected SqlException ProcessMessages(bool ignoreWarnings, bool ignoreNonFatalMessages) {
            SqlException result = null;
            SqlErrorCollection temp = null;  // temp variable to store that which is being thrown - so that local copies can be deleted

            if ( null != _errors ) {
                Debug.Assert( 0 != _errors.Count, "empty error collection?" ); // must be something in the collection

                if (ignoreNonFatalMessages) {
                    temp = new SqlErrorCollection();
                    foreach(SqlError error in _errors) {
                        if (error.Class >= TdsEnums.FATAL_ERROR_CLASS){
                            temp.Add(error);
                        }
                    }
                    if (temp.Count <= 0) {
                        temp = null;
                    }
                }
                else {
                    if (null != _warnings ) {
                        // When we throw an exception we place all the warnings that
                        // occurred at the end of the collection - after all the errors.
                        // That way the user can see all the errors AND warnings that
                        // occurred for the exception.
                        foreach ( SqlError warning in _warnings ) {
                            _errors.Add( warning );
                        }
                    }
                    temp = _errors;
                }

                _errors = null;
                _warnings = null;
            }
            else {
                Debug.Assert( null == _warnings || 0 != _warnings.Count, "empty warning collection?" );// must be something in the collection

                if (!ignoreWarnings) {
                    temp = _warnings;
                }
                _warnings = null;
            }

            if ( null != temp ) {
                result = SqlException.CreateException( temp, ServerVersion );
            }
            return result;
        }

        internal void CleanMessages() {
            SmiEventSink_Default parent = (SmiEventSink_Default)_parent;
            if (null != parent) {
                parent.CleanMessages();
            }
            else {
                _errors = null;
                _warnings = null;
            }
        }

        internal void ProcessMessagesAndThrow() {
            ProcessMessagesAndThrow(false);
        }

        internal void ProcessMessagesAndThrow(bool ignoreNonFatalMessages) {
            if (HasMessages) {
                DispatchMessages(ignoreNonFatalMessages);
            }
        }

        internal enum UnexpectedEventType {
            BatchCompleted,
            ColumnInfoAvailable,
            DefaultDatabaseChanged,
            MessagePosted,
            MetaDataAvailable,
            ParameterAvailable,
            ParametersAvailable,
            RowAvailable,
            StatementCompleted,
            TableNameAvailable,
            TransactionCommitted,
            TransactionDefected,
            TransactionEnlisted,
            TransactionEnded,
            TransactionRolledBack,
            TransactionStarted,
        }


        internal SmiEventSink_Default () {
        }

        internal SmiEventSink_Default ( SmiEventSink parent ) {
            _parent = parent;
        }


        // NOTE: See the note in SmiEventSink about throwing from these methods;
        //       We're throwing here because we don't want to miss something, but
        //       you'll need to turn on Bid tracing to figure out what it is that
        //       was thrown, because they will be eaten by the server and replaced
        //       with a different exception.


        // Called at end of stream
        internal override void BatchCompleted( ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.BatchCompleted);
            }
            _parent.BatchCompleted();
        }

        internal override void ParametersAvailable(SmiParameterMetaData[] metaData, ITypedGettersV3 paramValues) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.ParametersAvailable);
            }
            _parent.ParametersAvailable(metaData, paramValues);
        }

        internal override void ParameterAvailable(SmiParameterMetaData metaData, SmiTypedGetterSetter paramValue, int ordinal) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.ParameterAvailable);
            }
            _parent.ParameterAvailable(metaData, paramValue, ordinal);
        }

        // Called when the server database context changes (ENVCHANGE token)
        internal override void DefaultDatabaseChanged( string databaseName ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.DefaultDatabaseChanged);
            }
            _parent.DefaultDatabaseChanged( databaseName );
        }

        // Called for messages and errors (ERROR and INFO tokens)
        internal override void MessagePosted ( int number, byte state, byte errorClass, string server, string message, string procedure, int lineNumber ) {
            if (null == _parent) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SmiEventSink_Default.MessagePosted|ADV> %d#, number=%d state=%d errorClass=%d server='%ls' message='%ls' procedure='%ls' linenumber=%d.\n", 
                                0,
                                number, 
                                state, 
                                errorClass, 
                                (null != server) ? server : "<null>", 
                                (null != message) ? message : "<null>", 
                                (null != procedure) ? procedure : "<null>", 
                                lineNumber
                                );
                }
                SqlError error = new SqlError ( number, state, errorClass, server, message, procedure, lineNumber );
                
                if ( error.Class < TdsEnums.MIN_ERROR_CLASS ) {
                    Warnings.Add( error );
                }
                else {
                    Errors.Add( error );
                }
            }
            else {
                _parent.MessagePosted( number, state, errorClass, server, message, procedure, lineNumber );
            }
        }

        // Called for new resultset starting (COLMETADATA token)
        internal override void MetaDataAvailable( SmiQueryMetaData[] metaData, bool nextEventIsRow ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.MetaDataAvailable);
            }
            _parent.MetaDataAvailable( metaData, nextEventIsRow );
        }

        // Called when a new row arrives (ROW token)
        internal override void RowAvailable( ITypedGetters rowData ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.RowAvailable);
            }
            _parent.RowAvailable( rowData );
        }

        // Called when a new row arrives (ROW token)
        internal override void RowAvailable( ITypedGettersV3 rowData ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.RowAvailable);
            }
            _parent.RowAvailable( rowData );
        }

        // Called when any statement completes on server (DONE token)
        internal override void StatementCompleted( int rowsAffected ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.StatementCompleted);
            }
            _parent.StatementCompleted( rowsAffected );
        }

        // Called when a transaction is commited (ENVCHANGE token)
        internal override void TransactionCommitted( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionCommitted);
            }
            _parent.TransactionCommitted( transactionId );
        }

        // Called when a transaction is commited (ENVCHANGE token)
        internal override void TransactionDefected( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionDefected);
            }
            _parent.TransactionDefected( transactionId );
        }

        // Called when a transaction is commited (ENVCHANGE token)
        internal override void TransactionEnlisted( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionEnlisted);
            }
            _parent.TransactionEnlisted( transactionId );
        }

        // Called when a transaction is forcibly ended in the server, not requested
        // by the provider's batch (ENVCHANGE token)
        internal override void TransactionEnded( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionEnded);
            }
            _parent.TransactionEnded( transactionId );
        }

        // Called when a transaction is rolled back (ENVCHANGE token)
        internal override void TransactionRolledBack( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionRolledBack);
            }
            _parent.TransactionRolledBack( transactionId );
        }

        // Called when a transaction is started (ENVCHANGE token)
        internal override void TransactionStarted( long transactionId ) {
            if (null == _parent) {
                throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionStarted);
            }
            _parent.TransactionStarted( transactionId );
        }
    }    
}

