The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use Test::More;
use strict;
use warnings;
use IO::Socket::INET;
use t::Util;

use Net::Proxy;

# dummy data
my @lines = (
    "swa_a_p bang swish bap crunch\n",
    "zlonk zok zapeth crunch_eth crraack\n",
    "glipp zwapp urkkk cr_r_a_a_ck glurpp\n",
    "zzzzzwap thwapp zgruppp awk eee_yow\n",
    "ker_plop spla_a_t swoosh cr_r_a_a_ck bang_eth pam uggh\n",
    "AEGEAN_NUMBER_NINETY MATHEMATICAL_SANS_SERIF_ITALIC_SMALL_Y\n",
    "YI_SYLLABLE_SHUX ARABIC_LIGATURE_THEH_WITH_REH_FINAL_FORM\n",
    "TAG_PLUS_SIGN CYPRIOT_SYLLABLE_RE\n",
    "TAG_LATIN_CAPITAL_LETTER_S YI_SYLLABLE_QYRX\n",
    "MATHEMATICAL_DOUBLE_STRUCK_CAPITAL_U HALFWIDTH_HANGUL_LETTER_YEO\n",
    "linguine lasagne_ricce chiocciole\n",
    "fusilli_tricolore sedani_corti galla_mezzana\n",
    "fettucce_ricce maniche chifferi_rigati\n",
    "mista lasagne_festonate_a_nidi nidi\n",
    "capelvenere parigine lacchene\n",
    "occhi_di_passero guanti ditali\n",
);

# compute a seed and show it
init_rand( @ARGV );

# compute random configurations
my @confs = sort { $a->[0] <=> $b->[0] }
    map { [ int rand 16, int rand 8 ] } 1 .. 3;

# compute the total number of tests
my $tests = 1 + ( my $first = int rand 8 );
$tests += $_->[1] for @confs;
$tests += 1 + @confs;

# show the config if 
if( @ARGV ) { 
    diag sprintf "%2d %2d", @$_ for ( [ 0, $first ], @confs );
}
plan tests => $tests;

# lock 2 ports
my @ports = find_free_ports(3);

SKIP: {
    skip "Not enough available ports", $tests if @ports < 3;

    my ($proxy_port, $server_port, $fake_port) = @ports;
    my $pid = fork;

SKIP: {
        skip "fork failed", $tests if !defined $pid;
        if ( $pid == 0 ) {

            # the child process runs the proxy
            my $proxy = Net::Proxy->new(
                {   in => {
                        type => 'tcp',
                        host => 'localhost',
                        port => $proxy_port
                    },
                    out => {
                        type => 'tcp',
                        host => 'localhost',
                        port => $server_port
                    },
                }
            );

            $proxy->register();

            # test unregister()
            my $fake_proxy = Net::Proxy->new(
                {   in => {
                        type => 'tcp',
                        host => 'localhost',
                        port => $fake_port
                    },
                    out => {
                        type => 'tcp',
                        host => 'localhost',
                        port => $server_port
                    },
                }
            );
            $fake_proxy->register();
            $fake_proxy->unregister();

            Net::Proxy->set_verbosity( $ENV{NET_PROXY_VERBOSITY} || 0 );
            Net::Proxy->mainloop( @confs + 1 );
            exit;
        }
        else {

            # wait for the proxy to set up
            sleep 1;

            # start the server
            my $listener = listen_on_port($server_port)
                or skip "Couldn't start the server: $!", $tests;

            # create the first pair
            my %pairs;
            {
                my $pair = (
                    [   connect_to_port($proxy_port),
                        scalar $listener->accept(),
                        $first, 0
                    ]
                );
                %pairs = ( $pair => $pair );
            }

            # check the other proxy is not listening
            {
                my $client = connect_to_port($fake_port);
                is( $client, undef, "Second proxy not here: $!" );
            }

            my $step = my $n = my $count = 0;
            while (%pairs || @confs) {

                # create a new connection
            CONF:
                while ( @confs && $confs[0][0] == $step ) {
                    my $conf   = shift @confs;
                    my $client = connect_to_port($proxy_port)
                        or do {
                        diag "Couldn't start the client: $!";
                        next CONF;
                        };
                    my $server = $listener->accept()
                        or do { diag "Proxy didn't connect: $!"; next CONF; };
                    my $pair = [ $client, $server, $conf->[1], ++$count ];
                    $pairs{$pair} = $pair;
                }

            PAIR:
                for my $pair (values %pairs) {

                    # close the connection if finished
                    if ( $pair->[2] <= 0 ) {
                        $pair->[0]->close();
                        is_closed( $pair->[1],
                            "other socket of pair $pair->[3]" );
                        $pair->[1]->close();
                        delete $pairs{$pair};
                        next PAIR;
                    }

                    # fetch data to send
                    $n %= @lines;
                    my $line = $lines[$n];

                    # randomly swap client/server
                    @{$pair}[ 0, 1 ] = random_swap(@{$pair}[ 0, 1 ]);

                    # send data through the connection
                    print { $pair->[0] } $line;
                    is( $pair->[1]->getline(),
                        $line,
                        "Step $step: line $n sent through pair $pair->[3]" );
                    $pair->[2]--;
                    $n++;

                }
                $step++;
            }
        }
    }
}