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

#  my $State  = State->read();
#  my $comps  = $State->hash();
#  $State->ReadLock();
#  foreach my $e (keys %$comps) {
#    my $comp = $comps->{$e};
#    foreach my $topic (keys %$comp) {

# ALARM RECORD FORMAT:
#  0 : Severity
#  1 : Date Event-Descrition
#  2 : Name of Device
#  3 : Availability (0/1)
#  4 : summary of Event fields in 'attribute=value|' format
#  5 : Aggregate flag.
#  6 : list of aggregated events (list of events_id)
#  7 : acknowledge flag used in new gui
#  8 : arguments of the Event-Descrition for i18n business


use Util;
#use Time::HiRes qw (usleep);
use strict;
use System;
use Util::Http;
use Data::Dumper;
use Debug;
use SE;
use RasDB;
use base 'RasDB';

use vars qw($state_changed $STATE );

#  0 = normal / event
#  1 = warning
#  2 = error
#  3 = down
#  5 = minor (not supported)


sub sev_gifs {
  my $G = Labels->read();

  return [   "al_ok.gif   width=13 alt=\"[$G->{notice}]\"",
             "al_alert.gif width=13 alt=\"[$G->{warning}]\"",
             "al_crit.gif  width=13 alt=\"[$G->{error}]\"",
             "al_down.gif  width=13 alt=\"[$G->{down}]\"",
             "",
             "al_caution.gif width=13 alt=\"[$G->{minor}]\"",
         ];
} 

sub sev_labels {
  my $G = Labels->read();
  my @L = ($G->{notice},$G->{warning},$G->{error},$G->{down},undef,$G->{minor});
  return \@L;
}

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

  $STATE = $class->SUPER::new("ALARMS");
  return $STATE;
}

#
# read state file and push to master, must write first.
# NOT USED ANYMORE 
sub push {
  my($class, $master, $force) = @_;
  require Util::Http;
  $master = Util->findMaster;

  my($renv) = System->get_renv();
  my($F) = System->get_home() . "/DATA/state/" . $renv->{hostname} . ".slave";
  $force = 1 if (-f $F);

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

  open(O, $F);
  my(@a)  = <O>; close(O);
  Debug->print2("  State: push State to master");

  my($err, $ans)  = Util::Http->appendFile($master, "state/$renv->{hostname}.push",
                              join("", @a), 20);

  if ($ans !~ /OK/) {
     Debug->errNoRepeat(HTTP_STATE => undef, 2, "$err: $ans");
  } else {
     unlink $F;
  }
}

sub init {
  my($class) = @_;
  my($master) = Util->findMaster;
  my($renv)   = System->get_renv();
  my $host    = $renv->{hostname};

}


sub clean_topic {
  my($class, $topic) = @_;

    if ($topic eq "e") {
      $topic = "Enclosure";
    } elsif ($topic eq "ib" ) {
      $topic = "InBand";
    } elsif ($topic eq "oob" ) {
      $topic = "OutOfBand";
    }
  return $topic;
}


sub read_sev_summary {
  my($class) = @_;
  open(O2, System->get_home() . "/DATA/health_sev_summary");
  my $l = <O2>;
  chomp($l);
  my @SEV = split(/\|/, $l);
  if (wantarray) {
    my(@stats) = stat(O2);
    close(O2);
    return (\@SEV, Util->get_today($stats[9]));
  } else {
    close(O2);
    return \@SEV;
  }
}

sub save_sev_cache {
  my($class, $vals) = @_;

  $vals = "0|0|0|0" if (!$vals);
  open(O, ">" . System->get_home() . "/DATA/health_sev_summary");
  print O "$vals\n";
  close(O);
}


# Only count 1 per device.

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

  my $State = State->read();
  my $renv  = System->get_renv();
  my $Comp  = $State->{hash};
  my (@SEV, %MAP);

  $State->ReadLock();
  foreach my $q (keys %$Comp) {
     my($t, $key) = split(/\:/, $q);
    # next if ($t eq $renv->{solution});
     my $comp = $Comp->{$q};
     foreach my $topic (keys %$comp) {
       next if ($comp->{$topic}[5] < 0);
       my $sev = int($comp->{$topic}[0] + 0.5);
       $SEV[$sev]++ if ($sev > 0);

     }
  }
  $State->UnLock();

  $class->save_sev_cache("$SEV[0]|$SEV[1]|$SEV[2]|$SEV[3]");

  return [ $SEV[0],$SEV[1],$SEV[2],$SEV[3] ];
}


sub compare {
  my($k, $comp, $DB) = @_;
  if ($comp->[0] > $DB->{$k}[0]) {
     $DB->{$k} = $comp;
  } elsif ($comp->[0] == $DB->{$k}[0]) {
     if ($comp->[1] gt $DB->{$k}[1]) {
        $DB->{$k} = $comp;
     }
  }
}
#
# sends a hash with the highest severity problem for each component
# return $DB : $DB->{"t3:key"} = [highest-sev,desc];
#
sub getComponentState {
  my($class, $includeLinks) = @_;

  my $State = State->read();
  my $Comp  = $State->{hash};
  my %DB = ();

  $State->ReadLock();
  foreach my $q (keys %$Comp) {
     my $comp = $Comp->{$q};
     foreach my $topic (keys %$comp) {
       my $sev = int($comp->{$topic}[0] + 0.5);
       if ($sev >= 1) {
         my($t, $n, $p) = split(/:/, $q);
         my $k = "$t:$n";
         &compare($k, $comp->{$topic}, \%DB);
       }
     }
  }
  $State->UnLock();
  return \%DB;
}

# RACKS
# event with highest severity wins
sub slotCompare {
  my($k, $comp, $sev, $DB) = @_;

  if (!exists($DB->{$k}) ) {
     $DB->{$k} = [@$comp];
     $DB->{$k}[0] = $sev;

  } elsif ($sev > $DB->{$k}[0] ) {
     $DB->{$k} = [@$comp];
     $DB->{$k}[0] = $sev;

  } elsif ($sev == $DB->{$k}[0] ) {
     if ($comp->[1] gt $DB->{$k}[1]) {
        $DB->{$k} = [@$comp];
     }
  }
}

sub slotStatus {
  my($class, $devs) = @_;
  my %STATUS;
  my $State = State->read();
  my $Comps = $State->{hash};

  $State->ReadLock();

  foreach my $key (keys %$Comps) {
    my $comps = $Comps->{$key};
    foreach my $topic (keys %$comps) {
      my $comp = $comps->{$topic};
      my $sev = $comp->[0];
      next if ($comp->[5] < 0); # skipp aggregate events
      &slotCompare($key, $comp, $sev, \%STATUS);
    }
  }
  $State->UnLock();
  return \%STATUS;
}

sub note {
  my($class, $Comp, $key, $topic, $note) = @_;

  my $el = $Comp->{$key};
  return if (!$el);
  my $al = $el->{$topic};
  my $ack = $al->[7] || {};
  $ack->{note}  = $note;
  $el->{$topic}[7] = $ack;
  $Comp->{$key} = $el;
}

sub ack {
  my($class, $Comp, $key, $topic, $flag, $login) = @_;

  my $el = $Comp->{$key};
  return if (!$el);
  my $al = $el->{$topic};
  my $ack = $al->[7] || {};
  $ack->{ack}   = $flag;
  $ack->{login} = $login;
  $el->{$topic}[7] = $ack;
  $Comp->{$key} = $el;
}

sub delete {
  my($class, $Comp, $key, $topic) = @_;

  my $el = $Comp->{$key};
  return if (!$el);
  delete $el->{$topic};
  $Comp->{$key} = $el;
}



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

  my $State = State->read();
  my $Comp  = $State->{hash};
  my @ERR;
  $State->ReadLock();
  foreach my $q (sort keys %$Comp) {
     my $comp = $Comp->{$q};
     foreach my $topic (keys %$comp) {
       my $sev = int($comp->{$topic}[0] + 0.5);
       next if ($sev < 1);
       my $el = $comp->{$topic};
       #                      key        desc       name      avail  ev_summ
       push(@ERR, ['C', $sev, "$q:$topic", undef, $el->[1] , $el->[2], $el->[3], $el->[4]]);
     }
  }
  $State->UnLock();
     
  return \@ERR;
}

sub current {
  my($class) = @_;
  return $class->read();
}

#
# read from a state file on a foreign host
# CANNOT WORK ANYMORE, NEED TO REPLACE

sub readFromDevice {
  my($class, $ip, $filename) = @_;
  my $VAR1 = {};
  my($err, $rc) = Util::Http->readFile($ip, "/state/$filename");
  eval $rc;
  bless($VAR1, 'State');
  return $VAR1;
}
  
# jive the health with the devices/hosts.
# delete from ALARMS.db entries that have no parent present.
# devs and hosts are optional.

sub cleanState {
  my($class, $devs, $hosts) = @_;
  my($renv);
  if (!$devs) {
    ($renv, $devs, $hosts) = PDM::ConfigFile->read();
  } else {
     $renv = System->get_renv();
  }
  my $type = $renv->{solution} eq "N" ? "host" : "sp";
  RasDB->reset("ALARMS");
  my $State = State->read();
  my $Comp  = $State->{hash};
  my %KEY;
  foreach my $d (@$devs) {
     $KEY{"$d->{type}:$d->{key}"} = 1;
  }
  $KEY{"$type:$renv->{hostname}"} = 1;
  $KEY{"$type:" . System->hostid()} = 1;

  foreach my $d (@$hosts) {
     $KEY{"host:$d->{hostname}"} = 1;
     $KEY{"host:$d->{hostid}"}   = 1 if ($d->{hostid});
  }
  my $write;
  my @ERR;
  $State->Lock();
  #
  # Delete children that have no parents
  #
  my %SAVE;
  foreach my $el (sort keys %$Comp) {
     if (!(exists $KEY{$el}) && (substr($el,0,4) ne "hbas") ) {
       Debug->print2("  State: delete comp no key");
       delete $Comp->{$el};
     } else {
       my $comp = $Comp->{$el};
       foreach my $topic (keys %$comp) {
          my $c = $comp->{$topic};
          if ($c->[5] > 0) {  # save parent list
            my $org = State->eventHash($c->[4]);
            $SAVE{$org->{EID}} = 1;  # save parent EID
          }
       }
     }
  }
  foreach my $el (sort keys %$Comp) {
     my $comp = $Comp->{$el};
     my $DEL;
     foreach my $topic (keys %$comp) {
        my $c = $comp->{$topic};
        if ($c->[5] == -1) {         # child
          my $org = State->eventHash($c->[4]);
          if (!$SAVE{$org->{Parent}}) {
	     Debug->print2("  State: delete comp");
             delete $comp->{$topic}; $DEL++;
          }
        }
     }
     if ($DEL) {
        $Comp->{$el} = $comp;
     }
  }
  $State->UnLock();
}

sub convert {
  my($class, $host) = @_;
  my($D) = System->get_home() . "/DATA/state";
  my($renv) = System->get_renv();
  $host = $renv->{hostname} if (!$host);

  my($VAR1, @a);
  if (open(O, "$D/$host")) {
    @a = <O>; 
    close(O);
  }

  eval "@a";
  return if (!$VAR1);

  my $db = RasDB->new("ALARMS");
  my $ST2 = $db->hash();
  my %ST3;

  my $cc = $VAR1->{C};
  my $ll = $VAR1->{L};
  foreach my $k (keys %$cc) {
      my @a = split(/\:/, $k);
      $ST3{"$a[0]:$a[1]"}{$a[2]} = $cc->{$k};
  }

  $db->Lock();
  foreach my $k (keys %ST3) {
     $ST2->{$k} = $ST3{$k};
  }
  $db->UnLock();
  #unlink "$D/$host";
}

#
#  read and new are the same but read reads slaves pending events
#  $State = State->read();
#  returns (hash, handle) or just hash

sub read {
  my($class, $host) = @_;
  my($l);
  my $master = Util->findMaster();
  my $State  = $class->new();
  return $State ;
}

sub clearAll {
  my($class, $sev, $pattern) = @_;
  my $State  = $class->new();
  my $ST2    = $State->{hash};

  $State->Lock();

  foreach my $el (keys %$ST2) {
     delete $ST2->{$el};
  }
  $State->UnLock();
  $class->save_sev_cache();
  SE->vmcfg();
}

sub clearAllComponents {
  my($class, $sev, $pattern) = @_;
  my $State  = $class->new();
  my $ST2    = $State->{hash};
  my $LIST;
  my $deleted = 0;
  $State->Lock();

  foreach my $el (keys %$ST2) {
     my $comp = $ST2->{$el};
     my $upd;
     foreach my $topic (keys %$comp) {
         my $c = $comp->{$topic};
         my $s0 = int($c->[0] + 0.5);
         if ((!defined $sev && !defined $pattern) || ($s0 == $sev) || ($c->[3] =~ /$pattern/) ) {
            $LIST .= $c->[6] if ($c->[5] > 0);
            delete $comp->{$topic};
            $upd=1;
	    $deleted++;
         }
     }
     $ST2->{$el} = $comp if ($upd);
  }
  $State->clearSubEvents($LIST);
  $State->UnLock();
  $class->save_sev_cache();
  SE->vmcfg();
  return $deleted;
}

sub clearSubEvents {
  my($State, $list) = @_;
  my $deleted = 0;

  return 0 if (!$list);
  my $ST2    = $State->{hash};
  foreach my $el (keys %$ST2) {
     my $comp = $ST2->{$el};
     my $upd;
     foreach my $topic (keys %$comp) {
         my $c = $comp->{$topic};
         my $org = State->eventHash($c->[4]);
         if (index(",$list", ",$org->{EID},") >= 0) {
            delete $comp->{$topic};
            $upd = 1;
	    $deleted++;
         }
     }
     $ST2->{$el} = $comp if ($upd);
  }
  return $deleted;
}


sub ackComponent {
  my($class, $obj, $exact) = @_;
  my $State  = $class->new();
  my $ST2    = $State->{hash};
  my($t, $k, $topic) = split(/\:/, $obj);

  if (exists $ST2->{"$t:$k"}) {
      my $comp = $ST2->{"$t:$k"};
      $comp->[0] = - $comp->[0];
      $ST2->{"$t:$k"} = $comp;
  }
  $class->sev_summary();
  SE->vmcfg();
}


# CLEAR TOPIC
# obj = type:key:[topic]
# delete entry or delete topic if present

sub clearComponent {
  my($class, $obj, $exact) = @_;
  my $State  = $class->new();
  my $ST2    = $State->{hash};
  my $LIST;
  my($t, $k, $topic) = split(/\:/, $obj);
  $State->Lock();

  if (exists $ST2->{"$t:$k"}) {
    if (!$topic) {
      delete $ST2->{"$t:$k"};

    } elsif ($exact) {
      my $comp = $ST2->{"$t:$k"};
      my $c = $comp->{$topic};
      $LIST .= $c->[6] if ($c->[5] > 0);
      delete $comp->{$topic};
      $ST2->{"$t:$k"} = $comp;

    } else {
      my $comp = $ST2->{"$t:$k"};
      foreach my $el (keys %$comp) {
         if (substr($el,0,length($topic)) eq $topic) {
            my $c = $comp->{$el};
            $LIST .= $c->[6] if ($c->[5] > 0);
            delete $comp->{$el};
         }
      }
      $ST2->{"$t:$k"} = $comp;
    }
  }
  $State->clearSubEvents($LIST);
  $State->UnLock();
  $class->sev_summary();
  SE->vmcfg();
}

# CLEAR DEVICE
sub clear {
  my($class, $obj, $tstamp) = @_;
  my $State  = $class->new();
  my $ST2    = $State->{hash};
  if (exists $ST2->{$obj}) {
    delete $ST2->{$obj};
  }
}

sub clearLink {
  my($class, $typekeyport ) = @_;
  my $State = $class->new();
  my $Comps = $State->{hash};
  my ($t,$k,$p) = split(/\:/, $typekeyport);
  if (exists $Comps->{"$t:$k"}) {
     my $comp = $Comps->{"$t:$k"};
     delete $comp->{"port.$p.fc_link"};
     $Comps->{"$t:$k"} = $comp;
  }
}
  

# return one fc_link entry

sub link {
  my($class, $typekeyport ) = @_;
  my $State = $class->new();
  my $Comps = $State->{hash};
  my ($t,$k,$p) = split(/\:/, $typekeyport);
  if (exists $Comps->{"$t:$k"}) {
     my $comp = $Comps->{"$t:$k"};
     foreach my $topic (keys %$comp) {
        if ($topic eq "port.$p.fc_link") {
           return $comp->{$topic};
        }
     }
  }
  return undef;
}


sub readEvent {
  my($class, $key0) = @_;
  my $comp;
  my $State = $class->new();
  my $Comps = $State->{hash};
  my($type, $key, $topic) = split(/\:/, $key0);

  if (exists $Comps->{"$type:$key"}) {
      $comp = $Comps->{"$type:$key"};
      return $comp->{$topic} || [];
  }
}
           
  


#      $c = $comps->{$cname};
#      $c->[0]   : sev
#      $c->[1]   : message
#      $c->[2]   : enclosure name
# should now be used with an argument

sub components {
  my($class, $typekey) = @_;
  my $comp;
  my $State = $class->new();
  my $Comps = $State->{hash};

  if ($typekey) {
    if (exists $Comps->{$typekey}) {
      $comp = $Comps->{$typekey};
    } else {
      $comp = {};
    } 
  } else {
    $comp = {};
    $State->ReadLock();
    foreach my $k (keys %$Comps) {
        my $c = $Comps->{$k};
        foreach my $topic (keys %$c) {
           $comp->{"$k:$topic"} = $c->{$topic};
        }
    }
    $State->UnLock();
  }
  bless ($comp, "ST_comp");
  return $comp;
}


#
#  CLEAR EVENTS: only called from GUI on MASTER
#
sub queue {
  my($class, $val) = @_;
  open(OO3, ">>" . System->get_home() . "/DATA/health_queue");
  print OO3 "$val\n";
  close(OO3);
}

#
# only writes local state table.
#
sub write {
  return ;
}


# ->saveState($cat, $name, $keyval,
#         Message::SEVERITY_DOWN, "Lost Communication");
#            4=notpresent, 3=down, 2=error,1=warn,0=ok
#  this runs on the host that has instrumentation agent running.
#  ALARMS DB:
#  0 = $sev
#  1 = "$date $description"
#  2 = $name
#  3 = $avail
#  4 = summary Hash
#  5 = aggregate count
#  6 = aggregate_list
#  7 = Acknowledge flag
#  8 = description Args
#

sub saveState {
  my($class, $cat, $keyval, $comp, $name, $sev, $mess, $avail, $ev, $arg) = @_;

  my $service = $arg->{service};
  my $summ;
  $cat = lc($cat);
  my $abs = $arg->{absolute};  # use sev as is, no comparison with current.
  $name = $keyval if (!$name);
  $comp = "e" if (!$comp);  # enclosure
  my $date    = Util->today("YMDH");
  my ($displayTopic, $descArgs);
  if ($ev) {
    $descArgs = $ev->value("DescArgs");
    my $etime = $ev->value('EventTime');
    $date     = substr($etime,0,4) . "-" . substr($etime,4,2) . "-" .
                substr($etime,6,2) . " " . 
		substr($etime,8,2) . ":" .  substr($etime,10,2) . ":" .
	        substr($etime,12,2);

    my $serv = "|Service=$service" if ($service);
    $summ = "EventType="  . $ev->value("EventType") . 
            "|Target="    . $ev->value("Target") .
            "|TargetName=". $ev->value("TargetName") .
            "|Caption="   . $ev->value("Caption") .
            "|Parent="    . $ev->value("Parent") .
            "|Solid="        . $ev->value("SolutionId") .
            "|Component="    . $ev->value("Component") .
            "|DisplayTopic=" . $ev->value("DisplayTopic") .
            "|Actionable="   . $ev->value("Actionable") .
	     $serv .
            "|GridCode="  . $ev->value("GridCode") .
            "|SourceIP="  . $ev->value("SourceIP") .
            "|EID=".        $ev->value("EventId") .
            "|Severity="  . $ev->value("Severity") .
            "|PriorState="    . $ev->value("PriorState") .
            "|CurrentState="  . $ev->value("CurrentState");

    my $org = State->eventHash($ev->value("OriginalEvent"));
    if ($org->{Target}) {
      $summ .= "|OrigEventType="  . $org->{EventType}  .
               "|OrigTarget="     . $org->{Target}     .
               "|OrigTargetName=" . $org->{TargetName} .
               "|OrigCaption="    . $org->{Caption};
    } else {
      $summ .= "|CurrentValue="  . $ev->value("CurrentValue");
    }
  }

  my($master) = Util->findMaster;
  my($D)      = System->get_home() . "/DATA/state";
  my($renv)   = System->get_renv();
  my $host    = $renv->{hostname};

  if ($master) { # a slave
     $state_changed = 1;
     open(O, ">>$D/$host.slave");
     print O "C\t$cat:$keyval:$comp\t$sev\t$date\t$mess\t$name\t$avail\t$summ\n";
     close(O);
  }
  my $State = $class->new();
  my $Comp  = $State->{hash};
  my $k = "$cat:$keyval";
  my $v = exists $Comp->{$k} ? $Comp->{$k} : {};

  my $sev0 = 0;
  my $agg0 = 0;
  if (exists $v->{$comp}) {
     $sev0 = $v->{$comp}[0];
     $agg0 = $v->{$comp}[5];
  }
  my $agg   = $ev->value("Aggregate");
  my $discl = $ev->value("Disclaimer");

  # Update the ALARMS.db if the sev is higner or, in cases where the severity is lower
  # but the existing entry is aggegated (<0) and the new entry
  # is an aggregation (>0), update the ALARMS also.
  # This should not happen much except for bizarre fault signatures where the leader
  # is lower severity than the aggregees.
  if ( $sev >= $sev0 || $abs || ($agg > 0 && $agg0 < 0) ) {
    $state_changed = 1;
    $mess =~ s/[\|,\n]/ /g;
    $v->{$comp} = [$sev,"$date $mess", $name, $avail, 
                   $summ, $arg->{aggregate}, $arg->{agg_list}, undef, $descArgs, $discl];
    $Comp->{$k} = $v;
    # remove alarms against the previous agg_list
  }
}

sub eventHash {
  my($class, $comp, $del) = @_;
  my (%H, @L);
  if ($del) {
    @L= split(/$del/, $comp);
  } else {
    @L= split(/\|/, $comp);
  }
  foreach my $el (@L) {
    my $ix = index($el, "=");
    $H{substr($el,0,$ix)} = substr($el,$ix+1) if ($ix > 0);
  }
  return \%H;
}


sub isChanged {
  return $state_changed;
}

package ST_comp;

# find other alerts by skipping [excludes]

sub other {
  my($comps, $exclude, $other, $otherInfo) = @_;
  foreach my $el (keys %$comps) {
     my $skip=0;
     foreach my $ex (@$exclude) {
        if ($el =~ /$ex/) {
           $skip= 1; last;
        }
     }
     next if ($skip);
     $comps->summ($el, $other, $otherInfo);
  }
}


sub summ {
  my($comps, $el, $other, $otherInfo) = @_;

  my $val = $comps->{$el};
  if (!defined($$other)) {
     if ($val->[0] >= 0) {
        $$other     = $val->[0];
        $$otherInfo = $val->[1];
     }
  } elsif ( ($val->[0] > $$other)  ||
            ($val->[0] == $$other && ($val->[1] gt $$otherInfo)) ) {
     $$other     = $val->[0];
     $$otherInfo = $val->[1];
  }
}



#  a star at the end means exact match or '.' and more

sub value {
  my($comp, $val, $ix) = @_;

  if (substr($val,-1) eq "*") {
     my $current = [-2];
     my $found;
     chop($val);
     my $l2 = length($val);
     foreach my $el (keys %$comp) {
         if ($el eq $val || substr($el,0,$l2+1) eq "$val.") {
             my $f = $comp->{$el};
             if ($f->[0] > $current->[0]) {
               $current = $f ; $found = 1;
             }
         }
     }
     if ($found) {
        if (defined($ix)) {
          return $current->[$ix];
        } else {
          return $current;
        }
     }   
     if (defined($ix)) {
       return undef;
     } else {
       return [];
     }
  } 

  if (exists($comp->{$val})) {
    if (defined($ix)) {
      return $comp->{$val}[$ix];
    } else {
      return $comp->{$val};
    }
  }
  if (defined($ix)) {
    return undef;
  } else {
    return [];
  }
}





1;



