/* Copyright 2012. Bloomberg Finance L.P.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:  The above
 * copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
// blpapi_correlationid.h                                             -*-C++-*-
#ifndef INCLUDED_BLPAPI_CORRELATIONID
#define INCLUDED_BLPAPI_CORRELATIONID

//@PURPOSE: Provide a key to identify individual subscriptions or requests
//
//@CLASSES:
// blpapi::CorrelationId: a key to track requests and subscriptions
//
//@DESCRIPTION: This component provides an identifier that is attached on to
// individual subscriptions and requests. CorrelationId are used to distinguish
// between various subscriptions and are a way to find the response for an
// asynchronous request.

#ifndef INCLUDED_BLPAPI_TYPES
#include <blpapi_types.h>
#endif

#ifndef INCLUDED_BLPAPI_DEFS
#include <blpapi_defs.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct blpapi_ManagedPtr_t_;
typedef struct blpapi_ManagedPtr_t_ blpapi_ManagedPtr_t;

typedef int (*blpapi_ManagedPtr_ManagerFunction_t)(
                                            blpapi_ManagedPtr_t *managedPtr,
                                            const blpapi_ManagedPtr_t *srcPtr,
                                            int operation);

typedef union {
    int   intValue;
    void *ptr;
} blpapi_ManagedPtr_t_data_;

struct blpapi_ManagedPtr_t_ {
    void *pointer;
    blpapi_ManagedPtr_t_data_ userData[4];
    blpapi_ManagedPtr_ManagerFunction_t  manager;
};

typedef struct blpapi_CorrelationId_t_ {
    unsigned int  size:8;       // fill in the size of this struct
    unsigned int  valueType:4;  // type of value held by this correlation id
    unsigned int  classId:16;   // user defined classification id
    unsigned int  reserved:4;   // for internal use must be 0

    union {
        blpapi_UInt64_t      intValue;
        blpapi_ManagedPtr_t  ptrValue;
    } value;
} blpapi_CorrelationId_t;


#ifdef __cplusplus
}

#ifndef INCLUDED_CSTRING
#include <cstring>
#define INCLUDED_CSTRING
#endif

#ifndef INCLUDED_OSTREAM
#include <ostream>
#define INCLUDED_OSTREAM
#endif

#ifndef INCLUDED_ALGORITHM
#include <algorithm> // for swap
#define INCLUDED_ALGORITHM
#endif

namespace BloombergLP {
namespace blpapi {

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

class CorrelationId {
    // A key used to identify individual subscriptions or requests.
    //
    // CorrelationId objects are passed to many of the Session object
    // methods which initiate an asynchronous operations and are
    // obtained from Message objects which are delivered as a result
    // of those asynchronous operations.
    //
    // When subscribing or requesting information an application has
    // the choice of providing a CorrelationId they construct
    // themselves or allowing the session to construct one for
    // them. If the application supplies a CorrelationId it must not
    // re-use the value contained in it in another CorrelationId
    // whilst the original request or subscription is still active.
    //
    // It is possible that an application supplied CorrelationId and a
    // CorrelationId constructed by the API could return the same
    // result for asInteger(). However, they will not compare equal
    // using the defined operator== for CorrelationId and there is a
    // consistent order defined using the defined operator< for
    // CorrelationId.
    //
    // A CorrelationId constructed by an application can contain either
    //
    // - a 64 bit integer,
    //
    // - a simple pointer or
    //
    // - a "smart" pointer object (for example, tr1::shared_ptr)
    //
    // For 64 bit integers and simple pointers the values are copied
    // when CorrelationIds are copied and compared when CorrelationIds
    // are compared.
    //
    // For "smart" pointers the API can accommodate smart pointer classes
    // that meet the following restrictions.
    //
    // - It is no more than sizeof(void*)*4 bytes in size.
    //
    // - It is thread safe.
    //
    // - It performs all its necessary management as a direct side
    // effect of their copy constructor and destructor.
    //
    // - Its contents are location independent (that is given two
    // instances of a smart pointer s1 and s2 one can call std::swap(s1,
    // s2) ).
    //
    // The API will embed a smart pointer in the CorrelationId without
    // allocating memory separately for it. The specified 'smartPtr'
    // will have its copy constructor invoked when the CorrelationId
    // is copied and its destructor invoked when the CorrelationId is
    // destroyed so its resource management will continue to work as
    // normal.
    //
    // CorrelationId's based on a simple pointer and CorrelationId's
    // based on a smart pointer have the same ValueType
    // (POINTER_VALUE) which allows them to be compared to each other.
    //
    // A CorrelationId based on a simple pointer and a CorrelationId
    // based on a smart pointer will compare equally with operator==
    // as long as the pointer is the same.
    //
    // Likewise, when comparing two CorrelationId's based on a smart
    // pointer only the pointer value itself is used for the
    // comparison, the contents of the smart pointer object are
    // ignored.

    blpapi_CorrelationId_t d_impl;

    void copy(const blpapi_CorrelationId_t& src);

    template <typename TYPE>
    static int managerFunc(blpapi_ManagedPtr_t *managedPtr,
        const blpapi_ManagedPtr_t *srcPtr, int operation);

    template <typename TYPE>
    static void assertSmartPtrFits();

  public:
    // Possible return values for valueType() method.

    enum ValueType {
      UNSET_VALUE   = BLPAPI_CORRELATION_TYPE_UNSET,
          // The CorrelationId is unset. That is, it was created by the default
          // CorrelationId constructor.
      INT_VALUE     = BLPAPI_CORRELATION_TYPE_INT,
          // The CorrelationId was created from an integer supplied by the
          // user.
      POINTER_VALUE = BLPAPI_CORRELATION_TYPE_POINTER,
          // The CorrelationId was created from a pointer supplied by the user.
      AUTOGEN_VALUE = BLPAPI_CORRELATION_TYPE_AUTOGEN
          // The CorrelationId was created internally by API.
    };

    // The maximum value allowed for classId

    enum {
        MAX_CLASS_ID = BLPAPI_CORRELATION_MAX_CLASS_ID // The maximum value
                                                       // allowed for classId
    };

    CorrelationId();
        // The default constructor creates an uninitialized
        // CorrelationId. This will compare equal to another
        // CorrelationId object constructed using the default
        // constructor. The only valid operations on an uninitialized
        // CorrelationId are assignment, comparison for equality and
        // destruction.

    CorrelationId(const blpapi_CorrelationId_t &correlation);

    CorrelationId(const CorrelationId& original);
        // Copy constructor. If the specified 'original' contains a
        // smart pointer it will be copy constructed into this
        // CorrelationId.

    explicit CorrelationId(long long value, int classId = 0);
        // Construct a CorrelationId object and initialize it with the
        // specified integer 'value'.

    explicit CorrelationId(void *value, int classId = 0);
        // Construct a CorrelationId object and initialize it with the
        // specified pointer 'value'.

    template<typename TYPE>
    CorrelationId(const TYPE& smartPtr, void *pointerValue, int classId = 0);
        // Construct a CorrelationId object and initialize it with the
        // specified 'smartPtr' (whose copy constructor will be called
        // in the process) and with the specified 'pointerValue' which
        // should be the result of operator-> on the specified
        // 'smartPtr'.

    ~CorrelationId();
        // Destroy this CorrelationId. If this CorrelationId contains
        // a smart pointer its destructor will be called.

    // MANIPULATORS

    blpapi_CorrelationId_t& impl();

    void swap(CorrelationId &other);
        // Swap the value of this CorrelationId object and the
        // specified 'other' CorrelationId object.

    CorrelationId& operator=(const CorrelationId &rhs);
        // Assign to this CorrelationId object the value of the
        // specified 'rhs' CorrelationId object.  Return a modifiable
        // reference to this object.

    // ACCESSORS

    ValueType valueType() const;
        // Return the type of this CorrelationId object.

    unsigned short classId() const;
        // Return the user defined classification of this correlation
        // correlation id object.

    void* asPointer() const;
        // Return the value of this CorrelationId as a pointer
        // value. The result is undefined if this CorrelationId does
        // not have valueType()==POINTER_VALUE.

    template<typename TYPE>
    TYPE asSmartPointer() const;
        // Return the CorrelationId as a smart pointer.

    long long asInteger() const;
        // Return the value of this CorrelationId as an integer
        // value. The result is undefined if this CorrelationId does
        // not have valueType()==INT_VALUE or
        // valueType()==AUTOGEN_VALUE.

    const blpapi_CorrelationId_t& impl() const;
};

// FREE OPERATORS
inline
bool operator==(const CorrelationId& lhs, const CorrelationId& rhs);
    // Return true if the specified 'lhs' and 'rhs' CorrelationId
    // objects contain the same value. Return false otherwise. Two
    // CorrelationId objects contain the same value if the result of
    // valueType() is the same and the result of asPointer() or
    // asInteger() as appropriate is also the same.

inline
bool operator!=(const CorrelationId& lhs, const CorrelationId& rhs);
    // Equivalent to !(lhs==rhs).

inline
bool operator<(const CorrelationId& lhs, const CorrelationId& rhs);
    // Return true if the value of the specified 'lhs' CorrelationId
    // object is less than the value of the specified 'rhs'
    // CorrelationId object. Return false otherwise. The operator
    // takes account of the value of the CorrelationId as well as its
    // valueType() to ensure a consistent ordering amongst
    // CorrelationIds regardless of whether they are generated by the
    // API or the user. The operator is provided solely for
    // convenience for operations such as insertion into ordered
    // containers.

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

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

inline
CorrelationId::CorrelationId()
{
    std::memset(&d_impl, 0, sizeof(d_impl));
}

inline
CorrelationId::CorrelationId(const blpapi_CorrelationId_t &correlationId)
{
    copy(correlationId);
}

inline
CorrelationId::CorrelationId(const CorrelationId& original)
{
    copy(original.d_impl);
}

inline
CorrelationId::CorrelationId(long long intValue, int newClassId)
{
    std::memset(&d_impl, 0, sizeof(d_impl));

    d_impl.size           = static_cast<unsigned>(sizeof(d_impl));
    d_impl.valueType      = INT_VALUE;
    d_impl.value.intValue = intValue;
    d_impl.classId        = newClassId;
}

inline
CorrelationId::CorrelationId(void *ptrValue, int newClassId)
{
    std::memset(&d_impl, 0, sizeof(d_impl));

    d_impl.size                   = static_cast<unsigned>(sizeof(d_impl));
    d_impl.valueType              = POINTER_VALUE;
    d_impl.value.ptrValue.pointer = ptrValue;
    d_impl.classId                = newClassId;
}

template <typename TYPE>
inline
CorrelationId::CorrelationId(const TYPE& smartPtr, void *ptrValue,
    int newClassId)
{
    // If you get a compiler error here, the specified smart pointer does not
    // fit in the CorrelationId and cannot be used at this time.

    assertSmartPtrFits<TYPE>();

    std::memset(&d_impl, 0, sizeof(d_impl));

    d_impl.size           = sizeof(d_impl);
    d_impl.valueType      = POINTER_VALUE;
    d_impl.classId        = newClassId;

    d_impl.value.ptrValue.pointer = ptrValue;
    d_impl.value.ptrValue.manager = &CorrelationId::managerFunc<TYPE>;

    void *arena = (void *)d_impl.value.ptrValue.userData;
    new (arena) TYPE(smartPtr);
}

inline
CorrelationId::~CorrelationId()
{
    if (POINTER_VALUE == valueType()) {
        blpapi_ManagedPtr_ManagerFunction_t &manager =
            d_impl.value.ptrValue.manager;
        if (manager) {
            manager(&d_impl.value.ptrValue, 0, BLPAPI_MANAGEDPTR_DESTROY);
        }
    }
}

inline
void CorrelationId::swap(CorrelationId &other)
{
    std::swap(other.d_impl, d_impl);
}

inline
CorrelationId& CorrelationId::operator=(const CorrelationId &rhs)
{
    if (&rhs == this) {
        return *this;
    }
    CorrelationId tmp(rhs);
    tmp.swap(*this);
    return *this;
}

inline
blpapi_CorrelationId_t& CorrelationId::impl()
{
    return d_impl;
}

inline
CorrelationId::ValueType CorrelationId::valueType() const
{
    return (ValueType)d_impl.valueType;
}

inline
unsigned short CorrelationId::classId() const
{
    return d_impl.classId;
}

inline
void* CorrelationId::asPointer() const
{
    return d_impl.value.ptrValue.pointer;
}

template<typename TYPE>
inline
TYPE CorrelationId::asSmartPointer() const
{
    return *(TYPE *)d_impl.value.ptrValue.userData;
}

inline
long long CorrelationId::asInteger() const
{
    return d_impl.value.intValue;
}

inline
const blpapi_CorrelationId_t& CorrelationId::impl() const
{
    return d_impl;
}

inline
void CorrelationId::copy(const blpapi_CorrelationId_t& src)
{
    d_impl = src;

    if (POINTER_VALUE == valueType()) {
        blpapi_ManagedPtr_ManagerFunction_t& manager =
            d_impl.value.ptrValue.manager;
        if (manager) {
            manager(&d_impl.value.ptrValue, &src.value.ptrValue,
                BLPAPI_MANAGEDPTR_COPY);
        }
    }
}

template <typename TYPE>
inline
int CorrelationId::managerFunc(blpapi_ManagedPtr_t *managedPtr,
    const blpapi_ManagedPtr_t *srcPtr, int operation)
{
    if (operation == BLPAPI_MANAGEDPTR_COPY) {
        managedPtr->pointer = srcPtr->pointer;
        managedPtr->manager = srcPtr->manager;

        void *arena = managedPtr->userData;
        new (arena) TYPE(*((TYPE*)&srcPtr->userData[0]));
    }
    else if (operation == BLPAPI_MANAGEDPTR_DESTROY) {
        TYPE *managedPtr_p = (TYPE*)&managedPtr->userData[0];
        managedPtr_p->~TYPE();
    }
    return 0;
}

template <typename TYPE>
inline
void CorrelationId::assertSmartPtrFits()
{
    if (false) {
        // If you get a compiler error here, the specified smart pointer does
        // not fit in the CorrelationId and cannot be used at this time.

        char errorIfSmartPtrDoesNotFit[
            sizeof(TYPE) <= (sizeof(void*)*4) ? 1 : -1];
        (void)errorIfSmartPtrDoesNotFit;
    }
}

inline
bool operator==(const CorrelationId& lhs, const CorrelationId& rhs)
{
    if (lhs.valueType() != rhs.valueType()) {
        return false;
    }
    if (lhs.classId() != rhs.classId()) {
        return false;
    }

    if (lhs.valueType() == CorrelationId::POINTER_VALUE) {
        if (lhs.asPointer() != rhs.asPointer()) {
            return false;
        }
    }
    else if (lhs.asInteger() != rhs.asInteger()) {
        return false;
    }

    return true;
}

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

inline
bool operator<(const CorrelationId& lhs, const CorrelationId& rhs)
{
    return std::memcmp(&lhs.impl(), &rhs.impl(), sizeof(rhs.impl())) < 0;
}

inline
std::ostream& operator<<(std::ostream&        os,
                         const CorrelationId& correlator)
{
    const char *valueType = 0;
    switch (correlator.valueType()) {
      case CorrelationId::UNSET_VALUE:   valueType = "UNSET";   break;
      case CorrelationId::INT_VALUE:     valueType = "INT";     break;
      case CorrelationId::POINTER_VALUE: valueType = "POINTER"; break;
      case CorrelationId::AUTOGEN_VALUE: valueType = "AUTOGEN"; break;
      default: valueType = "UNKNOWN";
    }

    os << "[ valueType=" << valueType << " classId=" << correlator.classId()
       << " value=";

    if (correlator.valueType() == CorrelationId::POINTER_VALUE) {
        os << correlator.asPointer();
    }
    else os << correlator.asInteger();
    os << " ]";

    return os;
}

}  // close namespace blpapi
}  // close namespace BloombergLP

#endif // #ifdef __cplusplus
#endif // #ifndef INCLUDED_BLPAPI_CORRELATIONID
