/**
 * @file PipeMember.h
 *
 * @author John Wason, PhD
 *
 * @copyright Copyright 2011-2020 Wason Technology, LLC
 *
 * @par License
 * Software License Agreement (Apache License)
 * @par
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * @par
 * http://www.apache.org/licenses/LICENSE-2.0
 * @par
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include "RobotRaconteur/Message.h"
#include "RobotRaconteur/RobotRaconteurNode.h"
#include "RobotRaconteur/Endpoint.h"
#include "RobotRaconteur/DataTypes.h"
#include <boost/function.hpp>
#include <boost/unordered_map.hpp>
#include <boost/call_traits.hpp>
#include <list>

#ifdef _MSVC_VER
#pragma warning(push)
#pragma warning(disable : 4250)
#pragma warning(disable : 4996)
#endif

#include <boost/signals2.hpp>

namespace RobotRaconteur
{

class ROBOTRACONTEUR_CORE_API PipeBase;
class ROBOTRACONTEUR_CORE_API PipeEndpointBaseListener;
namespace detail
{
class PipeSubscription_connection;
}

/**
 * @brief Base class for PipeEndpoint
 *
 * Base class for templated PipeEndpoint
 *
 */
class ROBOTRACONTEUR_CORE_API PipeEndpointBase : public RR_ENABLE_SHARED_FROM_THIS<PipeEndpointBase>,
                                                 private boost::noncopyable
{
    friend class PipeBase;
    friend class PipeClientBase;
    friend class PipeServerBase;
    friend class PipeBroadcasterBase;
    friend class PipeSubscriptionBase;
    friend class detail::PipeSubscription_connection;

  public:
    virtual ~PipeEndpointBase() {}

    /**
     * @brief Returns the pipe endpoint index used when endpoint connected
     *
     * @return int32_t The pipe endpoint index
     */
    virtual int32_t GetIndex();

    /**
     * @brief Returns the Robot Raconteur node Endpoint ID
     *
     * Returns the endpoint associated with the ClientContext or ServerEndpoint
     * associated with the pipe endpoint.
     *
     * @return uint32_t The Robot Raconteur node Endpoint ID
     */
    virtual uint32_t GetEndpoint();

    /**
     * @brief Get if pipe endpoint is requesting acks
     *
     * @return true The pipe endpoint is requesting acks
     * @return false The pipe endpoint is not requesting acks
     */
    bool GetRequestPacketAck();

    /**
     * @brief Set if pipe endpoint should request packet acks
     *
     * Packet acks are generated by receiving endpoints to inform the sender that
     * a packet has been received. The ack contains the packet index, the sequence number
     * of the packet. Packet acks are used for flow control by PipeBroadcaster.
     *
     * @param ack true to request packet acks, otherwise false
     */
    void SetRequestPacketAck(bool ack);

    /**
     * @brief Close the pipe endpoint
     *
     * Close the pipe endpoint. Blocks until close complete. The peer endpoint is destroyed
     * automatically.
     *
     */
    virtual void Close();

    /**
     * @brief Asynchronously close the pipe endpoint
     *
     * Same as Close() but returns asynchronously
     *
     * @param handler A handler function to call on completion, possibly with an exception
     * @param timeout Timeout in milliseconds, or RR_TIMEOUT_INFINITE for no timeout
     */
    virtual void AsyncClose(boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)> handler,
                            int32_t timeout = RR_TIMEOUT_INFINITE);

    /**
     * @brief Return number of packets in the receive queue
     *
     * Invalid for *writeonly* pipes.
     *
     * @return size_t The number of packets in the receive queue
     */
    virtual size_t Available();

    /**
     * @brief Get if pipe endpoint is unreliable
     *
     * Pipe members may be declared as *unreliable* using member modifiers in the
     * service definition. Pipes confirm unreliable operation when pipe endpoints are connected.
     *
     * @return true The pipe endpoint is unreliable
     * @return false The pipe endpoint is reliable
     */
    bool IsUnreliable();

    /**
     * @brief The direction of the pipe
     *
     * Pipes may be declared *readonly* or *writeonly* in the service definition file. (If neither
     * is specified, the pipe is assumed to be full duplex.) *readonly* pipes may only send packets from
     * service to client. *writeonly* pipes may only send packets from client to service.
     *
     * @return MemberDefinition_Direction
     */
    MemberDefinition_Direction Direction();

    /**
     * @brief Get if pipe endpoint is ignoring incoming packets
     *
     * If true, pipe endpoint is ignoring incoming packets and is not adding
     * incoming packets to the receive queue.
     *
     * @return true Pipe endpoint is ignoring incoming packets
     * @return false Pipe endpoint is not ignoring incoming packets
     */
    bool GetIgnoreReceived();
    /**
     * @brief Set whether pipe endpoint should ignore incoming packets
     *
     * Pipe endpoints may optionally desire to ignore incoming data. This is useful if the endpoint
     * is only being used to send packets, and received packets may create a potential memory leak if they
     * are not being removed from the queue. If ignore is true, incoming packets will be discarded and
     * will not be added to the receive queue.
     *
     * @param ignore If true, incoming packets are ignored. If false, the packets are added to the receive queue.
     */
    void SetIgnoreReceived(bool ignore);

    virtual void AddListener(const RR_SHARED_PTR<PipeEndpointBaseListener>& listener);

    RR_SHARED_PTR<RobotRaconteurNode> GetNode();

  protected:
    virtual void RemoteClose();

    PipeEndpointBase(const RR_SHARED_PTR<PipeBase>& parent, int32_t index, uint32_t endpoint = 0,
                     bool unreliable = false, MemberDefinition_Direction direction = MemberDefinition_Direction_both);

    bool unreliable;
    MemberDefinition_Direction direction;

    bool RequestPacketAck;

    void AsyncSendPacketBase(const RR_INTRUSIVE_PTR<RRValue>& packet,
                             RR_MOVE_ARG(boost::function<void(uint32_t, const RR_SHARED_PTR<RobotRaconteurException>&)>)
                                 handler);

    RR_INTRUSIVE_PTR<RRValue> ReceivePacketBase();
    RR_INTRUSIVE_PTR<RRValue> PeekPacketBase();

    RR_INTRUSIVE_PTR<RRValue> ReceivePacketBaseWait(int32_t timeout = RR_TIMEOUT_INFINITE);
    RR_INTRUSIVE_PTR<RRValue> PeekPacketBaseWait(int32_t timeout = RR_TIMEOUT_INFINITE);

    bool TryReceivePacketBaseWait(RR_INTRUSIVE_PTR<RRValue>& packet, int32_t timeout = RR_TIMEOUT_INFINITE,
                                  bool peek = false);

    boost::mutex sendlock;
    boost::mutex recvlock;

    std::deque<RR_INTRUSIVE_PTR<RRValue> > recv_packets;
    boost::condition_variable recv_packets_wait;

    uint32_t increment_packet_number(uint32_t packetnum);

    void PipePacketReceived(const RR_INTRUSIVE_PTR<RRValue>& packet, uint32_t packetnum);

    void PipePacketAckReceived(uint32_t packetnum);

    void Shutdown();

    virtual void fire_PipeEndpointClosedCallback() = 0;

    virtual void fire_PacketReceivedEvent() = 0;

    virtual void fire_PacketAckReceivedEvent(uint32_t packetnum) = 0;

    RR_SHARED_PTR<PipeBase> GetParent();

    bool closed;

    uint32_t send_packet_number;
    uint32_t recv_packet_number;

    RR_WEAK_PTR<PipeBase> parent;
    int32_t index;
    uint32_t endpoint;
    std::string service_path;
    std::string member_name;

    RR_UNORDERED_MAP<uint32_t, RR_INTRUSIVE_PTR<RRValue> > out_of_order_packets;

    bool ignore_incoming_packets;

    boost::mutex listeners_lock;
    std::list<RR_WEAK_PTR<PipeEndpointBaseListener> > listeners;

    detail::async_signal_semaphore pipe_packet_received_semaphore;

    RR_WEAK_PTR<RobotRaconteurNode> node;
};

/**
 * @brief Pipe endpoint used to transmit reliable or unreliable data streams
 *
 * Pipe endpoints are used to communicate data between connected pipe members.
 * See Pipe for more information on pipe members.
 *
 * Pipe endpoints are created by clients using the Pipe::Connect() or Pipe::AsyncConnect()
 * functions. Services receive incoming pipe endpoint connection requests through a
 * callback function specified using the Pipe::SetPipeConnectCallback() function. Services
 * may also use the PipeBroadcaster class to automate managing pipe endpoint lifecycles and
 * sending packets to all connected client endpoints.
 *
 * Pipe endpoints are *indexed*, meaning that more than one pipe endpoint pair can be created
 * using the same member. This means that multiple data streams can be created independent of
 * each other between the client and service using the same member.
 *
 * Pipes send reliable packet streams between connected client/service endpoint pairs.
 * Packets are sent using the SendPacket() or AsyncSendPacket() functions. Packets
 * are read from the receive queue using the ReceivePacket(), ReceivePacketWait(),
 * TryReceivePacketWait(), TryReceivePacketWait(), or PeekNextPacket(). The endpoint is closed
 * using the Close() or AsyncClose() function.
 *
 * This class is instantiated by the Pipe class. It should not be instantiated
 * by the user.
 *
 * @tparam T The packet data type
 */

template <typename T>
class PipeEndpoint : public PipeEndpointBase
{
  private:
    boost::function<void(RR_SHARED_PTR<PipeEndpoint<T> >)> PipeEndpointClosedCallback;
    boost::mutex PipeEndpointClosedCallback_lock;

  public:
    /**
     * @brief Get the currently configured endpoint closed callback function
     *
     * @return boost::function<void (RR_SHARED_PTR<PipeEndpoint<T> >)>
     */
    boost::function<void(RR_SHARED_PTR<PipeEndpoint<T> >)> GetPipeEndpointClosedCallback()
    {
        boost::mutex::scoped_lock lock(PipeEndpointClosedCallback_lock);
        return PipeEndpointClosedCallback;
    }

    /**
     * @brief Set the endpoint closed callback function
     *
     * Sets a function to invoke when the pipe endpoint has been closed.
     *
     * Callback function must accept one argument, receiving the PipeEndpointPtr<T> that
     * was closed.
     *
     * @param callback The callback function
     */
    void SetPipeEndpointClosedCallback(boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> callback)
    {
        boost::mutex::scoped_lock lock(PipeEndpointClosedCallback_lock);
        PipeEndpointClosedCallback = callback;
    }

    /**
     * @brief Signal called when a packet has been received
     *
     * Callback function must accept one argument, receiving the PipeEndpointPtr<T> that
     * received a packet
     */
    boost::signals2::signal<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> PacketReceivedEvent;

    /**
     * @brief Signal called when a packet ack has been received
     *
     * Packet acks are generated if SetRequestPacketAck() is set to true. The receiving
     * endpoint generates acks to inform the sender that the packet has been received.
     *
     * Callback function must accept two arguments, receiving the PipeEndpointPtr<T>
     * that received the packet ack and the packet number that is being acked.
     */
    boost::signals2::signal<void(const RR_SHARED_PTR<PipeEndpoint<T> >&, uint32_t)> PacketAckReceivedEvent;

    /**
     * @brief Sends a packet to the peer endpoint
     *
     * Sends a packet to the peer endpoint. If the pipe is reliable, the packetsare  guaranteed to arrive
     * in order. If the pipe is set to unreliable, "best effort" is made to deliver packets, and they are not
     * guaranteed to arrive in order. This function will block until the packet has been transmitted by the
     * transport. It will return before the peer endpoint has received the packet.
     *
     * @param packet The packet to send
     * @return uint32_t The packet number of the sent packet
     */
    virtual uint32_t SendPacket(typename boost::call_traits<T>::param_type packet)
    {
        ROBOTRACONTEUR_ASSERT_MULTITHREADED(node);

        RR_SHARED_PTR<detail::sync_async_handler<uint32_t> > t =
            RR_MAKE_SHARED<detail::sync_async_handler<uint32_t> >();
        boost::function<void(const RR_SHARED_PTR<uint32_t>&, const RR_SHARED_PTR<RobotRaconteurException>&)> h =
            boost::bind(&detail::sync_async_handler<uint32_t>::operator(), t, RR_BOOST_PLACEHOLDERS(_1),
                        RR_BOOST_PLACEHOLDERS(_2));
        AsyncSendPacket(
            packet, boost::bind(&PipeEndpoint::send_handler, RR_BOOST_PLACEHOLDERS(_1), RR_BOOST_PLACEHOLDERS(_2), h));
        return *t->end();
    }

    /**
     * @brief Send a packet to the peer endpoint asynchronously
     *
     * Same as SendPacket(), but returns asynchronously.
     *
     * @param packet The packet to send
     * @param handler A handler function to receive the sent packet number or an exception
     */
    virtual void AsyncSendPacket(typename boost::call_traits<T>::param_type packet,
                                 boost::function<void(uint32_t, const RR_SHARED_PTR<RobotRaconteurException>&)> handler)
    {
        AsyncSendPacketBase(RRPrimUtil<T>::PrePack(packet), RR_MOVE(handler));
    }

    /**
     * @brief Receive the next packet in the receive queue
     *
     * Receive the next packet from the receive queue. This function will throw an
     * InvalidOperationException if there are no packets in the receive queue. Use
     * ReceivePacketWait() to block until a packet has been received.
     *
     * @return T The received packet
     */
    virtual T ReceivePacket() { return RRPrimUtil<T>::PreUnpack(ReceivePacketBase()); }

    /**
     * @brief Peeks the next packet in the receive queue
     *
     * Returns the first packet in the receive queue, but does not remove it from
     * the queue. Throws an InvalidOperationException if there are no packets in the
     * receive queue.
     *
     * @return T The next packet in the receive queue
     */
    virtual T PeekNextPacket() { return RRPrimUtil<T>::PreUnpack(PeekPacketBase()); }

    /**
     * @brief Receive the next packet in the receive queue, block if queue is empty
     *
     * Same as ReceivePacket(), but blocks if queue is empty
     *
     * @param timeout Timeout in milliseconds to wait for a packet, or RR_TIMEOUT_INFINITE for no timeout
     * @return T The received packet
     */
    virtual T ReceivePacketWait(int32_t timeout = RR_TIMEOUT_INFINITE)
    {
        return RRPrimUtil<T>::PreUnpack(ReceivePacketBaseWait(timeout));
    }

    /**
     * @brief Peek the next packet in the receive queue, block if queue is empty
     *
     * Same as PeekPacket(), but blocks if queue is empty
     *
     * @param timeout Timeout in milliseconds to wait for a packet, or RR_TIMEOUT_INFINITE for no timeout
     * @return T The received packet
     */
    virtual T PeekNextPacketWait(int32_t timeout = RR_TIMEOUT_INFINITE)
    {
        return RRPrimUtil<T>::PreUnpack(PeekPacketBaseWait(timeout));
    }

    /**
     * @brief Try receiving a packet, optionally blocking if the queue is empty
     *
     * Try receiving a packet with various options. Returns true if a packet has been
     * received, or false if no packet is available instead of throwing an exception on failure.
     * The timeout and peek parameters can be used to modify behavior to provide functionality
     * similar to the various Receive and Peek functions.
     *
     * @param val [out] The received packet
     * @param timeout The timeout in milliseconds. Set to zero for non-blocking operation, an arbitrary value
     *     in milliseconds for a finite duration timeout, or RR_TIMEOUT_INFINITE for no timeout
     * @param peek If true, the packet is not removed from the receive queue
     * @return true A packet has been successfully received
     * @return false No packet has been received. The content of val is undefined.
     */
    virtual bool TryReceivePacketWait(T& val, int32_t timeout = RR_TIMEOUT_INFINITE, bool peek = false)
    {
        RR_INTRUSIVE_PTR<RRValue> o;
        if (!TryReceivePacketBaseWait(o, timeout, peek))
            return false;
        val = RRPrimUtil<T>::PreUnpack(o);
        return true;
    }

    PipeEndpoint(const RR_SHARED_PTR<PipeBase>& parent, int32_t index, uint32_t endpoint = 0, bool unreliable = false,
                 MemberDefinition_Direction direction = MemberDefinition_Direction_both)
        : PipeEndpointBase(parent, index, endpoint, unreliable, direction){};

  protected:
    RR_OVIRTUAL void fire_PipeEndpointClosedCallback() RR_OVERRIDE
    {
        boost::function<void(RR_SHARED_PTR<PipeEndpoint<T> >)> c = GetPipeEndpointClosedCallback();
        if (!c)
            return;
        c(RR_STATIC_POINTER_CAST<PipeEndpoint<T> >(shared_from_this()));
    }

    RR_OVIRTUAL void fire_PacketReceivedEvent() RR_OVERRIDE
    {
        PacketReceivedEvent(RR_STATIC_POINTER_CAST<PipeEndpoint<T> >(shared_from_this()));
    }

    RR_OVIRTUAL void fire_PacketAckReceivedEvent(uint32_t packetnum) RR_OVERRIDE
    {
        PacketAckReceivedEvent(RR_STATIC_POINTER_CAST<PipeEndpoint<T> >(shared_from_this()), packetnum);
    }

    static void send_handler(uint32_t packetnumber, const RR_SHARED_PTR<RobotRaconteurException>& err,
                             const boost::function<void(const RR_SHARED_PTR<uint32_t>&,
                                                        const RR_SHARED_PTR<RobotRaconteurException>&)>& handler)
    {
        handler(RR_MAKE_SHARED<uint32_t>(packetnumber), err);
    }

  public:
    RR_OVIRTUAL void Close() RR_OVERRIDE
    {
        PipeEndpointBase::Close();
        {

            boost::mutex::scoped_lock lock(PipeEndpointClosedCallback_lock);
            PipeEndpointClosedCallback.clear();
        }
        PacketReceivedEvent.disconnect_all_slots();
        PacketAckReceivedEvent.disconnect_all_slots();
    }

  protected:
    virtual void AsyncClose1(const RR_SHARED_PTR<RobotRaconteurException>& err,
                             const boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)>& handler)
    {
        try
        {
            {
                boost::mutex::scoped_lock lock(PipeEndpointClosedCallback_lock);
                PipeEndpointClosedCallback.clear();
            }
            PacketReceivedEvent.disconnect_all_slots();
            PacketAckReceivedEvent.disconnect_all_slots();
        }
        catch (std::exception&)
        {}

        handler(err);
    }

  public:
    RR_OVIRTUAL void AsyncClose(boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)> handler,
                                int32_t timeout = 2000) RR_OVERRIDE
    {
        PipeEndpointBase::AsyncClose(boost::bind(&PipeEndpoint<T>::AsyncClose1,
                                                 RR_STATIC_POINTER_CAST<PipeEndpoint<T> >(shared_from_this()),
                                                 RR_BOOST_PLACEHOLDERS(_1), handler),
                                     timeout);
    }

  protected:
    RR_OVIRTUAL void RemoteClose() RR_OVERRIDE
    {
        PipeEndpointBase::RemoteClose();
        {
            boost::mutex::scoped_lock lock(PipeEndpointClosedCallback_lock);
            PipeEndpointClosedCallback.clear();
        }
        PacketReceivedEvent.disconnect_all_slots();
        PacketAckReceivedEvent.disconnect_all_slots();
    }
};

/**
 * @brief Base class for Pipe
 *
 * Base class for templated Pipe
 *
 */
class ROBOTRACONTEUR_CORE_API PipeBase : public RR_ENABLE_SHARED_FROM_THIS<PipeBase>, private boost::noncopyable
{
    friend class PipeEndpointBase;

  public:
    virtual ~PipeBase() {}

    /**
     * @brief Dynamically select pipe endpoint index
     *
     * Pass to Connect() or AsyncConnect() to use any available endpoint index
     */
    static const int32_t ANY_INDEX = -1;

    /**
     * @brief Get the member name of the pipe
     *
     * @return std::string
     */
    virtual std::string GetMemberName() = 0;

    virtual void PipePacketReceived(const RR_INTRUSIVE_PTR<MessageEntry>& m, uint32_t e = 0) = 0;

    virtual void Shutdown() = 0;

    virtual std::string GetServicePath() = 0;

    virtual void AsyncClose(const RR_SHARED_PTR<PipeEndpointBase>& endpoint, bool remote, uint32_t ee,
                            RR_MOVE_ARG(boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)>) handler,
                            int32_t timeout) = 0;

  protected:
    PipeBase();

    bool unreliable;

    virtual void AsyncSendPipePacket(
        const RR_INTRUSIVE_PTR<RRValue>& data, int32_t index, uint32_t packetnumber, bool requestack, uint32_t endpoint,
        bool unreliable,
        RR_MOVE_ARG(boost::function<void(uint32_t, const RR_SHARED_PTR<RobotRaconteurException>&)>) handler) = 0;

    bool rawelements;

    void DispatchPacketAck(const RR_INTRUSIVE_PTR<MessageElement>& me, const RR_SHARED_PTR<PipeEndpointBase>& e);

    bool DispatchPacket(const RR_INTRUSIVE_PTR<MessageElement>& me, const RR_SHARED_PTR<PipeEndpointBase>& e,
                        uint32_t& packetnumber);

    RR_INTRUSIVE_PTR<MessageElement> PackPacket(const RR_INTRUSIVE_PTR<RRValue>& data, int32_t index,
                                                uint32_t packetnumber, bool requestack);

    virtual void DeleteEndpoint(const RR_SHARED_PTR<PipeEndpointBase>& e) = 0;

    virtual RR_INTRUSIVE_PTR<MessageElementData> PackData(const RR_INTRUSIVE_PTR<RRValue>& data)
    {
        return GetNode()->PackVarType(data);
    }

    virtual RR_INTRUSIVE_PTR<RRValue> UnpackData(const RR_INTRUSIVE_PTR<MessageElement>& mdata)
    {
        return GetNode()->UnpackVarType(mdata);
    }

    RR_WEAK_PTR<RobotRaconteurNode> node;

    MemberDefinition_Direction direction;

  public:
    RR_SHARED_PTR<RobotRaconteurNode> GetNode();

    /**
     * @brief The direction of the pipe
     *
     * Pipes may be declared *readonly* or *writeonly* in the service definition file. (If neither
     * is specified, the pipe is assumed to be full duplex.) *readonly* pipes may only send packets from
     * service to client. *writeonly* pipes may only send packets from client to service.
     *
     * @return MemberDefinition_Direction
     */
    MemberDefinition_Direction Direction();

    /**
     * @brief Get if pipe is declared unreliable
     *
     * Pipe members may be declared as *unreliable* using member modifiers in the
     * service definition. Pipes confirm unreliable operation when pipe endpoints are connected.
     *
     * @return true The pipe is declared unreliable
     * @return false The pipe is declared reliable
     */
    bool IsUnreliable();
};

/**
 * @brief `pipe` member type interface
 *
 * The Pipe class implements the `pipe` member type. Pipes are declared in service definition files
 * using the `pipe` keyword within object declarations. Pipes provide reliable packet streaming between
 * clients and services. They work by creating pipe endpoint pairs (peers), with one endpoint in the client,
 * and one in the service. Packets are transmitted between endpoint pairs. Packets sent by one endpoint are received
 * by the other, where they are placed in a receive queue. Received packets can then be retrieved from the receive
 * queue.
 *
 * Pipe endpoints are created by the client using the Connect() or AsyncConnect() functions. Services receive
 * incoming connection requests through a callback function. This callback is configured using the
 * SetPipeConnectCallback() function. Services may also use the PipeBroadcaster class to automate managing pipe endpoint
 * lifecycles and sending packets to all connected client endpoints. If the SetPipeConnectCallback() function is used,
 * the service is responsible for keeping track of endpoints as the connect and disconnect. See PipeEndpoint for details
 * on sending and receiving packets.
 *
 * Pipe endpoints are *indexed*, meaning that more than one endpoint pair can be created between the client and the
 * service.
 *
 * Pipes may be *unreliable*, meaning that packets may arrive out of order or be dropped. Use IsUnreliable() to check
 * for unreliable pipes. The member modifier `unreliable` is used to specify that a pipe should be unreliable.
 *
 * Pipes may be declared *readonly* or *writeonly*. If neither is specified, the pipe is assumed to be full duplex.
 * *readonly* pipes may only send packets from service to client. *writeonly* pipes may only send packets from client to
 * service. Use Direction() to determine the direction of the pipe.
 *
 * The PipeBroadcaster is often used to simplify the use of Pipes. See PipeBroadcaster for more information.
 *
 * This class is instantiated by the node. It should not be instantiated by the user.
 *
 * @tparam T The packet data type
 */
template <typename T>
class Pipe : public virtual PipeBase
{
  public:
    friend class PipeEndpointBase;

    Pipe(boost::function<void(const RR_INTRUSIVE_PTR<RRValue>&)> verify) { this->verify = RR_MOVE(verify); }

    RR_OVIRTUAL ~Pipe() RR_OVERRIDE {}

    /**
     * @brief Get the currently configured pipe endpoint connected callback function
     *
     * Only valid for services. Will throw InvalidOperationException on client side.
     *
     * @return boost::function<void(RR_SHARED_PTR<PipeEndpoint<T> >)> The currently configured callback function
     */
    virtual boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> GetPipeConnectCallback() = 0;

    /**
     * @brief Set the pipe endpoint connected callback function
     *
     * Callback function invoked when a client attempts to connect a pipe endpoint. The callback
     * will receive the incoming pipe endpoint as a parameter. The service must maintain a reference to the
     * pipe endpoint, but the pipe will retain ownership of the endpoint until it is closed. Using
     * boost::weak_ptr to store the reference to the endpoint is recommended.
     *
     * The callback may throw an exception to reject incoming connect request.
     *
     * Note: Connect callback is configured automatically by PipeBroadcaster
     *
     * Only valid for services. Will throw InvalidOperationException on the client side.
     *
     * @param function Callback function to receive incoming pipe endpoint
     */
    virtual void SetPipeConnectCallback(boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> function) = 0;

    /**
     * @brief Connect a pipe endpoint
     *
     * Creates a connected pipe endpoint pair, and returns the local endpoint. Use to create the streaming data
     * connection to the service. Pipe endpoints are indexed, meaning that Connect() may be called multiple
     * times for the same client connection to create multple pipe endpoint pairs. For most cases Pipe::ANY_INDEX
     * (-1) can be used to automatically select an available index.
     *
     * Only valid on clients. Will throw InvalidOperationException on the service side.
     *
     * @param index The index of the pipe endpoint, or ANY_INDEX to automatically select an index
     * @return RR_SHARED_PTR<PipeEndpoint<T> > The connected pipe endpoint
     */
    virtual RR_SHARED_PTR<PipeEndpoint<T> > Connect(int32_t index) = 0;

    /**
     * @brief Asynchronously connect a pipe endpoint.
     *
     * Same as Connect(), but returns asynchronously.
     *
     * Only valid on clients. Will throw InvalidOperationException on the service side.
     *
     * @param index The index of the pipe endpoint, or ANY_INDEX to automatically select an index
     * @param handler A handler function to receive the connected endpoint, or an exception
     * @param timeout Timeout in milliseconds, or RR_TIMEOUT_INFINITE for no timeout
     */
    virtual void AsyncConnect(
        int32_t index,
        boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&, const RR_SHARED_PTR<RobotRaconteurException>&)>
            handler,
        int32_t timeout = RR_TIMEOUT_INFINITE) = 0;

    /**
     * @brief Asynchronously connect a pipe endpoint.
     *
     * Same as AsyncConnect(), but automatically selects a pipe endpoint index
     *
     * Only valid on clients. Will throw InvalidOperationException on the service side.
     *
     * @param handler A handler function to receive the connected endpoint, or an exception
     * @param timeout Timeout in milliseconds, or RR_TIMEOUT_INFINITE for no timeout
     */
    virtual void AsyncConnect(
        boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&, const RR_SHARED_PTR<RobotRaconteurException>&)>
            handler,
        int32_t timeout = RR_TIMEOUT_INFINITE)
    {
        AsyncConnect(-1, RR_MOVE(handler), timeout);
    }

    RR_OVIRTUAL RR_INTRUSIVE_PTR<MessageElementData> PackData(const RR_INTRUSIVE_PTR<RRValue>& data) RR_OVERRIDE
    {
        if (verify)
        {
            verify(data);
        }
        return GetNode()->template PackAnyType<typename RRPrimUtil<T>::BoxedType>(data);
    }

    RR_OVIRTUAL RR_INTRUSIVE_PTR<RRValue> UnpackData(const RR_INTRUSIVE_PTR<MessageElement>& mdata) RR_OVERRIDE
    {
        if (!verify)
        {
            return GetNode()->template UnpackAnyType<typename RRPrimUtil<T>::BoxedType>(mdata);
        }
        else
        {
            RR_INTRUSIVE_PTR<RRValue> ret = GetNode()->template UnpackAnyType<typename RRPrimUtil<T>::BoxedType>(mdata);
            verify(ret);
            return ret;
        }
    }

  protected:
    boost::function<void(const RR_INTRUSIVE_PTR<RRValue>&)> verify;
};

class ROBOTRACONTEUR_CORE_API ServiceStub;

class ROBOTRACONTEUR_CORE_API PipeClientBase : public virtual PipeBase
{
  public:
    friend class PipeSubscriptionBase;
    friend class detail::PipeSubscription_connection;

    RR_OVIRTUAL ~PipeClientBase() RR_OVERRIDE {}

    RR_OVIRTUAL std::string GetMemberName() RR_OVERRIDE;

    RR_OVIRTUAL void PipePacketReceived(const RR_INTRUSIVE_PTR<MessageEntry>& m, uint32_t e = 0) RR_OVERRIDE;

    RR_OVIRTUAL void Shutdown() RR_OVERRIDE;

    RR_OVIRTUAL void AsyncClose(const RR_SHARED_PTR<PipeEndpointBase>& endpoint, bool remote, uint32_t ee,
                                RR_MOVE_ARG(boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)>)
                                    handler,
                                int32_t timeout) RR_OVERRIDE;

    RR_SHARED_PTR<ServiceStub> GetStub();

    RR_OVIRTUAL std::string GetServicePath() RR_OVERRIDE;

  protected:
    RR_OVIRTUAL void AsyncSendPipePacket(
        const RR_INTRUSIVE_PTR<RRValue>& data, int32_t index, uint32_t packetnumber, bool requestack, uint32_t endpoint,
        bool unreliable,
        RR_MOVE_ARG(boost::function<void(uint32_t, const RR_SHARED_PTR<RobotRaconteurException>&)>)
            handler) RR_OVERRIDE;

    std::string m_MemberName;

    RR_UNORDERED_MAP<int32_t, RR_SHARED_PTR<PipeEndpointBase> > pipeendpoints;
    boost::mutex pipeendpoints_lock;

    RR_WEAK_PTR<ServiceStub> stub;

    std::list<boost::tuple<int32_t, int32_t> > connecting_endpoints;
    int32_t connecting_key_count;
    RR_UNORDERED_MAP<int32_t, RR_SHARED_PTR<PipeEndpointBase> > early_endpoints;
    std::string service_path;
    uint32_t endpoint;

    void AsyncConnect_internal(int32_t index,
                               RR_MOVE_ARG(boost::function<void(const RR_SHARED_PTR<PipeEndpointBase>&,
                                                                const RR_SHARED_PTR<RobotRaconteurException>&)>)
                                   handler,
                               int32_t timeout);

    void AsyncConnect_internal1(const RR_INTRUSIVE_PTR<MessageEntry>& ret,
                                const RR_SHARED_PTR<RobotRaconteurException>& err, int32_t index, int32_t key,
                                boost::function<void(const RR_SHARED_PTR<PipeEndpointBase>&,
                                                     const RR_SHARED_PTR<RobotRaconteurException>&)>& handler);

    PipeClientBase(boost::string_ref name, const RR_SHARED_PTR<ServiceStub>& stub, bool unreliable,
                   MemberDefinition_Direction direction);

    virtual RR_SHARED_PTR<PipeEndpointBase> CreateNewPipeEndpoint(int32_t index, bool unreliable,
                                                                  MemberDefinition_Direction direction) = 0;

    RR_OVIRTUAL void DeleteEndpoint(const RR_SHARED_PTR<PipeEndpointBase>& e) RR_OVERRIDE;
};

template <typename T>
class PipeClient : public virtual Pipe<T>, public virtual PipeClientBase
{
  public:
    RR_OVIRTUAL ~PipeClient() RR_OVERRIDE {}

    RR_OVIRTUAL boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> GetPipeConnectCallback() RR_OVERRIDE
    {
        ROBOTRACONTEUR_LOG_DEBUG_COMPONENT_PATH(node, Member, endpoint, service_path, m_MemberName,
                                                "GetPipeConnectCallback is not valid for PipeClient");
        throw InvalidOperationException("Not valid for client");
    }

    RR_OVIRTUAL void SetPipeConnectCallback(boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> function)
        RR_OVERRIDE
    {
        RR_UNUSED(function);
        ROBOTRACONTEUR_LOG_DEBUG_COMPONENT_PATH(node, Member, endpoint, service_path, m_MemberName,
                                                "SetPipeConnectCallback is not valid for PipeClient");
        throw InvalidOperationException("Not valid for client");
    }

    RR_OVIRTUAL void AsyncConnect(
        int32_t index,
        boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&, const RR_SHARED_PTR<RobotRaconteurException>&)>
            handler,
        int32_t timeout = RR_TIMEOUT_INFINITE) RR_OVERRIDE
    {

        AsyncConnect_internal(index,
                              boost::bind(handler,
                                          boost::bind(&PipeClient<T>::AsyncConnect_cast, RR_BOOST_PLACEHOLDERS(_1)),
                                          RR_BOOST_PLACEHOLDERS(_2)),
                              timeout);
    }

    RR_OVIRTUAL RR_SHARED_PTR<PipeEndpoint<T> > Connect(int32_t index) RR_OVERRIDE
    {
        ROBOTRACONTEUR_ASSERT_MULTITHREADED(node);

        RR_SHARED_PTR<detail::sync_async_handler<PipeEndpoint<T> > > t =
            RR_MAKE_SHARED<detail::sync_async_handler<PipeEndpoint<T> > >();
        AsyncConnect(index,
                     boost::bind(&detail::sync_async_handler<PipeEndpoint<T> >::operator(), t,
                                 RR_BOOST_PLACEHOLDERS(_1), RR_BOOST_PLACEHOLDERS(_2)),
                     GetNode()->GetRequestTimeout());
        return t->end();
    }

    PipeClient(boost::string_ref name, const RR_SHARED_PTR<ServiceStub>& stub, bool unreliable = false,
               MemberDefinition_Direction direction = MemberDefinition_Direction_both,
               boost::function<void(const RR_INTRUSIVE_PTR<RRValue>&)> verify = RR_NULL_FN)
        : PipeClientBase(name, stub, unreliable, direction), Pipe<T>(verify)
    {
        rawelements = (boost::is_same<T, RR_INTRUSIVE_PTR<MessageElement> >::value);
    }

    using PipeClientBase::AsyncClose;
    using PipeClientBase::AsyncSendPipePacket;
    using PipeClientBase::GetMemberName;
    using PipeClientBase::PipePacketReceived;
    using PipeClientBase::Shutdown;

  protected:
    static RR_SHARED_PTR<PipeEndpoint<T> > AsyncConnect_cast(const RR_SHARED_PTR<PipeEndpointBase>& b)
    {
        return rr_cast<PipeEndpoint<T> >(b);
    }

    RR_OVIRTUAL RR_SHARED_PTR<PipeEndpointBase> CreateNewPipeEndpoint(int32_t index, bool unreliable,
                                                                      MemberDefinition_Direction direction) RR_OVERRIDE
    {
        return RR_MAKE_SHARED<PipeEndpoint<T> >(RR_STATIC_POINTER_CAST<PipeBase>(shared_from_this()), index, 0,
                                                unreliable, direction);
    }
};

class ROBOTRACONTEUR_CORE_API ServiceSkel;
class ROBOTRACONTEUR_CORE_API PipeServerBase : public virtual PipeBase
{
  public:
    RR_OVIRTUAL ~PipeServerBase() RR_OVERRIDE {}

    RR_OVIRTUAL std::string GetMemberName() RR_OVERRIDE;

    RR_OVIRTUAL void PipePacketReceived(const RR_INTRUSIVE_PTR<MessageEntry>& m, uint32_t e = 0) RR_OVERRIDE;

    RR_OVIRTUAL void Shutdown() RR_OVERRIDE;

    RR_OVIRTUAL void AsyncSendPipePacket(
        const RR_INTRUSIVE_PTR<RRValue>& data, int32_t index, uint32_t packetnumber, bool requestack, uint32_t endpoint,
        bool unreliable,
        RR_MOVE_ARG(boost::function<void(uint32_t, const RR_SHARED_PTR<RobotRaconteurException>&)>)
            handler) RR_OVERRIDE;

    RR_OVIRTUAL void AsyncClose(const RR_SHARED_PTR<PipeEndpointBase>& endpoint, bool remote, uint32_t ee,
                                RR_MOVE_ARG(boost::function<void(const RR_SHARED_PTR<RobotRaconteurException>&)>)
                                    handler,
                                int32_t timeout) RR_OVERRIDE;

    virtual RR_INTRUSIVE_PTR<MessageEntry> PipeCommand(const RR_INTRUSIVE_PTR<MessageEntry>& m, uint32_t e);

    RR_SHARED_PTR<ServiceSkel> GetSkel();

    RR_OVIRTUAL std::string GetServicePath() RR_OVERRIDE;

  protected:
    std::string m_MemberName;
    std::string service_path;

    struct pipe_endpoint_server_id
    {
        pipe_endpoint_server_id(uint32_t endpoint, int32_t index)
        {
            this->endpoint = endpoint;
            this->index = index;
        }

        uint32_t endpoint;
        int32_t index;

        bool operator==(const pipe_endpoint_server_id& rhs) const
        {
            return (endpoint == rhs.endpoint && index == rhs.index);
        }
    };

    struct hash_value
    {
        std::size_t operator()(pipe_endpoint_server_id const& e) const
        {
            std::size_t seed = 0;
            boost::hash_combine(seed, e.endpoint);
            boost::hash_combine(seed, e.index);
            return seed;
        }
    };

    RR_UNORDERED_MAP<pipe_endpoint_server_id, RR_SHARED_PTR<PipeEndpointBase>, hash_value> pipeendpoints;
    boost::mutex pipeendpoints_lock;

    RR_WEAK_PTR<ServiceSkel> skel;

    PipeServerBase(boost::string_ref name, const RR_SHARED_PTR<ServiceSkel>& skel, bool unreliable,
                   MemberDefinition_Direction direction);

    virtual RR_SHARED_PTR<PipeEndpointBase> CreateNewPipeEndpoint(int32_t index, uint32_t endpoint, bool unreliable,
                                                                  MemberDefinition_Direction direction) = 0;

    RR_OVIRTUAL void DeleteEndpoint(const RR_SHARED_PTR<PipeEndpointBase>& e) RR_OVERRIDE;

    virtual void fire_PipeConnectCallback(const RR_SHARED_PTR<PipeEndpointBase>& e) = 0;

    bool init;
    boost::signals2::connection listener_connection;

  public:
    void ClientDisconnected(const RR_SHARED_PTR<ServerContext>& context, ServerServiceListenerEventType ev,
                            const RR_SHARED_PTR<void>& param);
};

template <typename T>
class PipeServer : public virtual PipeServerBase, public virtual Pipe<T>
{

  public:
    RR_OVIRTUAL ~PipeServer() RR_OVERRIDE {}

    RR_OVIRTUAL boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> GetPipeConnectCallback() RR_OVERRIDE
    {
        return callback;
    }

    RR_OVIRTUAL void SetPipeConnectCallback(boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&)> function)
        RR_OVERRIDE
    {
        callback = function;
    }

    RR_OVIRTUAL RR_SHARED_PTR<PipeEndpoint<T> > Connect(int32_t index) RR_OVERRIDE
    {
        RR_UNUSED(index);
        ROBOTRACONTEUR_LOG_DEBUG_COMPONENT_PATH(node, Member, -1, service_path, m_MemberName,
                                                "Connect is not valid for PipeServer");
        throw InvalidOperationException("Not valid for server");
    }

    RR_OVIRTUAL void AsyncConnect(
        int32_t index,
        boost::function<void(const RR_SHARED_PTR<PipeEndpoint<T> >&, const RR_SHARED_PTR<RobotRaconteurException>&)>
            handler,
        int32_t timeout = RR_TIMEOUT_INFINITE) RR_OVERRIDE
    {
        RR_UNUSED(index);
        RR_UNUSED(handler);
        RR_UNUSED(timeout);
        ROBOTRACONTEUR_LOG_DEBUG_COMPONENT_PATH(node, Member, -1, service_path, m_MemberName,
                                                "AsyncConnect is not valid for PipeServer");
        throw InvalidOperationException("Not valid for server");
    }

    PipeServer(boost::string_ref name, const RR_SHARED_PTR<ServiceSkel>& skel, bool unreliable = false,
               MemberDefinition_Direction direction = MemberDefinition_Direction_both,
               boost::function<void(const RR_INTRUSIVE_PTR<RRValue>&)> verify = RR_NULL_FN)
        : PipeServerBase(name, skel, unreliable, direction), Pipe<T>(verify)
    {
        rawelements = (boost::is_same<T, RR_INTRUSIVE_PTR<MessageElement> >::value);
    }

  protected:
    RR_OVIRTUAL RR_SHARED_PTR<PipeEndpointBase> CreateNewPipeEndpoint(int32_t index, uint32_t endpoint, bool unreliable,
                                                                      MemberDefinition_Direction direction) RR_OVERRIDE
    {
        return RR_MAKE_SHARED<PipeEndpoint<T> >(RR_STATIC_POINTER_CAST<PipeBase>(shared_from_this()), index, endpoint,
                                                unreliable, direction);
    }

    boost::function<void(RR_SHARED_PTR<PipeEndpoint<T> >)> callback;

    RR_OVIRTUAL void fire_PipeConnectCallback(const RR_SHARED_PTR<PipeEndpointBase>& e) RR_OVERRIDE
    {
        if (!callback)
            return;
        callback(RR_STATIC_POINTER_CAST<PipeEndpoint<T> >(e));
    }

  public:
    RR_OVIRTUAL void Shutdown() RR_OVERRIDE
    {
        PipeServerBase::Shutdown();

        callback.clear();
    }
};

namespace detail
{
class PipeBroadcasterBase_connected_endpoint;
struct PipeBroadcasterBase_async_send_operation;
} // namespace detail

/**
 * @brief Base class for PipeBroadcaster
 *
 * Base class for templated PipeBroadcaster class
 *
 */
class ROBOTRACONTEUR_CORE_API PipeBroadcasterBase : public RR_ENABLE_SHARED_FROM_THIS<PipeBroadcasterBase>,
                                                    private boost::noncopyable
{
  public:
    virtual ~PipeBroadcasterBase();

    size_t GetActivePipeEndpointCount();

    /**
     * @brief Get the current predicate callback function
     *
     * @return boost::function<bool(RR_SHARED_PTR<PipeBroadcasterBase>&, uint32_t, int32_t) > The predicate callback
     * function
     */
    boost::function<bool(const RR_SHARED_PTR<PipeBroadcasterBase>&, uint32_t, int32_t)> GetPredicate();

    /**
     * @brief Set the predicate callback function
     *
     * A predicate is optionally used to regulate when packets are sent to clients. This is used by the
     * BroadcastDownsampler to regulate update rates of packets sent to clients.
     *
     * The predicate callback is invoked before the broadcaster sends a packet to an endpoint. If the predicate returns
     * true, the packet will be sent. If it is false, the packet will not be sent to that endpoint. The predicate
     * callback must have the following signature:
     *
     *     bool broadcaster_predicate(PipeBroadcasterBasePtr& broadcaster, uint32_t client_endpoint, int32_t
     * pipe_endpoint_index);
     *
     * It receives the broadcaster, the client endpoint ID, and the pipe endpoint index. It returns true to send the
     * packet, or false to not send the packet.
     *
     * @param f The predicate callback function
     */
    void SetPredicate(boost::function<bool(const RR_SHARED_PTR<PipeBroadcasterBase>&, uint32_t, int32_t)> f);

    /**
     * @brief Gets the currently configured maximum backlog
     *
     * @return int32_t The maximum backlog
     */
    int32_t GetMaxBacklog();

    /**
     * @brief Set the maximum backlog
     *
     * PipeBroadcaster provides flow control by optionally tracking how many packets
     * are in flight to each client pipe endpoint. (This is accomplished using packet acks.) If a
     * maximum backlog is specified, pipe endpoints exceeding this count will stop sending packets.
     *
     * @param maximum_backlog The maximum number of packets in flight, or -1 for unlimited
     */
    void SetMaxBacklog(int32_t maximum_backlog);

  protected:
    PipeBroadcasterBase();

    void InitBase(const RR_SHARED_PTR<PipeBase>& pipe, int32_t maximum_backlog = -1);

    void EndpointConnectedBase(const RR_SHARED_PTR<PipeEndpointBase>& ep);

    void EndpointClosedBase(const RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint>& ep);

    void PacketAckReceivedBase(const RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint>& ep, uint32_t id);

    void handle_send(int32_t id, const RR_SHARED_PTR<RobotRaconteurException>& err,
                     const RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint>& ep,
                     const RR_SHARED_PTR<detail::PipeBroadcasterBase_async_send_operation>& op, int32_t key,
                     int32_t send_key, const boost::function<void()>& handler);

    void SendPacketBase(const RR_INTRUSIVE_PTR<RRValue>& packet);

    void AsyncSendPacketBase(const RR_INTRUSIVE_PTR<RRValue>& packet, RR_MOVE_ARG(boost::function<void()>) handler);

    virtual void AttachPipeServerEvents(const RR_SHARED_PTR<PipeServerBase>& p);

    virtual void AttachPipeEndpointEvents(const RR_SHARED_PTR<PipeEndpointBase>& p,
                                          const RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint>& cep);

    RR_SHARED_PTR<PipeBase> GetPipeBase();

    std::list<RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint> > endpoints;
    boost::mutex endpoints_lock;

    RR_WEAK_PTR<PipeServerBase> pipe;
    RR_WEAK_PTR<RobotRaconteurNode> node;
    int32_t maximum_backlog;
    std::string service_path;
    std::string member_name;

    bool copy_element;

    boost::function<bool(const RR_SHARED_PTR<PipeBroadcasterBase>&, uint32_t, int32_t)> predicate;
};

/**
 * @brief Broadcaster to send packets to all connected clients
 *
 * PipeBroadcaster is used by services to send packets to all connected
 * client endpoints. It attaches to the pipe on the service side, and
 * manages the lifecycle of connected endpoints. PipeBroadcaster should
 * only be used with pipes that are declared *readonly*, since it has
 * no provisions for receiving incoming packets from the client.
 *
 * PipeBroadcaster is initialized by the user, or by default implementation
 * classes generated by RobotRaconteurGen (*_default_impl). Default
 * implementation classes will automatically instantiate broadcasters
 * for pipes marked *readonly*. If default implementation classes are
 * not used, the broadcaster must be instantiated manually. It is
 * recommended this be done using the IRRServiceObject interface in
 * the overridden IRRServiceObject::RRServiceObjectInit() function. This
 * function is called after the pipes have been instantiated by the service.
 *
 * Use SendPacket() or AsyncSendPacket() to broadcast packets to all
 * connected clients.
 *
 * PipeBroadcaster provides flow control by optionally tracking how many packets
 * are in flight to each client pipe endpoint. (This is accomplished using packet acks.) If a
 * maximum backlog is specified, pipe endpoints exceeding this count will stop sending packets.
 * Specify the maximum backlog using the Init() function or the SetMaxBacklog() function.
 *
 * The rate that packets are sent can be regulated using a callback function configured
 * with the SetPredicate() function, or using the BroadcastDownsampler class.
 *
 * @tparam T The packet data type
 */
template <typename T>
class PipeBroadcaster : public PipeBroadcasterBase
{

  public:
    /**
     * @brief Construct a new PipeBroadcaster
     *
     * Must use boost::make_shared<PipeBroadcaster<T> >() to construct. Must
     * call Init() after construction.
     *
     */
    PipeBroadcaster() {}

    /**
     * @brief Initialize the PipeBroadcaster
     *
     * Initialize the PipeBroadcaster for use. Must be called after construction.
     *
     * @param pipe The pipe to use for broadcasting. Must be a pipe from a service object.
     * Specifying a client pipe will result in an exception.
     * @param maximum_backlog The maximum number of packets in flight, or -1 for unlimited
     */
    void Init(RR_SHARED_PTR<Pipe<T> > pipe, int32_t maximum_backlog = -1) { InitBase(pipe, maximum_backlog); }

    /**
     * @brief Send a packet to all connected pipe endpoint clients
     *
     * Blocks until packet has been sent by all endpoints
     *
     * @param packet The packet to send
     */
    void SendPacket(T packet) { SendPacketBase(RRPrimUtil<T>::PrePack(packet)); }

    /**
     * @brief Asynchronously send packet to all connected pipe endpoint clients
     *
     * Asynchronous version of SendPacket()
     *
     * @param packet The packet to send
     * @param handler A handler function for when packet has been sent by all endpoints
     */
    void AsyncSendPacket(T packet, boost::function<void()> handler)
    {
        AsyncSendPacketBase(RRPrimUtil<T>::PrePack(packet), RR_MOVE(handler));
    }

    /**
     * @brief Get the associated pipe
     *
     * @return RR_SHARED_PTR<Pipe<T> >
     */
    RR_SHARED_PTR<Pipe<T> > GetPipe() { return rr_cast<Pipe<T> >(GetPipeBase()); }

  protected:
    RR_OVIRTUAL void AttachPipeServerEvents(const RR_SHARED_PTR<PipeServerBase>& p) RR_OVERRIDE
    {
        RR_SHARED_PTR<PipeServer<T> > p_T = rr_cast<PipeServer<T> >(p);

        p_T->SetPipeConnectCallback(
            boost::bind(&PipeBroadcaster::EndpointConnectedBase, shared_from_this(), RR_BOOST_PLACEHOLDERS(_1)));
    }

    RR_OVIRTUAL void AttachPipeEndpointEvents(const RR_SHARED_PTR<PipeEndpointBase>& ep,
                                              const RR_SHARED_PTR<detail::PipeBroadcasterBase_connected_endpoint>& cep)
        RR_OVERRIDE
    {
        RR_SHARED_PTR<PipeEndpoint<T> > ep_T = rr_cast<PipeEndpoint<T> >(ep);

        ep_T->SetPipeEndpointClosedCallback(boost::bind(&PipeBroadcaster::EndpointClosedBase, shared_from_this(), cep));
        ep_T->PacketAckReceivedEvent.connect(
            boost::bind(&PipeBroadcaster::PacketAckReceivedBase, shared_from_this(), cep, RR_BOOST_PLACEHOLDERS(_2)));
    }
};
#ifndef ROBOTRACONTEUR_NO_CXX11_TEMPLATE_ALIASES
/** @brief Convenience alias for PipeEndpointBase shared_ptr */
using PipeEndpointBasePtr = RR_SHARED_PTR<PipeEndpointBase>;
/** @brief Convenience alias for PipeEndpoint shared_ptr */
template <typename T>
using PipeEndpointPtr = RR_SHARED_PTR<PipeEndpoint<T> >;
/** @brief Convenience alias for PipeBase shared_ptr */
using PipeBasePtr = RR_SHARED_PTR<PipeBase>;
/** @brief Convenience alias for Pipe shared_ptr */
template <typename T>
using PipePtr = RR_SHARED_PTR<Pipe<T> >;
/** @brief Convenience alias for PipeBroadcaster shared_ptr */
template <typename T>
using PipeBroadcasterPtr = RR_SHARED_PTR<PipeBroadcaster<T> >;
#endif
} // namespace RobotRaconteur

#ifdef _MSVC_VER
#pragma warning(pop)
#endif
