package RISCOS::Chunkfile;

require Exporter;
use RISCOS::File 0.02 qw(load);
use RISCOS::Chunk;
use Carp;
use strict;
use vars qw ($VERSION);

$VERSION = 0.04;

sub new ($) {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $chunks;

    if (ref($_[0]) eq 'ARRAY') {
	$chunks = shift;
    } elsif (ref($_[0]) eq 'RISCOS::Chunkfile') {
	$chunks = shift->Chunks();
    } else {
	my $file = &load;	# Pass on our arguments
	unless (defined $file) {
	    carp 'Could not load file' if $^W;
	    return undef;
	}
	my ($tag, $maxchunks, $numchunks) = unpack 'V3', $file;

	if ($tag != 0xC3CBC6C5) {
	    carp 'Not a chunk file' if $^W;
	    return undef;
	}

	if (length $file < 12 + 16 * $maxchunks) {
	    carp sprintf ('Chunk file with $maxchunks entries expected to be '
			  . '%d, actually %d', (12 + 16 * $maxchunks),
			  length $file)
	      if $^W;
	    return undef;
	}
        
	$chunks = [];

#	@$chunks = $numchunks;

	for (my $chunk = 0; $chunk < $maxchunks; $chunk++) {
	    my ($chunkid, $offset, $size) =
	      unpack 'A8V2', substr $file, 12 + 16 * $chunk, 16;
	
	    if ($offset) {
		carp "Used chunk '$chunkid' at $maxchunks - expected only "
		     . "$numchunks used" if ($chunk >= $numchunks);
	    } else {
		if ($chunk < $numchunks) {
		    carp "Unused chunk $maxchunks - expected $numchunks used";
		} else {
		    next;	# Unused
		}
	    }
	
	    push @$chunks, new RISCOS::Chunk $chunkid,
					     substr ($file, $offset, $size),
					     $size, $chunk, $offset;
	}
    }

    my $self  = {};
    my $hash  = {};
    $self->{'CHUNKS'} = $chunks;
    
    my $index = @$chunks;
    
    while ($index--) {
	my $chunk = $chunks->[$index];
	unshift @{$$hash{$chunk->ID}}, $index;
	# Erk ! :-)
	# It's a reference to a hash
	# The hash keys are the ID's
	# The hash entries are reference to arrays of all the chunks where this
	# ID can be found
    }

    $self->{'INDEX'} = $hash;

    bless ($self, $class);
}	# Would you believe that apart from missing this } this beastie worked
	# first time!

sub Chunks ($) {
    my $self = shift;
    $self->{'CHUNKS'};
}

sub Index ($) {
    my $self = shift;
    $self->{'INDEX'};
}

sub Lookup ($$) {
    my $self = shift;
    my $index = $self->{'INDEX'};
    $index->{$_[0]};
}

sub By_Number ($$) {
    my $self = shift;
    $self->{'CHUNKS'}->[$_[0]];
}
sub Single ($$) {
    my $self = shift;
    my $index = $self->{'INDEX'};
    my $array = $index->{$_[0]};
    
    return (@$array == 1) ? $array->[0] : undef;
}

sub Multiple ($$) {
    my $self = shift;
    my $index = $self->{'INDEX'};
    my $array = $index->{$_[0]};
    
    return (defined $array and @$array > 1) ? @$array : ();
}

sub Chunk ($$) {
    my $self = shift;
    my $index = $self->{'INDEX'};
    my $array = $index->{$_[0]};

    return (defined $array and @$array == 1) ? $self->{'CHUNKS'}->[$array->[0]] 
					     : undef;
}

1;
__END__

=head1 NAME

RISCOS::Chunkfile -- class for manipulating Acorn chunkfiles (AOF & ALF)

=head1 SYNOPSIS

    use RISCOS::Chunkfile;
    my $chunks = new RISCOS::Chunkfile $file;

=head1 DESCRIPTION

C<RISCOS::Chunkfile> provides a class for manipulating Acorn chunkfiles
(I<e.g.> I<AOF> - B<A>RM B<O>bject B<F>ormat and I<ALF> - B<A>corn B<L>ibrary
B<F>ormat). Currently this module is only used by the AOF and ALF modules.

C<RISCOS::Chunkfile> provides the following methods:

=over 4

=item new <array_ref>

=item new <file>

If passed a reference to an array it is assumed to be an array of
C<RISCOS::Chunk> objects to use as the file contents. Otherwise calls
C<RISCOS::File::load> to loads the file specified using and checks that it is a
chunkfile. Hence I<file> can be a filename, a reference to a filehandle, or a
reference to a scalar which is used as the file's contents.

If passed an array reference then this is used internally in the object, so
should be created with the anonymous array constructor C<[]> rather than a
reference to a named array variable (see  L<perldsc/Common Mistakes>).

Returns undefined if there was an error, or the file contents are corrupt.

=item Chunks

Returns a reference to the array of C<RISCOS::Chunk> objects, which are in the
same order as found in the file. Treat this as B<read only>. Changing the order
or number of the chunks will confuse all the other lookup methods. If you need
to manipulate the chunks pass this array back to C<new> to create a new
chunkfile object, and delete the old object (most simply by reusing the same
named variable).

=item Index

Returns a reference to a hash which indexes the array of C<RISCOS::Chunk>
objects returned by C<chunks>. The hash keys are the textual chunk IDs from the
chunk header, the hash entries are reference to arrays containing the numbers of
all the chunks where this ID can be found. For most IDs the array referenced
will have only one entry (for example an C<ALF> file will contain only one
C<LIB_DIRY> for the library directory) but where an ID occurs multiple times in
the chunk header the reference will be to an array of the indexes of all these
chunks (I<e.g.> in an C<ALF> library each object file is stored in a C<LIB_DATA>
chunk -  C<$index->{LIB_DATA}> will be a reference to the IDs of all of the
chunks containing object files).

=item Lookup <chunkID>

Returns a reference to the array of chunk indexes in the file where this chunk
ID is present. Effectively the value from a lookup on the hash returned by
C<Keys>, so will return undefined if no chunks in the file have this chunk ID.

=item By_Number <chunk_index>

Returns the C<RISCOS::Chunk> object at I<chunk_index> position in the file.
Effectively the value from the array returned by C<Chunks>.

=item Single <chunkID>

Returns the index of the only chunk in the file with ID I<chunkID>, or undefined
if zero or multiple chunks have this ID.

=item Multiple <chunkID> {

Returns the list of chunks in the file with ID I<chunkID>, or an empty list if
if less than two chunks have this ID.

=item Chunk <chunkID>

Returns the actual chunk of the only chunk in the file with ID I<chunkID>, or
undefined if zero or multiple chunks have this ID.

Effectively a call to C<By_Number> with the value returned from C<Single>

=back

=head1 EXAMPLE

To display the index of chunks in a chunkfile

    use RISCOS::Chunkfile;
    
    my $chunks = new RISCOS::Chunkfile $file;
    
    print "$file:\n";
    
    foreach my $id (sort keys %{$chunks->Index})
    {
	print "\t$id\t", join (' ', @{$chunks->Lookup ($id)}), "\n";
    }

=head1 BUGS

At present there is no standard way to return errors/diagnostics to the caller.
Currently C<new> will call C<warn> if warnings are turned on.

=head1 AUTHOR

Nicholas Clark <F<nick@unfortu.net>>
