#!/usr/bin/perl

# mp3cddb
# -------
# complete rewrite by: Peter Nelson <rufus@hackish.org>
#
# originally: mp3tocddb.pl by Meng Weng Wong (MP3::Info)
# modified by: <drewie@bigfoot.com>
# some code from: tagit.pl by Thomas Geffert (MP3::Tag)
#
# 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 2 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 (GPL) along with this program. if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

use strict;
use MP3::Info;
use MP3::Tag;
use CDDB;
use File::Basename;
use Cwd qw(abs_path cwd);
use Term::ReadLine;
use File::Path;

my $term = new Term::ReadLine 'mp3cddb';
unless ($term->Features->{preput})
{
	print q~Error: mp3cddb requires a full featured Term::ReadLine.
Either the ::Gnu or ::Perl versions should work
~;
	exit;
}

if ($ARGV[0] eq "-h" || $ARGV[0] eq "--help")
{
	print q~	 mp3cddb Version 1.0
performs cddb lookups and renaming of mp3s using directories as albums

usage: mp3cddb [-h|--help] [-g|--genres] [-n|--nocddb] [-nr|--norename
               [directory [directory]]

--help:	    print this help message
--genres:   print out a list of all accepted ID3v1 genres
--nocddb:   do not try to do any cddb lookup.  Useful if the id3 tags are more
            accurate than the FreeDB data.
--norename: do not rename any files, only set ID3 tags
directory:  a directory containing mp3s that are numerically in track order.
            if no directory is provided, the current working directory is used.
~;
	exit;
}

if ($ARGV[0] eq "-g" || $ARGV[0] eq "--genres")
{
	print "Accepted ID3v1 Genres:\n\n";
	print join("\n", sort @{MP3::Tag::ID3v1::genres()}) . "\n";
	exit;
}

my $nocddb = 0;
if($ARGV[0] eq "-n" || $ARGV[0] eq "--nocddb")
{
	$nocddb = 1;
	shift @ARGV;
}

my $norename = 0;
if($ARGV[0] eq "-nr" || $ARGV[0] eq "--norename")
{
	$norename = 1;
	shift @ARGV;
}


my %Config;
if (open(CONFIG, $ENV{"HOME"} . "/.mp3cddb"))
{
	while (<CONFIG>)
	{
		next if (/^(\#.*|\s*)$/);
		chomp;
		my ($key, $val) = split(/\s+=\s+/, $_, 2);
		if (defined $Config{$key})
		{
			print "Error: you have multiple $key declirations in ~/.mp3cddb\n";
			exit;
		}
		$Config{$key} = $val;
	}
	close(CONFIG);
	
	if (!$Config{"RENAME"} && !$Config{"DONTRENAME"})
	{
		print "You seem to want me to rename files, yet you don't tell me how to...\n" . 
			"I might be smart, but I can't read your mind..only ~/.mp3cddb\n";
		exit;
	}
	if($Config{"RENAME"}){ &check_config_rename($Config{"RENAME"}); }
	if($Config{"RENAMEVA"}){ &check_config_rename($Config{"RENAMEVA"}); }

	# thanks to irc://irc.openprojects.net/#perl !
	# rlandrum: if($var =~ /\{([^\}]+)\}/ && (@x = ($1 =~ m/\%a[aty]/g)) && @x == 1) {
	# xsdg:		perl -e '$txt = "%a - {%b -}%c"; if($txt =~ m#^[^}]* ([^{]* \{ [^}]* \%[^}]+ \})+ [^{}]*$#x){print "moo"}'
	# xsdg:		moo
}
else
{
	print "you don't seem to have a $ENV{'HOME'}/.mp3cddb config file.\n" .
		"I am now writing an example one there, be sure to read it and change as needed!\n";
	&write_config;
	exit;
}

if (! @ARGV)
{
	push(@ARGV, cwd);
}

# global hash of what we ended up doing
my %result;
# global array so that we can re-display errors (in case they get missed)
my @errors;

my $cddb = new CDDB (Debug=>0, Protocol_Version=>5) or die "unable to connect to CDDB: $!";

# having the complete absolute paths is a GOOD THING
my @albums = map { abs_path($_) } @ARGV;

ALBUM: foreach my $album (@albums)
{
	next ALBUM unless (-d $album);
	
	my @cdtoc;
	my @cddb_query;
	my @tracks_in;
	my %disc_info;
	
	print "Working on $album\n";
	
	# wow you really have to quote the hell out of stuff for glob
	my $quoted = $album;
	$quoted =~ s/([\[\]])/\\$1/g;
	foreach my $file (sort
			  {
			    $a=~m!^.*/[^/\d]*(\d+)[^/]+$!;
			    my $c=$1;
			    $b=~m!^.*/[^/\d]*(\d+)[^/]+$!;
			    my $d=$1;
			    $c <=> $d
			  }
			  glob quotemeta($quoted) . "/*.[Mm][Pp]3")
	{
		my $info  = get_mp3info($file);

		push (@cdtoc, $info );
		push (@tracks_in, basename($file));
	}
	
	unless ($#tracks_in >= 1)
	{
		&do_error("There are 1 or fewer mp3s in '$album'", $album);
		next ALBUM;
	}
	
	my ($my_disc_id,
		$my_total_tracks,
		$my_total_time,
		@my_frames) = &build_cddb_query(@cdtoc);
	
	print "searching for discid: $my_disc_id, total tracks: $my_total_tracks, total time: ".
	  int($my_total_time/60).":".($my_total_time%60)."\n";
	
	my @discs;
	unless($nocddb)
	{
		@discs = $cddb->get_discs($my_disc_id, [@my_frames], $my_total_time);
	}

	my $newblank = 0;
	if (! @discs)
	{
		print q~
Disk was not found.
Enter a DiscID then Genre (from freedb.org) to use,
a url from freedb.org with the DiscID and Genre in it,
`blank' to create a blank record,
`id3' to aquire data form ID3 tags as a single-artist album,
`id3va' to aquire artists from ID3 tags as a various-artist album,
or leave empty to skip.
~;
		my $new_disc_id = $term->readline("DiscID: ");
		if ($new_disc_id eq "blank")
		{
			$newblank = 1;
		}
		elsif ($new_disc_id eq "id3")
		{
			$newblank = 2;
		}
		elsif ($new_disc_id eq "id3va")
		{
			$newblank = 3;
		}
		elsif (($new_disc_id =~ /^[a-f0-9]{8}$/i) &&
		       ((my $new_genre = $term->readline("Genre: ")) ne ""))
		{
			@{$discs[0]} = ($new_genre, $new_disc_id, "");
		}
		elsif($new_disc_id =~ /cat=(.+?)\&id=([a-f0-9]{8})/i)
		{
			@{$discs[0]} = ($1, $2, "");
		}
		else
		{
			&do_error("Skipped directory '$album'", $album);
			next ALBUM;
		}
	}
	
	my @ranking;
	if (!$newblank)
	{
	
		$|=1;					# unbuffer output...buffer craps up the status output
	
		foreach my $disc (@discs)
		{
			print ".";			# nice status if there are lots of CD's
			
			my ($genre, $cddb_id, $title) = @$disc;
			$disc_info{"$cddb_id ($genre)"} = $cddb->get_disc_details($genre, $cddb_id);
			unless($disc_info{"$cddb_id ($genre)"})
			{
				&do_error("Could not find $cddb_id ($genre) for '$album'", $album);
				next ALBUM;
			}
			$disc_info{"$cddb_id ($genre)"}->{'dgenre'} ||= $genre;
		}
	
		print "\n";				# end status stuff
		$|=0;					# can buffer again
	
		push (@my_frames, $my_total_time * 75);
		my @my_lengths = &offsets_to_seconds (@my_frames);
		my %distance;
		foreach my $cddb_id (sort keys %disc_info)
		{
			my $disc_time = ($disc_info{$cddb_id}->{'disc length'} =~ /(\d+)/)[0];
			my $disc_id = $disc_info{$cddb_id}->{'discid'};
			my @track_offsets = @{$disc_info{$cddb_id}->{'offsets'}};
			
			push (@track_offsets, $disc_time * 75 + $track_offsets[0]);
			my @track_lengths = &offsets_to_seconds (@track_offsets);
			
			$distance{$cddb_id} = &sqr_distance (\@track_lengths, \@my_lengths);
		}
		
		@ranking = sort { $distance{$a} <=> $distance{$b} } keys %distance;
	}
	elsif($newblank == 1)
	{
		$disc_info{"new"}->{'dtitle'} = "";
		foreach my $i (0..$#tracks_in)
		{
			$disc_info{"new"}->{'ttitles'}[$i] = "";
		}
		@ranking = ("new");
	}
	elsif($newblank == 2 || $newblank == 3)
	{
		$disc_info{"new"} = get_id3tags($album, ($newblank==3), @tracks_in);
		@ranking = ("new");
	}

	if (my $choice = &prompt_best ($album, \@ranking, \%disc_info, \@tracks_in))
	{
		&tag_files ($album, $disc_info{$choice}, \@tracks_in);
		$result{$album} = "tagged";
	
		if ($Config{'DORENAME'} && !$norename)
		{
			if (&do_rename ($album, $disc_info{$choice}, \@tracks_in))
			{
				$result{$album} .= " and renamed";
			}
		}
	}
	else
	{
		print "skipping $album, moving on\n\n\n";
		$result{$album} = "skipped";
	}
}

print "Summary of actions:\n";

foreach my $album ( sort keys %result )
{
	print "$result{$album}\t  $album\n";
}

if (@errors)
{
	print "\n\nThere seem to have been some errors:\n\n";
	print "$_\n\n" foreach @errors;
}

exit;


####################
#		   #
#  My subroutines  #
#		   #
#  influenced by   #
# Thomas Geffert's #
#	  tagit.pl #
####################

sub get_id3tags
{
	my ($album, $va, @tracks_in) = @_;
	my $info;

	my $mp3 = MP3::Tag->new($album."/".$tracks_in[0]);
	my @temp = $mp3->autoinfo();
	$info->{'dtitle'} = $temp[2]." / ".$temp[3];
	$info->{'dyear'} = $temp[5];
	$info->{'dgenre'} = $temp[6];
	$info->{'comment'} = $temp[4];
	$mp3->close();

	foreach my $i (0..$#tracks_in) {
		my $mp3 = MP3::Tag->new($album."/".$tracks_in[$i]);
		my @temp = $mp3->autoinfo();
		if($va)
		{
			$info->{'ttitles'}[$i] = $temp[2]." / ".$temp[0];
		}
		else
		{
			$info->{'ttitles'}[$i] = $temp[0];
		}
		$mp3->close();
	}
	return $info;
}	

# prints the error and adds it to the global array of errors
# if $album is defined, it's a fatal error for that album and $result is updated as such
sub do_error
{
	my ($error, $album) = @_;
	print "$error\n\n\n";
	push(@errors, $error);
	$result{$album} = "error";
}
sub check_config_rename
{
	my $rename=shift;
	unless ($rename =~ m!
^(?:
	# each start can be anything not special
	[^{}%]*
		# test to make sure {}'s match and has only 1 proper %tag
		(?: \{ [^{}%]* \% (?:a[tagy]|t[abnt]) [^{}%]* \} |
			# or test just proper %tag not in {}'s
			\% (?:a[tagy]|t[abnt])
		)
)+
# end can be anything not special
[^{}%]* $
								   !oix)
# had enough RegEx fun for today? =)
	{
		print qq!You seem to have a bad RENAME[VA] line in ~/.mp3cddb.	Please check:
	You have matching {}\'s
	You have only 1 %tag inside of each {}
	You are using the apropriate tags (%at, %ta, etc)
!;
		exit;
	}

	if ($rename =~ m/\%t[abnt].*\//i)
	{
		print q!RENAME[VA] contains track-specific data (ta, tb, tn, tt) inside of a directory name.
This will break the renaming, please limit directory names to whole-album data
!;
		exit;
	}

}
sub do_rename
{
	my ($album, $info, @tracks) = (shift, shift,  @{+shift});

	my ($new_dir, @new_files) = &new_names ($album, $info, \@tracks);
	
	if (&make_path($new_dir, $album))
	{
		foreach my $num (0 .. $#tracks)
		{
			unless( rename "$new_dir/$tracks[$num]", "$new_dir/$new_files[$num]" )
			{
				&do_error("Couldn't move '$album/$tracks[$num]' to\n'$new_dir/$new_files[$num]'\nReason: $!", $album);
				return undef;
			}
		}
	}
	else
	{
		#make_path errored or something
		return undef;
	}

	return 1;
}

sub make_path
{
	my ($new_path, $old_path) = @_;
	
	return 1 if $new_path eq $old_path;
	
	mkpath(dirname($new_path), 0, 755);

	if ( -d $new_path )
	{
		# $new_path already exists, lets move everything into it and get rid of $old_path
	
		my $album_glob = $old_path;
		$album_glob =~ s/(\s)/\\$1/oig;
	
		foreach my $file (glob("$album_glob/*"))
		{
			unless( rename $file, "$new_path/" . basename($file) )
			{
				&do_error("Couldn't move '$file' to '$new_path/" . basename($file) . "'\nReason: $!", $old_path);
				return undef;
			}
		}
	
		if (glob("$album_glob/*"))
		{
			&do_error("There seems to be something left in '$old_path', yet it should be empty");
		}
		else
		{
			unless( rmdir($old_path) )
			{
				&do_error("Couldn't remove empty '$old_path'\nReason: $!");
			}
		}
	}
	else
	{
		# $new_path doesn't exist, so we can just rename old to new and be done
		unless( rename $old_path, $new_path )
		{
			&do_error("Couldn't move '$old_path' to '$new_path'\nReason: $!", $old_path);
			return undef;
		}
	}
	return 1;
}


# For my own reference
#	%at:  Album Title
#	%aa:  Album Artist
#	%ag:  Album Genre
#	%ay:  Album Year
#	%ac:  Album Comment
#	%tn:  Track Number (padded to 2 characters, 01..10..99)
#	%tt:  Track Title
#	%ta:  Track Artist (always set)
#	%tb:  Track Artist (only set if different from Album)
#	{.*(%..).*}: optional block
#	%%, %{, %}: replaced by %, {, and }

sub new_names {
	my ($album, $info, @tracks) = (shift, shift,  @{+shift});
	my @renamed;
	my %tag_info;

	# for %%, %{, and %}
	$tag_info{'%'} = '%';
	$tag_info{'{'} = '{';
	$tag_info{'}'} = '}';
	# get whole-album vars
	($tag_info{'aa'}, $tag_info{'at'}) = &split_title($info->{'dtitle'});
	$tag_info{'ag'} = $info->{'dgenre'};
	$tag_info{'ay'} = $info->{'dyear'};
	$tag_info{'ac'} = $info->{'comment'};
	my ($new_dir, $new_file);

	if($info->{'NoVA'} || !$Config{'RENAMEVA'}){
		$Config{'RENAME'} =~ /^(.*?)(?:\/)?([^\/]+)$/o;
		($new_dir, $new_file) = ($1, $2);
	} else {
		$Config{'RENAMEVA'} =~ /^(.*?)(?:\/)?([^\/]+)$/o;
		($new_dir, $new_file) = ($1, $2);
	}
	

	if ($new_dir)
	{
		foreach my $key (keys %tag_info)
		{
			$tag_info{$key} =~ tr!\/\\\:\?\"|\*!-!;
			$tag_info{$key} =~ tr!<>![]!;
			$tag_info{$key} =~ tr! !_! if $Config{'UNDERSCORE'};
		}

		# only album stuff (a[tagy]) is allowed in the dir name
		$new_dir =~ s!
					  (?:
						\{ ([^%{}]*) \% (a[tagy]|[%{}]) ([^%{}]*) \}
						 | ([^%{}]*) \% (a[tagy]|[%{}]) ([^%{}]*)
					  )
					 !
					 ( $5
					   ? $4.$tag_info{$5}.$6
					   : ( $tag_info{$2}
						   ? $1.$tag_info{$2}.$3
						   : ''
						 )
					 )!xoge;
		# if it's complete (starts with /) it's fine, otherwise complete it from $album
		unless($new_dir =~ /^\//o)
		{
			$new_dir = dirname($album) . "/" . $new_dir;
		}
	}
	else
	{
		# if there's no new directory in the config, just use $album
		$new_dir = $album;
	}
	
	foreach my $tNumber (0 .. $#tracks)
	{
		$tag_info{'tn'} = sprintf("%02u", $tNumber + 1);
		if($info->{'NoVA'})
		{
			($tag_info{'tb'}, $tag_info{'tt'}) = (undef, $info->{'ttitles'}[$tNumber]);
		}
		else
		{
			($tag_info{'tb'}, $tag_info{'tt'}) = &split_title($info->{'ttitles'}[$tNumber]);
		}
		# ta is always set, tb sometimes (VA cds)
		$tag_info{'ta'} = $tag_info{'tb'} || $tag_info{'aa'};

		# we have to do this each time to hit the new track info
		foreach my $key (keys %tag_info)
		{
			$tag_info{$key} =~ tr!\/\\\:\?\"|\*!-!;
			$tag_info{$key} =~ tr!<>![]!;
			$tag_info{$key} =~ tr! !_! if $Config{'UNDERSCORE'};
		}
		
		my $newname = $new_file;
		
		unless($newname =~ /\.\S{1,3}$/o)
		{
			$newname .= ".mp3";
		}
		
		$newname =~ s!
					  (?:
						\{ ([^%{}]*) \% (a[tagy]|t[abnt]|[%{}]) ([^%{}]*) \}
						 | ([^%{}]*) \% (a[tagy]|t[abnt]|[%{}]) ([^%{}]*)
					  )
					 !
					 ( $5
					   ? $4.$tag_info{$5}.$6
					   : ( $tag_info{$2}
						   ? $1.$tag_info{$2}.$3
						   : ''
						 )
					 )!xoge;
		push(@renamed, $newname);
	}
	return $new_dir, @renamed;
}

sub tag_files
{
	my ($album, $info, @tracks) = (shift, shift, @{+shift});
	
	my ($aArtist, $aTitle) = &split_title($info->{'dtitle'});
	
	foreach my $tNumber (0 .. $#tracks)
	{
		my ($tArtist, $tTitle);
		if($info->{'NoVA'})
		{
			$tTitle = $info->{'ttitles'}[$tNumber];
		}
		else
		{
			($tArtist, $tTitle) = &split_title($info->{'ttitles'}[$tNumber]);
		}
		
		# if there's a track artist, use it...otherwise use album artist
		$tArtist ||= $aArtist;
		
		my $mp3 = MP3::Tag->new($album . "/" . $tracks[$tNumber]);
		$mp3->get_tags();
		
		if ($Config{"DELID3V1"} && exists $mp3->{ID3v1})
		{
			$mp3->{ID3v1}->remove_tag();
		}
		
		if ($Config{"DELID3V2"} && exists $mp3->{ID3v2})
		{
			$mp3->{ID3v2}->remove_tag();
		}
		
		if ($Config{"ID3V1"})
		{
			# remove_tag on ID3v1 doesn't actually remove it, just blanks it..
			#so we don't have to worry about recreating it
			$mp3->new_tag("ID3v1") unless exists $mp3->{ID3v1};
			
			$mp3->{ID3v1}->song($tTitle);
			$mp3->{ID3v1}->artist($tArtist); 
			$mp3->{ID3v1}->album($aTitle);
			$mp3->{ID3v1}->comment($info->{'comment'}) if $info->{'comment'};
			$mp3->{ID3v1}->year($info->{'dyear'}) if $info->{'dyear'};
			$mp3->{ID3v1}->genre($info->{'dgenre'}) if $info->{'dgenre'};
			$mp3->{ID3v1}->track($tNumber + 1);
			
			$mp3->{ID3v1}->write_tag();
		}
		
		if ($Config{"ID3V2"})
		{
			# remove_tag on ID3v2 actually destorys the tag, so we have to re-create it when we delete it
			$mp3->new_tag("ID3v2") unless exists $mp3->{ID3v2} && !$Config{"DELID3V2"};
			
			my $frameID = $mp3->{ID3v2}->get_frame_ids;
			
			# remove frame (does nothing if it doesn't exist) and then add new one
			$mp3->{ID3v2}->remove_frame("TIT2"); $mp3->{ID3v2}->add_frame("TIT2", $tTitle);
			# artist could be either TPE1 or TPE2, so nuke both
			$mp3->{ID3v2}->remove_frame("TPE1"); $mp3->{ID3v2}->remove_frame("TPE1");
			$mp3->{ID3v2}->add_frame("TPE1", $tArtist);
			
			$mp3->{ID3v2}->remove_frame("TALB"); $mp3->{ID3v2}->add_frame("TALB", $aTitle);
			$mp3->{ID3v2}->remove_frame("COMM"); $mp3->{ID3v2}->add_frame("COMM", "ENG", "Comment",	 $info->{'comment'}) if $info->{'comment'};
			$mp3->{ID3v2}->remove_frame("TYER"); $mp3->{ID3v2}->add_frame("TYER", $info->{'dyear'}) if $info->{'dyear'};
			$mp3->{ID3v2}->remove_frame("TCON"); $mp3->{ID3v2}->add_frame("TCON", $info->{'dgenre'}) if $info->{'dgenre'};
			$mp3->{ID3v2}->remove_frame("TRCK"); $mp3->{ID3v2}->add_frame("TRCK", ($tNumber + 1) );
			
			$mp3->{ID3v2}->write_tag();
		}
	}
}

sub prompt_best
{
	my $album = shift;
	my @ranking = @{+shift};
	my %disc_info = %{+shift};
	my @tracks_in = @{+shift};
	
	my $current = $ranking[0];
	
 SELECT: while (1)
	{
		if ($#ranking)
		{
			print "\nMore than one possible matches are available.\n" .
				"The following is a list of all possible ones, sorted by my best guess:\n";
			
			foreach my $choice_num (0 .. $#ranking)
			{
				print join("\t", '', $choice_num + 1, $disc_info{$ranking[$choice_num]}->{'dgenre'},
						   $disc_info{$ranking[$choice_num]}->{'dtitle'},
						   $disc_info{$ranking[$choice_num]}->{'discid'}) . "\n";
			}
			print "\nThis is my best guess or your current selection:\n\n";
		}
		else
		{
			print "\nHere is the only available match:\n\n";
		}
		
		my $var_artist = &print_info($disc_info{$current});
		if($var_artist) {$disc_info{$current}->{'NoVA'} = 0;}
		else {$disc_info{$current}->{'NoVA'} = 1;}
		while (1)
		{
			print "\nChoices: use this match (Y), ";
			print "view a different match (1-" . ($#ranking + 1) . "), " if $#ranking;
			print "edit album info (e), edit track listing (t), ";
			print "show example renamed filenames (s), " if $Config{'DORENAME'};
			print "or disregard this album entirely (n)\n";
			
			print "This appears to be a Various Artist CD, select (a) if the artist (1st) " .
				"and track name (2nd) to be switched or (v) if this really isn't a VA cd.\n" if $var_artist;
			my $key = $term->readline("? ");
			
			if ($key =~ /^y$/oi || $key eq "")
			{
				return $current;
			}
			elsif (1 <= $key && $key <= ($#ranking + 1))
			{
				$current = $ranking[$key - 1];
				next SELECT;
			}
			elsif ($key =~ /^n$/oi)
			{
				return undef;
			}
			elsif ($key =~ /^e$/oi)
			{
				&edit_disc ($disc_info{$current});
				next SELECT;
			}
			elsif ($key =~ /^t$/oi)
			{
				&edit_track ($disc_info{$current});
				next SELECT;
			}
			elsif ($key =~ /^s$/oi)
			{
				my($new_dir, @renamed) =  &new_names ($album, $disc_info{$current}, \@tracks_in);
				foreach my $file (@renamed)
				{
					print "$new_dir/$file\n";
				}
			}
			elsif ($var_artist && $key =~ /^a$/oi)
			{
				&swap_titles ($disc_info{$current});
				next SELECT;
			}
			elsif ($var_artist && $key =~ /^v$/oi)
			{
				$disc_info{$current}->{'NoVA'} = 1;
				next SELECT;
			}
		}
	}
}

sub edit_disc
{
	my ($info) = @_;
	
	my ($artist, $real_title) = &split_title($info->{'dtitle'});
	my ($input1, $input2);
	
	$input1 = $term->readline("Artist: ",$artist);
	
	$input2 = $term->readline("Title: ",$real_title);
	$info->{'dtitle'} = $input1 . " / " . $input2;
	
	print "Make sure this matches the --genres list to be able to write to ID3V1 tags\n";
	$info->{'dgenre'} = $term->readline("Genre: ",$info->{'dgenre'});
	$info->{'dyear'} = $term->readline("Year: ",$info->{'dyear'});
	$info->{'comment'} = $term->readline("Comment ", $info->{'comment'});
}

sub edit_track
{
	my ($info) = @_;

	foreach my $track_number (0 .. $#{$info->{'ttitles'}})
	{
		my ($track_artist, $track_title);
		if($info->{'NoVA'}) 
		{
			$track_title = $info->{'ttitles'}[$track_number];
		}
		else
		{
			($track_artist, $track_title) = &split_title($info->{'ttitles'}[$track_number]);
		}
		if ($track_artist || $info->{'prompt'})
		{
			$track_artist = $term->readline("Track " . ($track_number + 1) .". Artist ", $track_artist);
		}
		$track_title = $term->readline("Track " . ($track_number + 1) .". Title	 ", $track_title);
		$info->{'ttitles'}[$track_number] = ($track_artist?"$track_artist / " :'') . $track_title;
	}
}

sub print_info
{
	my ($info) = @_;

	my ($artist, $real_title) = &split_title($info->{'dtitle'});
	my $var_artist = 0;
	
	print "Artist:\t$artist\nTitle:\t$real_title\n";
	print "Year:\t$info->{'dyear'}\n" if $info->{'dyear'};
	print "Genre:\t$info->{'dgenre'}\n" if $info->{'dgenre'};
	print "Comment:\t$info->{'comment'}\n" if $info->{'comment'};
	
	my @track_titles  = @{$info->{'ttitles'}};
	
	foreach my $track_number (0 .. $#track_titles)
	{
		my ($artist, $title);
		if($info->{'NoVA'})
		{
			$title = $track_titles[$track_number];
		}
		else
		{
			($artist, $title) = &split_title($track_titles[$track_number]);
		}
		printf "%2d.  %-40s%-s\n", ($track_number + 1), ($artist?$artist:$title), ($artist?$title:'');
		$var_artist = 1 if $artist;
	}
	return $var_artist;
}

sub swap_titles {
	my ($info) = @_;
	
	foreach my $track_number (0 .. $#{$info->{'ttitles'}})
	{
		my ($artist, $title) = &split_title($info->{'ttitles'}[$track_number]);
		$info->{'ttitles'}[$track_number] = "$title / $artist" if $artist;
	}
}

sub split_title
{
	local $_ = shift;
	
	my ($artist, $title);
	
	if (/(.*?)\s*\/\s*(.*)/)  { ($artist, $title) = ($1, $2) }
	elsif (/(.*?)\s+-+\s+(.*)/)	 { ($artist, $title) = ($1, $2) }
	else						 { ($artist, $title) = (undef, $_) }
	
	for ($artist, $title) { s/^\s*//; s/\s*$// }
	
	return ($artist, $title);
}


#######################
#		      #
#  CDDB Lookup Stuff  #
#		      #
#  by Meng Weng Wong  #
#######################

sub sqr_distance
{
	my @vector1 = @{+shift};
	my @vector2 = @{+shift};
	my $total = 0;

	foreach my $dimension (0 .. ($#vector1 < $#vector2 ? $#vector1 : $#vector2)) # too much paranoia never hurt anyone
	{
		my $difference = abs($vector1[$dimension] - $vector2[$dimension]);
		my $square	   = $difference ** 2;
		$total		  += $square;
	}
	return $total;
}

sub frames_to_ss
{
	my $frames = shift;
	my $ss = int($frames / 75);
	return $ss;
}

sub ss_to_mmss
{
	my $ss = shift;
	my $mm = $ss / 60;
	   $ss = $ss % 60;
	return sprintf ("%02d:%02d", $mm, $ss);
}

sub offsets_to_seconds
{
	my @offsets = @_;
	my @track_lengths = ();
	while (@offsets > 1)
	{
		unshift(@track_lengths, pop (@offsets) - $offsets[-1]);
	}
	return map { &frames_to_ss ($_) } @track_lengths;
}

sub build_cddb_query
{
	my @cdtoc = @_;
	my $count = 1;
	
	foreach (@cdtoc)
	{
		my ($mm, $ss) = ($_->{MM}, $_->{SS});
	}
	
	my $discid = cddb_discid(@cdtoc);
	
	my @frames		   = &invent_frame_numbers(@cdtoc);
	my $total_time	   = &total_time(@cdtoc);
	my $total_tracks   = @cdtoc;
	
	my $login		   = $ENV{USER};
	my $hostname	   = &hostname; use Sys::Hostname; $hostname = `hostname` if $hostname !~ /\./;
	my $client_name	   = "mp3cddb";
	my $client_version = "beta";

	return ( $discid,
			 $total_tracks,
			 $total_time,
			 @frames );
}

sub cddb_sum
{
	my ($n, $ret) = (shift, 0);
	for (split //, $n) { $ret += $_ }
	return $ret;
}

sub total_time
{
	my @cdtoc	   = @_;
	my $total_time = 0;
	
	foreach my $track (@cdtoc)
	{
		my $track_time = $track->{MM} * 60 + $track->{SS};
		$total_time += $track_time;
	}
	return $total_time;
}


sub cddb_discid
{
	my @cdtoc	   = @_;
	my $n		   = 0;
	my $total_time = 0;
	
	foreach my $track (@cdtoc)
	{
		my $track_time = $track->{MM} * 60 + $track->{SS};
		$n			+= &cddb_sum($total_time);
		$total_time +=			 $track_time;
	}
	return sprintf("%08x", ($n % 0xFF) << 24 | $total_time << 8 | @cdtoc);
}

sub invent_frame_numbers
{
	my @cdtoc	   = @_;
	my $n		   = 0;
	my $total_time = 0;
	
	foreach my $track (@cdtoc)
	{
		$track->{FRAME_OFFSET} = $total_time * 75;
		
		my $track_time	= $track->{MM} * 60 + $track->{SS};
		$total_time	   += $track_time;
	}
	return map { $_->{FRAME_OFFSET} } @cdtoc;
}


###################################
#				  #
#  Write the example config file  #
#				  #
###################################

sub write_config{
	open(WRITE, ">" . $ENV{"HOME"} . "/.mp3cddb") || die "couldn't write example config file!";
	print WRITE
q~# mp3cddb example configuration file
# YOU MUST ENABLE ONE OF THE RENAME LINES OR ELSE THE PROGRAM WILL NOT RUN!
# to enable one of the lines remove the # (comment) from before it, or add your own to the bottom
# options are 1 for true, 0 for false


# ID3V1 / ID3V2: write ID3 V1/2 tags
ID3V1 = 1
ID3V2 = 1

# DELID3V1 / DELID3V2: delete any existing ID3 V1/2
# if both this and above are set, then it will remove any existing tag and write a new clean one
DELID3V1 = 0
DELID3V2 = 0


# DORENAME: Rename the Files
DORENAME = 1

# UNDERSCORE: convert all spaces to underscores in file names
UNDERSCORE = 0

# RENAME: defines how to rename the files / directory

# Available variables:
#  %at:  Album Title
#  %aa:  Album Artist
#  %ag:  Album Genre
#  %ay:  Album Year
#  %ac:  Album Comment
#  %tn:  Track Number (padded to 2 characters, 01..10..99)
#  %tt:  Track Title
#  %ta:  Track Artist (always set)
#  %tb:  Track Artist (only set if different from Album)
# In case you actually want a literal %, {, or } there are the following:
#  %%, %{, %}: replaced by %, {, and }

# You can make a portion optional by enclosing in within {}, for example:
#  %aa - {%ay - }%at
# Will write either "Artist - Title" or "Artist - Year - Title"
# NOTE: do NOT put more than one variable within a single {}!

# Here are a few examples, showing options and how directories are handled
# READ THROUGH ALL OF THEM to get an idea of how exactly you want yours to be
# hint: just look for the output that closest resembles your current output!

# Each example shows the results on the following 2 files:
# /mp3s/New Downloads/into the unknown-Bad_religion-aps/01-its_only_over_when-bad_religion-aps.mp3
# /mp3s/Ska/VA-Ska_Sucks/Ska Sucks- 01- Dance Hall Crashers- He Wants Me Back.mp3


#  1. Ignore directories: if RENAME has no directory info, only the files
#     inside a directory are renamed

# Very simple
#RENAME = %tn - %tt
# /mp3s/New Downloads/into the unknown-Bad_religion-aps/01 - Its Only Over When.mp3
# /mp3s/Ska/VA-Ska_Sucks/01 - He Wants Me Back.mp3

# Show artist only when different from album
#RENAME = %tn - {%tb - }%tt
# /mp3s/New Downloads/into the unknown-Bad_religion-aps/01 - Its Only Over When.mp3
# /mp3s/Ska/VA-Ska_Sucks/01 - Dance Hall Crashers - He Wants Me Back.mp3

# Always shows artist
#RENAME = %tn - %ta - %tt
# /mp3s/New Downloads/into the unknown-Bad_religion-aps/01 - Bad Religion - Its Only Over When.mp3
# /mp3s/Ska/VA-Ska_Sucks/01 - Dance Hall Crashers - He Wants Me Back.mp3


#  2. Rename directory: if RENAME has some directory information, then then the
#     directory above the files will also be renamed.  if RENAME contains more
#     than one directory, it will create the extra ones

# Again, simple example:
#RENAME = %aa - %at/%tn - %tt
# /mp3s/New Downloads/Bad Religion - Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/Ska/VA - Ska Sucks/01 - He Wants Me Back.mp3

# Show year, if it's defined
#RENAME = %aa - {%ay - }%at/%tn - %tt
# /mp3s/New Downloads/Bad Religion - 1983 - Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/Ska/VA - Ska Sucks/01 - He Wants Me Back.mp3

# More than one directory:
#RENAME = %aa/%at/%tn - %tt
# /mp3s/New Downloads/Bad Religion/Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/Ska/VA/Ska Sucks/01 - He Wants Me Back.mp3

# Combination with more than 1 directory, optional year, optional track artist
#RENAME = %aa/{%ay - }%at/%tn - {%tb - }%tt
# /mp3s/New Downloads/Bad Religion/1983 - Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/Ska/VA/Ska Sucks/01 - Dance Hall Crashers - He Wants Me Back.mp3


#  3. Absolute directory: if RENAME is an absolute directory, then the
#     directory is moved to the proper location

# Again, simple example:
#RENAME = /mp3s/%aa - %at/%tn - %tt
# /mp3s/Bad Religion - Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/VA - Ska Sucks/01 - He Wants Me Back.mp3

# All of the above exactly, just add a /something to the front of them...

# My personal format:
#RENAME = /mp3s/%ag/%aa - {%ay - }%at/%tn - {%tb - }%tt
# /mp3s/Punk/Bad Religion - 1983 - Into the Unknown/01 - Its Only Over When.mp3
# /mp3s/Ska/VA - Ska Sucks/01 - Dance Hall Crashers - He Wants Me Back.mp3
~;
	close(WRITE);
}
