#!/usr/bin/perl -w

use strict;
use utf8;
use vars qw($config);

use Socket;
use XML::LibXML;

use Data::Dumper;

my $xmlparser = new XML::LibXML;
#BEGIN { require '/etc/ipform/config.pl'; }
$config->{path_template} = '/var/cache/xsltzone';
$| = 1;

sub usage() {
	print STDERR <<EOF;
Usage: $0 ganymede-data.xml gen-pool.xml
EOF
	exit 1;
}

sub ip2num($) {
	my $ip = shift;
	return unpack('N', inet_aton($ip));
}

sub num2ip($) {
	my $num = shift;
	return inet_ntoa(pack('N', $num));
}

sub get_ip_elements_in_network($$) {
	my $network = shift;
	my $hosttree = shift;
	
	return +() unless exists $hosttree->{$network};
	return @{$hosttree->{$network}};
}

sub get_net_infos($$) {
	my $net_el = shift;
	my $hosttree = shift;
	
	my $network = $net_el->findvalue('Name');
	my $netbase = $net_el->findvalue('Startadresse/ip/@val');
	my $netmasklen = $net_el->findvalue('Maske');
	
	#die "generated DHCP-Pool unsupported in /$netmasklen network\n" if (grep {$netmasklen == $_} 31 .. 32);
	
	my @netentries;
	
	my $netbasenum = ip2num($netbase);
	my $netaddrcount = 2 ** (32 - $netmasklen);
	
	foreach my $el (get_ip_elements_in_network($network, $hosttree)) {
		my $ip = $el->findvalue('IP-Adresse/ip/@val');
		my $ipnum = ip2num($ip);
		die unless $netbasenum <= $ipnum and $ipnum <= $netbasenum+$netaddrcount-1;
		$netentries[$ipnum - $netbasenum]{host} = $el;
	}
	
	return {
		el => $net_el,
		name => $network,
		base => $netbase,
		basenum => $netbasenum,
		masklen => $netmasklen,
		addrcount => $netaddrcount,
		entries => [ @netentries ],
	};
}

sub dump_net_infos($) {
	my $infos = shift;
	
	printf "network %s (%s/%s):",
		$infos->{name},
		$infos->{base},
		$infos->{masklen};
	
	foreach my $offset (0 .. $infos->{addrcount}-1) {
		if (($offset & 63) == 0) {
			printf "\n %3d: ", $offset;
		}
		if (exists $infos->{entries}[$offset]{host}) {
			if ($infos->{entries}[$offset]{host}->findvalue('../../DHCP_Vergabe') eq 'static') {
				print 'd';
			} else {
				print 'h';
			}
		} elsif ($offset == 0 or $offset == $infos->{addrcount}-1) {
			print '_';
		} else {
			print '.';
		}
	}
	print "\n";
}

sub dns_normalize($) {
	my $label = lc shift;
	
	#$label =~ s/^bau([0-9]+)/b$1/ or $label =~ s/^.*?\///;
	$label =~ s/[^a-z0-9-]/-/g;
	$label =~ s/-+/-/g;
	$label =~ s/^-//;
	$label =~ s/-$//;
	
	return $label;
}

sub evaluate_pattern($$) {
	my $pattern = shift;
	my $replace = shift;
	my $cnt = 0;
	
	while ($pattern =~ m{(%([0]?)([a-z]))}g) {
		die 'recursive pattern?' if $cnt >= 10;
		die "unknown pattern char $3 (known: ".join('', keys %$replace).")" unless exists $replace->{$3};
		my $str = $replace->{$3};
		$str = sprintf "%04d", $str if $2 eq '0';
		substr $pattern, $-[1], $+[1]-$-[1], $str;
		$cnt++;
	}
	
	return $pattern;
}

sub offsets_to_ranges($) {
	my $arr = shift;
	my $ranges;
	
	foreach my $elem (@$arr) {
		if ($#$ranges >= 0 and $ranges->[$#$ranges][1] eq $elem - 1) {
			$ranges->[$#$ranges][1] = $elem;
		} else {
			push @$ranges, [$elem, $elem];
		}
	}
	
	return $ranges;
}

sub genpool($$$$$) {
	my $genpool = shift;
	my $netinfo = shift;
	my $ganytree = shift;
	my $resdata = shift;
	my $pool_cnt = shift;
	
	my $pool_system = $xmlparser->parse_string(<<EOF, '')->documentElement();
    <object type="System">
      <Interfaces>
      <object type="EmbeddedInterface">
        <IP-Adressen>
        </IP-Adressen>
        <IPv6-Adressen>
        </IPv6-Adressen>
      </object>
      </Interfaces>
    </object>
EOF
	$resdata->appendChild($pool_system);
	
	my $rangev4 = $genpool->addNewChild('', 'Range');
	my $rangev6 = $genpool->addNewChild('', 'Rangev6');
	
	my $ipv4ent = $pool_system->findnodes('Interfaces/object/IP-Adressen')->shift();
	my $ipv6ent = $pool_system->findnodes('Interfaces/object/IPv6-Adressen')->shift();
	my $v4count = $genpool->findvalue('IPv4_Poolsize_Limit');
	$v4count = -1 unless $v4count =~ m/^[0-9]+$/;
	my $v6count = $genpool->findvalue('IPv6_Poolsize_Limit');
	$v6count = 64 unless $v6count =~ m/^[0-9]+$/;
	
	my $netname = $netinfo->{name};
	my $dns_netname = dns_normalize $netname;
	my $poolid = $genpool->findvalue('@id');
	my $poolname = dns_normalize $genpool->findvalue('Name');
	my $domain = $genpool->findvalue('Domain/invid/@id');
	my $pattern = $genpool->findvalue('Hostname_Pattern');
	$pattern = '%n-%f-%0c' unless defined $pattern and $pattern ne '';
	
	my @v4offsets;
	foreach my $offset (reverse 1 .. $netinfo->{addrcount}-2) {
		last if ($v4count == 0);
		next if (($offset & 0xff) == 0);
		next if (($offset & 0xff) == 255);
		next if (exists $netinfo->{entries}[$offset]{host});
		$v4count--;
		$netinfo->{entries}[$offset]{host} = '--mark--';
		unshift @v4offsets, $offset;
		
		my $ipv4 = num2ip($netinfo->{basenum} + $offset);
		my $hostname = evaluate_pattern($pattern, {p => $poolname, n => $dns_netname, f => 'ipv4', c => $offset});
		$ipv4ent->appendChild($xmlparser->parse_string(<<EOF, '')->documentElement());
        <object type="EmbeddedIPAdresse">
          <Netzwerk><invid type="Netzwerk" id="$netname"/></Netzwerk>
          <IP-Adresse><ip val="$ipv4"/></IP-Adresse>
          <Domain><invid type="Domain" id="$domain"/></Domain>
          <Hostname>$hostname</Hostname>
        </object>
EOF
	}
	#printf "v4 offsets: %s\n",join(' ', @v4offsets);
	my $v4ranges = offsets_to_ranges(\@v4offsets);
	#printf "v4 ranges: %s\n",join(' ', map {join '-', @$_} @$v4ranges);
	foreach my $range (@$v4ranges) {
		my $start = num2ip($netinfo->{basenum} + $range->[0]);
		my $end   = num2ip($netinfo->{basenum} + $range->[1]);
		$rangev4->appendChild($xmlparser->parse_string(<<EOF, '')->documentElement());
        <object type="EmbeddedRange">
          <Startadresse>$start</Startadresse>
          <Endadresse>$end</Endadresse>
        </object>
EOF
	}
	
	foreach my $offset (0 .. ($v6count-1)) {
		my $ipv6 = sprintf "%06x", 0x1000*($pool_cnt + 1) + $offset;
		substr($ipv6, 2, 0, ':'); substr($ipv6, 0, 0, ':ff:fe');
		my $hostname = evaluate_pattern($pattern, {p => $poolname, n => $dns_netname, f => 'ipv6', c => $offset});
		$ipv6ent->appendChild($xmlparser->parse_string(<<EOF, '')->documentElement());
        <object type="EmbeddedIPv6Adresse">
          <Netzwerk><invid type="Netzwerk" id="$netname"/></Netzwerk>
          <HostID>$ipv6</HostID>
          <Domain><invid type="Domain" id="$domain"/></Domain>
          <Hostname>$hostname</Hostname>
        </object>
EOF
	}
	if ($v6count) {
		my $start = sprintf "%06x", 0x1000*($pool_cnt + 1);
		substr($start, 2, 0, ':'); substr($start, 0, 0, ':ff:fe');
		my $end = sprintf "%06x", 0x1000*($pool_cnt + 1) + $v6count-1;
		substr($end, 2, 0, ':'); substr($end, 0, 0, ':ff:fe');
		$rangev6->appendChild($xmlparser->parse_string(<<EOF, '')->documentElement());
        <object type="IPv6Range">
          <Startadresse>$start</Startadresse>
          <Endadresse>$end</Endadresse>
        </object>
EOF
	}
}

usage() if (@ARGV != 2);
my $INPUT  = $ARGV[0];
my $OUTPUT = $ARGV[1];

my $ganytree = $xmlparser->parse_file($INPUT);
my $resdata = $ganytree->findnodes('/ganymede/ganydata')->shift();
my @genpoolnetworks = $ganytree->findnodes(q{/ganymede/ganydata/object[@type = 'Netzwerk' and count(Gen-Pools/object) > 0]});

my $network2ip_el;

foreach my $el ($ganytree->findnodes(qq{//object[\@type='EmbeddedIPAdresse']})) {
	my $net = $el->findvalue('Netzwerk/invid/@id');
	push @{$network2ip_el->{$net}}, $el;
}

foreach my $net (@genpoolnetworks) {
	#printf "%s\n", $net->nodePath();
	my $netinfo = get_net_infos($net, $network2ip_el);
	#dump_net_infos($netinfo);
	#print Dumper($netinfo);
	my @genpools = $net->findnodes(q{Gen-Pools/object});
	my $pool_cnt = 0;
	foreach my $genpool (@genpools) {
		#printf "%s\n", $genpool->nodePath();
		genpool($genpool, $netinfo, $ganytree, $resdata, $pool_cnt);
		$pool_cnt++;
	}
}

$ganytree->toFile($OUTPUT);

exit 0;
1;
