//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2020-2026, Lola Delannoy
// BSD-2-Clause license, see LICENSE.txt file or https://tsduck.io/license
//
//----------------------------------------------------------------------------
//!
//!  @file
//!  Secure Reliable Transport (SRT) Socket.
//!
//----------------------------------------------------------------------------

#pragma once
#include "tsReporterBase.h"
#include "tsIPSocketAddress.h"
#include "tsUString.h"
#include "tsReport.h"
#include "tsEnumUtils.h"

namespace ts {
    //!
    //! Secure Reliable Transport (SRT) socket mode.
    //!
    enum class SRTSocketMode : int {
        DEFAULT    = -1,  //!< Unspecified, use command line mode.
        LISTENER   =  0,  //!< Listener mode.
        CALLER     =  1,  //!< Caller mode.
        RENDEZVOUS =  2,  //!< Rendez-vous mode (unsupported).
        LEN        =  3,  //!< Unknown.
    };

    //!
    //! Secure Reliable Transport (SRT) statistics mode.
    //! Can be used as bitmask.
    //!
    enum class SRTStatMode : uint16_t {
        NONE     = 0x0000,  //!< Reports nothing.
        RECEIVE  = 0x0001,  //!< Receive statistics (ignored if nothing was received).
        SEND     = 0x0002,  //!< Sender statistics (ignored if nothing was sent).
        TOTAL    = 0x0004,  //!< Statistics since the socket was opened.
        INTERVAL = 0x0008,  //!< Statistics in the last interval (restarted each time it is used).
        ALL      = 0x000F,  //!< Report all statistics.
    };
}
TS_ENABLE_BITMASK_OPERATORS(ts::SRTStatMode);

namespace ts {

    class Args;
    class DuckContext;

    //!
    //! Secure Reliable Transport (SRT) Socket.
    //! If the libsrt is not available during compilation of this class,
    //! all methods will fail with an error status.
    //! @see https://github.com/Haivision/srt
    //! @see https://www.srtalliance.org/
    //! @ingroup libtsduck net
    //!
    class TSDUCKDLL SRTSocket : public ReporterBase
    {
        TS_NOBUILD_NOCOPY(SRTSocket);
    public:
        //!
        //! Constructor.
        //! @param [in] report Where to report errors. The @a report object must remain valid as long as this object
        //! exists or setReport() is used with another Report object. If @a report is null, log messages are discarded.
        //!
        explicit SRTSocket(Report* report);

        //!
        //! Destructor.
        //!
        virtual ~SRTSocket() override;

        //!
        //! Open the socket using parameters from the command line.
        //! @param [in] max_payload Maximum payload size in bytes. Unset if NPOS.
        //! @return True on success, false on error.
        //!
        bool open(size_t max_payload = NPOS)
        {
            return open(SRTSocketMode::DEFAULT, IPSocketAddress(), IPSocketAddress(), max_payload);
        }

        //!
        //! Open the socket.
        //! @param [in] mode SRT socket mode. If set to DEFAULT, the mode must have been specified in the command line options.
        //! @param [in] local Local socket address. Ignored in DEFAULT mode. Optional local IP address used in CALLER mode.
        //! @param [in] remote Remote socket address. Ignored in DEFAULT and LISTENER modes.
        //! @param [in] max_payload Maximum payload size in bytes. Unset if NPOS.
        //! @return True on success, false on error.
        //!
        bool open(SRTSocketMode mode, const IPSocketAddress& local, const IPSocketAddress& remote, size_t max_payload = NPOS);

        //!
        //! Close the socket.
        //! @param [in] silent If true, do not report errors through the logger. This is typically useful when the socket
        //! is in some error condition and closing it is necessary although it may generate additional meaningless errors.
        //! @return True on success, false on error.
        //!
        bool close(bool silent = false);

        //!
        //! Add command line option definitions in an Args.
        //! @param [in,out] args Command line arguments to update.
        //!
        void defineArgs(Args& args);

        //!
        //! Load arguments from command line.
        //! Args error indicator is set in case of incorrect arguments.
        //! @param [in,out] duck TSDuck execution context.
        //! @param [in,out] args Command line arguments.
        //! @return True on success, false on error in argument line.
        //!
        bool loadArgs(DuckContext& duck, Args& args);

        //!
        //! Preset local and remote socket addresses in string form.
        //! - If only @a listener is not empty, the socket is set in listener mode.
        //! - If only @a caller is not empty, the socket is set in caller mode.
        //! - If both addresses are not empty, the socket is set in rendezvous mode.
        //! - If both addresses are empty, the current mode of the socket is reset and local and/or
        //!   remote addresses must be specified by command line arguments or through open().
        //! @param [in] listener Local "[address:]port".
        //! @param [in] caller Remote "address:port".
        //! @param [in] local Optional, can be empty. In caller mode, specify the local outgoing IP address.
        //! @return True on success, false on error.
        //!
        bool setAddresses(const IPSocketAddress& listener, const IPSocketAddress& caller, const IPAddress& local = IPAddress())
        {
            return setAddressesInternal(listener, caller, local, true);
        }

        //!
        //! Get the socket peers, local and remote.
        //! @param [out] local Local socket address.
        //! @param [out] remote Remote socket address.
        //! @return True on success, false on error.
        //!
        bool getPeers(IPSocketAddress& local, IPSocketAddress& remote);

        //!
        //! Send a message to the default destination address and port.
        //! @param [in] data Address of the message to send.
        //! @param [in] size Size in bytes of the message to send.
        //! @return True on success, false on error.
        //!
        bool send(const void* data, size_t size);

        //!
        //! Receive a message.
        //! @param [out] data Address of the buffer for the received message.
        //! @param [in] max_size Size in bytes of the reception buffer.
        //! @param [out] ret_size Size in bytes of the received message. Will never be larger than @a max_size.
        //! @return True on success, false on error.
        //!
        bool receive(void* data, size_t max_size, size_t& ret_size);

        //!
        //! Receive a message with timestamp.
        //! @param [out] data Address of the buffer for the received message.
        //! @param [in] max_size Size in bytes of the reception buffer.
        //! @param [out] ret_size Size in bytes of the received message. Will never be larger than @a max_size.
        //! @param [out] timestamp Source timestamp in micro-seconds, negative if not available.
        //! @return True on success, false on error.
        //!
        bool receive(void* data, size_t max_size, size_t& ret_size, cn::microseconds& timestamp);

        //!
        //! Get the total number of sent bytes since the socket was opened.
        //! @return The total number of sent bytes since the socket was opened.
        //!
        size_t totalSentBytes() const;

        //!
        //! Get the total number of received bytes since the socket was opened.
        //! @return The total number of received bytes since the socket was opened.
        //!
        size_t totalReceivedBytes() const;

        //!
        //! Check if the connection was disconnected by the peer.
        //! This can be used after a send/receive error to differentiate between "end of session" and actual error.
        //! @return True if the connection was closed by the peer.
        //!
        bool peerDisconnected() const;

        //!
        //! Get statistics about the socket and report them.
        //! @param [in] mode Type of statistics to report (or'ing bitmask values is allowed).
        //! @return True on success, false on error.
        //!
        bool reportStatistics(SRTStatMode mode = SRTStatMode::ALL);

        //!
        //! Get SRT option.
        //! @param [in] optName Option name as enumeration. The possible values for @a optName are given
        //! by the enumeration type SRT_SOCKOPT in libsrt. The profile of this method uses "int" to remain
        //! portable in the absence of libsrt, but the actual values come from SRT_SOCKOPT in libsrt.
        //! @param [in] optNameStr Option name as ASCII string.
        //! @param [out] optval Address of returned value.
        //! @param [in,out] optlen Size of returned buffer (input), updated to size of returned value.
        //! @return True on success, false on error.
        //!
        bool getSockOpt(int optName, const char* optNameStr, void* optval, int& optlen) const;

        //!
        //! Get the underlying SRT socket handle (use with care).
        //! This method is reserved for low-level operations and should not be used by normal applications.
        //! @return The underlying SRT socket handle.
        //!
        int getSocket() const;

        //!
        //! Check if the SRT socket uses the Message API.
        //! @return True if the SRT socket uses the Message API. False if it uses the Buffer API.
        //!
        bool getMessageApi() const;

        //!
        //! Get the version of the SRT library.
        //! @return A string describing the SRT library version (or the lack of SRT support).
        //!
        static UString GetLibraryVersion();

    private:
        // The actual implementation is private to the body of the class.
        class Guts;
        Guts* _guts;

        // Internal verson of setAddresses().
        bool setAddressesInternal(const IPSocketAddress& listener, const IPSocketAddress& caller, const IPAddress& local, bool reset);
    };
}
