#!/usr/bin/perl
use warnings;
use strict;
use utf8;

use Carp;
use MP3::Tag;
use Getopt::Long;
use File::Temp;

sub hashprint(%);
sub sync_tags($);
sub setsource(@);


our $formatstring = "%a - %t%{c: - %c}.mp3";
our $printformatstring;
our $dorename  = 0;
our $doprint   = 0;
our $zerosep   = 0;
our $verbose   = 0;
our $dosync    = 0;
our $preferid3v= 0;
our $usename   = 0;
our $onlyid3   = 0;
our $overwrite = 0;
our $eol       = "\n";

Getopt::Long::Configure(qw(bundling));
GetOptions(
	"f|formatstring=s" => \$formatstring,
	"F|print-formatstring=s" => \$printformatstring,
		# optinally a separate print string for printing.
		# implies -p
	"z|zero"           => sub { $eol = "\0" },
	"Z|Zero"           => sub { $eol = "" },

	"p|print"          => \$doprint,
		#default if not -r || -s
	"r|rename"         => \$dorename,
	"O|overwrite"      => \$overwrite,
		# while renaming
	"s|sync"           => sub {$dosync += 1},
		# use twice to force updating from empty id3 tags
		# As a side effect, prohibits reading all values from the non-prefered tag
	"v|verbose"        => \$verbose,
		# some debugging output

	"1|prefer-id3v1"   => sub {$preferid3v=1},
	"2|prefer-id3v2"   => sub {$preferid3v=2},
	"prefer-id3=i"     => \$preferid3v,
	"o"                => \$onlyid3,
		# use only specified version of id3 tag (default: 2)
		# will also inhibit creating/modifying the other tag if syncing from filename
	"only-id3=i"       => sub {my $i = shift; $preferid3v=$1; $onlyid3=1; },
		# alias --prefer-id3= -o
	"N|usename"        => sub {$usename++},
		# use twice to prefer, three times to force
	"h|help"           => \&help,
) || exit(1);

if (!$dorename && !$dosync
 || $printformatstring)
	{ $doprint = 1; }

if ($preferid3v > 2 || $preferid3v < 0) {
	die "invalid value for --preferid3";
}
$preferid3v = $preferid3v || 2;

{
	my @id3;
	if ($onlyid3 || $dosync > 1) {
		@id3 = ($preferid3v == 1)? ("ID3v1") : ("ID3v2");
	} else {
		@id3 = ($preferid3v == 1)? ("ID3v1", "ID3v2") : ("ID3v2", "ID3v1");
	}

	if ($usename > 2) {
		setsource("filename");
	} elsif ($usename > 1) {
		setsource("filename", @id3);
	} elsif ($usename) {
		setsource(@id3, "filename");
	} else {
		setsource(@id3);
	}
}

for ($formatstring, $printformatstring) {
	if (!defined $_)
		{ next; }
	if ($_ eq 's') {
		$_ = "%a - %t";
	} elsif ($_ eq 'l') {
		$_ = <<EOF;
File   : >%N%E<
Artist : >%a<
Title  : >%t<%{l:
Album  : >%l<}%{n:
Track  : >%n<}%{c:
Comment: >%c<}%{y:
Year   : >%y<}%{g:
Genre  : >%g<}
EOF
#Times  : >%A<\t>%B<
#Speed  : >%z< [%Z]
	} elsif ($_ eq 'L') {
		$_ = <<EOF;
File   : >%N%E<
Artist : >%a<
Title  : >%t<
Album  : >%l<
Track  : >%n<
Comment: >%c<
Year   : >%y<
Genre  : >%g<
EOF
#Times  : >%A<\t>%B<
#Speed  : >%z< [%Z]
}	}

# main()
for my $f (@ARGV) {
	my $tag = MP3::Tag->new($f);
	$tag->get_tags;
	my $s = $tag->interpolate($formatstring);
	if ($dosync) {
		sync_tags($tag);
	}
	if ($doprint) {
		if (defined $printformatstring) {
			print $tag->interpolate($printformatstring), $eol;
		} else {
			print $s, $eol;
		}
	}
	if ($dorename) {
		my $S = $s;
		$S =~ s.\s*/\s*., .;
		if (!$overwrite && -e $S) {
			$S = File::Temp::tempnam( "./", $s."." );
		}
		print "mv \"\Q$f\E\" \"\Q$S\E\"\n" if $verbose;
		rename($f, $S) || print STDERR "rename(\"\Q$f\E\", \"\Q$S\E\": $!\n";
	}
}

#######################################################

sub setsource(@) {
	for my $a (qw(autoinfo title artist album year comment track genre)) {
		MP3::Tag->config($a, @_);
	}
}

sub sync_tags($) {
	my $tag = shift;
	my $s_preferid3v = ($preferid3v == 1)? "ID3v1" : "ID3v2";

	if ($usename > 2) {
		# overwrite with data from filename
		if ($onlyid3) {
			# only overwrite specified tag, leave alone the other
			$tag->new_tag($s_preferid3v);
		} else {
			# default: overwrite both tags
			$tag->new_tag("ID3v1");
			$tag->new_tag("ID3v2");
		}
	} elsif ($dosync > 1) {
		# overwrite with data from other tag
		my $s_killid3v = ($preferid3v == 1)? "ID3v2" : "ID3v1";
		$tag->new_tag($s_killid3v);
	} elsif ($usename && $onlyid3) {
		# only create/modify prefered tag
		if (!exists $tag->{$s_preferid3v}) {
			$tag->new_tag($s_preferid3v);
		}
	# otherwise, check if we need to create tags.
	# if there are no tags, we may still get new data from the filename
	} elsif (exists $tag->{ID3v1}) {
		if (!exists $tag->{ID3v2}) {
			# sync from existing v2 to non-existing v1
			$tag->new_tag("ID3v2");
		}
		# otherwise, both tags do exist
	} else {
		if (!exists $tag->{ID3v2}) {
			if ($usename) {
				# no tag exists, but we get data from the filename
				# -> create both tags
				$tag->new_tag("ID3v1");
				$tag->new_tag("ID3v2");
			} else {
				# no data to sync, no data from the filename, we are done
				return 0;
			}
		}
		$tag->new_tag("ID3v1");
	}

	$tag->update_tags(\%{$tag->autoinfo()}, 1);

	$tag->{ID3v2}->write_tag if $tag->{ID3v2};
	$tag->{ID3v1}->write_tag if $tag->{ID3v1};
	return 1;
}

sub isset($) {
	my $val = shift;
	return 0 if !defined $val;
	return ($val =~ /\S/);
}

sub hashprint(%) {
	my %x = @_;
	return join(', ', map("\"$_\" => $x{$_}", keys %x));
}

sub help() {
	print <<EOF;
1) Printing information about an mp3 file

  Default: "%a - %t%{c: - %c}.mp3" (with an EOL appended)

Change using:
 -f: Format string for all operations (like rename)
 --formatstring
 -F: Format string for printing only, forces printing
 --print-formatstring
Shorthands:
-fs
  equals -f"%a - %t"
-fl
  long info, with unset fields suppressed
-fL
  long info, all fields I like

Options for printing:
 -p, --print
   do print (default if no other operation (-r or -s)
 -z, --zero
   set eol string to "\\0"
 -Z, --Zero
   set eol string to "" (no printing of an extra EOL


2) Renaming (-r, --rename)

  Renames files using the format string

Options:

 -O, --overwrite
   If the destination file exists, id3 will usually append a random string.
   With -O, it will overwrite the files instead.
 -v, --verbose
   give some debug output.

3) Syncing id3v1 and id3v2 tags (-s, --sync)

Copy the values from the prefered tag into all tags.
If specified once, values will be searched in all tags.
If specified twice, only the prefered tag will be used.
(See below about using the filename)



Selecting the information source:
  -1, --prefer-id3v1
    use id3v1 tag first
  -2, --prefer-id3v2 (default)
    use id3v2 tag first
  --prefer-id3={1|2}
    use specified tag first
  -o
    only use prefered tag
    Do not create/modify the non-prefered tag when syncing from the filename
  --only-id3={1|2}
	alias --prefer-id3= -o
  -N, --usename
    once: use the name of the file as a source
    twice: prefer the information from the name to the tag data
    three times: use only the name
EOF
}
