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

#  $Name: bld61_002 $ 
#  $Id: Instance.pm,v 1.4 2005/01/05 23:41:51 ccadieux Exp $

use strict;
use CIM;
use Carp;
use System;
use Carp;
use NWS::Schema;
use CIM::Key;
use CIM::Property;
use Debug;
use Data::Dumper;

use base 'CIM::Base';

#
#  DATASTRUCTURE:
#    instance  : [type, class, qualifiers, properties ]
#    properties: [name, val, type, key, arg]
#    qualifiers: [name, value ]
#    reference : [className, key1, value1, key2, value2]
#
#   Examples:
#    $elem = CIM::Association->new('AssocClass',[
#               [ ref1  => objectNameRef],
#               [ ref2  => objectNameRef],
#               [ attribute1 => "value"],
#               ]);

use vars qw($error);

sub error {
   return "ERROR: $error";
}

sub newInstance {
  my($class, $className, $values);
  my @arg;
  foreach my $name (keys %$values){
	 push (@arg, [ $name, $values->{$name}]);
  }
  my($error, $props, $type);
  ($error, $type, $props) = &_readSchema( $class, \@arg, $className);
  if ($error){
	 my $msg = $error;
	 my $msg .= "\n($className, (";
	 foreach my $name (keys %$values){
		$msg .= "$name=".$values->{$name}.",  ";
	 }
	 $msg .= ");\n";
	 Debug->err(CIM_ERR => $msg);
  }	

  my $self = [ $type, $className, undef, $props ];

  bless($self, "CIM::Instance");
  return $self;
}

sub new {
  my($class, $className, $arg, $quals) = @_;
  my($error, $props, $type);

  ($error, $type, $props) = &_readSchema( $class, $arg, $className);

  Debug->err(CIM_ERR => $error) if ($error);
  if ($error){
	 if (Debug->level() > 1){
		my $msg = "$className: ";
		foreach my $v (@$arg) {
		  $msg .= $v->[0]."=".$v->[1].", ";
		}
		Debug->print2($msg);
	 }
  }
  my $self = [ $type, $className, undef, $props ];

  bless($self, "CIM::Instance");
  return $self;
}

sub keyLayout {
  my($class, $name) = @_;
  my($o);
  my($E) = NWS::Schema->database;

  my($e) = $E->{$name};
  croak("Invalid ClassName $name") if (!$e);

  my($db_props)  = $e->[1];
  
  foreach my $db_el (@$db_props) {
    if ($db_el->[2]) {  # a key
       $o .= "$db_el->[0], ";
    }
  }
  return $o;
}

sub type {
  my($ins) = @_;
  return $ins->[0];
}

sub instanceType {
  my($ins) = @_;
  return $ins->[0];
}

sub className {
  my($ins) = @_;
  return $ins->[1];
}

sub name {
  my($ins) = @_;
  return $ins->[1];
}

#
#  properties:  [$prop_name, $value, $type, $keyflag, $quals]
#
sub propertyByName {
  my($ins, $name, $quiet) = @_;
  my($p);
  foreach $p (@{$ins->properties}) {
    if ($p->name eq $name) {
       return $p;
    }
  }
  Debug->print4("No propertyByName for '$name'!") if (!$quiet);
  return CIM::Property->new([$name,undef,'S']);
}

sub valueq {
  my($ins, $name) = @_;
  my($p) = $ins->propertyByName($name, 1);
  return $p->value;
}

sub values {
  my($ins, $list) = @_;

  my (@r, $x, $p);
  foreach $p (@{$ins->properties}) {
    for ($x=0; $x <= $#$list; $x++) {
       if ($list->[$x] eq $p->name()) {
         $r[$x] = $p->value(); last;
       }
    }
  }
  return @r;
}

sub value {
  my($ins, $name) = @_;
  my($p) = $ins->propertyByName($name);
  return $p->value;
}

sub setValue {
  my($ins, $name, $value) = @_;
  my($p) = $ins->propertyByName($name);
  $p->setValue($value);
}

sub setValue2 {
  my($ins, $name, $value, $type) = @_;
  my($p) = $ins->propertyByName($name);
  if (!defined($p->[1]) && $p->[2] eq 'S') {
     $type = 'string' if (!$type);
     $ins->addProperty(CIM::Property->new( [$name, $value, $type]));
  } else {
     $p->setValue($value);
  }
}

sub addProperty {
  my($ins, $p) = @_;
  push(@{$ins->[3]} , $p);
}

sub properties {
  my($ins) = @_;
  return $ins->[3];
}

sub key {
  my($ins) = @_;
  my($p, @k);
  push(@k, $ins->[1]);  #name
  foreach $p (@{$ins->properties}) {  # properties
     if ($p->isKey) {
       push(@k, $p->name, $p->value);
     }
  }
  my($key) = CIM::Key->nv_new(\@k);
  return $key;
}

sub toSerial {
  my($ins, $indent) = @_;

  $Data::Dumper::Indent = $indent;
  my($o) = Dumper($ins);
  return substr($o, 7);
}

sub fromSerial {
  my($class, $s) = @_;
  my($o) = '';
  $s = '$o=' . $s;
  eval $s;
  return undef if ($@);
  return $o;
}

sub toXML {
  my($i) = @_;
  my($q, $tag, $p);

  my($o) = "<INSTANCE CLASSNAME=\"$i->[1]\">";
  if ($i->[0] eq "A") {
    $o .= "<QUALIFIER NAME=\"Association\" TYPE=\"boolean\"><VALUE>TRUE</VALUE></QUALIFIER>";
  }
  $o .= $i->XMLqualifiers($i->[2]) if ($i->[2]);
  foreach $p (@{$i->properties}) {
     $o .= $p->toXML;
  }
  $o .= "</INSTANCE>";
  return $o;

}


sub toString {
  my($ins, $indent, $html) = @_;
  my($p,$qual, $o, $b, $b2);
  if ($html) {
    $b = "<b>"; $b2 = "</b>";
  }
  eval {
    $qual = $ins->[2] || "undef";
    
    $o = " " x $indent . "$b ['$ins->[0]', '$ins->[1]', $qual, $b2 \n";
    foreach $p (@{$ins->properties}) {
       $o .= " " x $indent . " " . $p->toString($indent) . ", \n";
    }
    $o .= " " x $indent . "]";
  };
  if ($@) {
    Debug->err(ERROR => "Instance->toString: $@");
    return "";
  } else {
    return $o;
  }
}


sub _readSchema {
  my($class, $arg, $className) = @_;
  my($p, %argh, $props, $name, $db_el, @props, $db_props, @key, $val, $type);
  my($parent);

  my($E) = NWS::Schema->database(CIM->version() );
  croak("Invalid syntax at Instance creation :class name is not a string") 
       if (ref($className));
  my($e) = $E->{$className};
  my $renv = System->get_renv();

  croak("Invalid " . ref($class) . ":$className") if (!$e);
  my($db_type) = $e->[0]{type};

  foreach $p (@$arg) {
     $argh{$p->[0]} = $p;
  }
  my($pro, $key, $db_q, $qv, $level, %find);

  foreach $db_el (@{$e->[1]}) { # read the schema
        $name = $db_el->[0];
        $type = $db_el->[1];
        if ($type eq "R") {  #  REFERENCE
             $val = $argh{$name}->[1];
             my($ref_class) = $db_el->[2];
             $val = $val->key if (ref($val) eq "CIM::Instance");
             if (ref($val) ne "CIM::Key") {
                my $info = Carp::longmess;
                return ("Invalid Type for $className->$name : ". ref($val) . $info, "",[]);
             }
             if ($ref_class ne $val->className) {
               if (Debug->level > 1) {
                 my($sch_class) = $val->className; my($s_el);
                 while ($sch_class ne $ref_class) {
                    $s_el = $E->{$sch_class};
                    if (!$s_el) {
                       last;
                    } else {
                       $sch_class = $s_el->[0]{parent};
                    }
                 }
                 if ($sch_class ne $ref_class) {
                    Debug->print0("$className: Invalid className for reference:$ref_class != ". 
                           $val->className);
                 }
               }
             }
             $pro = CIM::Property->new([$name, $val, 'reference', 1]);

        } else {            #  REGULAR Properties 
             $key = ($db_el->[2])? 1:0;
             if (!$argh{$name}) { #no value, look for a default
                if (!defined($db_el->[3])) { # no default
                  next if (!$key);
                  $val = "[null]";

                } else {
                  if ($db_el->[3] eq "HOSTNAME") {
                    $val  = $renv->{hostname};
                    $val .= " " . System->solution_ip()  if ($renv->{solution} ne "N");

                  } elsif ($db_el->[3] eq "RUNID") {
                    $val = System->get_runId();
                  } elsif ($db_el->[3] eq "SOLUTIONID") {
                    $val = "";
                    $val = $renv->{solution} . ":" . 
                           $renv->{solution_model} . "." .  
                           System->hostid() if ($renv->{solution} ne "N");
 
                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "HOSTID") {
                    $val = System->hostid;
                  } elsif ($db_el->[1] eq "uint16" && $db_el->[3] eq "DEBUGLEVEL") {
                    $val = Debug->level();

                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "OS") {
                    $val = System->os;

                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "GRIDCODE") {
                    $val = Grid->getCode();

                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "PROVIDERS") {
                    $val = "";
                    if ($renv->{solution} ne "N") {
                      $val .= "NSCC," if ($renv->{"pro.nscc_email.active"} eq "Y");
                      $val .= "SSRR," if ($renv->{"pro.ssrr.active"} eq "Y");
                    }
                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "GRIDNO") {
                    $val = Grid->getNo();

                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "SVERSION") {
                    $val = System->get_schemaVersion;
                  } elsif ($db_el->[1] eq "datetime"  && $db_el->[3] eq "NOW") {
                    $val = CIM->cim_today ;
                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "CLASSNAME") {
                    $val = $className;

                  } elsif ($db_el->[1] eq "string" && $db_el->[3] eq "REGISTRATION") {
                    $val = $class->registration();
                  } else {
                    $val = $db_el->[3];
                  }
                }
             } else {
                $val = $argh{$name}->[1];
             }
             if ($key && !$val) {
                my $info = Carp::longmess;
                return ("Key field must have value!: $className:$name $info","",[]);
             }
             $type = $CIM::MAP{$type} || $type;
             if ($type eq "boolean") {
               $val = (lc($val) eq "true" || lc($val) eq "yes" || $val >= 1)? "TRUE":"FALSE";
             }
             if (!CIM->check_type($type)) {
                my $info = Carp::longmess;
                return ("Invalid type: $type on $className->$name $info", "",[]);
             }
             if ($type =~ /array/) {
                if (ref($val) ne "ARRAY") {
                  return ("$className->$name must be an array", "",[]);
                }
             } else {
                if (ref($val) eq "ARRAY") {
                  return ("$className->$name cannot be an array", "",[]);
                }
             }
             if ($db_el->[1] eq "E") {
                if (index(",".$db_el->[4].",", ",$val,") < 0) {
                   return ("Invalid Enumeration Value: $val for $class->$name", "",[]);
                }
             }
             $pro =  CIM::Property->new( [$name, $val, $type, $key] );
        }
        $db_q = $db_el->[5];
        if ($db_q) {   # only one quals works
             $qv = $argh{$name}->[2][1] || $db_q->[1];   # qual. value or default
             if (!$qv) {
               my $info = Carp::longmess;
               return ("Missing value for qualifier $className->$name->$db_q->[0] $info", "",[]);
             } else {
               $pro->addQualifier([ $db_q->[0], $qv , $db_q->[2]]);
             }
        }
        push(@props, $pro);
        $argh{$name} = "DONE";
   }
   foreach $name (keys %argh) {
     if ($argh{$name} ne "DONE") {
         return ( "Invalid property name $name in $className", "",[]);
     }
   }

   return ("", $db_type, \@props);
}


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

  my $F = System->get_home() . "/System/rm_registration_key";
  my $val;
  if (-f $F) {
     open(O, $F); my @lines = <O>; close(O);
     $val = "@lines";
  }
  return $val;
}



1;



__END__

=head1 NAME

Instance.pm - Generate a Instance and validate againt the Schema NWS::Schema


=head3 SYNOPSIS

 use Instance;

 $inst =  CIM::GenInstance->new('ClassName', [
                       [ field1   => "value"],   # a key
                       [ field1   => <ObjectName>|<instance>] ],  # a reference
                       [ attrib1  => "value"],
                       [ attrib1  => "value"],             # a string
               ]);


=head3 DESCRIPTION

This module creates CIM instance object based on a data structured that is 
passed at creation. It will validate against the schema and complains on
invalid attributes.



=head3 CONSTRUCTOR

=over 4

=item new( <ClassName> [<Properties>] )


=back 4

=head3 METHODS

=over 4

=item instanceType()

Returns the type of the instance : E, A, P , C

=item instanceType()

Returns the type of the instance : E, A, P , C

=item className()

Returns the CIM class name.

=item properties()

Returns an ARRAYREF to the properties.

=item key()

Returns the key to this instance as an CIM::Key.

=item propertyByName()

Returns a property from an instance.

=item toSerial()

Serialize the instance for transmission and storage.

=item fromSerial()

Unserialize the instance for reception.

=item toXML()

Convert to XML for communication with other programs.

=item toString()

Convert to a string for debugging.


=back 4



=head3 AUTHOR

 Christian Cadieux (ccadieux@central.sun)



=head3 COPYRIGHT

Copyright (c) 2000 Sun Microsystems

=cut
