The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use v6;

=for TODO

    * compare
    * size - accept a function; use as_list
    * as_list
    * contains
    * include Span::Code in Span.pm API
    * document that stringify doesn't show all elements  '
    * set_start / set_end

=cut

class Span::Code-0.01
{
    has Recurrence $.recurrence;
    has $.span;

submethod BUILD ( $.span, $.recurrence ) {}

method stringify ($self: ) {
    my @start;
    my @end;
    my $samples = 3;
    my $tmp = -Inf;
    for ( 1 .. $samples ) {
        $tmp = $self.next( $tmp );
        last if $tmp == Inf;
        push @start, $tmp;
    }
    $tmp = Inf;
    for ( 0 .. $samples ) {
        $tmp = $self.previous( $tmp );
        last if $tmp == -Inf;
        unshift @end, $tmp;
    }
    return '' unless @start;
    # if @start and @end intersect, don't print ".."
    if @end[0] == any( @start ) {
        push @start, @end;
        return @start.uniq.join(',');
    }
    shift @end;
    return @start.uniq.join(',') ~ '..' ~ @end.uniq.join(',');
}

method size () returns Object {
    # TODO - not lazy
    # TODO - empty set
    return undef;
}

method is_empty ($self: ) {
    return ! defined( $self.start );
}

method start ($self: ) {
    my $tmp = $self.next( -Inf );
    return $tmp == Inf ?? undef !! $tmp;
}

method end ($self: ) {
    my $tmp = $self.previous( Inf );
    return $tmp == -Inf ?? undef !! $tmp;
}

method start_is_closed () { return Bool::True }
method start_is_open   () { return Bool::False }
method end_is_closed   () { return Bool::True }
method end_is_open     () { return Bool::False }

method compare ($self: $set2 is copy) returns int {
    my $set1 = $self.span;
    my $cmp = $set1.compare( $set2 );
    return $cmp unless $cmp == 0;

    # TODO - this is hard
    ...
}

method contains ($self: $span is copy) returns bool {
    ...
}

method intersects ($self: $span is copy) returns bool {
    return Bool::False if ! $.span.intersects( $span );

    my $tmp = $self.intersection( $span );
    return ! $tmp.is_empty;
}

method union ($set1: $set2 is copy) {
    if ( $set2.isa( 'Span::Num' ) || $set2.isa( 'Span::Int' ) )
    {
        my @diff = $.span.difference( $set2 );
        @diff = @diff.map:{ $set1.new( recurrence => $.recurrence, span => $_ ) };
        my $intersect = $set1.new( recurrence => $.recurrence.get_universe, span => $set2 );
        return ( @diff, $intersect ).sort:{ $^a.compare( $^b ) };
    }

    # XXX - why this doesn't work?
    # my @set1_span = $set1.span.difference( $set2.span );

    # TODO - simplify the result if both sets have the same recurrence

    my $span1 = $set1.span;
    my $span2 = $set2.span;

    my @set1_span = $span1.difference( $span2 );
    my @set2_span = $span2.difference( $span1 );
    my @intersect = $span1.intersection( $span2 );

    my $union_recurr = $set1.recurrence.union( $set2.recurrence );

    @set1_span = @set1_span.map:{ $set1.new( recurrence => $set1.recurrence, span => $_ ) };
    @set2_span = @set2_span.map:{ $set2.new( recurrence => $set2.recurrence, span => $_ ) };
    @intersect = @intersect.map:{ $set1.new( recurrence => $union_recurr,    span => $_ ) };

    return ( @set1_span, @set2_span, @intersect ).sort:{ $^a.compare( $^b.span ) };

    # XXX - why this doesn't work?
    #.sort:{ $^a.span.compare( $^b.span );
}

method intersection ($set1: $set2 is copy) {
    if ( $set2.isa( 'Span::Num' ) || $set2.isa( 'Span::Int' ) )
    {
        my @inter = $.span.intersection( $set2 );
        @inter = @inter.map:{ $set1.new( recurrence => $.recurrence, span => $_ ) };
        return @inter;
    }
    my $span1 = $set1.span;
    my $span2 = $set2.span;
    my @intersect = $span1.intersection( $span2 );
    my $inter_recurr = $set1.recurrence.intersection( $set2.recurrence );
    @intersect = @intersect.map:{ $set1.new( recurrence => $inter_recurr, span => $_ ) };
    return @intersect;
}

method complement ($set1: ) {
    my $span1 = $set1.span;
    my @spans = $span1.complement;
    @spans = @spans.map:{ $set1.new( recurrence => $.recurrence.get_universe, span => $_ ) };
    my $compl_recurr = $set1.new( recurrence => $.recurrence.complement, span => $span1 );

    return ( @spans, $compl_recurr ).sort:{ $^a.compare( $^b.span ) };
}

method difference ($set1: $set2 ) {
    if ( $set2.isa( 'Span::Num' ) || $set2.isa( 'Span::Int' ) )
    {
        my @diff = $.span.difference( $set2 );
        @diff = @diff.map:{ $set1.new( recurrence => $.recurrence, span => $_ ) };
        return @diff;
    }
    my $span1 = $set1.span;
    my $span2 = $set2.span;

    my @set1_span = $span1.difference( $span2 );
    my @intersect = $span1.intersection( $span2 );

    my $diff_recurr = $set1.recurrence.difference( $set2.recurrence );

    @set1_span = @set1_span.map:{ $set1.new( recurrence => $set1.recurrence, span => $_ ) };
    @intersect = @intersect.map:{ $set1.new( recurrence => $diff_recurr,     span => $_ ) };

    return ( @set1_span, @intersect ).sort:{ $^a.compare( $^b.span ) };
}

method next ($self: $x is copy ) {
    $x = $.recurrence.next( $x );
    if $x < $.span.start && ! $.span.start_is_open {
        $x = $.recurrence.next( $.recurrence.previous( $.span.start ) );
    }
    elsif $x <= $.span.start && $.span.start_is_open {
        $x = $.recurrence.next( $.span.start );
    }
    if $x > $.span.end && ! $.span.end_is_open {
        $x = Inf;
    }
    elsif $x >= $.span.end && $.span.end_is_open {
        $x = Inf;
    }
    return $x;
}

method previous ($self: $x is copy ) {
    $x = $.recurrence.previous( $x );
    if $x > $.span.end && ! $.span.end_is_open {
        $x = $.recurrence.previous( $.recurrence.next( $.span.end ) );
    }
    elsif $x >= $.span.end && $.span.end_is_open {
        $x = $.recurrence.previous( $.span.end );
    }
    if $x < $.span.start && ! $.span.start_is_open {
        $x = -Inf;
    }
    elsif $x <= $.span.start && $.span.start_is_open {
        $x = -Inf;
    }
    return $x;
}


} # class Span::Code


=kwid

= NAME

Span::Code - An object representing a recurrence set span 

= SYNOPSIS

    use Span::Code;

    $span = Span::Code.new( recurrence => $recurrence_set, span => $span );

= AUTHOR

Flavio S. Glock, <fglock@gmail.com>

= COPYRIGHT

Copyright (c) 2005, Flavio S. Glock.  All rights reserved.

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

=cut