=head1 NAME

AudioFile::Identify::MusicBrainz::Query

=head1 DESCRIPTION

The main query object. This is probably the thing you want to use.

=head1 METHODS

=cut

package AudioFile::Identify::MusicBrainz::Query;

use AudioFile::Identify::MusicBrainz;

use strict;
use warnings::register;
use base qw(AudioFile::Identify::MusicBrainz::Object);

use LWP;
use LWP::UserAgent;
use Data::Dumper;
use XML::DOM;

use AudioFile::Identify::MusicBrainz::RDF qw(rdf);
use AudioFile::Identify::MusicBrainz::Artist;
use AudioFile::Identify::MusicBrainz::Album;
use AudioFile::Identify::MusicBrainz::Result;
use AudioFile::Identify::MusicBrainz::Track;
use AudioFile::Identify::MusicBrainz::Store;

sub init {
  my $self = shift;
  $self->store( AudioFile::Identify::MusicBrainz::Store->new() )
    unless $self->store;
  1;
}

sub url {
  return "http://mm.musicbrainz.org/cgi-bin/mq_2_1.pl";
}

sub _fileinfolookup_to_rdf {
  my $self = shift;
  my @fields = qw(
    trm
    artist
    album
    track
    tracknum
    secs
    filename
    artist_id
    album_id
    track_id
  );
  
  unshift(@fields, 'dummy'); # RDF templates number them from zero.

  my $query;
  if (ref($_[0])) {
    $query = shift;
  } else {
    $query = { @_ };
  }

  # MB wants length in milliseconds. Sigh.
  $query->{secs} = $query->{secs} * 1000 if $query->{secs};

  $query->{tracknum} =~ s/\D.*// if $query->{tracknum};
  $query->{track} ||= $query->{title}; # common error

  my $params = {};
  for (my $i = 0; $i < scalar(@fields); $i++) {
    $params->{$i} = $query->{$fields[$i]};
  }
  $params->{MAX_ITEMS} = $query->{items} || 20;

  my $rdf = rdf('MBQ_FileInfoLookup', $params);

  return $rdf;
}

=head2 FileInfoLookup($query)

Perform a MusicBrainz FileInfoLookup query - this is the clever one that I care
about. $query here is a hash / hashref where the keys are the information you
know:

  use AudioFile::Identify::MusicBrainz::Query;
  my $query = AudioFile:::Identify::MusicBrainz::Query->new();
  $query->FileInfoLookup(
      artist => 'coldplay',
      title => 'yellow',
      items => 30,
  ) or die "Could not query: " . $query->error();
  print "I got ".scalar(@{$query->results})." results\n";

keys you can use in the query are:

=over 4

=item trm

the trm - the audio fingerprint of the song. Generating this is a Future Project.

=item artist

the artist name

=item album

the album name

=item track

the track title

=item tracknum

the track number

=item secs

the track length in seconds

=item filename

the filename of the track

=back

This function will return true on success, false on error, and an error will
be accessible through the C<error> function.

=cut

sub FileInfoLookup {
  my $self = shift;

  my $query;
  if (ref($_[0])) {
    $query = shift;
  } else {
    $query = { @_ };
  }

  my $rdf = $self->_fileinfolookup_to_rdf($query);

  #die $rdf;

  my $VERSION = $AudioFile::Identify::MusicBrainz::VERSION; 
 
  my $ua = LWP::UserAgent->new(
    agent => "AudioFile::Identify::MusicBrainz/$VERSION http://jerakeen.org/programming/musicbrainz",
  );

  my $req = HTTP::Request->new(POST => $self->url,);
  $req->content($rdf);

  my $res = $ua->request($req);

  # Check the outcome of the response
  if ($res->is_success) {
    $self->response($res->content);
    return $self->parse();
  } else {
    $self->response('');
    $self->error('got bad http response: '.$res->code);
    return 0;
  }
}

sub response {
  my $self = shift;
  my $set = shift;
  if (defined($set)) {
    $self->{response} = $set;
    return $self;
  } else {
    return $self->{response};
  }
}

=head2 error

returns the last error generated by Query.

=cut

sub error {
  my $self = shift;
  my $set = shift;
  if (defined($set)) {
    $self->{error} = $set;
    return $self;
  } else {
    return $self->{error};
  }
}

sub parse {
  my $self = shift;
  return unless $self->response;
  my $parser = new XML::DOM::Parser;
  my $doc = $parser->parse($self->response);
  my $n;
  
  my $artist_nodes = $doc->getElementsByTagName('mm:Artist');

  $n = $artist_nodes->getLength;
  for (my $i = 0; $i < $n; $i++) {
    my $node = $artist_nodes->item($i);
    my $id = $node->getAttributeNode("rdf:about")->getValue;
    $self->store->artist($id,
      AudioFile::Identify::MusicBrainz::Artist->new()
                                              ->id($id)
                                              ->store($self->store)
                                              ->parse($node));
  }

  my $album_nodes = $doc->getElementsByTagName('mm:Album');

  $n = $album_nodes->getLength;
  for (my $i = 0; $i < $n; $i++) {
    my $node = $album_nodes->item($i);
    my $id = $node->getAttributeNode("rdf:about")->getValue;
    $self->store->album($id,
      AudioFile::Identify::MusicBrainz::Album->new()
                                             ->id($id)
                                             ->store($self->store)
                                             ->parse($node));
  }

  my $results = [];

  my $result_nodes = $doc->getElementsByTagName('mq:AlbumTrackResult');
  
  $n = $result_nodes->getLength;
  for (my $i = 0; $i < $n; $i++) {
    my $node = $result_nodes->item($i);
    my $result =
      AudioFile::Identify::MusicBrainz::Result->new()
                                              ->store($self->store)
                                              ->type('Track')
                                              ->parse($node);
    push @$results, $result;
  }

  $result_nodes = $doc->getElementsByTagName('mq:AlbumResult');
  
  $n = $result_nodes->getLength;
  for (my $i = 0; $i < $n; $i++) {
    my $node = $result_nodes->item($i);
    my $result =
      AudioFile::Identify::MusicBrainz::Result->new()
                                              ->store($self->store)
                                              ->type('Album')
                                              ->parse($node);
    push @$results, $result;
  }

  # get any errors.
  my $error_nodes = $doc->getElementsByTagName('mq:error');
  $n = $error_nodes->getLength;
  for (my $i = 0; $i < $n; $i++) {
    my $node = $error_nodes->item($i);
    my $error = $node->getFirstChild->toString;
    $self->error($error);
    return undef;
  }

  $self->{results} = $results;
  return 1;
}

=head2 results

returns a listref of C<AudioFile::Identify::MusicBrainz::Result> objects.

=cut

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

=head2 result(index)

returns a given result, at a given index.

=cut

sub result {
  my $self = shift;
  return $self->results->[shift];
}

1;

