package WWW::Discogs;

use strict;
use warnings;

use LWP::UserAgent;
use URI;
use URI::Escape;
use Carp;
use JSON::XS;
use Data::Dumper;

use 5.008;
our $VERSION = '0.11';

our @namespaces = qw ( Artist Release Label Search Master );

my %query_params  = (
    artist  => { releases => 0, },
    release => { },
    label   => { releases => 0, },
    search  => { type => 'all', 'q' => '', page => '1', },
    master  => { },
    );

for (@namespaces) {
    my $pkg = __PACKAGE__."::$_";
    my $name = "\L$_";

    my $namespace = eval qq{
        use $pkg;

        sub $name {
            Carp::croak
                "Params for '$name' should be key/value pairs, not hash ref"
                if ref(\$_[1]) eq 'HASH';
            my (\$self, \%args) = \@_;
            my \$id = \$args{id} || \$args{name} || '';

            Carp::croak "Call to '$name' missing required arguments."
                if !\$id && '$name' =~ /artist|release|label|master/;
            Carp::croak "No search query specified"
                if '$name' eq 'search' && !exists \$args{q};
            Carp::croak "Incorrect search query"
                if '$name' eq 'search' && \$args{q} =~ /^\\s*\$/;
            Carp::croak "id value for '$name' not a number"
                if '$name' =~ /master|release/ && \$id !~ /^\\d+\$/;
            Carp::croak "name value incorrect for '$name'"
                if '$name' =~ /artist|label/ && \$id =~ /^\\s*\$/;

            my \$query_params = \$self->_get_query_params('$name', \%args);

            my \$res = \$self->_request(
                path     => (\$id =~ /^\\s*\$/) ? '$name' : '$name'."/\$id",
                query    => \$query_params,
                );

            my \$json = JSON::XS::decode_json( \$res->decoded_content );
            my \$class_data = \$json->{resp}->{'$name'};
            \$class_data->{_uri} = \$res->base;
            \$class_data->{_params} = \$query_params;

            if (\$json->{resp}->{status} == JSON::XS::true &&
                defined \$json->{resp}->{'$name'}) {
                return $pkg->new(\%{\$class_data});
            }

            return undef;
        }

        1;
    };

    Carp::croak "Cannot create namespace $name: $@\n" if not $namespace;
}

sub _get_query_params {
    my ($self, $name, %args) = @_;
    my %params = ();

    for (keys %args) {
        if (exists $query_params{$name}->{$_}) {
            $params{$_} = $args{$_};
        } else {
            delete $args{$_};
        }
    }

    %params = ( %{$query_params{$name}}, %params );

    return \%params;
}


sub new {
    my ($class, @args) = @_;
    my $self = {};
    bless $self, $class;
    $self->_init(@args);

    return $self;
}

sub _init {
    my ($self, %args) = @_;
    $self->{apiurl} = $args{apiurl} || 'http://api.discogs.com';
    $self->{ua} = LWP::UserAgent->new;
    $self->{ua}->default_header(
        'Accept-Encoding' => 'gzip, deflate',
        );

    return $self;
}

sub _request {
    my ($self, %args) = @_;
    my $path  = $args{path};
    my $query = $args{query};

    my $uri = URI->new(
        $self->{'apiurl'},
        'http',
        );
    $uri->path($path);
    $uri->query_form($query) if keys %{$query};

    my $url = $uri->canonical->as_string;
    my $res = $self->{ua}->get($url);

    Carp::croak join(
        "\n",
        "Request to $url failed: ",
        $res->status_line, Dumper($res)
        ) unless $res->is_success;

    return $res;
}

1;
__END__

=head1 NAME

WWW::Discogs - get music related information and images

=head1 DESCRIPTION

Interface with www.discogs.com API to get music related information and
images. Discogs is a user-built database containing information on artists,
labels, and their recordings.

=head1 SYNOPSIS

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  
  # --
  # print all vinyl (12") releases from label 'Drumcode'
  # --
  my $label = $client->label(name => 'Drumcode', releases => 1);
  my @vinyls = grep { $_->{format} =~ /12\"/ } $label->releases;
  
  for my $rel (@vinyls) {
      print join("\t",
                 $rel->{catno},
                 $rel->{artist},
                 $rel->{title},
                 $rel->{format},
          );
      print "\n";
  }
  
  # --
  # print all covers for records by Nima Khak
  # --
  my @all_rels = $client->artist(name => 'Nima Khak', releases => 1)->releases;
  my @main_rels = grep { $_->{role} eq 'Main' } @all_rels;
  
  RELEASE:
  for my $r (@main_rels) {
      my $release;
  
      if ($r->{type} eq 'master') {
          my $master = $client->master(id => $r->{id});
          $release = $client->release(id => $master->main_release);
      }
      elsif ($r->{type} eq 'release') {
          $release = $client->release(id => $r->{id});
      }
  
      my @images = $release->images(type => 'primary');
      next RELEASE unless scalar(@images);
      print join("\t", $release->title, $images[0]->{uri}), "\n";
  }

=head1 METHODS

=head2 search( q => $search_string )

=head2 search( q => $search_string, type => $search_type )

=head2 search( q => $search_string, type => $search_type, page => $page )

Returns a C<WWW::Discogs::Search> object. If you want to narrow down search
results then provide C<$search_type> which can be one of 'all' (the default),
'releases' (also returns masters), 'artists' or 'labels'.
Search results are paginated (20 results per page) and default is
C<< page => 1 >>. You can check how many search results pages are there by
calling C<pages> method on C<WWW::Discogs::Search> object.

=head2 release( id => $release_id )

Returns a C<WWW::Discogs::Release> object. You can get a $release_id from a
search, artist, or label.

=head2 master( id => $master_id )

Returns a C<WWW::Discogs::Master> object. You can get a $master_id from a
search or release.

=head2 artist( name => $artist_name )

=head2 artist( name => $artist_name, releases => 1 )

Returns a C<WWW::Discogs::Artist> object. You can get the exact name of an
artist from a search result's title.

=head2 label( name => $label_name )

=head2 label( name => $label_name, releases => 1 )

Returns a C<WWW::Discogs::Label> object. You can get the exact name of a label
from a search result's title.

=head1 OBJECTS CREATED AND THEIR METHODS

=head2 WWW::Discogs::Search

=over

=item $search->exactresults

Returns list of hash references containing results exactly matching search
query. See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $search = $client->search(q => 'adam beyer');
  
  for my $result ($search->exactresults) {
      print join(" - ", $result->{type}, $result->{title}, $result->{uri});
      print "\n";
  }

=item $search->searchresults

Returns list of hash references containing search results.

=item $search->numresults

Returns a number of search results (counted without exact results).

=item $search->pages

Returns number of search results' pages. Each page contains max 20 search
results.

=back

=head2 WWW::Discogs::Release

=over

=item $release->id

Returns release ID.

=item $release->title

Returns title of the release.

=item $release->images

=item $release->images( type => $image_type )

Returns a list of hash references containing information about images for a
release. C< $image_type > can be one of 'primary' or 'secondary'.
See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 797674);
  
  for my $img ( $release->images(type => 'primary') ) {
      print join(" - ",
                 $img->{width}, $img->{height}, $img->{uri},
                 $img->{uri150}, $img->{type},
          );
      print "\n";
  }

=item $release->released

Returns release date in ISO 8601 format (YYYY-MM-DD).

=item $release->released_formatted

Returns formatted release date ('06 Oct 2006', 'Mar 2006' etc.)

=item $release->labels

Returns a list of hash references containing labels information.
See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 797674);
  
  for my $label ($release->labels) {
      print join(" - ", $label->{name}, $label->{catno});
  }

=item $release->country

Returns country.

=item $release->formats

Returns a list of hash references containing formats information.
See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 797674);
  
  for my $format ($release->formats) {
      printf("%d x %s, %s\n",
             $format->{qty},
             $format->{name},
             join(", ", @{ $format->{descriptions } }),
          );
  }

Prints:

 1 x CD, Album, Partially Mixed
 1 x CD, Compilation, Limited Edition

=item $release->status

Returns status.

=item $release->master_id

Returns master release ID associated with a release.

=item $release->year

Returns release year.

=item $release->notes

Returns release notes.

=item $release->styles

Returns a list of styles.

=item $release->genres

Returns a list of genres.

=item $release->artists

Returns a list of hash references containing artists information.

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 18618);
  
  for my $artist ($release->artists) {
      print join(" - ", $artist->{name}, $artist->{anv}, $artist->{role});
      print "\n";
  }

=item $release->extraartists

Returns a list of hash references containing extra artists information.

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 18618);
  
  for my $exart ($release->extraartists) {
      print join(" - ", $exart->{name}, $exart->{anv}, $exart->{role});
      print "\n";
  }

=item $release->tracklist

Returns tracklist as a list containing hash references. See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $release = $client->release(id => 830189);
  
  my @tracklist = $release->tracklist;
  for my $track (sort { $a->{position} <=> $b->{position} } @tracklist) {
      printf("%d. %s (%s)\n",
             $track->{position}, $track->{title}, $track->{duration},
          );
  }

=back

=head2 WWW::Discogs::Master

=over

=item $master->id

Returns master ID.

=item $master->main_release

Returns main release ID.

=item $master->versions

Returns a list of hash references containing versions information. See example
below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $master = $client->master(id => 104330);
  
  for my $version ( $master->versions ) {
      printf("%9d %7s %15s %18s %7s %15s\n",
             $version->{id}, $version->{country}, $version->{title},
             $version->{format}, $version->{catno}, $version->{label});
  }

Prints:

   116934  Sweden   Chaos & Order          CD, Album  HPCD20 H. Productions
    11168  Sweden   Chaos & Order        2xLP, Album  HPLP20 H. Productions
  2307050  Sweden   Chaos & Order 2xLP, Album, W/Lbl  HPLP20 H. Productions

Other available keys in C<< $version >> besides the ones in example above are
C<< $version->{status} >> and C<< $version->{released} >>.

=item $master->images

=item $master->images( type => $image_type )

Returns a list of hash references containing information about images for
a release. C< $image_type > can be one of 'primary' or 'secondary'.
See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $master = $client->master(id => 23992);
  
  for my $img ( $master->images(type => 'secondary') ) {
      print join(" - ",
               $img->{width}, $img->{height}, $img->{uri},
               $img->{uri150}, $img->{type},
          );
      print "\n";
  }

=item $master->year

Returns release year.

=item $master->notes

Returns release notes.

=item $master->styles

Returns a list of styles.

=item $master->genres

Returns a list of genres.

=item $master->artists

Returns a list of hash references containing artists information.
See C<< $release->artists >> for an example.

=item $master->extraartists

Returns a list of hash references containing extra artists information.
See C<< $release->extraartists >> for an example.

=item $master->tracklist

Returns tracklist. See C<< $release->tracklist >> for an example.

=back

=head2 WWW::Discogs::Artist

=over

=item $artist->name

Returns artist name.

=item $artist->realname

Returns artist's real name

=item $artist->aliases

Returns a list of aliases used by the artist.

=item $artist->namevariations

Returns a list of name variations for the artist.

=item $artist->profile

Returns artist's profile information.

=item $artist->urls

Returns a list of site's URLs linked to the artist.

=item $artist->images

=item $artist->images( type => $image_type )

Returns a list of hash references containing images information. See
C<< $release->images >> for an example.

=item $artist->releases

If $client->artist method creating a new C<WWW::Discogs::Artist> object was
called with C<< releases => 1 >> parameter you can get the list of artist's
releases by calling this method. The result will be a list of hash references
containing releases/master releases information. See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $artist = $client->artist(name => "Adam Beyer", releases => 1);
  
  foreach my $r ($artist->releases) {
    printf("%8d %7s %17s %s\n", $r->{id}, $r->{type}, $r->{role}, $r->{title});
  }

=over 4

=item *

C<< $r->{id} >> will contain release/master release ID

=item *

C<< $r->{type} >> will contain release type ('release' or 'master')

=item *

C<< $r->{role} >> will contain artist's role in release
('Main', 'Remix', 'Producer', 'Appearance', 'TrackAppearance' etc.)

=item *

C<< $r->{title} >> will contain release/master release title

=back

For releases with 'master' type you can get main release ID by checking the
value of C<< $r->{main_release} >>. Use C<Data::Dumper> to find out more about
this structure as results differ depending on artist's role and release type.

=back

=head2 WWW::Discogs::Label

=over

=item $label->name

Returns label's name.

=item $label->releases

If $client->label method creating a new C<WWW::Discogs::Label> object was
called with C<< releases => 1 >> parameter you can get the list of label's
releases by calling this method. The result will be a list of hash references
containing releases information. See example below:

  use WWW::Discogs;
  
  my $client = WWW::Discogs->new;
  my $label = $client->label(name => 'Southsoul Appendix', releases => 1);
  
  for my $r ($label->releases) {
      print join("\t", $r->{id}, $r->{catno}, $r->{artist},
                 $r->{title}, $r->{format}
          );
      print "\n";
  }

=item $label->contactinfo

Returns contact info to the label.

=item $label->sublabels

Returns a list containing names of sublabels.

=item $label->parentlabel

Returns the name of parent label.

=item $label->images

=item $label->images( type => $images_type)

Returns a list of hash references containing images information. See
C<< $release->images >> for an example.

=back

=head1 AUTHOR

0.11: Michal Gasek <michal@gasek.eu>

0.01-0.10: Lee Aylward <lee@laylward.com>

=head1 LICENSE

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

=cut
