package FSA;
use System;
use PDM;
use strict;
use Events;
use TO;
use Message;
use Grid;
use FSA::POLICIES;

#  $Id: FSA.pm,v 1.15 2004/10/14 22:25:05 mckenney Exp $

use vars qw($EXCLUDE);
$EXCLUDE = ",AuditEvent,DiscoveryEvent,Statistics,backup,topoBackup,".
           "PatchInfo,TopologyEvent,QuiesceStartEvent,QuiesceEndEvent,LocationChange,";

sub RUN {
  my($class, $TEST) = @_;
  my $renv = System->get_renv();
  if (!$renv->{fsa}) {
     Debug->print2("FSA is off.");
     return 1;
  }
  $DB::single = 1;
  CIM->version("1.1");

  my($renv, $devs, $hosts, $notifs, $Config) = PDM::ConfigFile->read();
  my $topo =  TO->readExistingTopo("MERGE-MASTER") ||  TO->readExistingTopo();
  Debug->print2("Running FSA...");

  $class->fsa_loop($Config, $topo, 'Device', { defaultAgg => 1} );

  if ($renv->{solution} ne "N") {
    $class->fsa_loop($Config, $topo, 'System');
  } else {
    $class->fsa_loop($Config, $topo, 'SAN');
  }
  return 1;
}

sub removeEmptyEvents {
  my($class, $elist) = @_;
  my @elist2;
  foreach my $el (@$elist) {
     push(@elist2, $el) if ($el);
  }
  return \@elist2;
}


sub fsa_loop {
  my($class, $Config, $topo, $pol_type, $arg) = @_;
  my $again = 1;
  my($cnt, $debug);
  while (--$again >= 0) {
     last if ($cnt++ > 4);
     my $fn = "groupBy$pol_type";
     # create groups of events that have no parent
     my $LIST = $class->$fn($Config, $topo);
     my @update_list;
     my $group_no;
     foreach my $l (@$LIST) {
       my $elist = $l->[0];
       my $nodes = $l->[1];
       if ($#$elist > 0) { # at least 2 events per group.
          $group_no++;
          bless( $elist, 'FSA::elist');
          $class->orderEvents($elist);

          $class->debug($elist, $pol_type, $group_no, $cnt);
          my($found_match, $new_elist, $sev, $ev, $pertains) = FSA::POLICIES->run(
             $pol_type, $Config, $topo, $nodes, $elist, $debug);

          if ($found_match) {
            if ($ev) {
              # $sev is the severity of the event that was selected to be the leader.
              # It may be one of the existing event or a synthesized event. If it's one
              # of the existing event, it's severity may be fractional for ordering 
              # reasons. The agg event should also have a fractional sev. to insure
              # that the event is written in the ALARMS.db. Normally, a sev >= to 
              # the current sev is needed for ALARMS to be updated.

              $class->newMessage($pol_type, $ev, $pertains, $new_elist, {severity => $sev} );
              push(@update_list, @$new_elist);
              $again=1;
            }
          } else {
            if ($arg->{defaultAgg}) {
              my($max_sev, $high_event) = $class->event_stats($elist);
              ($ev, $pertains) = FSA->newEvent($elist, $nodes, $high_event);
              $class->newMessage($pol_type, $ev, $pertains, $elist, {severity => $max_sev} );
              push(@update_list, @$elist);
            }
          }
       }
     }
     $class->update_children(\@update_list); # change severity and set parent 
  }
  return 1;
}

sub newMessage {
  my($class, $pol_type, $ev, $pertains, $elist, $arg) = @_;

  my($x, $agg_list, @LIST);
  for ($x=0; $x <= $#$elist; $x++) {
     my $ed      = $elist->[$x];
     my $ev      = $ed->instances(0);
     $agg_list .= $ev->value("EventId") . ",";
  }

  my $sd   = Events->sourceDetector({ event => $ev });
  my($category, $rest) = split(/\./, $ev->value("EventType"));
  my ($t_type, $t_key) = split(/\:/, $ev->value("Target"), 2);
  my $ev_id = { aggregate  => ($#$elist+1), 
                deviceName => $t_key,
                  pol_type => $pol_type, 
                  agg_list => $agg_list,
                  category => $category };

  my $ed    = Message->new({    id => $ev_id, 
                            severity => $arg->{severity} || $ev->value("Severity"),
                               state => [$ev->value("Component"), 0 ],
                           instances => [$ev, @$sd, $pertains] 
                              });

  my $parent_edoc_id = PDM->saveMessage($ed, undef, 1);

  foreach my $ed (@$elist) {
     $ed->id("new_parent", $parent_edoc_id);
  }
}

# update the database with parent and severity changes
#
sub update_children {
  my($class, $list) = @_;

  my $ED = RasDB->new("EDOCS");
  my $hash = $ED->hash();
  my %SEV = ( E => 2, W => 1, D => 3, I => 0 );
  $ED->Lock();

  foreach my $ed (@$list) {
     my $new_sev   = $ed->id('new_severity');
     my $new_parent= $ed->id('new_parent');
     if ($new_sev || $new_parent) {
        my $event_id  = $ed->id("edoc_id");
        my $db_ed     = $hash->{$event_id};  
        next if (!$db_ed);
        my $ev        = $db_ed->instances(0);
        if ($new_sev) {
           if ($new_sev eq "N") { # no event, hide with this flag
             $db_ed->id("delete_event", 1);
           } else { 
             $ev->setValue("Severity", $SEV{$new_sev});
           }
        }
        if ($new_parent) {
           $ev->setValue("Parent", $new_parent);
        }
        $hash->{$event_id} = $db_ed;
     }
  }
  $ED->UnLock();
}


# high_event is required but can be changed with values from $arg
#
sub newEvent {
  my($class, $elist, $targets, $high_event, $desc, $arg) = @_;

  my($cat, $key1, $rest)  = split(/\:/, $high_event->value("Target"));

  Grid->setCode($arg->{GridCode} || $high_event->value("GridCode"));
  my $id = PDM->getEventSequence(); 

  if (!$key1) {
     Debug->print2("FSA::newEvent: no key for this event, gridcode=" . 
                     $high_event->value("GridCode"));
     return ();
  }
        
  my $sev = exists $arg->{Severity}  ? $arg->{Severity}  : $high_event->value("Severity");
  my $act = exists $arg->{Actionable}? $arg->{Actionable}: $high_event->value("Actionable");

  my $ev = CIM::Instance->new('NWS_Event', [
             [ EventType   => $arg->{EventType}   || $high_event->value("EventType")  ],
             [ EventId     => $id       ],
             [ Target      => $arg->{Target}      || $high_event->value("Target")     ],
             [ TargetName  => $arg->{TargetName}  || $high_event->value("TargetName") ],
             [ MgmtLevel   => $high_event->value("MgmtLevel") ],
             [ Component   => $arg->{Component}   || $high_event->value("Component")  ],
             [ PriorValue  => $arg->{PriorValue}  || $high_event->value("PriorValue") ],
             [ CurrentValue=> $arg->{CurrentValue}|| $high_event->value("CurrentValue") ],
             [ Caption     => $arg->{Caption}     || $high_event->value("Caption")    ],
             [ DisplayTopic => $arg->{DisplayTopic} || $high_event->value("DisplayTopic")    ],
             [ SourceIP    => $arg->{SourceIP}    || $high_event->value("SourceIP")   ],
             [ Actionable  => $act          ],
             [ Severity    => $sev          ],
             [ Fault       => $arg->{Fault} ],
             [ Aggregate   => ($#$elist+1)  ], 
             [ Description => $desc || $high_event->value("Description")],
             [ Data        => $arg->{Data}  ],
                            ]);

  my $key = CIM::Key->new( ['NWS_System', Name  => $key1, 
                                    CreationClassName => 'NWS_System'] );
     
  my $pertains = CIM::Instance->new('NWS_EventPertainsToSystem', [
                     [ Event       => $ev  ],
                     [ Element     => $key ],
                     ]);
  return ($ev, $pertains, $arg);
}

# SKIP EVENTS THAT DON'T APPLY TO FSA
# OR ARE ALREADY PART OF A FAULT or AGGREGATION
#
sub skip {
  my($class, $ed, $pol_type) = @_;

  my $renv = System->get_renv();
  return 1 if ($ed->type() eq Message::TYPE_ALERT);
  my $ev  = $ed->instances(0);
  my $e0  = $ev->value('EventType');
  my $parent = $ev->value('Parent');
  return 1 if ($parent);
  my $agg    = $ev->value('Aggregate');
  if ($agg && $pol_type eq $ed->id("pol_type")) {
    return 1 ;
  }
  my($cat, $eventType, $rest) = split(/\./, $e0);
  return 1 if ($cat eq "agent");
  return 1 if (index($EXCLUDE, $eventType) >= 0);
  my $mgmtLevel = $ev->value("MgmtLevel");
  my $solid     = $ev->value("SolutionId");
  return 1 if ($solid && $renv->{solution} eq "N"); # already processed.
  return 1 if ($mgmtLevel eq "DS");                 # skip rack-level events for now.
  return 0;
}

#
# GROUP BY DEVICE TYPE:TARGET FOR FIRST ROUND
#
sub groupByDevice {
  my($class, $Config, $topo) = @_;

  my (@LIST, %ED);
  my $new = PDM->getMessages();
  foreach my $ed (@$new) {
      next if ($class->skip($ed, 'Device'));
      my $ev  = $ed->instances(0);
      my($type, $target1, $port) = split(/\:/, $ev->value('Target'));
      my $target    = "$type:$target1";

      push(@{$ED{$target}}, $ed);
  }

  foreach my $target (keys %ED) {
      push(@LIST, [ $ED{$target} , [$target] ]);
  }
  return \@LIST;
}

# GROUP WHAT IS LEFT AND AGGREGATION FROM PREVIOUS ROUND FOR SYSTEM PASS
#
sub groupBySystem {
  my($class, $Config, $topo) = @_;

  my (@LIST, %ED);
  my $new = PDM->getMessages();
  foreach my $ed (@$new) {
      next if ($class->skip($ed, 'System'));
      push(@{$ED{"system"}}, $ed);  # only one grouping for the complete system
  }

  foreach my $target (keys %ED) {
      push(@LIST, [ $ED{$target} , [$target] ]);
  }
  return \@LIST;
}


# FIND EVENTS THAT SHARE A FC-PATH FOR THE SAN PASS.
#
sub groupBySAN {
  my($class, $Config, $topo) = @_;
  my (@LIST, %ED);

  my $new = PDM->getMessages();
  foreach my $ed (@$new) {
    next if ($class->skip($ed, 'SAN'));
    my $ev  = $ed->instances(0);
    my $eventType  = $ev->value('EventType');
    my $target0   =  $ev->value('Target');
    my($type, $target1, $port) = split(/\:/, $target0);
    my $target = "$type:$target1";
    my $component =  $ev->value('Component');
    my $caption   =  $ev->value('Caption');

    if ($eventType =~ /^LinkEvent/) {
       push(@LIST, [ [ $ed ], [ "$target:$port", &nodeFrom($topo, "$target:$port")] ]);

       # logEvent that was identified as FC_PATH related
    } elsif ($eventType =~ /LogEvent/ && 
            ($caption eq "PATH" || substr($caption,0,7) eq "driver.") ) {
       push(@LIST, [ [ $ed ], [ $target, &nodesFrom($topo, $target)] ]);

    } elsif ($component =~ /^port.(\d+)$/ || $component =~ /^fcPort.(\d+)$/) {  # 
       my $port = $1;
       if ($type eq "host") {
         push(@LIST, [ [ $ed ], [ "$target:$port", &nodesFrom($topo, "$target:$port")] ]);
       } else {
         push(@LIST, [ [ $ed ], [ "$target:$port", &nodeFrom($topo, "$target:$port")] ]);
       }
    } else {
       if ($type eq "host") {
         push(@LIST, [ [ $ed ], [ $target, &nodesFrom($topo, $target)] ]);
       } else {
         push(@LIST, [ [ $ed ], [ $target] ] );
       }
    }
  }
  &merge(\@LIST);
  return \@LIST;
}


  
sub showList {
  my ($LIST, $label) = @_;
  my ($y,$x);
  for ($x=0; $x <= $#$LIST; $x++) {
     my $l = $LIST->[$x];
     my $elist = $l->[0];
     my $nodes = $l->[1];
     next if (!$nodes);
     print "$label-LIST $x\n";
     for ($y=0; $y <= $#$elist; $y++) {
       my $e = $elist->[$y];
       my $ev = $e->instances(0);
       print "      $y:" . $e->description() . ", et=" . $ev->value("EventType") . ", cap=" . 
                    $ev->value("Caption") . ", comp=" . $ev->value("Component") .
                    ", sev=" . $ev->value("Severity") . "\n";
     }
     print   "   Nodes:". join(", ", @$nodes) . "\n" if ($nodes);
     print "--------------------------\n";
  }
}

# all nodes visible from this target (port is optional)
#
sub nodesFrom {
  my($topo, $target) = @_;
  my @L;
  my $list = $topo->visit($target);
  foreach my $el (@$list) {
     push(@L, $el->name());
  }
  return @L;
}

# first nodeport visible from this nodeport
#
sub nodeFrom {
  my($topo, $nodeport) = @_;
  my $target_nodeport = $topo->nodeportTarget($nodeport);
  return ($target_nodeport);
}

sub merge {
  my($LIST) = @_;

  my ($x, $y);
  my $again = 1;
  my $loop = 0;
  while ($again && $loop++ < 100) {
     for ($x=0; $x <= $#$LIST; $x++) {
        my $E1 = $LIST->[$x];
        next if (!$E1);
        for ($y = $x+1; $y <= $#$LIST; $y++) {
           my $E2 = $LIST->[$y];
           next if (!$E2);
           my $int;
           if (($int = &intersect($E1->[1], $E2->[1])) ) {
              my $E1_elist = $E1->[0];
              my $E2_elist = $E2->[0];
              push(@$E1_elist, @$E2_elist);
              if ($E1->[2]) {
                #&uniq($E1->[1], $int);
                $E1->[1] = $int;
              } else {
                $E1->[1] = $int;
                $E1->[2] = 1;
              }
              $LIST->[$y] = undef;
              $again = 1;
           }
        }
     }
  }
}

sub uniq {
  my($l1, $l2) = @_;

  foreach my $el (@$l2) {
      my $found = 0;
      foreach my $el2 (@$l1) {
         if ($el2 eq $el) {
            $found = 1; last;
         }
      }
      if (!$found) {
         push(@$l1, $el);
      }
  }
}
#
# X:1 means enc X, port 1
# X   means enc X, all ports
#
sub intersect {
  my($E1, $E2) = @_;
  my ($x, $y);
  my @INT;
  foreach my $node1 (@$E1) {
     foreach my $node2 (@$E2) {
        my $l1 = length($node1);
        my $l2 = length($node2);
        my ($t1, $k1, $p1) = split(/\:/, $node1);  
        my ($t2, $k2, $p2) = split(/\:/, $node2);  
        if ($node1 eq $node2) {
           push(@INT, $node1);
        } elsif ($l1 < $l2 && substr("$node1:",0,$l1+1) eq substr($node2,0,$l1+1) ) {
           push(@INT, "$t1:$k1");

        } elsif ($l2 < $l1 && substr("$node2:",0,$l2+1) eq substr($node1,0,$l2+1) ) {
           push(@INT, "$t2:$k2");
        }
     }
  }
  return undef if ($#INT < 0);
  return \@INT;
}

sub swap {
  my($class, $elist, $x1, $x2) = @_;
  my $save = $elist->[$x1];
  $elist->[$x1] = $elist->[$x2];
  $elist->[$x2] = $save;
}

# RUN all rules from FSA/, stop if a new event is generated or
# a new event is identifned as the new leader.
#
sub rule_run {
  my($class, $Config, $topo, $targets, $elist) = @_;
  my($r);
  foreach my $type ('RuleSet1','T3Rules','DSPrules') {
    my $pk = "FSA::$type";
    require "FSA/$type.pm";
    for ($r=1; $r <= 100; $r++) {  # rules must start at 1, sequential
       my $r1 = "RULE$r";
       if ($pk->can($r1)) {
          my($ev, $pertains, $arg) = $pk->$r1($Config, $topo, $targets, $elist);
          if ($ev) {
           Debug->print2("FSA: Rule $pk->$r1 fired!");
           return ($ev, $pertains, "$pk->$r1", $arg);
          }
       } else {
         last;
       }
    }
  }
  return ();
}

sub exclude {
  my($class, $targets, $list) = @_;
  return 1 if ($#$targets > 0);
  my @T = split(/\:/, $targets->[0]);
  return 1 if (index(",$list,", ",$T[0],") < 0);
  return 0;
}




sub debug {
  my($class, $elist, $pol_type, $group_no, $iter) = @_;
  my $renv = System->get_renv();
  return if (!$renv->{fsa_debug});
  my($x);
  print "==================('$pol_type' group $group_no iteration $iter)===================\n";
  for ($x=0; $x <= $#$elist; $x++) {
    my $ed    = $elist->[$x];
    my $ev    = $ed->instances(0);
    my $sev   = $ed->severity2();
    print "severity2=$sev \n";
    foreach my $e ('EventType','Caption','Component','DisplayTopic','PriorValue','CurrentValue',
                 'Target','Description','Severity','Actionable', 'Aggregate') {
      print "$e=" . $ev->value($e) . "\n";
    }
    print "-------------------------------------\n";
  }
}




#  SUMMARY SECTION OF THE AGG. EVENT
#  order best first if all events are informational
#  else, order worst first.
#
sub orderEvents {
  my($class, $elist) = @_;
  my ($actionable);
  my $cnt = 0;
  my $agg_list; # ordered by severity
  my (%DATA,  $max_sev, $x, $high_event, @elist2);

  my $all_info = 1;
  for ($x=0; $x <= $#$elist; $x++) {
     my $ed      = $elist->[$x];
     my $ev      = $ed->instances(0);
     my $sev2    = $ed->severity2();
     $all_info   = 0 if (int($sev2+0.5) > 0);
  }
  for ($x=0; $x <= $#$elist; $x++) {
     my $ed      = $elist->[$x];
     my $ev      = $ed->instances(0);
     my $date    = $ev->value("EventTime");
     my $sev2    = $ed->severity2();
     my $prio    = $ed->id("priority"); # used to order logEvents.
     my $act     = $ev->value("Actionable");
     $date       = substr($date,4,10);
     $cnt++;
     my $order = ($sev2 * 100 + $prio) +30;
     $order += 50 if ($act eq "TRUE");
     my $order2 = $all_info ? $order : 999 - $order;
     $order2    = 0 if ($order2 < 0);

     $DATA{sprintf("%4.4d-%s-%d", $order2, $date, $x)} = 1;
     $elist2[$x] = $elist->[$x];
  }
  my $y=0;
  foreach my $x (sort keys %DATA) {
     my @s = split(/\-/, $x);
     $elist->[$y] = $elist2[$s[2]];
     $y++;
  }
}

sub event_stats {
  my($class, $elist) = @_;
  my($x, $max_sev, $actionable, $agg_list, $cnt, $high);
  $max_sev = -2;

  for ($x=0; $x <= $#$elist; $x++) {
     my $ed      = $elist->[$x];
     my $ev      = $ed->instances(0);
     my $act     = $ev->value("Actionable");
     $cnt++;
     $actionable = 1 if ($act eq "TRUE");
     my $sev2    = $ed->severity2();
     if ($sev2 > $max_sev) {
        $max_sev = $sev2; 
        $high    = $ev;
     }
  }
  return ($max_sev, $high);
}
  


sub wrap {
  my($desc, $space) = @_;
  my ($cnt, $out, $len);
  my @words = split(/\s/, $desc);

  foreach my $w (@words) {
     if ($len+length($w) > 60) {
       $out .= "\n$space"; $len = 0;
     }
     $out .= "$w "; $len += length($w);
  }
  return $out;
}

#sub RUN2 {
#  my $renv = System->get_renv();
#  my $OLDF = "previous_events";
#  my $new = PDM->getMessages();
#  my $old = Util->deserialize($OLDF);
#
#  if (!$old) {
#    Util->serialize($new, $OLDF);
#    return;
#  }
#  my $rules = Modules->load("FSA");
#  foreach my $rule0 (@$rules) {
#     my $rule = "FSA::$rule0";
#     if ($rule->can("RUN")) {
#        $rule->RUN( $old, $new);
#     }
#  }
#  my $new = PDM->getMessages();
#  my (@CURRENT, @OLD) ;
#  foreach my $e (@$old) {
#      push(@CURRENT, $e);
#  }
#  foreach my $e (@$new) {
#      if ($e->id("inAggregate") || $e->id("aggregate") ) {
#         push(@CURRENT, $e);
#      } else {
#         push(@OLD, $e);
#      }
#  }
#  Util->serialize(\@OLD, $OLDF);
#  PDF->setMessages(\@CURRENT);
#
#}

package FSA::elist;

# delete a set of events from an elist
# @list are the index of the event in the elist.

sub delete {
  my($elist, @list) = @_;
  my(@DEL, $x, $y, $del, @NEW);
  foreach $x (@list) {
     $DEL[$x] = 1;
  }
  for ($x=0; $x <= $#$elist; $x++) {
     my $ed =  $elist->[$x];
     if ($DEL[$x]) {
       PDM->deleteMessage($ed);
       $del++;
     } else {
       push(@NEW, $ed);
     }
  }
  for ($x=0; $x <= $#NEW; $x++) {
    $elist->[$x] = $NEW[$x];
  }
  $#$elist = $#$elist - $del;
}
  
sub desc {
  my($elist) = @_;
  my($x);
  for ($x=0; $x <= $#$elist; $x++) {
     my $ed = $elist->[$x];
     my $ev  = $ed->instances(0);
     my $sev = $ed->severity2();
     print "$x = ". $ev->value("EventType") . " " . $ev->value("Description") . "\n";
     print "     sev=$sev \n";
  }
}

1;

