package HaCi::Plugins::CrossRootNetTrace;

# Cross root tracing plugin for HaCi
#
# Based on code written by 
#  Andreas Lewitzki <andreas.lewitzki@bredband.com>
#  Pär Karlsson     <par.j.karlsson@telenor.com>

use strict;
use warnings;
use base qw/HaCi::Plugin/;

use HaCi::Mathematics qw(dec2net net2dec netv6Dec2net ipv62dec);
use HaCi::Utils qw(getNetworkParentFromDB getNetworkChilds getMaintInfosFromNet getTemplateData rootID2Name);
use HaCi::GUI::gettext qw(_gettext);

use Carp;
use Encode;
use English qw(-no_match_vars);
use Net::DNS;
use Data::Dumper;

our $conf;
*conf = \$HaCi::Conf::conf;
our $session;
*session = \$HaCi::HaCi::session;
our $INFO = {
	name				=> 'CrossRootNetTrace',
	version			=> '3.0',
	onDemand		=> 1,
	description	=> _gettext('This plugin tracks overlaying networks across all roots'),
	menuOnDemand	=> [
		{
			NAME			=> 'dnsResolver',
			DESCR			=> _gettext('Special DNS Resolver for this network'),
			TYPE			=> 'textbox',
			SIZE			=> 25,
			MAXLENGTH	=> 128,
			VALUE			=> '8.8.8.8,8.8.4.4'
		},
	],
	globMenuOnDemand	=> [
		{
			NAME			=> 'dnsResolver',
			DESCR			=> _gettext('DNS Resolver'),
			TYPE			=> 'textbox',
			SIZE			=> 25,
			MAXLENGTH	=> 128,
			VALUE			=> '8.8.8.8,8.8.4.4'
		},
	]
};

my $RE_IPV4_NETWORK	= qr{ (?:\d+[.]){3}\d+\/\d+ }msx;
my $RE_IPV6_NETWORK	= qr{ (?:\w+[:])+\w+\/\d+ }msx;
my $RE_NETWORK			= qr{ \A (?: $RE_IPV4_NETWORK|$RE_IPV6_NETWORK) \z }imsx;

sub makeKeyVal {
    my $self	= shift;
		my $key		= shift;
		my $val		= shift,
		my $url		= shift;
    $key			= encode( 'latin1', decode( 'utf8', $key ) );

    return {
        elements => [
            {
                colspan => 2,
                target  => 'key',
                type    => 'label',
                value   => $url ? "<a href=\"$url\">" . _gettext($key) . '</a>' : _gettext($key)
            },
            {
                target => 'value',
                type   => 'label',
                color  => '#0000ff',
                value  => $url ? "<a href=\"$url\">" . $val . '</a>' : _gettext($val)
            },
        ]
    };
}

sub ipSort {
    if ( $a =~ $RE_NETWORK && $b =~ $RE_NETWORK ) {
        my ( $a_ip, $a_cidr ) = split m{/}msx, $a;
        my ( $b_ip, $b_cidr ) = split m{/}msx, $b;
        my ( $a_addr, $b_addr );
        if ( $a =~ $RE_IPV4_NETWORK && $b =~ $RE_IPV4_NETWORK ) {
            $a_addr = net2dec($a);
            $b_addr = net2dec($b);
        }
        elsif ( $a =~ $RE_IPV6_NETWORK && $b =~ $RE_IPV6_NETWORK ) {
            $a_addr = ipv62dec($a_ip);
            $b_addr = ipv62dec($b_ip);
        }
        else {
            carp "Cannot compare mixed IPv4 and IPv6 addresses: $a <=> $b";
            return -1;
        }
        if ( $a_addr > $b_addr ) {
            return 1;
        }
        if ( $a_addr == $b_addr ) {
            if ( $a_cidr > $b_cidr ) {
                return 1;
            }
            else {
                return -1;
            }
        }
    }
    return -1;
}

sub run_onDemand {
	my $self					= shift;
	my $networkRef		= shift;
	my $config				= shift;

	my $netID					= $networkRef->{ID};
	my $networkDec		= $networkRef->{network};
	my $ipv6					= $networkRef->{ipv6};
	my $network				= ($ipv6) ? netv6Dec2net($networkDec) : dec2net($networkDec);
	$self->{networkRef}	= $networkRef;
	$self->{inetnum}	= $network;
	$self->{username}	= $session->param('username');

	if ( $network !~ $RE_NETWORK ) {
		$self->warnl("'$network' does not look like a network!");
		return;
	}

	my $supernets	= &getNetworkParentFromDB(-1, $networkDec, $ipv6, 0);
	my @subnets		= &getNetworkChilds($netID, 0, 1, 1);

	if (ref($supernets) eq 'ARRAY' && $#{$supernets} > -1) {
		foreach (@{$supernets}) {
			my $sNetRef		= $_;
			my $sNetID		= $sNetRef->{ID};
			my $sNetDec		= $sNetRef->{network};
			my $sNetInfo	= &getMaintInfosFromNet($sNetID);
			my $sNetIPv6	= $sNetInfo->{ipv6};
			my $sNet			= ($sNetIPv6) ? netv6Dec2net($sNetDec) : dec2net($sNetDec);

			if (0) { # do we need template data for output?
				my $templateData			= &getTemplateData($sNetID, $sNetInfo->{tmplID}, 1);
				$sNetInfo->{tmplData}	= $templateData;
			}

			push @{$self->{data}->{up}->{$sNet}}, $sNetInfo;
		}
	}

	if ($#subnets > -1) {
		foreach (@subnets) {
			my $sNetRef						= $_;
			my $sNetID						= $sNetRef->{ID};
			my $sNetDec						= $sNetRef->{network};
			my $sNetInfo					= &getMaintInfosFromNet($sNetID);
			my $sNetIPv6					= $sNetInfo->{ipv6};
			my $sNet							= ($sNetIPv6) ? netv6Dec2net($sNetDec) : dec2net($sNetDec);
			my $cidr							= $sNetDec % 256;

			if (0) { # do we need template data for output?
				my $templateData			= &getTemplateData($sNetID, $sNetInfo->{tmplID}, 1);
				$sNetInfo->{tmplData}	= $templateData;
			}

			push @{$self->{data}->{cidr}->{$cidr}->{$sNet}}, $sNetInfo;
			push @{$self->{data}->{down}->{$sNet}}, $sNetInfo;
		}
	}

	if (($network =~ /\/32$/ || $network =~ /\/128$/) && exists $config->{dnsResolver} && $config->{dnsResolver}) {
		my $resolver	= Net::DNS::Resolver->new(nameservers => [split(/\s*,\s*/, $config->{dnsResolver})]);
		my $ip				= (split(/\//, $network, 2))[0];
		my $reply			= $resolver->query($ip, 'PTR');
		my $hostname	= '';
		if ($reply) {
			foreach ($reply->answer()) {
				my $rr	= $_;
				next unless $rr->type() eq 'PTR';
				$hostname	= $rr->ptrdname();
			}
		}
		if ($hostname) {
			push @{$self->{data}->{down}->{$network}}, {
				rootID			=> -2,
				description	=> $hostname
			};
		}
	}

	return 1;
}

sub show {
    my $self = shift;
    my $show =
      { HEADER =>
          sprintf( _gettext("Cross Root Net Trace for %s"), $self->{inetnum} ),
      };
    my $ipnet  = $self->{inetnum};
    my $result = $self->{data};
    my @sorted = sort { ipSort($_) } keys %{ $result->{up} };
    for my $net (@sorted) {
        my $ndata		= $self->{data}->{up}->{$net};
        my ( $ipaddress, $cidr ) = split m{/}msx, $net;
        push @{ $show->{BODY} },
          {
            elements => [
                {
                    align   => 'center',
                    colspan => 3,
                    target  => 'value',
                    type    => 'vline',
                },
            ]
          };
        push @{ $show->{BODY} },
          {
            elements => [
                {
                    bold      => 1,
                    underline => 1,
                    align     => 'center',
                    colspan   => 3,
                    color     => ( $net eq $ipnet ? 'blue' : 'black' ),
                    target    => 'value',
                    type      => 'label',
                    value			=> _gettext( ( $net eq $ipnet ? ">> $net <<" : $net ) )
                }
            ]
          };
        for my $data ( sort { $a->{rootID} cmp $b->{rootID} } @{$ndata} ) {
            my $rootID		= $data->{rootID};
						my $rootName	= ($rootID == -2) ? 'DNS' : &rootID2Name($rootID);
            push @{ $show->{BODY} },
              $self->makeKeyVal( $rootName, $data->{description},
                "?jumpToButton=1&rootIDJump=$rootID&jumpTo=$ipaddress" );
        }
    }

    @sorted = sort { ipSort($_) } keys %{ $result->{down} };
    for my $cidr ( sort { $a <=> $b } keys %{ $result->{cidr} } ) {
        if ( scalar keys %{ $result->{cidr}->{$cidr} } == 1 ) {
            my $net   = [ keys %{ $result->{cidr}->{$cidr} } ]->[0];
            my $ndata = $result->{down}->{$net};
            my ( $ipaddress, $cidr ) = split m{/}msx, $net;
            push @{ $show->{BODY} },
              {
                elements => [
                    {
                        bold      => 1,
                        underline => 1,
                        align     => 'center',
                        colspan   => 3,
                        target    => 'value',
                        type      => 'label',
												color     => ( $net eq $ipnet ? 'blue' : 'black' ),
												value			=> _gettext( ( $net eq $ipnet ? ">> $net <<" : $net ) )
                    }
                ]
              };
            for my $data ( sort { $a->{rootID} cmp $b->{rootID} } @{$ndata} ) {
                my $rootID		= $data->{rootID};
								my $rootName	= ($rootID == -2) ? 'DNS' : &rootID2Name($rootID);
                push @{ $show->{BODY} },
                  $self->makeKeyVal( $rootName, $data->{description},
                    "?jumpToButton=1&rootIDJump=$rootID&jumpTo=$ipaddress" );
            }
        }
        else {
            my $netCount = {};
            for my $net ( keys %{ $result->{cidr}->{$cidr} } ) {
                for my $d ( @{ $result->{cidr}->{$cidr}->{$net} } ) {
                    my $rootID	= $d->{rootID};
                    my $root 		= &rootID2Name($rootID);
                    $netCount->{$root}++;
                }
            }
            push @{ $show->{BODY} },
              {
                elements => [
                    {
                        bold      => 1,
                        underline => 1,
                        align     => 'center',
                        colspan   => 3,
                        target    => 'value',
                        type      => 'label',
                        value     => _gettext("/$cidr")
                    }
                ]
              };
            for my $root ( keys %{$netCount} ) {
                push @{ $show->{BODY} },
                  $self->makeKeyVal( $root, $netCount->{$root} );
            }
        }
    }

    return $show;
}

1;
