#include <catch.hpp>

#include <pub_sub_intl.hh>
#include <pub_intl.hh>
#include <pub_tcp.hh>
#include <pub_zstd.hh>
#include <cds-pubsub/pub.hh>
#include <cds-pubsub/sub.hh>

#include <atomic>
#include <set>
#include <sstream>

namespace
{
    class RestartSubDebug : public pub_sub::SubDebugNotices
    {
    public:
        void
        connection_terminated( const pub_sub::SubId   id,
                               const pub_sub::KeyType keyType ) override
        {
            terminate_count_++;
        }

        void
        connection_started( const pub_sub::SubId   id,
                            const pub_sub::KeyType keyType ) override
        {
            start_count_++;
        }

        std::int64_t
        start_count( ) const
        {
            return start_count_;
        }

        std::int64_t
        terminate_count( ) const
        {
            return terminate_count_;
        }

    private:
        std::int64_t start_count_{ 0 };
        std::int64_t terminate_count_{ 0 };
    };
} // namespace

TEST_CASE( "You can destroy a publisher_intl" )
{
    auto debug = pub_sub::detail::NullPubDebugNotices( );
    {
        pub_sub::detail::PublisherIntl pub( debug );
    }
    REQUIRE( true );
}

TEST_CASE( "You can destroy a publisher" )
{
    auto debug = pub_sub::detail::NullPubDebugNotices( );
    {
        pub_sub::Publisher pub( debug );
    }
    REQUIRE( true );
}

TEST_CASE( "You can send messages through tcp" )
{
    std::atomic< int > good_messages{ 0 };
    std::set< int >    messages;

    auto debug_pub = pub_sub::detail::NullPubDebugNotices( );

    pub_sub::detail::PublisherIntlTcp pub_tcp( "127.0.0.1", 0, *debug_pub );

    std::string sub_str;
    {
        std::ostringstream stream{};
        auto               endpoint = pub_tcp.local_endpoint( );
        std::cout << "tcp://127.0.0.1:" << endpoint.port( ) << std::flush;
        stream << "tcp://127.0.0.1:" << endpoint.port( ) << std::flush;
        char buff[ 100 ];
        sprintf( &buff[ 0 ], "tcp://127.0.0.1:%d", endpoint.port( ) );
        // sub_str == stream.str();
        sub_str = buff;
        std::cout << sub_str << "\n";
        std::cout << "connecting through " << sub_str << std::endl;
    }
    auto sub = std::make_unique< pub_sub::Subscriber >( );
    sub->subscribe(
        sub_str, [&good_messages, &messages]( pub_sub::SubMessage sub_msg ) {
            if ( messages.find( sub_msg.key( ) ) != messages.end( ) )
            {
                return;
            }
            messages.insert( sub_msg.key( ) );
            if ( static_cast< unsigned char >( sub_msg.key( ) ) ==
                 *reinterpret_cast< unsigned char* >( sub_msg.data( ) ) )
            {
                good_messages++;
            }
        } );
    int i = 0;
    while ( good_messages.load( ) < 1000 )
    {
        pub_sub::DataPtr data( new unsigned char[ 20000 ] );
        std::fill( data.get( ),
                   data.get( ) + 20000,
                   static_cast< unsigned char >( i ) );
        pub_tcp.publish( i, pub_sub::Message( data, 20000 ) );
        std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
        ++i;
    }
}

TEST_CASE( "You can send messages through tcp and reset the tcp connection" )
{
    std::atomic< int > good_messages{ 0 };
    std::set< int >    messages;

    RestartSubDebug debug_sub;
    auto            debug_pub = pub_sub::detail::NullPubDebugNotices( );

    pub_sub::detail::PublisherIntlTcp pub_tcp( "127.0.0.1", 0, *debug_pub );

    std::string sub_str;
    {
        std::ostringstream stream{};
        auto               endpoint = pub_tcp.local_endpoint( );
        std::cout << "tcp://127.0.0.1:" << endpoint.port( ) << std::flush;
        stream << "tcp://127.0.0.1:" << endpoint.port( ) << std::flush;
        char buff[ 100 ];
        sprintf( &buff[ 0 ], "tcp://127.0.0.1:%d", endpoint.port( ) );
        // sub_str == stream.str();
        sub_str = buff;
        std::cout << sub_str << "\n";
        std::cout << "connecting through " << sub_str << std::endl;
    }
    auto sub = std::make_unique< pub_sub::Subscriber >( );
    sub->SetupDebugHooks( &debug_sub );
    auto sub_ptr = sub.get( );
    int  msg_count = 0;
    sub->subscribe(
        sub_str,
        [sub_ptr, &msg_count, &good_messages, &messages](
            pub_sub::SubMessage sub_msg ) {
            if ( msg_count % 100 == 0 )
            {
                sub_ptr->reset_subscription( sub_msg.sub_id( ) );
            }
            ++msg_count;
            if ( messages.find( sub_msg.key( ) ) != messages.end( ) )
            {
                return;
            }
            messages.insert( sub_msg.key( ) );
            if ( static_cast< unsigned char >( sub_msg.key( ) ) ==
                 *reinterpret_cast< unsigned char* >( sub_msg.data( ) ) )
            {
                good_messages++;
            }
        } );
    int i = 0;
    while ( good_messages.load( ) < 1000 )
    {
        pub_sub::DataPtr data( new unsigned char[ 20000 ] );
        std::fill( data.get( ),
                   data.get( ) + 20000,
                   static_cast< unsigned char >( i ) );
        pub_tcp.publish( i, pub_sub::Message( data, 20000 ) );
        std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
        ++i;
    }
    REQUIRE( debug_sub.start_count( ) > 3 );
    REQUIRE( debug_sub.terminate_count( ) > 3 );
}