package TO;
#<copyright>
# ----------------------------------------------------------
# Sun Proprietary/Confidential Code
# Copyright 2001, Sun Microsystems, Inc. All rights reserved.
# ----------------------------------------------------------
#</copyright>

use Util;
use System;
use PDM::ConfigFile;
use strict;
use FC;
use Modules;
BEGIN {Modules->load("TO") }

#use Agent::SWITCH;
use TO::Applet;
use vars qw($NEWTOPO $ERR);


#use Catalog;


##################################################################
# storage ports are 0/1, switch ports are 1,2,3.. , host ports are 0,1,2...

# TO->clearTopo();
# $to->get_NewTopo();                    # print topo data structure.
# $to = TO->readExistingTopo();
# $to = TO->readTopo();                 # will discover if ned be.
# print $to->toString;                  # print the whole topology
# $to->saveTopo();                      # save a topology in DATA/topo/<hostname>

# $list = $to->hostList();              # return a list of nodes.
# $list = $to->switchList();
# $list = $to->storageList();
# $list = $to->hubList();
# $list = $to->zoneList();
# link  = $to->linkName(port[3]);

# $list->[0]->name();                   # get the name of the node ('a5k:12321312');
# TO->nodeName($node);                  # get the name of a node (static);
# $node->name();                        # get the name of a node (dynamic)
# $node->type();                        # get the type of a node.

# $node = $to->nodeByName('a5k:123123'); # get the node for a specified key
# $node->toString();                    # print one node of the topology
# $portList  = $node->portInfo();       # info about each port
# $volList   = $node->volInfo();        # info about each volume (T3)
# $linkList  = $node->port();           # where does each port goes ('a5k:12312:0');
# $diskList  = $node->diskInfo();       # info about each disks
#
##################################################################


# hosts hbas start at 0
# Hosts->[0]{info}        = {hostname => ... }
# Hosts->[0]{port}[0]      = linkkey   Ex: s:0:1
# Hosts->[0]{portInfo}[0] = {physicalpath => ...}

# switch ports start at 1, 0 is empty;
# Switches->[0]{info}     = {name => , ip => ...}
# Switches->[0]{port}[1]  = linkKey
# Switches->[0]{portInfo}[1]  = { type => ,...}

# hosts port start at 0
# Storages->[0]{info}      = { type => "A/T", info => 'A|T|...'}
# Storages->[0]{port}[0]      =  linkKey   
# Storages->[0]{portInfo}[0] =  { port info ... }
# Storages->[0]{diskInfo}{f1,f2,f3,r1,r2,r3...} =  { disk info ... }
#
# Names   ->{'a:DPL1'} = 1;    means that a5000 with name DPL1 is at Storages[1]
# 

#
#  Hosts    0
#  Switches 1
#  Storages 2
#  Hubs     3
#  Names    4
#  SANZones 5
#  SystemInfo 6

sub push {
  my($class, $master, $force) = @_;

  return if (!$master);
  return if (!$NEWTOPO & !$force);

  Debug->print2("  Topology: push new topo to master");

  my($renv) = System->get_renv();
  my($F) = System->get_home() . "/DATA/topo/" . $renv->{hostname};


  if (open(O, $F)) {
 
    my(@a)  = <O>; close(O);
    my($err, $ans) = Util::Http->saveFile($master, "topo/$renv->{hostname}", "@a");

    if ($ans !~ /OK/) {
      Debug->errNoRepeat(HTTP_TO => undef, 1, $err);
    }
  } 

}

sub errors {
  my($class, $host) = @_;
  my $renv = System->get_renv();
  $host = $renv->{hostname} if (!$host);
  my($l, $err, $stderr);
  my $T = System->get_home() . "/DATA/topo/$host";
  if (open(O1, $T)) {
    while ($l = <O1>) {
      if ($l =~ /sys_error' => '(.*)',/) {
         $err = $1;
      } elsif ($l =~ /sys_stderr' => '(.*)',/) {
         $stderr = $1;
      }
    }
    close(O1);
  }
  return ($err, $stderr);
}

sub isStorage {
  my($class, $type) = @_;
  if (index(",host,ve,switch,", ",$type,") >= 0) {
     return 0;
  } 
  return 1;
}

#  $size = $to->size('hst'); # host + switch + storage

sub size {
  my($to, $s) = @_;
  my($list, $tot);

  if (index($s, "h") >= 0) {
     $list = $to->hostList();  
     $tot += $#$list;
  }

  if (index($s, "s") >= 0) {
     $list = $to->switchList();    
     $tot += $#$list;
  }
  if (index($s, "t") >= 0) {
     $list = $to->storageList();
     $tot += $#$list;
  }
  return $tot;
}


sub hostname {
  my($to) = @_;
  my $hosts = $to->[0];
  if ($hosts) {
     my @names = keys %$hosts;
     my $h1 = $hosts->{$names[0]};
     return substr($h1->{info}{name},5);
  }
  return undef;
}


#
# return one or a list of host if it's a merge-topo

sub host2list {
  my($class, $host) = @_;

  if (substr($host,0,6) eq "MERGE-"){
    my($file1) = System->get_home() . "/DATA/topo_merge";
    open(O, $file1);
    my(@hlist, $l);
    while ($l = <O>) {
       chop($l);
       my(@c) = split(/\|/, $l);
       if ($c[0] eq substr($host,6)) {
          @hlist = split(/\t/, $c[2]);
          last;
       }
    }
    close(O);
    return @hlist;
  } else {
    return ($host);
  }
}

#
#  reads the current topology
#  if topo/hostname is missing, get a new topology and save it
#
#  host may be 'nscc-test or 'MERGE-san

sub readExistingTopo {
  my($class, $host) = @_;
  my(@lines, $VAR1);
  $ERR = undef;
  my $D    = System->get_home() . "/DATA/topo";
  my $renv = System->get_renv();

  if (!$host) {
    $host = $renv->{hostname} ;

  } elsif (substr($host,0,6) eq "MERGE-"){
    return $class->readMerge($host);
  }

  if (open(O, "$D/$host")) {
    @lines = <O>; close(O);
    eval "@lines";
    if ($VAR1) {
      return $VAR1;
    } else {
       $ERR = "readExistingTopo $host: invalid topology";
    }
  } else {
    $ERR = "readExistingTopo $host: $! <br>May need to Generate Topology Snapshot first.";
  }
  return undef;
}

sub toC {
  my($to) = @_;
  my($out, $name);
  my($hosts)    = $to->hostList();
  my($switches) = $to->switchList();
  my($storages) = $to->storageList();
  my $cnt = 0;
  foreach my $host (@$hosts) {
     $cnt++;
     $name = $host->name();
     $out .= "HO\[$cnt\]\tkey\t$name\n";
     $out .= &traverse($cnt, "HO\[$cnt\]", $host);
  }
  foreach my $switch (@$switches) {
     $cnt++;
     $name = $switch->name();
     $out .= "SW\[$cnt\]\tkey\t$name\n";
     $out .= &traverse($cnt, "SW\[$cnt\]", $switch);
  }
  foreach my $storage (@$storages) {
     $cnt++;
     $name = $storage->name();
  
     $out .= "ST\[$cnt\]\tkey\t$name\n";
     $out .= &traverse($cnt, "ST\[$cnt\]", $storage);
  }
  return $out;
}

sub traverse {
   my($cnt, $type, $host) = @_;
   my ($out,$x, $y);
require Data::Dumper;
$Data::Dumper::Indent = 0;
   foreach my $sec (keys %$host) {
      my $section = $host->{$sec};
      if (ref($section) eq "ARRAY") {
         for ($x = 0; $x <= $#$section; $x++) {
              my $asection = $section->[$x];
              if (ref($asection) eq "HASH") {
                 foreach my $el (keys %$asection) {
                    $out .= "$type\t$sec\[$x\]\t$el = $asection->{$el}\n";
                 }
              } elsif (ref($asection) eq "ARRAY") {
                 for ($y = 0; $y <= $#$asection; $y++) {
                    $out .= "$type\t$sec\[$x\]\[$y\] = $asection->[$y]\n";
                 }
              } elsif (defined($asection)) {
                 $out .= "$type\t$sec\[$x\]\t$asection\n";
              }
         }
      } elsif (ref($section) eq "HASH") {
         foreach my $el (keys %$section) {
            my $v = $section->{$el};
            if (ref($v) eq "HASH") {
               foreach my $el2 (keys %$v) {
                 $out .= "$type\t$sec\t$el\t$el2 = $v->{$el2}\n";
               }
            } elsif (ref($v) eq "ARRAY") {
               for ($y = 0; $y <= $#$v; $y++) {
                 $out .= "$type\t$sec\t$el\[$y\] = $v->[$y]\n";
               }
            } else {
               $out .= "$type\t$sec\t$el = $v\n";
            }
         }
      } else {
         $out .= "$type\t$sec\tvalue\t$section\n";
      }
    }
    return $out;
}



#
# runs locally, gets a new local or remote topo;
# 
sub getNewTopo {
  my($class, $host, $to) = @_;
  my $VAR1;
  my($renv) = System->get_renv();
  $to = 120 if (!$to);

  if (!$host) {
    $host = $renv->{hostname} ;
  }
  if ($host eq $renv->{hostname}) {
    TO->clearTopo();
    return TO->readTopo();
  }
  
  my $data = Util::Http->getCommand($host, "TO::NewTopo&host=$host", $to);

  eval $data;
  if ($VAR1) {
     $VAR1->saveTopo($host);
     return $VAR1;
  }
  return undef;
}

# execute on the remote machine
sub get_NewTopo {
  my($q) = @_;
require Data::Dumper;
  
  TO->clearTopo();
  my $to = TO->readTopo();
    
  if ($to) {
    $Data::Dumper::Indent = 1;
    print Data::Dumper::Dumper($to);
  }
}

sub mergeList {
  my($class, $hlist, $name) = @_;
  my (@lines, $to1, $topoM);
  my $renv = System->get_renv();
  my($D) = System->get_home() . "/DATA/topo";

  foreach my $host0 (@$hlist) { # list of host from config-file
    next if (substr($host0,0,6) eq "MERGE-");
    if (open(O, "$D/$host0")) {
       @lines = <O>; close(O);
       my $VAR1;
       eval "@lines";
       if (!$VAR1) {
          Debug->print0("Cannot read topo $host0");
          next;
       }
       $to1  = $VAR1;
       $class->removeHubs($to1);
       if ($topoM) {
          $class->merge($topoM, $to1, $name);
       } else {
          $topoM = $to1;
       }
    } else {
       #Debug->print0("Cannot find topo $host0");
    }
  }
  if ($topoM) {
    $topoM->crossConnect();
    if ($renv->{use_hub}) {
       require "Discman.pm";
       Discman->addHubs($topoM);
    }
  }
  return $topoM;
}

# read a MERGE- topo

sub readMerge {
  my($class, $host) = @_;
  my(@lines, $VAR1);
  my($D) = System->get_home() . "/DATA/topo";
  my(@hlist) = $class->host2list($host);

  my($d1, $host0) ;
  my $still_good = 1;

  if (! -f "$D/$host") {
    $still_good = 0;

  } else {
    my $merge_age = (stat("$D/$host"))[9];
    foreach $host0 (@hlist) {
       if (! -f "$D/$host0") {
         $still_good = 0;
         last;   
       }
       $d1 = (stat("$D/$host0"))[9];
       if ($d1 > $merge_age) {
         $still_good = 0;
         last;   
       }
    }
  }

  if ($still_good) {   # merged topo is still current
    if (open(O, "$D/$host")) {
      @lines = <O>; close(O);
      eval "@lines";
      if ($VAR1) {
        return $VAR1;
      }
    }
    Debug->print0("Cannot read $host topo");
    return;
  }

  my($topoM, $to1);
  $topoM = $class->mergeList(\@hlist, lc(substr($host,6)) );

  if ($topoM) {
    $topoM->[5] = $topoM->getZones();
    $topoM->saveTopo($host) ;
  }
  return $topoM;
}

# read an existing topo if it's in the topo dir
# if not, create a new topo if it's a local one.
# save means use the existing discman file.

sub readTopo {
  my($class, $host, $save) = @_;
  my(@lines, $VAR1);
  my($D) = System->get_home() . "/DATA/topo";

  my $Config = PDM::ConfigFile->read();
  my $renv = $Config->renv();

  if (!$host) {
    $host = $renv->{hostname} ;

# read a MERGE- topo

  } elsif (substr($host,0,6) eq "MERGE-"){
    return $class->readMerge($host);
  }

  if (open(O, "$D/$host")) {
    @lines = <O>; close(O);
    eval "@lines";
    if ($VAR1) {
      return $VAR1;
    }
  }
  if ($host ne  $renv->{hostname} && !$save) {
    return undef;
  }
  my $topo = TO->getCurrentTopo($host, $save);

  $topo->saveTopo($host);

  return $topo;
}

# Get current Topo but don't save it
sub getCurrentTopo {
  my($class, $host, $save, $hbaOnly) = @_;
  my(@lines, $VAR1);
  my($D) = System->get_home() . "/DATA/topo";

  my $Config = PDM::ConfigFile->read();
  my $renv = $Config->renv();

  $host = $renv->{hostname} if (!$host);

  my ($err1, $stderr1, $d)  = TO->getDiscman($host, $save, $hbaOnly);

  my($trace);
  my $topo = TO->newTopo(); # empty topo
  $topo->[6]{system} = {sys_error => $err1, sys_stderr => $stderr1};

  if ($d && $#$d >= 0) {
    require "Discman.pm";
    $trace  = Discman->parse($topo, $d);
  } else {
    TO::HOST->new($topo, $host, {});
  }
  return $topo if ($hbaOnly);

  my $mods = Modules->load("TO");
  my $errs;
  foreach my $m0 (@$mods) {
     next if ($m0 eq "SE");
     my $m = "TO::".$m0;
     if ($m->leaf() && $m->can("addFromConfig")) {
        $errs .= $m->addFromConfig($topo, $Config);
     }
  }
  TO::SE->addFromConfig($topo, $Config);

  $topo->[6]{system}{sys_error} .= " $errs" if ($errs);

  $topo->crossConnect(); # complete the connections that you can

  if ($renv->{use_hub}) {
    require "Discman.pm";
    Discman->addHubs($topo);
  }

  $topo->[5] = $topo->getZones();

  return $topo;
}

# conect switches to hba and storage
#          HOST <----> SWITCH <----> SWITCH <----> STORAGE
#   QLogic             SL        T          TL,SL
#   Brocade            F         E          L
#

sub crossConnect0 {
  my($to) = @_;
  my $switches = $to->switchList();
  my $storages = $to->storageList();
  my($hosts)    = $to->hostList();
  my $Config = PDM::ConfigFile->read();

  foreach my $sw (@$switches) {
    my $pi = $sw->portInfo();
    next if ($sw->type() eq "ve");
    my $sw_po = $sw->port();
    my($y,$x);
    for ($x=1; $x <= $#$pi; $x++) {
      next if (!defined($pi->[$x]{PortWWN}));
      my $link = $pi->[$x]{PortWWN};

      my ($node, $node_p) = $to->nodeByKey($link);

      # switch 2 switch
      if ($node && index("T_Port,E_Port", $pi->[$x]{sw_PortType}) >= 0) {
        if (defined($pi->[$x]{PortTargetPort}) ) {
          my $t1 = $node->port();
          $t1->[$pi->[$x]{PortTargetPort}] = $sw->name() . ":$x";
        }
      }

      next if (index("L_Port,TL_Port,F_Port",$pi->[$x]{sw_PortType}) < 0);

      if ($node) { # found a link to that port
         my $vipo = $node->port();
         $vipo->[$node_p] = $sw->name() . ":$x";
         $sw_po->[$x] = $node->name() . ":$node_p";

      } elsif (substr($link,0,1) eq "2") {# connect to HBA
        # look for a host port.
        foreach my $ho (@$hosts) {
           my $pi = $ho->portInfo();
           my $po = $ho->port();
           for ($y=0; $y <= $#$pi; $y++) {
               if ($pi->[$y]{PortWWN} eq $link) {
                  $po->[$y] = $sw->name() . ":$x";
                  $sw_po->[$x] = $ho->name() . ":$y";
               }
           }
        }

      } elsif ($link) { # T3/A5K
        my($dev, $port) = $Config->deviceByWWN($link);
        next if (!$dev);
        my $k1 = "$dev->{type}:" . $dev->key();
        my $d = $to->nodeByName($k1);
        if ($d) {
           $k1 .= ":$port";
           my $k2 = $sw->name() . ":$x";
           $sw_po->[$x] = $k1;
           my $dev_po = $d->port();
           $dev_po->[$port] = $k2;
        }
      }
    }
  }
}


sub crossConnect {
  my($to) = @_;
  my $switches = $to->switchList();
  my $storages = $to->storageList();
  my($hosts)    = $to->hostList();
  my $Config = PDM::ConfigFile->read();

  foreach my $sw (@$switches) {
    my $pi = $sw->portInfo();
    next if ($sw->type() eq "ve");
    my $sw_po = $sw->port();
    my($y,$x);
    for ($x=0; $x <= $#$pi; $x++) {
       my $link;
       if (defined($pi->[$x]{PortWWN})) {
         $link = $pi->[$x]{PortWWN};
       } else {
         next;
       }

       my ($node, $node_p) = $to->nodeByKey($link);
 
       # SWITCH2SWITCH
       if ($node && defined($pi->[$x]{PortTargetPort}) ) {
           my $t1 = $node->port();
           $t1->[$pi->[$x]{PortTargetPort}] = $sw->name() . ":$x";
           next;
       }
       if ($node) { # found a link to that port
           &connect_($node, $node_p, $sw, $x);
           next;
       }
 
       my($dev, $port) = $Config->deviceByWWN($link);
       if ($dev) {
          my $k1 = "$dev->{type}:" . $dev->key();
          my $d = $to->nodeByName($k1);
          if ($d) {
             $k1 .= ":$port";
             my $k2 = $sw->name() . ":$x";
             $sw_po->[$x] = $k1;
             my $dev_po = $d->port();
             $dev_po->[$port] = $k2;
             next;
          }
       }
         
       # look for a host port.
       my $done = 0;
       &linkToHost($hosts, $link, $sw, $x);
    }
  }
}

sub connect_ {
  my($node, $node_p, $sw, $x) = @_;

  my $vipo = $node->port();
  $vipo->[$node_p] = $sw->name() . ":$x";
  my $sw_po = $sw->port();
  $sw_po->[$x] = $node->name() . ":$node_p";
}

sub seCrossConnect {
  my($to) = @_;

  my $hosts    = $to->hostList();
  my $storages = $to->storageList();
  my $switches = $to->switchList();

  foreach my $st (@$storages) {
    my $pi = $st->portInfo();
    next if (substr($st->type(),0,2) ne "se");
    my $pis = $st->portInfo();
    my $x;
    for ($x=0; $x <= $#$pis; $x++) {
        if (defined($pis->[$x]{PortWWN})) {
            my $found = &linkToHost($hosts, $pis->[$x]{PortWWN}, $st, $x);
            if (!$found) {
              my ($node, $node_p) = $to->nodeByKey($pis->[$x]{PortWWN});
              if ($node) { # found a link to that port
                 connect_($node, $node_p, $st, $x);
              }
            }
        }
    }
  }

  foreach my $sw (@$switches) {
    my $pi = $sw->portInfo();
    next if ($sw->type() eq "ve");
    my $sw_po = $sw->port();
    my($y,$x);
    for ($x=0; $x <= $#$pi; $x++) {
       next if (!defined($pi->[$x]{PortWWN}));
       my $link = $pi->[$x]{PortWWN};
       my ($node, $node_p) = $to->nodeByKey("S$link");
       if ($node) { # found a link to that port
           connect_($node, $node_p, $sw, $x);
       }
    }
  }
}

sub linkToHost {
  my($hosts, $link, $sw, $port_no) = @_;
  my ($y, $done);
  my $sw_po = $sw->port();
  foreach my $ho (@$hosts) {
     my $pi = $ho->portInfo();
     my $po = $ho->port();
     for ($y=0; $y <= $#$pi; $y++) {
         if ($pi->[$y]{PortWWN} eq $link) {
            $po->[$y] = $sw->name() . ":$port_no";
            $sw_po->[$port_no] = $ho->name() . ":$y";
            $done = 1; last;
         }
     }
     last if ($done);
  }
  return $done;
}

###################################################
#                  ZONES
###################################################

sub getZones {
  my($to) = @_;
  my %done;
  my $switches = $to->switchList();
  my %IS;
  my $cnt=1;
  my $used;
  foreach my $sw (@$switches) {
    my $z = $sw->getZoneList('hd');   # hd0, hd1 ...
    $cnt++ if ($used);
    $used = 0;
    foreach my $k (keys %$z) {
       next if (index($done{$k}, $sw->name()) >= 0);
       my $sws = $to->getZoneSwitches($sw, $k);
       if ($sws && $#$sws > 0) {
         my $ty = substr($k,0,2);
         $IS{"zone:$cnt:$k"} = bless ({ name => "zone:$cnt:$k", type => $ty, members => $sws }, 'TO::Zone'); 
         $used = 1;
         $done{$k} .= join(' ', @$sws) . " ";
       }
    }
  }
  return \%IS;
}

# find all nodes attached to a list of switches
# $l = $to->getZoneNodes($zone);
# returns strings

sub getZoneNodes {
  my($to, $zone) = @_;
  my(@R);
  my $l = $to->[5]{$zone};
  my($z, $no, $t) = split(/\:/, $zone);  # zone:1:hd1
  my $zt = substr($t,0,2);
  my $zno = substr($t,2) + 0;
  return undef if (!$l);
  foreach my $sw (@$l) {  # find nodes on that sw;
     if (exists($sw->{zones}{$zt})) {
        my $ports = $sw->port();
        my $members = $$sw->{zones}{$zt}[$zno];
        my @L = split(/ /, $members);
        foreach my $m (@L) {
            if ($ports->[$m]) {
               push(@R, $ports->[$m]);
            }
        }
     }
  }
  return \@R;
}


# return a list of switches in the same Zone
# zone = 'hd.0'
# $to->getZoneSwitches($startNode, "hd1"); # all ports

sub getZoneSwitches {
  my($to, $startnode, $zone) = @_;
  my(@LIST, @NEW);
  my (%PORT, @PORTS, %SW, @SWS);
  my $ports = $startnode->port();

  my $piz = $startnode->getPortsInZone($zone);
  my @Z = split(/ /, $piz);
  $SW{$startnode->name()} = 1;
  push(@SWS, $startnode->name());

  foreach my $p (@Z) {
     if ($ports->[$p]) {
       if (substr($ports->[$p],0,6) eq "switch") {
         push(@LIST,  $ports->[$p]) ;
       }
     }
  }
  while (1) {
     @NEW = ();
     foreach my $node (@LIST) {
       my($node2,$p2) = $to->nodeByName($node);
       next if (!$node2);
       my $piz = $node2->getPortsInZone($zone);

       if (index(" $piz ", " $p2 ") >= 0){# found port in same zone
         my $pp = $node2->port();
         my $name2 = $node2->name();
         if (!$SW{$name2}) {
            $SW{$name2} = 1; 
            push(@SWS, $name2);
         }
         #add ports for next run
         my @Z = split(/ /, $piz);
         foreach my $p (@Z) {
           my $p0 = $pp->[$p];
           next if (!$p0);
           my ($t, $k, $port2) = split(/\:/, $p0);
           if ($t eq "switch") {
              if (!$SW{"$t:$k"}) {
                push(@NEW, $p0);
              }
           }
         }
       }
     }
     if ($#NEW  < 0) {
        last;
     } else {
        @LIST = @NEW;
     }
  }
  
  return \@SWS;
}


sub appletStream {
  my($to, $arg)  = @_;

  return TO::Applet::stream($to, $arg);
}




# $select = TO->topoListSelect("host", "ccadieux");
#
sub topoListSelect {
  my($class, $name, $value) = @_;
  my $l = $class->topoList();
  my $select = "<select name=$name>";
  foreach my $x (sort @$l) {
     #next if ($x eq "MERGE-MASTER");
     my $sel = ($x eq $value) ? "selected":"";
     $select .= "<option $sel>$x";
  }
  $select .= "</select>";
  return $select;
}


sub topoList {
  my($class) = @_;
  
  my($l, @list);
  my $D =  System->get_home() . "/DATA/topo";
  opendir(O, $D);
  while ($l = readdir(O)) {
    next if (-d "$D/$l");
    next if (substr($l,0,1) eq ".");
    push(@list,$l);
  }
  return \@list;
}

#
# merge all available topologies
#
sub mergeAll {
  my($class) = @_;
  my @L;
  my $first = 1;
  my $Config = PDM::ConfigFile->read();
  my $list   = $Config->hostsList(); # all slaves + master
  my $topoM  = $class->mergeList($list, "master");
  if (wantarray) {
    return ($topoM, $list);
  } else {
    return $topoM;
  }
}
use TO::HUB;

sub merge_ports {
  my($TO, $key, $S, $s, $name) = @_;
  my($x);
  my $node = $S->{$key};
  my $new  = $s->{$key};
  my $P = $node->{port};  # merge ports
  my $p = $new->{port};  # new ports
  for ($x=0; $x <= $#$p; $x++) {
      next if (!$p->[$x]);
      if (!$P->[$x]) {
         $P->[$x] = $p->[$x];
      }
  }
}

sub merge0 {
  my($s0, $s1, $desc) = @_;
  my($x, $err);
  if (ref($s1) eq "HASH") {
    foreach $x (keys %$s1) {
      if (!$s0->{$x}) {
         $s0->{$x} = $s1->{$x};
      }
    }
     
  } else {
    for ($x=0; $x <= $#$s1; $x++) {
      next if (!$s1->[$x]);
      if (!$s0->[$x]) {
         $s0->[$x] = $s1->[$x];
      } elsif (ref($s0->[$x]) eq "HASH") {
         my $size1 = (keys %{$s0->[$x]});
         my $size2 = (keys %{$s1->[$x]});
         if ($size1 < $size2) {
            $s0->[$x] = $s1->[$x];
         }
      } else {
        $err .= "$desc $x already exist, not merged<br>";
      }
    }
  }
  return $err;
}

sub removeHubs {
  my($class, $to) = @_;
  my($x,$y);
  my $hu = $to->[3];
  foreach my $key (keys %$hu) {
     my $hub = $hu->{$key};
     my $ports = $hub->port();
     foreach my $p (@$ports) {  # hosts => storages
        next if (substr($p,0,4) ne "host");
        my($host_node, $host_p) = $to->nodeByName($p);
        foreach my $p2 (@$ports) {
           next if (substr($p2,0,4) eq "host");
           my($st_node, $st_p) = $to->nodeByName($p2);

           my $host_po = $host_node->port();
           $host_po->[$host_p] = $p2;
           my $st_po = $st_node->port();
           $st_po->[$st_p] = $p;
        }
     }
  }
  $to->[3] = {};
}


sub merge {
  my($class, $TO, $to, $name) = @_;
  my $err;
  my($HO) = $TO->[0];
  my($SW) = $TO->[1];
  my($ST) = $TO->[2];
  my($SY) = $TO->[4];
  my($HU) = $TO->[3];

  my($ho) = $to->[0];
  my($sw) = $to->[1];
  my($st) = $to->[2];
  my($sy) = $to->[4];
  my($hu) = $to->[3];


  foreach my $key (keys %$sy) {  # add syns
     $SY->{$key} = $sy->{$key};
  }
  foreach my $key (keys %$ho) {  # new hosts
     $HO->{$key} = $ho->{$key};
     &merge_ports($TO, $key, $HO, $ho, $name);
  }

  foreach my $key (keys %$sw) {
     if (exists($SW->{$key}))  {
       my $I1 = $SW->{$key}{info};
       my $i1 = $sw->{$key}{info};
       &host_array($I1, $i1);

       $err .= &merge0($SW->{$key}{dport},    $sw->{$key}{dport},    "$key:dport");
       $err .= &merge0($SW->{$key}{portInfo}, $sw->{$key}{portInfo}, "$key:portInfo");
       if ($sw->{$key}{volInfo} && !exists($SW->{$key}{volInfo})) {
          $SW->{$key}{volInfo} = {};
       }
       $err .= &merge0($SW->{$key}{volInfo},  $sw->{$key}{volInfo},  "$key:volInfo");
     } else {
       $SW->{$key} = $sw->{$key};
     }
     &merge_ports($TO, $key, $SW, $sw, $name);
  }

  foreach my $key (keys %$st) {
     my $node = $st->{$key};
     if ($node->type() eq 't3'  && (index($key, ".") < 0) ) { # skip t3 with wwn as name
         next;
     }
        
     if (exists($ST->{$key}))  {
       my $I1 = $ST->{$key}{info};
       my $i1 = $st->{$key}{info};
       &host_array($I1, $i1);

       $err .= &merge0($ST->{$key}{portInfo}, $st->{$key}{portInfo}, "$key:portInfo");
       if ($st->{$key}{volInfo} && !exists($ST->{$key}{volInfo})) {
          $ST->{$key}{volInfo} = {};
       }
       $err .= &merge0($ST->{$key}{volInfo},  $st->{$key}{volInfo},  "$key:volInfo");

     } else {
       $ST->{$key} = $st->{$key};
     }
     &merge_ports($TO, $key, $ST, $st, $name);
  }

}

sub host_array {
 my($I1, $i1) = @_;
 if ($I1->{host} ne $i1->{host} ) {
     $I1->{hosts}[0] = $I1->{host};
     $I1->{hosts_type}[0] = $I1->{host_type};
     my $ht = $I1->{hosts};
     my $found = 0; my $x;
     for ($x=1; $x <= $#$ht; $x++ ) {
         if ($ht->[$x] eq $i1->{host}) {
            $found = 1; last;
         }
     }
     if (!$found) {
        my $j = $#$ht+1;
        $I1->{hosts}[$j] = $i1->{host};
        $I1->{hosts_type}[$j] = $i1->{host_type};
     }
  }
}


sub diff {
  my($class, $to1, $to2, $max) = @_;
  my @out;

  my $err1 = $class->_diff($to1->[0], $to2->[0], "HOST","", 0, $max);
  push(@out, @$err1) if ($#$err1 >= 0);

  my $err2 = $class->_diff($to1->[1], $to2->[1], "SWITCH","", 0, $max);
  push(@out, @$err2) if ($#$err2 >= 0);

  my $err3 = $class->_diff($to1->[2], $to2->[2], "STORAGE","", 0, $max);
  push(@out, @$err3) if ($#$err3 >= 0);

  return \@out if ($#out >= 0);

  return [];
}

# $e = old
# e2 = new

sub _diff {
  my($class, $e, $e2, $type, $desc, $level, $max) = @_;
  my @out;
  if ($e =~ /HASH/) {
    my @size1 = (keys %$e);
    my @size2 = (keys %$e2);
    
    foreach my $key (keys %$e) {    # each host, switch, storage
       my $el = $e->{$key};
       if($key =~ /DeviceID/){
	 # Don't want to display deviceID difference's, skip it
	 next;
       }

       if (!exists($e2->{$key})) {
	 push(@out, [$type, "NODE_REMOVED", "'$key' was removed"]) if ($level <= $max) ;
         next;
       }
       my $el2 = $e2->{$key};
       if (ref($el) ne ref($el2)) {
         push(@out, [$type, "TYPE_CHANGE", "$desc: " . ref($el) . " != " . ref($el2)]);
         next;
       }
       my $kk = $key;
       my $out2 = $class->_diff($el, $el2, $type, $desc . "{$kk}", $level+1, $max);
       if ($#$out2 >= 0) {
         push(@out, @$out2);
       }
    }
    foreach my $key (keys %$e2) {    # each host, switch, storage
       my $el = $e2->{$key};
       if (!exists($e->{$key})) {
         
	 push(@out, [$type, "NODE_ADDED", "'$key' was added"]) if ($level <= $max);

       }
    }

  } elsif ($e =~ /ARRAY/) {
     if ($e2 !~ /ARRAY/) {
       push(@out, [$type, "TYPE_CHANGE", "$desc not an array"]);
       return \@out;
     }
     if ($#$e > $#$e2) {
       my $dd = $#$e > $#$e2;
       my($t1, $t2) = &nameof($type,$desc);
       push(@out, [$type, "SIZE_CHANGE", "$dd $t1 removed from $t2"]);
     } elsif ($#$e < $#$e2) {
       my $dd = $#$e2 > $#$e;
       my($t1, $t2) = &nameof($type,$desc);
       push(@out, [$type, "SIZE_CHANGE", "$dd $t1 added to $t2"]);

     }
     my $x;
     for ($x=0; $x <= $#$e; $x++) {
         my $out2 = $class->_diff($e->[$x], $e2->[$x], $type, 
                                    $desc . "[$x]", $level+1, $max);
         if ($#$out2 >= 0) {
            push(@out, @$out2);
         }
     }

  } elsif ($e ne $e2) {
     my($t1, $t2) = &nameof($type,$desc);
     my ($f1, $f2);
     $f1 = $e ? "'$e'" : "[undefined]";
     $f2 = $e2? "'$e2'" : "[undefined]";
     push(@out, [$type, "VALUE_CHANGE", "'$t1' on '$t2' changed from $f1 to $f2"]);
  }
  return \@out;
}

use vars qw(%MAP);
%MAP = ('HOST-portInfo' => 'Hba-Path', 
        'HOST-port' => 'Hba-FC-Connection'
       );

sub nameof {
  my($type, $desc) = @_;
  my $i = index($desc, "}");
  my $d = substr($desc, $i+2, -1);
  $d =~ s/[\]}{\[]+/./g;
  my $t;
  if ("$type-$d" =~ /SWITCH\-port\.\d+/) {
     $t = "$d connection";
  } else {
     $t = $MAP{"$type-$d"} || $d;
  }
  return ($t, substr($desc, 1, $i-1));
}



sub _diff2 {
  my($class, $e, $e2, $desc) = @_;
  my($x);
  my @size1 = (keys %$e);
  my @size2 = (keys %$e2);
  if ($#size1 != $#size2) {
     return ("SIZE_CHANGE", $desc);
  }

  foreach my $key (keys %$e) {    # each host, switch, storage
      my $p = $e->{$key};
      my $p2 = $e2->{$key};
#      my($type, $key0) = split(/\:/, $key);
      foreach my $section (keys %$p) { # each section (portInfo, info, port, dport ..
           my $pA = $p->{$section};
           my $pA2 = $p2->{$section};
           if ($section eq "info" && $pA->{name} ne $pA2->{name}) {
              return ("NAME_CHANGE", "$desc $key");

           } elsif (ref($pA) eq "ARRAY") {
              if ($#$pA != $#$pA2) {
                 return ("SIZE_CHANGE", "$desc $section");
              }
              for ($x=0; $x <= $#$pA; $x++) {
                  my $pB = $pA->[$x];
                  my $pB2 = $pA2->[$x];
                  if (ref($pB) eq "HASH") {
                    foreach my $value (keys %$pB) { # each value
                       if ($pB->{$value} ne $pB2->{$value}) {
                          return ("VALUE_CHANGE", "$desc:$key FIELD:$section:$x:$value");
                       }
                    }
                  } elsif ($pB ne $pB2) {
                    return ("VALUE_CHANGE", "$desc:$key FIELD:$section:$x");
                  }
              }
           } elsif (ref($pA) eq "HASH") {
              my @size1 = (keys %$pA);
              my @size2 = (keys %$pA2);
              if ($#size1 != $#size2) {
                  return ("SIZE_CHANGE","$desc:$key FIELD:$section");
              }
              foreach my $ael (keys %$pA) {
                  my $pB = $pA->{$ael};
                  my $pB2 = $pA2->{$ael};
                  if (ref($pB) eq "HASH") {
                    foreach my $value (keys %$pB) { # each value
                       if ($pB->{$value} ne $pB2->{$value}) {
                          return ("VALUE_CHANGE", "$desc:$key FIELD:$section:$ael:$value")
;
                       }
                    }

                  } elsif (ref($pB) eq "ARRAY") {
                     if ($#$pB != $#$pB2) {
                       return ("SIZE_CHANGE", "$desc:$key $section $ael");
                     }
                     for ($x=0; $x <= $#$pB; $x++) {
                         my $pC = $pB->[$x];
                         my $pC2 = $pB2->[$x];
                         if (!ref($pC) && ($pC ne $pC2)) {
                            return ('VALUE_CHANGE', "$desc:$key FIELD-$x:$pC");
                         }
                     }
                  } elsif ($pA->{$ael} ne $pA2->{$ael}) {
                     return ("VALUE_CHANGE", "$desc:$key FIELD:$section:$ael");
                  }
              }
           }
       }
   }
   return (undef,$desc);
}

sub saveHistory {
  my($to, $file) = @_;
require Data::Dumper;

  my($renv) = System->get_renv();

  my $CD = System->get_home() . "/DATA/topohist";
  mkdir $CD, 0777 if (!-d $CD);

  opendir(O,  $CD);
  my @l = readdir(O); closedir(O);
  my $cnt;
  foreach my $f (@l) {
     $cnt++ if (substr($f,0,length($file)) eq $file) ;
  }
  return undef if ($cnt > $renv->{topo_history_size});

  my $today = Util->get_today();
  $today =~ s/ /_/g;
  $file = System->get_home() . "/DATA/topohist/$file.$today";

  $Data::Dumper::Indent = 1;
  my($data) = Data::Dumper::Dumper($to);
  $data =~ s/^        /\t\t/gm;     
  open(O, ">$file");
  print O $data;
  close(O);
  return $file;

}

# instance method

sub saveTopo {  # save a topology
  my($Topo, $file) = @_;
require Data::Dumper;

  open(O, ">" .  System->get_home() . "/DATA/topo/$file"); 
  $Data::Dumper::Indent = 1;
  $Data::Dumper::Deepcopy = 1;
  my($data) = Data::Dumper::Dumper($Topo);
  $data =~ s/^        /\t\t/gm;     
  print O $data;
  close(O);
}

# returns the date of the last topology generated

sub lastTopo {
  my($class, $host) = @_;
  my($renv) = System->get_renv();
  $host = $renv->{hostname} if (!$host);

  my($file) = System->get_home() . "/DATA/topo/" . $host;
  if (-f $file) {
     return Util->get_file_created($file);
  } else {
     return undef;
  }
}

sub topoAge {
  my($class, $host) = @_;
  my($renv) = System->get_renv();
  $host = $renv->{hostname} if (!$host);
  my $topo_file = System->get_home() . "/DATA/topo/$host";
  my $age = (stat($topo_file))[9];
  return $age;
}

sub clearTopo {
  my($class, $host) = @_;

  my($renv) = System->get_renv();
  $host = $renv->{hostname} if (!$host);
  $NEWTOPO = 1;
  my($file) = System->get_home() . "/DATA/topo/" . $host;
  unlink $file;
}

#
# if topo/name  is gone, reset the incomplete cache.
#
sub resetIncomplete {
  my($class) = @_;
  my($renv) = System->get_renv();
  my $host = $renv->{hostname};
  my($file) = System->get_home() . "/DATA/topo/" . $host;
  if (!-f $file) {
    my $h = PDM->getCacheHandle("incomplete_topo");
    $h->{try} = undef;
  }
}

sub setIncomplete {
  my($class) = @_;
  my $h = PDM->getCacheHandle("incomplete_topo");
  $h->{try} = 2;
}



sub toString {
  my($Topo) = @_;
  my($x);

  my($hosts) = $Topo->hostList();
  foreach my $ho (@{$Topo->hostList()}) {
     print $ho->toString();
     print "\n";
  }
  foreach my $sw (@{$Topo->switchList()}) {
     print $sw->toString();
     print "\n";
  }
  foreach my $st (@{$Topo->storageList()}) {
     print $st->toString();
     print "\n";
  }
}


sub val2 {
  my($class, $ix, @a) = @_;
  my($o) = -2;
  my($val);
  foreach my $x (@a) {
    if (defined($x->[0]) && $x->[0] > $o) {
          $o = $x->[0];
          $val = $x->[$ix];
    }
  }
  return $val;
}

sub valArray {
  my($class, @a) = @_;
  my ($other, $otherInfo);
  foreach my $x (@a) {
      my $o = -2; my $o_text = "";
      foreach my $y (@$x) {
          if (defined($y->[0]) && ($y->[0] > $o)) {
            $o = $y->[0]; $o_text = $y->[1];
          }
      }
      $o = "" if ($o == -2);
      $other .= "$o,"; 
      $otherInfo .= "$o_text,"; 
  }
  return ($other, $otherInfo);
}
  

sub val {
  my($class, @a) = @_;
  my($o) = -2;
  foreach my $x (@a) {
     $o = $x if (defined($x) && ($x > $o));
  }
  return "" if ($o == -2);
  return $o;
}


#
# save XY coord of a topo, done by applet
#
sub put_topo_xy {
 my($q, $post_data) = @_;
 my($topoFile) = $q->{topo}.$q->{FILTER} || "data1";
  
 my $ruser = System->get_ruser();
 my($F) = System->get_home() . "/DATA/tmp/topoxy_$topoFile$q->{isCustomer}";
 
 #open(O, ">>/tmp/log"); print O "$F $q->{isCustomer}\n";
 #close(O);

  if (open(O, ">$F")) {
    print O "$post_data\n";
    close(O);
    chmod 0666, $F;
    print "\Saved (" . length($post_data) . " bytes)\n";
  } else {
    print "\nERR: $!\n";
  }
}



sub newTopo {
  my($class) = @_;
  my($Topo) = [{}, {}, {}, {}, {}, {}, {}];
  bless($Topo, 'TO');
  return $Topo;
}

#################################
#          DISCMAN
#################################

sub discmanPath {
  my($class) = @_;


  return System->get_home() . "/Diags/bin/discman";
}

sub available {
  my($class) = @_;

  return 0 if (System->os_version() lt "5.8");
  if (-f $class->discmanPath() ) {
     return 1;
  }

  return 0;
}

# returns cached discman output if available
# called by rasagent locally

sub getDiscman {
  my($class, $host, $save, $hbaOnly) = @_;
  my($command, @a);

  my($renv) = System->get_renv();
  $host = $renv->{hostname} if (!$host);

  my($file) = System->get_home() . "/DATA/discman/$host";

  if ($save && -r $file) {
     open(O, $file);
     my(@x) = <O>; close(O);
     return  ("", "", \@x);
  }

  return undef if (!$class->available() );

  $command = $class->discmanPath();

  if (!-x $command) {
     Debug->err(TEXT => "$command not accessible: $!");
  }

  $command .= " -oh" if ($hbaOnly);
  my($err,$com, $stderr) = Util->run_command( $command, "discman.test",  
                                     $renv->{"timeout.discman"}) ;
  
  Debug->print2("discman returned $#$com lines");
  if ($stderr) {
    if($stderr=~/g_get_lilp_map/)
    {
         return ("Detected possible downlevel luxadm patch", "Detected possible downlevel luxadm patch. Run Revision checking and ensure all patches are up to date.");
    }
    Debug->errNoRepeat(DISCMAN => undef, 8, $stderr);
  }

  if($err =~ /Timeout/){
     return ("Timed out waiting for discman to run","May need to adjust timeout values for discman under System Utilities.", $com);
  }


   
  if($#$com <  6)
  {
     # discam didn't run (Should have more than 6 lines if it did)
     # Remove file
     unlink $file;
     return ("Discman did not run", $stderr, $com);
  }

  if (open(O, ">$file")) {
    print O join("\n", @$com);
    close(O);
  } else {
    Debug->err(TEXT => "$file: $!");
  }
  return ($err, $stderr, $com);
}

sub clearDiscman {
  my($class) = @_;

  my($renv) = System->get_renv();
  my($file) = System->get_home() . "/DATA/discman/" . $renv->{hostname};
  unlink $file;
}





sub zoneList {
  my($Topo) = @_;
  my($zones) = $Topo->[5];
  my(@list) = (values %$zones);
  return \@list;
}

sub hostList {
  my($Topo, $arg) = @_;
  my @list;
  my $excl = $arg->{mgmtExclude};
  my($hosts) = $Topo->[0];
  foreach my $e (sort values %$hosts) {
    next if ($excl && $excl eq $e->{info}{mgmtLevel});
    push(@list, $e);
  }
  return \@list;
}

sub switchList {
  my($Topo, $arg) = @_;
  my $excl = $arg->{mgmtExclude};
  my @list;
  my($hosts) = $Topo->[1];
  foreach my $e (values %$hosts) {
    next if ($excl && $excl eq $e->{info}{mgmtLevel});
    push(@list, $e);
  }
  return \@list;
}

sub storageList {
  my($Topo, $arg) = @_;
  my @list;
  my $excl = $arg->{mgmtExclude};
  my($hosts) = $Topo->[2];
  foreach my $e (sort values %$hosts) {
    next if ($excl && $excl eq $e->{info}{mgmtLevel});
    push(@list, $e);
  }
  return \@list;
}

sub hubList {
  my($Topo) = @_;
  my($hosts) = $Topo->[3];
  my(@list) = (values %$hosts);
  return \@list;
}

sub wwnList {
  my($Topo) = @_;
  return $Topo->[4];
}
 
#
# works for wwn and keys
#
sub nodeByKey {
  my($to, $key) = @_;

  my $syns = $to->[4];
  my $n = $syns->{$key};
  if ($n) {
     return $to->nodeByName($n);
  }
  return undef;
}

# translate an entry in port[] into node name and portLabel

sub linkName {
   my($to , $link) = @_ ;
   my($type, $key, $port) = split(/\:/, $link);
   my $node = $to->nodeByName("$type:$key");
   return undef if (!$node);
   return $node->id() . " " . $node->portLabel($port);
}

# find the node and port at the other end of a nodeport string
#  my($target, $target_port) = nodeportTarget("t3:12312312312312:1");
#  print $target->name() . ":" . $target_port

sub nodeportTarget {
  my($to, $nodeport) = @_;

  my ($node, $p) = $to->nodeByName($nodeport);
  return undef if (!$node);
  my $ports = $node->port();
  my $port = $ports->[$p];
  if (wantarray) {
    my ($target_node, $target_p) = $to->nodeByName($port);
    return ($target_node, $target_p);
  } else {
    return $port;
  }
}
  
  



sub nodeByName {
  my($Topo, $name) = @_;
  my($x);

  my($type, $n, $port) = split(/:/, $name);
  $name = "$type:$n";

  for ($x=0; $x <= 3; $x++) {
    my($n) = $Topo->[$x];
    if ($n->{$name}) {
       if (wantarray) {
          return ($n->{$name}, $port);
       } else {
          return $n->{$name};
       }
    }
  }
  return undef;
}

#
#  $To->nodeName($node);

sub nodeName {
  my($Topo, $node) = @_;
  
  return $node->{info}{name};
}


sub cleanN {
  my($s) = @_;
  my(@a) = split(/:/, $s);
  if ($a[0] eq "hub") {  
    return "u:$a[1]";
  }
  return substr($a[0],0,1) . ":" . $a[1];
}

1;
