#! /usr/bin/env perl

#
# This is a configure script.
#
# TODO: rework types of dependencies (from *_in, from *.dtx...)
# TODO: clean up handling of *_in files.  Watch out for duplicate _in filter.
# TODO: make more generic, use objects instead of strings for dependencies?
#

use 5.006_001;
use strict;
use warnings;
use Fatal qw(open close);
use File::Find;
use File::Path qw(mkpath);
use Getopt::Long qw(:config bundling);

#
# Global variables, default parameters.
#

our $name = "tptlibressavoirs";
our $cwd = `pwd`; chomp($cwd);
our $default_basedir = $0; $default_basedir =~ s{/.*?$}{};
our $default_srcdir = "src";
our $default_srcpicdir = "logos";
our $default_builddir = "build";
our $default_prefix = "/usr/local/share/texmf";
our $default_texdir = "tex/latex/$name";
our $default_docdir = "doc/latex/$name";
our $default_kpse = "kpsexpand \\\$TEXMFLOCAL";
our $default_mktexlsr = "mktexlsr";
our $default_install_cmd =
  (-x "/usr/ucb/install")? "/usr/ucb/install" : "install";
our $default_perl = "perl";
our $perl_version_required = "5.008";
our $disable_kpse = 0;
our $disable_mktexlsr = 0;
our $disable_doc = 0;
our $disable_old = 0;
our $need_mktexlsr = 0;
our $verbose = 1;
our $re_skipfiles = qr{\.(bak|orig|rej|old)$|[~#]$};
our ($version, $year, $month, $day, $date,
     $basedir, $srcdir, $srcpicdir, $builddir,
     $prefix, $texdir, $docdir, $picdir, $mktexlsr, $install_cmd, $perl,
     @depend_rules);
our @src_files_types = qw(tex sty dtx logo);
our %src_files = map { $_ => {}, "old$_" => {} } @src_files_types;


#
# Subroutine prototypes.
#
sub parse_options();
sub get_version();
sub register_file($);
sub find_in_src();
sub generate_makefile();
sub squawk($$);


#
# Main execution thread.
#

# Parse and check options, print usage and exit if required.
parse_options();

# Get package version and modification date from changelog.
($version, $year, $month, $day, $date) = get_version();

# Create build directory, determine build targets.
mkpath($builddir, { verbose => ($verbose > 2) });
my $build_target = "build-tex";
$build_target .= " build-tex-old" unless ($disable_old);
$build_target .= " build-doc" unless ($disable_doc);
$build_target .= " build-doc-old" unless ($disable_doc || $disable_old);
my $install_target = "install-tex";
$install_target .= " install-tex-old" unless ($disable_old);
$install_target .= " install-doc" unless ($disable_doc);
$install_target .= " install-doc-old" unless ($disable_doc || $disable_old);
$install_target .= " mktexlsr" unless ($disable_mktexlsr);

# Search source directory for files, note interesting actions.
find({ wanted => \&find_in_src, no_chdir => 1 }, $srcdir);

# Get all logos.
find({ no_chdir => 1,
       wanted => sub {
	 if ((-f $_) && (! m{$re_skipfiles})) {
	   s{^$srcpicdir/*}{};
	   my $type = (m{^old/})? "oldlogo" : "logo";
	   $src_files{$type}->{"\$(srcpicdir)/$_"} = 1;
	 }
       }
     }, $srcpicdir);

# Actually generate new Makefile.
generate_makefile();

exit(0);

#
# Subroutines.
#

#
# get_version(): get package version and modification date from changelog.
#
sub get_version() {
  my ($version, $year, $month, $day, $date);
  my $changelog = "$basedir/debian/changelog";

  squawk(2, "Reading changelog \"$changelog\"\n");
  open(my $fh, "<", $changelog);

  # Get version from first line.
  my $version_text = <$fh>;
  if (defined($version_text) && ($version_text =~ m{.*$name \(([^\)]+)\)})) {
    $version = $1;
  } else {
    squawk(1, "Failed to get version from \"$changelog\"\n");
    exit(1);
  }

  # Read next lines for modification date.
  while (<$fh>) {
    ($day, $month, $year) = m{^ -- .*,\s+(\d+) (...) (\d+)\b};
    if (defined($year)) {
      # Translate month into number, then stop reading $fh.
      $month = {
		jan => "01", feb => "02", mar => "03", apr => "04",
		may => "05", jun => "06", jul => "07", aug => "08",
		sep => "09", oct => "10", nov => "11", dec => "12"
	       }->{lc($month)};
      last;
    }
  }
  close($fh);

  unless (defined($year) && defined($month) && defined($day)) {
    squawk(1, "Failed to get date from \"$changelog\"\n");
    exit(1);
  }

  $date = sprintf("%04d/%02d/%02d", $year, $month, $day);
  squawk(2, "Version: $version; date: $date\n");
  return ($version, $year, $month, $day, $date);
}


#
# register_file(): push a file name into the appropriate list.
#
sub register_file($) {
  $_ = shift;
  my $old = (m{^old/})? "old" : "";
  foreach my $ext (@src_files_types) {
    $src_files{"$old$ext"}->{$1} = 1 if (m{^(.*\.$ext)(_in)?$}i);
  }
}


#
# parse_ins_file(): parse a .ins file for \generate{\file{}{\from{}{}}} lines.
#
sub parse_ins_file($$) {
  my ($path, $name) = (@_);
  my @generate_files = ();
  my %source_files = ();

  # Read file contents, filtering out % comments.
  open(my $fh, "<", $path);
  my $text = join("", map { s{\%.*$}{}; $_ } (<$fh>));
  close($fh);

  # Parse as many \generate directives as there is.
  while ($text =~ m{
		     \\generate\s*\{
		       ((.*?\\file\s*\{[^\}]+\}\s*\{
			 (\s*\\from\s*\{[^\}]+\}\s*\{[^\}]*\})+?
			 \s*\}
		       )+)
		       [^\}]*?\}(.*)$
		 }sx) {
    my $filecmd = $1;
    $text = $4;

    # Parse as many \file directives as there is in this \generate.
    while ($filecmd =~ m{
			  \\file\s*\{([^\}]+)\}\s*\{
			    ((\s*\\from\s*\{[^\}]+\}\s*\{[^\}]*\})+)
			    \s*\}(.*)$
		      }sx) {
      my $file = $1;
      my $fromcmd = $2;
      $filecmd = $4;

      $file =~ s{^\s*}{};
      $file =~ s{\s*$}{};
      register_file($file);
      push(@generate_files, $file);

      # Parse as many \from directives as there is in this \file.
      while ($fromcmd =~ m{
			    \\from\s*\{([^\}]+)\}\s*\{([^\}]*)\}(.*)$
			}sx) {
	my $from_file = $1;
	my $from_option = $2;
	$fromcmd = $3;

	$from_file =~ s{^\s*}{};
	$from_file =~ s{\s*$}{};
	register_file($from_file);
	$source_files{$from_file} = 1;
	push(@depend_rules, "$file: $from_file");
      }
    }
  }
  push(@depend_rules,
       join(" ", @generate_files)
       . ": $name "
       . join(" ", sort(keys(%source_files)))
       . "; \$(PDFLATEX) \$<");
}


#
# find_in_src(): hook called by File::Find in source directory.
# For each file or directory, execute appropriate action:
# -- directory: create same-named directory in $builddir
# -- file *_in: create rule in depend-Makefile to sed+copy it to $builddir
# -- other files except *~, *.bak etc.: create rule to copy it to $builddir
# -- file *.ins / *.ins_in: parse it for generated file dependencies
#
sub find_in_src() {
  my $path_name = $_;
  s{^$srcdir/*}{};
  my $rel_path_name = $_;
  return unless ($rel_path_name);

  if (-d $path_name) {
    squawk(3, "Creating directory \"$rel_path_name\".\n");
    mkpath("$builddir/$rel_path_name");
    return;
  } elsif (! -f $path_name) {
    squawk(3, "$rel_path_name: not a regular file, skipping.\n");
    return;
  } elsif (($path_name =~ m{$re_skipfiles})
	   || ($rel_path_name eq "Makefile.in")) {
    squawk(3, "$rel_path_name: skipped.\n");
    return;
  }

  squawk(3, "Processing \"$rel_path_name\".\n");
  register_file($rel_path_name);

  if ($rel_path_name =~ m{^(.*)_in$}) {
    my $radix = $1;
    push(@depend_rules,
	 "$radix: \$(srcdir)/${radix}_in \$(builddir)/Makefile\n\t\@perl -p -e "
	 . join(" \\\n\t         -e ",
		map { "'s{\\\@$_\\\@}{\$($_)}g;'" }
		qw(name version date year))
	 . " \\\n\t         -e 'if (m{\\\@include\\s+([^\@]+)\\\@}) {'"
	 . " \\\n\t         -e  "
	 . " 'local \$\$/; open(F, \"<\", \$\$1); \$\$_ = <F>; close(F);'"
	 . " \\\n\t         -e '}'"
	 . " \\\n\t         < \$< > \$\@\n");
    # FIXME: should also depend on debian/changelog?
    # FIXME: add dependency on include file(s)!
    # FIXME: document @include@ replaces its line; necessary to avoid
    # double-includes.
  } else {
    # Add a rule to copy this source file to the build directory.
    push(@depend_rules,
	 "$rel_path_name: \$(srcdir)/$rel_path_name; \@cp -p \$< \$@");
  }

  if ($rel_path_name =~ m{^(.*\.ins)(_in)?$}) {
    parse_ins_file($path_name, $1);
  }
}


#
# generate_makefile(): read $srcdir/Makefile.in, substitute values.
#
sub generate_makefile() {
  # Read source file.
  open(my $fh, "<", "$srcdir/Makefile.in");
  my $src_text = join("", (<$fh>));
  close($fh);

  # Cache list of generated files.
  # Hack: code snippets x.y.snip.tex generate file x-y.tex.
  foreach my $type (qw(tex oldtex)) {
    foreach my $f (sort(keys(%{$src_files{$type}}))) {
      my ($radix, $variant) = ($f =~ m{^(.+)\.(.+)\.snip\.tex$}) or next;
      my $source = "$radix.tex";
      my $target = "$radix-$variant.tex";

      # Adjust list of files.
      $src_files{$type}->{$target} = 1;
      delete($src_files{$type}->{$f});

      # Create this dependency.
      my $targetpdf = "$radix-$variant.pdf";
      push(@depend_rules,
	   "$target: $source $f; "
	   . "perl -pe 'use Fatal qw(open); s{^\\\\use(outer)?theme.*\\s*\$\$}{local \$\$/; open(F, \"<\", \"$f\"); my \$\$x = <F>; close(F); \$\$x}e;'"
	   . " < \"$source\" > \$@");
    }
  }

  # Join file lists into appropriate text variables.
  my ($tex_files, $sty_files, $dtx_files, $logo_files,
      $old_tex_files, $old_sty_files, $old_dtx_files, $old_logo_files);
  foreach my $r (["tex", \$tex_files, \$old_tex_files],
		 ["sty", \$sty_files, \$old_sty_files],
		 ["dtx", \$dtx_files, \$old_dtx_files],
		 ["logo", \$logo_files, \$old_logo_files]) {
    my ($type, $list, $oldlist) = @$r;
    $$list =    join(" ", sort(keys(%{$src_files{$type}})));
    $$oldlist = join(" ", sort(keys(%{$src_files{"old$type"}})));
  }

  # Perform substitutions.
  $_ = $src_text;
  s{\@name\@}{$name}g;
  s{\@date\@}{$date}g;
  s{\@year\@}{$year}g;
  s{\@version\@}{$version}g;
  s{\@prefix\@}{$prefix}g;
  s{\@texdir\@}{$texdir}g;
  s{\@picdir\@}{$picdir}g;
  s{\@docdir\@}{$docdir}g;
  s{\@basedir\@}{$basedir}g;
  s{\@srcdir\@}{$srcdir}g;
  s{\@srcpicdir\@}{$srcpicdir}g;
  s{\@builddir\@}{$builddir}g;
  s{\@install_cmd\@}{$install_cmd}g;
  s{\@perl\@}{$perl}g;
  s{\@mktexlsr\@}{$mktexlsr}g;
  s{\@build_target\@}{$build_target}g;
  s{\@install_target\@}{$install_target}g;
  s{\@dtx_files\@}{$dtx_files}g;
  s{\@sty_files\@}{$sty_files}g;
  s{\@tex_files\@}{$tex_files}g;
  s{\@logos\@}{$logo_files}g;
  s{\@old_dtx_files\@}{$old_dtx_files}g;
  s{\@old_sty_files\@}{$old_sty_files}g;
  s{\@old_tex_files\@}{$old_tex_files}g;
  s{\@old_logos\@}{$old_logo_files}g;
  my $dest_text = $_;

  # Write substituted text and auto-generated rules into target.
  open($fh, ">", "$builddir/Makefile");
  print $fh $dest_text . join("\n", @depend_rules) . "\n";
  close($fh);
  squawk(1, "Generated $builddir/Makefile\n");
}


# TODO: use Pod::Usage
sub usage() {
  squawk(0, << "EOF");

Syntax and options:

$0 [options]

Options:
--prefix=PREFIX         install prefix [$default_prefix]
--texdir=TEXDIR         TeX files install directory [$default_texdir]
--picdir=PICDIR         picture/logos install directory [same as texdir]
--docdir=DOCDIR         examples install directory [$default_docdir]
--basedir=BASEDIR       source base directory [$default_basedir]
--srcdir=SRCDIR         source actual directory [BASEDIR/$default_srcdir]
--srcpicdir=SRCPICDIR   picture/logos source [BASEDIR/$default_srcpicdir]
--builddir=BUILDDIR     build directory  [BASEDIR/$default_builddir]
--with-install=<cmd>    BSD-like install command [$default_install_cmd]
--with-perl=<cmd>       Perl interpreter [$default_perl]
--with-kpse=<cmd>       kpsexpand command [$default_kpse]
--with-mktexlsr=<cmd>   mktexlsr command [$default_mktexlsr]
--without-kpse          do not query kpathsea for local TeX directory
--without-mktexlsr      do not execute mktexlsr after install
--without-doc           do not install documentation
--without-old           do not install older versions
--verbose, -v           increase verbosity
--quiet, -q             decrease verbosity

TEXDIR, PICDIR, etc. may be given as absolute paths (beginning with "/")
or relative to PREFIX.  They will be created by "make install" if necessary.
EOF
}


#
# check_options(): check options values, exit if there is a big problem.
#
sub check_options() {
  my $errno = undef;
  exit($errno) if defined($errno);
}

#
# print_options(): print a summary of options values.
#
sub print_options() {
  squawk(0, "Using options:\n");
  squawk(0, "  prefix        " . (defined($prefix)? $prefix : "N/A") . "\n");
  squawk(0, "  texdir        $texdir\n");
  squawk(0, "  picdir        $picdir\n");
  squawk(0, "  docdir        " . (($disable_doc)? "N/A" : $docdir) . "\n");
  squawk(0, << "EOF");
  basedir       $basedir
  srcdir        $srcdir
  srcpicdir     $srcpicdir
  builddir      $builddir
  install       $install_cmd
  perl          $perl
  verbosity     $verbose
EOF
}

#
# parse_options(): parse command line, fill in options and flags,
# print usage if required, and check consistency with check_options().
#
sub parse_options() {
  my $print_usage = 0;
  my $quiet = 0;
  my $kpse;
  my %optstring = ('prefix=s' => \$prefix,
		   'texdir=s' => \$texdir,
		   'picdir=s' => \$picdir,
		   'docdir=s' => \$docdir,
		   'basedir=s' => \$basedir,
		   'srcdir=s' => \$srcdir,
		   'srcpicdir=s' => \$srcpicdir,
		   'builddir=s' => \$builddir,
		   'with-install=s' => \$install_cmd,
		   'with-perl=s' => \$perl,
		   'with-kpse=s' => \$kpse,
		   'with-mktexlsr=s' => \$mktexlsr,
		   'without-kpse' => sub { $disable_kpse = 1; },
		   'without-mktexlsr' => sub { $disable_mktexlsr = 1; },
		   'without-doc' => sub { $disable_doc = 1; },
		   'without-old' => sub { $disable_old = 1; },
		   'help|h|?' => sub { $print_usage = 1; die('!FINISH'); },
                   'verbose|v+' => \$verbose,
                   'quiet|q+' => \$quiet
		  );
  my $result = GetOptions(%optstring);
  if (! $result) {
    # Could be a usage request.
    $print_usage = 1 unless ($print_usage);
  }
  if ($print_usage > 0) {
    usage();
    exit($result);
  }

  #
  # Interpret options, fill in default values.
  #

  # Straightforward options.
  $verbose -= $quiet;
  $install_cmd = $default_install_cmd unless defined($install_cmd);
  $mktexlsr = $default_mktexlsr unless (defined($mktexlsr));

  # Check perl version.
  $perl = $default_perl unless defined($perl);
  my $perl_version = `$perl -e 'print \$];'`;
  die("Perl interpreter '$perl' is version $perl_version,"
      . " but $perl_version_required is required.\nPlease use --with-perl=...")
    unless ($perl_version ge $perl_version_required);

  # Determine all source and build directories, make them absolute.
  $basedir = $default_basedir unless defined($basedir);
  $basedir = "$cwd/$basedir" unless ($basedir =~ m{^/});
  $srcdir = $default_srcdir unless defined($srcdir);
  $srcdir = "$basedir/$srcdir" unless ($srcdir =~ m{^/});
  $srcpicdir = $default_srcpicdir unless defined($srcpicdir);
  $srcpicdir = "$basedir/$srcpicdir" unless ($srcpicdir =~ m{^/});
  $builddir = $default_builddir unless defined($builddir);
  $builddir = "$basedir/$builddir" unless ($builddir =~ m{^/});

  # If TEXDIR, PICDIR, etc. are all absolute, no need for PREFIX.
  my $all_paths_absolute = 0;
  if (defined($texdir) && ($texdir =~ m{^/})
      && defined($picdir) && ($picdir =~ m{^/})
      && ($disable_doc || (defined($docdir) && ($docdir =~ m{^/})))) {
    squawk(1, "Paths all absolute, PREFIX ignored.\n");
    $all_paths_absolute = 1;
  }

  # If neither absolute install directory nor prefix is specified, and
  # querying Kpathsea is allowed, deduce install directory from `$kpse`.
  if (defined($texdir) && ($texdir =~ m{^/})) {
    squawk(1, "--texdir specified and absolute, kpsexpand ignored.\n")
      if (defined($kpse));
  } elsif (defined($prefix)) {
    squawk(1, "--prefix specified, kpsexpand ignored.\n")
      if (defined($kpse));
  } elsif (! $disable_kpse) {
    # Attempt to query Kpathsea.
    my ($kpse_result, $kpse_retval);
    $kpse = $default_kpse unless defined($kpse);
    squawk(1, "Querying Kpathsea: \"$kpse\"... ");
    if ($kpse) {
      chomp($kpse_result = `$kpse`);
      $kpse_retval = $?;
    }
    if ($kpse_result && defined($kpse_retval) && ($kpse_retval == 0)) {
      squawk(1, "$kpse_result\n");
      $prefix = $kpse_result;
    } else {
      squawk(1, "failed, reverting to default prefix.\n");
    }
  }

  # Now we have all the data, actually determine install directories.
  if ($all_paths_absolute) {
    $prefix = undef;
  } else {
    $prefix = $default_prefix unless (defined($prefix) && $prefix);
    $prefix = "$basedir/$prefix" unless ($prefix =~ m{^/});
    $texdir = $default_texdir unless defined($texdir);
    $picdir = $texdir unless defined($picdir);
    $docdir = $default_docdir unless defined($docdir);
  }

  # Print options if verbose enough, and check in all cases.
  check_options();
  print_options() if ($verbose >= 2);
}

#
# squawk(): report message if verbose.
#
sub squawk($$) {
  my ($loglevel, $text) = (@_);
  print STDERR $text if ($verbose >= $loglevel);
}

