use strict;
use warnings;

use Math::Int128 qw(uint128);
use Test::Fatal;
use Test::More 0.88;

use Net::Works::Address;

{
    my $ip = Net::Works::Address->new_from_string( string => '1.2.3.4' );
    is(
        $ip->as_string(),
        '1.2.3.4',
        '->as_string returns string passed to constructor'
    );

    my $next = $ip->next_ip();
    isa_ok(
        $next,
        'Net::Works::Address',
        'return value of ->next_ip'
    );

    is(
        $next->as_string(),
        '1.2.3.5',
        'next ip after 1.2.3.4 is 1.2.3.5'
    );

    my $prev = $ip->previous_ip();
    isa_ok(
        $prev,
        'Net::Works::Address',
        'return value of ->previous_ip'
    );

    is(
        $prev->as_string(),
        '1.2.3.3',
        'previous ip before 1.2.3.4 is 1.2.3.3'
    );

    cmp_ok(
        $ip, '<', $next,
        'numeric overloading (<) on address objects works'
    );

    cmp_ok(
        $next, '>', $ip,
        'numeric overloading (>) on address objects works'
    );

    my $same_ip = Net::Works::Address->new_from_string( string => '1.2.3.4' );

    cmp_ok(
        $ip, '==', $same_ip,
        'numeric overloading (==) on address objects works'
    );

    is(
        $ip <=> $same_ip,
        0,
        'comparison overloading (==) on address objects works'
    );
}

{
    my $ip = Net::Works::Address->new_from_string( string => '192.168.0.255' )
        ->next_ip();
    is(
        $ip->as_string(),
        '192.168.1.0',
        '->next_ip wraps to the next ip address'
    );
}

{
    my $ip = Net::Works::Address->new_from_string( string => 'ffff::a:1234' );
    is(
        $ip->as_string(),
        'ffff::a:1234',
        '->as_string returns string passed to constructor'
    );

    my $prev = $ip->previous_ip();
    is(
        $prev->as_string(),
        'ffff::a:1233',
        'previous ip before ffff::a:1234 is ffff::a:1233'
    );

    my $next = $ip->next_ip();
    is(
        $next->as_string(),
        'ffff::a:1235',
        'next ip after ffff::a:1234 is ffff::a:1235'
    );
}

{
    my $ip = Net::Works::Address->new_from_string(
        string => 'ffff::0000:000a:1234' );
    is(
        $ip->as_string(),
        'ffff::a:1234',
        '->as_string returns compact form of IPv6'
    );
}

{
    for my $address (
        qw( 255.255.255.255 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff )) {

        my $ip = Net::Works::Address->new_from_string( string => $address );

        like(
            exception { $ip->next_ip() },
            qr/\Q$address is the last address in its range/,
            'cannot call ->next_ip on the last address in a range'
        );
    }
}

{
    for my $address (qw( 0.0.0.0 :: )) {

        my $ip = Net::Works::Address->new_from_string( string => $address );

        like(
            exception { $ip->previous_ip() },
            qr/\Q$address is the first address in its range/,
            'cannot call ->previous_ip on the first address in a range'
        );
    }
}

{
    my $ip = Net::Works::Address->new_from_integer(
        integer => 0,
        version => 4,
    );

    is(
        $ip->as_string(),
        '0.0.0.0',
        'new_from_integer(0), IPv4'
    );

    is(
        $ip->as_integer(),
        0,
        'as_integer returns 0'
    );

    is(
        $ip->as_bit_string(),
        '0' x 32,
        'as_bit_string returns 0x32'
    );

    $ip = Net::Works::Address->new_from_integer(
        integer => 2**32 - 1,
        version => 4,
    );

    is(
        $ip->as_string(),
        '255.255.255.255',
        'new_from_integer(2**32 - 1), IPv4'
    );

    is(
        $ip->as_integer(),
        2**32 - 1,
        'as_integer returns 2**32 - 1'
    );

    is(
        $ip->as_bit_string(),
        '1' x 32,
        'as_bit_string returns 1x32'
    );

    $ip = Net::Works::Address->new_from_integer(
        integer => 0,
        version => 6,
    );

    is(
        $ip->as_string(),
        '::',
        'new_from_integer(0), IPv6'
    );

    is(
        $ip->as_integer(),
        0,
        'as_integer returns 0, IPv6'
    );

    is(
        $ip->as_bit_string(),
        '0' x 128,
        'as_bit_string returns 0x128'
    );

    $ip = Net::Works::Address->new_from_integer(
        integer => 2**32 - 1,
        version => 6,
    );

    is(
        $ip->as_string(),
        '::255.255.255.255',
        'new_from_integer(2**32 - 1), IPv6'
    );

    is(
        $ip->as_bit_string(),
        ( '0' x 96 ) . ( '1' x 32 ),
        'as_bit_string returns 0x96 . 1x32'
    );

    is(
        $ip->as_integer(),
        2**32 - 1,
        'as_integer returns 2**32 - 1, IPv6'
    );

    my $max_128 = uint128(2)**128 - uint128(1);
    $ip = Net::Works::Address->new_from_integer(
        integer => $max_128,
        version => 6,
    );

    is(
        $ip->as_string(),
        'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
        'new_from_integer(2**128 - 1), IPv6'
    );

    is(
        $ip->as_integer(),
        $max_128,
        'as_integer returns 2**128 - 1, IPv6'
    );

    is(
        $ip->as_bit_string(),
        '1' x 128,
        'as_bit_string returns 1x128'
    );
}

{
    my %tests = (
        '::0'         => '0.0.0.0',
        '::2'         => '0.0.0.2',
        '::ffff'      => '0.0.255.255',
        '::ffff:ffff' => '255.255.255.255',
    );

    for my $raw ( sort keys %tests ) {
        my $ip = Net::Works::Address->new_from_string(
            string  => $raw,
            version => 6,
        );

        is(
            $ip->as_ipv4_string(),
            $tests{$raw},
            "$raw as IPv4 is $tests{$raw}"
        );
    }
}

{
    my $ip = Net::Works::Address->new_from_string(
        string  => '::1:ffff:ffff',
        version => 6,
    );

    like(
        exception { $ip->as_ipv4_string() },
        qr/\QCannot represent IP address larger than 2**32-1 as an IPv4 string/,
        'cannot represent an IPv6 address >= 2**32 as an IPv4 string'
    );
}

done_testing();
