package Audio::Tagger::File::MP3;

use strict;
use warnings;

use MP3::Tag;

# utf8 and Encode support to fix iso-8859-1 encoded frames
use Encode;
use base qw/Audio::Tagger::File/;

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

	throw Audio::Tagger::File::Error("Object not of type Audio::Tagger::File") unless $self->isa("Audio::Tagger::File");

	my $mp3 = MP3::Tag->new($file)
		or throw Audio::Tagger::File::Error("Could not open file with MP3::Tag : $file");

	$mp3->get_tags;

	my $tag;

	$tag = $mp3->{ID3v1} if defined $mp3->{ID3v1};
	$tag = $mp3->{ID3v2} if defined $mp3->{ID3v2};

	my ($artist, $track, $album, $albumartist);

	if (defined $tag) {
		$artist->{name}     = _fix($tag->artist);
		$artist->{id}       = _get_artist_id($tag);
		$artist->{sortname} = _get_sortname($tag);

		$albumartist->{name}     = _get_albumartist($tag);
		$albumartist->{id}       = _get_albumartist_id($tag);
		$albumartist->{sortname} = _get_albumartist_sortname($tag);

		$track->{name}      = _fix($tag->song);
		$track->{id}        = _get_track_id($tag);
		my $num = $tag->track;

		if(defined $num && $num =~ m/^(([0-9]+)(\/([0-9]+))?)$/) {
			$track->{number} = int($2);
			$track->{number} = undef unless defined $track->{number} && $track->{number} > 0;
			$album->{count}  = int($4) if defined $4;
			$album->{count}  = undef unless defined $album->{count} && $album->{count} > 0;
		} else {
			$track->{number} = undef;
			$album->{count}  = undef;
		}

		$album->{name}      = _fix($tag->album);
		$album->{id}        = _get_album_id($tag);
	}

	$track->{duration} = $mp3->total_secs() * 1000 + $mp3->leftover_msec;

	$self->{bitrate}    = int($mp3->bitrate_kbps||-1);
	$self->{samplerate} = int($mp3->frequency_Hz());

	$self->artist($artist);
	$self->track($track);
	$self->album($album);
	$self->album->artist($albumartist);

	$mp3->close;


	return $self;
}

sub save {
	my ($self, $remove) = @_;
	$remove ||= 0;
	my $mp3 = MP3::Tag->new($self->absname)
		or throw Audio::Tagger::File::Error("Could not open file with MP3::Tag : " . $self->filename);

	$mp3->get_tags;

	# Remove ID3v1 tag and write ID3v2
	$mp3->{ID3v1}->remove_tag if exists $mp3->{ID3v1};
	$mp3->{ID3v2}->remove_tag if $remove;
	$mp3->new_tag("ID3v2") unless defined $mp3->{ID3v2};

	my $tag = $mp3->{ID3v2};
	my $number = $self->track->number;
	$number .= "/" . $self->album->count if defined $self->album->count;

	my %tags = (
		TIT2 => $self->track->name,
		TRCK => $number,
		TALB => $self->album->name,
		TPE1 => $self->artist->name, );

	while (my ($frame, $value) = each %tags) {
		$tag->remove_frame($frame);
		$value = Encode::decode_utf8($value);
		if ($MP3::Tag::ID3v2::VERSION >= 0.9705) { # Encoding support appeared in MP3::Tag version 0.9705
			$tag->add_frame($frame,3,$value) if defined $value;
		} else {
			$tag->add_frame($frame,$value) if defined $value;
		}
	}

	_set_artist_id($tag, $self->artist->id);
	_set_track_id ($tag, $self->track->id);
	_set_album_id ($tag, $self->album->id);
	_set_sortname ($tag, $self->artist->sortname);
	_set_albumartist($tag, $self->album->artist->name);
	_set_albumartist_id($tag, $self->album->artist->id);
	_set_albumartist_sortname($tag, $self->album->artist->sortname);

	my @frames = keys %{$tag->get_frame_ids()};
	for my $frame (@frames) {
		$tag->remove_frame($frame) if $frame =~ m/^(GEOB|MCDI|TCOP|WXXX)/;
	}
	$mp3->{ID3v2}->write_tag(1) # ignore failed conversion of PIC/CMR tags
		or throw Audio::Tagger::File::Error("Could not write tags with MP3::Tag : " . $self->filename);
	$mp3->close;
	1;
}

#                                               $tag,  $field,      $value
sub _set_artist_id            {return _set_txxx(shift, "Artist Id", shift);}
sub _set_album_id             {return _set_txxx(shift, "Album Id",  shift);}
sub _set_sortname             {return _set_txxx(shift, "Sortname",  shift);}
sub _set_albumartist          {return _set_txxx(shift, "Album Artist", shift);}
sub _set_albumartist_sortname {return _set_txxx(shift, "Album Artist Sortname", shift);}
sub _set_albumartist_id       {return _set_txxx(shift, "Album Artist Id", shift);}

sub _get_albumartist          {return _get_txxx(shift, "Album Artist", shift);}
sub _get_albumartist_id       {return _get_txxx(shift, "Album Artist Id", shift);}
sub _get_albumartist_sortname {return _get_txxx(shift, "Album Artist Sortname", shift);}
sub _get_artist_id            {return _get_txxx(shift, "Artist Id", shift);}
sub _get_album_id             {return _get_txxx(shift, "Album Id",  shift);}
sub _get_sortname             {return _get_txxx(shift, "Sortname",  shift);}

sub _get_track_id {
	my ($tag) = @_;
	return unless defined $tag && $tag->isa("MP3::Tag::ID3v2");

	foreach my $name ( grep(/^UFID/, keys(%{$tag->get_frame_ids})) ) {
		my $frame = $tag->get_frame($name);
		# Check if any of them contain the information we are looking
		# for, if $value is set change the frame to new value
		if ($frame->{Text} eq "http://musicbrainz.org") {
			return $frame->{_Data};
		}
	}
	return;
}

sub _set_track_id {
	my ($tag, $value) = @_;
	return unless defined $tag && $tag->isa("MP3::Tag::ID3v2");

	foreach my $name ( grep(/^UFID/, keys(%{$tag->get_frame_ids})) ) {
		my $frame = $tag->get_frame($name);
		# Check if any of them contain the information we are looking
		# for, if $value is set change the frame to new value
		if ($frame->{Text} eq "http://musicbrainz.org") {
			if(defined $value) {
#				$value = Encode::encode_utf8($value);
				$tag->change_frame($name, "http://musicbrainz.org", $value)
					or (warn("Could not change UFID to $value") and return);
				return $value;
			}
			$tag->remove_frame($name);
			return 1;
		}
	}
	if (defined $value) {
		# No previous frames found, creating a new one
		$tag->add_frame("UFID", "http://musicbrainz.org", $value)
			or (warn("Could not set UFID to $value") and return);
		return $value;
	}
	return;
}

sub _get_txxx {
	my ($tag, $type, $value) = @_;
	return unless defined $tag && $tag->isa("MP3::Tag::ID3v2");

	for my $name (grep(/^TXXX/, keys(%{$tag->get_frame_ids}))) {
		# Search the frames for exsisting frames of same type
		my $frame = $tag->get_frame($name);

		if ($frame->{Description} eq "MusicBrainz $type") {
			return $frame->{Text};
		}
	}
	return;
}

sub _set_txxx {
	my ($tag, $type, $value) = @_;
	return unless defined $tag && $tag->isa("MP3::Tag::ID3v2");

	for my $name (grep(/^TXXX/, keys(%{$tag->get_frame_ids}))) {
		# Search the frames for exsisting frames of same type
		my $frame = $tag->get_frame($name);

		if ($frame->{Description} eq "MusicBrainz $type") {
			# Found exsiting frame, update info
			if (defined $value) {
#				$value = Encode::encode_utf8($value);
				$tag->change_frame($name, 0, "MusicBrainz $type", $value)
					or (warn("Couldn't set TXXX frame $type to $value") and return);
				return $value;
			}
			$tag->remove_frame($name);
			return 1;
		}
	}
	if (defined $value) {
		# Create new TXXX frame
		$tag->add_frame("TXXX", 0, "MusicBrainz $type", $value)
			or (warn("Coulnd't add TXXX frame $type with value: $value") and return);
		return $value;
	}
	return;
}

sub _fix {
	my $text = shift;

	return unless defined $text;

	# need better way of checking if text is utf8 !!!
	# Return utf8 string
	return $text if utf8::is_utf8($text);

	# Decode from iso-8859-1 and rencode as utf8
	return encode("utf8", decode("iso-8859-1", $text));
}

1;

__END__

=head1 NAME

Audio::Tagger::File::MP3 - Access and edit ID3v2 meta-data from MP3s

=head1 DESCRIPTION

This module is a simple wrapper around L<MP3::Tag> so that we can use
L<Audio::Tagger> with mp3-files

This class should not be used directly, see L<Audio::Tagger::File> instead.

=head1 BUGS/ISSUES

=head2 UTF-8

Depending on your version of MP3::Tag this module will not be able to indicate
that the meta-data is UTf-8.  All version should be able to save the tags as
UTF-8, but only >= 0.9705 will set the correct encoding so that other
applications easily can determine if the tag is UTF-8

