#!/usr/bin/perl -w

#############################################################################
# Math/String/Sequence.pm -- defines a sequence or range of strings.
#
# Copyright (C) 2001 by Tels. All rights reserved.
#############################################################################

# the following hash values are used
# _first : first string
# _last	 : last string
# _set	 : charset for first/last
# _size	 : last-first
# _rev   : 1 if reversed sequence

package Math::String::Sequence;
use vars qw($VERSION);
$VERSION = 1.02;    # Current version of this package
require  5.005;     # requires this Perl version or later

use Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(sequence);
#@EXPORT = qw( );

use Math::BigInt;
use Math::String;
use Math::String::Charset;

use strict;
my $class = "Math::String::Sequence";

# some shortcuts for easier life
sub sequence
  {
  # exportable version of new
  return $class->new(@_);
  }

###############################################################################
# constructor

sub new
  {
  # takes the following arguments:
  # first, last: Math:Strings or scalars
  # charset: optional, if you pass a scalar as first or last

  my $class = shift;
  $class = ref($class) || $class;

  my $args;
  if (ref $_[0] eq 'HASH')
    {
    $args = shift;
    }
  else
    {
    $args->{first} = shift;
    $args->{last} = shift;
    $args->{charset} = shift;
    }

  my $self = {};
  bless $self, $class;
  if (ref $args eq $class)
    {
    # make copy
    for (qw/_first _last/)
      {
      $self->{$_} = Math::String->new($args->{$_});
      }
    return $self; 
    }
  my $first = $args->{first};
  my $last = $args->{last};
  my $set = $args->{charset};

  $first = Math::String->new($first,$set) unless ref $first;
  $last = Math::String->new($last,$set) unless ref $last;
 
  die ("first is NaN") if $first->is_nan();
  die ("last is NaN") if $last->is_nan();
  #die ("$first is not smaller than $last") if 
  # adjustment by $self->_size(): $self->{_rev} = $first > $last ? 1 : 0; 

  bless $self, $class;
  $self->{_first} = $first;
  $self->{_last} = $last;
  $self->_initialize();
  return $self; 
  }

#############################################################################
# private, initialize self 

sub _initialize
  {
  # init sequence
  my $self = shift;

  $self->_size();
  $self->{_set} = $self->{_first}->{_set};
  return $self;
  }

sub _size
  {
  # calculate new size and adjust _rev
  my $self = shift;

  $self->{_rev} = $self->{_first} < $self->{_last} ? 0 : 1; 
  $self->{_size} = $self->{_last} - $self->{_first};
  $self->{_size} = $self->{_size}->babs()->as_number();
  $self->{_size}++;
  return $self;
  }

#############################################################################
# public

sub charset
  {
  my $self = shift;
  return $self->{_first}->{_set};
  }

sub length
  {
  my $self = shift;
  return $self->{_size};
  }

sub is_reversed
  {
  # return true if the sequence is reversed, or false
  my $self = shift;
  return $self->{_rev};
  }

sub first
  {
  my $self = shift;
  if (defined $_[0])
    {
    $self->{_first} = shift;
    $self->{_first} = Math::String->new($self->{_first},$self->{_set})
      unless ref $self->{_first};
    $self->_size();
    }
  return $self->{_first};
  }

sub last
  {
  my $self = shift;
  if (defined $_[0])
    {
    $self->{_last} = shift;
    $self->{_last} = Math::String->new($self->{_last},$self->{_set})
      unless ref $self->{_last};
    $self->_size();
    }
  return $self->{_last};
  }

sub string
  {
  # return the Nth string in sequence or undef for out-of-range
  my $self = shift;
  my $nr = shift; $nr = 0 if !defined $nr;

  $nr = Math::BigInt->new($nr) unless ref $nr;
  my $n;
  if ($self->{_rev})
    {
    if ($nr < 0)
      {
      $n = $self->{_last}-$nr; $n--;
      }
    else
      {
      $n = $self->{_first}-$nr;
      } 
    return if $n > $self->{_first} || $n < $self->{_last};
    }
  else
    {
    if ($nr < 0)
      {
      $n = $self->{_last}+$nr; $n++;
      }
    else
      {
      $n = $self->{_first}+$nr;
      } 
    return if $n > $self->{_last} || $n < $self->{_first};
    }
  return $n;
  }

sub error
  {
  my $x = shift;
  return $x->{_set}->error();
  }

sub as_array
  {
  # return the sequence as array of strings
  my $x = shift;

  my @a;
  my $f = $x->{_first}; my $l = $x->{_last};
  if ($x->{_rev})
    {
    while ($f >= $l) { push @a,$f; $f--; }
    }
  else
    {
    while ($f <= $l) { push @a,$f; $f++; }
    }
  return @a;
  }

#############################################################################

=head1 NAME

Math::String::Sequence - defines a sequence (or range) of strings.

=head1 SYNOPSIS

	use Math::String::Sequence;
	use Math::String::Charset;

	$seq = Math::String::Sequence->new( a, zzz );		   # set a-z
	$seq = Math::String::Sequence->new( a, zzz, ['z'..'a'] );  # set z..a
	$seq = Math::String::Sequence->new( 
          { first => 'a', last => 'zzz', charset => ['z'..'a'] 
          } ); 							   # same
        $x = Math::String->new('a');
        $y = Math::String->new('zz');
	$seq = Math::String::Sequence->new( { 
          first => $x, last => $y, } );  			   # same

	print "length: ",$seq->length(),"\n";
	print "first: ",$seq->first(),"\n";
	print "last: ",$seq->last(),"\n";
	print "5th: ",$seq->string(5),"\n"; 	
	print "out-of-range: ",$seq->string(10000000),"\n"; 	   # undef	
	
	print "as array:: ",$seq->as_array(),"\n"; 	   	   # as array

=head1 REQUIRES

perl5.005, Exporter, Math::BigInt, Math::String, Math::String::Charset

=head1 EXPORTS

Exports nothing on default, but can export C<sequence()>.

=head1 DESCRIPTION

This module creates a sequence, or range of Math::Strings. Given a B<first> and
B<last> string it represents all strings in between, including B<first> and
B<last>. The sequence can be reversed, unlike 'A'..'Z', which needs the first
argument be smaller than the second.

=over 1

=item Default charset

The default charset is the set containing "abcdefghijklmnopqrstuvwxyz"
(thus producing always lower case output). If either C<first> or C<last> is
not an Math::String, they will get the given charset or this default.

=back

=head1 USEFULL METHODS

=head2 B<new()>

            new();

Create a new Math::String::Sequence object. Arguments are the 
first and last string, and optional charset. You can give a hash ref, that must
then contain the keys C<first>, C<last> and C<charset>.

=head2 B<length()>

            $sequence->length();

Returns the amount of strings this sequence contains, aka (last-first)+1.

=head2 B<is_reversed()>

            $sequence->is_reversed();

Returns true or false, depending wether the first string in the sequence
is smaller than the last.

=head2 B<first()>

            $sequence->first($length);

Return the first string in the sequence. The optional argument becomes the
new first string.

=head2 B<last()>

            $sequence->last($length);

Return the last string in the sequence. The optional argument becomes the
new last string.

=head2 B<charset()>

            $sequence->charset();

Return a reference to the charset of the Math::String::Sequence object.

=head2 B<string()>

            $sequence->string($n);

Returns the Nth string in the sequence, 0 beeing the C<first>. Negative
arguments count backward from C<last>, just like with arrays.

=head2 B<as_array()>

            @array = $sequence->as_array();

Returns the sequence as array of strings. Usefull for emulating things like

	foreach ('a'..'z')
	  {
          print "$_\n";
          }

via

	my $sequence = Math::String::Sequence->new('foo','bar');

	foreach ($sequence->as_array())
	  {
	  print "$_\n";
          }

Beware, might create HUGE arrays!

=head1 BUGS

None discovered yet.

=head1 AUTHOR

If you use this module in one of your projects, then please email me. I want
to hear about how my code helps you ;)

This module is (C) Tels http://bloodgate.com 2001.

=cut

1;
