/* 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_subscriptionlist.h                                          -*-C++-*-
#ifndef INCLUDED_BLPAPI_SUBSCRIPTIONLIST
#define INCLUDED_BLPAPI_SUBSCRIPTIONLIST

//@PURPOSE: Provide a list of subscriptions.
//
//@CLASSES:
// blpapi::SubscriptionList: Represents a list of subscriptions.
//
//@DESCRIPTION: This component provides a structure to hold the data used (and
// returned) by the 'Session::subscribe', 'Session::resubscribe', and
// 'Session::unsubscribe' methods.  This structure comprises a list in which
// each list entry contains two primary fields: a 'CorrelationId' associated
// with the subscription, and a string, called a *subscription* *string*,
// describing the data to be delivered as a part of the subscription.
//
/// Structure of Subscription String
///---------------------------------
// The simplest form of a subscription string is a *fully* *qualified*
// subscription string, which has the following structure:
//
//..
// "//blp/mktdata/ticker/IBM US Equity?fields=BID,ASK&interval=2"
//  \-----------/\------/\-----------/\------------------------/
//        |          |         |                  |
//     Service    Prefix   Instrument           Suffix
//..
//
// Such a fully-qualified string is composed of:
//: Service Identifier: a string matching the expression
//:   '^//[-_.a-zA-Z0-9]+/[-_.a-zA-Z0-9]+$', e.g. //blp/mktdata.  See
//:   'blpapi_abstractsession' for further details.
//:
//: Prefix: a string matching the expression '/([-_.a-zA-Z0-9]+/)?', often used
//:   as a symbology identifier.  Common examples include '/ticker/' and
//:   '/cusip/'.  Not all services make use of prefixes.  Note than an "empty"
//:   topic prefix consists of the string "/", so the topic prefix always
//:   separates the service string from the instrument string.
//:
//: Instrument: a non-empty string that does not contain the character '?'
//:   (i.e. a string matching '[^?]+') e.g. "IBM US Equity", or "SPX Index".
//:   The service, prefix, and instrument together uniquely identify a source
//:   for subscription data.
//:
//: Suffix: a suffix contains a question mark followed by a list of options
//:   which can affect the content delivery.  The format of these options is
//:   service specific, but they generally follow URI query string conventions:
//:   a sequence of "key=value" pairs separated by "&" characters.  Further,
//:   many common services follow the convention that the value given for the
//:   'fields' key is formatted as a comma-separated list of field names.
//:   BLPAPI provides several convenience functions to assist in formatting
//:   subscription strings for services that make use of these conventions;
//:   see the 'SubscriptionList::add' methods for details.
//
// Subscription strings need not be fully qualified: BLPAPI allows the service
// and prefix to be omitted from subscription strings, and automatically
// qualifies these strings using information stored in a 'Session' object.
//
/// Qualifying Subscription Strings
///-------------------------------
// The subscription strings passed to 'Session::subscribe' and
// 'Session::resubscribe' are automatically qualified if the service identifier
// is missing (i.e. if the subscription string does not start with "//"). The
// subscription parameters (i.e. the optional part after instrument identifier)
// are never modified.
// The rules for qualifying the subscription string are:
//
//: o If the subscription string begins with '//' then it is assumed to be a
//:   a fully qualified subscription string including service identifier,
//:   prefix, and instrument.  In this case the string will not be modified and
//:   session options defaults have no affect on the subscription.
//:
//: o If the subscription string begins with a '/' and the second character is
//:   not '/', then the string is assumed to begin with the topic prefix, but
//:   no service identifier. In this case the string is qualified by prepending
//:   the 'SessionOptions::defaultSubscriptionService()' to the specified
//:   string.
//:
//: o If the subscription string does not begin with a '/' it is assumed to
//:   begin with an instrument identifier.  In this case the string is
//:   qualified by prepending the
//:   'SessionOptions::defaultSubscriptionService()' followed by
//:   'SessionOptions::defaultTopicPrefix()' to the specified string.
//:   If the 'defaultTopicPrefix' is empty or null, then the prefix used is
//:   '/'.  Otherwise (in the case of a nontrivial prefix) if the separator '/'
//:   is not specified at the beginning or the end of the 'defaultTopicPrefix',
//:   then it will be added.
//
/// Use of 'SubscriptionList'
///-------------------------
// A 'SubscriptionList' is used when calling 'Session::subscribe()',
// 'Session::resubscribe()' and 'Session::unsubscribe()'.  The entries can
// be constructed in a variety of ways.
//
// The two important elements when creating a subscription are:
//: *Subscription String*: A subscription string represents a topic whose
//:    updates user is interested in.  A subscription string follows the
//:    structure specified above.
//: *CorrelationId*: the unique identifier to tag all data associated with
//:    this subscription.
//
// The following table describes how various operations use the above
// elements:
//..
// +----------------------------------------------------------------------+
// | OPERATION   |  SUBSCRIPTION STRING  |       CORRELATION ID           |
// |-------------|-----------------------+--------------------------------|
// | 'subscribe' |Used to specify the    |Identifier for the subscription.|
// |             |topic to subscribe to. |If uninitialized correlationid  |
// |             |                       |was specified an internally     |
// |             |                       |generated correlationId will be |
// |             |                       |set for the subscription.       |
// |-------------+-----------------------+--------------------------------|
// |'resubscribe'|Used to specify the new|Identifier of the subscription  |
// |             |topic to which the     |which needs to be modified.     |
// |             |subscription should be |                                |
// |             |modified to.           |                                |
// |-------------+-----------------------+--------------------------------|
// |'unsubscribe'|        NOT USED       |Identifier of the subscription  |
// |             |                       |which needs to be canceled.     |
// +----------------------------------------------------------------------+
//..

#ifndef INCLUDED_BLPAPI_CALL
#include <blpapi_call.h>
#endif

#ifndef INCLUDED_BLPAPI_CORRELATIONID
#include <blpapi_correlationid.h>
#endif

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

#ifndef INCLUDED_BLPAPI_EXCEPTION
#include <blpapi_exception.h>
#endif

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

#include <stddef.h>

struct blpapi_SubscriptionList;
typedef struct blpapi_SubscriptionList blpapi_SubscriptionList_t;

#ifdef __cplusplus
extern "C" {
#endif

BLPAPI_EXPORT
blpapi_SubscriptionList_t *blpapi_SubscriptionList_create(void);

BLPAPI_EXPORT
void blpapi_SubscriptionList_destroy(blpapi_SubscriptionList_t *list);

BLPAPI_EXPORT
int blpapi_SubscriptionList_add(blpapi_SubscriptionList_t *list,
        const char *subscriptionString,
        const blpapi_CorrelationId_t *correlationId,
        const char **fields,
        const char **options,
        size_t numfields,
        size_t numOptions);

BLPAPI_EXPORT
int blpapi_SubscriptionList_addResolved(blpapi_SubscriptionList_t *list,
        const char *subscriptionString,
        const blpapi_CorrelationId_t *correlationId);

BLPAPI_EXPORT
int blpapi_SubscriptionList_clear(blpapi_SubscriptionList_t *list);

BLPAPI_EXPORT
int blpapi_SubscriptionList_append(
        blpapi_SubscriptionList_t *dest, const blpapi_SubscriptionList_t *src);

BLPAPI_EXPORT
int blpapi_SubscriptionList_size(const blpapi_SubscriptionList_t *list);

BLPAPI_EXPORT
int blpapi_SubscriptionList_correlationIdAt(
        const blpapi_SubscriptionList_t *list,
        blpapi_CorrelationId_t *result,
        size_t index);

BLPAPI_EXPORT
int blpapi_SubscriptionList_topicStringAt(
        blpapi_SubscriptionList_t *list, const char **result, size_t index);

BLPAPI_EXPORT
int blpapi_SubscriptionList_isResolvedAt(
        blpapi_SubscriptionList_t *list, int *result, size_t index);

#ifdef __cplusplus
}

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

#ifndef INCLUDED_STRING
#include <string>
#define INCLUDED_STRING
#endif

#ifndef INCLUDED_VECTOR
#include <vector>
#define INCLUDED_VECTOR
#endif

namespace BloombergLP {
namespace blpapi {
// ======================
// class SubscriptionList
// ======================

class SubscriptionList {
    // Contains a list of subscriptions used when subscribing and
    // unsubscribing.
    //

    blpapi_SubscriptionList_t *d_handle_p;

  public:
    SubscriptionList();
    // Create an empty 'SubscriptionList'.
    SubscriptionList(const SubscriptionList& original);
    // Create a 'SubscriptionList' object initialized to the list of
    // subscriptions from the specified 'original'.

    ~SubscriptionList();
    // Destroy this object.

    // MANIPULATORS

    int add(const char *subscriptionString);
    // Append the specified 'subscriptionString' to this 'SubscriptionList'
    // object, associating an internally generated 'CorrelationId' with it.
    // The subscription string may include options.  Note that the
    // subscription string provided to this function may be resolved by
    // functions operating on this 'SubscriptionList' object; use
    // 'SubscriptionList::addResolved', below, to request that such
    // functions bypass resolution.

    int add(const char *subscriptionString,
            const CorrelationId& correlationId);
    // Append the specified 'subscriptionString' to this 'SubscriptionList'
    // object, associating the specified 'correlationId' with it.  The
    // subscription string may include options.  Note that the subscription
    // string provided to this function  may be resolved by functions
    // operating on this 'SubscriptionList' object; use
    // 'SubscriptionList::addResolved', below, to request that such
    // functions bypass resolution.

    int add(const char *subscriptionWithoutOptions,
            const std::vector<std::string>& fields,
            const std::vector<std::string>& options,
            const CorrelationId& correlationId);
    // Append the specified 'subscriptionString', with the specified
    // 'fields' and the specified 'options', to this 'SubscriptionList'
    // object, associating the specified 'correlationId' with it.

    int add(const char *subscriptionWithoutOptions,
            const char *fields,
            const char *options,
            const CorrelationId& correlationId);
    // Append the specified 'subscriptionString' with the specified
    // 'fields', which must be formatted as a comma-separated list, and the
    // specified 'options', which must be formatted as an
    // ampersand-separated list, to this 'SubscriptionList' object, and
    // associate the specified 'correlationId' with it.

    int add(const CorrelationId& correlationId);
    // Append the specified 'correlationId' to this 'SubscriptionList'
    // object.  A 'SubscriptionList' entry containing only a
    // 'CorrelationId' can be used only in a 'Session::unsubscribe' call.

    int addResolved(const char *subscriptionString);
    // Add the specified 'subscriptionString' to this 'SubscriptionList',
    // associating an internally generated 'CorrelationId' with it.  The
    // subscription string may include options.  The behavior of this
    // function, and of functions operating on this 'SubscriptionList'
    // object, is undefined unless 'subscriptionString' is a
    // fully-resolved subscription string; clients that cannot provide
    // fully-resolved subscription strings should use
    // 'SubscriptionList::add' instead.  Note that it is at the discretion
    // of each function operating on a 'SubscriptionList' whether to
    // perform resolution on this subscription.

    int addResolved(const char *subscriptionString,
            const CorrelationId& correlationId);
    // Add the specified 'subscriptionString' to this 'SubscriptionList'
    // object, associating the specified 'correlationId' with it.  The
    // subscription string may include options.  The behavior of this
    // function, and of functions operating on this 'SubscriptionList'
    // object, is undefined unless 'subscriptionString' is a
    // fully-resolved subscription string; clients that cannot provide
    // fully-resolved subscription strings should use
    // 'SubscriptionList::add' instead.  Note that it is at the discretion
    // of each function operating on a 'SubscriptionList' whether to
    // perform resolution on this subscription.

    int append(const SubscriptionList& other);
    // Extend this object by appending a copy of each entry in the
    // specified 'other'.  Note that this function adds 'other.size()' new
    // entries to this object.  Note also that this function is alias-safe;
    // i.e. 'x.append(x)' has well-defined behavior.

    void clear();
    // Remove all entries from this object.

    SubscriptionList& operator=(const SubscriptionList& rhs);
    // Replace the contents of this 'SubscriptionList' object with the
    // contents of the specified 'rhs' and return a modifiable reference to
    // this 'SubscriptionList' object.

    // ACCESSORS

    size_t size() const;
    // Return the number of entries in this object.

    CorrelationId correlationIdAt(size_t index) const;
    // Return the 'CorrelationId' of the specified 'index'th entry in this
    // 'SubscriptionList' object.  An exception is thrown if
    // 'index >= size()'.

    const char *topicStringAt(size_t index) const;
    // Return a pointer to a null-terminated string which contains the full
    // topic string (including any field and option portions) of the
    // 'index'th entry in this 'SubscriptionList' object.  The pointer
    // remains valid until this 'SubscriptionList' object is destroyed or
    // 'clear()' is called.  An exception is thrown if 'index >= size()'.

    bool isResolvedTopicAt(size_t index) const;
    // Return 'true' if the 'index'th entry in this 'SubscriptionList'
    // object was created using 'SubscriptionList::addResolved' and 'false'
    // if it was created using 'SubscriptionList::add'.  An exception is
    // thrown if 'index >= size()'.

    const blpapi_SubscriptionList_t *impl() const;
};

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

// ----------------------
// class SubscriptionList
// ----------------------
inline SubscriptionList::SubscriptionList()
    : d_handle_p(blpapi_SubscriptionList_create())
{
}

inline SubscriptionList::SubscriptionList(const SubscriptionList& original)
    : d_handle_p(blpapi_SubscriptionList_create())
{
    blpapi_SubscriptionList_append(d_handle_p, original.d_handle_p);
}

inline SubscriptionList::~SubscriptionList()
{
    blpapi_SubscriptionList_destroy(d_handle_p);
}

inline int SubscriptionList::add(const char *subscriptionString)
{
    blpapi_CorrelationId_t correlationId;
    std::memset(&correlationId, 0, sizeof(correlationId));
    return blpapi_SubscriptionList_add(
            d_handle_p, subscriptionString, &correlationId, 0, 0, 0, 0);
}

inline int SubscriptionList::add(
        const char *subscriptionString, const CorrelationId& correlationId)
{
    return blpapi_SubscriptionList_add(
            d_handle_p, subscriptionString, &correlationId.impl(), 0, 0, 0, 0);
}

inline int SubscriptionList::add(const char *subscriptionString,
        const std::vector<std::string>& fields,
        const std::vector<std::string>& options,
        const CorrelationId& correlationId)
{
    std::vector<const char *> tmpVector;
    const char *arena[256];
    const char **tmpArray = arena;
    size_t sizeNeeded = fields.size() + options.size();

    if (sizeNeeded > sizeof(arena) / sizeof(arena[0])) {
        tmpVector.resize(sizeNeeded);
        tmpArray = &tmpVector[0];
    }

    const char **p = tmpArray;
    for (std::vector<std::string>::const_iterator itr = fields.begin(),
                                                  end = fields.end();
            itr != end;
            ++itr, ++p) {
        *p = itr->c_str();
    }

    for (std::vector<std::string>::const_iterator itr = options.begin(),
                                                  end = options.end();
            itr != end;
            ++itr, ++p) {
        *p = itr->c_str();
    }

    return blpapi_SubscriptionList_add(d_handle_p,
            subscriptionString,
            &correlationId.impl(),
            tmpArray,
            tmpArray + fields.size(),
            fields.size(),
            options.size());
}

inline int SubscriptionList::add(const char *subscriptionString,
        const char *fields,
        const char *options,
        const CorrelationId& correlationId)
{
    return blpapi_SubscriptionList_add(d_handle_p,
            subscriptionString,
            &correlationId.impl(),
            &fields,
            &options,
            fields ? 1u : 0u,
            options ? 1u : 0u);
}

inline int SubscriptionList::add(const CorrelationId& correlationId)
{
    return blpapi_SubscriptionList_add(
            d_handle_p, "", &correlationId.impl(), 0, 0, 0, 0);
}

inline int SubscriptionList::addResolved(const char *subscriptionString)
{
    blpapi_CorrelationId_t correlationId;
    std::memset(&correlationId, 0, sizeof(correlationId));
    return BLPAPI_CALL_SUBSCRIPTIONLIST_ADDRESOLVED(
            d_handle_p, subscriptionString, &correlationId);
}

inline int SubscriptionList::addResolved(
        const char *subscriptionString, const CorrelationId& correlationId)
{
    return BLPAPI_CALL_SUBSCRIPTIONLIST_ADDRESOLVED(
            d_handle_p, subscriptionString, &correlationId.impl());
}

inline int SubscriptionList::append(const SubscriptionList& other)
{
    return blpapi_SubscriptionList_append(d_handle_p, other.d_handle_p);
}

inline void SubscriptionList::clear()
{
    blpapi_SubscriptionList_clear(d_handle_p);
}

inline SubscriptionList& SubscriptionList::operator=(
        const SubscriptionList& rhs)
{
    blpapi_SubscriptionList_clear(d_handle_p);
    blpapi_SubscriptionList_append(d_handle_p, rhs.d_handle_p);
    return *this;
}

inline size_t SubscriptionList::size() const
{
    return static_cast<size_t>(blpapi_SubscriptionList_size(d_handle_p));
}

inline CorrelationId SubscriptionList::correlationIdAt(size_t index) const
{
    blpapi_CorrelationId_t correlationId;

    ExceptionUtil::throwOnError(blpapi_SubscriptionList_correlationIdAt(
            d_handle_p, &correlationId, index));

    return CorrelationId(correlationId);
}

inline const char *SubscriptionList::topicStringAt(size_t index) const
{
    const char *result = 0;

    ExceptionUtil::throwOnError(
            blpapi_SubscriptionList_topicStringAt(d_handle_p, &result, index));

    return result;
}

inline bool SubscriptionList::isResolvedTopicAt(size_t index) const
{
    int result = 0;

    ExceptionUtil::throwOnError(BLPAPI_CALL_SUBSCRIPTIONLIST_ISRESOLVEDAT(
            d_handle_p, &result, index));

    return result ? true : false;
}

inline const blpapi_SubscriptionList_t *SubscriptionList::impl() const
{
    return d_handle_p;
}

} // close package namespace
} // close enterprise namespace

#endif // ifdef __cplusplus

#endif // #ifndef INCLUDED_BLPAPI_SUBSCRIPTIONLIST