/* Copyright 2019. 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_testutil.h                                                  -*-C++-*-
#ifndef INCLUDED_BLPAPI_TESTUTIL
#define INCLUDED_BLPAPI_TESTUTIL

//@PURPOSE: Provides static utility for unit testing BLPAPI applications.
//@CLASSES:
// blpapi::test::TestUtil: Static utility class for unit testing
//
//@SEE_ALSO: blpapi_messageformatter
//
//@DESCRIPTION: This component defines a class, TestUtil, that provides helpers
// for unit-testing BLPAPI. This includes serializing / deserializing services,
// creating test events, and appending / formatting messages in these events.
//
///Usage
///-----
//
///Example: Create subscription data with correlation id:
/// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// For the purpose of unit testing the behavior of our tick processing, we
// want to create a subscription data event containing a single tick for a
// given subscription, identified with a correlation id 'correlationId' that
// is available in the test.
//
// We first create the service from a string:
//
//..
// using BloombergLP::blpapi::Event;
// using BloombergLP::blpapi::Name;
//..
// using BloombergLP::blpapi::test::MessageFormatter;
// using BloombergLP::blpapi::test::MessageProperties;
// using BloombergLP::blpapi::test::TestUtil;
//..
// const char *schema = "<ServiceDefinition name=...>...</ServiceDefinition>";
//
// std::istringstream stream(schema);
// Service service = TestUtil::deserializeService(stream);
//..
//
// We then create a subscription event
//
//..
// Event event = TestUtil::createSubscriptionEvent(service);
//..
//
// We prepare the message properties:
//
//..
// MessageProperties properties;
// properties.setCorrelationId(correlationId);
//..
//
// Then, we obtain the definition for the message from the schema:
//
//..
// const Name msgName("MarketDataEvents");
// const SchemaElementDefinition def = service.getEventDefinition(msgName);
//..
//
// Now we append a message and obtain a formatter:
//
//..
// MessageFormatter fmt = TestUtil::appendMessage(event, def, properties);
//..
//
// The formatter can now be used to fill in the data we want to be using in
// the test.
// Note that service creation and the definitions for the ticks can be shared
// by all of the unit tests that need to create the same type of events.



#include <blpapi_defs.h>
#include <blpapi_types.h>
#include <blpapi_correlationid.h>
#include <blpapi_streamproxy.h>
#include <blpapi_service.h>
#include <blpapi_topic.h>
#include <blpapi_event.h>
#include <blpapi_datetime.h>
#include <blpapi_messageformatter.h>

#ifdef __cplusplus
extern "C" {
#endif

BLPAPI_EXPORT
int blpapi_TestUtil_createEvent(blpapi_Event_t **event, int eventType);

BLPAPI_EXPORT
int blpapi_TestUtil_deserializeService(const char        *schema,
                                       size_t             schemaLength,
                                       blpapi_Service_t **service);

BLPAPI_EXPORT
int blpapi_TestUtil_serializeService(blpapi_StreamWriter_t   streamWriter,
                                     void                   *stream,
                                     const blpapi_Service_t *service);

BLPAPI_EXPORT
int blpapi_TestUtil_appendMessage(
                         blpapi_MessageFormatter_t              **formatter,
                         blpapi_Event_t                          *event,
                         const blpapi_SchemaElementDefinition_t  *messageType,
                         const blpapi_MessageProperties_t        *properties);

BLPAPI_EXPORT
int blpapi_TestUtil_createTopic(blpapi_Topic_t         **topic,
                                const blpapi_Service_t  *service,
                                int                      isActive);

BLPAPI_EXPORT
int blpapi_TestUtil_getAdminMessageDefinition(
                               blpapi_SchemaElementDefinition_t **definition,
                               blpapi_Name_t                     *messageName);

BLPAPI_EXPORT
int blpapi_MessageProperties_create(
                               blpapi_MessageProperties_t **messageProperties);

BLPAPI_EXPORT
void blpapi_MessageProperties_destroy(
                                blpapi_MessageProperties_t *messageProperties);

BLPAPI_EXPORT
int blpapi_MessageProperties_copy(blpapi_MessageProperties_t       **dest,
                                  const blpapi_MessageProperties_t  *src);

BLPAPI_EXPORT
int blpapi_MessageProperties_assign(blpapi_MessageProperties_t       *lhs,
                                    const blpapi_MessageProperties_t *rhs);

BLPAPI_EXPORT
int blpapi_MessageProperties_setCorrelationIds(
                              blpapi_MessageProperties_t   *messageProperties,
                              const blpapi_CorrelationId_t *correlationIds,
                              size_t                        numCorrelationIds);

BLPAPI_EXPORT
int blpapi_MessageProperties_setRecapType(
                                 blpapi_MessageProperties_t *messageProperties,
                                 int                         recap,
                                 int                         fragment);

BLPAPI_EXPORT
int blpapi_MessageProperties_setTimeReceived(
                       blpapi_MessageProperties_t           *messageProperties,
                       const blpapi_HighPrecisionDatetime_t *timestamp);

BLPAPI_EXPORT
int blpapi_MessageProperties_setService(
                                 blpapi_MessageProperties_t *messageProperties,
                                 const blpapi_Service_t     *service);

#ifdef __cplusplus
} // extern "C"

#include <fstream>
#include <vector>
#include <cstdio>

namespace BloombergLP {
namespace blpapi {
namespace test {

                        // =======================
                        // class MessageProperties
                        // =======================

class MessageProperties {
    // This class represents properties of a message that are not part of
    // the message contents, for example the correlation ids, or timestamp.

  private:
    blpapi_MessageProperties_t *d_handle_p;

  public:
    // CREATORS
    MessageProperties();
        // Creates a 'MessageProperties' with default values. Default
        // value for 'CorrelationId' property is an empty vector. Default
        // value for 'RecapType' property is 'Message::RecapType::e_none'.
        // Default value for 'FragmentType' property is
        // 'Message::FRAGMENT_NONE'. Value for 'Service' and timestamp is
        // "unset".

    MessageProperties(const MessageProperties& original);
        // Creates a 'MessageProperties' from 'original'.

    ~MessageProperties();
        // Destroys this 'MessageProperties'.

    // MANIPULATORS
    MessageProperties& operator=(const MessageProperties& rhs);
        // Make this 'MessageProperties' same as 'rhs'.


    MessageProperties& setCorrelationIds(
                         const blpapi_CorrelationId_t *correlationIds,
                         size_t                        correlationIdCount);
        // Sets the 'CorrelationId' property. 'correlationIdCount' provides
        // the number of 'correlationIds' to be added. The behavior is
        // undefined unless 'correlationIds' points to an array of at least
        // 'correlationIdCount' elements. The default value of
        // 'CorrelationId' property is an empty vector.

    MessageProperties& setCorrelationIds(
                         const std::vector<CorrelationId>& correlationIds);
        // Sets the 'CorrelationId' property. 'correlationIds' provides
        // the 'CorrelationId' to be added.

    MessageProperties& setCorrelationId(
                                      const CorrelationId& correlationId);
        // Equivalent to 'setCorrelationIds(&correlationId, 1)'.

    MessageProperties& setRecapType(
           Message::RecapType::Type recapType,
           Message::Fragment        fragmentType = Message::FRAGMENT_NONE);
        // Sets the 'RecapType' and 'FragmentType' properties.

    MessageProperties& setTimeReceived(const Datetime& timeReceived);
        // Sets the time received property. The default timestamp is
        // "unset", so attempting to retrieve the timestamp of a message
        // with default properties will throw an exception.

    MessageProperties& setService(const Service& service);
        // Sets the service property.

    // ACCESSORS
    blpapi_MessageProperties_t *handle() const;
        // Return the handle of the current 'MessageProperties'. For
        // *internal* use only.
};


                        // ==============
                        // class TestUtil
                        // ==============

class TestUtil {
    // This class provides a set of utility functions to allow SDK clients
    // to create events/messages for unit-testing their applications.

  public:
    // TYPES

    // *Deprecated*
    // Following typedef is provided for backwards compatibility. It will be
    // removed in a future release.
    typedef test::MessageProperties MessageProperties;

    // CLASS METHODS
    static Event createEvent(Event::EventType eventType);
        // Creates and returns an 'Event' to be used for testing with the
        // specified 'eventType'. The returned 'Event' cannot be used for
        // publishing. The behavior is undefined if 'EventFormatter' is
        // used with the returned 'Event'.

    static MessageFormatter appendMessage(
              Event&                         event,
              const SchemaElementDefinition& elementDef,
              const MessageProperties&       properties = MessageProperties());
        // Creates a new message and appends it to the specified 'event'.
        // Returns a 'MessageFormatter' to format the last appended message.
        // The specified 'event' must be a test 'Event' created from
        // 'TestUtil::createEvent()'. 'elementDef' is used to verify and encode
        // the contents of the message and the specified 'properties' are used
        // to set the metadata properties for the message. An exception is
        // thrown if the method fails to append the message.

    static Service deserializeService(std::istream& stream);
        // Creates a 'Service' object from the specified 'stream'. The
        // format of the stream must be 'XML'. The stream should only contain
        // ASCII characters without any embedded 'null' characters. Returns
        // the 'Service' object on success or throws 'blpapi::Exception' on
        // failure.

    static void serializeService(std::ostream&  stream,
                                 const Service& service);
        // Serialize the specific 'service' into the specified 'stream' in
        // 'XML' format. An exception is thrown if the service can't be
        // serialized successfully.

    static Topic createTopic(const Service& service, bool isActive = true);
        // Create a valid 'Topic' with the specified 'service' to support
        // testing publishers. The expected use case is to support returning a
        // custom 'Topic' while mocking `session.getTopic()` methods.

    static SchemaElementDefinition getAdminMessageDefinition(
                                                      const Name& messageName);
        // Return the definition for an admin message of the specified
        // 'messageName'.
};

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

                        // --------------
                        // class TestUtil
                        // --------------

inline
Event TestUtil::createEvent(Event::EventType eventType)
{
    blpapi_Event_t *event = 0;
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_TestUtil_createEvent)(&event, eventType));
    return event;
}

inline
Service TestUtil::deserializeService(std::istream& stream)
{
    blpapi_Service_t *buffer = 0;

    // This is a workaround for SunOS compiler not supporting construction from
    // 'std::istreambuf_iterator'.
    std::string schema;
    std::getline(stream, schema, '\0');

    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_TestUtil_deserializeService)(schema.c_str(),
                                                        schema.length(),
                                                        &buffer));
    return buffer;
}

inline
void TestUtil::serializeService(std::ostream& stream, const Service& service)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_TestUtil_serializeService)(
            StreamProxyOstream::writeToStream,
            &stream,
            service.handle()));
}

inline
MessageFormatter TestUtil::appendMessage(
                                     Event&                         event,
                                     const SchemaElementDefinition& elementDef,
                                     const MessageProperties&       properties)
{
    blpapi_MessageFormatter_t *formatter = 0;
    ExceptionUtil::throwOnError(BLPAPI_CALL(blpapi_TestUtil_appendMessage)(
        &formatter, event.impl(), elementDef.impl(), properties.handle()));

    return MessageFormatter(formatter);
}

inline
Topic TestUtil::createTopic(const Service& service, bool isActive)
{
    blpapi_Topic_t *topic = 0;
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_TestUtil_createTopic)(
                                          &topic,
                                          service.handle(),
                                          isActive));
    return Topic(topic);
}

inline
SchemaElementDefinition TestUtil::getAdminMessageDefinition(
                                                       const Name& messageName)
{
    blpapi_SchemaElementDefinition_t *definition = 0;
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_TestUtil_getAdminMessageDefinition)(
                                          &definition,
                                          messageName.impl()));
    return SchemaElementDefinition(definition);
}

inline
MessageProperties::MessageProperties()
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_create)(&d_handle_p));
}

inline
MessageProperties::MessageProperties(const MessageProperties& original)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_copy)(&d_handle_p,
                                                   original.handle()));
}

inline
MessageProperties& MessageProperties::operator=(const MessageProperties& rhs)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_assign)(
                        d_handle_p,
                        rhs.handle()));
    return *this;
}

inline
MessageProperties::~MessageProperties()
{
    BLPAPI_CALL_UNCHECKED(blpapi_MessageProperties_destroy)(d_handle_p);
}

inline
blpapi_MessageProperties_t *MessageProperties::handle() const
{
    return d_handle_p;
}

inline
MessageProperties& MessageProperties::setCorrelationId(
                                                      const CorrelationId& cid)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_setCorrelationIds)(
            d_handle_p, &cid.impl(), 1));
    return *this;
}

inline
MessageProperties& MessageProperties::setCorrelationIds(
                                        const std::vector<CorrelationId>& cids)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_setCorrelationIds)(
                        d_handle_p,
                        &cids[0].impl(),
                        cids.size()));
    return *this;
}

inline
MessageProperties& MessageProperties::setCorrelationIds(
                              const blpapi_CorrelationId_t *correlationIds,
                              size_t                        correlationIdCount)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_setCorrelationIds)(
            d_handle_p, correlationIds, correlationIdCount));
    return *this;
}

inline
MessageProperties& MessageProperties::setRecapType(
                                         Message::RecapType::Type recapType,
                                         Message::Fragment        fragmentType)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_setRecapType)(
                        d_handle_p,
                        static_cast<int>(recapType),
                        static_cast<int>(fragmentType)));
    return *this;
}

inline
MessageProperties& MessageProperties::setTimeReceived(
                                                  const Datetime& timeReceived)
{
    ExceptionUtil::throwOnError(
        BLPAPI_CALL(blpapi_MessageProperties_setTimeReceived)(
                        d_handle_p,
                        &timeReceived.rawHighPrecisionValue()));
    return *this;
}

inline
MessageProperties& MessageProperties::setService(const Service& service)
{
    ExceptionUtil::throwOnError(BLPAPI_CALL(
        blpapi_MessageProperties_setService)(d_handle_p, service.handle()));
    return *this;
}

}  // close namespace test


// *Deprecated*
// Following typedef is provided for backwards compatibility.  It will be
// removed in a future release.
typedef test::TestUtil TestUtil;

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

#endif // #ifdef __cplusplus
#endif // #ifndef INCLUDED_BLPAPI_TESTUTIL