package WWW::OReillyMedia::Store;

use strict; use warnings;

use overload q("") => \&as_string, fallback => 1;

=head1 NAME

WWW::OReillyMedia::Store - Interface to the OReilly Media Store.

=head1 VERSION

Version 0.05

=cut

our $VERSION = '0.05';

use Carp;
use Readonly;
use Data::Dumper;
use HTTP::Request;
use LWP::UserAgent;
use WWW::OReillyMedia::Store::Book;

Readonly my $URL =>
[
    'http://oreilly.com/store/complete.html',
    'http://oreilly.com/store/complete2.html',
    'http://oreilly.com/store/complete3.html',
    'http://oreilly.com/store/complete4.html'
];

sub new
{
    my $class = shift;
    my $self  = { books   => undef,
                  browser => LWP::UserAgent->new()
                };
    
    bless $self, $class;
    $self->fetch_books();
    return $self;
}

=head1 DESCRIPTION

Objective of this module is to provide an interface to the OReilly online media store. 

=head1 METHODS

=head2 fetch_books()

Fetches  books  details  from OReilly online store realtime. It does so as part of constructor
functionality.  You DONT really need to do call this explicitly. However it is still there for
you to play with it.

=cut

sub fetch_books
{
    my $self    = shift;
    my $browser = $self->{browser};
    
    my ($books, $book);
    my ($request, $response, $contents);
        
    foreach (@{$URL})
    {
        $request  = HTTP::Request->new(GET=>$_);
        $response = $browser->request($request);
        croak("ERROR: Couldn't connect to search.cpan.org.\n") 
            unless $response->is_success;
    
        $contents = $response->content;
        foreach (split(/\n/,$contents))
        {
            chomp;
            s/^\s+//g;
            s/\s+$//g;
            next if /^$/;
            
            if (/\<a class\=\"tt\" id=\"(.*)\" href=\"(.*)\"\>(.*)/)
            {
                push @$books, WWW::OReillyMedia::Store::Book->new($book) if defined $book;
                $book = undef;
                $book->{id}   = $1;
                $book->{url}  = $2;
                $book->{desc} = $3;
            }
            elsif (/^[A-Z][a-z][a-z]\s\d{4}\<\/td\>$/) 
            {
                /(.*)\<\/td\>/;
                $book->{released} = $1;
            }
            elsif (/^\$\d*?\.?\d*?$/) 
            {
                /^\$(.*)$/;
                $book->{price} = $1;
            }
            elsif (/^\<a href=\"(.*)\"\>.*Available as Ebook.*/)
            {
                $book->{ebook} = 1;
            }
            elsif (/^\<a href=\"(.*)\"\>.*Read it on online with Safari.*/)
            {
                $book->{online} = $1;
            }
        }
    }
    $self->{books} = $books;
}

=head2 search_book()

Search for  a  book  in  the book store. Search  can be done by it's id, description, released
year,price,ebook and online availability. Each of this can be provided as key to the anonymous
hash.

    use strict; use warnings;
    use WWW::OReillyMedia::Store;

    my ($store, $books);
    $store = WWW::OReillyMedia::Store->new();

    # Search by ID.
    $books = $store->search_book({id => 9780596005054});
    print $books->[0]->as_string();
    
    # Search by Description.
    $books = $store->search_book({description => '.NET Framework Essentials'});
    print $books->[0]->as_string();    
    
    # Search by Released year.
    $books = $store->search_book({released => 'Mar 2003'});
    print $books->[0]->as_string();
    
    # Search by price i.e. less than or equal to $10.
    $books = $store->search_book({price => 10});
    print $books->[0]->as_string();
    
    # Search by ebook i.e. return all ebooks.
    $books = $store->search_book({ebook => 1});
    print $books->[0]->as_string();

    # Search by online i.e. return all online books.
    $books = $store->search_book({online => 1});
    print $books->[0]->as_string();
    
=cut

sub search_book
{
    my $self  = shift;
    my $param = shift;
    
    my $books;
    foreach ( @{$self->{books}} )
    {
        if (exists($param->{id}) && defined($param->{id}) && ($param->{id} =~ /^\d+$/))
        {
            return [$_] if ($_->get_id() == $param->{id});
        }
        elsif (exists($param->{description}) && defined($param->{description}))
        {
            push @{$books}, $_
                if ($_->get_description() =~ /$param->{description}/i);
        }
        elsif (exists($param->{released}) && defined($param->{released}))
        {
            push @{$books}, $_
                if ($_->get_released() =~ /$param->{released}/i);
        }
        if (exists($param->{price}) && defined($param->{price}) && ($param->{price} =~ /^\d*\.?\d*?$/))
        {
            push @{$books}, $_
                if ($_->get_price() <= $param->{price});
        }
        elsif (exists($param->{ebook}) && defined($param->{ebook}))
        {
            push @{$books}, $_  if $_->is_ebook_available();
        }
        elsif (exists($param->{online}) && defined($param->{online}))
        {
            push @{$books}, $_  if $_->is_available_online();
        }
    }
    return $books;
}

=head2 book_count()

Returns the total books count in the OReilly online store.

    use strict; use warnings;
    use WWW::OReillyMedia::Store;

    my $store = WWW::OReillyMedia::Store->new();
    print "Count: [" . $store->book_count() . "]\n"

=cut

sub book_count
{
    my $self = shift;
    return scalar(@{$self->{books}});
}

=head2 get_all_books()

Returns all the books that have been fetched from the OReilly online store.  Data  would be in
the form of list of objects of class WWW::OReillyMedia::Store::Book.

    use strict; use warnings;
    use WWW::OReillyMedia::Store;

    my $store = WWW::OReillyMedia::Store->new();
    my $books = $store->get_all_books();
    print $books->[0]->as_string();

=cut

sub get_all_books
{
    my $self = shift;
    return $self->{books};
}

=head2 as_string()

Return all books in human readable format.

    use strict; use warnings;
    use WWW::OReillyMedia::Store;

    my $store = WWW::OReillyMedia::Store->new();
    # Be warned, it is a bit slow and most likely to bring back quite a lot of output.
    print $store;

=cut

sub as_string
{
    my $self   = shift;
    my $string = '';
    foreach (@{$self->{books}})
    {
        $string .= $_;
        $string .= "----------------------------------------\n";
    }
    return $string;
}

=head1 AUTHOR

Mohammad S Anwar, C<< <mohammad.anwar at yahoo.com> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-www-oreillymedia-store at rt.cpan.org>, or
through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=WWW-OReillyMedia-Store>.  
I will be notified and then you'll automatically be notified of progress on your bug as I make
changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc WWW::OReillyMedia::Store

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=WWW-OReillyMedia-Store>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/WWW-OReillyMedia-Store>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/WWW-OReillyMedia-Store>

=item * Search CPAN

L<http://search.cpan.org/dist/WWW-OReillyMedia-Store/>

=back

=head1 LICENSE AND COPYRIGHT

Copyright 2011 Mohammad S Anwar.

This  program  is  free  software; you can redistribute it and/or modify it under the terms of
either:  the  GNU  General Public License as published by the Free Software Foundation; or the
Artistic License.

See http://dev.perl.org/licenses/ for more information.

=head1 DISCLAIMER

This  program  is  distributed  in  the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

=cut

1; # End of WWW::OReillyMedia::Store