=head1 NAME 

iPE::Util::BoundaryCoordinate - The class for a boundary-relative coordinate.

=head1 DESCRIPTION

Many things in iPE are described relative to the two boundaries of an undetermined group of items, be it bases or features.

An example of this is the exon feature which occurs for all CDS features between the initial exon and the terminal exon.  The description (in a 0-based index) of this with boundary-relative coordinates is (1, N-2).

=head1 FUNCTIONS

=over 8

=cut

package iPE::Util::BoundaryCoordinate;
use Carp;
use strict;

=item new (coordinate, letter)

Pass in the coordinate as well as the letter which describes the coordinate as being relative to the end (usually "N" or "L").

=cut
sub new {
    my ($class, $coord, $letter) = @_;
    my $this = bless {}, $class;

    $this->{letter_} = $letter;
    $this->{coord_} = $coord;

    if($this->{coord_} =~ m/$letter/)   { $this->{isEnd_}  = 1 }
    else                                { $this->{isEnd_}  = 0 }
    $this->{numeral_} =  $this->{coord_};
    $this->{numeral_} =~ s/$letter//;
    if($this->{numeral_} =~ m/[^-\+\d]/ && $this->{numeral_} ne ".") {
        croak "Ill-formed coordinate: $coord\n".
              "Must contain only $letter, -, and numbers\n";
    }
    $this->{numeral_} = 0   if($this->{numeral_} eq "");

    return $this;
}

=item clone ()

Return a copy of the calling object

=cut
sub clone {
    my ($this) = @_;
    my $clone = bless { %$this }, ref($this);
    return $clone;
}

=item format ()

Format a BoundaryCoordinate for outputting (returns a string).

=cut
sub format { shift->coord }

=item coord ()

Returns the coordinate as passed into the constructor.

=item numeral ()

Returns the number without the letter in the coordinate.

=item letter ()

Returns the letter representing the high end boundary of the set as passed into the constructor (usually "N" or "L").

=item isEnd ()

Returns 1 if the coordinate is relative to the end-boundary.

=cut
sub coord   { shift->{coord_}   }
sub numeral { shift->{numeral_} }
sub letter  { shift->{letter_}  }
sub isEnd   { shift->{isEnd_}   }

=item translate (low, high)

Translates a boundary coordinate given a low and high boundary of a real interval.  If it is relative to the end boundary, the coordinate is translated by the end boundary, and if it is relative to the begin boundary, the coordinate is translated by the begin boundary.

=cut
sub translate {
    my ($this, $low, $high) = @_;

    if($this->isEnd)    { return $this->numeral+$high+1     }
    else                { return $this->numeral+$low        }
}

=item cmp (a, b)

Compares two boundary coordinates.  Useful for sorting.

=cut
sub cmp {
    use bytes;
    my ($first, $second) = @_;

    #this code predates boundary coordinates.
    my ($a, $b) = ($first->{coord_}, $second->{coord_});
    my $l;
    if($a =~ /([A-Za-z])/) { $l = $1 }
    if($b =~ /([A-Za-z])/) { 
        my $bl = $1;
        croak "Attempt to match disparate boundary coords $l $bl \n" 
            if(defined($l) && $l ne $bl);
        $l = $bl;
    }
    
    if(defined($l)) {
        if($a =~ /$l/ && $b !~ /$l/){ return 1; }
        elsif($a !~ /$l/ && $b =~ /$l/){ return -1; }
        elsif($a =~ /$l/ && $b =~ /$l/) {
            #get the L's and spaces out but leave the negative number
            my $re = "[$l ]";
            $a =~ s/$l//g;
            $b =~ s/$l//g;
            #if no digits remain in the string, it was just "L"
            if($a !~ /\d/) {
                if($b !~ /\d/) { return 1   }
                else           { return -1  }
            }
            elsif($b !~ /\d/) { return 1; }
        }
    }
    if($a < $b)                 { return -1; }
    elsif($a > $b)              { return 1;  }
    else                        { return 1;  }
}

=back

=head1 SEE ALSO

L<iPE::Interval>

=head1 AUTHOR

Bob Zimmermann (rpz@cse.wustl.edu)

(and he just keeps getting better.)

=cut

1;
