The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

use Test::More 0.88;

use List::AllUtils qw( each_array );
use Math::Int128 qw(uint128);
use Net::Works::Network;

{
    _test_remove_reserved_subnets(
        [ '0.0.0.1', '1.2.3.4' ] => [ [ '0.0.0.1', '1.2.3.4' ] ] );

    _test_remove_reserved_subnets( [ '9.10.11.12', '9.255.255.255' ] =>
            [ [ '9.10.11.12', '9.255.255.255' ] ] );

    _test_remove_reserved_subnets(
        [ '9.0.0.0', '12.0.0.0' ] => [
            [ '9.0.0.0',  '9.255.255.255' ],
            [ '11.0.0.0', '12.0.0.0' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '9.0.0.0', '10.255.255.255' ] => [
            [ '9.0.0.0', '9.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '10.2.3.4', '12.255.255.255' ] => [
            [ '11.0.0.0', '12.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets( [ '10.0.0.0', '10.10.10.10' ] => [] );

    _test_remove_reserved_subnets(
        [ '0.0.0.0', '255.255.255.255' ] => [
            [ '0.0.0.0',      '9.255.255.255' ],
            [ '11.0.0.0',     '126.255.255.255' ],
            [ '128.0.0.0',    '169.253.255.255' ],
            [ '169.255.0.0',  '172.15.255.255' ],
            [ '172.32.0.0',   '192.0.1.255' ],
            [ '192.0.3.0',    '192.88.98.255' ],
            [ '192.88.100.0', '192.167.255.255' ],
            [ '192.169.0.0',  '223.255.255.255' ],
            [ '240.0.0.0',    '255.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '::', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ] => [
            map {
                [
                    map {
                        Net::Works::Address->new_from_string(
                            string  => $_,
                            version => 6
                            )->as_string()
                    } @{$_}
                ]
                } (
                [ '::',             '::9.255.255.255' ],
                [ '::11.0.0.0',     '::126.255.255.255' ],
                [ '::128.0.0.0',    '::169.253.255.255' ],
                [ '::169.255.0.0',  '::172.15.255.255' ],
                [ '::172.32.0.0',   '::192.0.1.255' ],
                [ '::192.0.3.0',    '::192.88.98.255' ],
                [ '::192.88.100.0', '::192.167.255.255' ],
                [ '::192.169.0.0',  '::223.255.255.255' ],
                [ '::240.0.0.0',
                    '2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    '2001:1::',
                    'fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    'fe00::',
                    'fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    'fec0::',
                    'feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                ),
        ],
    );
}

{
    my $start = '0.0.0.1';
    my $end   = '0.0.0.255';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            0.0.0.1/32
            0.0.0.2/31
            0.0.0.4/30
            0.0.0.8/29
            0.0.0.16/28
            0.0.0.32/27
            0.0.0.64/26
            0.0.0.128/25
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '10.0.0.0';
    my $end   = '12.0.0.0';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            11.0.0.0/8
            12.0.0.0/32
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '0.0.0.1';
    my $end   = '19.255.255.255';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            0.0.0.1/32
            0.0.0.2/31
            0.0.0.4/30
            0.0.0.8/29
            0.0.0.16/28
            0.0.0.32/27
            0.0.0.64/26
            0.0.0.128/25
            0.0.1.0/24
            0.0.2.0/23
            0.0.4.0/22
            0.0.8.0/21
            0.0.16.0/20
            0.0.32.0/19
            0.0.64.0/18
            0.0.128.0/17
            0.1.0.0/16
            0.2.0.0/15
            0.4.0.0/14
            0.8.0.0/13
            0.16.0.0/12
            0.32.0.0/11
            0.64.0.0/10
            0.128.0.0/9
            1.0.0.0/8
            2.0.0.0/7
            4.0.0.0/6
            8.0.0.0/7
            11.0.0.0/8
            12.0.0.0/6
            16.0.0.0/6
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '::1';
    my $end   = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff';

    my @subnets = do {
        map {
            Net::Works::Address->new_from_integer(
                integer => uint128(2)**$_,
                version => 6
                )->as_string()
                . '/'
                . ( 128 - $_ )
        } 0 .. 26;
    };

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_, version => 6 ) } @subnets,
        qw(
            ::8.0.0.0/103
            ::11.0.0.0/104
            ::12.0.0.0/102
            ::16.0.0.0/100
            ::32.0.0.0/99
            ::64.0.0.0/99
            ::96.0.0.0/100
            ::112.0.0.0/101
            ::120.0.0.0/102
            ::124.0.0.0/103
            ::126.0.0.0/104
            ::128.0.0.0/99
            ::160.0.0.0/101
            ::168.0.0.0/104
            ::169.0.0.0/105
            ::169.128.0.0/106
            ::169.192.0.0/107
            ::169.224.0.0/108
            ::169.240.0.0/109
            ::169.248.0.0/110
            ::169.252.0.0/111
            ::169.255.0.0/112
            ::170.0.0.0/103
            ::172.0.0.0/108
            ::172.32.0.0/107
            ::172.64.0.0/106
            ::172.128.0.0/105
            ::173.0.0.0/104
            ::174.0.0.0/103
            ::176.0.0.0/100
            ::192.0.0.0/119
            ::192.0.3.0/120
            ::192.0.4.0/118
            ::192.0.8.0/117
            ::192.0.16.0/116
            ::192.0.32.0/115
            ::192.0.64.0/114
            ::192.0.128.0/113
            ::192.1.0.0/112
            ::192.2.0.0/111
            ::192.4.0.0/110
            ::192.8.0.0/109
            ::192.16.0.0/108
            ::192.32.0.0/107
            ::192.64.0.0/108
            ::192.80.0.0/109
            ::192.88.0.0/114
            ::192.88.64.0/115
            ::192.88.96.0/119
            ::192.88.98.0/120
            ::192.88.100.0/118
            ::192.88.104.0/117
            ::192.88.112.0/116
            ::192.88.128.0/113
            ::192.89.0.0/112
            ::192.90.0.0/111
            ::192.92.0.0/110
            ::192.96.0.0/107
            ::192.128.0.0/107
            ::192.160.0.0/109
            ::192.169.0.0/112
            ::192.170.0.0/111
            ::192.172.0.0/110
            ::192.176.0.0/108
            ::192.192.0.0/106
            ::193.0.0.0/104
            ::194.0.0.0/103
            ::196.0.0.0/102
            ::200.0.0.0/101
            ::208.0.0.0/100
            ::240.0.0.0/100
            ::1:0:0/96
            ::2:0:0/95
            ::4:0:0/94
            ::8:0:0/93
            ::10:0:0/92
            ::20:0:0/91
            ::40:0:0/90
            ::80:0:0/89
            ::100:0:0/88
            ::200:0:0/87
            ::400:0:0/86
            ::800:0:0/85
            ::1000:0:0/84
            ::2000:0:0/83
            ::4000:0:0/82
            ::8000:0:0/81
            ::1:0:0:0/80
            ::2:0:0:0/79
            ::4:0:0:0/78
            ::8:0:0:0/77
            ::10:0:0:0/76
            ::20:0:0:0/75
            ::40:0:0:0/74
            ::80:0:0:0/73
            ::100:0:0:0/72
            ::200:0:0:0/71
            ::400:0:0:0/70
            ::800:0:0:0/69
            ::1000:0:0:0/68
            ::2000:0:0:0/67
            ::4000:0:0:0/66
            ::8000:0:0:0/65
            0:0:0:1::/64
            0:0:0:2::/63
            0:0:0:4::/62
            0:0:0:8::/61
            0:0:0:10::/60
            0:0:0:20::/59
            0:0:0:40::/58
            0:0:0:80::/57
            0:0:0:100::/56
            0:0:0:200::/55
            0:0:0:400::/54
            0:0:0:800::/53
            0:0:0:1000::/52
            0:0:0:2000::/51
            0:0:0:4000::/50
            0:0:0:8000::/49
            0:0:1::/48
            0:0:2::/47
            0:0:4::/46
            0:0:8::/45
            0:0:10::/44
            0:0:20::/43
            0:0:40::/42
            0:0:80::/41
            0:0:100::/40
            0:0:200::/39
            0:0:400::/38
            0:0:800::/37
            0:0:1000::/36
            0:0:2000::/35
            0:0:4000::/34
            0:0:8000::/33
            0:1::/32
            0:2::/31
            0:4::/30
            0:8::/29
            0:10::/28
            0:20::/27
            0:40::/26
            0:80::/25
            0:100::/24
            0:200::/23
            0:400::/22
            0:800::/21
            0:1000::/20
            0:2000::/19
            0:4000::/18
            0:8000::/17
            1::/16
            2::/15
            4::/14
            8::/13
            10::/12
            20::/11
            40::/10
            80::/9
            100::/8
            200::/7
            400::/6
            800::/5
            1000::/4
            2000::/16
            2001:1::/32
            2001:2::/31
            2001:4::/30
            2001:8::/29
            2001:10::/28
            2001:20::/27
            2001:40::/26
            2001:80::/25
            2001:100::/24
            2001:200::/23
            2001:400::/22
            2001:800::/21
            2001:1000::/20
            2001:2000::/19
            2001:4000::/18
            2001:8000::/17
            2002::/15
            2004::/14
            2008::/13
            2010::/12
            2020::/11
            2040::/10
            2080::/9
            2100::/8
            2200::/7
            2400::/6
            2800::/5
            3000::/4
            4000::/2
            8000::/2
            c000::/3
            e000::/4
            f000::/5
            f800::/6
            fe00::/9
            fec0::/10
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

done_testing();

sub _test_remove_reserved_subnets {
    my $range  = shift;
    my $expect = shift;

    my $version = $range->[0] =~ /:/ ? 6 : 4;

    # All these conversions are gross but normally this is handled by the
    # range_as_subnets method.
    my $first = Net::Works::Address->new_from_string(
        string  => $range->[0],
        version => $version,
    )->as_integer();

    my $last = Net::Works::Address->new_from_string(
        string  => $range->[1],
        version => $version
    )->as_integer();

    my @got = map {
        [
            map {
                Net::Works::Address->new_from_integer(
                    integer => $_,
                    version => $version
                    )
            } @{$_}
        ]
        } Net::Works::Network->_remove_reserved_subnets_from_range(
        $first,
        $last,
        $version,
        );

    is_deeply(
        \@got,
        $expect,
        "_remove_reserved_subnets_from_range returned expected result for $range->[0] - $range->[1]"
    );
}

sub _test_range_as_subnets {
    my $start          = shift;
    my $end            = shift;
    my $expect_subnets = shift;

    my @subnets = Net::Works::Network->range_as_subnets( $start, $end );

    is(
        scalar @subnets,
        scalar @{$expect_subnets},
        'got the same number of subnets as we expect'
    );

    my $iter = each_array( @subnets, @{$expect_subnets} );

    while ( my ( $got, $expect ) = $iter->() ) {
        last unless $got && $expect;

        my $first = eval { $expect->first()->as_ipv4_string() }
            // $expect->first()->as_string();

        is(
            $got->first()->as_string(),
            $expect->first()->as_string(),
            "subnet first matches expected first - split $start - $end ($first)"
        );

        my $last = eval { $expect->last()->as_ipv4_string() }
            // $expect->last()->as_string();

        is(
            $got->last()->as_string(),
            $expect->last()->as_string(),
            "subnet last matches expected last - split $start - $end ($last)"
        );

        my $netmask = $expect->mask_length();
        is(
            $got->mask_length(),
            $netmask,
            "netmask matches expected netmask - split $start - $end ($netmask)"
        );
    }
}