#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long;
use File::Find;
use Audio::File;
use File::Path qw(make_path);
use File::Copy;

my $VERSION;
$VERSION = '0.1.0';

my $verbose;
my $dry_run;
my $copy;
my %tags;

sub main
{
	my %opts;
	my $dir;
	my $tag_name;
	my $tag_value;

	STDOUT->autoflush();

	Getopt::Long::Configure("no_ignore_case", "bundling", "gnu_compat",
		"no_getopt_compat");
	if (not GetOptions(\%opts,
			"v|verbose",
			"n|dry-run",
			"c|copy",
			"t|tag=s%",
			"h|help",
			"V|version",
		)) {
		print_usage(*STDERR);
		exit(1);
	}

	if (exists($opts{'h'})) {
		help(*STDOUT);
		exit(0);
	}
	if (exists($opts{'V'})) {
		version(*STDOUT);
		exit(0);
	}

	if ($#ARGV lt 0) {
		print_usage(*STDERR);
		exit(1);
	}

	$verbose = $opts{'v'};
	$dry_run = $opts{'n'};
	$copy = $opts{'c'};

	%tags = ('artist' => undef, 'date' => undef, 'album' => undef,
		'tracknumber' => undef, 'title' => undef);
	while (($tag_name, $tag_value) = each(%{$opts{'t'}})) {
		if (exists($tags{$tag_name})) {
			$tags{$tag_name} = $tag_value;
		} else {
			warning("Unknown tag name \"%s\"\n", $tag_name);
		}
	}

	for $dir (@ARGV) {
		find({'wanted' => \&mv_file, 'no_chdir' => 1}, $dir);
	}
}

sub print_usage
{
	my ($fh) = @_;

	printf($fh "Usage: %s <directory> ...\n", $0);
}

sub help
{
	my ($fh) = @_;

	print_usage($fh);
	print("\nOptions:\n");
	print("  -v, --verbose      Print a message for each moved file\n");
	print("  -n, --dry-run      Only simulate file organization without " .
		"moving files\n");
	print("  -c, --copy         Copy rather than move files\n");
	print("  -t, --tag <name>=<value>\n");
	print("                     Override a tag value\n");
	print("  -h, --help         Display this information\n");
	print("  -V, --version      Display version information\n");
}

sub version
{
	my ($fh) = @_;

	printf("organize %s\n", $VERSION);
	print("Copyright (C) 2013 Patrick \"P. J.\" McDermott\n");
	print("License GPLv3+: GNU GPL version 3 or later " .
		"<http://gnu.org/licenses/gpl.html>.\n");
	print("This is free software: you are free to change and " .
		"redistribute it.\n");
	print("There is NO WARRANTY, to the extent permitted by law.\n");
}

sub warning
{
	my ($fmt, @args) = @_;

	printf(STDERR "organize: Warning: " . $fmt, @args);
}

sub mv_file
{
	my $file;
	my $artist;
	my $date;
	my $album;
	my $tracknumber;
	my $title;
	my $length;
	my $type;
	my $new_file;
	my $new_dir;

	if (not -f $_) {
		return 0;
	}

	$SIG{'__WARN__'} = sub{};
	$file = Audio::File->new($_);
	undef $SIG{'__WARN__'};
	if (not ref($file) =~ m/Audio::File.*/) {
		return;
	}

	$artist = $tags{'artist'};
	$artist = $file->tag->artist() unless defined($artist);
	if (not $artist) {
		warning("Missing artist in track \"%s\"\n", $_);
		return;
	}
	$artist =~ s@["*/:<>?\\|]@-@g;

	$date = $tags{'date'};
	$date = $file->tag->year() unless defined($date);
	$date =~ s@["*/:<>?\\|]@-@g if $date;

	$album = $tags{'album'};
	$album = $file->tag->album() unless defined($album);
	$album =~ s@["*/:<>?\\|]@-@g if $album;
	if (not $album or $album eq '-') {
		$album = "(No album)";
	}

	$tracknumber = $tags{'tracknumber'};
	$SIG{'__WARN__'} = sub{};
	$tracknumber = $file->tag->track() unless defined($tracknumber);
	undef $SIG{'__WARN__'};
	# TODO: An option to force this.
	if (not $tracknumber) {
		# Try to detect the track number from the file name.
		$tracknumber = $_;
		$tracknumber =~
			s@^(.*/)?(\Q$artist\E - )?(\d\d)(. | - |_)[^/]*$@$3@i;
		if ($tracknumber eq $_) {
			$tracknumber = undef;
		}
	}

	$title = $tags{'title'};
	$title = $file->tag->title() unless defined($title);
	$title =~ s@^(\Q$artist\E - )?(\d\d(. | - |_))?@@i if $title;
	$title =~ s@["*/:<>?\\|]@-@g if $title;
	if ($title eq '') {
		warning("Missing title in track \"%s\"\n", $_);
		return;
	}

	$length = $file->audio_properties->length();
	$length = sprintf("%d.%02d", $length / 60, $length % 60);

	$type = $file->type();
	$type =~ s@["*/:<>?\\|]@-@g if $type;

	if (not $date and not $tracknumber) {
		$new_file = sprintf("%s/%s/%s (%s).%s",
			$artist, $album, $title, $length, $type);
	} elsif (not $date) {
		$new_file = sprintf("%s/%s/%02d. %s (%s).%s",
			$artist, $album, $tracknumber, $title, $length, $type);
	} elsif (not $tracknumber) {
		$new_file = sprintf("%s/[%s] %s/%s (%s).%s",
			$artist, $date, $album, $title, $length, $type);
	} else {
		$new_file = sprintf("%s/[%s] %s/%02d. %s (%s).%s",
			$artist, $date, $album, $tracknumber, $title, $length,
			$type);
	}

	# FAT apparently forbids trailing spaces in file names.
	$new_file =~ s@ */@/@g;
	$new_file =~ s@ *$@@;

	if (not $dry_run) {
		$new_dir = "./" . $new_file;
		$new_dir =~ s@/[^/]*$@@;
		make_path($new_dir);
	}

	if (not $copy) {
		if ($verbose) {
			printf("Moving \"%s\" to \"%s\"...\n", $_, $new_file);
		}
		if (not $dry_run) {
			move($_, $new_file);
		}
	} else {
		if ($verbose) {
			printf("Copying \"%s\" to \"%s\"...\n", $_, $new_file);
		}
		if (not $dry_run) {
			copy($_, $new_file);
		}
	}
}

main();
