# id3fs - a FUSE-based filesystem for browsing audio metadata
# Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

package ID3FS::AudioFile::Mp3;

use strict;
use warnings;
use MP3::Tag;
use MP3::Info;

sub new
{
    my $proto=shift;
    my $class=ref($proto) || $proto;
    my $self={};
    bless($self,$class);

    $self->{path}=shift;
    $self->{mp3tag}=MP3::Tag->new($self->{path});
    $self->{mp3info}=MP3::Info->new($self->{path});
    $self->get_tags();
    $self->{tags}={};

    return $self;
}

sub set
{
    my ($self, $func, $value)=@_;
    return $self->choose($func) unless($value);
    unless(exists($self->{mp3tag}->{ID3v1}))
    {
	$self->{mp3tag}->new_tag("ID3v1");
    }
    unless(exists($self->{mp3tag}->{ID3v2}))
    {
	$self->{mp3tag}->new_tag("ID3v2");
    }
    my $method=$func . "_set";
    $self->{mp3tag}->$method($value, 1);
    return $value;
}

sub choose
{
    my($self, $func)=@_;
    my $thing=undef;
    if(exists($self->{mp3tag}->{ID3v2}))
    {
	$thing=$self->{mp3tag}->{ID3v2}->$func();
    }
    if(exists($self->{mp3tag}->{ID3v1}) && (!defined($thing) || !length($thing)))
    {
	$thing=$self->{mp3tag}->{ID3v1}->$func();
    }
    return $thing;
}

sub year      { return(shift->set("year",     @_)); }
sub artist    { return(shift->set("artist",   @_)); }
sub album     { return(shift->set("album",    @_)); }
sub track     { return(shift->set("title",    @_)); }
sub tracknum  { return(shift->set("track",    @_)); }
sub comment   { return(shift->set("comment",  @_)); }

sub audiotype { return "mp3";         }
sub haspic    { return undef;         } # NEXTVERSION

sub v1genre
{
    my($self, $val)=@_;
    if($val)
    {
	$self->{mp3tag}->new_tag("ID3v1") unless(defined($self->{mp3tag}->{ID3v1}));
	$self->{mp3tag}->{ID3v1}->genre($val);
	return $val;
    }
    my $genre=undef;
    $genre=$self->{ID3v1}->genre() if(defined($self->{ID3v1}));
    return $genre;
}

sub tags
{
    my $self=shift;
    return() unless(exists($self->{mp3tag}->{ID3v2}) && defined($self->{mp3tag}->{ID3v2}));
    return($self->{mp3tag}->{ID3v2}->genre());
}

sub get_tags
{
    my ($self)=@_;
    # MP3::Tag->get_tags shows cryptic debug info via print when it finds
    # an unhandled id3v2 version, in addition to the warning, so use
    # select to send prints to /dev/null
    my $oldout=undef;
    if(open(NULL,">/dev/null"))
    {
	$oldout=select(NULL);
    }
    eval { $self->{mp3tag}->get_tags; };
    warn("$self->{path}: $@\n") if($@);
    if(defined($oldout))
    {
	select($oldout);
	close(NULL);
    }
}

sub add_tags
{
    my($self, @tags)=@_;
    my $existing=$self->tags();
    my @existing=split(/\s*,\s*/, $existing) if($existing);
    my @merged=$self->uniq(@tags, @existing);
    my $genre=join(', ', @merged);
    return($self->set("genre", $genre));
}

sub write
{
    my $self=shift;
    if(exists($self->{mp3tag}->{ID3v1}))
    {
	$self->{mp3tag}->{ID3v1}->write_tag;
    }
    if(exists($self->{mp3tag}->{ID3v2}))
    {
	$self->{mp3tag}->{ID3v2}->write_tag;
    }
}

sub delete_artist   { shift->delete("artist");  }
sub delete_album    { shift->delete("album");   }
sub delete_track    { shift->delete("song");    }
sub delete_tracknum { shift->delete("track");   }
sub delete_year     { shift->delete("year");    }
sub delete_v1genre  { shift->delete("v1genre"); }
sub delete_comment  { shift->delete("comment"); }
sub delete_genre    { shift->delete("genre");   }

sub delete_tags
{
    my($self, @tags)=@_;
    my $current=$self->tags();
    my @current=split(/\s*,\s*/, $current);
    my %hash=();
    @hash{@current}=();
    for my $tag (@tags)
    {
	delete($hash{$tag}) if(exists($hash{$tag}));
    }
    my @tagsout=sort keys(%hash);
    my $genre=join(', ', @tagsout);
    if(length($genre))
    {
	return($self->set("genre", $genre));
    }
    else
    {
	return($self->delete_genre());
    }
}

sub delete_all
{
    my($self)=@_;
    if(exists($self->{mp3tag}->{ID3v1}))
    {
	$self->{mp3tag}->{ID3v1}->remove_tag;
    }
    if(exists($self->{mp3tag}->{ID3v2}))
    {
	$self->{mp3tag}->{ID3v2}->remove_tag;
    }
}

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

    if(exists($self->{mp3tag}->{ID3v1}) && $thing ne "genre")
    {
	my $action=$thing;
	$action="genre" if($action eq "v1genre");
	if($action eq "track")
	{
	    $self->{mp3tag}->{ID3v1}->track("00");
	}
	else
	{
	    $self->{mp3tag}->{ID3v1}->$action(" ");
	}
    }

    if(exists($self->{mp3tag}->{ID3v2}))
    {
	print "2: remove: $thing\n";
	if($thing eq "artist")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TPE1");
	    $self->{mp3tag}->{ID3v2}->remove_frame("TPE2");
	}
	elsif($thing eq "album")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TALB");
	}
	elsif($thing eq "song")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TIT2");
	}
	elsif($thing eq "track")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TRCK");
	}
	elsif($thing eq "year")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TYER");
	    $self->{mp3tag}->{ID3v2}->remove_frame("TDRC");
	}
	elsif($thing eq "comment")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("COMM");
	}
	elsif($thing eq "genre")
	{
	    $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
	}
    }
}

sub uniq
{
    my ($self, @things)=@_;
    my %hash=();
    @hash{@things}=();
    return(sort keys(%hash));
}


sub channels
{
    my($self)=@_;
    return undef unless($self->{mp3info});
    return( ($self->{mp3info}->stereo()) ? 2 : 1 );
}

sub bitrate
{
    my($self)=@_;
    return undef unless($self->{mp3info});
    return( int($self->{mp3info}->bitrate()) );
}

sub samplerate
{
    my($self)=@_;
    return undef unless($self->{mp3info});
    return(int($self->{mp3info}->frequency() * 1000));
}


1;
