#  Copyright (c) 1997-2006
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#----------------------------------------------------------------------------
#  $Project: polymake $$Id: Object.pm 7557 2007-01-12 17:48:17Z gawrilow $

use strict;
use namespaces;

use Poly::PropertyValue;
use Poly::File;

package Poly::Object;
use Poly::Ext;

use Struct (
   [ '$name' => 'undef' ],	# unique(?) object name
   '@contents',			# PropertyValues in the chronological (file) order
   '%dictionary',		# Property => index in @contents
   [ '$persistent' => 'undef' ],# some object with load() and save() methods
   '@transactions',		# Transaction
   '%failed_rules',		# Rule => undef  rules should not be applied to the object any more
   '$changed',			# Object changed since last load() or save()
);

####################################################################################

package Poly::Transaction;

use Struct (
   '$content_end',		# length of Object->contents at the transaction start
   '%backup',			# overwritten data: content_index => old PropertyValue
   [ '$activeRule' => 'undef' ],# Rule
   '$keep_overwritten',		# boolean  true in verification mode
   '$changed',			# boolean  object was changed during the transaction
   '%run_rules',		# Rule => exit code  rules already executed
   '%extra',			# additional parameters for context-specific use
);

sub new {		# CLASS, Object =>
   my $self=&_new;
   my ($object)=@_;
   $self->content_end=$#{$object->contents};
   $self->keep_overwritten= do {
      my $trans=$object->transactions->[0];
      defined($trans) && $trans->keep_overwritten && $trans->extra->{inherit_keep_overwritten}
   };
   return $self;
}

sub merge {
   my ($self, $trans)=@_;
   while (my ($content_index, $old_pv)=each %{$trans->backup}) {
      if ($content_index <= $self->content_end) {
	 $self->backup->{$content_index} ||= $old_pv;
      }
   }
   while (my ($rule, $code)=each %{$trans->run_rules}) {
      $self->run_rules->{$rule}=$code;
   }
   $self->changed |= $trans->changed;
}
####################################################################################
package Poly::Object;

sub new {
   my $self=&_new;
   if (@_==1  and  is_object($_[0])) {
      my $ph=shift;
      if ($ph->can("save")) {
	 $self->persistent=$ph;
      } else {
	 croak( ref($ph), " doesn't implement the persistency handler interface" );
      }
   } else {
      if (@_%2) {
	 $self->name=shift;
      }
      $self->begin_transaction;
      if (@_) {
	 while (my ($prop_name, $value)=splice @_, 0, 2) {
	    my ($prop)=$self->prototype->property($prop_name);	# allow undeclared properties
	    if (exists $self->dictionary->{$prop}) {
	       croak( "multiple values for property '$prop_name'" );
	    } else {
	       my $pv=new Poly::PropertyValue($prop, $value, $self);
	       push @{$self->contents}, $pv;
	       $self->dictionary->{$prop}=$#{$self->contents};
	       if ($prop->temporary) {
		  need_cleanup($self, $#{$self->contents});
	       }
	    }
	 }
	 $self->transactions->[0]->changed=1;
      }
   }
   $self;
}
####################################################################################
sub load {
   my $self=is_object($_[0]) ? shift : &_new;
   ( $self->persistent ||= do {
        my $ph=shift;
	is_object($ph) ? $ph : new File($ph);
     }
   )->load($self, @_);
   $self;
}
####################################################################################
sub exists {	# 'request' || [request] => boolean
   my ($self, $req)=@_;
   if (!is_ARRAY($req)) {
      $req=$self->prototype->encode_read_request($req);
   }

   foreach my $prop (@$req) {
      if (defined (my $content_index=$self->dictionary->{$prop})) {
	 return 1 if defined($self->contents->[$content_index]);
      }
   }

   0
}
####################################################################################
sub isa {
   my ($self, $proto_name)=@_;
   my $object_pkg=namespaces::lookup_class($self, $proto_name);
   $object_pkg && UNIVERSAL::isa($self, $object_pkg->prototype->object_type);
}
####################################################################################
sub lookup_pv {		# 'request' || [request] => PropertyValue
   my ($self, $req)=@_;
   my $proto=$self->prototype;
   if (!is_ARRAY($req)) {
      $req=$proto->encode_read_request($req);
   }

   foreach my $prop (@$req) {
      if (defined (my $content_index=$self->dictionary->{$prop})) {
	 if (my $trans=$self->transactions->[0] and
	     !defined($self->contents->[$content_index]->value)) {
	    if (defined($trans->activeRule) and
		$content_index > $trans->content_end || exists $trans->backup->{$content_index}) {
	       die "rule '", $trans->activeRule->header, "' attempts to read its output property ", $prop->name,
	           " before it is created\n";
	    }
	 }
	 return $self->contents->[$content_index];
      }
   }
   undef;
}

sub lookup {
   my $pv=&lookup_pv;
   $pv && $pv->value;
}
####################################################################################
sub put {
   my ($self, $prop, $data)=@_;
   my $trans=$self->transactions->[0];
   confess( "Poly::Object::put out of transaction scope" ) if !defined $trans;

   my $pv=new PropertyValue($prop, $data, $self);
   my $content_index=$self->dictionary->{$prop};
   if (defined $trans->activeRule) {
      # creating a property in a rule
      if (!defined $content_index  or
	  $content_index<=$trans->content_end && !exists $trans->backup->{$content_index}) {
	 die "attempt to create property '", $prop->name, "' which is not declared as a rule target\n";
      }
      $self->contents->[$content_index]=$pv;

   } elsif (defined $content_index) {
      # overwriting an existing property
      my $old_pv=$self->contents->[$content_index];
      $self->contents->[$content_index]=$pv;
      $trans->backup->{$content_index} ||= $old_pv;
      $trans->changed=1 if $trans->keep_overwritten;
      return;

   } else {
      # new property created outside of a production rule
      push @{$self->contents}, $pv;
      $self->dictionary->{$prop}=$content_index=$#{$self->contents};
   }

   if ($prop->temporary) {
      need_cleanup($self, $content_index);
   } else {
      $trans->changed=1;
   }
   defined(wantarray) && $pv->value;
}
####################################################################################
sub add {
   my ($self, $prop, $data)=@_;
   croak( "multiple values for property ", $prop->name, "\n" )
      if exists $self->dictionary->{$prop};
   my ($value, $conv)=new PropertyValue($prop, $data, $self);
   push @{$self->contents}, $value;
   $self->dictionary->{$prop}=$#{$self->contents};
   if ($conv) {
      $self->changed=1;
      $main::scope->cleanup->{$self} ||= [ undef ];
   }
}
####################################################################################
sub take {
   my ($self, $prop_name, $data)=@_;

   if (@_==2) {
      # empty property
      $data=[ ];
   } elsif (defined($data) and !ref($data)) {
      # mixed data stream
      my (@data);
      foreach my $line (@_[2..$#_]) {
	 foreach (split /\n/, $line) {
	    s/^\s+//;
	    next if length==0;
	    if (substr($_,0,1) ne "#") {
	       push @data, "$_\n";
	    }
	 }
      }
      $data=\@data;
   }

   my $trans=$self->transactions->[0] || $self->begin_transaction;
   my $prop=
      defined($trans->activeRule)
      ? $self->prototype->property($prop_name)
      : ( $self->prototype->property($prop_name) )[0];	# let it create a new property if undeclared
   $self->put($prop, $data);
}
####################################################################################
sub remove($$) {
   my ($self, $prop_name)=@_;
   my $trans=$self->transactions->[0];
   confess( "Poly::Object::remove out of transaction scope" ) if !defined $trans;
   my $prop=$self->prototype->property($prop_name);
   if (defined (my $content_index=delete $self->dictionary->{$prop})) {
      $trans->backup->{$content_index} ||= $self->contents->[$content_index];
      undef $self->contents->[$content_index];
      $trans->changed=1;
   } elsif (defined $trans->activeRule) {
      die "attempt to remove property '$prop_name' not declared as a rule source\n";
   }
}
####################################################################################
sub begin_transaction {
   my ($self, $active_rule)=@_;
   my $trans=new Transaction($self);
   unshift @{$self->transactions}, $trans;

   if (defined $active_rule) {
      $trans->activeRule=$active_rule;
      if (ref($active_rule->output)) {
	 # is a production rule

	 foreach my $prop (@{$active_rule->output}) {
	    my $pv=new PropertyValue($prop);
	    if (defined (my $content_index=$self->dictionary->{$prop})) {
	       $trans->backup->{$content_index}=$self->contents->[$content_index];
	       $self->contents->[$content_index]=$pv;
	    } else {
	       push @{$self->contents}, $pv;
	       $self->dictionary->{$prop}=$#{$self->contents};
	    }
	 }
      }
   }
   return $trans;
}
####################################################################################
sub remember_failed_rules {
   my ($self, $trans)=@_;
   while (my ($rule, $code)=each %{$trans->run_rules}) {
      if ($code==$Poly::Rule::exec_failed) {
	 $self->failed_rules->{$rule}=1;
      }
   }
}
####################################################################################
sub commit {
   my ($self)=@_;
   my $trans=shift @{$self->transactions};
   confess( "Poly::Object::commit out of transaction scope" ) if !defined $trans;

   if ($trans->keep_overwritten) {
      commit_overwrite($self, $trans);
   } else {
      commit_ignore($self, $trans);
   }

   if (@{$self->transactions}) {
      $self->transactions->[0]->merge($trans);
   } else {
      if (!$self->changed and
	  $self->changed = $trans->changed || $#{$self->contents} > $trans->content_end) {
	 $main::scope->cleanup->{$self} ||= [ undef ];
      }
      remember_failed_rules($self,$trans);
   }
}
####################################################################################
sub commit_ignore {
   my ($self, $trans)=@_;
   my $proto=$self->prototype;

   while (my ($index, $old_pv)=each %{$trans->backup}) {
      my $pv=$self->contents->[$index];
      next if !defined($pv) || !defined (my $old_value=$old_pv->value);

      my $prop=$pv->property;
      if (defined (my $diff_sub=$prop->diff)) {
	 my @need_diff=grep { $_ > $trans->content_end } map { $self->dictionary->{$_} } @{$prop->diff_sensitive};
	 if (@need_diff) {
	    my $diff=eval { select_method($diff_sub, $self)->($self->contents->[$index]->value, $old_value) };
	    if ($@) {
	       $self->rollback($trans);
	       die "calculation of diff(".$prop->name.") failed: $@";
	    }
	    if (defined $diff) {
	       # make new properties created together with $prop (which is going to be discarded soon) compatible
	       # with the old version of $prop
	       foreach my $other_index (@need_diff) {
		  my $other_pv=$self->contents->[$other_index];
		  if (defined($other_pv->value)) {
		     my $other_prop=$other_pv->property;
		     dbg_print( "applying diff(".$prop->name.") to ", $other_prop->name ) if $Switches::v>=2;
		     eval {
			$other_pv->set_value( select_method($other_prop->apply_diff->{$prop}, $self)
					      ->($other_pv->value, $diff) )
		     };
		     if ($@) {
			$self->rollback($trans);
			die "applying diff(".$prop->name.") to ".$other_prop->name." failed: $@";
		     }
		  }
	       }
	    }
	 }
      }

      $self->contents->[$index]=$old_pv;
      delete $trans->backup->{$index};
   }
}
####################################################################################
sub equal_floats($$) {
   no integer;
   my ($x1, $x2)=@_;
   my $a=max(abs($x1),abs($x2));
   $a<=1e-7 or abs($x1-$x2)<=1e-7*$a;
}

sub equal_lines($$) {
   my ($l1, $l2)=@_;
   return 1 if $l1 eq $l2;
   return 0 if $l1 !~ /^[-+\d.eE\s]+$/ or $l2 !~ /^[-+\d.eE\s]+$/;
   my @d1=split /\s+/, $l1;
   my @d2=split /\s+/, $l2;
   return 0 if $#d1 != $#d2;
   for (my $i=0; $i<=$#d1; ++$i) {
      return 0 if !equal_floats($d1[$i], $d2[$i]);
   }
   1;
}

sub plain_compare($$) {
   my ($new, $old)=@_;
   if (ref($new) ne ref($old)) {
      return "type mismatch: new " . ref($new) . " old " . ref($old) . "\n";
   }
   if (ref($new) eq "ARRAY") {
      if ($#$new != $#$old) {
	 return "length mismatch: new " . ($#$new+1) . " old " . ($#$old+1) . "\n";
      }
      my $diff="";
      for (my $i=0; $i<=$#$new; ++$i) {
	 if (!equal_lines($new->[$i], $old->[$i])) {
	    $diff .= "line $i mismatch: new <";
	    $_=$new->[$i]; chomp; $diff.=$_;
	    $diff .= "> old <";
	    $_=$old->[$i]; chomp; $diff.=$_;
	    $diff .= ">\n";
	 }
      }
      return $diff;
   }
   if (ref($new)) {
      die "don't know how to compare ", ref($new), "\n";
   }
   !equal_lines($new, $old)
   && "value mismatch: new <$new> old <$old>\n";
}
####################################################################################
sub commit_overwrite {
   my ($self, $trans)=@_;
   my (%add_to_backup, %compared);
   my $proto=$self->prototype;
   my $errors="";
   while (my ($index, $old_pv)=each %{$trans->backup}) {
      my $pv=$self->contents->[$index];
      $compared{$index}=undef, next if !defined $pv;

      # first simple check: defined-ness must not change
      my $prop=$pv->property;
      my $old_value=$old_pv->value;
      my $is_defined=defined($pv->value);

      if ($is_defined != defined($old_value)) {
	 $errors .= "verification of '" . $prop->name . "' failed:\n" .
	            "new <" . ($is_defined ? "defined value" : "UNDEFINED ") .
		    "> old <" . ($is_defined ? "UNDEFINED" : "defined value") . ">\n";
	 $compared{$index}=undef, next;
      }
      $compared{$index}=undef, next unless $is_defined;

      if (defined (my $diff_sub=$prop->diff)) {
	 my $diff=eval { select_method($diff_sub, $self)->($old_value, $pv->value) };
	 $compared{$index}=undef;
	 if ($@) {
	    $errors .= "verification of ".$prop->name." failed: $@";
	    next;
	 } elsif ($Switches::v) {
	    dbg_print( "verification of ".$prop->name." successful" );
	 }

	 if (defined $diff) {
	    foreach my $other_prop (@{$prop->diff_sensitive}) {
	       my $other_index=$self->dictionary->{$other_prop};

	       # is the sensitive property older than this transaction?
	       if (defined ($other_index)
		   and $other_index <= $trans->content_end
		   and defined (my $other_pv=$self->contents->[$other_index])) {
		  $compared{$other_index}=undef, next if !defined $other_pv->value;	# no need to synchronize UNDEF

		  if (defined (my $other_old_pv=$trans->backup->{$other_index})) {
		     # got overwritten together with the generator: prepare the old value for comparison
		     if (defined $other_old_pv->value) {
			my $cmp_value=eval {
			   select_method($other_prop->apply_diff->{$prop}, $self)->($other_old_pv->value, $diff)
		        };
			if ($@ && $Switches::v) {
			   warn_print( "applying diff(".$prop->name.") to ".$other_prop->name." failed: $@",
			               "verification not performed" );
			}
			$compared{$other_index}=$cmp_value;
		     }

		  } else {
		     # must be synchronized
		     my $new_value=eval {
			select_method($other_prop->apply_diff->{$prop}, $self)->($other_pv->value, $diff)
		     };
		     if ($@) {
			warn_print( "applying diff(".$prop->name.") to ".$other_prop->name." failed: $@" ) if $Switches::v;
			delete $self->dictionary->{$other_prop};
			undef $self->contents->[$other_index];
			$compared{$other_index}=undef;
		     } else {
			$self->contents->[$other_index]=new PropertyValue($other_prop, $new_value);
		     }
		     $add_to_backup{$other_index}=$other_pv;
		  }
	       }
	    }
	 }
      }
   }

   while (my ($index, $old_pv)=each %{$trans->backup}) {
      my $old_value;
      if (exists $compared{$index}) {
	 next unless defined($old_value=$compared{$index});
      } else {
	 $old_value=$old_pv->value;
      }
      my $pv=$self->contents->[$index];
      my $diff=plain_compare($pv->value, $old_value);
      if (length($diff)) {
	 $errors .= "verification of ".$pv->property->name." failed:\n$diff";
      } elsif ($Switches::v) {
	 dbg_print( "verification of ".$pv->property->name." successful" );
      }
   }

   if (length($errors)) {
      $self->rollback($trans);
      die $errors;
   }
   while (my ($index, $pv)=each %add_to_backup) {
      $trans->backup->{$index} ||= $pv;
   }
}
####################################################################################
sub restore($$) {
   my ($self, $trans)=@_;
   while (my ($index, $pv)=each %{$trans->backup}) {
      if (!defined($self->contents->[$index])) {
	 $self->dictionary->{$pv->property}=$index;
      }
      $self->contents->[$index]=$pv;
   }
   %{$trans->backup}=();
}
####################################################################################
sub rollback {	# [ Transaction (only if called from commit) => ]
   my ($self, $trans)=@_;
   $trans ||= shift @{$self->transactions};
   confess( "Poly::Object::rollback out of transaction scope" ) if !defined $trans;

   restore($self, $trans);
   my $limit=$trans->content_end;
   foreach my $pv (@{$self->contents}[$limit+1..$#{$self->contents}]) {
      delete $self->dictionary->{$pv->property} if ref($pv);
   }
   $#{$self->contents}=$limit;
   remember_failed_rules($self,$trans);
}
####################################################################################
# private:
sub rollback_open {
   my ($self)=@_;
   if (@{$self->transactions}) {
      $self->rollback($self->transactions->[-1]);
      @{$self->transactions}=();
   }
}
####################################################################################
sub cleanup {
   my ($self, $index)=@_;
   rollback_open($self);
   if (defined($index)) {
      my $gap=0;
      for (my $last=$#{$self->contents};  $index<=$last;  ++$index) {
	 my $pv=$self->contents->[$index];
	 if (ref($pv)) {
	    my $prop=$pv->property;
	    if ($prop->temporary) {
	       delete $self->dictionary->{$prop};
	       ++$gap;
	    } elsif ($gap) {
	       $self->contents->[$index-$gap]=$self->contents->[$index];
	       $self->dictionary->{$prop}=$index-$gap;
	    }
	 } else {
	    ++$gap;
	 }
      }
      $#{$self->contents}-=$gap;
   }
   if ($self->changed && defined($self->persistent)  &&  @{$self->contents}) {
      $self->persistent->save($self);
      $self->changed=0;
   }
}
####################################################################################
sub save {
   # if with optional TRUE argument, then save temporaries too
   cleanup($_[0], $_[1] ? 0 : undef);
}
####################################################################################
sub need_cleanup {
   my ($self, $index)=@_;
   my $cleanup_action=($main::scope->cleanup->{$self} ||= [ undef ]);
   if (is_ARRAY($cleanup_action)) {
      assign_min($cleanup_action->[0], $index);
   }
}
####################################################################################
sub DESTROY {
   my $self=shift;
   if ($main::scope  and  my $action=delete $main::scope->cleanup->{$self}) {
      cleanup($self, @$action);
   }
}
####################################################################################

use Poly::Scheduler;

sub eval_input_list($$) {	# [ Property ] => true if ready
   my ($self, $input_list)=@_;
   my ($content_index, $pv);
   foreach my $prop (@$input_list) {
      return 1 if defined ($content_index=$self->dictionary->{$prop})
               && defined ($pv=$self->contents->[$content_index]);
   }
   0
}
####################################################################################
sub provide_request {
   my ($self, $request)=@_;
   my $request_rule=create Rule('request', $request, $Poly::Rule::is_function);
   my $chain=initial RuleChain($self,$request_rule);
   if ((my $success=$chain && $chain->resolve($self)) <= 0) {
      my @lacking;
      foreach my $input_list (@$request) {
	 if (! $self->eval_input_list($input_list)) {
	    push @lacking, "'" . join(" | ", map { $_->name } @$input_list) . "'";
	 }
      }
      die $success==0 ? "available data insufficient to make " : "no more rules available to make ",
          join(", ", @lacking), "\n";
   }
}
####################################################################################
# private:
sub _give {
   my $self=shift;
   my $give=pop @_;
   my $req=is_ARRAY($_[0]) ? \@_ : [ map { $self->prototype->encode_read_request($_) } @_ ];
   if (!@{$self->transactions} || !$self->transactions->[0]->activeRule) {
      provide_request($self, $req);
   }
   if (wantarray) {
      map { $give->($self,$_) } @$req;
   } elsif (defined wantarray  and  @$req==1) {
      $give->($self,@$req);
   }
}
sub give_pv {
   push @_, \&lookup_pv;
   &_give;
}
sub give {
   push @_, \&lookup;
   &_give;
}
####################################################################################
sub give_schedule {
   my $self=shift;
   my $request=is_ARRAY($_[0]) ? \@_ : [ map { $self->prototype->encode_read_request($_) } @_ ];
   my $request_rule=create Rule('request', $request, $Poly::Rule::is_function);
   my $chain=initial RuleChain($self,$request_rule);
   return $chain->resolve($self, 0);
}
####################################################################################
sub create_prop_accessor {
   my $prop=shift;
   my $accessor=sub : method { _prop_set_accessor($prop) ? &put : &get };
   declare_lvalue($accessor);
   $accessor;
}
####################################################################################
sub get {
   my ($self, $prop)=@_;
   my $content_index=$self->dictionary->{$prop};
   if (defined $content_index) {
      _get_alternatives();
   } else {
      my @alt=_get_alternatives();
    TRY_ALT: {
	 foreach my $alt_prop (@alt) {
	    $alt_prop=$self->prototype->lookup_property($alt_prop)
	    || die "unknown property $alt_prop\n";
	    last TRY_ALT if defined ($content_index=$self->dictionary->{$alt_prop});
	 }
	 my $trans=$self->transactions->[0];
	 if (defined($trans) && $trans->activeRule) {
	    if (@alt) {
	       die "attempt to access undefined properties ", join(" | ", map { $_->name} $prop, @alt), "\n";
	    } else {
	       die "attempt to access an undefined property ", $prop->name, "\n";
	    }
	 }
	 unshift @alt, $prop;
	 provide_request($self, [ \@alt ]);
	 foreach $prop (@alt) {
	    last if defined ($content_index=$self->dictionary->{$prop});
	 }
      }
   }
   $self->contents->[$content_index]->value;
}

####################################################################################
sub verify {
   my $self=shift;
   my $trans=$self->begin_transaction;
   $trans->keep_overwritten=1;
   $trans->extra->{inherit_keep_overwritten}=1;
   my $scope=new Scope();
   $scope->cleanup->{$self}=\&rollback_open;

   my $proto=$self->prototype;
   my (@request, $preferred);
 REQ:
   foreach (@_) {
      if (defined (my $prop=$proto->lookup_property($_))) {
	 if (defined (my $content_index=$self->dictionary->{$prop})) {
	    my $pv=$self->contents->[$content_index];
	    if (defined($pv)) {
	       $trans->backup->{$content_index}=$pv;
	       $self->contents->[$content_index]=undef;
	       push @request, [ $prop ];
	       next;
	    } else {
	       # maybe just repeating argument?
	       foreach my $seen (@request) {
		  next REQ if $seen == $prop;
	       }
	    }
	 }
	 die "cannot verify non-existing property $_\n";

      } elsif (defined (my $label=$proto->application->prefs->find_label($_))) {
	 $label->set_temp_preferred($scope);
	 $preferred=1;

      } else {
	 die "unknown property or label: '$_'\n";
      }
   }

   my $chain=new InitRuleChain($self, create Rule('verify', \@request, $Poly::Rule::is_function));
   restore($self, $trans);
   my $success= defined($chain) && do {
      $chain->gather_rules($self);
      $chain->constrain_to_preferred if $preferred;
      $chain->resolve($self, !$Switches::n);
   };
   if ($Switches::n && $success) {
      print $success->report, "\n";

   } elsif ($success <= 0) {
      if ($success<0) {
	 my @lacking;
	 foreach my $input_list (@request) {
	    my $prop=$input_list->[0];
	    my $content_index=$self->dictionary->{$prop};
	    if (! exists $trans->backup->{$content_index}) {
	       push @lacking, $prop->name;
	    }
	 }
	 die "no more rules available to verify " . join(", ", @lacking);
      } else {
	 die $preferred ? "no matching rules\n" : "data insufficient for verification\n";
      }
   }
}
####################################################################################
sub take_verify {
   my $self=shift;
   $main::scope->cleanup->{$self}=\&rollback_open;
   my $trans=$self->begin_transaction;
   $trans->keep_overwritten=1;
   $trans->extra->{inherit_keep_overwritten}=1;

   $self->begin_transaction;
   while (@_) {
      $self->take(splice @_, 0, 2);
   }
   $self->commit;
}

declare @EXPORT_METHODS=qw( verify );

1

# Local Variables:
# c-basic-offset:3
# End:
