The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Games::Rezrov::ZProperty;
# object properties

use strict;

use constant FIRST_PROPERTY => -1;
# used to find the first property in the object

use Games::Rezrov::Inliner;
use Games::Rezrov::InlinedPrivateMethod;

my $code = new Games::Rezrov::InlinedPrivateMethod("-manual" => 1,
						   "-names" =>
						   [ qw (
							 _property_exists
							 _property_number
							 _property_len
							 _property_offset
							 _size_byte
							 _pointer
							 _pre_v4
							 _zobj
							 _search_id
							)
						   ],
						  );
Games::Rezrov::Inliner::inline($code);

#print $$code; die;

eval $$code; die $@ if $@;

1;

__DATA__

sub property_exists {
  # public, read-only
  return $_[0]->_property_exists();
}

sub property_number {
  # public, read-only
  return $_[0]->_property_number();
}

sub get_value {
  # return this value for this property
  if ($_[0]->_property_exists()) {
    # this object provides this property
    my $len = $_[0]->_property_len();
    my $v;
    if ($len == 2) {
      $v = GET_WORD_AT($_[0]->_property_offset());
    } elsif ($len == 1) {
      $v = GET_BYTE_AT($_[0]->_property_offset());
    } else {
      die "get_value() called on long property";
    }

    if (Games::Rezrov::ZOptions::SNOOP_PROPERTIES()) {
      printf STDERR "[get property %s of %s (%s) = %s (size=%d)\n",
	$_[0]->property_number(),
	  $_[0]->_zobj()->object_id(),
	    ${$_[0]->_zobj()->print()},
	      $v,
		$len;
  }

    return $v;
  } else {
    # object does not provide this property: get default value
    return $_[0]->get_default_value();
  }
}

sub next {
  # search for a specific property, or move to the next one
  my ($self, $search_id) = @_;
  die("attempt to read past end of property list")
    if ($self->_size_byte() == 0);
  my $pointer = $self->_pointer();

  my $property_number;
  my $exists = 0;
  my $size_byte;
  my $property_len;
  my $last_id;
  my $property_offset = 0;
  my $pre_v4 = $self->_pre_v4();
  while (1) {
#    print STDERR "search\n";
    $size_byte = GET_BYTE_AT($pointer);
    if ($size_byte == 0) {
      $property_number = 0;
      last;
    } else {
      my $size_bytes = 1;
      if ($pre_v4) {
	# spec 12.4.1:
	$property_number = $size_byte & 0x1f;
	# property number is in bottom 5 bytes
	$property_len = ($size_byte >> 5) + 1;
	# 12.4.1: shifted value is # of bytes minus 1
      } else {
	# spec 12.4.2:
	$property_number = $size_byte & 0x3f;
	# property number in bottom 6 bits
	if (($size_byte & 0x80) > 0) {
	  # top bit is set, there is a second size byte
	  $property_len = GET_BYTE_AT($pointer + 1) & 0x3f;
	  # length in bottom 6 bits
	  $size_bytes = 2;
	  if ($property_len == 0) {
	    # 12.4.2.1.1
#	    print STDERR "wacky inform compiler size; test this!";
	    $property_len = 64;
	  }
	} else {
	  # 14.2.2.2
	  $property_len = ($size_byte & 0x40) > 0 ? 2 : 1;
	}
      }
      $property_offset = $pointer + $size_bytes;
      $pointer += $size_bytes + $property_len;
    }

    if (!(defined $search_id) or $search_id == FIRST_PROPERTY) {
      # move to next/first property
      $exists = 1;
      last;
    } else {
      if ($last_id and $property_number > $last_id) {
	# 12.4: properties are stored in descending numerical order
	# this means we are past the end
	# ...need example case here!
	last;
      } elsif ($search_id > $property_number) {
	# went past where it would have been had it existed
	last;
      } else {
	$last_id = $property_number;
	if ($property_number == $search_id) {
	  #      print STDERR "got it\n";
	  $exists = 1;
	  last;
	  # 12.4.1
	}
      }
    }
  }
  $self->_property_exists($exists);
#  print STDERR "exists: $exists\n";
  $self->_property_len($property_len);
  $self->_property_number($property_number);
  $self->_size_byte($size_byte);
  $self->_property_offset($property_offset);
  $self->_pointer($pointer);
}

sub get_default_value {
  # get the default value for this property ID
  # spec 12.2
  my $offset = Games::Rezrov::StoryFile::header()->object_table_address() +
    (($_[0]->_search_id() - 1) * 2);
  # FIX ME
  return(GET_WORD_AT($offset));
}

sub new {
  my ($type, $search_id, $zobj, $psi) = @_;

#  printf STDERR "new zprop %s for obj %s\n", $search_id, $zobj->object_id();

  my $self = [];
  bless $self, $type;

  $self->_zobj($zobj);
  $self->_pre_v4(Games::Rezrov::StoryFile::version() <= 3);
  $self->_search_id($search_id);

  $self->_size_byte(-1);
  $self->_pointer($psi);
  $self->_property_offset(-1);
  $self->next($search_id);
  return $self;
}

sub set_value {
  # set this property to specified value
  my ($self, $value) = @_;
  if ($self->_property_exists()) {
#    print STDERR "set_value to $value\n";
    my $len = $self->_property_len();
    my $offset = $self->_property_offset();
    if (Games::Rezrov::ZOptions::SNOOP_PROPERTIES()) {
      Games::Rezrov::StoryFile::write_text(sprintf("[set property %d of %s (%s) = %d]",
					   $self->_property_number(),
					   $self->_zobj()->object_id(),
					   ${$self->_zobj()->print()},
                                           $value), 1);
    }
    if ($len == 1) {
      Games::Rezrov::StoryFile::set_byte_at($offset, $value);
    } elsif ($len == 2) {
      Games::Rezrov::StoryFile::set_word_at($offset, $value);
    } else {
      die("set_value called on long property");
    }
  } else {
    die("attempt to set nonexistent property") unless $Games::Rezrov::IGNORE_PROPERTY_ERRORS;
    # cheating
  }
}

sub get_data_address {
  return $_[0]->_property_offset();
}

sub get_next {
  # return a new ZProperty object representing the property 
  # after this one.  total hack!
  my $self = shift;
  my $next = [];
  bless $next, ref $self;
  @{$next} = @{$self};
  # make a copy of of $self
  $next->next();
  # make new property point to the next one in the list
  return $next;
}

1;