# AlbumReview::Plugin.pm by mh 2005
#
# This code is derived from code with the following copyright message:
#
# SliMP3 Server Copyright (C) 2001 Sean Adams, Slim Devices Inc.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.
#
# Changelog
# 1.36 - fix an issue with looping when returning from the screensaver
# 1.35 - add fix for Ewe's bad URL encoding...
# 1.34 - add pure.html 
# 1.33 - replace & in urls with &amp;
# 1.32 - fix a crasher when a http client is used
# 1.31 - make album/artist search case insensitive
# 1.30 - add AMG rating to the web page
# 1.22 - make better use of the Template Toolkit
# 1.21 - added option to remove stuff in brackets from album name
# 1.2  - recover from ip blocking and error messages (timeout) by allmusic 
# 1.1  - added publishing year
#      - improve error messages
#      - improved filter for "cd x of y" & co.
# 1.0  - first release

package Plugins::AlbumReview::Plugin;

use strict;

use vars qw($VERSION);
$VERSION = substr(q$Revision: 1.36 $,10);

use File::Spec;
use FindBin qw($Bin);
use lib(File::Spec->catdir($Bin, 'Plugins','MHCPAN'));
use HTML::FormatText;
use Slim::Utils::Strings qw(string);

my $baseURL    = 'http://www.allmusic.com';
my $searchURL  = "$baseURL/cg/amg.dll";
my $albumRatingURL = "$baseURL/i/pages/site/icons/mini-amg.gif";
my $albumPickURL = "$baseURL/i/pages/site/icons/check.gif";

my ($cacheFolder, %status);

sub initPlugin {
	$cacheFolder = initCacheFolder();

	initSetting('plugin_albumreview_remove_brackets', 0);
	initSetting('plugin_albumreview_refresh', 0);
	initSetting('plugin_albumreview_cache', 999999);	
}

sub initSetting {
	my $setting = shift;
	my $default = shift;
	
	if (!Slim::Utils::Prefs::isDefined($setting)) {
		Slim::Utils::Prefs::set($setting, $default);
	}
}

sub getDisplayName {
	return 'PLUGIN_ALBUMREVIEW';
}

sub setMode {
	my ($client, $method) = @_;
	$client->lines( \&lines );

	# Please wait...
	$client->update();

	my $review = getCachedReview($client) if ($status{$client}{status} ne '-1');

	if ((not $review) && (not defined $status{$client}{status}) && ($method ne 'pop')) {
		Slim::Buttons::Block::block($client, $client->string('PLUGIN_ALBUMREVIEW_COLLECTINGINFO'));
		loadReview($client);
	}
	elsif (not $review && $status{$client}{status} ne '2') {
		Slim::Display::Animation::bumpRight($client);
		Slim::Buttons::Common::popModeRight($client);
	}
	
	$client->update();
}

sub lines {
	my $client = shift;

	my $line1 = '';
	my $line2 = '';
	$status{$client}{line} ||= 0;
	
	if ($status{$client}{status} eq "0") {
		$line1 = $client->string('PLUGIN_ALBUMREVIEW_COLLECTINGINFO');
	} 
	elsif ($status{$client}{status} eq "-1") {
		$line1 = $client->string('PLUGIN_ALBUMREVIEW');
		$line2 = $client->string('PLUGIN_ALBUMREVIEW_NOTHINGFOUND');
	} 
	elsif ($status{$client}{status} eq "2") {
		$line1 = $client->string('PLUGIN_ALBUMREVIEW');
		$line2 = $client->string('PLUGIN_ALBUMREVIEW_NOALBUM');
	} 
	else {
		$line1 = $client->string('PLUGIN_ALBUMREVIEW') . ' - ' . $status{$client}{album};
		$line2 = $status{$client}{playerReview}[$status{$client}{line}];
	}

	return ($line1, $line2);
}

sub getFunctions {
	my %functions = (
		'up' => sub  {
			my $client = shift;
			$status{$client}{line} = Slim::Buttons::Common::scroll($client, -1, $#{ $status{$client}{playerReview} } + 1, $status{$client}{line} || 0);
			$client->update();
		},
	
		'down' => sub  {
			my $client = shift;
			$status{$client}{line} = Slim::Buttons::Common::scroll($client, 1, $#{ $status{$client}{playerReview} } + 1, $status{$client}{line} || 0);
			$client->update();
		},
		
		'left' => sub  {
			my $client = shift;
			Slim::Buttons::Common::popModeRight($client);
		},
	
		'right' => sub  {
			my $client = shift;
			Slim::Display::Animation::bumpRight($client);
		}
	);

	return \%functions;
}

sub webPages {
	my %pages = ("index\.htm" => \&handleWebIndex, "pure\.html" => \&handleWebIndex);
	return (\%pages, "index.html");
}

sub handleWebIndex {
	my ($client, $params, $callback, $httpClient, $response) = @_;

	# some browsers (Ewe...) do too much encoding...
	if ($params->{'url_query'} =~ /player%3D/) {
		$params->{'url_query'} = Slim::Web::HTTP::unescape($params->{'url_query'});

		foreach my $param (split /\&/, $params->{'url_query'}) {
			if ($param =~ /([^=]+)=(.*)/) {
				my $name  = Slim::Web::HTTP::unescape($1, 1);
				my $value = Slim::Web::HTTP::unescape($2, 1);
				$params->{$name} = $value;
			}
		}

		$client = Slim::Player::Client::getClient($params->{'player'}) if $params->{'player'};
		$params->{'myClientState'} = $client;
	}

	if (!$client) {
		$params->{'artist'} = '42';
		$params->{'album'} = string('NO_PLAYER_FOUND');
		$params->{'albumreview'} = string('NO_PLAYER_DETAILS');
	} 
	elsif ($status{$client}{status} eq '99') {
		$params->{'msg'} = string('PLUGIN_ALBUMREVIEW_NOTREADY') . " ($status{$client}{status})";
	}
	elsif ((not getCachedReview($client)) && (not defined $status{$client}{status})) {
		loadReview($client, $params, $callback, $httpClient, $response);
		return undef;
	}
	elsif (getCachedReview($client)) {
		$params->{'artist'}       = $status{$client}{status};
		$params->{'album'}        = $status{$client}{album};
		$params->{'albumreview'}  = $status{$client}{htmlReview};
		$params->{'albumimages'}  = $status{$client}{images} if (not $status{$client}{notFound});
		$params->{'albumtracks'}  = $status{$client}{trackList};
		$params->{'albumyear'}    = $status{$client}{year};
		$params->{'albumrating'}  = $status{$client}{rating};

		if (Slim::Utils::Prefs::get('plugin_albumreview_refresh')) {
			$params->{'refresh'} = Slim::Utils::Prefs::get('plugin_albumreview_refresh') * 1000;
		}
	} 
	elsif ($status{$client}{status} eq '-1') {
		$params->{'msg'} = string('PLUGIN_ALBUMREVIEW_NOTREADY') . " ($status{$client}{status})";
		$params->{'refresh'} = 5000;
	}
	else {
		$params->{'artist'} = string('PLUGIN_ALBUMREVIEW_NOTHINGFOUND') . "($status{$client}{status})";
	}

	return Slim::Web::HTTP::filltemplatefile($params->{'path'}, $params);
}

sub loadReview {
	my ($client, $params, $callback, $httpClient, $response) = @_;
	$status{$client}{status} = '-1';
	my $album = getAlbumFromSongURL($client);

	my $http = Slim::Networking::SimpleAsyncHTTP->new(\&gotAlbumInfo, \&gotErrorViaHTTP, 
														{
															client => $client, 
															params => $params, 
															callback => $callback, 
															httpClient => $httpClient, 
															response => $response, 
															searchAlbum => $album
														});
	my $searchAlbum = Slim::Utils::Text::matchCase($album->{album});
	$searchAlbum = Slim::Web::HTTP::escape($searchAlbum);
	$http->post($searchURL, 'Content-Type' => 'application/x-www-form-urlencoded', "SQL=$searchAlbum&OPT1=2&submit=Go&P=amg");
}

sub gotAlbumInfo {
	my $http = shift;
	my $params = $http->params();

   	initStatus($params->{client}, '-1');
	my $cachedFile = getCachedAlbumFilename($params->{searchAlbum});

    my $html = $http->content();
    if (not $html) {
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: timeout problem?\n");
    	initStatus($params->{client}, '2', [string('PLUGIN_ALBUMREVIEW_NOTHINGFOUND')]);
    }
	
	# direct hit?
	elsif (($html =~ /Overview/si) && ($html =~ /Review/si) && ($html =~ /Credits/si)) {
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: Match!\n");
		storeCachedFile($cachedFile, $html);
		initStatus($params->{client}, '0');
	} 
	
	# try artist & album combo
	else {
		my %albumsFound;
		while ($html =~ /<tr class="visible".*?>(.*?)<\/tr>/sig) {
			my @albumInfo = split(/<td.*?>(.*?)<\/td.*?>/si, $1);
			#$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: $albumInfo[9] ($albumInfo[5], $albumInfo[3])\n");
	
			# get the album name and the url to its review
			$albumInfo[9] =~ /<a href="(\/cg\/amg.dll\?p=amg\&(amp\;)?sql=\d{1,2}:\w+)">(.*?)<\/a>/si;

			my ($url, $album) = ($1, $3);
			$url =~ s/\&amp\;/\&/g;
			
			$album = HTML::Entities::decode_entities($album);
			
			# they sometimes have double spaces...
			$album =~ s/\s+/ /sg;
			$album = lc($album);
			$albumInfo[5] =~ s/\s+/ /sg;
			$albumInfo[5] = lc(HTML::Entities::decode_entities($albumInfo[5]));

			my %albumInfo = ( 'url' => $url, 'year' => $albumInfo[3] );
			$albumsFound{$albumInfo[5]}->{$album} = \%albumInfo;
		}

		# let's see if we've got our artist in the current list
		my $albumTitle = escapeRegExpChars($params->{searchAlbum}->{album});
		
		my $searchAlbumArtist = lc($params->{searchAlbum}->{artist});
		my $searchAlbum = lc($params->{searchAlbum}->{album});

		if ($albumsFound{$searchAlbumArtist}->{$searchAlbum}) {
			# got it! Get that page
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: direct hit! $params->{searchAlbum}->{album}: $params->{searchAlbum}->{artist}, $albumsFound{$searchAlbumArtist}{$params->{searchAlbum}->{album}}->{year}\n");
			my $http = Slim::Networking::SimpleAsyncHTTP->new(\&gotMoreAlbumInfo, \&gotErrorViaHTTP, $params);
			$http->get($baseURL . $albumsFound{$searchAlbumArtist}{$searchAlbum}->{url});

			# don't create the page yet!
			return;
		}
		elsif (my @albums = grep(/$albumTitle/i, keys %{$albumsFound{$searchAlbumArtist}})) {
			# we got the artist, see if we can find a similar album
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: artist found, but not album. $params->{searchAlbum}->{album}: $params->{searchAlbum}->{artist}\n");
			
			# are we lucky? give the first a try...
			my $http = Slim::Networking::SimpleAsyncHTTP->new(\&gotMoreAlbumInfo, \&gotErrorViaHTTP, $params);
			my $url = $baseURL . $albumsFound{$searchAlbumArtist}{$albums[0]}->{url};
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: $url\n");
			$http->get($url);
			# don't create the page yet!
			return;			
		}
		else {
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: nothing found?\n");
			# got nothing at all... show list
			storeCachedFile($cachedFile, "<!-- NOT FOUND --><ul>$html</ul>\n");
			initStatus($params->{client}, '0');
		}
	}
	createAsyncWebPage($params);
	Slim::Buttons::Block::unblock($params->{'client'});
}

sub gotMoreAlbumInfo {
	my $http = shift;
	my $html = $http->content();
	my $params = $http->params();
   	storeCachedFile(getCachedAlbumFilename($params->{searchAlbum}), $html);
   	initStatus($params->{client}, '0');
	createAsyncWebPage($params);
	Slim::Buttons::Block::unblock($params->{'client'});
}

sub gotErrorViaHTTP {
	my $http = shift;
	my $params = $http->params();
	
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: got error\n");
	initStatus($params->{client}, '99', [string('PLUGIN_ALBUMREVIEW_NOTREADY')]);
	
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: initialised error\n");
	use Data::Dumper;
	print Data::Dumper::Dumper($params);
	createAsyncWebPage($params);
	
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: created error page\n");
	Slim::Buttons::Block::unblock($params->{'client'});
}

sub createAsyncWebPage {
	my $params = shift;
	# create webpage if we were called by the web interface

	if ($params->{httpClient}) {
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: httpClient available\n");
		my $output = handleWebIndex($params->{client}, $params->{params}, $params->{callback}, $params->{httpClient}, $params->{response});
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (output): $output\n");
		my $current_player;
		if (defined($params->{client})) {
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: client available\n");
			$current_player = $params->{client}->id();
		}
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (current_player): $current_player\n");
		
		$params->{callback}->($current_player, $params->{params}, $output, $params->{httpClient}, $params->{response});
	}
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: page created?\n");
}


sub getCachedReview {
	my $client = shift;

	# check whether there's actually a song!
	my $album = getAlbumFromSongURL($client);
	if ($album && defined $album->{album} && defined $album->{artist}) {
		# only read review if artist and album changed
		if ((defined $status{$client}{status}) && ($album->{artist} eq $status{$client}{status}) 
				&& (defined $status{$client}{album}) && ($album->{album} eq $status{$client}{album})) {
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (cache): $album->{album}/$album->{artist} was in memory cache\n");	
		}
		else {
			# initialize status hash
			initStatus($client, '-1');

			# Try and use the locally cached copy first
			my $cachedFile = getCachedAlbumFilename($album);
			my $cacheAge = Slim::Utils::Prefs::get('plugin_albumreview_cache') || 30;

			# we're fine! cached file found
			if (-e $cachedFile && -M $cachedFile < $cacheAge) {
				$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (cache): $album->{album}/$album->{artist} was in disk cache\n");
				local $/ = undef;
		
				open CACHE, $cachedFile;
				my $albumHTML = <CACHE>;
				close CACHE;
				
				# cache values for web interface
				$status{$client}{status} = $album->{artist};
				$status{$client}{album} = $album->{album};

				if ((not $albumHTML) || $albumHTML =~ /(traffic monitoring.*?allmusic.*?rate and speed|Error 2 Timeout)/i) {
					$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: Oops... Timeout? Or have we been blocked by allmusic.com?..\n");
					
					$status{$client}{htmlReview} = string('PLUGIN_ALBUMREVIEW_NOTREADY');
					$status{$client}{playerReview} = [$client->string('PLUGIN_ALBUMREVIEW_NOREVIEW')];

					# remove the file as it only contents an error message
					unlink $cachedFile;
				}
				elsif ($albumHTML =~ /<!-- NOT FOUND -->/i) {
					$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: Display albumlist\n");
					
					$status{$client}{htmlReview} = '';

					while ($albumHTML =~ /<tr class="visible".*?>(.*?)<\/tr>/sig) {
						my $albumEntry = $1;
						my @albumInfo = split(/<td.*?>(.*?)<\/td.*?>/si, $albumEntry);
				
						# get the album name and the url to its review
						$albumInfo[9] =~ /<a href="(\/cg\/amg.dll\?p=amg\&(amp\;)?sql=\d{1,2}:\w+)">(.*?)<\/a>/si;

						my ($url, $albumTitle) = ($1, $3);
						$url =~ s/\&amp\;/\&/g;

						#$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: $albumInfo[9] ($albumInfo[5], $albumInfo[3])\n");
						$status{$client}{htmlReview} .= '<li>' . fixBaseURL($albumInfo[9]) . " - ($albumInfo[5], $albumInfo[3])</li>\n";
					}
					
					# remove other artists, if our artist is in list
					my $albumTitle = escapeRegExpChars($album->{album});
					my $artist = escapeRegExpChars($album->{artist});
					if (($status{$client}{htmlReview} =~ /$artist/i) || ($status{$client}{htmlReview} =~ /$albumTitle/i)) {
						my $htmlReview = '';
						while ($status{$client}{htmlReview} =~ /(<li>.*?<\/li>)/sig) {
							my $line = $1;
							$htmlReview .= $line if (($line =~ /$artist/i) || ($line =~ /$albumTitle/i));
						}
						$status{$client}{htmlReview} = $htmlReview;
					}
					
					$status{$client}{htmlReview} = "<ul>$status{$client}{htmlReview}</ul>";
					$status{$client}{playerReview} = [$client->string('PLUGIN_ALBUMREVIEW_NOREVIEW')];
				}
				else {
					$status{$client}{htmlReview} = getAlbumReview($albumHTML);
					$status{$client}{trackList} = getTrackList($albumHTML);
					$status{$client}{year} = getAlbumYear($albumHTML);
					$status{$client}{rating} = getAlbumRating($albumHTML);
					
					if ($status{$client}{htmlReview} eq $client->string('PLUGIN_ALBUMREVIEW_NOREVIEW')) {
						$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: Review found, but empty...\n");
						$status{$client}{playerReview} = [$client->string('PLUGIN_ALBUMREVIEW_NOREVIEW')];
						$status{$client}{notFound} = 1;
					}
					# display a list of found albums
					else {
						$status{$client}{images} = $album->{coverart};
						my $formattedReview = $status{$client}{htmlReview};
	
						# wrap for player's display
						my $reviewFormatter = HTML::FormatText->new(leftmargin => 0, rightmargin => (charsPerLine($client, $formattedReview)-1));
						$formattedReview = $reviewFormatter->format_string($formattedReview);
						$formattedReview =~ s/\n+/\n/g;
		
						@{$status{$client}{playerReview}} = split(/\n/, $formattedReview);
		
						if (@{$status{$client}{playerReview}} == 0) {
							push(@{$status{$client}{playerReview}}, $client->string('PLUGIN_ALBUMREVIEW_NOTEXTFOUND'));
						} 
						else {
							my $rating = getAlbumRating($albumHTML, $client);
							if ($rating) {
								unshift(@{$status{$client}{playerReview}}, $rating);
							}
							push(@{$status{$client}{playerReview}}, '------------------------------------------------------------------------');
						}
					}
				}
			}
			# no cached content - we'll have to go the hard way...
			else {
				$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (cache): need to look online for $album->{artist}/$album->{album}...\n");
				initStatus($client, undef);
				return 0;
			} 
		}
	} 
	else {
		$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (cache): no album found\n");
		initStatus($client, 2, [string('PLUGIN_ALBUMREVIEW_NOALBUM')]);
		$status{$client}{album} = string('PLUGIN_ALBUMREVIEW_NOALBUM');
		return 0;
	}
	return 1;
}

# calculate the average character width for the current display based on our text...
sub charsPerLine {
	my $client = shift;
	my $text = shift;
	
	my $width = 40;

	# wrap the text to the device's width
	if (UNIVERSAL::can($client, 'displayWidth') && $client->displayWidth() > 40) {
		$text =~ s/\n//gs;
		$width = int($client->displayWidth / ($client->measureText($text, 2) / length($text))) - 1;
	}
	return $width;
}

sub storeCachedFile {
	my $cachedFile    = shift;
	my $cachedContent = shift;
	
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview: writing '$cachedFile' to cache\n");
	open(CACHE, ">$cachedFile") || Slim::Utils::Misc::msg("Could not open $cachedFile for writing: $!\n");
	print CACHE $cachedContent;
	close(CACHE);
}

sub getCachedAlbumFilename {
	my $album = shift;
	my $artistName = Slim::Web::HTTP::escape($album->{artist});
	my $albumName = Slim::Web::HTTP::escape($album->{album});
	return "$cacheFolder/$artistName-$albumName.html";
}

sub getAlbumReview {
	my $albumHTML = shift;
	if ($albumHTML =~ /<div id="bio">(<table.*?<\/table)/si) {
		# remove all tags to get text only - we'll do the layout in _our_ html template
		my @review = split(/<td.*?>(.*?)<\/td.*?>/si, $1);
		$review[5] =~ s/^<p.*?>(.*)<\/p>$/$1/si;
		return fixBaseURL("$review[5]\n<br><br>($review[1] $review[3])\n");
	}

	return string('PLUGIN_ALBUMREVIEW_NOREVIEW');
}

sub getTrackList {
	my $albumHTML = shift;
	my %trackList;

	while ($albumHTML =~ /<tr class="visible"[^>]*?>(.*?)<\/tr>/sig) {
		my $track = $1;
		while ($track =~ /<td.*?>(.*?)<\/td>\s*<td.*?<\/td>\s*<td.*?>(\d*?)<\/td.*?>\s*<td.*?<\/td>\s*<td.*?>(.*?)<\/td.*?>\s*<td.*?>(.*?)<\/td.*?>\s*<td.*?>(.*?)<\/td/sig) {
			my ($hasReview, $trackNo, $title, $composer, $time) = ($1, $2, $3, $4, $5);
			
			# if there's no track review available, remove link
			if (($hasReview !~ /<a href/i) && ($title =~ /<a href.*?>(.*?)<\/a/i)) {
				$title = $1;
			}
	
			my %trackInfo = ( 
				'title' => fixBaseURL($title),
				'composer' => $composer,
				'time' => $time
			);
			# pad the tracknumbers to get them correctly sorted in TT
			$trackList{sprintf('%3.d', $trackNo)} = \%trackInfo;
		}
	}
	return \%trackList;
}

sub getAlbumYear {
	my $albumHTML = shift;
	my ($year, @years);

	while ($albumHTML =~ /<tr class="visible"[^>]*?>(.*?)<\/tr>/sig) {
		if ($1 =~ /^\s*?<td.*?>(\d{4})\s*?<\/td>\s*<td.*?>([^<]*?)<\/td>\s*<td.*?>([^<]*?)<\/td>\s*<td.*?>([^<]*?)<\/td>\s*$/sig) {
			push @years, $1;
		}
	}
	
	if (@years) {
		@years = sort @years;
		$year = shift @years;
	}
	
	return $year;
}

sub getAlbumRating {
	my $albumHTML = shift;
	my $client = shift;
	
	my $rating;

	if ($client) {
		$albumHTML =~ /alt="([\d\.]+) star/i;
		$rating = $1;

		if ($rating) {
			$rating = sprintf($client->string('PLUGIN_ALBUMREVIEW_RATING') . " $rating " . $client->string('PLUGIN_ALBUMREVIEW_STARS'), $rating)
		}

		if ($albumHTML =~ /amg-pick-icon\.gif/i) {
			$rating .= ($rating ? ', ' : '') . $client->string('PLUGIN_ALBUMREVIEW_PICK');
		}
	}
	else {
		$albumHTML =~ /"([^"]*?\/st_r\d+\.gif)"/i;
		$rating = $1;

		if ($rating) {
			$rating = ' <img src="' . $baseURL . $rating . '" alt="Rating">'
		}
	
		if ($albumHTML =~ /amg-pick-icon\.gif/i) {
			$rating = " <img src=\"$albumRatingURL\" alt=\"AMG\"><img src=\"$albumPickURL\" alt=\"Album Pick\">" . $rating;
		}
		
	}
	$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (rating) $rating\n");

	return $rating;
}

sub initStatus {
	my $client = shift;
	$status{$client}{status} = shift;
	$status{$client}{album} = shift;
	$status{$client}{playerReview} = shift;
	$status{$client}{playerReview} ||= ();
	$status{$client}{line} = shift;
	$status{$client}{htmlReview} = shift;
	$status{$client}{images} = shift;
	$status{$client}{links} = shift;
	$status{$client}{notFound} = shift;
	$status{$client}{trackList} = shift;
	$status{$client}{year} = shift;
	$status{$client}{rating} = shift;
}

sub getAlbumFromSongURL {
	my $client = shift;
	my %album;

	if (my $url = Slim::Player::Playlist::song($client)) {
		my $ds    = Slim::Music::Info::getCurrentDataStore();
		my $track = $ds->objectForUrl($url);
	
		my $artist = $track->artist();
		my $album = $track->album();
		if ($album && $artist) {
			my $coverart;
			if ($track->coverArt('thumb')) {
				$coverart = $track->id();
			}
			
			# optionally remove everything between () or []... But don't for PG's eponymous first four albums :-)
			$album =~ s/[\(\[].*?[\)\]]//g if (Slim::Utils::Prefs::get('plugin_albumreview_remove_brackets') && $album !~ /Peter Gabriel \[[1-4]\]/i);
	
			# remove stuff like "CD02", "1 of 2"
			$album =~ s/\b(disc \d+ of \d+)\b//ig;
			$album =~ s/\d+\/\d+//ig;
			$album =~ s/\b(cd\s*\d+|\d+ of \d+|disc \d+)\b//ig;
			# remove trailing non-word characters
			$album =~ s/[\s\W]{2,}$//;
			$album =~ s/\s*$//;
	
			$::d_plugins && Slim::Utils::Misc::msg("AlbumReview (get artist's name): '$artist', album: '$album'\n");
			if ($artist && $album) {
				%album = ('artist' => $artist, 'album' => $album, 'coverart' => $coverart);
				return \%album;
			}
		}
	}
	return \%album;
}

sub escapeRegExpChars {
	my $value = shift;
	# escape special characters
	$value =~ s/([\\\(\)\[\]\*\?\.\$])/\\$1/g;
	return $value;
}

sub fixBaseURL {
	my $html = shift;

	$html =~ s/href="(.*?allmovie.com.*?)"/href="$1" target="_blank"/sig;
	$html =~ s/href="\/(.*?)"/href="$baseURL\/$1" target="_blank"/sig;

	return $html;
}

sub setupGroup {

	my %setupGroup = (
		PrefOrder => ['plugin_albumreview_refresh', 'plugin_albumreview_remove_brackets', 'plugin_albumreview_cache', 'plugin_albumreview_cache_reset'],
		GroupHead => string('PLUGIN_ALBUMREVIEW'),
		GroupLine => 1,
		GroupSub => 1,
		Suppress_PrefSub => 1,
		Suppress_PrefLine => 1
	);

	my %setupPrefs = (
		'plugin_albumreview_refresh' => {
			'validate' => \&Slim::Web::Setup::validateInt,
			'validateArgs' => [0,undef,1]
		},

		'plugin_albumreview_cache' => {
			'validate' => \&Slim::Web::Setup::validateInt,
			'validateArgs' => [1,undef,1],
			'onChange' => sub {
				my ($client,$changeref,$paramref,$pageref) = @_;
				Slim::Utils::Prefs::set('plugin_albumreview_cache', $changeref->{'plugin_albumreview_cache'}->{'new'});
				initCacheFolder();
			}
		},
		
		'plugin_albumreview_cache_reset' => {
			'validate' => \&Slim::Web::Setup::validateAcceptAll,
			'onChange' => sub {
				# purge the cache folder
				initCacheFolder(1);
			},
			'inputTemplate' => 'setup_input_submit.html',
			'dontSet' => 1,
			'ChangeButton' => string('SETUP_PLUGIN_ALBUMREVIEW_CACHE_RESET'),
#			'ChangeButton' => Slim::Utils::Strings::string('SETUP_PLUGIN_ALBUMREVIEW_CACHE_RESET'),
			'changeMsg' => ''
		},
		
		'plugin_albumreview_remove_brackets' => {
			validate => \&Slim::Web::Setup::validateTrueFalse,
			options  => {
				1 => string('ON'),
				0 => string('OFF')
			},
			'PrefChoose' => string('SETUP_PLUGIN_ALBUMREVIEW_REMOVE_BRACKETS')
		}
	);

	return (\%setupGroup, \%setupPrefs);
}

sub initCacheFolder {
	my $purge = shift;
	my $cacheFolder = Slim::Utils::Prefs::get('cachedir');

	mkdir($cacheFolder) unless (-d $cacheFolder);

	$cacheFolder .= "/.slimserver-albumreview-cache";

	mkdir($cacheFolder) unless (-d $cacheFolder);
	
	my $cacheAge = Slim::Utils::Prefs::get('plugin_albumreview_cache') || 30;
	
	# purge the cache
	if (opendir(DIR, $cacheFolder)) {
		while (defined(my $cachedFile = readdir(DIR))) {
	
			$cachedFile = "$cacheFolder/$cachedFile";
			if (-M $cachedFile > $cacheAge || $purge) {
				unlink $cachedFile;
			}
		}
	}
	else {
		$::d_plugins && Slim::Utils::Misc::msg("can't opendir $cacheFolder: $!\n");
	}

	closedir(DIR);
	
	# set timer to purge cache once a day
	Slim::Utils::Timers::setTimer(0, Time::HiRes::time() + 60*60*24, \&initCacheFolder);
	
	return $cacheFolder;
}


sub strings {
	return q^
PLUGIN_ALBUMREVIEW
	DE	Album Review
	EN	Album Review
	ES	Reseña de Discos

PLUGIN_ALBUMREVIEW_RELOAD
	DE	Aktualisieren
	EN	Refresh
	ES	Refrescar

PLUGIN_ALBUMREVIEW_COLLECTINGINFO
	DE	Suche Informationen...
	EN	Gathering information...
	ES	Recopilando información...

PLUGIN_ALBUMREVIEW_NOTHINGFOUND
	DE	Konnte keine Informationen finden!
	EN	Couldn't find any information!
	ES	No se encontró información

PLUGIN_ALBUMREVIEW_NOREVIEW
	DE	Es wurde keine Album Review gefunden!
	EN	No Review has been found.
	ES	No se encontró reseña.

PLUGIN_ALBUMREVIEW_RATING
	DE	Bewertung:
	EN	Rating:

PLUGIN_ALBUMREVIEW_STARS
	DE	Punkte
	EN	Stars

PLUGIN_ALBUMREVIEW_PICK
	EN	AMG Pick

PLUGIN_ALBUMREVIEW_NOALBUM
	DE	Es konnte kein Albumname gefunden werden!
	EN	No album name has been found.
	ES	No se encontró un disco con ese nombre.

PLUGIN_ALBUMREVIEW_NOTREADY
	DE	Die Daten konnten nicht abgerufen werden. Ev. besteht ein Netzwerkproblem. Bitte versuchen Sie es später noch einmal!
	EN	Information could not be collected. There might be a network problem. Please try again later!
	ES	No se pudo obtener información. Puede haber algún problema de red. Intente nuevamente más tarde!

SETUP_PLUGIN_ALBUMREVIEW_REFRESH
	DE	Aktualisierungs Intervall
	EN	Refresh interval
	ES	Intervalo de refresco

SETUP_PLUGIN_ALBUMREVIEW_REFRESH_CHOOSE
	DE	Aktualisierungs-Intervall in Sekunden:
	EN	Refresh interval in seconds:
	ES	Intervalo de refresco, en segundos:

SETUP_PLUGIN_ALBUMREVIEW_REFRESH_DESC
	DE	Sie können die Reviews im Web-Interface automatisch aktualisieren lassen, um sie mit der aktuell gespielten Musik zu synchronisieren. Geben Sie ein Zeitinterval an, in welchem sie aktualisiert werden soll. "0" deaktiviert die automatische Aktualisierung. (Vorgabe: 0)
	EN	You can have the review in your web interface update automatically to stay in sync with the currently playing song. Define the refresh interval in seconds or 0 if you don't want it to be updated automatically. (Default: 0)
	ES	Se puede hacer que la reseña que se muestra en la interface web se actualice automáticamente para estar sincronizada con la canción que se está escuchando actualmente. Definir el intervalo de refresco en segundos, o 0 si no se quiere la actualización automática. (Por defecto: 0)

SETUP_PLUGIN_ALBUMREVIEW_CACHE
	DE	Review Cache
	EN	Review cache
	ES	Cache de reseñas

SETUP_PLUGIN_ALBUMREVIEW_CACHE_CHOOSE
	DE	Anzahl Tage, die die Reviews lokal gespeichert bleiben sollen:
	EN	Days to keep local copies of reviews:
	ES	Cantidad de días en que se conservarán las copias locales de las reseñas:

SETUP_PLUGIN_ALBUMREVIEW_CACHE_DESC
	DE	Die Review-Daten werden im SlimServer Cache-Verzeichnis zwischengespeichert, um den Zugriff zu beschleunigen. Definieren Sie hier, wieviele Tage die Daten lokal gespeichert werden sollen.
	EN	The album reviews are cached locally in SlimServer's cache folder to speed up the display. Define for how many days you want to keep the local copies. (Default: 30)
	ES	Las reseñas de discos se guardan en un cache local en SlimServer para acelerar su muestra. Definir cuantos días se desea conservar las copias locales. (Por defecto: 30)

SETUP_PLUGIN_ALBUMREVIEW_CACHE_RESET
	DE	Cache leeren
	EN	Reset disk cache
	ES	Vaciar cache en disco
	
SETUP_PLUGIN_ALBUMREVIEW_REMOVE_BRACKETS
	DE	Geklammerte Inhalte aus Albumtitel entfernen
	EN	Remove values in brackets/parenthesis from album title
	ES	Eliminar los valores entre paréntesis/corchetes del título del disco

SETUP_PLUGIN_ALBUMREVIEW_REMOVE_BRACKETS_DESC
	DE	Enthält ein Albumtitel Text in in Klammern, so kann dieser optional entfernt werden. Dies sind z.B. häufig Angaben zur Disknummer "(Disc 1 of 2)" oder zusätzliche Bemerkungen wie "[Bonus Track]". Es werden normale () und eckige [] Klammern berücksichtigt.
	EN	Sometimes album titles contain values in brackets or parenthesis which can hinder the album lookup. These can be information to disc sets like "(1/2)" or additional information like "[Bonus Track]". If you turn on this option, values between parenthesis () and brackets [] are removed from the album title.
	ES	A veces, los títulos de los discos contienen valores entre corchetes o paréntesis, los cuales pueden afectar la búsqueda. Estos valores pueden ser información sobre discos múltiples como "(1/2)" o información adicional como "[Bonus Track]". Si se habilita esta opción, los valores entre paréntesis () y corchetes [] serán removidos del título del album.
^;
}

1;
