/*******************************************************************************
 * Copyright (c) 2021 Nerian Vision GmbH
 *
 * 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.
 *******************************************************************************/

#ifndef VISIONTRANSFER_IMAGEPROTOCOL_H
#define VISIONTRANSFER_IMAGEPROTOCOL_H

#include "visiontransfer/common.h"
#include "visiontransfer/imageset.h"

#include <vector>

namespace visiontransfer {

/**
 * \brief A lightweight protocol for transferring image sets.
 *
 * Two images are transferred together as a set. These are usually the left
 * and right image of a stereo camera, or the left image and a disparity map.
 *
 * The images are 8- or 12-bit monochrome, or 8-bit RGB color. For simplicity,
 * 12-bit images are inflated to 16-bit by introducing additional padding
 * bits. Both images must always have the same image size.
 *
 * When receiving, the class takes in an image set and chops it down to several
 * network messages. When sending, the class takes a set of messages and
 * assembles them into an image set. We have to differentiate between TCP and
 * UDP in both cases.
 */
class VT_EXPORT ImageProtocol {
public:
    /// Supported network protocols
    enum ProtocolType {
        /// The connection oriented TCP transport protocol
        PROTOCOL_TCP,

        /// The connection-less UDP transport protocol
        PROTOCOL_UDP
    };

    /**
     * \brief Creates a new instance for decoding / encoding network messages
     * for the given network protocol.
     *
     * \param server If set to true, this object will be a communication server.
     * \param maxUdpPacketSize Maximum allowed size of a UDP packet when sending data.
     */
    ImageProtocol(bool server, ProtocolType protType, int maxUdpPacketSize = 1472);

    ~ImageProtocol();

    /**
     * \brief Sets a new image that will be transfer.
     *
     * \param imageSet The image set that shall be transmitted.
     *
     * After setting the transfer image, subsequent calls to
     * getTransferMessage() can be made for obtaining the encoded
     * network messages.
     */
    void setTransferImageSet(const ImageSet& imageSet);

    /**
     * \brief Sets the already pre-formatted image data for the next transfer.
     *
     * \param metaData ImageSet object containing all the meta data but no
     *        pixel data.
     * \param rawData Pre-formatted data for this transfer.
     * \param firstTileWidth If not 0, specifies the width of the first tile in
     *        a tiled transfer.
     * \param middleTilesWidth If not 0, specifies the width of the tiles  between
              the first and the last tile in a tiled transfer.
     * \param lastTileWidth If not 0, specifies the width of the last tile in
     *        a tiled transfer.
     * \param validBytes The number of bytes that are valid in \c rawData.
     *
     * This method is a more efficient alternative to setTransferImage(). In this case
     * the image data already has to be pre-formatted in the format of
     * the image protocol, which means row-wise interleaving both images.
     * For 12-bit images, the pixel data must be packed in LSB order.
     *
     * The pixel data contained in \c metaData is ignored, while all
     * other data is transmitted. The actual pixel data must be encoded in
     * \c rawData.
     *
     * Parts of \c rawData will be overwritten. There must be at least 4 additional
     * allocated bytes after the pixel data in \c rawData.
     *
     * If \c validBytes is set to a value smaller than the total transfer
     * size, only a partial transfer is performed. Subsequent calls to
     * setRawValidBytes() are then necessary.
     */
    void setRawTransferData(const ImageSet& metaData, const std::vector<unsigned char*>& imageData,
        int firstTileWidth = 0, int middleTilesWidth = 0, int lastTileWidth = 0, int validBytes = 0x7FFFFFFF);

    /**
     * \brief Updates the number of valid bytes in a partial raw transfer.
     *
     * \param validBytes The number of already valid bytes in the previously
     *        set raw data pointer.
     *
     * This method has to be called whenever new data is available in a raw
     * transfer. \see setRawTransferData()
     */
    void setRawValidBytes(const std::vector<int>& validBytes);

    /**
     * \brief Gets the next network message for the current transfer.
     *
     * \param length The length of the network message.
     * \return Pointer to the network message data.
     *
     * If the transfer has already been completed, a null pointer is returned.
     */
    const unsigned char* getTransferMessage(int& length);

    /**
     * \brief Returns true if the current transfer has been completed.
     */
    bool transferComplete();

    /**
     * \brief Aborts the transmission of the current transfer and performs a
     * reset of the internal state.
     */
    void resetTransfer();

    /**
     * \brief Returns a received image when complete.
     *
     * \param imageSet Will be set to the received image set.
     * \return Returns true if a new image set has been received. Otherwise
     *         false.
     *
     * After obtaining a received image set, reception is reset and
     * subsequent calls to getReceivedImageSet() or imagesReceived()
     * will return false.
     *
     * Please note that the received image data is only valid until processing
     * the first network message of the next image transfer.
     */
    bool getReceivedImageSet(ImageSet& imageSet);

    /**
     * \brief Returns a partially received image.
     *
     * \param imageSet Will be set to the partially received image set.
     * \param validRows The number of valid image rows in the partially received
                        image set.
     * \param complete True if the image set has been fully received.
     * \return Returns true if a full or partial image have been received.
     *         Otherwise false.
     *
     * If a partial image has been received, the meta data returned in
     * \c imageSet will be complete. The pixel data contained in
     * \c imageSet, however, will only be valid for the first
     * \c validRows rows.
     *
     * After obtaining a complete image set, reception is reset and
     * subsequent calls to getPartiallyReceivedImageSet() or imagesReceived()
     * will return false.
     *
     * Please note that the received image data is only valid until processing
     * the first network message of the next image transfer.
     */
    bool getPartiallyReceivedImageSet(ImageSet& imageSet, int& validRows, bool& complete);

    /**
     * \brief Returns true if the images of the current transfer have been received.
     */
    bool imagesReceived() const;

    /**
     * \brief Returns the buffer for receiving the next network message.
     *
     * \param maxLength Maximum allowed length for the next network message
     * \return Pointer to the buffer memory.
     **/
    unsigned char* getNextReceiveBuffer(int& maxLength);

    /**
     * \brief Handles a received network message
     *
     * \param length Length of the received network message.
     *
     * This method has to be called for every network message that has been
     * received. The message data must be located in the most recent buffer
     * that has been obtained with getNextReceiveBuffer().
     *
     * After calling this method, please check if a new image has been received
     * by calling getReceivedImageSet() or getPartiallyReceivedImageSet().
     *
     * In order to handle connection timeouts this method should be called
     * frequently. If no new data is available, a length of 0 can be passed.
     */
    void processReceivedMessage(int length);

    /**
     * \brief Returns the number of frames that have been dropped since
     * connecting to the current remote host.
     *
     * Dropped frames are caused by dropped packets due to a poor network
     * connection
     */
    int getNumDroppedFrames() const;

    /**
     * \brief Aborts the reception of the current image transfer and resets
     * the internal state.
     */
    void resetReception();

    /**
     * \brief Returns true if the last message has established a new connection
     * from a client
     */
    bool newClientConnected();

    /**
     * \brief Returns true if a remote connection is established
     */
    bool isConnected() const;

    /**
     * \brief If a control message is pending to be transmitted then
     * the message data will be returned by this method.
     *
     * \param length Will be set to the length of the message
     * \return Pointer to the message data or NULL if no message is pending
     *
     * Control messages are only needed when using the UDP network protocol.
     */
    const unsigned char* getNextControlMessage(int& length);

    std::string statusReport();

private:
    // We follow the pimpl idiom
    class Pimpl;
    Pimpl* pimpl;

    // This class cannot be copied
    ImageProtocol(const ImageProtocol& other);
    ImageProtocol& operator=(const ImageProtocol&);
};

} // namespace

#endif
