// ball_context.h                                                     -*-C++-*-

// ----------------------------------------------------------------------------
//                                   NOTICE
//
// This component is not up to date with current BDE coding standards, and
// should not be used as an example for new development.
// ----------------------------------------------------------------------------

#ifndef INCLUDED_BALL_CONTEXT
#define INCLUDED_BALL_CONTEXT

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a container for the context of a transmitted log record.
//
//@CLASSES:
//  ball::Context: context of a transmitted log record
//
//@SEE_ALSO: ball_transmission, ball_observer, ball_record
//
//@DESCRIPTION: This component defines a container for aggregating a message's
// publication cause, as well as the record (or message) index and sequence
// length of messages delivered as part of a message sequence.  Note that
// messages that are not part of a sequence (i.e., PASSTHROUGH) will have the
// index and sequence length fields set to 0 and 1, respectively.
//
// The context attributes held by 'ball::Context' are detailed in the following
// table:
//..
//  Attribute          Type                      Description        Default
//  -----------------  ------------------------  -----------------  -----------
//  transmissionCause  ball::Transmission::Cause cause of output    PASSTHROUGH
//  recordIndex        int                       index in sequence  0
//  sequenceLength     int                       # records in seq.  1
//..
//
///Constraints
///-----------
// This attribute class assumes that the following constraints on contained
// values hold:
//..
//    if (ball::Transmission::e_PASSTHROUGH == transmissionCause()) {
//        assert(0 == recordIndex());
//        assert(1 == sequenceLength());
//    }
//    else {
//        assert(
//         ball::Transmission::e_TRIGGER            == transmissionCause()
//      || ball::Transmission::e_TRIGGER_ALL        == transmissionCause()
//      || ball::Transmission::e_MANUAL_PUBLISH     == transmissionCause()
//      || ball::Transmission::e_MANUAL_PUBLISH_ALL == transmissionCause());
//        assert(0 <= recordIndex());
//        assert(1 <= sequenceLength());
//        assert(recordIndex() < sequenceLength());
//    }
//..
// A static 'isValid' method is provided to verify that particular
// 'transmissionCause', 'recordIndex', and 'sequenceLength' values are valid
// before they are used to create or (unilaterally) modify a context object.
//
///Usage
///-----
// A 'ball::Context' object holds sufficient information to determine the
// length of a message sequence and the index of a message within that
// sequence.  In addition, 'ball::Context' indicates the cause for the
// transmission of a message.  The following example illustrates the essentials
// of working with these contextual attributes.
//
// This example illustrates the use of 'ball::Context' by a hypothetical
// logging system.  First we define a simple logger class named 'my_Logger':
//..
//    // my_logger.h
//
//    #include <string>
//    #include <vector>
//
//    class my_Logger {
//
//        bsl::vector<bsl::string> archive;  // log message archive
//
//        // NOT IMPLEMENTED
//        my_Logger(const my_Logger&);
//        my_Logger& operator=(const my_Logger&);
//
//        // PRIVATE MANIPULATORS
//        void publish(const bsl::string&   message,
//                     const ball::Context& context);
//
//      public:
//        // TYPES
//        enum Severity { ERROR = 0, WARN = 1, TRACE = 2 };
//
//        // CREATORS
//        my_Logger();
//        ~my_Logger();
//
//        // MANIPULATORS
//        void logMessage(const bsl::string& message, Severity severity);
//    };
//..
// Clients of 'my_Logger' log messages at one of three severity levels through
// the 'logMessage' method.  Messages logged with 'TRACE' severity are simply
// archived by 'my_Logger'.  Messages logged with 'WARN' severity are archived
// and also output to 'stdout' (say, to a console terminal overseen by an
// operator) through the 'publish' method.  Messages logged with 'ERROR'
// severity report serious conditions; these trigger a dump of the backlog of
// messages that 'my_Logger' has archived to that point.  The 'ball::Context'
// argument passed to 'publish' provides contextual information regarding the
// message it is being asked to publish.
//
// A complete implementation of this trivial logger follows:
//..
//    // my_Logger.cpp
//
//    // PRIVATE MANIPULATORS
//    void my_Logger::publish(const bsl::string&   message,
//                            const ball::Context& context)
//    {
//        using namespace std;
//
//        switch (context.transmissionCause()) {
//          case ball::Transmission::e_PASSTHROUGH: {
//            cout << "Single Pass-through Message: ";
//          } break;
//          case ball::Transmission::e_TRIGGER_ALL: {
//            cout << "Remotely ";               // no 'break'; concatenated
//                                               // output
//          } break;
//          case ball::Transmission::e_TRIGGER: {
//            cout << "Triggered Publication Sequence: Message "
//                 << context.recordIndex() + 1  // Account for 0-based index.
//                 << " of " << context.sequenceLength() << ": ";
//          } break;
//          case ball::Transmission::e_MANUAL_PUBLISH: {
//            cout << "Manually triggered Message: ";
//          } break;
//          default: {
//            cout << "***ERROR*** Unsupported Message Cause: ";
//            return;
//          } break;
//        }
//        cout << message << endl;
//    }
//
//    // CREATORS
//    my_Logger::my_Logger() { }
//    my_Logger::~my_Logger() { }
//
//    // MANIPULATORS
//    void my_Logger::logMessage(const bsl::string& message, Severity severity)
//    {
//        archive.append(message);
//        switch (severity) {
//          case TRACE: {
//            // Do nothing beyond archiving the message.
//          } break;
//          case WARN: {
//            ball::Context context(ball::Transmission::e_PASSTHROUGH, 0, 1);
//            publish(message, context);
//          } break;
//          case ERROR: {
//            int index  = 0;
//            int length = archive.length();
//            ball::Context context(ball::Transmission::e_TRIGGER,
//                                  index, length);
//            while (length--) {
//                publish(archive[length], context);
//                context.setRecordIndexRaw(++index);
//            }
//            archive.removeAll();  // flush archive
//          } break;
//        }
//    }
//..
// Note that 'ball::Transmission::e_TRIGGER_ALL' is not used by 'my_Logger',
// but is included in the switch statement for completeness.
//
// Finally, we declare a 'my_Logger' named 'logger' and simulate the logging of
// several messages of varying severity:
//..
//      my_Logger   logger;
//      bsl::string message;
//
//      message = "TRACE 1";  logger.logMessage(message, my_Logger::TRACE);
//      message = "TRACE 2";  logger.logMessage(message, my_Logger::TRACE);
//      message = "WARNING";  logger.logMessage(message, my_Logger::WARN);
//      message = "TRACE 3";  logger.logMessage(message, my_Logger::TRACE);
//      message = "TROUBLE!"; logger.logMessage(message, my_Logger::ERROR);
//..
// The following output is produced on 'stdout':
//..
//      Single Pass-through Message: WARNING
//      Triggered Publication Sequence: Message 1 of 5: TROUBLE!
//      Triggered Publication Sequence: Message 2 of 5: TRACE 3
//      Triggered Publication Sequence: Message 3 of 5: WARNING
//      Triggered Publication Sequence: Message 4 of 5: TRACE 2
//      Triggered Publication Sequence: Message 5 of 5: TRACE 1
//..
// Note that the warning message (severity 'WARN') was emitted first since the
// trace messages (severity 'TRACE') were simply archived.  When the error
// message (severity 'ERROR') was logged, it triggered a dump of the complete
// message archive (in reverse order).

#include <balscm_version.h>

#include <ball_transmission.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsls_platform.h>

#include <bsl_iosfwd.h>

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
#if defined(BSLS_PLATFORM_CMP_MSVC) && defined(PASSTHROUGH)
    // Note: on Windows -> WinGDI.h:#define PASSTHROUGH 19
#undef PASSTHROUGH
#endif
#endif // BDE_OMIT_INTERNAL_DEPRECATED

namespace BloombergLP {
namespace ball {

                        // =============
                        // class Context
                        // =============

class Context {
    // This class provides a container for aggregating the auxiliary
    // information needed to transmit a log record.  For each context attribute
    // in this class (e.g., 'recordIndex'), there is an accessor for obtaining
    // the attribute's value ('recordIndex') and there are manipulators for
    // changing the contained attribute values ('setAttributes' checks
    // attribute constraints; 'setAttributesRaw' and 'setRecordIndexRaw' do
    // not).  A static 'isValid' method is also provided to verify that
    // particular attribute values are consistent before they are used to
    // create or modify a context object.  Note that it is the client's
    // responsibility not to construct or unilaterally modify a context object
    // to hold incompatible attribute values.
    //
    // Additionally, this class supports a complete set of *value* *semantic*
    // operations, including copy construction, assignment and equality
    // comparison, and 'ostream' printing.  A precise operational definition of
    // when two instances have the same value can be found in the description
    // of 'operator==' for the class.  This class is *exception* *neutral* with
    // no guarantee of rollback: if an exception is thrown during the
    // invocation of a method on a pre-existing instance, the object is left in
    // a valid state, but its value is undefined.  In no event is memory
    // leaked.  Finally, *aliasing* (e.g., using all or part of an object as
    // both source and destination) is supported in all cases.

    // DATA
    Transmission::Cause d_transmissionCause;  // cause of transmitted record
    int                 d_recordIndex;        // 0-based index within sequence
    int                 d_sequenceLength;     // number of records in sequence

    // PRIVATE TYPES
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    // FRIENDS
    friend bool operator==(const Context&, const Context&);

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(Context, bslma::UsesBslmaAllocator);

    // CLASS METHODS
    static bool isValid(Transmission::Cause transmissionCause,
                        int                 recordIndex,
                        int                 sequenceLength);
        // Return 'true' if the specified 'transmissionCause', 'recordIndex',
        // and 'sequenceLength' represent a valid context, and 'false'
        // otherwise.  (See the CONSTRAINTS section of the component-level
        // documentation above for a complete specification of the constraints
        // on attribute values.)

    // CREATORS
    Context(bslma::Allocator *basicAllocator = 0);
        // Create a context object with all attributes having default values.
        // Optionally specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.  Note that 'basicAllocator' is currently ignored.

    Context(Transmission::Cause  transmissionCause,
            int                  recordIndex,
            int                  sequenceLength,
            bslma::Allocator    *basicAllocator = 0);
        // Create a context object indicating the specified
        // 'transmissionCause', 'recordIndex', and 'sequenceLength' values.
        // Optionally specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.  The behavior is undefined unless the resulting attribute
        // values are compatible.  Note that 'basicAllocator' is currently
        // ignored.

    Context(const Context& original, bslma::Allocator *basicAllocator = 0);
        // Create a context object having the value of the specified 'original'
        // context object.  Optionally specify a 'basicAllocator' used to
        // supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.  Note that 'basicAllocator' is currently
        // ignored.

    // ~Context();
        // Destroy this context object.  Note that this trivial destructor is
        // generated by the compiler.

    // MANIPULATORS
    Context& operator=(const Context& rhs);
        // Assign to this context object the value of the specified 'rhs'
        // context object.

    int setAttributes(Transmission::Cause transmissionCause,
                      int                 recordIndex,
                      int                 sequenceLength);
        // Set the value of this context object to the specified
        // 'transmissionCause', 'recordIndex', and 'sequenceLength' values if
        // 'transmissionCause', 'recordIndex', and 'sequenceLength' represent a
        // valid context.  Return 0 on success, and a non-zero value (with no
        // effect on this context object) otherwise.

    void setAttributesRaw(Transmission::Cause transmissionCause,
                          int                 recordIndex,
                          int                 sequenceLength);
        // Set the value of this context object to the specified
        // 'transmissionCause', 'recordIndex', and 'sequenceLength' values.
        // The behavior is undefined if the resulting attribute values are
        // incompatible.

    void setRecordIndexRaw(int index);
        // Set the record index attribute of this context object to the
        // specified 'index'.  The behavior is undefined if the resulting
        // attribute values are incompatible.

    // ACCESSORS
    Transmission::Cause transmissionCause() const;
        // Return the transmission cause attribute of this context object.

    int recordIndex() const;
        // Return the record index attribute of this context object.

    int sequenceLength() const;
        // Return the sequence length attribute of this context object.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
        // Format this object to the specified output 'stream' at the
        // optionally specified indentation 'level' and return a reference to
        // the modifiable 'stream'.  If 'level' is specified, optionally
        // specify 'spacesPerLevel', the number of spaces per indentation level
        // for this and all of its nested objects.  Each line is indented by
        // the absolute value of 'level * spacesPerLevel'.  If 'level' is
        // negative, suppress indentation of the first line.  If
        // 'spacesPerLevel' is negative, suppress line breaks and format the
        // entire output on one line.  If 'stream' is initially invalid, this
        // operation has no effect.
};

// FREE OPERATORS
bool operator==(const Context& lhs, const Context& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' context objects have the
    // same value, and 'false' otherwise.  Two context objects have the same
    // value if each respective pair of corresponding attributes have the same
    // value.

bool operator!=(const Context& lhs, const Context& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' context objects do not
    // have the same value, and 'false' otherwise.  Two context objects do not
    // have the same value if one or more respective attributes differ in
    // value.

bsl::ostream& operator<<(bsl::ostream& stream, const Context& rhs);
    // Write the specified 'rhs' context to the specified output 'stream' and
    // return a reference to the modifiable 'stream'.

// ============================================================================
//                              INLINE DEFINITIONS
// ============================================================================

                        // -------------
                        // class Context
                        // -------------

// CREATORS
inline
Context::Context(bslma::Allocator *)
: d_transmissionCause(Transmission::e_PASSTHROUGH)
, d_recordIndex(0)
, d_sequenceLength(1)
{
}

inline
Context::Context(Transmission::Cause  transmissionCause,
                 int                  recordIndex,
                 int                  sequenceLength,
                 bslma::Allocator    *)
: d_transmissionCause(transmissionCause)
, d_recordIndex(recordIndex)
, d_sequenceLength(sequenceLength)
{
}

inline
Context::Context(const Context& original, bslma::Allocator *)
: d_transmissionCause(original.d_transmissionCause)
, d_recordIndex(original.d_recordIndex)
, d_sequenceLength(original.d_sequenceLength)
{
}

// MANIPULATORS
inline
Context& Context::operator=(const Context& rhs)
{
    d_transmissionCause = rhs.d_transmissionCause;
    d_recordIndex       = rhs.d_recordIndex;
    d_sequenceLength    = rhs.d_sequenceLength;
    return *this;
}

inline
void Context::setAttributesRaw(Transmission::Cause transmissionCause,
                               int                 recordIndex,
                               int                 sequenceLength)
{
    d_transmissionCause = transmissionCause;
    d_recordIndex       = recordIndex;
    d_sequenceLength    = sequenceLength;
}

inline
int Context::setAttributes(Transmission::Cause transmissionCause,
                           int                 recordIndex,
                           int                 sequenceLength)
{
    if (isValid(transmissionCause, recordIndex, sequenceLength)) {
        setAttributesRaw(transmissionCause, recordIndex, sequenceLength);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Context::setRecordIndexRaw(int index)
{
    d_recordIndex = index;
}

// ACCESSORS
inline
Transmission::Cause Context::transmissionCause() const
{
    return d_transmissionCause;
}

inline
int Context::recordIndex() const
{
    return d_recordIndex;
}

inline
int Context::sequenceLength() const
{
    return d_sequenceLength;
}

}  // close package namespace

// FREE OPERATORS
inline
bool ball::operator==(const Context& lhs, const Context& rhs)
{
    return lhs.d_transmissionCause == rhs.d_transmissionCause
        && lhs.d_recordIndex       == rhs.d_recordIndex
        && lhs.d_sequenceLength    == rhs.d_sequenceLength;
}

inline
bool ball::operator!=(const Context& lhs, const Context& rhs)
{
    return !(lhs == rhs);
}

inline
bsl::ostream& ball::operator<<(bsl::ostream& stream, const Context& rhs)
{
    return rhs.print(stream, 0, -1);
}

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------