package Audio::Tagger::Lookup::MusicBrainz;

use strict;
use warnings;

use Audio::Tagger::Error;
use XML::Smart;
use URI::Escape;
use Encode;

use Data::Dumper::Simple;

our $url = "http://musicbrainz.org";
our $max = 25;
our $escape  = qr/[\Q\+-&|!(){}[]^"~*?:\E]/;
our $tospace = qr([\Q\/_-+:.,|\E]);
our $testing = 0;

# TODO use XML::DOM instead of XML::Smart as it seems more common...

# TODO document lookup!
# TODO doucment the fact that we override the default meta/lookupresult so that
# we get magic geters...

sub lookup {
	# TODO die if $meta isnt a Audio::Tagger::Meta or hash
	my $meta = shift;
	my $query = "";

	my $artist    = _lucene_escape($meta->artist->name);
	my $album     = _lucene_escape($meta->album->name);
	my $track     = _lucene_escape($meta->track->name);

	my $artist_id = _mbid_check($meta->artist->id);
	my $album_id  = _mbid_check($meta->album->id);
	my $track_id  = _mbid_check($meta->track->id);

	if (defined $track_id) {
		my @trackid_lookup =  _lookup_id($meta->track->id);

		return @trackid_lookup if @trackid_lookup;
	}

	my $track_num = int($meta->track->number||0);
	my $duration  = int(($meta->track->duration||0) / 2000);

	$query .= join " AND ", split(/\s+/, $track) if $track;
	$query .= " trid:$track_id" if $track_id;
	$query .= " artist:(". join(" AND ", split(/\s+/, $artist)) . ")" if $artist;
	$query .= " arid:$artist_id" if $artist_id;
	$query .= " album:(". join(" AND ", split(/\s+/, $album)) . ")" if $album;
	$query .= " reid:$album_id" if $album_id;
	$query .= " (qdur:$duration OR qdur:" . ($duration - 1) . " OR qdur:" . ($duration + 1) . ")" if $duration;
	$query .= " tnum:" . $track_num if $track_num;

	throw Audio::Tagger::Lookup::Error "Could not build query\n" . Dumper($meta) if not length $query;

#	$query = uri_escape($query);

	$query = $url . "/ws/1/track/?type=xml&query=" . $query . "&limit=" . $max;

	$query = "t/test.xml" if $testing;

	my $result = XML::Smart->new($query);
	if ($result->null) {
		$query = Encode::decode("utf-8", $query, Encode::FB_DEFAULT);
		$result = XML::Smart->new($query);
	}

	return undef if $result->null;

	my @tracks = $result->{"metadata"}->{"track-list"}->{"track"}('@');

	foreach my $track (@tracks) {
		# track metadata
		my $t = {
			name     => Encode::encode_utf8($track->{"title"}->content),
			id       => $track->{"id"}->content,
			number   => (1 + $track->{"release-list"}->{"release"}->{"track-list"}->{"offset"}->content),
			duration => $track->{"duration"}->content,
		};
		# albumartist metadata
		#my $aa = _albumartist($track->{'release-list'}->{release}->{id}->content);

		# release metadata
		my $r = {
			name     => Encode::encode_utf8($track->{'release-list'}->{release}->{title}->content),
			id       => $track->{'release-list'}->{release}->{id}->content,
			count    => $track->{'release-list'}->{release}->{'track-list'}->{count}->content,
			artist   => undef,
		};
		# artist metadata
		#my $a = _artist($track->{artist}->{id}->content);
		my $a = {
			name     => Encode::encode_utf8($track->{artist}->{name}->content),
			sortname => undef,
			id       => $track->{artist}->{id}->content,
		};

		$track = Audio::Tagger::Meta->new({
			album    => $r,
			track    => $t,
			artist   => $a,
		});

		bless $track->artist, "Audio::Tagger::Lookup::MusicBrainz::Meta::Artist";
		bless $track->album, "Audio::Tagger::Lookup::MusicBrainz::Meta::Album";
		bless $track, "Audio::Tagger::Lookup::MusicBrainz::Meta";
	}
	return @tracks;
}

sub _lookup_id {
	my $trackid = _mbid_check(shift);
	return if length $trackid == 0;

	my $result    = XML::Smart->new($url . "/ws/1/track/" . $trackid . "?type=xml&inc=artist%20releases");

	my $releaseid = $result->{"metadata"}->{track}->{'release-list'}->{release}->{id}->content || '';
	my $result2   = XML::Smart->new($url . "/ws/1/release/" . $releaseid . "?type=xml&inc=artist%20counts");

	return undef if $result->null || $result2->null;

	my $track = $result->{"metadata"}->{"track"};
	# track metadata
	my $t = {
		name     => Encode::encode_utf8($track->{"title"}->content),
		id       => $track->{"id"}->content,
		number   => (1 + $track->{"release-list"}->{"release"}->{"track-list"}->{"offset"}->content),
		duration => $track->{"duration"}->content,
	};

	my $release = $result2->{metadata}->{release};

	# albumartist metadata
	my $aa = {
		name     => Encode::encode_utf8($release->{artist}->{name}->content),
		sortname => Encode::encode_utf8($release->{artist}->{'sort-name'}->content),
		id       => $release->{artist}->{id}->content,
	};

	# release metadata
	my $r = {
		name     => Encode::encode_utf8($release->{title}->content),
		id       => $release->{id}->content,
		count    => $release->{'track-list'}->{count}->content,
		artist   => $aa,
	};
	# artist metadata
	my $a = {
		name     => Encode::encode_utf8($track->{artist}->{name}->content),
		sortname => Encode::encode_utf8($track->{artist}->{'sort-name'}->content),
		id       => $track->{artist}->{id}->content,
	};

	my @tmp = ();
	push @tmp,Audio::Tagger::Meta->new({
		album    => $r,
		track    => $t,
		artist   => $a,
	});

	return @tmp;
}

sub _lucene_escape {
	my $string = shift || "";
	$string =~ s/($escape)/\\$1/g;
	$string =~ s/($tospace)/ /g;
	return lc($string);
}

sub _mbid_check {
	my $id = shift;

	return $id if defined $id && $id =~ m/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;

	return "";
}

1;
package Audio::Tagger::Lookup::MusicBrainz::Meta;
use base qw/Audio::Tagger::Meta/;
sub artist {
	my $self = shift;
	$self->SUPER::artist->sortname;
	return $self->SUPER::artist(@_);
}
sub album {
	my $self = shift;
	$self->SUPER::album->artist;
	return $self->SUPER::album(@_);
}

# Magicly retrive the sortname if it is accessed
package Audio::Tagger::Lookup::MusicBrainz::Meta::Artist;
use base qw/Audio::Tagger::Meta::Artist/;
use Memoize;
memoize('_sortname');

sub sortname {
	my ($self, $sortname) = @_;

	return $self->SUPER::sortname($sortname) if defined $sortname;
	return $self->SUPER::sortname(_sortname($self->id));
}

sub _sortname {
	my $id = shift;
	return undef if not defined $id;

	my $query = $url . "/ws/1/artist/" . $id . "?type=xml";

	my $result = XML::Smart->new($query);
	if ($result->null) {
		$query = Encode::decode("utf-8", $query, Encode::FB_DEFAULT);
		$result = XML::Smart->new($query);
	}

	return undef if $result->null;

	return Encode::encode_utf8($result->{metadata}->{artist}->{'sort-name'}->content),
}
1;

package Audio::Tagger::Lookup::MusicBrainz::Meta::Album;
use base qw/Audio::Tagger::Meta::Album/;
use Memoize;
memoize('_albumartist');

sub artist {
	my ($self,$artist) = @_;
	return $self->SUPER::artist($artist) if defined $artist;
	return $self->SUPER::artist(_albumartist($self->id));
}

sub _albumartist {
	my $album_id = shift;
	return if not defined $album_id;

	my $query = $url . "/ws/1/release/" . $album_id . "?type=xml&inc=artist";

	my $result = XML::Smart->new($query);
	if ($result->null) {
		$query = Encode::decode("utf-8", $query, Encode::FB_DEFAULT);
		$result = XML::Smart->new($query);
	}

	return undef if $result->null;

	return Audio::Tagger::Meta::Artist->new({
		name     => Encode::encode_utf8($result->{metadata}->{release}->{artist}->{name}->content),
		id       => $result->{metadata}->{release}->{artist}->{id}->content,
		sortname => Encode::encode_utf8($result->{metadata}->{release}->{artist}->{'sort-name'}->content),
	});
}

1;

__END__


=head1 NAME

Audio::Tagger::Lookup::MusicBrainz - Wrapper module for talking to MusicBrainz

=head1 SYNOPSIS

This module extends Audio::Tagger::Lookup and overrides the lookup function
with the new MusicBrainz webservice.

Please see Audio::Tagger::Lookup for more information and example usage.

=head1 DESCRIPTION

This module takes takes care of all the work involved in building a suitible
Lucecne query and parsing the resulting XML.

