package WWW::Ohloh::API::Projects;

use strict;
use warnings;

use Carp;
use Object::InsideOut;
use XML::LibXML;
use Readonly;
use List::MoreUtils qw/ none any /;

use overload  '<>' => \&next;

our $VERSION = '0.0.4';

my @all_read         :Field :Default(0);
my @cache_of         :Field;
my @total_entries_of :Field  :Default(-1);
my @ohloh_of :Field  :Arg(ohloh);
my @page_of :Field   :Default(0);
my @query_of :Field  :Arg(query);
my @sort_order_of :Field :Arg(sort) :Type(\&WWW::Ohloh::API::Projects::is_allowed_sort);
my @max_entries_of :Field :Arg(max) :Get(max);

Readonly our @ALLOWED_SORTING => map { $_, $_ . '_reverse' }
  qw/ created_at description id name stack_count updated_at /;

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub is_allowed_sort {
    my $s = shift;
    return any { $s eq $_ } @ALLOWED_SORTING;
}

sub _init :Init {
    my $self = shift;

    $cache_of[ $$self ] = [ ];  # initialize to empty array

    if ( my $s = $sort_order_of[ $$self ] ) {
        if ( none { $s eq $_ } @ALLOWED_SORTING ) {
            croak "sorting order given: $s, must be one of the following: ".
                    join ', ' => @ALLOWED_SORTING;
        }
    }
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub next {
    my $self = shift;
    my $nbr_requested = shift || 1;

    while ( @{ $cache_of[ $$self ] } < $nbr_requested 
            and not $all_read[ $$self ] ) {
        $self->_gather_more;
    }

    my @bunch = splice @{ $cache_of[ $$self ] }, 0, $nbr_requested;

    if ( @bunch ) {
        return wantarray ? @bunch : $bunch[0] ;
    }

    # we've nothing else to return
    
    $page_of[ $$self ] = 0;
    $all_read[ $$self ] = 0;

    return;
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub _gather_more {
    my $self = shift;

    my ( $url, $xml ) = $ohloh_of[ $$self ]->_query_server( 
        'projects.xml', {
            ( query => $query_of[ $$self ] ) x !!$query_of[ $$self ],
            ( sort => $sort_order_of[ $$self ] ) x !!$sort_order_of[ $$self ],
            page => ++$page_of[ $$self ]  } );

    my @new_batch = 
         map { WWW::Ohloh::API::Project->new( 
                    ohloh => $ohloh_of[ $$self ],
                    xml => $_,
                ) } 
         $xml->findnodes( 'project' );


    # get total projects + where we are

    $total_entries_of[ $$self ] = $xml->findvalue( 
        '/response/items_available/text()' );

    my $first_item = $xml->findvalue( 
          '/response/first_item_position/text()' );

      $all_read[ $$self ] = 1 unless $total_entries_of[ $$self ];

    if ( $first_item + @new_batch - 1 >= $total_entries_of[ $$self ] ) {
        $all_read[ $$self ] = 1;
    }

    if ( my $max = $self->max ) {
        if ( $first_item + @new_batch - 1 >= $max ) {
            @new_batch = splice @new_batch, 0, $max - $first_item;    
            $all_read[ $$self ] = 1;
        }
    }

    push @{ $cache_of[ $$self ] }, @new_batch;

    return;
}



#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub all {
    my $self = shift;

    unless ( $self->max ) {
        croak "call to all only permitted if 'max' set in get_projects()";
    }

    $self->_gather_more until ( $all_read[$$self] );

    my @bunch = @{ $cache_of[ $$self ] };
    $cache_of[ $$self ] = [];

    $page_of[ $$self ] = 0;
    $all_read[ $$self ] = 0;

    return @bunch;
}

'end of WWW::Ohloh::API::Projects';
__END__

=head1 NAME

WWW::Ohloh::API::Projects - a set of Ohloh projects

=head1 SYNOPSIS

    use WWW::Ohloh::API;

    my $ohloh = WWW::Ohloh::API->new( api_key => $my_api_key );
    my $projects = $ohloh->get_projects( query => 'Ohloh', max => 100 );

    while ( my $p = $projects->next ) {
        print $p->name;
    }

=head1 DESCRIPTION

W::O::A::Projects returns the results of an Ohloh project query.
To be properly populated, it must be created via
the C<get_projects> method of a L<WWW::Ohloh::API> object.

The results of a query are not all captured at the call of
<get_projects>, but are retrieved from Ohloh as required, usually
by batches of 25 items.  

=head1 METHODS 

=head2 next( [ $n ] )

Return the next project of the set or, if I<$n> is given,
the I<$n> following projects (or all remaining projects if they
are less than I<$n>). When all projects have been returned, C<next()>
returns C<undef> once, and then restarts from the beginning.

Examples:

    # typical iterator
    while( my $project = $projects->next ) {
        print $project->name;
    }

    # read projects 10 at a time
    while( my @p = $project->next( 10 ) ) {
        # do something with them...
    }

=head2 all

Return all the remaining projects of the set. A call
to C<all()> immediatly reset the set. I.e., a subsequent call
to C<next()> would return the first project of the set, not I<undef>.

As a precaution against
memory meltdown, calls to C<all()> are not permited unless the parameter
I<max> of C<get_projects> has been set.

Example:

    my $projects = $ohloh->get_projects( sort => 'stack_count', max => 100 );
    # get the first project special
    $top = $projects->next;
    # get the rest
    @most_stacked = $projects->all;

=head1 OVERLOADING

=head2 Iteration

If called on a W:O:A:Projects object, the iteration operator (<>) acts
as a call to '$projects->next'.

E.g.,

    while( my $p = <$projects> ) { 
        ### do stuff with $p
    }

=head1 SEE ALSO

=over

=item * 

L<WWW::Ohloh::API>, 
L<WWW::Ohloh::API::Project>,
L<WWW::Ohloh::API::Analysis>, 
L<WWW::Ohloh::API::Account>.


=item *

Ohloh API reference: http://www.ohloh.net/api/getting_started

=item * 

Ohloh Account API reference: http://www.ohloh.net/api/reference/project

=back

=head1 VERSION

This document describes WWW::Ohloh::API version 0.0.4

=head1 BUGS AND LIMITATIONS

WWW::Ohloh::API is very extremely alpha quality. It'll improve,
but till then: I<Caveat emptor>.

Please report any bugs or feature requests to
C<bug-www-ohloh-api@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org>.


=head1 AUTHOR

Yanick Champoux  C<< <yanick@cpan.org> >>

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2008, Yanick Champoux C<< <yanick@cpan.org> >>. All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.



