/* Common header for pub_sub code
Copyright (C) 2020 Jonathan Hanks

This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef PUB_SUB2_COMMON_HH
#define PUB_SUB2_COMMON_HH

#include <chrono>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>

/*!
 * @file common.hh
 * Common definitions for the pub_sub code.  Items shared by both publishers
 * and subscribers.
 */
namespace pub_sub
{
    /*!
     * @brief The KeyType is an 64bit unsigned int that should be
     * monotonically increasing.
     * @note It is the applications responsibility to ensure that it
     * is monotonically increasing.
     */
    using KeyType = std::uint64_t;

    /*!
     * @brief The largest possible KeyType is a sentinel value used to signal
     * just return the next arriving message.
     * @return The largest possible KeyType.
     */
    inline constexpr KeyType
                     NEXT_PUB_MSG( )
    {
        return std::numeric_limits< KeyType >::max( );
    }

    /*!
     * @brief put a maximum message size in for sanity
     * @return The maximum allowed message size.
     */
    inline constexpr std::size_t
                     MAX_MSG_SIZE( )
    {
        return 1024 * 1024 * 200;
    }

    /*!
     * @brief a Message is simply a buffer with a length.
     */
    using DataPtr = std::shared_ptr< unsigned char[] >;

    /*!
     * @brief A Message is a data block, it is data + length
     * @note Messages have shared ownership on the data given to them.
     */
    struct Message
    {

        Message( const DataPtr& new_data, std::size_t new_length )
            : data_{ new_data }, length{ new_length }
        {
        }
        Message( DataPtr&& new_data, std::size_t new_length )
            : data_{ std::move( new_data ) }, length{ new_length }
        {
        }

        Message( const Message& other ) = default;
        Message( Message&& other ) = default;

        Message& operator=( const Message& other ) = default;
        Message& operator=( Message&& other ) = default;

        void*
        data( ) const
        {
            return data_.get( );
        }

        void
        clear( )
        {
            data_ = static_cast< DataPtr >( nullptr );
            length = 0;
        }

        DataPtr     data_{ nullptr };
        std::size_t length{ 0 };
    };

    /*!
     * @brief SubId represents the id of an individual subscription
     */
    using SubId = int;

    /*!
     * @brief A class for callbacks/stats/...
     */
    class SubDebugNotices
    {
    public:
        SubDebugNotices( ) = default;
        virtual ~SubDebugNotices( ) = default;

        virtual void message_started( const SubId, const KeyType ){};
        virtual void message_received( const SubId,
                                       const KeyType,
                                       std::size_t               size,
                                       std::chrono::milliseconds spread ){};
        virtual void retransmit_request_made( const SubId,
                                              const KeyType,
                                              int packet_count ){};
        virtual void duplicate_packet( const SubId ){};
        virtual void message_dropped( const SubId, const KeyType ){};
        virtual void connection_terminated( const SubId, const KeyType ){};
        virtual void connection_started( const SubId, const KeyType ){};
    };

    class PubDebugNotices
    {
    public:
        PubDebugNotices( ) = default;
        virtual ~PubDebugNotices( ) = default;

        virtual void retransmit_request_seen( const KeyType,
                                              int packet_count ){};
        virtual void retransmit_sent( const KeyType, int packet_count ){};
        // virtual void message_block_destroyed( const KeyType ) {};
    };

    /*!
     * @brief A SubMessage is the message returned from a producer to a
     * subscription handler.  It is a wrapper to hold subscription ids, message
     * keys, the message, and to automate cleanup of the message buffer.
     */
    class SubMessage
    {
    public:
        SubMessage( SubId id, KeyType key, Message msg )
            : sub_id_{ id }, key_{ key }, msg_{ std::move( msg ) }
        {
        }

        SubMessage( const SubMessage& other ) noexcept = default;
        SubMessage( SubMessage&& other ) noexcept = default;

        SubMessage& operator=( const SubMessage& other ) noexcept = default;
        SubMessage& operator=( SubMessage&& other ) noexcept = default;

        SubId
        sub_id( ) const noexcept
        {
            return sub_id_;
        }

        KeyType
        key( ) const noexcept
        {
            return key_;
        }

        std::size_t
        size( ) const noexcept
        {
            return msg_.length;
        }

        void*
        data( ) const noexcept
        {
            return msg_.data( );
        }

        Message
        message( ) const noexcept
        {
            return msg_;
        }

    private:
        SubId   sub_id_;
        KeyType key_;
        Message msg_;
    };

    /*!
     * @brief The signaturel/type of a subscription handler, a function that
     * receives SubMessage objects.
     */
    using SubHandler = std::function< void( SubMessage sub_message ) >;
} // namespace pub_sub

#endif // PUB_SUB2_COMMON_HH
