#!/usr/bin/perl -w

use strict;
use IO::Uncompress::Unzip qw(unzip $UnzipError);
use File::Basename;
use File::Copy;
use File::stat;
#use XML::LibXML;
#use XML::LibXSLT;
use XML::Simple;
use Template;
use YAML;
use Cwd;
use Date::Manip;
use Data::Dumper;

=head1 NAME

tellico2html - tellico data web site generation

=head1 DESCRIPTION

tellico2html allow the automatic generation of a full web site from tellico data.
It uses perl and Template::Toolkit templates to perform its tasks.

=head1 SYNOPSIS

tellico2html [collection.tc ...]

=head1 ARGUMENTS

collection.tc is a tellico data file

=head1 WEB SITES

The main Web site of the project is available at L<https://github.com/bcornec/tellico2html>.

=head1 CONFIGURATION FILE

tellico2html reads the configuration file .tellico2htmlrc in your HOME directory.
It can contain 3 variables describing your environement:

=over 4

=item B<outdir:>

The directory in which the web pages will be generated

=item B<sharedir:>

The directory where tellico places its data files (shouldn't need to be changed except if you did a local install of tellico under /usr/local e.g.)

=item B<tmpldir:>

The directory where tellico2html is using the Template Toolkit template files for your tellico data files. The template file should be named the same as the tellico tc file. If you want to process a collection.tc file in a directory, you need to create 2 files in that tmpldir directory, the collection.tt to describe the list of items and the collection_files.tt to describe the info for a specific item. Examples are provided which should serve as a base to support your own .tc file setup.

=back

=head1 AUTHORS

Bruno Cornec L<mailto:bruno@project-builder.org>.

=head1 COPYRIGHT

tellico2html is distributed under the AGPL v3.0 license
described in the file C<LICENSE> included with the distribution.

=cut

### PATHS that can be modified to adapt to local environement
### used as defaults to create the YAML configuration file
#
# Dir where HTML files will be generated
my $outdir = "$ENV{'HOME'}/www";
# Dir where tellico provided files are stored
my $sharedir = "/usr/share/tellico";
#
my $tmpldir = "$ENV{'HOME'}/tmpl";
#
### END of PATHS modification
#

# Manages configuration file if it exists
my $cf = "$ENV{'HOME'}/.tellico2htmlrc";
if (not -f $cf) {
	print "Creating YAML conf file $cf\n";
	open(CF,"> $cf") || die "Unable to create $cf";
	print CF "---\n";
	print CF "  outdir: $outdir\n";
	print CF "  sharedir: $sharedir\n";
	print CF "  tmpldir $tmpldir\n";
	close(CF);
	print "Please review $cf to adapt to your context\n";
}
print "Loading YAML conf file $cf\n";
my $lh = YAML::LoadFile($cf);

# Setup output directory
$outdir = $lh->{outdir} if (defined $lh->{outdir});
mkdir "$outdir",0755 if (not -d "$outdir");

# Setup tellico directory
$sharedir = $lh->{sharedir} if (defined $lh->{sharedir});
die "tellico files not available under $sharedir" if (not -d "$sharedir");

$tmpldir = $lh->{tmpldir} if (defined $lh->{tmpldir});
die "templates not available under $tmpldir" if (not -d "$tmpldir");

print "\n--------- tellico2html Conf ------------\n";
print "Output Directory: $outdir\n";
print "Template Directory: $tmpldir\n";
print "Tellico Directory: $sharedir\n";
print "--------- tellico2html End Conf --------\n";

# Managing an index
my $idx;

#my $xslt = XML::LibXSLT->new();
#my $style = XML::LibXML->load_xml(location=>"", no_cdata=>1);

# By default use all tc files available where we are
my @params = <*.tc>;
# Force the param if given on CLI
@params = @ARGV if (defined $ARGV[0]);

my %dup;
my $curdir = getcwd();
my $changed = 0;

# Process each tellico file
foreach my $file (@params) {
	print "\nProcessing tellico file $file\n";
	print "-----------------------\n";
	# Look for template early in the same directory as the tellico file
	my $dir = dirname($file);
	chdir "$dir" || die "Unable to change directory to $dir";
	my $out = basename($file,".tc");
	my $tout = "$out.tt";
	if (not -f "$tmpldir/$tout") {
		warn "Unable to find template $tmpldir/$tout";
		next;
	}
	my $tsub = $out."_files.tt";
	if (not -f "$tmpldir/$tsub") {
		warn "Unable to find template $tmpldir/$tsub";
		next;
	}

	# read compressed input file
	my $output;
	unzip basename("$file") => \$output || die "Unable to open ".basename($file).": $UnzipError";
	#print Dumper($output)."\n";
	
	# with Simple
	my $h = XMLin($output);
	
	# Loop on the complete collection fields
	my $e = $h->{collection}->{fields}->{field};
	my @dates;
	#my @fields;
	foreach my $k (keys %$e) {
		# Name of fields to be changed (contains '-')
		#print "Processing field $k\n";
		if ($k =~ /-/) {
			#push(@fields, $k);
			duplicate_hyphen($k,$e);
		}
	}

	foreach my $k (keys %$e) {
		# keep track of dates fields now that we have all fields created 
		# (can't be done with previous loop)
		push(@dates, $k) if ($e->{$k}->{type} == 12);
	}
	#print Dumper(@dates)."\n";
	#

	# Loop on the complete collection entries
	$e = $h->{collection}->{entry};
	foreach my $k (@$e) {
		foreach my $f (keys %$k) {
				#print "Analyzing $f: ";
			if ($f =~ /-/) {
				#print "found '-'";
				duplicate_hyphen($f,$k);
			}
			#print "\n";
		}
		# transform Tellico dates into perl dates
		foreach my $d (@dates) {
			if (defined $k->{$d}) {
				$k->{$d}->{year} = "0001" if (not defined $k->{$d}->{year});
				$k->{$d}->{month} = "01" if (not defined $k->{$d}->{month});
				$k->{$d}->{day} = "01" if (not defined $k->{$d}->{day});
				$k->{$d} = "$k->{$d}->{year}"."-"."$k->{$d}->{month}"."-"."$k->{$d}->{day}";
			}	
		}
		# Remove id attributes for Simple as it duplicates the value
		$k->{id} = $k->{id}[0];
		#print Dumper($k)."\n";
	}
	#print Dumper($h)."\n";

	# with LibXML
	#my $parser = XML::LibXML->new;
	#my $xslt = XML::LibXSLT->new;
	#my $src = $parser->parse_string($output);
	#print Dumper($src)."\n";

	my $fdir = "$outdir/$out"."_files";
	mkdir "$fdir",0755 if (not -d "$fdir");

	$h->{fdir} = "$fdir";
	$h->{ldir} = basename("$fdir");
	$idx->{collection}->{$h->{collection}->{title}} = "$out.html";
	$h->{outdir} = "$outdir";
	$h->{tout} = "$tout";
	$h->{collection}->{maintitle} = $h->{collection}->{title};

	my $fout = "$outdir/$out.html";

	# Do not analyze if not modified since last generation
	# and similarly for the template
	my $sb = stat("$fout");
	my $outdate = scalar($sb->mtime) if (defined $sb);
	my $tsb = stat("$tmpldir/$tout");
	my $ttdate = scalar($tsb->mtime) if (defined $tsb);
	my $tcsb = stat("$file");
	my $tcdate = scalar($tcsb->mtime) if (defined $tcsb);
	#print "File ($fout): $outdate - Template($tmpldir/$tout): $ttdate - TC ($file): $tcdate\n";
	if ((defined $sb) && (defined $tsb) && (defined $tcsb) && ($outdate >= $tcdate) && ($outdate >= $ttdate)) {
		print "Nothing changed since last run, no need to generate $fout\n";
		next;
	}

	$changed = 1;
	print "Generating main HTML file $fout using template $tmpldir/$tout\n";

	# link some mandatory files
	foreach my $file ("pics/checkmark.png","tellico2html.js","shadowAlpha.png") {
		my $dfile =basename($file);
		warn "tellico file $file not available under $sharedir" if (not -f "$sharedir/$file");
		copy("$sharedir/$file","$outdir/$dfile") if (not -f "$outdir/$dfile");
	}

	# prepare HTML output file using Template Toolkit
	my $tt = Template->new({ENCODING => 'utf8', ABSOLUTE => 1, INCLUDE_PATH => "$tmpldir"}) || die "$Template::ERROR\n";
	#print("STEP 2\n");
	$tt->process("$tmpldir/$tout",$h,"$fout",{ binmode => ':encoding(utf8)' }) || die "Template process failed: ", $tt->error(), "\n";

	print "Generating up to ".@$e." HTML detailed files using template $tmpldir/$tsub\n";
	my $srcdir = "$out"."_files";
	# copy gradient files
	foreach my $f ("gradient_header.png","gradient_bg.png") {
		if (not -f "$outdir/$f") {
			my $ret = link("$srcdir/$f","$outdir/$f");
			copy("$srcdir/$f","$outdir/$f") if (not $ret);
		}
	}
	# prepare HTML output subfiles using Template Toolkit
	my $cpt = 0;
	foreach my $k (@$e) {
		my $subf = "$fdir/$k->{id}.html";
		# Copy cover image 
		if ((defined $k->{cover}) && (-f "$srcdir/$k->{cover}") && (not -f "$fdir/$k->{cover}")) {
			#print "Cover: $dir/$out"."_files/$k->{cover}\n";
			# Link first and copy if unsuccessful
			my $ret = link("$srcdir/$k->{cover}","$fdir/$k->{cover}");
			copy("$srcdir/$k->{cover}","$fdir/$k->{cover}") if (not $ret);
		}
		if ((defined $k->{cover}) && (not -f "$srcdir/$k->{cover}")) {
			warn "Unable to copy image file $srcdir/$k->{cover}";
		}
		# Fix dates format
		$k->{cdate} .= " 23:59:59" if (defined $k->{cdate});
		$k->{mdate} .= " 23:59:59" if (defined $k->{mdate});
		# Add collection content to $k
		my $cc = $h->{collection}->{fields}->{field};
		foreach my $c (keys %$cc) {
			# keep track of useful fields
			$k->{collection}->{$c}->{title} = $cc->{$c}->{title};
			$k->{collection}->{$c}->{category} = $cc->{$c}->{category};
			$k->{collection}->{$c}->{prop} = $cc->{$c}->{prop};
		}
		$k->{collection}->{maintitle} = $h->{collection}->{title};
		$k->{collection}->{mainhtml} = "../$out.html";
		$k->{tout} = "$tsub";

		# Do not generate if not modified since last generation
		# and similarly for the template
		$sb = stat($subf);
		$outdate = scalar($sb->mtime) if (defined $sb);
		$tsb = stat("$tmpldir/$tsub");
		$ttdate = scalar($tsb->mtime) if (defined $tsb);
		#print "mdate: $k->{mdate}\n";
		$tcdate = UnixDate($k->{mdate},"%s") if (defined $k->{mdate});
		$tcdate = UnixDate("1900-01-01","%s") if (not defined $tcdate);
		#print "File ($subf): $outdate - Template($tmpldir/$tsub): $ttdate - TC ($file): $tcdate\n";
		if ((defined $sb) && (defined $tsb) && ($outdate >= $tcdate) && ($outdate >= $ttdate)) {
			#print "$subf unchanged since last run\n";
			next;
		}
		$cpt++;

		$tt = Template->new({ENCODING => 'utf8', ABSOLUTE => 1, INCLUDE_PATH => "$tmpldir"}) || die "$Template::ERROR\n";
		#print Dumper($k);
		$tt->process("$tmpldir/$tsub",$k,"$subf",{ binmode => ':encoding(utf8)' }) || die "Template process failed: ", $tt->error(), "\n";
		if (ref($k->{titles}->{title}) eq 'ARRAY') {
			print "Generating $subf ($k->{titles}->{title}->[0])\n";
		} elsif (defined ($k->{titles}->{title})) {
			print "Generating $subf ($k->{titles}->{title})\n";
		} elsif (defined ($k->{title})) {
			print "Generating $subf ($k->{title})\n";
		} else {
			print "Generating $subf (Unknown)\n";
		}
	}
	print "$cpt HTML detailed files really generated out of ".@$e."\n";
	# just treat the first file for now
	#last;
	chdir($curdir);
}
if ($changed == 1) {
	my $tt = Template->new({ENCODING => 'utf8', ABSOLUTE => 1, INCLUDE_PATH => "$tmpldir"}) || die "$Template::ERROR\n";
	$tmpldir = $lh->{tmpldir} if (defined $lh->{tmpldir});
	$idx->{tout} = "index.tt";
	if (not -f "$tmpldir/index.tt") {
		warn "tellico index.tt template not available under $tmpldir";
		warn "Not generating an index.html file";
	} else {
		$tt->process("$tmpldir/index.tt",$idx,"$outdir/index.html",{ binmode => ':encoding(utf8)' }) || die "Template process failed: ", $tt->error(), "\n";
	}
}
exit;

sub duplicate_hyphen {

# Modify private attribute name with '-' into '_' 
# as templating doesn't deal with - in names 
# (handled as perl variables)
#
my $attr = shift;
my $k = shift;

my $newattr = $attr;
$newattr =~ s|-|_|g;
my $jj = $k->{"$attr"};
#print "Managing key $attr vs $newattr\n";

# nothing to be done if no substitution done
return if (($newattr eq $attr) || (not defined $jj));

print "Changing key $attr into $newattr\n" if (not defined $dup{$attr});
$dup{$attr} = $newattr;
# print only once per key

#print Dumper($attr);
#print Dumper($jj)."\n";
#print ref($jj)."\n";
#print "---------\n";
if (ref($jj) eq 'HASH') {
	#print "Found HASH for $attr\n";
	foreach  my $j (keys %$jj) {
		$k->{$newattr}->{$j} = $jj->{$j};
		# Recurse here
		if ($j =~ /-/) {
			duplicate_hyphen($j,$k->{$newattr});
		}
	}
} elsif (ref($jj) eq 'ARRAY') {
	my $nn = $k->{$newattr};
	foreach my $j (@$jj) {
		push(@$nn,$j);
		if ($j =~ /-/) {
			duplicate_hyphen($j,$k->{$newattr});
		}
	}
# If we don't test empty, we don't get a scalar for that variable ?
} elsif ((ref($jj) eq 'SCALAR') || (ref($jj) eq '')) {
		$k->{$newattr} = $jj;
}
}
