#!/usr/bin/perl -w
# $Id$
$DevMatrix = '../DeviceMatrix.txt';
$DevHTML   = 'DeviceMatrix.html';
$DevPNG    = 'DeviceMatrix.png';
$Attributes= {};
# Parse Data File
$matrix = parse_data($DevMatrix);
# Graph it for fun
eval "use GraphViz::Data::Structure;";
if ($@) {
    print "GraphViz::Data::Structure not installed. $@\n";
} else {
    my %graph = ();
    foreach my $vendor (sort sort_nocase keys %$matrix){
        $graph{$vendor} = {};
        foreach my $family  (sort sort_nocase keys %{$matrix->{$vendor}->{families}} ){
            my @models;
            foreach my $mod (keys %{$matrix->{$vendor}->{families}->{$family}->{models}}){
                push(@models,split(/\s*,\s*/,$mod));
            }
            if (scalar @models){
                $graph{$vendor}->{$family}=\@models;
            } else {
                $graph{$vendor}->{$family}=[];
            }
            
        }
    }
    my $now = scalar localtime;
    my $gvds = GraphViz::Data::Structure->new(\%graph,Orientation=>'vertical',
        Colors=> 'Deep',
        graph => {label=>"SNMP::Info and Netdisco Supported Devices \n $now",'fontpath'=>'/usr/local/netdisco','fontname'=>'lucon',concentrate=>'true','overlap'=>'false',spline=>'true',bgcolor=>'wheat'},
        node  => {fontname=>'lucon'},
        );
    $gvds->graph()->as_png($DevPNG);
}
print "Creating $DevHTML\n";
open (HTML, "> $DevHTML") or die "Can't open $DevHTML. $!\n";
$old_fh = select(HTML);
&html_head;
print_vendors($matrix);
foreach my $vendor (sort sort_nocase keys %$matrix){
    print "$vendor\n";
    print "
\n";
    my $vendor_defaults = $matrix->{$vendor}->{defaults};
    print_notes($vendor_defaults,1);
    my $families = $matrix->{$vendor}->{families};
    foreach my $family (sort sort_nocase keys %$families ) {
        print "- $family Family\n";
        my $family_defaults = $families->{$family}->{defaults};
        print_notes($family_defaults,2);
        my $models = $families->{$family}->{models};
        foreach my $model (sort sort_nocase keys %$models ){
            my $model_defaults = $models->{$model}->{defaults};
            print "
- $model\n";
            print "\n";
            print_notes($model_defaults,3);
            print "- \n";
            print_headers();
            print "- \n";
            foreach my $a (sort sort_nocase keys %$Attributes) {
                my $val;
                next if $a eq 'note';
                $val = ['-'];
                $class = 'none';
                if (defined $model_defaults->{$a}) {
                    $val = $model_defaults->{$a};
                    $class = 'model';
                } elsif (defined $family_defaults->{$a}){
                    $val = $family_defaults->{$a};
                    $class = 'family';
                } elsif (defined $vendor_defaults->{$a}){
                    $val = $vendor_defaults->{$a};
                    $class = 'vendor';
                } 
                print "  | ",join(" \n";
            }
            print "\n",@$val),"
 |  
 \n";
            print "
 \n";
        }
    }
    print "
\n";
}
&html_tail;
select ($old_fh);
close (HTML) or die "Can't write $DevHTML. $!\n";
# Data Structures
# Matrix =
#   ( vendor => { families  => { family => family_hash },
#                  defaults => { cmd    => [values]    },
#               }
#   )
# Family Hash
#   ( models   => { model => model_hash },
#     defaults => { cmd   => [values]   }
#   )
# Model Hash
#   ( defaults => { cmd => [values] } )
sub parse_data {
    my $file = shift;
    my %ignore = map { $_ => 1  }  @_;
    my $Matrix;
    my @Lines;
    open (DM, "< $file") or die "Can't open $file. $!\n";
    {
        @Lines = ;
    }
    close (DM);
    my ($device,$family,$vendor,$class);
    foreach my $line (@Lines){
        chomp($line);
        # Comments
        $line =~ s/#.*//;
        # Blank Lines
        next if $line =~ /^\s*$/;
        # Trim whitespace
        $line =~ s/^\s+//;
        $line =~ s/\s+$//;
        my ($cmd,$value);
        if ($line =~ /^([a-z-_]+)\s*:\s*(.*)$/) {
            $cmd = $1;  $value = $2; 
        } else {
            print "What do i do with this line : $line \n";
            next;
        }
        if (exists $ignore{$cmd}){
            print "Ignoring $cmd\n";
        }
        # Set Class {vendor,family,device}
        if ($cmd eq 'device-vendor'){
            $vendor = $value;
            $family = $model = undef;
            $Matrix->{$vendor} = {} unless defined $Matrix->{$vendor};
            $class = $Matrix->{$vendor};
            $class->{defaults}->{type}='vendor';
            next;
        }
        if ($cmd eq 'device-family'){
            $family = $value;
            $model = undef;
            print "$family has no vendor.\n" unless defined $vendor;
            $Matrix->{$vendor}->{families}->{$family} = {} 
                unless defined $Matrix->{$vendor}->{families}->{$family};
            $class = $Matrix->{$vendor}->{families}->{$family};
            $class->{defaults}->{type}='family';
            next;
        }   
        if ($cmd eq 'device') {
            $model = $value;
            print "$model has no family.\n" unless defined $family;
            print "$model has no vendor.\n" unless defined $vendor;
            $Matrix->{$vendor}->{families}->{$family}->{models}->{$model} = {} 
                unless defined $Matrix->{$vendor}->{families}->{$family}->{models}->{$model};
            $class = $Matrix->{$vendor}->{families}->{$family}->{models}->{$model};
            $class->{defaults}->{type}='device';
            next;
        }
        # Store attribute
        push (@{$class->{defaults}->{$cmd}} , $value);
        $Attributes->{$cmd}++;
    }
    return $Matrix;
}
sub sort_nocase {
    return lc($a) cmp lc($b);
}
sub print_notes {
    my $defaults = shift;
    my $level    = shift;
    my $notes    = $defaults->{note} || [];
    foreach my $note (@$notes){
        if ($note =~ s/^!//){
            $note = '' . $note . '';
        }
    }
    if (scalar @$notes){
        print "\n";
        my $print_note = join("\n",@$notes);
        print "\n";
    }
}
sub print_vendors {
    my $matrix=shift;
    print "Device Vendors
\n";
    foreach my $vendor (sort sort_nocase keys %$matrix){
        print "[$vendor]\n";
    }
    print "
\n";
}
sub html_head {
    print <<"end_head";
SNMP::Info - Device Compatibility Matrix
SNMP::Info - Device Compatibility Matrix
end_head
}
sub html_tail {
    print <<'end_tail';
Color Key
[Model Attribute]
[Family Attribute]
[Vendor Attribute]
Attribute Key
A value of - signifies the information is not specified and can not
be assumed working.
    | Arpnip | Ability to collect ARP tables for MAC to IP translation. | 
    | Class | SNMP::Info Class the the device currently uses.  Devices using more generic
        interfaces like Layer2 or Layer3 may eventually get their
        own subclass. | 
    | Duplex | Ability to cull duplex settings from device. 
 
            no - Can't recover current or admin setting.
            link - Can get current setting only.
            both - Can get admin and link setting.
            write - Can get admin and link setting and perform sets.
         | 
    | Macsuck | Ability to get CAM tables for MAC to switch port mapping. 
 
            no - Have not found an SNMP method to get data yet.
            yes - Can get through normal SWITCH-MIB method.
            vlan - Have to re-connect to each VLAN and then fetch with normal
        method.
         | 
    | Modules | Ability to gather hardware module information. | 
    | Portmac | Whether the device will list the MAC address of the switch port on each
        switch port when doing a Macsuck. | 
    | Topo | Ability to get Layer 2 Topology Information from device if the
        protocol is enabled.  SNMP::Info supports querying Link Layer
        Discovery Protocol (LLDP), Cisco Discovery Protocol (CDP),
        SynOptics/Bay/Nortel/Avaya Network Management Protocol (SONMP),
        Foundry/Brocade Discovery Protocol (FDP), Extreme Discovery
        Protocol (EDP), and Alcatel Mapping Adjacency Protocol (AMAP). | 
    | Ver | SNMP Protocol Version the device has to use. | 
    | Vlan | Ability to get VLAN port assignments. 
 
            no - Have not found an SNMP method to get data yet.
            yes - Can read information.
            write - Can read and write (set).
         | 
end_tail
    
}
sub print_headers {
    print "\n";
    foreach my $a (sort sort_nocase keys %$Attributes) {
        next if $a eq 'note';
        print "  | $a\n";
    }
    print " | 
\n";
}