# $Id: Revision.pm,v 1.13 2005/10/24 20:16:36 sbrooks Exp $
# Copyright 2004 Sun Microsystems, Inc., All Rights Reserved.
# Sun Proprietary/Confidential: For Internal Use Only
package Catalog::Revision;

use strict;
use base 'Catalog::Entry';

use Catalog::Message;
use Catalog::ReportClass;
use Data::Dumper;
use XML::LibXML;

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

  my $this = { "type"   => $type,
               "entry"  => "revision.xml",
					"dir"    => $dir,
					"msgs"   => new Catalog::Message($type, $dir)
				 };
  bless($this, "Catalog::Revision");
  return $this->loadEntry();
}

sub load {
  my ($this) = @_;

  my $parser = XML::LibXML->new();
  my $dom;
  eval {
	 $dom  = $parser->parse_file($this->{file});
  };
  if ((!$dom)||($@)){
	 $this->setError("Error parsing XML document element. $@");
	 return;
  }
  my $revision = $dom->getDocumentElement();

  my %rmap;
  foreach my $rev ($revision->findnodes("revision")){
	 my $revData = $this->_loadRevision($rev);
	 if ($revData){
		my $revClass = $revData->{class};
		my $ca = $rmap{$revClass};
		if (!$ca){
		  $ca = [];
		  $rmap{$revClass} = $ca;
		}
		push(@$ca, $revData);
	 }	
	
  }
  $this->{revClassMap} = \%rmap;
}

sub _loadRevision {
  my ($this, $rel) = @_;

  my $rev = { class    => $rel->getAttribute("class"),
				  comments => $rel->getAttribute("comments"),
				  patch    => "xxxxxx-xx"
				};
  foreach my $el ($rel->getChildNodes()){
	 if ($el->getType() == &XML::LibXML::ELEMENT_NODE() ){
		if ($el->getName() eq "current"){
		  $rev->{current} =
			 { property => $el->getAttribute("property"),
				value    => $el->textContent(),
				format   => $el->getAttribute("format")
			 };
		}
		elsif ($el->getName() eq "match"){
		  $rev->{match} = $this->_loadMatch($el);
		}
		elsif ($el->getName() eq "patch"){
		  $rev->{patch} = $el->textContent();
		}
	 }
  }
  return $rev;
}

sub _loadMatch {
  my ($this, $mel) = @_;

  my %match;
  foreach my $prop ($mel->findnodes("property")){
	 $match{$prop->getAttribute("name")} = $prop->textContent();
  }
  return \%match;
}

sub _doesMatch {
  my ($this, $rev, $comp) = @_;

  my $match = $rev->{match};
  foreach my $name (keys %$match){
	 my $mval = $match->{$name};
	 my $cval = $comp->getComponentProperty($name);
	 if (!($this->_matchValue($cval, $mval))){
		return undef;
	 }
  }
  return 1;
}

sub _matchValue {
  my ($this, $value, $exp) = @_;
  if ($value eq $exp) {
	 return 1;
  }
  else {
	return $value =~ $exp;
 }
}


# PASS, UPREV, DOWNREV, NI (not installed), ERR
sub _calcStatus {
  my ($this, $cur, $exp, $format) = @_;

  if (!$cur){
	 return 'NI';
  }
  if (!$format){
	 return $this->_compValue($cur, $exp);
  }

  if (($cur !~ /$format/) || ($exp !~ /$format/)){
	 return "ERR";
  }

  my @ca = ($cur =~ /$format/);
  my @ea = ($exp =~ /$format/);

  for (my $index = 0; $index <= $#ca; $index++){
	 my $comp = $this->_compValue($ca[$index], $ea[$index]);
	 if ($comp ne "PASS"){
		return $comp;
	 }
  }
  return 'PASS';
}

sub _compValue {
  my ($this, $cur, $exp) = @_;
  my $cval = $cur cmp $exp;
  if ($cval == 0){
	 return 'PASS';
  }
  elsif ($cval > 0){
	 return 'UPREV';
  }else {
	 return 'DOWNREV';
  }
}

sub enabled {
  my ($this) = @_;
  if ($this->{error}){
	 return 0;
  }
  return 1;
}

sub check {
  my ($this) = @_;

  my $out = undef;
  my $revMap = $this->{revClassMap};
  foreach my $classname (keys %$revMap){
	 my $crevs = $revMap->{$classname};
	 foreach my $rev (@$crevs){
		if (!$rev->{class}){
		  $out .= "Revision element missing required class attribute.\n";
		}
		if (!$rev->{current}){
		  $out .= "Revision element for $classname missing required current element.\n";
		}	
		my $current = $rev->{current};
		if ($current->{format}){
		  my $format = $current->{format};
		  my $value = $current->{value};
		  if ($value !~ /$format/){
			 $out .= "Expression mismatch for $classname " .
				$rev->{current}->{property} . " " .
				  $current->{value} . " =~ /" .
					 $current->{format} . "/\n";
		  }
		}	
		if ($rev->{comments}){
		  if ($rev->{comments} !~ /\@.*/){
			 $out .= "Revision comments for $classname " .
				$rev->{current}->{property} . " not localized.\n";
		  }
		}
	 }
  }
  return $out;
}

sub evaluateRevision {
  my ($this, $dev, $comp, $ocomp) = @_;

  my $revs = $this->{revClassMap}->{$comp->getClassName()};

  # If there is no entry for this type in the revision.xml file
  if ($#$revs < 0) {
     return undef;
  }
     
  # Loop for the number of entries in the revision.xml file for this type
  for(my $index = 0; $index <= $#$revs; $index++) {
	 my $rev = $revs->[$index];
	 if ($this->_doesMatch($rev, $comp)){
		my $cprop = $rev->{current}->{property};
		my $cval = $comp->getComponentProperty($cprop);
                my $oval;

		if ($ocomp){
		  $oval = $ocomp->getComponentProperty($cprop);
		  if ($oval eq $cval){
			 return undef;
		  }
		}

                my ($descArgs, $desc) = $this->{msgs}->getComponentDescription
                  ($comp,  $rev->{comments});
  
                my $event = { type        => $dev->{type},
  			      device      => $dev->{name} || $dev->{key},
  			      component   => $comp->getDisplayName(),
  			      description => $desc,
  			      className   => $comp->{className}
  			    };

		my $needed  = $rev->{current}->{value};
		my $format  = $rev->{current}->{format};
		
		$event->{status} = $this->_calcStatus($cval, $needed, $format);
		$event->{curr_version}     = $cval;
		$event->{prev_version}     = $oval;
		$event->{expected_version} = $needed;
		$event->{current_patch}    = "N/A";
		$event->{expected_patch}   = $rev->{patch};
		if (!$event->{description}){
		  $event->{description} = "Revision for " . $comp->{className};
		}
		
		return $event;
	 }
  }

  # If the type is in the revision.xml file, but there was no match, create a
  # default message.
  my ($descArgs, $desc) = $this->{msgs}->getComponentDescription
                                                    ($comp,  '@revision.unspecifiedmodel');

  my $prevRev;					
  if ($ocomp)
  {
    $prevRev = $ocomp->getComponentProperty("fru._Firmware");
  }	

  my $event = { type        => $dev->{type},
  	        device      => $dev->{name} || $dev->{key},
  	        component   => $comp->getDisplayName(),
  	        className   => $comp->{className},
		status      => 'INFO',
                expected_patch => "N/A",
		curr_version => $comp->getComponentProperty("fru._Firmware"),
		prev_version => $prevRev,
		description => $desc
  	      };

  if (!$event->{description}){
    $event->{description} = "No revision information exists for this model. Please see " .
                            "sunsolve online to obtain a newer Storage Automated Diagnostic " .
                            "Environment patch if available, or information on newer firmware " .
                            "patches. Contact your SUN representative for additional information.";
  }


  return $event;
}

1;
