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;
use Client;
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.
# $err = $to->errorsHash(); foreach my $err_name (keys %$err) {....
# 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 errorsHash {
  my($to) = @_;
  my(%E);
  my $errs = $to->[6];
  foreach my $k (keys %$errs) {
     my $p = $errs->{$k};
     foreach my $k1 (keys %$p) {
       if ($p->{$k1}) {
          $E{"$k.$k1"} = $p->{$k1};
       }
     }
  }
  return \%E;
}

sub deleteStorage {
  my($class, $key) = @_;
  my $st = $class->[2];

  if (exists $st->{$key}) {
     my $id = $st->{$key};
     Debug->print25("seCross: Deleting " . $id->id() . " from topo");
     delete ($st->{$key});
  }
}

sub deleteSwitch {
  my($class, $key) = @_;
  my $st = $class->[1];
  delete ($st->{$key});
}

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);
    } else {
      #Util::Http->createFile($master, "master_topo_marker");
    }
  } 

}

sub errors {
  my($class, $host) = @_;
  my $renv = System->get_renv();
  $host = $renv->{hostname} if (!$host);
  my($l, $err, $stderr);
  my $topo = Util->deserialize("topo/$host");
  $err = $topo->[6]{system}{sys_error};
  $stderr = $topo->[6]{system}{sys_stderr};
  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;
}

sub write_wwn_map {
  my($to) = @_;
  my $to1 = TO->readExistingTopo();
  return if (!$to1);
  my $sts = $to1->storageList();
  my $wwns;
  foreach my $st (@$sts) {
     next if (!$st);
     next if ($st->type() ne "a5k");
     my $disks = $st->diskInfo();
     foreach my $disk (keys %$disks) {
        my $ix = index($disk, "_");
        my $dd = $disk;
        my $slot;
        $dd = substr($dd,0,$ix) if ($ix > 0);
        if (substr($dd,0,1) eq "f") {
           $slot = "front." . substr($dd,1);
        } else {
           $slot = "rear." . substr($dd,1);
        }
        $wwns .= $disks->{$disk}{PortWWN} .
                "=Box:$st->{info}{BoxName}\tDRIVE:$slot\t" .
                "Box WWN:" .substr($st->{info}{name},4) ."\tLogical Path:".
                " $disks->{$disk}{LogicalPath}" .  "\n";
     }
   }
   open(O, ">" . System->get_home() . "/DATA/WWN_MAP");
   print O $wwns;
   close(O);
}



#
# 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);
  }
}

# create empty topology if needed
# re-create the topo if it is corrupted

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

  if (!-f $F) {
     my $topo = TO->newTopo();
     $topo->saveTopo($hostname);

  } else {
    my($VAR1);
    open(O, $F);
    my @lines = <O>; close(O);
    eval "@lines";
    if (!$VAR1) {
       my $topo = TO->newTopo();
       $topo->saveTopo($hostname);
    }
  }
}

#
#  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} ;
  }

  if (substr($host,0,6) eq "MERGE-" && !-f "$D/$host" ){
    $class->mergeTopos($host);
  }

  if (open(O, "$D/$host")) {
    @lines = <O>; close(O);
    eval "@lines";
    if ($VAR1) {
      return $VAR1;
    } else {
       $ERR = "readExistingTopo $host: invalid topology, deleting";
    }
  } else {
    $ERR = "readExistingTopo $host: $!";
  }
  return undef;

  #my $topo = TO->newTopo(); # empty topo
  #return $topo; 
}

sub filter_list {
 my($to, $FILTER) = @_;
 my($FILTER_TYPE, $FILTER_ID) = split(/\:/,$FILTER,2);
 my($GR, $GR_MAP, $ZONE_ID, $FILTER_LIST);

 if ($FILTER_TYPE eq "group") {
   my($host1,$cat, $rest) = Grouping->keyParts($FILTER_ID);
   my $G = Grouping->read("$host1:$cat");
#  use Data::Dumper; print "<pre>" . Dumper($G);
   if ($G) {
     $GR = $G->members();
     $GR_MAP = $G->map();
   }

 } elsif ($FILTER_TYPE eq "zone") {
   $ZONE_ID = $FILTER_ID;

 } elsif ($FILTER_TYPE eq "island") {
   my $IS = Util->deserialize("island_list");
   if ($IS) {
      $FILTER_LIST = $IS->[$FILTER_ID];
   }

 } else {  # SWITCHES
   my $s = $to->nodeByName($FILTER);
   my $y;
   if ($s) {
      $FILTER_LIST .= $s->name() . ",";
      my $ports = $s->{port};
      for ($y=0; $y <= $#$ports; $y++) {
         if ($ports->[$y]) {
            my($t1, $w1, $rest) = split(/\:/, $ports->[$y]);
            my $s2 = $to->nodeByName("$t1:$w1");
            if($s2){
               $FILTER_LIST .= $s2->name() . ",";
            }
         }
      }
   } elsif ($FILTER && $FILTER ne "*") {
      $FILTER_LIST = "XX";
   }
 }
 return ($ZONE_ID, $FILTER_LIST, $GR, $GR_MAP);
}


sub toC2 {
  my($to, $html, $filter)  = @_;
  my($out, $name, $x);
  my($hosts)    = $to->hostList();
  my($switches) = $to->switchList();
  my($storages) = $to->storageList();
  my $cnt = 0;
  my($ZONE_ID, $FILTER_LIST, $GR, $GR_MAP) = $to->filter_list($filter);
  
  foreach my $dev (@$hosts, @$switches, @$storages) {
     next if ($FILTER_LIST && index($FILTER_LIST, $dev->name()) < 0);
     $out .= "\n";
     $out .= $to->toC_dev($dev, $html);
  }
  return $out;
}

#
# return a hash with node info and info about each port connection
#
sub connections {
  my($to, $dev) = @_;
  my $model = $dev->info("model2") || $dev->info("model");
  my $pid   = $dev->info("VendorID") . " " . 
                     ($dev->info("ProductID") || $dev->info("LGroup"));
  my $type = Util->abb($dev->type() . ".medium");
  my $pos  = $dev->port();
  my $pis  = $dev->portInfo();
  my %rc;

  my ($cnt, $x);
  for ($x=0; $x <= $#$pos; $x++) {
        next if (!$pos->[$x]);
        my $ww = $pis->[$x]{LocalPortWWN} || $pis->[$x]{PortWWN};
        my $pa =  $pis->[$x]{path};
        my $ptype = $pis->[$x]{sw_PortType};
        my $ppath = $pis->[$x]{hba_path};
        my $lb = $dev->portLabel($x);
        my @S  = split(/\:/ ,$pos->[$x]);
        my ($node, $node_p, $kind) = $to->nodeByName($pos->[$x]);

        my $port1 = {   wwn => $ww, 
                       path => $pa, 
                     portNo => $x,
                       type => $ptype,
                       port => $lb,
                 remote_key => "$S[0]:$S[1]",
                remote_type => $S[0],

                remote_name => $S[1],
                remote_port => $S[2],
              remote_portNo => $S[2],
                    };

        if ($node) {
           $port1->{remote_name}   = $node->info("BoxName") if ($node->info("BoxName"));
           $port1->{remote_port}   = $node->portLabel($node_p);
        }
        $rc{"port$x"} = $port1;
   }
   return \%rc;
}


sub toC_dev {
  my($to, $dev, $html, $link, $link_end) = @_;
  my $out;
     my $model = $dev->info("model2") || $dev->info("model");
     my $pid   = $dev->info("VendorID") . " " . ($dev->info("ProductID") || $dev->info("LGroup"));
     my $type = Util->abb($dev->type() . ".medium");
     if ($html == 2) {
        $out .= "<tr><td bgcolor=$Style::LIGHT colspan=2><b>" . $dev->id() . " $model $pid</td>";
     } elsif ($html == 1) {
        $out .= "<b>" . $dev->id() . " $model $pid</b>\n";
     } else {
        $out .= $dev->id() . " $model $pid\n";
     }
     my $pos = $dev->port();
     my $pis = $dev->portInfo();
     my ($cnt, $x);
     for ($x=0; $x <= $#$pos; $x++) {
        next if (!$pos->[$x]);
        my $ww = $pis->[$x]{LocalPortWWN} || $pis->[$x]{PortWWN};
        my $pa =  $pis->[$x]{path};
        my $ptype = $pis->[$x]{sw_PortType};
        my $ppath = $pis->[$x]{hba_path};
        my $lb = $dev->portLabel($x);
        if ($html == 2) {
          $out .= "<tr><td nowrap>$lb</td><td>" .  "$pis->[$x]{PortWWN} $pa $ptype $ppath </td>";
        } else {
          $out .= sprintf("%9s: %s\n", $lb, "$pis->[$x]{PortWWN} $pa $ptype $ppath");
        }
        my ($node, $node_p, $kind) = $to->nodeByName($pos->[$x]);
        my ($info);
        if ($node) {
           my $bn = $node->info('BoxName');
           $info = ($bn || $node->id()) . " " . $node->portLabel($node_p)
        } else {
           $info = $pos->[$x];
        }
        if ($html == 2) {
           my $ln;
           if ($link) {
             $ln = "$link&start=" . $dev->name() . ":$x&end=" . 
                    $node->name() . ":$node_p" . ">$link_end";
           }
           $out .= "<tr><td></td><td>Connected to $info $ln</td>";
        } else {
          $out .= "         Connected to $info\n";
        }
        $cnt++;
     }
     if (!$cnt) {
        $out .= $html == 2 ? "<tr><td colspan=2>&nbsp;&nbsp;No Connections!</td>" : 
                             "    No Connections!\n";
     }
  return $out;
}

sub toXML {
  my($to, $filename, $excludeHBA) = @_;
  my($out, $name, $x);
  my $renv = System->get_renv();
  my @NAMES = ('HOST','SWITCH','STORAGE','HUB');
  for ($x=0; $x <= 3; $x++) {
    my $section = $to->[$x];
    foreach my $e (sort keys %$section) {
      my $dev = $section->{$e};
      $name = $dev->name();
      next if ($excludeHBA && !$renv->{show_hba_devices} && substr($name,0,4) eq "hbas");
      $out .= "<$NAMES[$x] ID=\"$e\">\n";
      $out .= &xml_traverse($dev, 2);
      $out .= "</$NAMES[$x]>\n";
    }
  }
  if ($filename) {
    open(OO, ">$filename");
    print OO $out;
    close(OO);
  }
  return $out;
}


sub xml_traverse {
   my($host, $off) = @_;
   my ($out,$x, $y);
   foreach my $sec (sort keys %$host) {
      my $head = "  <" . uc($sec) . ">\n";
      my $out1;
      my $section = $host->{$sec};
      if (ref($section) eq "ARRAY") {
         for ($x = 0; $x <= $#$section; $x++) {
              my $asection = $section->[$x];
              if (ref($asection) eq "HASH") {
                 $out1 .= "    <ELEMENT ID=\"$x\">\n";
                 foreach my $el (sort keys %$asection) {
                    $out1 .= &value($el, $asection->{$el});
                 }
                 $out1 .= "    </ELEMENT>\n";
              } elsif (ref($asection) eq "ARRAY") {
                 $out1 .= "    <ELEMENT ID=\"$x\">\n";
                 for ($y = 0; $y <= $#$asection; $y++) {
                    $out1 .= &value($y, $asection->[$y]);
                 }
                 $out1 .= "    </ELEMENT>\n";
              } elsif (defined($asection)) {
                 $out1 .= &value($x, $asection);
              }
         }
      } elsif (ref($section) eq "HASH") {
         foreach my $el (sort keys %$section) {
            my $v = $section->{$el};
            if (ref($v) eq "HASH") {
               $out1 .= "    <ELEMENT ID=\"$el\">\n";
               foreach my $el2 (sort keys %$v) {
                 $out1 .= &value($el2, $v->{$el2});
               }
               $out1 .= "    </ELEMENT>\n";
            } elsif (ref($v) eq "ARRAY") {
               $out1 .= "    <ELEMENT ID=\"$el\">\n";
               for ($y = 0; $y <= $#$v; $y++) {
                 $out1 .= &value($y, $v->[$y]);
               }
               $out1 .= "    </ELEMENT>\n";
            } else {
               $out1 .= &value($el, $v);
            }
         }
      } else {
         $out1 .= "  <VALUE>$section</VALUE>\n";
      }
      my $footer .= "  </" . uc($sec) . ">\n";
      $out .= "$head$out1$footer" if ($out1);
    }
    return $out;
}


sub value {
  my($id, $val) = @_;
  $val = Client->xmlEncode($val);
  return "<VALUE ID=\"$id\">$val</VALUE>\n";
}


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;
}


sub copyTopo {
  my($class, $from, $to) = @_;
  my $D = System->get_home() . "/DATA/topo";
  my $l;

  my $from1 = "$D/$from";
  $from1 =~ /(.+)/;    #TAINT
  $from = $1;

  my $to1 = "$D/$to";
  $to1 =~ /(.+)/;
  $to = $1;

  if (open(R, $from) && open(W, ">$to")) {
    while ($l = <R>) {
       print W $l;
    }
    close(R); close(W);
    return undef;
  } else {
    return $!;
  }
}


#
# 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($D) = System->get_home() . "/DATA/topo";
  my $renv = System->get_renv();
  my $clear_topo_ts = $renv->{clear_topo_timestamp};

  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->print2("Cannot read topo $host0");
          next;
       }
       $to1  = $VAR1;
       my $all_hosts = $to1->[0];
       my $first_host;
       foreach my $h (keys %$all_hosts) {
          $first_host = $h; last;
       }
       if ($first_host) {
         my $first_host_info = $all_hosts->{$first_host}{info};

         if ($clear_topo_ts && exists $first_host_info->{clear_topo_timestamp} &&
            $clear_topo_ts != $first_host_info->{clear_topo_timestamp}) {
           next;
         }
       }
       $class->removeHubs($to1);
       if ($topoM) {
          $class->merge($topoM, $to1, $name);
       } else {
          $topoM = $to1;
       }
    } else {
       #Debug->print0("Cannot find topo $host0");
    }
  }
  if ($topoM) {
    $topoM->removeDuplicates();
    $topoM->crossConnect();
    $topoM->seCrossConnect() if ($renv->{solution} eq "N");
    # Run cross Connect one more time fixes bug 6389586
    $topoM->crossConnect();

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

# read a MERGE- topo or create one if missing or old
# MERGE-MASTER always include all agents.

sub list_ {
  my($class, $host) = @_;
  my $hlist;
  if ($host eq "MERGE-MASTER") {
     my $Config = PDM::ConfigFile->read();
     $hlist   = $Config->hostsList(); # all slaves + master
  } else {
     my @hlist0 = $class->host2list($host);
     $hlist = \@hlist0;
  }
  return $hlist;
}


# TO->mergeTopos('MERGE-MASTER');
# will create the merged topology and save it.
#
sub mergeTopos {
  my($class, $host) = @_;
  my(@lines, $hlist, $layout) ;

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

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

sub archive {
  my($to, $host) = @_;

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

  my $current = Util->deserialize("topo/$host"); 
  if ($current && $current->toXML() ne $to->toXML()) {
    my $A = System->get_home() . "/DATA/topohist";

    my $F  = "$D/$host";
    my $AF = "$A/B_${host}.$today";
    rename $F, $AF;

    opendir(D, $A);
    my @L = readdir(D); closedir(D);
    my($cnt);
    foreach my $l (reverse sort @L) {
       next if ($l !~ /^B_${host}/);
       unlink "$A/$l" if ($cnt++ > 10);
    }
  }
}

# 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} ;
  }
  if (substr($host,0,6) eq "MERGE-" && !-f "$D/$host" ){
    $class->mergeTopos($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);
  if ($renv->{solution} eq "N") {
    Debug->print2("Running Discman..");
    ($err1, $stderr1, $d) = TO->getDiscman($host, $save, $hbaOnly);
  }

  my($incomplete, $trace);
  my $topo = TO->newTopo(); # empty topo

  if ($d && $#$d >= 0) {
    require "Discman.pm";
    ($trace, $incomplete)  = Discman->parse($topo, $d);
    if ($incomplete->{flag}) {
        $err1 .= "\n" . $incomplete->{err};
    }
    print $trace if (Debug->level() == 3);

  } elsif ($renv->{solution} eq "N") {
    require TO::HOST;
    TO::HOST->new($topo, $host, {});
  }
  $topo->[6]{system} = {sys_error => $err1, sys_stderr => $stderr1};

  return $topo if ($hbaOnly);

  my $mods = Modules->load("TO");
  my $errs;
  my $agent_err;
  my $host_skip = $renv->{solution} eq "N" ? "SP" : "HOST";
  Debug->print2("Running Topology modules..");
  foreach my $m0 (@$mods) {
     next if (substr($m0,0,2) eq "SE");
     next if ($m0 eq $host_skip);
     my $m = "TO::".$m0;
     if ($m->leaf() && $m->can("addFromConfig")) {
        $agent_err = $m->addFromConfig($topo, $Config);
        if($agent_err){
          $errs .= "$agent_err in module $m addFromConfig\n";
        }
     }
  }
  TO::SE->addFromConfig($topo, $Config);
  TO::SE2->addFromConfig($topo, $Config);

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

  Debug->print2("Running crossConnect..");
  $topo->connectAll();

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

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

  return $topo;
}

# REMOVE FROM THE TOPOLOGY DEVICES THAT ARE NOT IN THE CONFIGFILE
# $dev is optional
sub syncWithConfig {
  my($topo, $dev) = @_;

  my $switches = $topo->[1];
  my $storages = $topo->[2];

  if ($dev) { # remove only one device from the config.
     my $k = "$dev->{type}:$dev->{key}";
     delete $switches->{$k};
     delete $storages->{$k};
     # Must remove ports that are linked to this device as well
     
     for (my $x=0; $x <= 3; $x++) {
        my $section = $topo->[$x];
        foreach my $e (sort keys %$section) {
           my $dev2 = $section->{$e};
	   my $ports = $dev2->{port};
	   for (my $y = 0; $y <= $#$ports; $y++) {
	     if($ports->[$y] =~ /$dev->{key}/){
	       	 $ports->[$y] = undef;
	     }
	   }
        }
     }
     # Done removing
     return;
  }

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

  my(%K);
  foreach my $d (@$devices) {
     $K{"$d->{type}:$d->{key}"} = $d;
  }


  foreach my $s (keys %$switches) {
    next if ($s =~ /^hbaswitch/);
    if (!$K{$s}) {
       delete $switches->{$s};
    }
  }

  foreach my $s (keys %$storages) {
    next if ($s =~ /^hbastorage/);
    if (!$K{$s}) {
       delete $storages->{$s};
    }
  }
}



sub connectAll {
  my($topo, $skip_systems) = @_;
  my $renv = System->get_renv();

  $topo->crossConnect();     # complete the connections that you can
  return if ($skip_systems);
  if ($renv->{solution} eq "N" && $topo->seCrossConnect()) {
    $topo->crossConnect();
  }
}
  


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

# REMOVE hbaapi DEVICES THAT ARE ALREADY PRESENT AS non-hbaapi NODES
  my(%SAVE);
  foreach my $st (@$storages) {
    if ($st->type() eq "hbaapi") {
       $SAVE{$st->{info}{NodeWWN}} = $st;
    }
  }
  foreach my $st (@$storages) {
    if ($st->type() ne "hbaapi") {
      my $pi = $st->portInfo();
      foreach my $p (@$pi) {
         my $dev0 = $SAVE{$p->{NodeWWN}};
         # DELETE THE DEVICE AT $dev0
      }
    }
  }

  foreach my $sw (@$switches) {
    my $pi = $sw->portInfo();
    next if (index(",ve,sve,", "," . $sw->type() . ",") >= 0);
    my $sw_po = $sw->port();
    my($y,$x);
    for ($x=0; $x <= $#$pi; $x++) {
       my $link;
       if ($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); 
    }
  }
}



############################################################
#                   NOT USED
# 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 connect_ {
  my($node, $node_p, $sw, $x) = @_;

  return if ($node eq $sw);
  Debug->print25("Connecting " . $sw->name() . ":$x to " . $node->name() . ":$node_p ");
  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();
  my $cnt;
  my $internal_wwns;
  foreach my $st (@$storages , @$switches) {
    my $pi = $st->portInfo();
    if (substr($st->type(),0,2) eq "se") {
      # TRY TO CONNECT RACKS DIRECTLY TO HOSTS if ports of RACK point to an HBA.
      $internal_wwns .= $st->info('wwnList');
      my $pis = $st->portInfo();
      my $x;
      for ($x=0; $x <= $#$pis; $x++) {
        if (defined($pis->[$x]{PortWWN})) {
           my $found = &seToHost($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
                # Don't connect this port if it is already conected to a switch
                if( ($node->{port}[$node_p] !~ /switch/)  &&
                    ($node->{port}[$node_p] !~ /brocade/) &&
                    ($node->{port}[$node_p] !~ /mcdata/) 
                ){
                   connect_($node, $node_p, $st, $x);
                   $cnt++;
                }
             }
           }
        }
      }
    } else {
      my ($y, $found, $x);
      for ($x=0; $x <= $#$pi; $x++) {  # all ports of non rack device
        next if (!$pi->[$x]{PortWWN});
        foreach my $rack (@$storages) {
           next if (substr($rack->type(),0,2) ne "se"); # look for racks
           my $rack_pi = $rack->portInfo();
           my $found2;
           for ($y=0; $y <= $#$rack_pi; $y++) { # all ports of all racks
              if ($pi->[$x]{PortWWN} eq $rack_pi->[$y]{PortWWN} && !$rack_pi->[$y]{RemotePortWWN}) {
                 Debug->print25("seCross: Found port $pi->[$x]{PortWWN} of device " . $st->name() . " as port $y of rack " . $rack->name() ) ;
                 $found2 =1;
                 $cnt++;
                 $rack_pi->[$y]{LogicalPath} = $pi->[$x]{LogicalPath};
                 $rack_pi->[$y]{PhysPath}    = $pi->[$x]{PhysPath};
                 $rack_pi->[$y]{ProductID}   = $pi->[$x]{ProductID};
                 $rack_pi->[$y]{NodeWWN}     = $pi->[$x]{NodeWWN};
                 $rack_pi->[$y]{PortMode}    = $pi->[$x]{PortMode};
                 $rack_pi->[$y]{'Path State'}= $pi->[$x]{'Path State'};
                 $rack_pi->[$y]{Capacity}    = $pi->[$x]{Capacity};
                 $rack_pi->[$y]{host_type}   = $st->{info}{host_type};
                 $rack_pi->[$y]{dataHost}    = $pi->[$x]{dataHost};
                 $rack->{info}{host_type}    = "ib";
		 my $save = $st->{info}{name} . ":$x";
                 $rack->{port}[$y]           = $save;
                 my ($other_node, $other_port) = $to->nodeByName($save);
                 $other_node->{port}[$other_port] = $rack->name() . ":$y" if ($other_node);
                 if (exists $st->{volInfo}) {
                   my $vi = $st->{volInfo};
                   foreach my $e (keys %$vi) {
                      $rack->{volInfo}{$e} = $st->{volInfo}{$e};
                   }
                 }
                 last;
              }
           }
           # IF YOU FIND a PORT OF THIS STORAGE DEVICE 
           # IN THE wwnList OF THE RACK, REMOVE THE STORAGE DEVICE

           my $pwwn    = substr($pi->[$x]{PortWWN},4);
           my $wwnList = $rack->info('wwnList');
           if (!$found2 && index("$wwnList|", "$pwwn|") >= 0) {
               $found2 =1;
               Debug->print25("seCross: found port $pi->[$x]{PortWWN} of " . $st->id() . " in rack " . $rack->name() );
               $cnt++;
               if (exists $st->{volInfo}) {  # copy luns into rack
                 my $vi = $st->{volInfo};
                 $rack->{info}{host_type}    = "ib";
                 foreach my $e (keys %$vi) {
                    $rack->{volInfo}{$e} = $st->{volInfo}{$e};
                 }
               }
           }
           $found += $found2;
           last if ($found2);
        }
      }
      if ($found) {
        $to->deleteStorage($st->name());
      }
    }
  }
  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);
           $cnt++;
       }
    }
  }

# DELETE INTERNAL COMPONENTS OF RACKS

  my $switches = $to->switchList();
  foreach my $st (@$switches) {
      my $key = $st->key();
      if (index($internal_wwns, substr($key,2)) >= 0) {
        Debug->print25("seCross: Deleting switch " . $st->id() . " from topo($key)");
        $to->deleteSwitch($st->name());
      }
  }
  return $cnt;
}


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 && $pi->[$y]{RegisterName} ne 'scsi') {
         if ($pi->[$y]{PortWWN} eq $link && ($pi->[$y]{RegisterName} !~ /scsi/)) {

            $po->[$y] = $sw->name() . ":$port_no";
            $sw_po->[$port_no] = $ho->name() . ":$y";
            $done = 1; last;
         }
     }
     last if ($done);
  }
  return $done;
}

sub seToHost {
  my($hosts, $link, $sw, $port_no) = @_;
  my ($y, $done);
  my $sw_po = $sw->port();
  my $internal_wwns = $sw->info('wwnList');
  foreach my $ho (@$hosts) {
     my $pi = $ho->portInfo();
     my $po = $ho->port();
     for ($y=0; $y <= $#$pi; $y++) {
         if (($po->[$y] =~ /t3:$link:\d/) || ($po->[$y] =~ /6[01]20:$link:\d/) ) {
            $po->[$y] = $sw->name() . ":$port_no";
            $sw_po->[$port_no] = $ho->name() . ":$y";
            $done = 1; last;

         } elsif ($pi->[$y]{PortWWN} eq $link) {
            $po->[$y] = $sw->name() . ":$port_no";
            $sw_po->[$port_no] = $ho->name() . ":$y";
            $done = 1; last;
         }
     }
     last if ($done);
     # CONNECT $sw($port_no) TO an hbas of a HOST
     #for ($y=0; $y <= $#$po; $y++) {
     #   next if (!$po->[$y]);
     #   my($potype,$poy,$pop) = split(/\:/, $po->[$y]);

     #  if (index($internal_wwns, $poy) >= 0 && !$sw->{port}[$port_no]) {
     #      $po->[$y] = $sw->name() . ":$port_no";
     #      $sw_po->[$port_no] = $ho->name() . ":$y";
     #      $done = 1; last;
     #   }
     #}
  }
  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 "." || $l eq "MERGE-MASTER");
    push(@list,$l);
  }
  push(@list, "MERGE-MASTER");
  closedir(O);
  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");

       if ($sw->{$key}{volInfoHost} && !exists($SW->{$key}{volInfoHost})) {
          $SW->{$key}{volInfoHost} = {};
       }
       $err .= &merge0($SW->{$key}{volInfoHost},  $sw->{$key}{volInfoHost},  "$key:volInfoHost");

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

  foreach my $key (keys %$st) {
     my $node = $st->{$key};

     if (index("t3,6020,6120", $node->type()) >= 0  && (index($key, ".") < 0) ) { # skip t3 with wwn as name
         next;
     }
     if($key =~ /:0000000000000000/){
       Debug->print2("Skipping this key: $key\n");
       next;
     }
     my(@L);
     push(@L, $node->{info}{uniqueName});
     push(@L, $node->{info}{PortWWN});
     push(@L, $node->{info}{NodeWWN});

     my $RealTO_KEY;
     my $ST_hbasKey ;


     foreach my $wwn0 (@L) {
        next if (!$wwn0);
        my $key_list = $TO->keysByWWN($wwn0);
        foreach my $this_key (@$key_list) {
	  if($ST->{$this_key}{info}{type} !~ /^hbas/){
	     $RealTO_KEY = $this_key;
	  }else{
	    $ST_hbasKey = $this_key;
	    last;
	  }
	}
     }
     
     Discover->mergeWithHBADevice($TO, $node);
     my $TO_KEY = $RealTO_KEY || $key;   
     if (exists($ST->{$TO_KEY}))  {

       my $I1 = $ST->{$TO_KEY}{info};
       my $i1 = $st->{$key}{info};
       &host_array($I1, $i1);

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

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

     } elsif($st->{$key}{info}{type} !~ /^hbas/ && $ST_hbasKey){
       $ST->{$key} = $st->{$key};
       $st->{$key} = $ST->{$ST_hbasKey};
       $TO_KEY = $key;
       my $I1 = $ST->{$TO_KEY}{info};
       my $i1 = $st->{$key}{info};
       &host_array($I1, $i1);

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

       if ($st->{$key}{volInfoHost} && !exists($ST->{$TO_KEY}{volInfoHost})) {
          $ST->{$TO_KEY}{volInfoHost} = {};
       }
       $err .= &merge0($ST->{$TO_KEY}{volInfoHost},  $st->{$key}{volInfoHost},  "$TO_KEY:volInfoHost");
       delete($ST->{$ST_hbasKey});


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

sub mergeDevices {
  my($to, $ST, $st) = @_;

  my $I1 = $ST->{info};
  my $i1 = $st->{info};
  &host_array($I1, $i1);

  my $err = &merge0($ST->{portInfo}, $st->{portInfo}, "portInfo");

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

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


  if ($st->{diskInfo} && !exists($ST->{diskInfo})) {
     $ST->{diskInfo} = {};
  }
  $err .= &merge0($ST->{diskInfo},  $st->{diskInfo},  "diskInfo");
  return $err;
}

sub host_array2 {
 my($class, $I1, $i1) = @_;

 if (!$I1->{host}) {
     $I1->{host}      = $i1->{host};
     $I1->{host_type} = $i1->{host_type};
     return;
 }

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


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};
     }
  }
}


#
# remove 'HBASwitch' and 'HBAStorage' duplicate devices.
#
sub removeDuplicates {
  my($to) = @_;
  my($x);
  for ($x=1; $x <= 2; $x++) {  # 1 = switch, 2=storage
    my $devs = $to->[$x];
    foreach my $key (keys %$devs) {
       my($type, $key0) = split(/\:/, $key, 2);
       if ($type !~ /^hbas/) {
          my $node = $devs->{$key};
          my $dev = { type => $type, key => $key0, wwn => $devs->{$key}{info}{NodeWWN},
	                         wwns => $devs->{$key}{info}{wwns}};
          Discover->mergeWithHBADevice($to, $dev);
       }
    }
  }

}




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;
  my($type);
  if (!$file) {
     my $hosts = $Topo->[0];
     foreach my $t (keys %$hosts) {
        ($type, $file) = split(/\:/, $t, 2);
        last;
     }
  }

  my $outf = System->get_home() . "/DATA/topo/$file";
  $outf =~ /(.+)/;  # TAINT
  open(O, ">$1");
  $Data::Dumper::Indent = 1;
  $Data::Dumper::Deepcopy = 1;
  my($data) = Data::Dumper::Dumper($Topo);
  $data =~ s/^        /\t\t/gm;     
  print O $data;
  close(O);
  chmod 0664, $outf;
  if (substr($file,0,6) ne "MERGE-") {
    open(O, ">" .  System->get_home() . "/DATA/master_topo_marker");
    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;
  $file =~ /(.*)/;  # TAINT
  unlink $1;
}

#
# 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" . ($q->{HIST}? "hist":"") . "/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 0664, $F;
    print "\nSaved (" . 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_dot() < 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";
  $file .= "_hba" if ($hbaOnly);

  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"}, {cache => 0} ) ;
  
  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];
}
 
# 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, $kind) = $to->nodeByName($port);
    return ($target_node, $target_p, $kind);   # object, port#
  } else {
    return $port;                       # "type:key:port"
  }
}
  
  
# find all nodes that a node can see
# host will go to switch and storage, not to other hosts.
# storage will go to switches and hosts, not to other storage.
# works with a specific port or a node
# start0 = "host:diag245.central.sun.com"   : all hbas
# stast0 = "host:diag245.central.sun.com:10": port 10

sub visit {
  my($to, $start0) = @_;
  my($x);
  my $ix = index($start0, ":");
  my $ix2 = rindex($start0, ":");
  my ($start, $start_p, $kind) = $to->nodeByName($start0); # 0=host,1=switch,2=storage
  return undef if (!$start);
  my $ports = $start->port();
  my (@LIST);
  if ($ix != $ix2) {
     my $p = $ports->[$start_p];
     $to->visit0($p, \@LIST, $kind);
  } else {
     for ($x=0; $x <= $#$ports; $x++) {
        my $p = $ports->[$x];
        next if (!$p);
        $to->visit0($p, \@LIST, $kind);
     }
  }
  return \@LIST;
}

sub visit0 {
  my($to, $nodeport, $LIST, $kind) = @_;
  my($x); 
  my($target, $target_p, $target_k) = $to->nodeByName($nodeport);
  return if (!$target);
  return if (($kind == 0 && $target_k == 0) || ($kind == 2 && $target_k == 2));
  my $found = 0;
  foreach my $l (@$LIST) {
     if ($l eq $target) {
        $found = 1; last;
     }
  }
  if (!$found) {
    push(@$LIST, $target);
  } else {
    return;
  }
  return if ($target_k == 0 || $target_k == 2);
  my $ports = $target->port();
  for ($x=0; $x <= $#$ports; $x++) {
      my $p = $ports->[$x];
      next if (!$p);
      $to->visit0($p, $LIST, $kind);
  }
}

####################################################################
# FIND NODE USING SYNONYMS HASH
####################################################################
#

sub addSyn {
  my($to, $name , $value) = @_;
  return;
  my $syns = $to->[4];
  $syns->{$name} = $value;
}

#
# works for wwn and keys
#
sub nodeByKey {
  my($to, $key) = @_;

  return $to->nodeByWWN($key);

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

sub nodeBySynonym {
  my($to, $key, $n) = @_;
  my $exists;
  if (ref($key)) { # this is a device
    my $dev = $key;
    foreach my $wwn ('wwn', 'wwn2', 'wwn3', 'wwn4') {
       $exists = $to->nodeByWWN($dev->{$wwn});
       return $exists if ($exists);
    }

    my @list = split(/,/, $dev->{wwns});
    foreach my $wwn (@list) {
      $exists = $to->nodeByWWN($wwn);
      return $exists if ($exists);
    }
    return undef;

  } else {
    return undef if (!$key);
    return $to->nodeByWWN($key, $n);
  }
}


# return a list of nodes that have this wwn 
sub nodesByWWN {
  my($to, $wwn) = @_;
  my($x, @OUT, $done);
  return [] if (!$wwn);

  for ($x=0; $x <= 3; $x++) {
    my($section) = $to->[$x];

    foreach my $name (keys %$section) {
       my $s = $section->{$name};
       my($type, $key) = split(/\:/, $name ,2);
       if ($key eq $wwn || $wwn eq $s->{info}{uniqueName} || $name eq $wwn || $s->{info}{NodeWWN} eq $wwn || $s->{info}{wwns} eq $wwn) {
          if (index($done, $name) < 0) {
             push(@OUT, $section->{$name});
             $done .= $name;
          }
       }
       my $pi = $s->{portInfo};
       my($y);
       for ($y=0; $y <= $#$pi; $y++) {
          my $p = $pi->[$y];
          if ($p->{LocalPortWWN} eq $wwn ) {
             if (index($done, $name) < 0) {
               push(@OUT, $section->{$name});
               $done .= $name;
             }
          }
       }
    }
  }
  return \@OUT;

}
# return a list of nodes that have this wwn 
sub keysByWWN {
  my($to, $wwn) = @_;
  my($x, @OUT, $done);
  return [] if (!$wwn);

  for ($x=0; $x <= 3; $x++) {
    my($section) = $to->[$x];

    foreach my $name (keys %$section) {
       my $s = $section->{$name};
       my($type, $key) = split(/\:/, $name ,2);
       if ($key eq $wwn || $wwn eq $s->{info}{uniqueName} || $name eq $wwn || $s->{info}{NodeWWN} eq $wwn || $s->{info}{wwns} eq $wwn) {
          if (index($done, $name) < 0) {
             push(@OUT, $name);
             $done .= $name;
          }
       }
       my $pi = $s->{portInfo};
       my($y);
       for ($y=0; $y <= $#$pi; $y++) {
          my $p = $pi->[$y];
          if ($p->{LocalPortWWN} eq $wwn ) {
             if (index($done, $name) < 0) {
               push(@OUT, $name);
               $done .= $name;
             }
          }
       }
    }
  }
  return \@OUT;

}


#
# TRY TO FIND A NODE BY LOOKING AT KEYS, NodeWWN and LocalPortWWN for a certain WWN
# Called BY EVERYBODY NOW.
#
sub nodeByWWN {
  my($to, $wwn, $n) = @_;

  return wantarray ? () : undef if (!$wwn);

  my($x);
  for ($x=0; $x <= 3; $x++) {
    my($section) = $to->[$x];

    foreach my $name (keys %$section) {
       my $s = $section->{$name};
       my($type, $key) = split(/\:/, $name ,2);
       if ($key eq $wwn || $name eq $wwn || $s->{info}{NodeWWN} eq $wwn || $s->{info}{wwns} eq $wwn) {
          return wantarray ? ($section->{$name}, undef, $x) : $section->{$name};
       }
       my $pi = $s->{portInfo};
       my($y);
       for ($y=0; $y <= $#$pi; $y++) {
          my $p = $pi->[$y];
          if (substr($p->{LocalPortWWN},$n) eq substr($wwn,$n) ) {
             return  wantarray ? ($section->{$name}, $y, $x) : $section->{$name};
          }
       }
    }
  }
  return undef;
}


####################################################################


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, $x);
       } 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;
