# vim:ts=4 sw=4
# ----------------------------------------------------------------------------------------------------
#  Name		: Class::STL::Iterators.pm
#  Created	: 22 February 2006
#  Author	: Mario Gaffiero (gaffie)
#
# Copyright 2006-2007 Mario Gaffiero.
# 
# This file is part of Class::STL::Containers(TM).
# 
# Class::STL::Containers 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; version 2 of the License.
# 
# Class::STL::Containers 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.
# 
# You should have received a copy of the GNU General Public License
# along with Class::STL::Containers; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# ----------------------------------------------------------------------------------------------------
# Modification History
# When          Version     Who     What
# ----------------------------------------------------------------------------------------------------
# TO DO:
# ----------------------------------------------------------------------------------------------------
package Class::STL::Iterators;
require 5.005_62;
use strict;
use warnings;
use vars qw( $VERSION $BUILD @ISA @EXPORT_OK %EXPORT_TAGS );
require Exporter;
@ISA = 'Exporter';
my @export_names = qw( 
	iterator 
	bidirectional_iterator 
	reverse_iterator 
	forward_iterator 
	distance 
	advance 
	back_insert_iterator 
	front_insert_iterator 
	back_inserter 
	front_inserter 
	insert_iterator 
	inserter 
);
@EXPORT_OK = (@export_names);
%EXPORT_TAGS = ( all => [@export_names] );
$VERSION = '0.18';
$BUILD = 'Thursday April 27 23:08:34 GMT 2006';
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators;
	use vars qw( $AUTOLOAD );
	sub AUTOLOAD
	{
		(my $func = $AUTOLOAD) =~ s/.*:://;
		return Class::STL::Iterators::BiDirectional->new(@_) if ($func eq 'iterator');
		return Class::STL::Iterators::BiDirectional->new(@_) if ($func eq 'bidirectional_iterator');
		return Class::STL::Iterators::Forward->new(@_) if ($func eq 'forward_iterator');
		return Class::STL::Iterators::Reverse->new(@_) if ($func eq 'reverse_iterator');
		return Class::STL::Iterators::Abstract::distance(@_) if ($func eq 'distance');
		return Class::STL::Iterators::Abstract::advance(@_) if ($func eq 'advance');
		return Class::STL::Iterators::BackInsertIterator->new(@_) if ($func eq 'back_insert_iterator');
		return Class::STL::Iterators::FrontInsertIterator->new(@_) if ($func eq 'front_insert_iterator');
		return Class::STL::Iterators::Abstract::back_inserter(@_) if ($func eq 'back_inserter');
		return Class::STL::Iterators::Abstract::front_inserter(@_) if ($func eq 'front_inserter');
		return Class::STL::Iterators::InsertIterator->new(@_) if ($func eq 'insert_iterator');
		return Class::STL::Iterators::Abstract::inserter(@_) if ($func eq 'inserter');
	}
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::Abstract;
	use base qw(Class::STL::Element); 
	use Carp qw(confess);
	use overload '++' => 'next', '--' => 'prev', '=' => 'clone', 'bool' => '_bool',
		'+' => 'advance', '+=' => 'advance', '-' => 'retreat', '-=' => 'retreat',
		'==' => 'eq', '!=' => 'ne', '>' => 'gt', '<' => 'lt', '>=' => 'ge', '<=' => 'le', '<=>' => 'cmp';
	use Class::STL::ClassMembers qw(p_container),
		Class::STL::ClassMembers::DataMember->new(name => 'arr_idx', default => -1);
	use Class::STL::ClassMembers::Constructor;
	sub p_element
	{
		my $self = shift;
		return $self->arr_idx() < 0 || $self->arr_idx() >= $self->p_container()->size()
			? 0
			: ${$self->p_container()->data()}[$self->arr_idx()]
	}
	sub idx_check # (void)
	{
		my $self = shift;
		$self->arr_idx($self->p_container()->size()-1) if ($self->arr_idx() >= $self->p_container()->size());
		$self->arr_idx(-1) if ($self->arr_idx() < 0);
		return;
	}
	sub at_end # (void)
	{
		my $self = shift;
		$self->idx_check();
		return $self->arr_idx() == -1 ? 1 : 0;
	}
	sub prev # (void)
	{
		my $self = shift;
		$self->idx_check();
		return $self->last() if ($self->arr_idx() == -1);
		(!$self->p_container()->size() || $self->arr_idx() == 0)
			? $self->arr_idx(-1)
			: $self->arr_idx($self->arr_idx() -1);
		return $self; # iterator
	}
	sub next # (void)
	{
		my $self = shift;
		$self->idx_check();
		return $self if ($self->arr_idx() == -1);
		(!$self->p_container()->size() || $self->arr_idx()+1 >= $self->p_container()->size())
			? $self->arr_idx(-1)
			: $self->arr_idx($self->arr_idx() +1);
		return $self; # iterator
	}
	sub first # (void)
	{
		my $self = shift;
		$self->idx_check();
		(!$self->p_container()->size())
			? $self->arr_idx(-1)
			: $self->arr_idx(0);
		return $self; # iterator
	}
	sub last # (void)
	{
		my $self = shift;
		$self->idx_check();
		(!$self->p_container()->size())
			? $self->arr_idx(-1)
			: $self->arr_idx($self->p_container()->size()-1);
		return $self; # iterator
	}
	sub front_inserter # (container) -- static function
	{
		my $c = shift;
		confess "A front_insert_iterator can only be used with a container that defines the push_front() member function\n"
			unless (ref($c) && $c->isa('Class::STL::Containers::Abstract') && $c->can('push_front'));
		return Class::STL::Iterators::FrontInsertIterator->new(p_container => $c);
	}
	sub back_inserter # (container) -- static function
	{
		my $c = shift;
		confess "A back_insert_iterator can only be used with a container that defines the push_back() member function\n"
			unless (ref($c) && $c->isa('Class::STL::Containers::Abstract') && $c->can('push_back'));
		return Class::STL::Iterators::BackInsertIterator->new(p_container => $c);
	}
	sub inserter # (container, iterator) -- static function
	{
		my $c = shift;
		my $i = shift;
		confess "Usage:inserter(container, iterator)"
			unless (ref($c) && $c->isa('Class::STL::Containers::Abstract')
				&& ref($i) && $i->isa('Class::STL::Iterators::Abstract'));
		return Class::STL::Iterators::InsertIterator->new(p_container => $c, arr_idx => $i->arr_idx());
	}
	sub distance # (iterator, iterator) -- static function
	{
		my $iter_start = shift;
		my $iter_finish = shift;
		confess "@{[ __PACKAGE__ ]}::distance usage:\ndistance( iterator-start, iterator-finish );"
			unless (
				defined($iter_start) && ref($iter_start) && $iter_start->isa('Class::STL::Iterators::Abstract')
				&& defined($iter_finish) && ref($iter_finish) && $iter_finish->isa('Class::STL::Iterators::Abstract')
				&& $iter_start->p_container() == $iter_finish->p_container()
			);
		return -1 if ($iter_start->at_end() && $iter_finish->at_end());
		return -1 if ($iter_start->at_end() || $iter_start->gt($iter_finish));
		return $iter_finish->p_container()->size()-1 - $iter_start->arr_idx() if ($iter_finish->at_end());
		return $iter_finish->arr_idx() - $iter_start->arr_idx();
	}
	sub advance # (size) -- static function
	{
		my $iter = shift;
		my $size = shift;
		if ($size >= 0)
		{
			for (my $i=0; $i<$size; ++$i) { $iter->next(); }
		}
		else
		{
			for (my $i=$size; $i!=0; ++$i) { $iter->prev(); }
		}
		return $iter;
	}
	sub retreat # (size) -- static function
	{
		my $iter = shift;
		my $size = shift;
		return $iter->advance(-$size);
		return $iter;
	}
	sub eq # (element)
	{
		my $self = shift;
		my $other = shift;
		return 
#?			$self->p_container() == $other->p_container()
			$self->arr_idx() == $other->arr_idx();
	}
	sub ne # (element)
	{
		my $self = shift;
		return $self->eq(shift) ? 0 : 1;
	}
	sub gt # (element)
	{
		my $self = shift;
		my $other = shift;
		return !$self->at_end() && !$other->at_end()
#?			&& $self->p_container() == $other->p_container()
			&& $self->arr_idx() > $other->arr_idx();
	}
	sub ge # (element)
	{
		my $self = shift;
		my $other = shift;
		return !$self->at_end() && !$other->at_end()
#?			&& $self->p_container() == $other->p_container()
			&& $self->arr_idx() >= $other->arr_idx();
	}
	sub lt # (element)
	{
		my $self = shift;
		my $other = shift;
		return !$self->at_end() && !$other->at_end()
#?			&& $self->p_container() == $other->p_container()
			&& $self->arr_idx() < $other->arr_idx();
	}
	sub le # (element)
	{
		my $self = shift;
		my $other = shift;
		return !$self->at_end() && !$other->at_end()
#?			&& $self->p_container() == $other->p_container() # -- don't want overloaded == !!
			&& $self->arr_idx() <= $other->arr_idx();
	}
	sub cmp # (element)
	{
		my $self = shift;
		my $other = shift;
		return $self->eq($other) ? 0 : $self->lt($other) ? -1 : 1;
	}
	sub _bool
	{
		my $self = shift;
		return $self;
	}
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::BiDirectional;
	use base qw(Class::STL::Iterators::Abstract); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::Forward;
	use base qw(Class::STL::Iterators::BiDirectional); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
	use Class::STL::ClassMembers::Disable qw(prev);
	use Class::STL::ClassMembers::Disable qw(last);
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::Reverse;
	use base qw(Class::STL::Iterators::BiDirectional); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
	sub last # (void)
	{
		my $self = shift;
		return $self->SUPER::first(); # iterator
	}
	sub first # (void)
	{
		my $self = shift;
		return $self->SUPER::last(); # iterator
	}
	sub next # (void)
	{
		my $self = shift;
		return $self->SUPER::prev(); # iterator
	}
	sub prev # (void)
	{
		my $self = shift;
		return $self->SUPER::next(); # iterator
	}
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::BackInsertIterator;
	use base qw(Class::STL::Iterators::Abstract); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
	sub assign # (element)
	{
		my $self = shift;
		$self->p_container()->push_back(@_);
	}
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::FrontInsertIterator;
	use base qw(Class::STL::Iterators::Abstract); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
	sub assign # (element)
	{
		my $self = shift;
		$self->p_container()->push_front(@_);
	}
}
# ----------------------------------------------------------------------------------------------------
{
	package Class::STL::Iterators::InsertIterator;
	use base qw(Class::STL::Iterators::Abstract); 
	use Class::STL::ClassMembers;
	use Class::STL::ClassMembers::Constructor;
	sub assign # (element)
	{
		my $self = shift;
		if (!$self->p_container()->size() || $self->at_end())
		{
			$self->p_container()->push(@_);
		}
		else
		{
			CORE::splice(@{$self->p_container()->data()}, $self->arr_idx(), 0, 
				grep(ref && $_->isa('Class::STL::Element'), @_));
		}
		return $self->next();
	}
}
# ----------------------------------------------------------------------------------------------------
1;