The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
###############################################################################
#List.pm
#Last Change: 2009-28-01
#Copyright (c) 2009 Marc-Seabstian "Maluku" Lucksch
#Version 0.3
####################
#This file is part of the sofu.pm project, a parser library for an all-purpose
#ASCII file format. More information can be found on the project web site
#at http://sofu.sourceforge.net/ .
#
#sofu.pm is published under the terms of the MIT license, which basically means
#"Do with it whatever you want". For more information, see the license.txt
#file that should be enclosed with libsofu distributions. A copy of the license
#is (at the time of this writing) also available at
#http://www.opensource.org/licenses/mit-license.php .
###############################################################################

=head1 NAME

Data::Sofu::List - A Sofu List

=head1 DESCRIPTION

Provides a interface similar to the original SofuD (sofu.sf.net)

=head1 Synopsis 

	require Data::Sofu::List;
	my $list = Data::Sofu::List->new();
	$list->appendElement(Data::Sofu::Value->new($_)) foreach (0 .. 10);;

=head1 SYNTAX

This Module is pure OO, exports nothing

=cut


package Data::Sofu::List;
use strict;
use warnings;
require Data::Sofu::Object;
our @ISA = qw/Data::Sofu::Object/;
our $VERSION="0.3";

=head1 METHODS

Also look at C<Data::Sofu::Object> for methods, cause List inherits from it

=head2 new([DATA])
Creates a new C<Data::Sofu::List> and returns it

DATA has to be an Arrayhref

	$inc = Data::Sofu::List->new(\@INC);

=cut 

sub new {
	my $self={};
	$self->{List}=[];
	bless $self,shift;
	if (@_) {
		$self->set(@_);		
	}
	return $self;
}

=head2 set(DATA)

Sets the contents of this list (replaces the old contents).

DATA has to be an Arrayhref

	$inc->set(\@INC);

=cut

sub set {
	my $self=shift;
	local $_;
	#@{$self->{List}}=map {Data::Sofu::Object->new($_)} @_;
	my $temp=shift;
	foreach (@$temp) {
		$_=Data::Sofu::Object->new($_);
	}
	$self->{List}=$temp;
}

=head2 asList() 

Returns itself, used to make sure this List is really a List (C<Data::Sofu::Map> and C<Data::Sofu::Value> will die if called with this method)

=cut

sub asList {
	return shift;
}

=head2 asArray()

Perl only

Returns the list as a perl array.

=cut

sub asArray {
	my $self=shift;
	return @{$$self{List}};
}

=head2 isList()

Returns 1

=cut

sub isList {
	return 1;
}

=head2 object(INDEX) 

Return the object at the position INDEX in the List.

Dies if the List is shorter than INDEX.

=cut

sub object {
	my $self=shift;
	my $k=int shift;
	if (exists $self->{List}->[$k]) {
		return $self->{List}->[$k];
	}
	die "Requested object $k doesn't exists in this List";
}

=head2 hasElement(INDEX)

Deprecated!

Returns a true value if the List has an Element with the number INDEX

=cut

sub hasElement {
	my $self=shift;
	my $k=int shift;
	return exists $self->{List}->[$k];
}

=head2 hasObject(INDEX)

Returns a true value if the List has an Element with the number INDEX

=cut

sub hasObject {
	my $self=shift;
	my $k=int shift;
	return exists $self->{List}->[$k];
}

=head2 hasValue(INDEX) 

Returns 1 if this List has an Element at INDEX and this Element is a C<Data::Sofu::Value>.

	$inc->hasValue(2) === $inc->hasElement(2) and $inc->object(2)->isValue();

Note: Return 0 if the Object is not a Value and under if the Element doesn't exist at all.

=cut

sub hasValue {
	my $self=shift;
	my $k=int shift;
	return $self->{List}->[$k]->isValue() if exists $self->{List}->[$k];
	return undef;
}

=head2 hasMap(INDEX) 

Returns 1 if this List has an Element at INDEX and this Element is a C<Data::Sofu::Map>.

	$inc->hasMap(2) === $inc->hasElement(2) and $inc->object(2)->isMap();

Note: Return 0 if the Object is not a Map and under if the Element doesn't exist at all.

=cut

sub hasMap {
	my $self=shift;
	my $k=int shift;
	return $self->{List}->[$k]->isMap() if exists $self->{List}->[$k];
	return undef;
}

=head2 hasList(INDEX) 

Returns 1 if this List has an Element at INDEX and this Element is a C<Data::Sofu::List>.

	$inc->hasMap(2) === $inc->hasElement(2) and $inc->object(2)->isList();

Note: Return 0 if the Object is not a List and under if the Element doesn't exist at all.

=cut

sub hasList {
	my $self=shift;
	my $k=int shift;
	return $self->{List}->[$k]->isList() if exists $self->{List}->[$k];
	return undef;
}

=head2 list(INDEX)

Returns the Object at the postition "INDEX" as a C<Data::Sofu::List>.

Dies if the Object is not a C<Data::Sofu::List>.
	
	$inc->list(2) === $inc->object(2)->asList()

=cut

sub list {
	my $self=shift;
	return $self->object(shift(@_))->asList();
}

=head2 map(INDEX)

Returns the Object at the postition "INDEX" as a C<Data::Sofu::Map>.

Dies if the Object is not a C<Data::Sofu::Map>.
	
	$inc->map(2) === $inc->object(2)->asMap()

=cut

sub map {
	my $self=shift;
	return $self->object(shift(@_))->asMap();
}

=head2 value(INDEX)

Returns the Object at the postition "INDEX" as a C<Data::Sofu::Value>.

Dies if the Object is not a C<Data::Sofu::Value>.
	
	$inc->value(2) === $inc->object(2)->asValue()

=cut

sub value {
	my $self=shift;
	return $self->object(shift(@_))->asValue();
}

=head2 setElement(INDEX, VALUE)

Perl only (for now)

Sets the Element at INDEX to VALUE

	$inc->setElement(0,Data::Sofu::Value->new("."));

=cut 

sub setElement {
	my $self=shift;
	my $key = int shift;
	$self->{List}->[$key]=Data::Sofu::Object->new(shift);
}

=head2 next()

Iterartes over the List, return the next Element on every call and undef at the end of the List.

When called in void context it just resets the iterator.

=cut

sub next {
	my $self=shift;
	$self->{Iter} = 0 unless $self->{Iter};
	unless (defined wantarray) {
		$self->{Iter}=0;
		return;
	}
	if ($self->{Iter} > $#{$self->{List}}) {
		delete $self->{Iter};
		return undef;
	}
	
	return $self->{List}->[$self->{Iter}++];
}

=head2 splice(OFFSET, LENGTH, REPLACEMENT)

Perl only (for now)

Like Perl splice, replaces LENGTH Elements from OFFSET with REPLACEMENT, returns the replaced Elements.
	
	my $lib = new Data::Sofu::List();
	$inc->splice(2,0,Data::Sofu::Value->new("."),Data::Sofu::Value->new(".."),Data::Sofu::Value->new("../lib"));
		# Inserts 3 new Elements after the second Element

=cut

sub splice {
	my $self=shift;
	return CORE::splice(@{$self->{List}},@_);
}

=head2 spliceList(OFFSET, LENGTH, REPLACEMENT)

Perl only (for now)

Like splice, replaces LENGTH Elements from OFFSET with REPLACEMENT, returns the replaced Elements.

REPLACEMENT is another C<Data::Sofu::List>
	
	my $lib = new Data::Sofu::List(Data::Sofu::Value->new("."),Data::Sofu::Value->new(".."),Data::Sofu::Value->new("../lib"));
	$inc->spliceList(2,0,$lib);
		# Inserts the list $lib after the second Element.

=cut

sub spliceList {
	my $self=shift;
	my $off=shift;
	my $len=shift;
	my $rep=shift;
	return CORE::splice(@{$self->{List}},$off,$len,$rep->asArray());
}

=head2 appendElement(ELEMENT)

Appends one (or multiple (Perl only)) ELEMENT to the end of this List.

	$inc->appendElement(Data::Sofu::Value->new("lib/"));

=cut

sub appendElement {
	my $self=shift;
	local $_;
	push @{$self->{List}},map {Data::Sofu::Object->new($_)} @_;
}

=head2 firstElement()

Perl only (for now)

Removes and returns the first Element of this List

=cut

sub firstElement {
	my $self=shift;
	return shift @{$self->{List}};
}

=head2 lastElement()

Perl only (for now)

Removes and returns the last Element of this List

=cut

sub lastElement {
	my $self=shift;
	return pop @{$self->{List}};
}


=head2 insertElement(ELEMENT)

Perl only (for now)

Appends one (or multiple (Perl only)) ELEMENT to the front of this List.

	$inc->insertElement(Data::Sofu::Value->new("lib/"));

=cut

sub insertElement {
	my $self=shift;
	local $_;
	unshift @{$self->{List}},map {Data::Sofu::Object->new($_)} @_;
}

=head2 appendList(LIST)

Perl only (for now)

Appends another LIST to the end of this List.

	my $lib = new Data::Sofu::List(Data::Sofu::Value->new("."),Data::Sofu::Value->new(".."),Data::Sofu::Value->new("../lib"));
	$inc->appendList($lib);

=cut

sub appendList {
	my $self=shift;
	my $other=shift;
	push @{$self->{List}},$other->asArray();

}

=head2 insertList(LIST)

Perl only (for now)

Appends another LIST to the front of this List.

	my $lib = new Data::Sofu::List(Data::Sofu::Value->new("."),Data::Sofu::Value->new(".."),Data::Sofu::Value->new("../lib"));
	$inc->insertList($lib);

=cut

sub insertList {
	my $self=shift;
	my $other=shift;
	unshift @{$self->{List}},$other->asArray();

}

=head2 elementIndex(VALUE) 

Returns the index of the first Element that machtes VALUE

=cut

sub elementIndex {
	my $self=shift;
	my $o = shift;
	for (my $i=0; $i<@{$self->{List}};$i++) {
		return $i if $self->{List}->[$i] eq $o;
	}
	return undef;
}

=head2 clear(VALUE)

Perl only (for now)

Empties this list

=cut

sub clear {
	my $self=shift;
	$self->{List}=[];
}

=head2 length

Perl only (for now)

Returns the length of this List.

Note: The index of the last element is length-1!

=cut

sub length {
	my $self=shift;
	return scalar @{$self->{List}};
}


=head2 opApply()

Takes a Subroutine and iterates with it over this List. Values can't be modified.

The Subroutine takes one Argument: The Value.

	$inc->opApply(sub {
		print "Element = $_[0]->asValue->toString(),"\n";
	});

Note: The Values are Objects, so they still can be changed, but not replaced.

=cut

sub opApply {
	my $self=shift;
	my $code=shift;
	croak("opApply needs a Code Reference") unless ref $code and lc ref $code eq "code";
	foreach my $e (@{$self->{Map}}) { 
		my $element=$e;
		$code->($element);
	}
}


=head2 opApplyDeluxe()

Perl only.

Takes a Subroutine and iterates with it over this List. Values can be modified.

The Subroutine takes one Argument: The Value.

	$inc->opApplyDeluxe(sub {
		$_[0]=Data::Sofu::List(split /\//,$_[0]->asValue()->toString());
	});


Note: Please make sure every replaced Value is a C<Data::Sofu::Object> or inherits from it.

=cut

sub opApplyDeluxe {
	my $self=shift;
	my $code=shift;
	croak("opApplyDeluxe needs a Code Reference") unless ref $code and lc ref $code eq "code";
	foreach my $e (@{$self->{Map}}) { 
		$code->($e);
	}
}

=head2 storeComment(TREE,COMMENT) 

Stores a comment in the Object if TREE is empty, otherwise it propagades the Comment to all its Elements

Should not be called directly, use importComments() instead.

=cut

sub storeComment {
	my $self=shift;
	my $tree=shift;
	my $comment=shift;
	#print "Tree = $tree, Comment = @{$comment}\n";
	if ($tree eq "") {
		$self->{Comment}=$comment;
	}
	else {
		my ($value,$tree) = split(/\-\>/,$tree,2);
		#$value=Sofukeyunescape($value);
		$self->{List}->[$value]->storeComment($tree,$comment);
	}

}

=head2 stringify(LEVEL, TREE)

Returns a string representing this List and all its elements.

Runs string(LEVEL+1,TREE+index) on all its elements.

=cut


sub stringify {
	my $self=shift;
	my $level=shift;
	my $tree=shift;
	$level-=1 if $level < 0;
	my $str="";
	$str="Value = " unless $level;
	$str.="(";
	$str.=$self->stringComment();
	$str.="\n";
	my $i=0;
	foreach my $elem (@{$self->{List}}) {
		$str.=$self->indent($level);
		$str.=$elem->string($level+1,$tree."->".$i++);
	}
	$str.=$self->indent($level-1) if $level > 1;
	$str.=")\n";
	return $str;
}

=head2 binarify(TREE,BINARY DRIVER)

Returns the binary version of this List and all its elements using the BINARY DRIVER. Don't call this one, use binaryPack instead.

=cut


sub binarify {
	my $self=shift;
	my $tree=shift;
	my $bin=shift;
	my $str=$bin->packType(2);
	$str.=$self->packComment($bin);
	$str.=$bin->packLong(scalar @{$self->{List}});
	my $i=0;
	foreach my $elem (@{$self->{List}}) {
		$str.=$elem->binary("$tree->".$i++,$bin);
	}
	return $str;
}

=head1 BUGS

Some Methods here are not included in Sofud, but they should be so their name might change (Old ones will be preserved)

=head1 SEE ALSO

L<Data::Sofu>, L<Data::Sofu::Binary>, L<Data::Sofu::Object>, L<Data::Sofu::Map>, L<Data::Sofu::Value>, L<Data::Sofu::Undefined>, L<http://sofu.sf.net>

=cut 

1;