package Net::IPAM::Block::Private;

use 5.10.0;
use strict;
use warnings;
use utf8;

use Carp          ();
use Scalar::Util  ();
use Net::IPAM::IP ();

=head1 NAME

Net::IPAM::Block::Private - Just private functions for Net::IPAM::Block

=head1 DESCRIPTION

Don't pollute the B<< namespace >> of Net::IPAM::Block

=cut

# use some pre-calculated tables for speed, see end of package
my ( %valid_masks4, @table_mask_n, @nlz8 );

# 1.2.3.4/255.0.0.0  => {base: 1.0.0.0, last: 1.255.255.255, mask: 255.0.0.0}
# 1.2.3.4/24         => {base: 1.2.3.0, last: 1.2.3.255,     mask: 255.255.255.0}
# fe80::1/124 =>        {base: fe80::,  last: fe80::f,       mask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0}
sub _fromMask {
  my ( $self, $addr, $idx ) = @_;

  # split input in prefix and suffix:
  #   10.0.0.0/255.0.0.0 => 10.0.0.0, 255.0.0.0
  #   10.0.0.0/8         => 10.0.0.0, 8
  #   fe80::1/124        => fe80::1,  124
  #
  my $prefix_str = substr( $addr, 0, $idx );
  my $suffix_str = substr( $addr, $idx + 1 );

  my $prefix = Net::IPAM::IP->new($prefix_str) // return;

  my $mask_n;
  if ( index( $suffix_str, '.' ) >= 0 ) {
    my $ip = Net::IPAM::IP->new($suffix_str) // return;
    $mask_n = $ip->bytes;
    return unless exists $valid_masks4{$mask_n};
  }
  else {
    my $bits = 32;
    $bits = 128 if $prefix->version == 6;

    return unless $suffix_str =~ m/^\d+$/;    # pos integer
    return if $suffix_str > $bits;

    $mask_n = _make_mask_n( $suffix_str, $bits );
  }

  $self->{base} = Net::IPAM::IP->new_from_bytes( _make_base_n( $prefix->bytes, $mask_n ) );
  $self->{last} = Net::IPAM::IP->new_from_bytes( _make_last_n( $prefix->bytes, $mask_n ) );

  return $self;
}

# 1.2.3.4-1.2.3.17  => {base: 1.2.3.4, last: 1.2.3.17, mask: undef}
# fe80::-fe80::ffff => {base: fe80::, last: fe80::ffff, mask: ffff:ffff:ffff:ffff:ffff:ffff:ffff::}
sub _fromRange {
  my ( $self, $addr, $idx ) = @_;

  # split range in base and last 10.0.0.1-10.0.0.3 => 10.0.0.1, 10.0.0.3
  my $base_str = substr( $addr, 0, $idx );
  my $last_str = substr( $addr, $idx + 1 );

  my $base_ip = Net::IPAM::IP->new($base_str) // return;
  my $last_ip = Net::IPAM::IP->new($last_str) // return;

  # version base != version last
  my $version = $base_ip->version;
  return if $version != $last_ip->version;

  # base > last?
  return if $base_ip->cmp($last_ip) > 0;

  $self->{base} = $base_ip;
  $self->{last} = $last_ip;

  return $self;
}

# 1.2.3.4 => {base: 1.2.3.4, last: 1.2.3.4, mask: 255.255.255.255}
# fe80::1 => {base: fe80::1, last: fe80::1, mask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff}
sub _fromAddr {
  my ( $self, $addr ) = @_;

  my $base = Net::IPAM::IP->new($addr) // return;

  $self->{base} = $base;
  $self->{last} = $base;

  return $self;
}

# 1.2.3.4 => {base: 1.2.3.4, last: 1.2.3.4, mask: 255.255.255.255}
# fe80::1 => {base: fe80::1, last: fe80::1, mask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff}
sub _fromIP {
  my ( $self, $ip ) = @_;

  return unless Scalar::Util::blessed($ip) && $ip->isa('Net::IPAM::IP');

  $self->{base} = $ip;
  $self->{last} = $ip;

  return $self;
}

# my $b = _clone($self)
sub _clone {
  my $self  = shift;
  my $clone = bless {}, ref $self;

  $clone->{base} = Net::IPAM::IP->new_from_bytes( $self->{base}->bytes );
  $clone->{last} = Net::IPAM::IP->new_from_bytes( $self->{last}->bytes );

  return $clone;
}

# a.base <= b && a.last >= b
sub _contains_ip {
  return $_[0]->{base}->cmp( $_[1] ) <= 0 && $_[0]->{last}->cmp( $_[1] ) >= 0;
}

sub _contains_block {

  # a == b, return false
  return 0 if $_[0]->cmp( $_[1] ) == 0;

  # a.base <= b.base && a.last >= b.last
  return $_[0]->{base}->cmp( $_[1]->{base} ) <= 0 && $_[0]->{last}->cmp( $_[1]->{last} ) >= 0;
}

# count leading zeros
sub _leading_zeros {
  my $n     = shift or die 'missing arg,';
  my $zeros = 0;

  # step byte-wise through $n from left to right
  for my $i ( 0 .. length($n) - 1 ) {
    my $byte = vec( $n, $i, 8 );

    # count bytes
    if ( $byte == 0x00 ) {
      $zeros += 8;
      next;
    }

    # count bits, use a pre-calculated table
    $zeros += $nlz8[$byte];
    last;
  }

  return $zeros;
}

# number of common leading bits
sub _common_prefix {
  my ( $base_n, $last_n ) = @_;
  return _leading_zeros( $base_n ^ $last_n );
}

# returns the common prefix bits and ok. ok is true when the range is a CIDR.
sub _common_ok {
  my ( $base_n, $last_n, $bits ) = @_;

  my $pfx    = _common_prefix( $base_n, $last_n );
  my $mask_n = _make_mask_n( $pfx, $bits );

  # apply mask to base and last as AND and OR
  # and check for all zeros and all ones => it's a CIDR
  my $is_all_zeros = ( $base_n ^ ( $base_n & $mask_n ) ) eq _make_mask_n( 0, $bits );
  my $is_all_ones  = ( $last_n | $mask_n ) eq _make_mask_n( 128, $bits );

  return $pfx, $is_all_zeros && $is_all_ones;
}

# switch to binary representation
# push start block on stack
#
# loop while stack not dry
# check if is_cidr
# split the range in the middle
# put both halves on stack (reverse order)
# next
#
# return cidrs
#
sub _to_cidrs_iter {
  my $self = shift;
  my @stack;
  my @result;

  my $bits = 32;
  $bits = 128 if $self->version == 6;

  # make binary representation, put it as start on the stack
  push @stack, [ $self->{base}->bytes, $self->{last}->bytes ];

  while ( scalar @stack > 0 ) {
    my $block_n = pop @stack;
    my $base_n  = $block_n->[0];
    my $last_n  = $block_n->[1];

    my ( $pfx, $is_cidr ) = _common_ok( $base_n, $last_n, $bits );

    # is this block already a CIDR?
    if ($is_cidr) {

      # add block ro result
      my $block = bless {}, ref $self;
      $block->{base} = Net::IPAM::IP->new_from_bytes($base_n);
      $block->{last} = Net::IPAM::IP->new_from_bytes($last_n);

      push @result, $block;
      next;
    }

    # split block in two halves, add 1 to common prefix
    my $mask_n = _make_mask_n( $pfx + 1, $bits );

    # split range with new mask
    my $mid_last_n = _make_last_n( $base_n, $mask_n );
    my $mid_base_n = _make_base_n( $last_n, $mask_n );

    # make two new blocks
    my $block1_n = [ $base_n,     $mid_last_n ];
    my $block2_n = [ $mid_base_n, $last_n ];

    # push both blocks to stack, reverse the order
    push @stack, $block2_n, $block1_n;
  }

  return \@result;
}

# returns true if base and last forms a CIDR range
sub _is_cidr {
  my $self = shift;

  my $bits = 32;
  $bits = 128 if $self->version == 6;

  my $base_n = $self->{base}->bytes;
  my $last_n = $self->{last}->bytes;

  my ( $pfx, $is_cidr ) = _common_ok( $base_n, $last_n, $bits );
  return $is_cidr;
}

# base = addr & netMask
sub _make_base_n {
  my ( $addr_n, $mask_n ) = @_;
  return $addr_n & $mask_n;
}

# last = addr | hostMask
sub _make_last_n {
  my ( $addr_n, $mask_n ) = @_;
  return $addr_n | ~$mask_n;
}

# cut off the bits after 32, used for IPv4
sub _strip_to_32_bits { return $_[0] & "\xff\xff\xff\xff" }

# make CIDR mask from bits:
# (24, 32)  => 0xffff_ff00
# (56, 128) => 0xffff_ffff_ffff_ff00_0000_0000_0000_0000
#
sub _make_mask_n {
  my ( $n, $bits ) = @_;
  my $mask_n = $table_mask_n[$n];
  #
  # cut to first 32 bits
  if ( $bits == 32 ) {
    return _strip_to_32_bits($mask_n);
  }
  return $mask_n;
}

sub _get_mask_n {
  my ( $base_ip, $last_ip ) = @_;

  my $pfx = _common_prefix( $base_ip->bytes, $last_ip->bytes );

  my $bits = 32;
  $bits = 128 if $base_ip->version == 6;

  return _make_mask_n( $pfx, $bits );
}

#############################
#     pre-calc tables
#############################

#<<< skip marker for perltidy

# pre-calced masks for 0.0.0.0 - 255.255.255.255
# only IPv4 addresses representable as a sequence of 1 bits followed by 0 bits
# are allowed as masks as input

%valid_masks4 = (
  "\x00\x00\x00\x00" => 0,  "\x80\x00\x00\x00" => 1,  "\xc0\x00\x00\x00" => 2,
  "\xe0\x00\x00\x00" => 3,  "\xf0\x00\x00\x00" => 4,  "\xf8\x00\x00\x00" => 5,
  "\xfc\x00\x00\x00" => 6,  "\xfe\x00\x00\x00" => 7,  "\xff\x00\x00\x00" => 8,
  "\xff\x80\x00\x00" => 9,  "\xff\xc0\x00\x00" => 10, "\xff\xe0\x00\x00" => 11,
  "\xff\xf0\x00\x00" => 12, "\xff\xf8\x00\x00" => 13, "\xff\xfc\x00\x00" => 14,
  "\xff\xfe\x00\x00" => 15, "\xff\xff\x00\x00" => 16, "\xff\xff\x80\x00" => 17,
  "\xff\xff\xc0\x00" => 18, "\xff\xff\xe0\x00" => 19, "\xff\xff\xf0\x00" => 20,
  "\xff\xff\xf8\x00" => 21, "\xff\xff\xfc\x00" => 22, "\xff\xff\xfe\x00" => 23,
  "\xff\xff\xff\x00" => 24, "\xff\xff\xff\x80" => 25, "\xff\xff\xff\xc0" => 26,
  "\xff\xff\xff\xe0" => 27, "\xff\xff\xff\xf0" => 28, "\xff\xff\xff\xf8" => 29,
  "\xff\xff\xff\xfc" => 30, "\xff\xff\xff\xfe" => 31, "\xff\xff\xff\xff" => 32,
);

# pre-calced CIDR masks for /0 .. /128,
# e.g.
# $table_mask_n[3]   == "\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# $table_mask_n[64]  == "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00"
# $table_mask_n[128] == "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"

@table_mask_n = (
  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe",
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff",
);

# pre-calced 'number of leading zeros' for byte values 0x00 - 0xff
# e.g.
# $nlz[0]  ==  8 # 0b0000_0000
# $nlz[1]  ==  7 # 0b0000_0001
# $nlz[64] ==  1 # 0b0100_0000
# $nlz[128] == 0 # 0b1000_0000

@nlz8 = (
	8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, # 00..1f
	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # 20..3f
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 40..5f
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 60..7f
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 80..9f
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # a0..bf
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # c0..df
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # e0..ff
);

####################################
#>>> end of skip marker for perltidy
####################################

=head1 AUTHOR

Karl Gaissmaier, C<< <karl.gaissmaier(at)uni-ulm.de> >>

=head1 LICENSE AND COPYRIGHT

This software is copyright (c) 2020-2022 by Karl Gaissmaier.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

1;
