# Simple Photo Gallery
# (c) 2003--2015 Martin Mares <mj@ucw.cz>

package UCW::Gallery;

use common::sense;

use File::Spec;
use Storable;

### Class methods ###

sub new($) {
	my ($class) = @_;
	my $self = { };
	$self->{cfg} = {
		# Directories
		OrigDir => '.',			# Original images
		PhotoDir => 'photo',		# Scaled-down photos for web
		CacheDir => 'cache',		# Cache with meta-data and thumbnails

		# URL prefixes
		PhotoUrlPrefix => 'photo/',
		ThumbUrlPrefix => 'thumb/',
		ThemeUrlPrefix => 'gal/',

		# Processing machinery settings
		ScanDefaultTransform => 's',
		PhotoMaxWidth => 1024,
		PhotoMaxHeight => 1024,
		ThumbFormats => [ "114x94" ],	# Built-in themes use the first size,
						# but more can be generated
		CacheExif => 0,			# Cache selected EXIF meta-data
		CacheHashes => 1,		# Let gal-scan cache file hashes

		# Titles and navigation
		Title => 'An Unnamed Gallery',
		SubTitle => "",
		ParentURL => '../',
		BackURL => "",
		FwdURL => "",

		# Hacks
		GeoHack => 0,
	};
	return bless $self, $class;
}

sub load_config($) {
	my $cfg = "./gallery.cf";
	my $self = do $cfg;
	unless (defined $self) {
		if ($@) {
			die "Error parsing $cfg: $@";
		} elsif ($!) {
			die "Cannot load $cfg: $!\n";
		} else {
			die "Cannot load $cfg, check that it returns true\n";
		}
	}
	return $self;
}

### Object methods ###

sub get($$) {
	my ($self, $key) = @_;
	if (exists $self->{cfg}->{$key}) {
		my $val = $self->{cfg}->{$key};
		defined $val or warn "Gallery: Config item $key is not set\n";
		return $val;
	} else {
		warn "Gallery: Config item $key does not exist\n";
		return;
	}
}

sub try_get($$) {
	my ($self, $key) = @_;
	return $self->{cfg}->{$key};
}

sub def($@) {
	my $self = shift;
	while (my $key = shift @_) {
		my $val = shift @_;
		$self->{cfg}->{$key} //= $val;
	}
}

sub set($@) {
	my $self = shift;
	while (my $key = shift @_) {
		$self->{cfg}->{$key} = shift @_;
	}
}

sub get_config_keys($) {
	my ($self) = @_;
	return keys %{$self->{cfg}};
}

our %list_attrs = (
	'file' => 0,		# 0 = not permitted as extended attribute
	'id' => 0,
	'orientation' => 1,	# 1 = aliases for normal attributes
	'title' => 1,
	'xf' => 2,		# 2 = ... and propagated to gal-gen
	'lat' => 3,		# 3 = normal extended attributes, propagated to gal-gen
	'lon' => 3,
	'alt' => 3,
	't' => 3,
);

sub write_list($$$) {
	my ($self, $file, $images) = @_;
	open my $fh, '>:utf8', "$file.new" or die "Cannot create $file.new: $!\n";
	for my $i (@$images) {
		print $fh join("\t",
			$i->{file},
			$i->{id},
			$i->{orientation},
			$i->{xf},
			($i->{title} eq '' ? '-' : $i->{title}),
		), "\n";
		for my $k (keys %$i) {
			print $fh "\t$k=", $i->{$k}, "\n" if $list_attrs{$k} >= 3;
		}
	}
	close $fh;
	rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
}

sub read_list_fh($$) {
	my ($self, $fh) = @_;
	my @images = ();
	while (<$fh>) {
		chomp;
		/^$/ and next;
		/^#/ and next;
		if (/^\t/) {
			@images or die "Misplaced continuation line before first image\n";
			if (my ($k, $v) = /^\t+(.*?)=(.*)/) {
				# Continutation of previous line
				my $i = $images[-1];
				if ($list_attrs{$k}) {
					$i->{$k} = $v;
				} else {
					print STDERR "Ignoring unknown attribute $k for ", $i->{file}, "\n";
				}
			} else {
				die "Invalid continuation line. Expecting 'key=value'.\n";
			}
		} else {
			my $i = {};
			($i->{file}, $i->{id}, $i->{orientation}, $i->{xf}, $i->{title}) = split /\t/;
			if (!defined $i->{title} || $i->{title} eq '-') { $i->{title} = ""; }
			push @images, $i;
		}
	}
	return \@images;
}

sub read_list($$) {
	my ($self, $file) = @_;
	open my $fh, '<:utf8', $file or return;
	my $list = $self->read_list_fh($fh);
	close $fh;
	return $list;
}

sub write_meta($$) {
	my ($self, $file, $meta) = @_;
	open my $fh, '>', "$file.new" or die "Cannot create $file.new: $!\n";
	Storable::nstore_fd($meta, $fh);
	close $fh;
	rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
}

sub read_meta($) {
	my ($self, $file) = @_;
	open my $fh, '<', $file or die "Cannot read $file: $!\n";
	my $meta = Storable::fd_retrieve($fh) or die "Cannot parse $file\n";
	close $fh;
	return $meta;
}

sub photo_meta_name($) {
	my ($self) = @_;
	return File::Spec->catfile($self->get('PhotoDir'), 'gallery.meta');
}

sub cache_meta_name($) {
	my ($self) = @_;
	return File::Spec->catfile($self->get('CacheDir'), 'cache.meta');
}

sub thumb_fmt_to_size($$) {
	my ($self, $fmt) = @_;
	my ($tw, $th) = ($fmt =~ m{^(\d+)x(\d+)$}) or die "Cannot parse thumbnail format $fmt\n";
	return ($tw, $th);
}

sub photo_file_name($$$) {
	my ($self, $photo_meta, $id) = @_;
	return $id . '.' . ($photo_meta->{fmt} // 'jpg');
}

sub photo_name($$$) {
	my ($self, $photo_meta, $id) = @_;
	return File::Spec->catfile($self->get('PhotoDir'), $self->photo_file_name($photo_meta, $id));
}


1;
