/* 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.
 */

/** \file blpapi_subscriptionlist.h */
/** \defgroup blpapi_subscriptionlist Component blpapi_subscriptionlist
\brief Provide a list of subscriptions.
\file blpapi_subscriptionlist.h
\brief Provide a list of subscriptions.
*/

#ifndef INCLUDED_BLPAPI_SUBSCRIPTIONLIST
#define INCLUDED_BLPAPI_SUBSCRIPTIONLIST

/** \addtogroup blpapi
 * @{
 */
/** \addtogroup blpapi_subscriptionlist
 * @{
 * <A NAME="purpose"></A>
 * <A NAME="1"> \par Purpose: </A>
 * Provide a list of subscriptions.
 * \par
 * \par
 * <A NAME="classes"></A>
 * <A NAME="2"> \par Classes: </A>
 * <table>
 * <tr>
 * <td>blpapi::SubscriptionList</td>
 * <td>Represents a list of subscriptions.</td>
 * </tr>
 * </table>
 * \par
 * \par
 * <A NAME="description"></A>
 * <A NAME="3"> \par Description: </A>
 *  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
 * <em>subscription</em> <em>string</em>, describing the data to be delivered
 * as a part of the subscription.
 * \par
 * \par
 * <A NAME="structure_of_subscription_string"></A>
 * <A NAME="description.structure_of_subscription_string"></A>
 * <A NAME="3.1"> \par Structure of Subscription String: </A>
 * The simplest form of a subscription string is a <em>fully</em>
 * <em>qualified</em> subscription string, which has the following structure:
 * \par
\code
 "//blp/mktdata/ticker/IBM US Equity?fields=BID,ASK&interval=2"
  \-----------/\------/\-----------/\------------------------/
        |          |         |                  |
     Service    Prefix   Instrument           Suffix
\endcode
 * \par
 * Such a fully-qualified string is composed of:
 * \par
 *  - 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.
 *
 * \par
 * 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 the *default subscription service* and
 * *default topic prefix* from `SessionOptions`.
 * \par
 * \par
 * <A NAME="qualifying_subscription_strings"></A>
 * <A NAME="description.qualifying_subscription_strings"></A>
 * <A NAME="3.2"> \par Qualifying Subscription Strings: </A>
 * 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:
 * \par
 * <ul>
 * <li>
 * 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.
 * </li>
 * <li>
 * 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.
 * </li>
 * <li>
 * 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.
 * </li>
 * </ul>
 * \par
 * \par
 * <A NAME="use_of_subscriptionlist"></A>
 * <A NAME="description.use_of_subscriptionlist"></A>
 * <A NAME="3.3"> \par Use of `SubscriptionList`: </A>
 * A `SubscriptionList` is used when calling
`Session::subscribe()`,
 * `Session::resubscribe()` and `Session::unsubscribe()`.
The entries can
 * be constructed in a variety of ways.
 * \par
 * The two important elements when creating a subscription are:
 * <div class="hanging">
 * \par
 *  <em>Subscription String</em>: A subscription string represents a topic
whose
 * updates user is interested in.  A subscription string follows the
 * structure specified above.
 *
 * \par
 *  <em>CorrelationId</em>: the unique identifier to tag all data associated
with
 * this subscription.
 *
 * \par
 * </div>
 * \par
 * The following table describes how various operations use the above
 * elements:
\code
 +----------------------------------------------------------------------+
 | 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.     |
 +----------------------------------------------------------------------+
\endcode
 */
/** @} */
/** @} */

#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

/** \addtogroup blpapi
 * @{
 */
/** \addtogroup blpapi_subscriptionlist
 * @{
 */

namespace BloombergLP {
namespace blpapi {

// ======================
// class SubscriptionList
// ======================

/*!
 *     Contains a list of subscriptions used when subscribing and
 *     unsubscribing.
 *
 */
/*!
 * See \ref blpapi_subscriptionlist
 */
class SubscriptionList {

    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.
     * This function is alias safe, a `SubscriptionList` can be assigned to
     * itself and it will not change its value.
     */

    // 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()`.
     */

    //! \cond INTERNAL
    const blpapi_SubscriptionList_t *impl() const;
    //! \endcond
};

/** @} */
/** @} */

// ============================================================================
//                      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)
{
    if (d_handle_p != rhs.d_handle_p) {
        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
