package WebService::GData::Query;
use WebService::GData;
use base 'WebService::GData';

use WebService::GData::Error;
use WebService::GData::Constants qw(:all);

#specify default parameters

our $VERSION = 0.0205;

sub __init {
    my $this = shift;

    $this->{_query} = {
        'v'         => GDATA_MINIMUM_VERSION,
        alt         => JSON,
        prettyprint => FALSE,
        strict      => TRUE,
    };
    return $this;
}

sub disable {
    my ( $parameters, $package, $is_strict ) = @_;
    $package = $package || caller;
    WebService::GData::install_in_package(
        $parameters,
        sub {
            return sub {

                #keep the chaining
                return shift();
              }
        },
        $package
    );

}

sub install_sub {
    my $subname = shift;
    my $field   = $subname;
    $field =~ s/_/-/g;
    return sub {
        my ( $this, $val ) = @_;

        if ( my $code = $this->can( '_' . $field . '_is_valid' ) ) {

            my $res = &$code($val);
            if ($res) {
                return $this->_set_query( $field, $val );
            }
            else {
                die new WebService::GData::Error( 'invalid_parameter_type',
                    $subname . '() did not get a proper value.' );
            }
        }
        return $this->_set_query( $field, $val );

      }
}

sub install {
    my $parameters = shift;
    my $package    = caller;
    WebService::GData::install_in_package( $parameters, \&install_sub,
        $package );
}

install(
    [
        'strict',        'fields', 'v',           'alt',
        'prettyprint',   'author', 'updated_min', 'updated_max',
        'published_min', 'published_max'
    ]
);

#move this else where...

sub _is_date_format {
    my $val = shift;
    return $val
      if ( $val =~
        m/[0-9]{4}-[0-9]{2}-[0-9]{3}T[0-9]{2}:[0-9]{2}:[0-9]{2}-[0-9]{2}:00/ );
}

sub _is_boolean {
    my $val = shift;
    return $val if ( $val eq FALSE || $val eq TRUE );
}

sub _v_is_valid {
    my $val = shift;
    return $val if ( $val >= GDATA_MINIMUM_VERSION );
}

sub _published_max_is_valid {
    return _is_date_format( shift() );
}

sub _published_min_is_valid {
    return _is_date_format( shift() );
}

sub _updated_max_is_valid {
    return _is_date_format( shift() );
}

sub _updated_min_is_valid {
    return _is_date_format( shift() );
}

sub _prettyprint_is_valid {
    return _is_boolean( shift() );
}

sub _strict_is_valid {
    return _is_boolean( shift() );
}

sub start_index {
    my ( $this, $start ) = @_;
    return $this->_set_query( 'start-index', ( int($start) < 1 ) ? 1 : $start );
}

sub max_results {
    my ( $this, $max ) = @_;
    return $this->_set_query( 'max-results', ( int($max) < 1 ) ? 1 : $max );
}

sub limit {
    my ( $this, $max, $offset ) = @_;
    $this->start_index($offset);
    return $this->max_results($max);
}

sub q {
    my ( $this, $search ) = @_;
    $search =~ s/\s+AND\s+/ /g;
    return $this->_set_query( 'q', $search );
}

sub category {
    my ( $this, $category ) = @_;
    $category =~ s/\s+OR\s+/|/g;
    $category =~ s/\s+AND\s+/,/g;
    $category =~ s/\s{1}/,/g;
    return $this->_set_query( 'category', $category );
}

sub _set_query {
    my ( $this, $key, $val ) = @_;
    $this->{_query}->{$key} = $val;
    return $this;
}

sub get {
    my ( $this, $key ) = @_;
    return $this->{_query}->{$key};
}

sub to_query_string {
    my $this  = shift;
    my @query = ();
    while ( my ( $field, $value ) = each %{ $this->{_query} } ) {
        push @query, $field . '=' . _urlencode($value) if ( defined $value );
        push @query, $field if ( !defined $value );
    }
    return '?' . join '&', @query;
}

sub _urlencode {
    my ($string) = shift;
    $string =~ s/(\W)/"%" . unpack("H2", $1)/ge;
    return $string;
}

sub __to_string {
    return shift()->to_query_string();
}

"The earth is blue like an orange.";

__END__


=pod

=head1 NAME

WebService::GData::Query - implements the core query parameters available in the google data API v2.

=head1 SYNOPSIS

    use WebService::GData::Query;
    use WebService::GData::Constants qw(:format :query :general);

    my $query = new WebService::GData::Query();

    $query->to_query_string();# by default:?alt=json&v=2&prettyprint=false&strict=true

    #?alt=jsonc&v=2&prettyprint=false&strict=true&start-index=1&max-results=10
    $query->alt('jsonc')->limit(10,1)->to_query_string();

    print $query->get('alt');#jsonc

    $query->v(1);#throw an error as only 2 is ok.
    $query->prettyprint(1);#throw an error as only 'true' or 'false' is possible.

    #use constants where you can 

    $query->prettyprint(TRUE);

    $query->alt('json-c');#this is wrong

    $query->alt(JSONC);#ok!


=head1 DESCRIPTION

I<inherits from L<WebService::GData>>.

Google data API supports searching different services via a common set of parameters.
Unfortunately, some services only handles a subset of this "core" parameters...
You should read the service Query documentation to know exactly the available parameters.
This package also implements some helpers functions to shorten up a little the parameter settings.

In order to avoid to send uncorrect parameter values, the package checks for their validity
and will throw a L<WebService::GData::Error> object containing 'invalid_parameter_type' as the C<code> and the name of the function as the C<content>.
Checking the data before sending a request will avoid unnecessary network transactions and
reduce the risk of reaching quota limitations in use for the service you are querying.

L<WebService::GData::Constants> contains predefined value that you can use to set the parameters.
Using the constants can avoid typo errors or unnecessary code change when an update is available with a new value.


=head2 CONSTRUCTOR

=head3 new

=over

Creates a basic query instance.
The following parameters are set by default:

=over 4

=item C<alt         = WebService::GData::Constants::JSON>

=item C<v           = WebService::GData::Constants::GDATA_MINIMUM_VERSION>

=item C<prettyprint = WebService::GData::Constants::FALSE>

=item C<strict      = WebService::GData::Constants::TRUE>

=back


B<Parameters>

=over 4

=item C<none>

=back

B<Returns> 

=over 4

=item C<WebService::GData::Query>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->to_query_string();# by default:?alt=json&v=2&prettyprint=false&strict=true

=back

=head2 GENERAL METHODS


=head3 get

=over

Returns the parameter specified.

The function uses the underscore nomenclature where the parameters use the hyphen nomenclature.
You should change all the underscore to hyphen when accessing the value.

B<Parameters>

=over 4

=item C<parameter_name:Scalar>

=back

I<Return> 

=over 4

=item C<parameter_value::Scalar>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->get('alt');#json

    $query->get('published_min');#does not work...

    $query->get('published-min');#ok!

=back

=head3 to_query_string

=over

Concatene each parameter/value pair into a  query string.
This function is also called in a string overload context. "$instance" is the same as $instance->to_query_string();

B<Parameters>

=over 4

=item C<none>

=back

B<Returns> 

=over 4 

=item C<query_string:Scalar>

=back


Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->to_query_string();#?alt=json&v=2&prettyprint=false&strict=true
    "$query";                 #?alt=json&v=2&prettyprint=false&strict=true
    print $query;             #?alt=json&v=2&prettyprint=false&strict=true

=back


=head2 PARAMETER METHODS

All the methods that set a parameter return the instance so that you can chain the function calls.

Example:

    $query->alt(JSONC)->limit(10,1)->strict(TRUE)->prettyprint(FALSE)->to_query_string();


The following setters are available:

=head3 strict

=over

If set to true (default),  setting a parameter not supported by a service will fail the request.

B<Parameters>

=over 4

=item C<true_or_false:Scalar> - The value can be L<WebService::Gdata::Constants>::TRUE or L<WebService::Gdata::Constants>::FALSE

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->strict('true');

    $query->strict(TRUE);#better

    $query->strict('hello');#die


=back


=head3 fields

=over

Allows you to query partial data. 
This is a Google data experimental feature as of this version.

B<Parameters>

=over 4

=item C<partial_query:Scalar>

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back


Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->fields('id,entry(author)');#only get the id and the author in the entry tag

B<See Also>

The reference for the partial queries:

L<http://code.google.com/intl/en/apis/gdata/docs/2.0/reference.html#PartialResponse>

=back

=head3 v

=over

Set the google Data API version number. Default to WebService::GData::Constants::GDATA_MINIMUM_VERSION. 
You shoud not set this unless you know what you do.

=back

=head3 alt

=over

Specify the response format used. Default to WebService::GData::Constants::JSON.
You shoud not set this unless you know what you do.

=back


=head3 prettyprint

=over

If set to true (default false),the result from the service will contain indentation. 

B<Parameters>

=over 4

=item C<true_or_false:Scalar> (Default: L<WebService::Gdata::Constants>::FALSE)

The value can be L<WebService::Gdata::Constants>::TRUE or L<WebService::Gdata::Constants>::FALSE

=back

B<Returns>

=over 4

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->prettyprint('true');

    $query->prettyprint(TRUE);#better

    $query->prettyprint('hello');#die

=back

=head3 author

=over

Specify the author of the contents you want to retrieve.
Each service derives the meaning for their own feed.

B<Parameters>

=over 4

=item C<author_name:Scalar> 

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->author('GoogleDeveloper');

=back

=head3 updated_min

=over

Retrieve the contents which update date is a minimum equal to the one specified (inclusive).
Note that you should retrieve the value as 'updated-min' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<date:Scalar> - Format:2005-08-09T10:57:00-08:00

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

=back

=head3 updated_max 

=over

Retrieve the contents which update date is at maximum equal to the one specified (exclusive).
Note that you should retrieve the value as 'updated-max' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<date:Scalar> - Format:2005-08-09T10:57:00-08:00

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

=back

=head3 published_min 

=over

Retrieve the contents which publish date is a minimum equal to the one specified (inclusive).
Note that you should retrieve the value as 'published-min' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<date:Scalar> - Format:2005-08-09T10:57:00-08:00

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

=back

=head3 published_max 

=over

Retrieve the contents which publish date is a maximum equal to the one specified (exclusive).
Note that you should retrieve the value as 'published-max' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<date:Scalar> - Format:2005-08-09T10:57:00-08:00

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

B<Throws> 

=over 4

=item C<WebService::GData::Error>

=back

=back

=head3 start_index 

=over

Retrieve the contents starting from a certain result. Start from 1.
Setting 0 will revert to 1.
Note that you should retrieve the value as 'start-index' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<index_number:Int> - Setting the number to 0 or anything but an integer will coerce it to 1.

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

=back

=head3 max_results 

=over

Retrieve the contents up to a certain amount of entry (Most of the services set it to 25 by default).

Note that you should retrieve the value as 'max-results' when used with L<WebService::GData::Query>::get().

B<Parameters>

=over 4

=item C<index_number:Int> - Setting the number to 0 or anything but an integer will coerce it to 1.

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

=back

=head3 limit

=over

An extension that allows you to set start_index and max_results in one method call:
get('limit') will return undef.
Follow the same coercicion logic of start_index and max_results.

B<Parameters>

=over 4

=item C<max_results:Int> - The number of result you want to get back

=item C<start_index:Int> - The offset from where to start

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->limit(10,5);
    #equivalent to
    $query->max_results(10)->start_index(5);	

=back

=head3 q

=over

Insensitive freewords search where:

=over 4

=item * words in quotation means exact match:"word1 word2"

=item * words separated by a space means AND:word1 word2

=item * words prefixed with an hyphen means NOT(containing):-word1

=back

B<Parameters>

=over 4

=item C<search:Scalar>

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

Example:

    use WebService::GData::Query;

    
    my $query = new WebService::GData::Query();

    $query->q('"exact phrase" snowbaord sports -ski');

=back


=head3 category

=over

Allow to narrow down the result to specifics categories.

=over

=item * words separated by a comma(,) means AND:word1,word2

=item * words separated by a pipe(|) means OR:word1|word2

=item * words prefixed by an hyphen(-) are disgarded:-word1

=back

B<Parameters>

=over 4

=item C<category:Scalar>

=back

B<Returns> 

=over 4 

=item C<WebService::GData::Query>

=back

Example:

    use WebService::GData::Query;

    my $query = new WebService::GData::Query();

    $query->category('-Shows,Entertainment|Sports');

=back

=head1  SEE ALSO

Documentation of the parameters:

L<http://code.google.com/intl/en/apis/gdata/docs/2.0/reference.html#Queries>


=head1 BUGS AND LIMITATIONS

If you do me the favor to _use_ this module and find a bug, please email me
i will try to do my best to fix it (patches welcome)!

=head1 AUTHOR

shiriru E<lt>shirirulestheworld[arobas]gmail.comE<gt>

=head1 LICENSE AND COPYRIGHT

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

=cut
