#!/usr/bin/perl -w

#   Copyright (C) 2005-2014 Tobias Leupold
#
#   serienbrief - Easy creation of form letters written in LaTeX
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation in version 2
#   of the license.
#
#   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 General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, write to the Free Software Foundation, Inc.,
#   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

use strict;

use Getopt::Long;
use Term::ANSIColor;
$Term::ANSIColor::AUTORESET = 1;

use Locale::Messages qw(:locale_h);
use POSIX qw(setlocale LC_MESSAGES);
setlocale(LC_MESSAGES, '');
textdomain('serienbrief');

my $title = 'serienbrief 0.2.5';
my %cmdArgs;

# Colored text output
sub cprint
{
    my $color = shift;
    my $text = shift;

    if (! $cmdArgs{'nocolor'}) {
        print colored [$color], $text;
    } else {
        print $text;
    }

    my $remain;
    print $remain while $remain = shift;
}

# Wipe all temporary files
sub cleanUp
{
    return system("rm $cmdArgs{'texfile'}_serienbrief_work_$$\*");
}

# Give a visual feedback about trying to clean up
sub tryCleanup
{
    print '>>> ', gettext('Trying to clean up temporary files ...'), ' ';
    if (cleanUp != 0) {
        print sprintf(gettext('failed: %1$s'), $!), "\n";
    } else {
        print gettext('finished.'), "\n";
    }
}

# Catch signal
$SIG{'INT'} = 'myBreak';
sub myBreak
{
    print "\n";
    cprint 'red bold', ' * ', gettext('Caught Interrupt. Aborting.'), "\n";
    tryCleanup;
    exit 1;
}

# Abort the program
sub myDie
{
    my $error = shift;
    cprint 'red bold', ' * ', $error;
    print gettext('Aborting.'), "\n";
    exit 1;
}

# Display a help message
sub displayHelpMessage
{
    my $option = shift;
    my $helpText = shift;

    print " " x 20;
    print $helpText;
    print "\r  $option\n";
}

# Default values
$cmdArgs{'delimiter'}  = '**';
$cmdArgs{'outdir'}     = 'serienbrief';
$cmdArgs{'latex'}      = 'pdflatex -interaction=nonstopmode';
$cmdArgs{'repeat'}     = 0;
$cmdArgs{'outext'}     = 'pdf';
$cmdArgs{'outpattern'} = '[_filename]-[_number]';
$cmdArgs{'pretend'}    = '';
$cmdArgs{'force'}      = '';
$cmdArgs{'help'}       = '';

GetOptions(
    \%cmdArgs,
    'delimiter|d=s',
    'outdir|o=s',
    'latex|l=s',
    'repeat|r=i',
    'outext|e=s',
    'outpattern|a=s',
    'pretend|p',
    'force|f',
    'nocolor',
    'help|?'
);

if ($#ARGV != 1 and ! $cmdArgs{'help'}) {
    printf gettext('Usage: %1$s [options] <LaTeX source file> <database file>'), $0;
    print "\n";
    exit 1;
}

# Output title
print "\n"; cprint 'white bold', $title, "\n\n";

# Help message
if ($cmdArgs{'help'}) {
    printf gettext('Usage: %1$s [options] <LaTeX source file> <database file>'), $0;
    print "\n\n";
    print gettext('The following options can be set:'); print "\n\n";

    displayHelpMessage
        '--delimiter, -d',
        sprintf(
            gettext('Delimiter before and after form letter fields (Default: "%1$s")'),
            $cmdArgs{'delimiter'}
        );
    displayHelpMessage
        '--outdir, -o',
        sprintf(
            gettext('Directory to store the output files (Default: "%1$s")'),
            $cmdArgs{'outdir'}
        );
    displayHelpMessage
        '--outpattern, -a',
        sprintf(
            gettext('Pattern for output file names (Default: "%1$s")'), $cmdArgs{'outpattern'}
        );
    displayHelpMessage
        '--latex, -l',
        sprintf(gettext('LaTeX command (Default: "%1$s")'), $cmdArgs{'latex'});
    displayHelpMessage
        '--repeat, -r',
        gettext('Repeat the LaTeX run x times (Default: 0)');
    displayHelpMessage
        '--outext, -e',
        sprintf(
            gettext('File extension of the output files (Default: "%1$s")'),
            $cmdArgs{'outext'}
        );
    displayHelpMessage
        '--pretend, -p',
        gettext('Don\'t actually create the letters');
    displayHelpMessage
        '--force, -f',
        gettext('Ignore all errors');
    displayHelpMessage
        '--nocolor',
        gettext('Don\'t use colored output');
    displayHelpMessage
        '--help, -?',
        gettext('Display this help message');

    print "\n";
    exit 0;
}

$cmdArgs{'dataset'} = $ARGV[1];
$cmdArgs{'texfile'} = $ARGV[0];

# Check the parameters
if (! $cmdArgs{'pretend'}) {
    if (! -d $cmdArgs{'outdir'}) {
        mkdir "$cmdArgs{'outdir'}"
        or myDie sprintf(gettext('Could not create output directory "%1$s":') . " $!\n", $cmdArgs{'outdir'});
    }
}

my $error = `$cmdArgs{'latex'} --version 2>/dev/stdout`;

myDie sprintf(gettext('Could not run LaTeX with "%1$s":') . " $error", $cmdArgs{'latex'})
    if $?;

myDie sprintf(gettext('Could not open database file "%1$s"'), $cmdArgs{'dataset'})
    if ! -f $cmdArgs{'dataset'};

myDie sprintf(gettext('Could not open LaTeX file "%1$s"'), $cmdArgs{'texfile'})
    if ! -f $cmdArgs{'texfile'};

#################
# Program start #
#################

cprint 'green bold', ' * ', gettext('Creating serial letter based on:'), "\n";
print " " x 5, gettext('LaTeX file   :'), " $cmdArgs{'texfile'}\n";
print " " x 5, gettext('Database file:'), " $cmdArgs{'dataset'}\n\n";

print '>>> ', gettext('Reading records ...'), ' ';

my $dz;
open $dz, '<:crlf', $cmdArgs{'dataset'};

my $dataSets = 0;
my @indices;
my @data;

# Get indices
$_ = <$dz>;
s/\t+/\t/g;
chomp;
@indices = split "\t";

# Get database entries
while (<$dz>) {
    s/\t+/\t/g;
    chomp;
    next if ! $_;
    $data[$dataSets] = $_;
    $dataSets++;
}

close $dz;

my $tp;
$tp = $#data + 1;

printf gettext('found %1$s records'), $tp, "\n";

my $i;
my $entry;
my @dataSet;
my @texData;
my @temp;

# Load LaTeX file
open $dz, $cmdArgs{'texfile'};
@texData = <$dz>;
close $dz;

# Check all form letter fields
my %fieldCheck;
my $index;
my $occurrence;

# Cols from the database
$fieldCheck{$_} = 1 foreach (@indices);

# Fields from the LaTeX file
my @strike;
foreach (@texData) {
    @strike = /\Q$cmdArgs{'delimiter'}\E(.*?)\Q$cmdArgs{'delimiter'}\E/g;

    foreach (@strike) {
        if (exists($fieldCheck{$_})) {
            $fieldCheck{$_} = 0;
        } else {
            $fieldCheck{$_} = -1;
        }
    }

    @strike = 0;
}

my $checkError = 0;
foreach (values(%fieldCheck)) {
    if ($_ != 0) {
        $checkError = 1;
        last;
    }
}

if ($checkError) {
    $checkError = 1;

    foreach (values(%fieldCheck)) {
        $checkError = 0 if $_ != 1;
    }

    myDie sprintf(gettext('No form letter fields were found in "%1$s"'), $cmdArgs{'texfile'}) . "\n"
        if $checkError;

    print "\n";
    cprint 'yellow bold', ' * ',
        gettext('There are differences between the LaTeX file and the dataset file:'), "\n";

    while (($index, $occurrence) = each(%fieldCheck)) {
        next if $occurrence == 0;

        print " " x 5, "\"$index\" ";

        if ($occurrence == 1) {
            print gettext('is found in the dataset file but not in the LaTeX document'), "\n";
        } else {
            print gettext('is found in the LaTeX document but not in the dataset file'), "\n";
        }
    }

    if (! $cmdArgs{'force'}) {
        print "\n", gettext('Should I proceed anyway (y/n)?'), ' ';
        chomp(my $res = <STDIN>);
        $res =~ tr/A-Z/a-z/;

        if ($res ne gettext('y')) {
            print gettext('Aborting.'), "\n";
            exit 1;
        }
    } else {
        print "\n";
        cprint 'yellow bold', ' * ', gettext('Proceeding anyway'), "\n";
    }
}

print "\n";
cprint 'green bold', ' * ', gettext('The following form letter fields will be processed:'), "\n";

while (($index, $occurrence) = each(%fieldCheck)) {
    print " " x 5, "$index\n" if $occurrence == 0;
}

# Check the LaTeX source file

my $tmpFile = "$cmdArgs{'texfile'}_serienbrief_work_$$.0.tex";

print "\n>>> ", gettext('Checking the LaTeX source file ...'), "\n";

myDie sprintf(gettext('Could not copy "%1$s" to "%2$s":'), $cmdArgs{'texfile'}, $tmpFile) . " $!\n"
    if system("cp $cmdArgs{'texfile'} $tmpFile") != 0;

if (system("($cmdArgs{'latex'} $tmpFile &>/dev/null)") != 0) {
    print "\n";
    cprint 'red bold', ' * ', gettext('The following errors occured:'), "\n";

    my $log = $tmpFile;
    $log =~ s/\.tex$/\.log/;

    system("grep \"Error\" $log");

    if (! $cmdArgs{'force'}) {
        print "\n";
        cprint 'red bold', ' * ', gettext('Use --force to proceed anyway'), "\n";
        print gettext('Aborting.'), "\n";
        exit 1;
    } else {
        print "\n";
        cprint 'yellow bold', ' * ', gettext('Proceeding anyway ...'), "\n\n";
    }
} else {
    cprint 'green bold', ' * ', gettext('No errors found'), "\n\n";
}

# Clean up all files created by the test process
myDie gettext('Could not delete temporary files:') . " $!\n"
    if cleanUp != 0;

# Create form letter

if (-e $tmpFile) {
    if ($cmdArgs{'force'}) {
        cprint 'yellow bold', ' * ',
            sprintf(gettext('The temporary file "%1$s" already exists'), $tmpFile), "\n";
        cprint 'yellow bold', ' * ', gettext('Proceeding anyway'), "\n\n";
    } else {
        cprint 'red bold', ' * ',
            sprintf(gettext('The temporary file "%1$s" already exists'), $tmpFile), "\n";
        myDie gettext('I won\'t overwrite this file.') .
            gettext('Use --force to proceed anyway.') . "\n";
    }
}

my $strippedInputName = "$cmdArgs{'texfile'}";
$strippedInputName  =~ s/.tex//;

my $count = $#data + 1;

cprint 'yellow bold', ' * ',
    gettext('No form letters will be created as the --pretend option is set!'), "\n\n"
    if $cmdArgs{'pretend'};

my $outputNoFormat = '%0' . length($#data) . 'd';
my $outputFileName;
my $outputNumber;
my $extender;
my $checkedOutputFileName;

my $repeat;
$cmdArgs{'repeat'} = 0 if $cmdArgs{'repeat'} < 0;

# Append the two "special" entries to the indices
push (@indices, '_filename');
push (@indices, '_number');

for ($entry = 0; $entry <= $#data; $entry++) {
    $tmpFile = "$cmdArgs{'texfile'}_serienbrief_work_$$.$entry.tex";

    @dataSet = split "\t", $data[$entry];
    @temp = @texData;

    $tp = $entry + 1;
    print '>>> ', sprintf(gettext('Creating form letter %1$s of %2$s'), $tp, $count), "\n";

    open $dz, ">$tmpFile" if ! $cmdArgs{'pretend'};

    foreach (@temp) {
        for ($i = 0; $i <= $#indices; $i++) {
            s/\Q$cmdArgs{'delimiter'}\E$indices[$i]\Q$cmdArgs{'delimiter'}\E/$dataSet[$i]/g;
        }

        print $dz $_ if ! $cmdArgs{'pretend'};
    }

    if (! $cmdArgs{'pretend'}) {
        close $dz;

        for ($repeat = 0; $repeat <= $cmdArgs{'repeat'}; $repeat++) {
            myBreak if `($cmdArgs{'latex'} $tmpFile 1>/dev/null)`;
        }

        $tmpFile =~ s/\.tex$/\.$cmdArgs{'outext'}/;
        $outputNumber = sprintf($outputNoFormat, $entry + 1);

        # We start at the pattern
        my $outputFileName = $cmdArgs{'outpattern'};

        # Include the input name and consecutive number in the current data set
        push (@dataSet, $strippedInputName);
        push (@dataSet, $outputNumber);

        # Apply all possible wildcards
        for ($i = 0; $i <= $#indices; $i++) {
            $outputFileName =~ s/\Q[$indices[$i]]\E/$dataSet[$i]/g;
        }

        # Be sure that the output file does not exist
        # (we could have a collision)
        $extender = 0;
        $checkedOutputFileName = $outputFileName;
        while (-e "$cmdArgs{'outdir'}/$checkedOutputFileName.$cmdArgs{'outext'}") {
            $extender++;
            $checkedOutputFileName = $outputFileName . "_" . $extender;
        }

        $outputFileName = $checkedOutputFileName;
        $outputFileName =~ s!/!_!g;      # Be sure not to have slashes in the output file name
        $outputFileName =~ s/\Q"\E/\"/g; # Mask quotes
        $outputFileName = "$cmdArgs{'outdir'}/$outputFileName.$cmdArgs{'outext'}";

        if (system("mv \"$tmpFile\" \"$outputFileName\"") != 0) {
            cprint 'red bold', ' * ',
                sprintf(gettext('Could not move "%1$s" to "%2$s:"'), $tmpFile, $outputFileName),
                " $!\n";
            tryCleanup;
            exit 1;
        }

        # Clean up the files used for this letter before processing the next one.
        myDie gettext('Could not delete temporary files:') . " $!\n"
            if cleanUp != 0;
    }
}

print "\n";
cprint 'green bold', ' * ', gettext('Finished!'), "\n";

if (! $cmdArgs{'pretend'}) {
    cprint 'green bold', ' * ',
        sprintf(gettext('Stored %1$s files in "%2$s"'), $count, $cmdArgs{'outdir'});
} else {
    cprint 'yellow bold', ' * ',
        sprintf(
            gettext('To actually create the form letters, re-run without the --pretend option.'),
            $count, $cmdArgs{'outdir'}
        );
}

print "\n\n";
