The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Text::Snippet::TabStop::Cursor;
BEGIN {
  $Text::Snippet::TabStop::Cursor::VERSION = '0.04';
}

# ABSTRACT: Provides a bi-directional iterator interface for moving from one tab-stop to it's siblings

use strict;
use warnings;
use Class::XSAccessor getters => {snippet => 'snippet'};
use Scalar::Util qw(blessed refaddr);
use Carp qw(croak);


sub new {
	my $class = shift;
	my %args = (i => -1, @_);
	croak "snippet must be ISA Text::Snippet" unless blessed $args{snippet} eq 'Text::Snippet';
	return bless \%args, $class;
}

sub has_prev {
	my $self  = shift;
	my $stops = $self->snippet->tab_stops;
	return @$stops && $self->{i} > 0;
}

sub prev {
	my $self = shift;
	return $self->has_prev ? $self->snippet->tab_stops->[ --$self->{i} ] : ();
}

sub has_next {
	my $self  = shift;
	my $stops = $self->snippet->tab_stops;
	return @$stops && $self->{i} < $#{$stops};
}

sub next {
	my $self = shift;
	return $self->has_next ? $self->snippet->tab_stops->[ ++$self->{i} ] : ();
}

sub current {
	my $self  = shift;
	return unless($self->{i} >= 0);
	my $stops = $self->snippet->tab_stops;
	if ( exists( $stops->[ $self->{i} ] ) ) {
		return $stops->[ $self->{i} ];
	}
	return;
}

sub _is_current {
	my $self = shift;
	my $current = $self->current;
	my $tab_stop = shift;
	return blessed($tab_stop) && (refaddr($tab_stop) == refaddr($current) || ($tab_stop->has_parent && refaddr($tab_stop->parent) == refaddr($current)));
}

sub current_position {
	my $self = shift;
	my $current = $self->current;
	my ($line, $column) = (0,0);
	return [$line,$column] if ! defined $current;
	
	foreach my $c(@{ $self->snippet->chunks }){
		last if($self->_is_current($c));
		my $text = blessed($c) && $c->can('to_string') ? $c->to_string || '': "$c";
		foreach my $char(split(/(\n)/,$text)){
			if($char eq "\n"){
				$line++;
				$column = 0;
			} else {
				$column += length($char);
			}
		}
	}
	return [$line,$column];
}

sub current_char_position {
	my $self = shift;
	my $pos = 0;
	my $current = $self->current;
	return $pos if ! defined $current;
	foreach my $c( @{ $self->snippet->chunks } ){
		last if($self->_is_current($c));
		my $text = blessed($c) && $c->can('to_string') ? $c->to_string || '': "$c";
		$pos += length($text);
	}
	return $pos;
}

sub is_terminal {
	my $self = shift;
	return if(!defined$self->current);
	return $self->current->index == 0;
}

1;

__END__
=pod

=head1 NAME

Text::Snippet::TabStop::Cursor - Provides a bi-directional iterator interface for moving from one tab-stop to it's siblings

=head1 VERSION

version 0.04

=head1 CLASS METHODS

=head2 new

=head1 INSTANCE METHODS

=over 4

=item * snippet

Maintains a reference to the snippet that this cursor is iterating over.

=item * has_prev

Returns true/false depending on whether the cursor can move to a previous
tab stop.

=item * prev

Moves the cursor to the previous tab stop and returns that tab stop.
You can only iterate one element off the end of the underlying set of
tab stops.

=item * has_next

Returns true/false depending on whether the cursor can move to a
subsequent tab stop.

=item * next

Moves the cursor to the next tab stop and returns that tab stop.  You can
only iterate one element off the end of the underlying set of tab stops.

=item * current

Returns the tab stop the cursor is currently pointing at.  When the cursor
is first created, this method will always return C<undef> until C<next>
has been called at least once.

=item * current_position

Returns an ArrayRef reflecting the line/column position relative to
the beginning of the snippet.  Both numbers are zero-based so a tab
stop starting on the first line, first character would return a value
of C<[0,0]>.

=item * current_char_position

Returns an integer reflecting the current cursor position where 0 is the 
first character of the snippet and each character is counted up until
the current position of the cursor.

=item * is_terminal

Returns true if this tab stop is a "terminal" tab stop (i.e. once the
user iterates to this tab stop, the iterator should be restored and
normal editing should resume).

=back

=head1 AUTHOR

  Brian Phillips <bphillips@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2010 by Brian Phillips.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut