#!/usr/bin/perl
use strict;
use File::Find;
use File::Basename;
use File::Spec;
use Getopt::Long;
use Date::Manip;
use Math::Round;
use Image::ExifTool;
use Encode;

my ($version, $program, $date);
my ($longside,$thumblongside,$title, $subtitle, $cssfile, $photographer, $participants, $keywords, $description, $cssfile, $ncols, $nores);
my ($dir,$file,$thumbfile,$copyright,$writecaption,$writedate,$output,$author,$period,$imageCounter,$imgCounter);
my ($DateTimeOriginal,$captionAbstract,$imgWidth,$imgHeight,$factor,$width,$height,$status,$ssifooter,$ggmaps);
my (@imgTitles,@imgFilenames,@imgWidths,@imgHeights);
my $CoordFormat = Encode::decode_utf8("%d° %d' %.2f\"");

$version = "0.2";
$date = "2006-11-24";
$program = $0;
$program =~ s/(\.\/)//;
my $usage = "$program (version $version, $date)\nUsage: $program --dir myphotos/dir/ --ncols 4 --longside 900 --thumblongside 200 --output \"index.shtml\" [--cssfile \"/path/to/your/css/file.css\"] [--title \"Your photogallery title\"] [--subtitle \"Your photogallery subtitle\"] [--photographer \"the photographer(s) name(s)\"] [--participants \"the participant(s) name(s)\"] [--writecaption] [--writedate] [--copyright \"your copyright info\"] [--keywords \"waterfall,mountains,lakes,hotel,cablecar\"] [--description \"your HTML description tag content\"] [--ssifooter \"/your/path/to_ssi/file.html\"] [--ggmaps 0.005] [--nores]\n";

#get parameters
GetOptions("dir=s" => \$dir,
		"ncols=i" => \$ncols,
		"longside=i" => \$longside,
		"thumblongside=i" => \$thumblongside,
		"output=s" => \$output,
		"title=s" => \$title,
		"subtitle=s" => \$subtitle,
		"cssfile=s" => \$cssfile,
		"photographer=s" => \$photographer,
		"period=s" => \$period,
		"participants=s" => \$participants,
		"keywords=s" => \$keywords,
		"author=s" => \$author,
		"copyright=s" => \$copyright,
		"description=s" => \$description,
		"writecaption" => \$writecaption,
		"writedate" => \$writedate,
		"ssifooter=s" => \$ssifooter,
		"ggmaps" => \$ggmaps,
		"nores" => \$nores
	);

unless ($dir) {
	die "$usage you have to specify a directory containing the images (--dir)!\n";
}

unless ($ncols) {
	die "$usage you have to specify the number of columns (--ncols)!\n";
}

unless ($longside) {
	die "$usage you have to specify the long side of a web-photo in pixel (--longside)!\n";
}

unless ($thumblongside) {
	die "$usage you have to specify the long side of a thumbnails in pixel (--thumblongside)!\n";
}

unless ($output) {
	die "$usage you have to specify a output filename (--output)!\n";
}

#treat strings to be correctly utf8
if ($dir) {
	$dir = Encode::encode("utf8",$dir);
}
if ($output) {
	$output = Encode::encode("utf8",$output);
}
if ($title) {
	$title = Encode::encode("utf8",$title);
}
if ($subtitle) {
	$subtitle = Encode::encode("utf8",$subtitle);
}
if ($cssfile) {
	$cssfile = Encode::encode("utf8",$cssfile);
}
if ($photographer) {
	$photographer = Encode::encode("utf8",$photographer);
}
if ($period) {
	$period = Encode::encode("utf8",$period);
}
if ($participants) {
	$participants = Encode::encode("utf8",$participants);
}
if ($keywords) {
	$keywords = Encode::encode("utf8",$keywords);
}
if ($author) {
	$author = Encode::encode("utf8",$author);
}
if ($copyright) {
	$copyright = Encode::encode("utf8",$copyright);
}
if ($description) {
	$description = Encode::encode("utf8",$description);
}
if ($ssifooter) {
	$ssifooter = Encode::encode("utf8",$ssifooter);
}

open(INDEXFILE,">",$dir.$output) or die "Can't open file ".$dir.$output." for writing: $!\n";

#print XHTML header
print INDEXFILE qq(<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
);

#include css file
if ($cssfile) {
	print INDEXFILE "<link rel=\"stylesheet\" type=\"text/css\" href=\"".$cssfile."\" />\n";
}

#create title
if ($title) {
	print INDEXFILE "<title>".$title."</title>\n";
}
else {
	print INDEXFILE "<title>Photogallery by photoGallery.pl</title>";
}

#add generator and charset information
print INDEXFILE qq(<meta name="generator" content="photoGaller.pl by Andreas Neumann" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="robots" content="index" />
);

#add author information
if ($author) {
	print INDEXFILE "<meta name=\"author\" content=\"".$author."\" />\n";
}

#add description and keywords
if ($description) {
	print INDEXFILE "<meta name=\"description\" content=\"".$description."\" />\n";
}
if ($keywords) {
	print INDEXFILE "<meta name=\"keywords\" content=\"".$keywords."\" />\n";
}
#add generation date
print INDEXFILE "<meta name=\"date\" content=\"".UnixDate("today","%Y-%m-%e")."\" />";

#close header and start body
print INDEXFILE qq(</head>
<body>
<center>
);

#add title and subtitle
if ($title) {
	print INDEXFILE "<h1>".$title."</h1>\n";
}
if ($subtitle) {
	print INDEXFILE "<h2>".$subtitle."</h2>\n";
}

#add date (period) information
if ($period) {
	print INDEXFILE "<p><b>Pictures taken:</b> ".$period."</p>\n";
}

#add photographer information
if ($photographer) {
	print INDEXFILE "<p><b>Photographer:</b> ".$photographer."</p>\n";
}

#add participant information
if ($participants) {
	print INDEXFILE "<p><b>Participants:</b> ".$participants."</p>\n";
}

#opening table
print INDEXFILE "<table width=\"100%\">\n";

$imageCounter = 0;
#read directory and count image files
opendir(DIR, $dir) or die "can't open directory $dir: $!";
print "\nprocessing directory \"$dir\"\n";

#test if thumbnails directory exists
unless (-d $dir."thumbnails") { #see if directory exists
	mkdir($dir."thumbnails");
}

while (defined($file = readdir(DIR))) {
	$file = File::Spec->rel2abs($dir.$file);
	#first check if it is a jpeg file
	my ($base, $dir, $ext) = fileparse($file,'\..*');
	if ($ext eq ".jpg" || $ext eq ".JPG" || $ext eq ".jpeg" || $ext eq ".JPEG") {
		#first read width and height value
		my $exifTool = new Image::ExifTool;
		$exifTool->Options(Charset => 'UTF8');
		my $imgInfo = $exifTool->ImageInfo($file,"Caption-Abstract","ImageWidth","ImageHeight");
		my @tags = $exifTool->GetRequestedTags();
		my $captionAbstract = Encode::encode("utf8",$exifTool->GetValue($tags[0]));
		my $imgWidth = Encode::encode("utf8",$exifTool->GetValue($tags[1]));
		my $imgHeight = Encode::encode("utf8",$exifTool->GetValue($tags[2]));
		
		push(@imgTitles,$captionAbstract);
		push(@imgFilenames,$base);
		push(@imgWidths,$imgWidth);
		push(@imgHeights,$imgHeight);
		$imageCounter++;
	}
}
closedir(DIR);

print "working on $imageCounter images ...\n\n";

#read directory and count image files
opendir(DIR, $dir) or die "can't open directory $dir: $!";
$imgCounter = 0;

while (defined($file = readdir(DIR))) {
	$thumbfile = File::Spec->rel2abs($dir."thumbnails/".$file);
	$file = File::Spec->rel2abs($dir.$file);
	#first check if it is a jpeg file
	my ($base, $dir, $ext) = fileparse($file,'\..*');
	if ($ext eq ".jpg" || $ext eq ".JPG" || $ext eq ".jpeg" || $ext eq ".JPEG") {
		print "working on image ".($imgCounter + 1)." of a total of $imageCounter images\n";
		#first read width and height value
		my $exifTool = new Image::ExifTool;
		#set a few parameters
		$exifTool->Options(Charset => 'UTF8', CoordFormat => $CoordFormat);
		my $imgInfo = $exifTool->ImageInfo($file,"DateTimeOriginal","Caption-Abstract","ImageWidth","ImageHeight","Country-PrimaryLocationName","Province-State","City","GPSLatitude","GPSLatitudeRef","GPSLongitude","GPSLongitudeRef","GPSAltitude","GPSAltitudeRef","Model","ExposureTime","ApertureValue","FocalLengthIn35mmFormat","ISO","GPSDestLatitude","GPSDestLatitudeRef","GPSDestLongitude","GPSDestLongitudeRef","GPSImageDirection","GPSImageDirectionRef","GPSDestDistance","GPSDestDistanceRef");
		#get individual parameters
		my @tags = $exifTool->GetRequestedTags();
		my $DateTimeOriginal = Encode::encode("utf8",$exifTool->GetValue($tags[0]));
		my $captionAbstract = Encode::encode("utf8",$exifTool->GetValue($tags[1]));
		my $imgWidth = Encode::encode("utf8",$exifTool->GetValue($tags[2]));
		my $imgHeight = Encode::encode("utf8",$exifTool->GetValue($tags[3]));
		my $country = Encode::encode("utf8",$exifTool->GetValue($tags[4]));
		my $province = Encode::encode("utf8",$exifTool->GetValue($tags[5]));
		my $city = Encode::encode("utf8",$exifTool->GetValue($tags[6]));
		my $lat = Encode::encode("utf8",$exifTool->GetValue($tags[7]));
		my $latRef = Encode::encode("utf8",$exifTool->GetValue($tags[8]));
		my $lon = Encode::encode("utf8",$exifTool->GetValue($tags[9]));
		my $lonRef = Encode::encode("utf8",$exifTool->GetValue($tags[10]));
		my $alt = Encode::encode("utf8",$exifTool->GetValue($tags[11]));
		my $altRef = Encode::encode("utf8",$exifTool->GetValue($tags[12]));
		my $cameraModel = Encode::encode("utf8",$exifTool->GetValue($tags[13]));
		my $exposure = Encode::encode("utf8",$exifTool->GetValue($tags[14]));
		my $aperture = Encode::encode("utf8",$exifTool->GetValue($tags[15]));
		my $focalLength = Encode::encode("utf8",$exifTool->GetValue($tags[16]));
		my $iso = Encode::encode("utf8",$exifTool->GetValue($tags[17]));
		my $destLat = Encode::encode("utf8",$exifTool->GetValue($tags[18]));
		my $destLatRef = Encode::encode("utf8",$exifTool->GetValue($tags[19]));
		my $destLon = Encode::encode("utf8",$exifTool->GetValue($tags[20]));
		my $destLonRef = Encode::encode("utf8",$exifTool->GetValue($tags[21]));
		my $imageDir = Encode::encode("utf8",$exifTool->GetValue($tags[22]));
		my $imageDirRef = Encode::encode("utf8",$exifTool->GetValue($tags[23]));
		my $imageDist = Encode::encode("utf8",$exifTool->GetValue($tags[24]));
		my $imageDistRef = Encode::encode("utf8",$exifTool->GetValue($tags[25]));

		#creating thumbnail
		if ($imgWidth > $imgHeight) {
			$factor = $imgHeight / $imgWidth;
			$width = $thumblongside;
			$height = nearest(1,($width * $factor));
		}
		else {
			$factor = $imgWidth / $imgHeight;
			$height = $thumblongside;
			$width = nearest(1,($height * $factor));
		}
		unless ($nores) {
			$status = system("convert ".$file." -resize ".$width."x".$height." ".$thumbfile);
			die "could not create thumbnail $thumbfile" if $status == 1;
		}
		
		#now write out information to HTML file
		if (($imgCounter % $ncols) == 0) {
			if ($imgCounter != 0) {
				print INDEXFILE "</tr>\n";		
			}
			print INDEXFILE "<tr align=\"center\" valign=\"top\">\n";
		}	
		print INDEXFILE "<td><a href=\"".$base.".html\"><img src=\"thumbnails/".$base.$ext."\" width=\"".$width."\" height=\"".$height."\" alt=\"".$captionAbstract."\" /></a><p>".$captionAbstract."</p></td>\n";
		
		#resizing original image
		if ($imgWidth > $imgHeight) {
			$width = $longside;
			$height = nearest(1,($width * $factor));
		}
		else {
			$height = $longside;
			$width = nearest(1,($height * $factor));
		}
		#$status = system("convert ".$file." -resize ".$width."x".$height." ".$file);
		#die "could not resize $file" if $status == 1;
		
		#now write out the html file containing the big image
		open(PHOTOFILE,">",$dir.$base.".html") or die "Can't open file ".$dir.$base.".html for writing: $!\n";

		#print XHTML header
		print PHOTOFILE qq(<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>);

		#include css file
		if ($cssfile) {
			print PHOTOFILE "\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"".$cssfile."\" />\n";
		}

		#create title
		print PHOTOFILE "\t<title>$captionAbstract</title>\n";

		#add author information
		if ($author) {
			print PHOTOFILE "\t<meta name=\"author\" content=\"".$author."\" />\n";
		}

		#add charset, description and keywords
		print PHOTOFILE "\t<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
		print PHOTOFILE "\t<meta name=\"description\" content=\"".$captionAbstract."\" />\n";
		print PHOTOFILE "\t<meta name=\"keywords\" content=\"".$captionAbstract."\" />\n";
		#add generation date
		print PHOTOFILE "\t<meta name=\"date\" content=\"".&UnixDate("today","%Y-%m-%e")."\" />\n";

		#close header and start body
		print PHOTOFILE qq(</head>
<body>
<center>
);
		
		if ($height > $width) {
			#case: portrait image
			#add previous or next image and image metadata
			print PHOTOFILE "<table width=\"100%\">\n\t<tr>\n\t\t<td valign=\"top\" align=\"right\">\n";
			unless ($imgCounter == 0) {
				print PHOTOFILE "\t\t\t<a href=\"".$imgFilenames[$imgCounter-1].".html\"><img src=\"thumbnails/".$imgFilenames[$imgCounter-1].$ext."\" alt=\"".$imgTitles[$imgCounter-1]."\" /></a><br />Previous Image: ".$imgTitles[$imgCounter-1]."\n";
			}
			print PHOTOFILE "\t\t</td>\n\t\t<td width=\"".($width + 10)."\" align=\"center\" valign=\"top\">\n\t\t\t";
		}
		
		#add image-tag
		print PHOTOFILE "<img src=\"".$base.$ext."\" alt=\"".$captionAbstract."\" width=\"".$width."\" height=\"".$height."\" />\n";

		if ($height > $width) {
			#case portrait image
			print PHOTOFILE "\t\t</td>\n\t\t<td valign=\"top\" align=\"left\">\n";
			unless ($imgCounter == ($imageCounter - 1)) {
				print PHOTOFILE "\t\t\t<a href=\"".$imgFilenames[$imgCounter+1].".html\"><img src=\"thumbnails/".$imgFilenames[$imgCounter+1].$ext."\" alt=\"".$imgTitles[$imgCounter+1]."\" /></a><br />Next Image: ".$imgTitles[$imgCounter+1]."<p />\n\t\t\t<hr />";
			}
		}
		else {
			#case landscape image
			print PHOTOFILE "<table width=\"$width\">\n\t<tr>\n\t\t<td width=\"".$thumblongside."px\" valign=\"top\" align=\"left\">\n";
			#add previous or next image and image metadata
			unless ($imgCounter == 0) {
				print PHOTOFILE "\t\t\t<a href=\"".$imgFilenames[$imgCounter-1].".html\"><img src=\"thumbnails/".$imgFilenames[$imgCounter-1].$ext."\" alt=\"".$imgTitles[$imgCounter-1]."\" /></a><br />Previous Image: ".$imgTitles[$imgCounter-1]."\n";
			}
			print PHOTOFILE "\t\t</td>\n\t\t<td valign=\"top\" align=\"center\">\n";
		}
		
		#print out information of the current image
		print PHOTOFILE "\t\t\t<p><b>$captionAbstract</b><br /><a href=\"".$output."\">(Back to Image Gallery)</a></p>\n";
		print PHOTOFILE "\t\t\t<p>Date/Time: $DateTimeOriginal";
		if ($country && $province && $city) {
			print PHOTOFILE ", Location: $country &rarr; $province &rarr; $city";
		}
		print PHOTOFILE "</p>\n";
		
		#print out camera/photo info
		print PHOTOFILE "<p>Camera: $cameraModel (ISO $iso), $exposure\", F: $aperture, Focal-Length (35mm equiv.): ".$focalLength."mm</p>\n";
		
		if ($lat && $latRef && $lon && $lonRef) {
			print PHOTOFILE "\t\t\t<p>Coords (WGS84): $lat, $lon, ".$alt."m<br />\n";
			$lat =~ s/°|'|(" N)|(" S)//g;
			my @lats = split(/\s/,$lat);
			my $latf = dms2dd($lats[0],$lats[1],$lats[2]);
			$lon =~ s/°|'|(" E)|(" W)//g;
			my @lons = split(/\s/,$lon);
			my $lonf = dms2dd($lons[0],$lons[1],$lons[2]);
			#write out destination or direction
			if ($destLat && $destLon) {
				print PHOTOFILE "\t\t\tDest.-Coords (WGS84): $destLat, $destLon<br />\n";			
			}
			if ($imageDir) {
				print PHOTOFILE "\t\t\tImg-Direction: $imageDir";
				if ($imageDirRef == "M") {
					print PHOTOFILE " (magnetic)";	
				}
				else {
					print PHOTOFILE " (true dir)";						
				}
				if ($imageDist) {
					print PHOTOFILE ", Dest-Distance: $imageDist ($imageDistRef)";												
				}
			}
			else {
				if ($destLat && $destLon) {
					$destLat =~ s/°|'|(" N)|(" S)//g;
					my @destlats = split(/\s/,$destLat);
					my $destlatf = dms2dd($destlats[0],$destlats[1],$destlats[2]);
					$destLon =~ s/°|'|(" E)|(" W)//g;
					my @destlons = split(/\s/,$destLon);
					my $destlonf = dms2dd($destlons[0],$destlons[1],$destlons[2]);
					$imageDir = iniBearingEarth($latf,$lonf,$destlatf,$destlonf);
					$imageDist = distEarth($latf,$lonf,$destlatf,$destlonf);
					print PHOTOFILE "Image-Direction: ".nearest(1,$imageDir)."°";												
					print PHOTOFILE ", Dest-Distance: ".nearest(0.001,$imageDist)."km";												
				}
			}
			print PHOTOFILE "</p>\n";
			if ($ggmaps) {
				#google maps
				print PHOTOFILE "\t\t\t<p><a href=\"http://maps.google.com/maps?ll=$latf,$lonf&amp;z=16&t=m&amp;hl=en&amp;ie=UTF8\" target=\"_blank\">Link to Google Maps</a> | \n";
				
				#Multimap
				print PHOTOFILE "\t\t\t<a href=\"http://www.multimap.com/map/browse.cgi?lat=$latf&amp;lon=$lonf&amp;scale=10000&amp;icon=x\" target=\"_blank\">Link to Multimap</a> | \n";
				
				#MSN Maps
				print PHOTOFILE "\t\t\t<a href=\"http://maps.msn.com/map.aspx?&amp;lats1=$latf&amp;lons1=$lonf&amp;alts1=1&amp;regn1=1&amp;name=$captionAbstract\" target=\"_blank\">Link to MSN Maps</a> | \n";
				
				#Yahoo Maps
				print PHOTOFILE "\t\t\t<a href=\"http://maps.yahoo.com/#tp=undefined&maxp=search&mvt=m&trf=0&lat=$latf&amp;lon=$lonf&amp;mag=2\" target=\"_blank\">Link to Yahoo Maps</a></p>\n";				
			}
		}
		
		#
		if ($height < $width) {
			#case landscape image
			print PHOTOFILE "\t\t</td>\n\t\t<td width=\"".$thumblongside."px\" align=\"right\" valign=\"top\">\n";
			unless ($imgCounter == ($imageCounter - 1)) {
				print PHOTOFILE "\t\t\t<a href=\"".$imgFilenames[$imgCounter+1].".html\"><img src=\"thumbnails/".$imgFilenames[$imgCounter+1].$ext."\" alt=\"".$imgTitles[$imgCounter+1]."\" /></a><br />Next Image: ".$imgTitles[$imgCounter+1]."\n\t\t</td>\n\t</tr>\n</table>\n";
			}
		}
		else {
			print PHOTOFILE "\t\t</td>\n\t</tr>\n</table>\n";
		}
	
		print PHOTOFILE qq(
</center>
</body>
</html>);

		close(PHOTOFILE);
		$imgCounter++;
	}
}
closedir(DIR);

print INDEXFILE "</tr>\n</table>";

if ($ssifooter) {
	print INDEXFILE "\n<!--#include virtual=\"".$ssifooter."\"-->";	
}

print INDEXFILE qq(
</center>
</body>
</html>);

#close html file
close(INDEXFILE);

#subroutine to calculate degree/minutes/seconds to decimal degrees
sub dms2dd {
	my $deg = shift;
	my $min = shift;
	my $sec = shift;
	return ($deg + ($min / 60) + ($sec / 3600));
}

# subroutine acos (source: http://jan.ucc.nau.edu/~cvm/latlon_formula.html)
# input: an angle in radians
# output: returns the arc cosine of the angle
# description: this is needed because perl does not provide an arc cosine function
sub acos {
   my($x) = @_;
   my $ret = atan2(sqrt(1 - $x**2), $x);
   return $ret;
}

# subroutine distance calculation on earth
# (source: http://jan.ucc.nau.edu/~cvm/latlon_formula.html)
sub distEarth {
	my $lat1 = shift; #lat1 in dec degrees	
	my $lon1 = shift; #lon1 in dec degrees	
	my $lat2 = shift; #lat2 in dec degrees	
	my $lon2 = shift; #lon2 in dec degrees	
	my $r = 6371; #radius of the earth
	my $pi = atan2(1,1) * 4;
	
	#calculate radians
	$lat1 *= ($pi/180);
	$lon1 *= ($pi/180);
	$lat2 *= ($pi/180);
	$lon2 *= ($pi/180);
	
	#calculate distance
	return acos(cos($lat1)*cos($lon1)*cos($lat2)*cos($lon2) + cos($lat1)*sin($lon1)*cos($lat2)*sin($lon2) + sin($lat1)*sin($lat2)) * $r;
}

# subroutine distance calculation on earth
# (source: http://www.movable-type.co.uk/scripts/LatLong.html)
sub iniBearingEarth {
	my $lat1 = shift; #lat1 in dec degrees	
	my $lon1 = shift; #lon1 in dec degrees	
	my $lat2 = shift; #lat2 in dec degrees	
	my $lon2 = shift; #lon2 in dec degrees	
	my $r = 6371; #radius of the earth
	my $pi = atan2(1,1) * 4;
	my $deltaLong = $lon2 - $lon1;
	
	#calculate radians
	$lat1 *= ($pi/180);
	$lon1 *= ($pi/180);
	$lat2 *= ($pi/180);
	$lon2 *= ($pi/180);

	my $x = sin($deltaLong) * cos($lat2);
	my $y = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($deltaLong);
	
	my $result = rad2Brng(atan2($y,$x));
	
	return $result;
}

# subroutine radians to bearing
# (source: http://www.movable-type.co.uk/scripts/LatLong.html)
sub rad2Brng {
	my $rad = shift;
	my $pi = atan2(1,1) * 4;
	return abs((($rad + 2 * $pi) % (2 * $pi)) * 180 / $pi);
}