// Copyright 2014-2023 Bloomberg Finance L.P.
// SPDX-License-Identifier: Apache-2.0
//
// 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.

// bmqt_correlationid.h                                               -*-C++-*-
#ifndef INCLUDED_BMQT_CORRELATIONID
#define INCLUDED_BMQT_CORRELATIONID

//@PURPOSE: Provide a value-semantic type usable as an efficient identifier.
//
//@CLASSES:
//  bmqt::CorrelationId:     correlation ID class.
//  bmqt::CorrelationIdLess: comparison functor for 'CorrelationId' as map key
//  bsl::hash:               hash functor specialization
//
//@DESCRIPTION: This component implements a value-semantic class,
// 'bmqt::CorrelationId', which can be used to identify any async operations.
// The correlationId contains a value (64-bit integer, raw pointer or
// sharedPtr) supplied by the application or uses an auto-assigned value.  The
// type and the value of the correlationId can be set at construction time and
// changed later via the 'setNumeric()', 'setPointer()' and 'setSharedPointer'
// methods.  Alternatively, a 'CorrelationId::AutoValue' can be used to
// generate a unique correlationId from within the process.  The
// 'bmqt::CorrelationIdLess' comparison functor can be used for storing
// 'CorrelationId' in a map as the key element; and a hash functor
// specialization is provided in the 'bsl::hash' namespace.
//
///AutoValue
///---------
// If the application doesn't care about the actual value of the correlation,
// AutoValue type can be used to create a unique correlationId.  An AutoValue
// correlationId behaves exactly the same as any other type of correlation,
// with the exception that it's value can not be retrieved (but two
// correlationId can be still compared equal).
//..
//  bmqt::CorrelationId corrId = bmqt::CorrelationId::autoValue();
//..
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Correlating Responses
/// - - - - - - - - - - - - - - - -
// Suppose that we have the following asynchronous messaging interface that we
// want to use to implement a basic request/response class.
//..
//  class Messenger {
//
//    public:
//      // TYPES
//      typedef bsl::function<
//        void(void                       *buffer,
//             int                         bufferLength,
//             const bmqt::CorrelationId&  correlationId)> MessageHandler;
//          // 'MessageHandler' is an alias for a functor that handles received
//          // messages consisting of the specified 'buffer', having the
//          // specified 'bufferLength', as well as the specified associated
//          // 'correlationId' if the message is in response to a previously
//          // transmitted message.
//
//      // MANIPULATORS
//      void sendMessage(void                       *buffer,
//                       int                         bufferLength,
//                       const bmqt::CorrelationId&  correlationId);
//          // Send a message containing the specified 'buffer' of the
//          // specified 'bufferLength', associating the specified
//          // 'correlationId' with any messages later received in response.
//
//      void setMessageHandler(const MessageHandler& handler);
//          // Set the functor to handle messages received by this object to
//          // the specified 'handler'.
//  };
//..
// First we declare a requester class.
//..
//  class Requester {
//
//      // DATA
//      Messenger        *d_messenger_p;  // used to send messages (held)
//      bslma::Allocator *d_allocator_p;  // memory supply (held)
//
//      // PRIVATE CLASS METHODS
//      static void handleMessage(void                       *buffer,
//                                int                         bufferLength,
//                                const bmqt::CorrelationId&  correlationId);
//          // Handle the response message consisting fo the specified 'buffer'
//          // having the specified 'bufferLength', and associated
//          // 'correlationId'.
//
//    public:
//      // TYPES
//      typedef bsl::function<void(void *buffer, int bufferLength)>
//                                                            ResponseCallback;
//          // 'ResponseCallback' is an alias for a functor that is used to
//          // process received responses consisting of the specified 'buffer'
//          // having the specified 'bufferLength'.
//
//      // CREATORS
//      explicit Requester(Messenger        *messenger,
//                         bslma::Allocator *basicAllocator);
//          // Create a 'Requester' that uses the specified 'messenger' to send
//          // requests and receive responses, and the specified
//          // 'basicAllocator' to supply memory.
//
//      // MANIPULATORS
//      void sendRequest(void                    *buffer,
//                       int                      bufferLength,
//                       const ResponseCallback&  callback);
//          // Send a request consisting of the specified 'buffer' having the
//          // specified 'bufferLength', and invoke the specified 'callback'
//          // with any asynchronous response.
//  };
//..
// Then, we implement the constructor, setting the message handler on the
// provided 'Messenger' to our class method.
//..
//  Requester::Requester(Messenger        *messenger,
//                       bslma::Allocator *basicAllocator)
//  : d_messenger_p(messenger)
//  , d_allocator_p(basicAllocator)
//  {
//      d_messenger_p->setMessageHandler(&Requester::handleMessage);
//  }
//..
// Now, we implement 'sendRequest', copying the given 'callback' into a
// correlationId that is provided to the messenger.
//..
//  void Requester::sendRequest(void                    *buffer,
//                              int                      bufferLength,
//                              const ResponseCallback&  callback)
//  {
//      bsl::shared_ptr<ResponseCallback> callbackCopy;
//      callbackCopy.createInplace(d_allocator_p, callback, d_allocator_p);
//      bmqt::CorrelationId correlationId(bsl::shared_ptr<void>(callbackCopy));
//      d_messenger_p->sendMessage(buffer, bufferLength, correlationId);
//  }
//..
// Finally, we implement our message handler, extracting the response callback
// from the correlationId, and invoking it with the received response message.
//..
//  void Requester::handleMessage(void                       *buffer,
//                                int                         bufferLength,
//                                const bmqt::CorrelationId&  correlationId)
//  {
//      assert(correlationId.isSharedPtr());
//      ResponseCallback *callback = static_cast<ResponseCallback *>(
//                                         correlationId.theSharedPtr().get());
//      (*callback)(buffer, bufferLength);
//  }
//..
//


// BMQ
#include <bmqscm_version.h>

// BDE
#include <bdlb_variant.h>

#include <bsl_cstdint.h>
#include <bsl_iosfwd.h>
#include <bsl_memory.h>
#include <bsls_assert.h>
#include <bsls_types.h>
#include <bslh_hash.h>

namespace BloombergLP {
namespace bmqt {


                            // ===================
                            // class CorrelationId
                            // ===================

class CorrelationId {
    // This class implements an in-core value-semantic type for correlating an
    // asynchronous result with the original operation.

    // FRIENDS
    //  Needed to access getAsAutoValue
    friend bool operator==(const CorrelationId& lhs, const CorrelationId& rhs);
    friend bool operator!=(const CorrelationId& lhs, const CorrelationId& rhs);
    friend struct CorrelationIdLess;
    friend bsl::ostream& operator<<(bsl::ostream&        stream,
                                    const CorrelationId& rhs);

    template <class HASH_ALGORITHM>
    friend
    void hashAppend(HASH_ALGORITHM& hashAlgo, const CorrelationId& value);

  private:
    // PRIVATE TYPES
    typedef bsls::Types::Uint64 AutoValue;
        // 'AutoValue' is an alias for a 64-bit unsigned integer value that is
        // used to generate automatic, opaque, correlationId values.

    // DATA
    bdlb::Variant4<bsls::Types::Int64,
                   void*,
                   bsl::shared_ptr<void>,
                   AutoValue> d_variant;
                               // The variant used to hold the value of a
                               // 'CorrelationId', that may either be unset,
                               // hold a 64-bit integer, a raw pointer, a
                               // shared pointer, or an 'AutoValue'.

    // PRIVATE ACCESSORS
    AutoValue theAutoValue() const;
        // Return the value of this correlationId as an 'AutoValue'.  The
        // behavior is undefined unless 'isAutoValue'.  Note that this method
        // is only available in order to implement the comparison and hash
        // operations.

  public:
    // TYPES
    enum Type {
        e_NUMERIC     // the 'CorrelationId' holds a 64-bit integer
      , e_POINTER     // the 'CorrelationId' holds a raw pointer
      , e_SHARED_PTR  // the 'CorrelationId' holds a shared pointer
      , e_AUTO_VALUE  // the 'CorrelationId' holds an auto value
      , e_UNSET       // the 'CorrelationId' is not set
    };

    // CLASS METHOD
    static CorrelationId autoValue();
        // Return a 'CorrelationId' having a unique, automatically assigned
        // opaque value.

    // CREATORS
    explicit CorrelationId();
        // Create a 'CorrelationId' having the default, unset, value.

    explicit CorrelationId(bsls::Types::Int64 numeric);
        // Create a 'CorrelationId' object initialized with the specified
        // 'numeric' integer value.

    explicit CorrelationId(void *pointer);
        // Create a 'CorrelationId' object initialized with the specified
        // 'pointer' value.

    explicit CorrelationId(const bsl::shared_ptr<void>& sharedPtr);
        // Create a 'CorrelationId' object initialized with the specified
        // 'sharedPtr' shared pointer value.

    // MANIPULATORS
    CorrelationId& makeUnset();
        // Unset the value of this object.

    CorrelationId& setNumeric(bsls::Types::Int64 numeric);
        // Set the value of this object value to the specified 'numeric'.
        // Return a reference to this object.

    CorrelationId& setPointer(void* pointer);
        // Set the value of this object value to the specified 'pointer'.
        // Return a reference to this object.

    CorrelationId& setSharedPointer(const bsl::shared_ptr<void>& sharedPtr);
        // Set the value of this object value to the specified 'sharedPtr'.
        // Return a reference to this object.

    // void setAutoValue
        // There is explicitly no setAuto, autoCorrelation should only be used
        // at creation

    // ACCESSORS
    bool isUnset() const;
        // Return 'true' if the value of this object is unset;

    bool isNumeric() const;
        // Return 'true' if the value of this object is an integer value, and
        // otherwise return 'false'.

    bool isPointer() const;
        // Return 'true' if the value of this object is a pointer value, and
        // otherwise return 'false'.

    bool isSharedPtr() const;
        // Return 'true' if the value of this object is a shared pointer value,
        // and otherwise return 'false'.

    bool isAutoValue() const;
        // Return 'true' if the value of this object is an auto correlation
        // value, and otherwise return 'false'.

    bsls::Types::Int64 theNumeric() const;
        // Return the 64-bit integer value of this object.  The behavior is
        // undefined unless method 'isNumeric' returns 'true'.

    void* thePointer() const;
        // Return the pointer value of this object.  The behavior is undefined
        // unless method 'isPointer' returns 'true'.

    const bsl::shared_ptr<void>& theSharedPtr() const;
        // Return the pointer value of this object.  The behavior is undefined
        // unless method 'isSharedPtr' returns 'true'.

    Type type() const;
        // Return the type of the value of this object.

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

// FREE OPERATORS

                            // -------------------
                            // class CorrelationId
                            // -------------------

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

bool operator==(const CorrelationId& lhs, const CorrelationId& rhs);
    // Return 'true' if 'rhs' object contains the value of the same type as
    // contained in 'lhs' object and the value itself is the same in both
    // objects, return false otherwise.

bool operator!=(const CorrelationId& lhs, const CorrelationId& rhs);
    // Return 'false' if 'rhs' object contains the value of the same type as
    // contained in 'lhs' object and the value itself is the same in both
    // objects, return 'true' otherwise.

bool operator<(const CorrelationId& lhs, const CorrelationId& rhs);
    // Operator used to allow comparison between the specified 'lhs' and 'rhs'
    // CorrelationId objects so that CorrelationId can be used as key in a map.


                          // =======================
                          // class CorrelationIdLess
                          // =======================

struct CorrelationIdLess
{
    // ACCESSORS
    bool operator()(const CorrelationId& lhs, const CorrelationId& rhs) const;
        // Return 'true' if the specified 'lhs' should be considered as having
        // a value less than the specified 'rhs'.
};


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


                            // -------------------
                            // class CorrelationId
                            // -------------------

inline
CorrelationId::AutoValue
CorrelationId::theAutoValue() const
{
    // PRECONDITIONS
    BSLS_ASSERT(isAutoValue());

    return d_variant.the<AutoValue>();
}

// CREATORS
inline
CorrelationId::CorrelationId()
: d_variant()
{
    // NOTHING
}

inline
CorrelationId::CorrelationId(bsls::Types::Int64 numeric)
: d_variant(numeric)
{
    // NOTHING
}

inline
CorrelationId::CorrelationId(void *pointer)
: d_variant(pointer)
{
    // NOTHING
}

inline
CorrelationId::CorrelationId(const bsl::shared_ptr<void>& sharedPtr)
: d_variant(sharedPtr)
{
    // NOTHING
}

// MANIPULATORS
inline
CorrelationId&
CorrelationId::makeUnset()
{
    d_variant.reset();
    return *this;
}

inline
CorrelationId&
CorrelationId::setNumeric(bsls::Types::Int64 numeric)
{
    d_variant.assign(numeric);
    return *this;
}

inline
CorrelationId&
CorrelationId::setPointer(void* pointer)
{
    d_variant.assign(pointer);
    return *this;
}

inline
CorrelationId&
CorrelationId::setSharedPointer(const bsl::shared_ptr<void>& sharedPtr)
{
    d_variant.assign(sharedPtr);
    return *this;
}

// ACCESSORS
inline
bool
CorrelationId::isUnset() const
{
    return d_variant.isUnset();
}

inline
bool
CorrelationId::isNumeric() const
{
    return d_variant.is<bsls::Types::Int64>();
}

inline
bool
CorrelationId::isPointer() const
{
    return d_variant.is<void*>();
}

inline
bool
CorrelationId::isSharedPtr() const
{
    return d_variant.is<bsl::shared_ptr<void> >();
}

inline
bool
CorrelationId::isAutoValue() const
{
    return d_variant.is<AutoValue>();
}

inline
bsls::Types::Int64
CorrelationId::theNumeric() const
{
    // PRECONDITIONS
    BSLS_ASSERT(isNumeric());

    return d_variant.the<bsls::Types::Int64>();
}

inline
void*
CorrelationId::thePointer() const
{
    // PRECONDITIONS
    BSLS_ASSERT(isPointer());

    return d_variant.the<void*>();
}

inline
const bsl::shared_ptr<void>&
CorrelationId::theSharedPtr() const
{
    // PRECONDITIONS
    BSLS_ASSERT(isSharedPtr());

    return d_variant.the<bsl::shared_ptr<void> >();
}

inline
CorrelationId::Type
CorrelationId::type() const
{
    int result = isUnset()     ? e_UNSET
               : isNumeric()   ? e_NUMERIC
               : isPointer()   ? e_POINTER
               : isSharedPtr() ? e_SHARED_PTR
               : isAutoValue() ? e_AUTO_VALUE
               : -1;
    BSLS_ASSERT_OPT(-1 != result && "Unknown correlation type");

    return static_cast<Type>(result);
}

// FREE FUNCTIONS
template <class HASH_ALGORITHM>
void
hashAppend(HASH_ALGORITHM&      hashAlgo,
           const CorrelationId& value)
{
    using bslh::hashAppend;  // for ADL

    CorrelationId::Type type = value.type();
    hashAppend(hashAlgo, static_cast<int>(type));

    switch (type) {
      case CorrelationId::e_NUMERIC: {
          hashAppend(hashAlgo, value.theNumeric());
      } break;
      case CorrelationId::e_POINTER: {
          hashAppend(hashAlgo, value.thePointer());
      } break;
      case CorrelationId::e_SHARED_PTR: {
          hashAppend(hashAlgo, value.theSharedPtr());
      } break;
      case CorrelationId::e_AUTO_VALUE: {
          hashAppend(hashAlgo, value.theAutoValue());
      } break;
      case CorrelationId::e_UNSET: {
          hashAppend(hashAlgo, 0);
      } break;
      default: {
        BSLS_ASSERT_OPT(false && "Unknown correlationId type");
        hashAppend(hashAlgo, 0);
      }
    }
}

                          // ------------------------
                          // struct CorrelationIdLess
                          // ------------------------

inline
bool
CorrelationIdLess::operator()(const CorrelationId& lhs,
                              const CorrelationId& rhs) const
{
    // If 'lhs' and 'rhs' have different types, they are compared by their
    // corresponding type indices.  Otherwise, they are compared by value.

    bool result;
    if (lhs.d_variant.typeIndex() != rhs.d_variant.typeIndex()) {
        result = lhs.d_variant.typeIndex() < rhs.d_variant.typeIndex();
    } else if (lhs.isNumeric()) {
        result = lhs.theNumeric() < rhs.theNumeric();
    } else if (lhs.isPointer()) {
        result = reinterpret_cast<bsl::uintptr_t>(lhs.thePointer()) <
                 reinterpret_cast<bsl::uintptr_t>(rhs.thePointer());
    } else if (lhs.isSharedPtr()) {
        result = lhs.theSharedPtr() < rhs.theSharedPtr();
    } else if (lhs.isAutoValue()) {
        result = lhs.theAutoValue() < rhs.theAutoValue();
    } else {
        BSLS_ASSERT_OPT(false && "Unknown correlator type");
        result = false;
    }

    return result;
}

}  // close package namespace

                            // -------------------
                            // class CorrelationId
                            // -------------------

// FREE OPERATORS
inline
bool
bmqt::operator==(const bmqt::CorrelationId& lhs,
                 const bmqt::CorrelationId& rhs)
{
    return lhs.d_variant == rhs.d_variant;
}

inline
bool
bmqt::operator!=(const bmqt::CorrelationId& lhs,
                 const bmqt::CorrelationId& rhs)
{
    return lhs.d_variant != rhs.d_variant;
}

inline
bool
bmqt::operator<(const bmqt::CorrelationId& lhs,
                const bmqt::CorrelationId& rhs)
{
    CorrelationIdLess less;
    return less(lhs, rhs);
}

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

}  // close enterprise namespace

#endif