#!/usr/bin/perl

=head1 NAME

	site-generator - HPR Site Generator

=head1 SYNOPSIS

	site-generator [OPTION]... PAGE|PAGE=<comma separated list of ids>...

	-a, --all      generate all pages defined in configuration file
	-l, --list     print list of configured pages
	-p, --preview  print generated pages to standard out
	-q, --quiet    suppress progress information while generating pages
	-v, --verbose  print extended progress information while generating pages
	--help         print this help message

	Where I<PAGE> is a file name of a web page 
	or the special I<ALL> (to generate all pages).

	Examples:

		Generate two specific pages:
		site-generator index about

		Generate the whole site:
		site-generator --all

		Generate pages based on the same template:
		site-generator correspondent=1,3,5..10

=head1 DESCRIPTION

This is a site generator for the Hacker Public Radio website based upon the Perl Templates Toolkit.

=head1 INSTALLATION

	* Create the sqlite3 database from the files in the _sql directory. The default name for the 
	  database file is "hpr.db" and should be located in the root of the project directory. The 
	  name and location can be set in the site.cfg file.
	* Two sql helper scripts are available to generate an empty database or a database filled with test data.
	  - For an empty database: `cat Create_Database_Empty.sql | sqlite3 hpr.db`
	  - For a database with test data: `cat Create_Database_Test.sql | sqlite3 hpr.db`
	* SQLite v3.8.3 or greater is recommended. CTE WITH clauses are used in some template queries. Must convert WITH 
	  clauses to sub-queries when using earlier versions of SQLite.
	* Install the needed Perl modules using preferred method (distribution packages, CPAN, etc.)
		* GetOpt
		* Pod::Usage
		* Config::Std
		* Template
		* Template::Plugin::File
		* Template::Plugin::DBI
		* DBI
		* Tie::DBI
		* DBD::SQLite
		* Date::Calc

=head1 AUTHOR

	Roan Horning <roan.horning@no-spam.gmail.com>

=head1 LICENSE

	site-generator -- a static website generator for HPR
	Copyright (C) 2022 Roan Horning

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.	

=cut

use strict;
use warnings;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Config::Std;
use Template;
use Data::Dumper;

exit main();

sub main {

	# Argument parsing
	my $all;
	my $preview;
	my $verbose;
	my $quiet;
	GetOptions(
		'all'     => \$all,
		'list'    => \&print_available_pages,
		'preview' => \$preview,
		'verbose' => \$verbose,
		'quiet'   => \$quiet,
	) or pod2usage(1);
	pod2usage(1) unless @ARGV || $all;
	my (@page_args) = @ARGV;

	if ($quiet) {
		$verbose = 'quiet';
	};

	# Load config file
	read_config "site.cfg" => my %config;

	my $tt = get_template_html($config{DBI});

	# If command line option all is set, parse configuration file 
	# for all pages
	if ($all) {
		@page_args = keys %config;

		# Remove non page sections of the configuration file
		# from the generated list of pages.
		@page_args= grep { $_ ne 'DBI' } @page_args;
		@page_args= grep { $_ ne 'root_template' } @page_args;
	};
	foreach my $page_arg (@page_args) {
		my %parsed_arg = parse_page_arg($page_arg);
		if (exists($config{$parsed_arg{'page'}})) {
			my $page_config = $config{$parsed_arg{'page'}}; 
			$page_config->{'page'} = $parsed_arg{'page'};

			# Set page's root_template to the default root_template if the 
			# page root_template property is not set in the configuration file.
			if (exists $page_config->{'root_template'} == 0) {
				$page_config->{'root_template'} = $config{root_template}{content};
			}

			if (exists $config{root_template}{baseurl}) {
				$page_config->{'baseurl'} = $config{root_template}{baseurl};
			}

			if ($page_config->{'multipage'} && $page_config->{'multipage'} eq 'true') {
				if (scalar @{$parsed_arg{'ids'}} == 1) {
				  @{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config);	
				}
				foreach my $id (@{$parsed_arg{'ids'}}) {
					$page_config->{'id'} = $id;
					verbose ($verbose, "Generating page: $page_config->{'page'} with id: $id");
					generate_page($tt, \$page_config, $preview);
				}
			}
			else {
				verbose ($verbose, "Generating page: $page_config->{'page'}");
				generate_page($tt, \$page_config, $preview);
			}
		}
		else {
			verbose (1, "\nWarning: Page $parsed_arg{'page'} is not defined in the configuration file.");
		}
	}
    verbose (1, "\nFinished processing the files.");
	return 0;
}

sub get_template_html (\%@)  {
	# For an HTML based Template file, define the 
	# template start and end tags to also function as 
	# HTML comments to make the template file valid HTML.
	#
	return Template->new({
			INCLUDE_PATH => './templates',
			OUTPUT_PATH	 => './public_html',
			EVAL_PERL    => 1,
			START_TAG	 => '<!--%',
			END_TAG		 => '%-->',
			PRE_CHOMP  => 1,
			POST_CHOMP => 1,
			CONSTANTS    => {
				driver   => $_[0]{driver},
				user     => $_[0]{user},
				password => $_[0]{password},
			}
		}) || die $Template::ERROR, "\n";

}

sub generate_page  {
	my ($tt, $config, $preview) = @_;
	my $html;
	if (!$preview) {
		$html = get_filename($$config); 
	}
	$tt->process($$config->{root_template}, $$config, $html)
	|| die $tt->error(), "\n";

}

sub verbose {
	my ($verbose, $message) = @_;
	if ($verbose) {
		if ($verbose ne 'quiet') { 
			print STDOUT "$message\n";
		}
	}
	else {
		STDOUT->autoflush(1);
		print STDOUT ".";
	};
}

sub parse_page_arg {
	my ($page_arg) =  @_;
	# Split page name from page ids if available.
	my ($page, $ids) = split(/=/, $page_arg);
	my @ids = [];

	if(!$ids) {
		$ids = "";
	}
	else {
		# Parse the page ids and push them onto @ids array 
		my @ids_by_comma = split(/\,/, $ids);
		foreach my $id_by_comma (@ids_by_comma) {
			my @ids_for_range = split(/\.\./, $id_by_comma);
			if ((scalar @ids_for_range) == 2) {
				push @ids, $ids_for_range[0]..$ids_for_range[1]; 
			}
			elsif ((scalar @ids_for_range) == 1) {
				push @ids, $ids_for_range[0];
			}
			else {
				verbose (1, "\nWarning: Page $page id range $id_by_comma could not be parsed.");
			}
		}
	}
	return ('page' => $page, 'ids' => [@ids]);
}

sub get_ids_from_db {
	# Use a template to generate a string of page identifiers.
	# The template should return the string in the form of 
	# <comma><identifier><comma><identifier>...
	#
	my ($tt, $config)  = @_;
	my $selected_ids = "";
	my $id_template = "ids-$$config->{'page'}.tpl.html";

	$tt->process($id_template, $$config, \$selected_ids)
	|| die $tt->error(), "\n";
	
	return split(/,/, substr($selected_ids, 1));
}

sub get_filename {
	my ($config) = @_;
	my $filename = "output.html";
	my $base_path = "";

	if ($$config{'filename'}) {
		if (substr($$config{'filename'}, -1) eq '/') {
			$base_path = $$config{'filename'};
		}
		else {
			$filename = $$config{'filename'};
			my $padded_index = "";
			if (exists $$config{'id'}) {
				$padded_index = sprintf("%04d", $$config{'id'});
			}
			$filename =~ s/\[id\]/$padded_index/;
		    return $filename;
		}
	}
	# Default naming if full filename configuration is not supplied.
	if ($$config{'multipage'} && $$config{'multipage'} eq 'true') {
		my $padded_index = sprintf("%04d", $$config{'id'});
		$filename = "$base_path$$config{'page'}${padded_index}.html";	
	}
	else {
		$filename = "$base_path$$config{'page'}.html";
	}
	return $filename;
}

sub print_available_pages {
	# Load config file
	read_config "site.cfg" => my %config;

	my @page_args = sort (keys %config);

	# Remove non page sections of the configuration file
	# from the generated list of pages.
	@page_args= grep { $_ ne 'DBI' } @page_args;
	@page_args= grep { $_ ne 'root_template' } @page_args;

	foreach my $page_arg (@page_args) {
		print "$page_arg\n";
	}
	exit;
}