# $Id: ReportClass.pm,v 1.9 2006/05/24 16:55:24 mckenney Exp $
# Copyright 2004 Sun Microsystems, Inc., All Rights Reserved.
# Sun Proprietary/Confidential: For Internal Use Only
#
# ReportClass contains properties from an instrumentation report for
# one class within the report.
package Catalog::ReportClass;

use strict;
use Catalog::Model;

# Create a new system level report class with classname and instance name.
sub new {
  my ($class, $className, $key) = @_;
  $className = "system" if (!$className);

  my $this = { "className" => $className,
               "children"  => {},
			      "vars"      => { "_Key" => $key },
					"path"      => undef # system has no path like disk.1
				 };
  bless($this, "Catalog::ReportClass");
  return $this;
}

# Create a report from a file
sub createFromFile {
  my ($class, $file, $sep) = @_;
  $sep = '=' if (!$sep);
  return undef if (!$file);

  open(O, "$file");
  my @L = <O> ; close(O);
  my $report = {};
  foreach my $l (@L) {
	 my $line = $l;
	 my $sindex = index($line, $sep);
	 next if ($sindex <= 0);
	 next if (index($line, "#") == 0);
	 $report->{ substr($line, 0, $sindex) } = substr($line, $sindex+1);
	 chomp %$report;
  }
  return createFromReport Catalog::ReportClass( $report );
}

# Create a sub-instance of a component in an existing system report class.
sub newSubInstance {
  my ($this, $className, $key) = @_;
  my $rc = new  Catalog::ReportClass($className, $key);
  $this->addChild($rc);
  return $rc;
}

# Create a new ReportClass from a hash of report values.
sub createFromReport {
  my ($class, $rep) = @_;

  my $report = new Catalog::ReportClass("system", $rep->{_Key});

  my $cpath;
  my $crep;

  foreach my $el (sort keys %$rep) {
	 next if ($el =~ /$\.count/);
	 my $value  = $rep->{$el};
	 my ($cname, $index, $var) = split(/\./, $el, 3);

	 if (!$var){
		$report->setProperty( $index, $value);
		next;
	 }

	 my ($subPath, $subVar) = Catalog::ReportClass->getPathVar($el);

	 if (!($subPath eq $cpath)){
		$cpath = $subPath;
		$crep = $report->getSubComponent($el);
		if (!$crep){
		  $crep = $report->addSubComponent($el);
		}
	 }
	 if ($crep){
		$crep->setPathProperty( $var, $value);
	 }
  }
  $report->_mapFRUs();
  return $report;
}

# return array of given type of component.
# getComponents("disk") returns all disk classes.
sub getComponents {
   my ($this, $type) = @_;
	return $this->{children}->{$type};
}

# Get the number of a given type of classes.
sub getComponentCount {
  my ($this, $type, $model) = @_;
  if (!$model){
	 my $comps = $this->{children}->{$type};
	 return $#$comps + 1;
  }
}

# return array ofsub-components that are instances of the given
# class name.  Will follow inheritence if a Model object for the 
# type is given.
sub getSubComponents {
   my ($this, $type, $model) = @_;
	if (!$model){
	  return $this->{children}->{$type};
	}
	else {
	  my $subs = $this->{children};
	  my @comps;
	  foreach my $compClass (keys %$subs){
		 if ($model->isA($compClass, $type)){
			my $devs = $subs->{$compClass};
			foreach my $dev (@$devs){
			  push(@comps, $dev);
			}
		 }
	  }
	  return \@comps;
	}
}

# return a map of keys to sub-components of a given model type.
sub getSubComponentMap {
  my ($this, $type, $model) = @_;
  if (!$model){
	 my $devs = $this->{children}->{$type};
	 my %map;
	 foreach my $dev (@$devs){
		$map{$dev->getKey()} = $dev;
	 }
	 return \%map;
  }
  else {
	 my $subs = $this->{children};
	  my %map;
	  foreach my $compClass (keys %$subs){
		 if ($model->isA($compClass, $type)){
			my $devs = $subs->{$compClass};
			foreach my $dev (@$devs){
			  $map{$dev->getKey()} = $dev;
			}
		 }
	  }
	  return \%map;
  }
}	


# find first sub component that has a key == value.
sub findSubComponent {
   my ($this, $key, $value)= @_;
	if ($this->{vars}->{$key} eq $value){
	  return $this;
	}
	my $subs = $this->{children};
	foreach my $subClass (keys %$subs){
	  my $subList = $subs->{$subClass};
	  for ( my $i = 0; $i <= $#$subList; $i++){
		 my $sub = $subList->[$i];
		 if ($sub->{vars}->{$key} eq {$value}){
			return $sub;
		 }
		 $sub->findSubComponent($key, $value);
	  }
	}
	return undef;
}

# create a map of _Key to ReportClass
# Optional cmap will be used if provided.
sub mapObjects {
  my ($this, $cmap) = @_;
  if (!$cmap){
	 $cmap = { $this->getKey() => $this};
  }

  my $subs = $this->{children};
  foreach my $subClass (keys %$subs){
	 my $subList = $subs->{$subClass};
	 for ( my $i = 0; $i <= $#$subList; $i++){
		my $sub = $subList->[$i];
		$cmap->{$sub->getKey()} = $sub;
		$sub->mapObjects($cmap);
	 }
  }
  return $cmap;
}

# get the stored object => Key map
sub getObjectMap {
  my ($this) = @_;
  return $this->{objMap} if ($this->{objMap});
  $this->{objMap} = $this->mapObjects();
  return $this->{objMap};
}

# Find a sub-component: getSubComponent("disk.1.fcport.2");
sub getSubComponent {
  my ($this, $path)= @_;
  if ($path eq $this->{className}){
	 return $this;
  }

  my ($cname, $index, $var) = split(/\./, $path, 3);
  if ("count" eq $index){
	 return undef;
  }
  my $slist = $this->{children}->{$cname};
  if (!$slist){
	 return undef;
  }
  if (($index) > $#$slist){
	 return undef;
  }
  my $subcomp = $slist->[$index - 1];
  if (index($var, ".") > 0){
	 $subcomp = $subcomp->getSubComponent($var);
  }
  return $subcomp;
}

sub addSubComponent {
  my ($this, $path, $key)= @_;
  my ($cname, $index, $var) = split(/\./, $path, 3);

  my $slist = $this->{children}->{$cname};
  if (!$slist){
	 $slist = [];
  }
  for (my $i = $#$slist+1; $i < $index; $i++){
	 $slist->[$i] = $this->newSubInstance($cname, $key);
  } 
  my $subcomp = $slist->[$index - 1];
  if (index($var, ".")>0){
	 my $subsub = $subcomp->addSubComponent($var);
	 if ($subsub){
		$subcomp = $subsub;
	 }
  }
  return $subcomp;
}

# add a new child class to this class instance.
sub addChild {
  my ($this, $reportChild) = @_;

  my $children = $this->{children};
  my $carray = $children->{ $reportChild->{className}};
  if (!$carray){
	 $carray = [];
	 $children->{ $reportChild->{className}} = $carray;
  }
  push  @$carray, $reportChild;
  my $index = $#$carray+1;
  if ($this->{path}){
	 $reportChild->{path} = $this->{path}.".".
		$reportChild->{className} . "." . $index;
  }
  else {
	 $reportChild->{path} = $reportChild->{className}. "." . $index;
  }
  $reportChild->{parent} = $this;
}

# Set the value of a report property.
sub setProperty {
  my ($this, $name, $value) = @_;
  $this->{vars}->{$name} = $value;
}

# Get the value of a report property.
sub getProperty {
  my ($this, $name) = @_;
  return $this->{vars}->{$name};
}


sub setPathProperty {
  my ($this, $name, $value) = @_;
  if ( $name =~ /.*\.\d+\./){
	 my ($path, $var) = Catalog::ReportClass->getPathVar($name);
	 $this->{vars}->{$var} = $value;
  }
  else {
	  $this->{vars}->{$name} = $value;
  }
}

# Get the report class name for this element.
sub getClassName {
  my ($this) = @_;
  return $this->{className};
}

# Returns FRU id for physical components and device id for logical components.
sub getComponentKey {
  my ($this) = @_;
  my $v = $this->{vars};
  return $v->{_Realized} || $this->getKey();
}

# Get a property from a component. If name starts with fru. such as 
# fru._Revision then get the property from the fru.
sub getComponentProperty {
  my ($this, $name) = @_;
  if (index($name, "fru.") >= 0){
	 return $this->getFRUProperty(substr($name, 4));
  }
  return $this->{vars}->{$name};
}

# Returns the key for an element.
# All reports should have a key but this sub will return another key property
# if key is not set.
sub getKey {
  my ($this) = @_;
  my $key =  $this->{vars}->{_Key};
  if ($key){ return $key;}
  my $v = $this->{vars};
  $key = $v->{DeviceID} || $v->{Tag} || $v->{_Name} || $v->{_name};
  $v->{_Key} = $key;
  return $key;
}

# Get the path from the root element to this element.
# the path contains <classname>.<index>
sub getPath {
  my ($this) = @_;
  return $this->{path};
}

# Get the fru from a logical component class that has is realized.
sub getFRU {
  my ($this) = @_;
  my $fru = $this->{fru};
  if ($fru){
	 my $top = $this->getRoot();
	 my $map = $top->getObjectMap();
	 return $map->{$fru};
  }
}

# Get a property from a realized fru.
sub getFRUProperty {
  my ($this, $name) = @_;
  my $fru = $this->getFRU();
  return undef if (!$fru);
  return $fru->getProperty($name);
}

# Get a good name for this element to display to an end user.
sub getDisplayName {
  my ($this) = @_;
  my $vars = $this->{vars};
  if(defined($vars->{_Name})){
    return $vars->{_Name};
  }
  return $vars->{_ElementName} || $vars->{_Caption} ||
	 $vars->{_Description} || $vars->{_Key} || "?";
}

# Get the status for this component.
sub getStatus {
  my ($this) = @_;
  my $vars = $this->{vars};
  my $status = $vars->{_Status} ||
	 $vars->{_VolumeStatus} || $vars->{_ExtentStatus};
  if ("Other" eq $status){
	 $status = $vars->{_RawStatus};
  }
  return $status;
}

# Get the state for a component.
sub getState {
  my ($this) = @_;
  my $vars = $this->{vars};
  my $state = $vars->{"_EnabledState"};
  return $state;
}

# Get the top level system class.
sub getSystem {
  my ($this) = @_;
  if ($this->{className} eq "system"){
	 return $this;
  }
  my $parent = $this->{parent};
  if ($parent){
	 return $parent->getSystem();
  }
  return undef;
}

# Get the display name of the top level system object.
sub getSystemDisplayName {
  my ($this) = @_;
  my $system = $this->getSystem();
  if ($system){
	 return $system->getDisplayName();
  }
  return "?";
}

# get the key value of the top level system object.
sub getSystemKey {
  my ($this) = @_;
  my $system = $this->getSystem();
  if ($system){
	 return $system->getKey();
  }
}

# Set the FRU for a component.
sub setFRU {
  my ($this, $fru) = @_;
  $this->{fru} = $fru;
}

sub _mapFRUs {
  my ($this) = @_;
  my $map = $this->getObjectMap();
  foreach my $name (keys %$map) {
	 my $comp  = $map->{$name};
	 my $fru   = $comp->getProperty("_Realized");
	 $comp->{fru} = $fru if ($fru);
  }
}

# Set the device discovered properties from rasagent.conf for this device.
sub setDevice {
  my ($this, $device) = @_;
  $this->{device} = $device;
}

# Get the device properties previously set for this device.
sub getDevice {
  my ($this) = @_;
  return $this->{device};
}

# Create a report with name value pairs.
sub toString {
  my ($this, $sep) = @_;
  $sep = "\t" if (!$sep);
  my $cmap = {};
  return $this->_toString($sep, $cmap);
}

sub _toString {
  my ($this, $sep) = @_;

  my $out;
  my $vars = $this->{vars};
  my $root = $this->{path} || $this->{className};
  foreach my $el (sort keys %$vars) {
	 $out .= $root.".".$el.$sep.$vars->{$el}."\n";
  }

  my $children = $this->{children};
  foreach my $type (sort keys %$children) {
	 my $clist = $children->{$type};
	 my $count = $#$clist + 1;
	 if ($count){
		if ($this->{path}){
		  $out .= $this->{path}.".".$type.".count".$sep.$count."\n";
		}
		else {
		  $out .= $type.".count".$sep.$count."\n";
		}
	 }
	 for (my $i=0; $i <= $#$clist; $i++){
		my $child = $clist->[$i];
		$out .= $child->toString($sep, $out);
	 }
  }
  return $out;
}

sub toReport {
  my ($this, $out) = @_;

  $out = {} if (!$out);

  my $vars = $this->{vars};
  my $root = $this->{path} || $this->{className};
  foreach my $el (sort keys %$vars) {
	 $out->{$root.".".$el} = $vars->{$el};
  }

  my $children = $this->{children};
  foreach my $type (sort keys %$children) {
	 my $clist = $children->{$type};
	 my $count = $#$clist + 1;
	 if ($count){
		if ($this->{path}){
		  $out->{$this->{path}.".".$type.".count"} = $count;
		}
		else {
		  $out->{$type.".count"} = $count;
		}
	 }
	 for (my $i=0; $i <= $#$clist; $i++){
		my $child = $clist->[$i];
		$child->toReport($out);
	 }
  }
  return $out;
}

sub setModel {
  my ($this, $model) = @_;
  $this->{model} = $model;
}
sub isA {
  my ($this, $className) = @_;
  my $model = $this->{model};
  if ($model){
	 return $model->isA($this->{className}, $className);
  }
  return ($className eq $this->{className});
}

sub getRoot {
  my ($this) = @_;
  my $parent = $this->{parent};
  if ($parent){
	 if ($parent eq $this){
		return $this;
	 }
	 return $parent->getRoot();
  }
  return $this;
}

sub toXML {
  my ($this, $pre) = @_;

  my $npre = $pre . " ";

  my $out;
  my $vars = $this->{vars};
  my $root = $this->{path} || $this->{className};

  $out .= "$pre<class type='". $this->{className} .
	 "' key='" . $vars->{_Key} . "'>\n";

  foreach my $el (sort keys %$vars) {
	 next if ($el eq "_Key");
	 $out .= "$pre <value name='" . $el . "'>";
	 $out .= Catalog::ReportClass->_xmlEncode($vars->{$el});
	 $out .= "</value>\n";
  }

  my $children = $this->{children};
  foreach my $type (sort keys %$children) {
	 my $clist = $children->{$type};
	 for (my $i=0; $i <= $#$clist; $i++){
		my $child = $clist->[$i];
		$out .= $child->toXML($npre);
	 }
  }

  $out .= "$pre</class>\n";
  return $out;
}

sub getPathVar {
   my ($class, $path) = @_;
	$path =~ /(.*\d+)\.(.*)/;
	return ($1, $2);
}

sub _xmlEncode {
  my($class, $value) = @_;

  my $ret = $value;
  $ret =~ s/&/&amp;/g;
  $ret =~ s/</&lt;/g;
  $ret =~ s/>/&gt;/g;
  $ret =~ s/"/&quot;/g;
  $ret =~ s/'/&apos;/g;

  return $ret;
}
1;
