#ifndef __lcm_cpp_hpp__
#define __lcm_cpp_hpp__

#include <string>
#include <vector>
#include <cstdio>  /* needed for FILE* */
#include "lcm.h"

namespace lcm {

/**
 * @defgroup LcmCpp C++ API Reference
 *
 * THe %LCM C++ API provides classes and data structures for communicating with
 * other %LCM clients, as well as reading and writing %LCM log files.  It is a
 * pure header wrapper around the C API, and has the same linking requirements
 * as the C API.
 *
 * @{
 */

class Subscription;

struct ReceiveBuffer;

/**
 * @brief Core communications class for the C++ API.
 *
 * @headerfile lcm/lcm-cpp.hpp
 */
class LCM {
    public:
        /**
         * @brief Constructor.
         *
         * Initializes the LCM instance and connects it to the specified LCM
         * network.  See the documentation on lcm_create() in the C API for
         * details on how lcm_url is formatted.
         *
         * @sa lcm_create()
         */
        inline LCM(std::string lcm_url="");

        /**
         * @brief Constructor.
         *
         * Initializes the c++ LCM instance from an existing C instance.
         *
         * @sa lcm_create()
         */
        inline LCM(lcm_t * lcm_in);

        /**
         * @brief Destructor.
         *
         * Disconnects from the LCM network, and destroys all outstanding
         * Subscription objects.
         */
        inline ~LCM();

        /**
         * @brief Checks if initialization succeeded during object
         * construction.
         *
         * @return true if initialization succeeded and the instance appears
         * ready for communication, false if not.
         */
        inline bool good() const;

        /**
         * @brief Publishes a raw data message.
         *
         * @param channel the channel to publish the message on.
         * @param data data buffer containing the message to publish
         * @param datalen length of the message, in bytes.
         *
         * @return 0 on success, -1 on failure.
         */
        inline int publish(const std::string& channel, const void *data,
                unsigned int datalen);

        /**
         * @brief Publishes a message with automatic message encoding.
         *
         * This template method is designed for use with C++ classes generated
         * by lcm-gen.
         *
         * @param channel the channel to publish the message on.
         * @param msg the message to publish.
         *
         * @return 0 on success, -1 on failure.
         */
        template<class MessageType>
        inline int publish(const std::string& channel, const MessageType* msg);

        /**
         * @brief Returns a file descriptor or socket that can be used with
         * @c select(), @c poll(), or other event loops for asynchronous
         * notification of incoming messages.
         *
         * This method is useful when integrating LCM into another event loop,
         * such as the Qt event loop (via QSocketNotifier), the GLib event loop
         * (via GIOChannel), a custom @c select() @c - or @c poll() @c -based event loop, or any other
         * event loop that supports file descriptors.
         *
         * @todo link to example code.
         *
         * @return a non-negative file descriptor on success, or -1 if something
         * is wrong.
         * @sa lcm_get_fileno()
         */
        inline int getFileno();

        /**
         * @brief Waits for and dispatches messages.
         *
         * @return 0 on success, -1 if something went wrong.
         * @sa lcm_handle()
         */
        inline int handle();

        /**
         * @brief Waits for and dispatches messages, with a timeout.
         *
         * New in LCM 1.1.0.
         *
         * @return >0 if a message was handled, 0 if the function timed out,
         * and <0 if an error occured.
         * @sa lcm_handle_timeout()
         */
        inline int handleTimeout(int timeout_millis);

        /**
         * @brief Subscribes a callback method of an object to a channel, with
         * automatic message decoding.
         *
         * This method is designed for use with C++ classes generated by
         * @c lcm-gen @c .
         *
         * The callback method will be invoked on the object when a message
         * arrives on the specified channel.  Prior to method invocation, LCM
         * will attempt to automatically decode the message to the specified
         * message type @c MessageType @c , which should be a class generated
         * by @c lcm-gen @c .  If message
         * decoding fails, the callback method is not invoked and an error
         * message is printed to stderr.
         *
         * The callback method is invoked during calls to LCM::handle().
         * Callback methods are invoked by the same thread that invokes
         * LCM::handle(), in the order that they were subscribed.
         *
         * For example:
         *
         * \code
         * #include <exlcm/example_t.lcm>
         * #include <lcm/lcm-cpp.hpp>
         *
         * class MyMessageHandler {
         *   void onMessage(const lcm::ReceiveBuffer* rbuf, const std::string& channel,
         *           const exlcm::example_t* msg) {
         *      // do something with the message
         *   }
         * };
         *
         * int main(int argc, char** argv) {
         *   lcm::LCM lcm;
         *   MyMessageHandler handler;
         *   lcm.subscribe("CHANNEL", &MyMessageHandler::onMessage, &handler);
         *   while(true)
         *     lcm.handle();
         *   return 0;
         * }
         * \endcode
         *
         * @param channel The channel to subscribe to.  This is treated as a
         * regular expression implicitly surrounded by '^' and '$'.
         * @param handlerMethod A class method pointer identifying the callback
         * method.
         * @param handler A class instance that the callback method will be
         * invoked on.
         *
         * @return a Subscription object that can be used to adjust the
         * subscription and unsubscribe.  The Subscription object is managed by
         * the LCM class, and is automatically destroyed when its LCM instance
         * is destroyed.
         */
        template <class MessageType, class MessageHandlerClass>
        Subscription* subscribe(const std::string& channel,
            void (MessageHandlerClass::*handlerMethod)(const ReceiveBuffer* rbuf, const std::string& channel, const MessageType* msg),
            MessageHandlerClass* handler);

        /**
         * @brief Subscribe a callback method of an object to a channel,
         * without automatic message decoding.
         *
         * This method is designed for use when automatic message decoding is
         * not desired.
         *
         * The callback method will be invoked on the object when a message
         * arrives on the specified channel.  Callback methods are invoked
         * during calls to LCM::handle(), by the same thread that calls
         * LCM::handle().  Callbacks are invoked in the order that they were
         * subscribed.
         *
         * For example:
         *
         * \code
         * #include <lcm/lcm-cpp.hpp>
         *
         * class MyMessageHandler {
         *   void onMessage(const lcm::ReceiveBuffer* rbuf, const std::string& channel) {
         *      // do something with the message.  Raw message bytes are
         *      // accessible via rbuf->data
         *   }
         * };
         *
         * int main(int argc, char** argv) {
         *   lcm::LCM lcm;
         *   MyMessageHandler handler;
         *   lcm.subscribe("CHANNEL", &MyMessageHandler::onMessage, &handler);
         *   while(true)
         *     lcm.handle();
         *   return 0;
         * }
         * \endcode
         *
         * @param channel The channel to subscribe to.  This is treated as a
         * regular expression implicitly surrounded by '^' and '$'.
         * @param handlerMethod A class method pointer identifying the callback
         * method.
         * @param handler A class instance that the callback method will be
         * invoked on.
         *
         * @return a Subscription object that can be used to adjust the
         * subscription and unsubscribe.  The Subscription object is managed by
         * the LCM class, and is automatically destroyed when its LCM instance
         * is destroyed.
         */
        template <class MessageHandlerClass>
        Subscription* subscribe(const std::string& channel,
            void (MessageHandlerClass::*handlerMethod)(const ReceiveBuffer* rbuf, const std::string& channel),
            MessageHandlerClass* handler);

        /**
         * @brief Subscribe a function callback to a channel, with automatic
         * message decoding.
         *
         * This method is designed for use with static member functions and
         * C-style functions.
         *
         * The callback function will be invoked on the object when a message
         * arrives on the specified channel.  Prior to callback invocation, LCM
         * will attempt to automatically decode the message to the specified
         * message type @c MessageType @c , which should be a class generated
         * by @c lcm-gen @c .  If message decoding fails, the callback function
         * is not invoked and an error message is printed to stderr.
         *
         * The callback function is invoked during calls to LCM::handle().
         * Callbacks are invoked by the same thread that invokes
         * LCM::handle(), in the order that they were subscribed.
         *
         * For example:
         *
         * \code
         * #include <lcm/lcm-cpp.hpp>
         *
         * class State {
         * public:
         *   lcm::LCM lcm;
         *   int usefulVariable;
         * };
         *
         * void onMessage(const lcm::ReceiveBuffer* rbuf, const std::string& channel, const MessageType* msg, State* state) {
         *   // do something with the message.
         * }
         *
         * int main(int argc, char** argv) {
         *   State* state = new State;
         *   state->lcm.subscribe("CHANNEL", onMessage, state);
         *   while(true)
         *     state->lcm.handle();
         *   delete state;
         *   return 0;
         * }
         * \endcode
         *
         * @param channel The channel to subscribe to.  This is treated as a
         * regular expression implicitly surrounded by '^' and '$'.
         * @param handler A function pointer identifying the callback
         * function.
         * @param context A context variable that will be passed to the
         * callback function.  This can be used to pass state or other
         * information to the callback function.  If not needed, then @c
         * ContextClass @c can be set to void*, and this argument set to NULL.
         *
         * @return a Subscription object that can be used to adjust the
         * subscription and unsubscribe.  The Subscription object is managed by
         * the LCM class, and is automatically destroyed when its LCM instance
         * is destroyed.
         */
        template <class MessageType, class ContextClass>
        Subscription* subscribeFunction(const std::string& channel,
                void (*handler)(const ReceiveBuffer* rbuf,
                                const std::string& channel,
                                const MessageType *msg,
                                ContextClass context),
                ContextClass context);

        /**
         * @brief Subscribe a function callback to a channel, without automatic
         * message decoding.
         *
         * This method is designed for use when automatic message decoding is
         * not desired.
         *
         * For example:
         *
         * \code
         * #include <lcm/lcm-cpp.hpp>
         *
         * void onMessage(const lcm::ReceiveBuffer* rbuf, const std::string& channel, void*) {
         *   // do something with the message.  Raw message bytes are
         *   // accessible via rbuf->data
         * }
         *
         * int main(int argc, char** argv) {
         *   LCM::lcm lcm;
         *   lcm.subscribe("CHANNEL", onMessage, NULL);
         *   while(true)
         *     lcm.handle();
         *   return 0;
         * }
         * \endcode
         *
         * @param channel The channel to subscribe to.  This is treated as a
         * regular expression implicitly surrounded by '^' and '$'.
         * @param handler A function pointer identifying the callback
         * function.
         * @param context A context variable that will be passed to the
         * callback function.  This can be used to pass state or other
         * information to the callback function.  If not needed, then @c
         * ContextClass @c can be set to void*, and this argument set to NULL.
         *
         * @return a Subscription object that can be used to adjust the
         * subscription and unsubscribe.  The Subscription object is managed by
         * the LCM class, and is automatically destroyed when its LCM instance
         * is destroyed.
         */
        template <class ContextClass>
        Subscription* subscribeFunction(const std::string& channel,
                void (*handler)(const ReceiveBuffer* rbuf,
                                const std::string& channel,
                                ContextClass context),
                ContextClass context);

        /**
         * @brief Unsubscribes a message handler.
         *
         * After unsubscription, the callback registered by the original call
         * to subscribe() or subscribeFunction() will no longer be invoked when
         * messages are received.
         * The Subscription object is destroyed by this method.
         *
         * @param subscription a Subscription object previously returned by a
         * call to subscribe() or subscribeFunction().
         *
         * @return 0 on success, -1 if @p subscription is not a valid
         * subscription.
         */
        inline int unsubscribe(Subscription* subscription);

        /**
         * @brief retrives the lcm_t C data structure wrapped by this class.
         *
         * This method should be used carefully and sparingly.  An example use
         * case would be extending the subscription mechanism to Boost
         * Function objects.
         *
         * @return the lcm_t instance wrapped by this object.
         *
         * @sa lcm_t
         */
        inline lcm_t* getUnderlyingLCM();

    private:
        lcm_t *lcm;
        bool owns_lcm;

        std::vector<Subscription*> subscriptions;
};

/**
 * @brief Stores the raw bytes and timestamp of a received message.
 *
 * @headerfile lcm/lcm-cpp.hpp
 */
struct ReceiveBuffer {
    /**
     * Message payload data, represented as a raw byte buffer.
     */
    void *data;
    /**
     * Length of message payload, in bytes.
     */
    uint32_t data_size;
    /**
     * Timestamp identifying when the message was received.  Specified in
     * microseconds since the UNIX epoch.
     */
    int64_t recv_utime;
};

/**
 * @brief Represents a channel subscription, and can be used to unsubscribe
 * and set options.
 *
 * This class is not meant to be instantiated by the user, and instead is
 * constructed and returned by a call to LCM::subscribe() or
 * LCM::subscribeFunction().
 *
 * To unsubscribe, pass the instance to LCM::unsubscribe().  Once unsubscribed,
 * the object is destroyed and can not be used anymore.
 *
 * @headerfile lcm/lcm-cpp.hpp
 */
class Subscription {
    public:
        virtual ~Subscription() {}

        /**
         * @brief Adjusts the maximum number of received messages that can be
         * queued up for this subscription.
         *
         * @param num_messages the maximum queue size, in messages.  The
         * default is 30.
         *
         * Setting this to a low number may reduce
         * overall latency at the expense of dropping more messages.
         * Conversely, setting this to a high number may drop fewer messages at
         * the expense of increased latency.  A value of 0 indicates no limit,
         * and should be used very carefully.
         *
         */
        inline int setQueueCapacity(int num_messages);

    friend class LCM;
    protected:
        Subscription() {};
        /**
         * The underlying lcm_subscription_t object wrapped by this
         * subscription.
         */
        lcm_subscription_t *c_subs;
};

/**
 * @brief Represents a single event (message) in a log file.
 *
 *
 *
 * This struct is the C++ counterpart for lcm_eventlog_event_t.
 *
 * @sa lcm_eventlog_event_t
 *
 * @headerfile lcm/lcm-cpp.hpp
 */
struct LogEvent {
    /**
     * Monotically increasing counter identifying the event number.  This field
     * is managed by LCM, and there should be no need to ever set it manually.
     */
    int64_t eventnum;
    /**
     * Timestamp identifying when the event was received.  Represented in
     * microseconds since the UNIX epoch.
     */
    int64_t timestamp;
    /**
     * The LCM channel on which the message was received.
     */
    std::string channel;
    /**
     * The length of the message payload, in bytes
     */
    int32_t datalen;
    /**
     * The message payload.
     */
    void* data;
};

/**
 * @brief Read and write %LCM log files.
 *
 * This class is the C++ counterpart for lcm_eventlog_t.
 *
 * @sa lcm_eventlog_t
 *
 * @headerfile lcm/lcm-cpp.hpp
 */
class LogFile {
    public:
        /**
         * Constructor.  Opens the specified log file for reading or writing.
         * @param path the file to open
         * @param mode "r" (read mode) or "w" (write mode)
         *
         * @sa lcm_eventlog_create()
         */
        inline LogFile(const std::string & path, const std::string & mode);

        /**
         * Destructor.  Closes the log file.
         */
        inline ~LogFile();

        /**
         * @return true if the log file is ready for reading/writing.
         */
        inline bool good() const;

        /**
         * Reads the next event in the log file.  Valid in read mode only.
         *
         * The LogFile class manages the memory of the read event.  The
         * returned event is valid until the next call to this method.
         *
         * @return the next event, or NULL if the end of the log file has been
         * reached.
         */
        inline const LogEvent* readNextEvent();

        /**
         * Seek close to the specified timestamp in the log file.  Valid
         * in read mode only.
         *
         * @param timestamp the desired seek point in the log file.
         *
         * @return 0 on success, -1 on error.
         * @sa lcm_eventlog_seek_to_timestamp()
         */
        inline int seekToTimestamp(int64_t timestamp);

        /**
         * Writes an event to the log file.  Valid in write mode only.
         *
         * @param event the event to write.  The timestamp, channel, datalen,
         * and data fields should be filled in.  The eventnum field will be
         * automatically filled in.
         *
         * @return 0 on success, -1 on error.
         * @sa lcm_eventlog_write_event()
         */
        inline int writeEvent(LogEvent* event);

        /**
         * @brief retrives the underlying FILE* wrapped by this class.
         *
         * This method should be used carefully and sparingly.
         * An example use-case is borrowing to tweak the behavior of the I/O.
         * Calls of interest include fflush(), fileno(), setvbuf(), etc
         * It is a bad idea to attempt reading or writing on the raw FILE*
         *
         * @return the FILE* wrapped by this object.
         */
         inline FILE* getFilePtr();

    private:
        LogEvent curEvent;
        lcm_eventlog_t* eventlog;
        lcm_eventlog_event_t* last_event;
};

/**
 * @}
 */

#define __lcm_cpp_impl_ok__
#include "lcm-cpp-impl.hpp"
#undef __lcm_cpp_impl_ok__

}

#endif
