package Audio::Tagger::Lookup;

use strict;
use warnings;

use Audio::Tagger::Lookup::MusicBrainz;
use String::Approx 'adist', 'adistr';
use Data::Dumper::Simple;


# TODO make lookup OO-based.  check how the function was called...

sub lookup {
#	return (Audio::Tagger::Lookup::Lousy::lookup(@_),Audio::Tagger::Lookup::MusicBrainz::lookup(@_));
#	return (Audio::Tagger::Lookup::Lousy::lookup(@_));
	# TODO make the lookup function compute the scores...
	return Audio::Tagger::Lookup::MusicBrainz::lookup(@_);
}

our $delimiter = "-|\/| _ |   "; # use "-", "/", " _ " and "   " as delimiters
our $minvalue = 0.7;
our $misvalue = 0.4;

# TODO clean up vidars code!

sub score {
	my ($meta, $result, $ignore) = @_;

	return 0 unless defined $result;

	# just so i don't have to check for it in the rest of the matching
	return 0 unless (defined $result->artist->id && defined $result->artist->name);
	return 0 unless (defined $result->album->id && defined $result->album->name);
	return 0 unless (defined $result->album->artist->id && defined $result->album->artist->name);
	return 0 unless (defined $result->track->id && defined $result->track->name && defined $result->track->duration && defined $result->track->number);

	my $filename = $meta->file->absname;
	my $ext = $meta->file->ext;
	$filename =~ s/\.$ext$//;
	$filename =~ s#^.*/([^/]+)/([^/]+)/([^/]+$)#$1/$2/$3#;

	my @filenamefields        = split(/$delimiter/, $filename);
	my @metaartistfields      = split(/$delimiter/, $meta->artist->name) if (defined $meta->artist->name);
	my @metaalbumfields       = split(/$delimiter/, $meta->album->name)  if (defined $meta->album->name);
	my @metatrackfields       = split(/$delimiter/, $meta->track->name)  if (defined $meta->track->name);
	my @metaalbumartistfields = split(/$delimiter/, $meta->artist->name) if (defined $meta->artist->name);

	my $score;

	# if ignore == true skip the following tests
	unless ($ignore) {
		# matching using musicbrainz id's
		$score->{"artistid"} = 1 if (defined $meta->artist->id && $meta->artist->id eq $result->artist->id);
		$score->{"albumid"}  = 1 if (defined $meta->album->id  && $meta->album->id  eq $result->album->id);
		$score->{"trackid"}  = 1 if (defined $meta->track->id  && $meta->track->id  eq $result->track->id);

		if (defined $score->{"artistid"} && defined $score->{"albumid"} && defined $score->{"trackid"}) {
			# we got what we need, return 100% score
			$meta->track->score(100) if $meta->isa('Audio::Tagger');
			return 100;
		}
	}

	my $meta_duration   = $meta->track->duration;
	my $result_duration = $result->track->duration;
	if (defined $meta_duration && $meta_duration =~ m/^[0-9]+$/ && $meta_duration > 0 && $result_duration =~ m/^[0-9]+$/ && $result_duration > 0) {
		my $diff = abs($meta_duration - $result_duration);
		return 0 if ($diff > 5000.0); # song is too much off, it's not the one we're looking for
		$score->{"duration"} = 0.0;
		$score->{"duration"} = 1.0 - ($diff / 5000.0) if ($diff < 5000.0);
	}
	#return 0 if (defined $meta->track->number && !($meta->track->number eq $result->track->number));
	$score->{"tracknum"} = 0.0;
	if (defined $meta->track->number && $meta->track->number eq $result->track->number) {
		$score->{"tracknum"} = 1.0;
	} else {
		my $search = $result->track->number;
		$score->{"tracknum"} = 0.9 if ($filename =~ m/(\D+|^)0$search(\D+|$)|(\D+|^)$search(\D+|$)/);
	}

	$score->{"album"} = 1 if (defined $meta->album->name && $meta->album->name =~ m/^$result->album->name$/i);
	$score->{"album"} = _delimitermatch(\@metaalbumfields, $result->album->name) unless (defined $score->{"albumid"} || defined $score->{"album"});

	$score->{"track"} = 1 if (defined $meta->track->name && $meta->track->name =~ m/^$result->track->name$/i);
	$score->{"track"} = _delimitermatch(\@metatrackfields, $result->track->name, 1) unless (defined $score->{"trackid"} || defined $score->{"track"});

	$score->{"artist"} = 1 if (defined $meta->artist->name && $meta->artist->name =~ m/^$result->artist->name$/i);
	$score->{"artist"} = _delimitermatch(\@metaartistfields, $result->artist->name) unless (defined $score->{"artistid"} || defined $score->{"artist"});

	$score->{"albumartist"} = 1 if (defined $meta->artist->name && !($result->artist->name eq $result->album->artist->name) && $meta->artist->name =~ m/^$result->album->artist->name$/i);
	$score->{"albumartist"} = _delimitermatch(\@metaalbumartistfields, $result->album->artist->name) unless ($result->artist->name eq $result->album->artist->name || defined $score->{"albumartist"});

	$score->{"artist_in_track_field"} = _delimitermatch(\@metatrackfields,       $result->artist->name);

	$score->{"filenametrack"}       = _delimitermatch(\@filenamefields, $result->track->name, 1)         unless (defined $score->{"trackid"});
	$score->{"filenameartist"}      = _delimitermatch(\@filenamefields, $result->artist->name, 1)        unless (defined $score->{"artistid"});
	$score->{"filenamealbumartist"} = _delimitermatch(\@filenamefields, $result->album->artist->name, 1) unless ($result->artist->name eq $result->album->artist->name);
	$score->{"filenamealbum"}       = _delimitermatch(\@filenamefields, $result->album->name, 1)         unless (defined $score->{"albumid"});

	my $finalscore = _calculatescore($score);

	return $finalscore;
};

# Mainly for internal use
sub _calculatescore {
	my $score = shift;

	my $mismatch = 0;

	my $artist;
	$artist = $score->{"artistid"}              if (defined $score->{"artistid"});
	$artist = $score->{"artist"}                if (defined $score->{"artist"} && (!defined $artist || $score->{"artist"} > $artist));
	$artist = $score->{"artist_in_track_field"} if (defined $score->{"artist_in_track_field"} && (!defined $artist || $score->{"artist_in_track_field"} > $artist));
	$mismatch++ if (defined $artist && $artist < ($misvalue));
	$artist = $score->{"filenameartist"} if (defined $score->{"filenameartist"} && (!defined $artist || $score->{"filenameartist"} > $artist));

	my $track;
	$track = $score->{"trackid"}       if (defined $score->{"trackid"});
	$track = $score->{"track"}         if (defined $score->{"track"}         && (!defined $track || $score->{"track"} > $track));
	$track = $score->{"filenametrack"} if (defined $score->{"filenametrack"} && (!defined $track || $score->{"filenametrack"} > $track));
	$mismatch++ if (defined $track && $track < ($misvalue));
	# note:
	# since id3v1 cuts off text when the "track" field got more than 32 characters (bytes)
	# we gotta do the mismatch after we search for the trackname in the filename
	# we should not do this anywhere else
	# only allow this as it's very common that the track title is found in the filename

	my $album;
	$album = $score->{"albumid"} if (defined $score->{"albumid"});
	$album = $score->{"album"} if (defined $score->{"album"} && (!defined $album || $score->{"album"} > $album));
	$mismatch++ if (defined $album && $album < ($misvalue));
	$album = $score->{"filenamealbum"} if (defined $score->{"filenamealbum"} && (!defined $album || $score->{"filenamealbum"} > $album));

	my $albumartist;
	$albumartist = $score->{"albumartist"} if (defined $score->{"albumartist"});
	$mismatch++ if (defined $albumartist && $albumartist < ($misvalue));
	$albumartist = $score->{"filenamealbumartist"} if (defined $score->{"filenamealbumartist"} && (!defined $albumartist || $score->{"filenamealbumartist"} > $albumartist));

	my $duration = $score->{"duration"};

	my $tracknum = $score->{"tracknum"};

	my $tagmatch    = 0;
	my $numbermatch = 0;
	my $tags        = 8;
	my $finalscore  = 1;

	if (defined $artist && $artist > $minvalue) {
		$finalscore *= $artist;
		$tagmatch++;
	}
	if (defined $track && $track > $minvalue) {
		$finalscore *= $track;
		$tagmatch++;
	}
	if (defined $album && $album > $minvalue) {
		$finalscore *= $album;
		$tagmatch++;
	}
	if (defined $albumartist && $albumartist > $minvalue) {
		$finalscore *= $albumartist;
		$tagmatch++;
	}
	if (defined $duration && $duration > 0) {
		$finalscore *= $duration;
		$numbermatch++;
	}
	if (defined $tracknum && $tracknum > 0) {
		$finalscore *= $tracknum;
		$numbermatch++;
	}

	my $magicmultiplier = ($tagmatch + ($numbermatch * 2) - $mismatch);
	my $magicnumber     = ($magicmultiplier * $magicmultiplier) / ($tags * $tags);

#	warn $finalscore, " | ", $magicnumber, " | ", $tagmatch, " | ", $numbermatch, " | ", $mismatch, "\n";
	return 100 * $finalscore * $magicnumber if ($tagmatch >= 3 || ($tagmatch == 2 && $numbermatch > 0) || ($tagmatch == 1 && $numbermatch > 1 && $mismatch == 0));
	return 0;
}

sub _delimitermatch {
	# this... thing...
	# it all makes sense in my head, but i can't explain how it works :\
	my ($meta, $result, $remove) = @_;
	return undef if (!defined $meta || @$meta == 0 || !defined $result || $result eq "");

	my $bestmatch;
	for (my $wm = 1; $wm <= @$meta; $wm++) {
		for (my $sm = 0; $sm <= @$meta - $wm; $sm++) {
			my $score = _compare($result, join(" ", @$meta[$sm .. ($sm + $wm - 1)]));
			if (!defined $bestmatch->{"score"} || $score > $bestmatch->{"score"}) {
				$bestmatch->{"score"} = $score;
				$bestmatch->{"wm"} = $wm;
				$bestmatch->{"sm"} = $sm;
			}
		}
	}
	return 0 unless (defined $bestmatch->{"score"});
	splice(@$meta, $bestmatch->{"sm"}, $bestmatch->{"wm"}) if (defined $remove);
	return $bestmatch->{"score"};
}

sub _compare {
	my ($string1, $string2) = @_;

	# Check that both strings are defined
	return 0.0 unless (defined $string1 && defined $string2);

	# small hack: remove some weird characters to simplify matching
	$string1 =~ s/[\\\/\+:,\|\?_\-]/ /g;
	$string1 =~ s/\ +/ /g;
	$string1 =~ s/^the\ |\ the\ |\.|^\ |\ $//gi;

	$string2 =~ s/[\\\/\+:,\|\?_\-]/ /g;
	$string2 =~ s/\ +/ /g;
	$string2 =~ s/^the\ |\ the\ |\.|^\ |\ $//gi;

	return 0.0 if ($string1 eq "" || $string2 eq "");

	return 1.0 - abs(adist(lc $string1, lc $string2)) / length($string1) if (length($string1) >= length($string2));

	return 1.0 - (abs(adist(lc $string1, lc $string2)) + (length($string2) - length($string1))) / length($string2);
}


# old version...

sub _score_old {
	# Should now support both 00 and non-00 invocation
	my ($meta, $result, $ignore) = @_;

	if($meta->isa('Audio::Tagger') && !$result->isa('Audio::Tagger::Meta')) {
		# TODO write test to check if this even works
		# If result isn't blessed as Audio::Tagger::Meta we assume that
		# $result is the result number stored within $meta which has to
		# be blessed as Audio::Tagger
		$result = $meta->result($result)
			or return;
	}

	my $score = 0;

	# Score based on adistr
	$score += 18 * _compare_old($meta->artist->name, $result->artist->name);
	$score += 15 * _compare_old($meta->album->name, $result->album->name);
	$score += 20 * _compare_old($meta->track->name, $result->track->name);

	# We accept +- 5 secs
	$score += 4 if abs(($meta->track->length||0)- ($result->track->length||0)) < 5;

	# We want an exact match for the following
	$score += 2 if defined $meta->track->number && defined $result->track->number && $meta->track->number eq $result->track->number;


	# if ignore == true skip the following tests
	if  (not $ignore) {
		$score += 20 if defined($meta->track->id) && defined($result->track->id) && $meta->track->id eq $result->track->id;
		$score += 20 if defined($meta->album->id) && defined($result->album->id) && $meta->album->id  eq $result->album->id;
		$score += 20 if defined($meta->artist->id) && defined($result->artist->id) && $meta->artist->id eq $result->artist->id;
	}

	# Give bonus points based on what was selected for last track (only if
	# files are in same folder, and make sure files only get bonus points
	# once

	my $artist = 1;
	my $album  = 1;

	for my $last (@{$meta->{last}}) {
		if (defined $last && $last->file->folder eq $meta->file->folder) {
			if ($artist && $result->artist->id eq $last->artist->id) {
				$score += 5;
				$artist = 0;
			}
			if ($album && $result->album->id eq $last->album->id) {
				$score += 5;
				$album = 0;
			}
		}
	}

	# Compare number of files on album (data from folder, or meta-data)
	$score += 5 if defined $meta->album->count && defined $meta->file->count && defined $result->album->count &&  ($meta->album->count || $meta->file->count)  eq $result->album->count;

	$score = 100 if $score > 100;

	$meta->track->score($score) if $meta->isa('Audio::Tagger');

	return $score;
};

# Mainly for internal use
sub _compare_old {
	my ($string1, $string2) = @_;

	# Check that both strings are defined
	return 0 unless defined($string1) && defined($string2);

	# adistr does substring matching, so we make sure that we combare the
	# strings in the right order
	return 1 - abs adistr($string1, $string2) if (length($string1) > length($string2) );
	return 1 - abs adistr($string2, $string1);
}

1;

__END__

=head1 NAME

Audio::Tagger::Lookup - Lookup song meta-data from data-source and score the results.

=head1 SYNOPSIS

	my @results  = Audio::Tagger::Lookup::lookup($meta);

	for my $result (@results) {
		print "Score: " . Audio::Tagger::Lookup::score($meta, $result);
	}

=head1 DESCRIPTION

This module is responsible for getting meta-data from various data-sources and
scoring the results.  Currently the default lookup method is
L<Audio::Tagger::Lookup::MusicBrainz>.

This method is normaly not called directly though it is posible.

=head1 METHODS

=over

=item lookup

Lookup the supplied file or meta data.

=item score

Compare the two supplied objects and return score between ....

=back

