#!/usr/bin/env perl

use strict;
use warnings;
use utf8;
use Getopt::Long;
use Data::Dumper;
use Text::Amuse::Compile;
use File::Path qw/mkpath/;
use File::Spec::Functions qw/catfile/;
use Pod::Usage;
use Text::Amuse::Compile::Utils qw/append_file/;
use Text::Amuse::Compile::TemplateOptions;
use Text::Amuse::Compile::Templates;
use Text::Amuse::Compile::Fonts;
use Path::Tiny;
use Encode;

binmode STDOUT, ':encoding(UTF-8)';
binmode STDERR, ':encoding(UTF-8)';

my %options;
GetOptions (\%options,
            qw/epub
               html
               bare-html
               a4-pdf
               lt-pdf
               tex
               sl-tex
               slides
               pdf
               zip
               ttdir=s
               fontspec=s
               output-templates
               log=s
               extra=s%
               no-cleanup
               recursive=s
               dry-run
               luatex
               version
               verbose
               coverpage-only-if-toc
               include-path=s@
               purge
               run-timeout=i
               help/) or die "Bad option passed!\n";

if ($options{help}) {
    pod2usage({ -exitval => 'NOEXIT' });
    if ($options{verbose}) {
        Text::Amuse::Compile::TemplateOptions->show_options;
    }
    exit 2;
}
if ($options{version}) {
    print Text::Amuse::Compile->version;
    exit 0;
}


=encoding utf8

=head1 NAME

muse-compile.pl - format your muse document using Text::Amuse

=head1 SYNOPSIS

  muse-compile.pl [ options ] file1.muse [ file2.muse  , .... ]

This program uses Text::Amuse to produce usable output in HTML, EPUB,
LaTeX and PDF format.

By default, all formats will be generated. You can specify which
format you want using one or more of the following options:

=over 4

=item --html

Full HTML output.

=item --epub

Full EPUB output.

=item --bare-html

HTML body alone (wrapped in a C<div> tag)

=item --tex

LaTeX output

=item --zip

Pack the tex, the source and the html with the attachments in a zip
file.

=item --pdf

PDF output.

=item --slides | --sl-pdf

PDF output. Extension is .sl.pdf and it is a Beamer PDF. File .sl.tex
is left in place.

Please note that the muse file must say that the slides are required.
The header for that is C<#slides> set to a value other than empty,
C<0>, C<no>, C<false>.

=item --sl-tex

Produce the beamer LaTeX file, using the same rule above.

=item --a4-pdf

PDF imposed on A4 paper, with a variable signature in the range of 40-80

=item --lt-pdf

As above, but on Letter paper.

=item --ttdir

The directory with the templates. Optional and somehow discouraged for
normal usage.

=item --include-path path/to/directory

Repeatable. Directory where to look for included files.

=item --fontspec fontspec.json

A specification JSON file with the list of fonts. This permits you to
use arbitrary fonts. To create one with the default list, you can use
the command L<muse-create-font-file.pl>, generate one, then pass it to
this script together with --extra mainfont="Font Name".

If not provided, the main/sans/mono font options will be used. Please
note that you can't build epub with embedded fonts without the
fontspec file (because the location of the fonts is unknown) and that
you will be bypassing any font name validation, so there is no
guarantee that they are valid. Here the script assumes that you know
what you are doing when setting a font name.

=item --output-templates

Option to populated the above directory with the built-in templates.

=item --log <file>

A file where we can append the report failures

=item --no-cleanup

Prevent the removing of the status file. This is turned on if you use
--recursive, to prevent multiple runs to compile everything again.

=item --extra key:value

This option can be repeated at will. The key/value pairs will be
passed to every template we process, regardless of the type, even if
only the built-in LaTeX template support them.

The input is assumed to be UTF-8 (if you pass non-ascii characters).
The values, before being passed to the templates, are interpreted as
L<Text::Amuse> strings. This normally doesn't have any side effects
for simple strings, while for text this has the sane behaviour to
escape special characters and to permit inline markup.

Example:

  muse-compile.pl --extra site=http://anarhija.net \
                  --extra papersize=a6 --extra division=15 --extra twoside=true \
                  --extra bcor=10mm --extra mainfont="Charis SIL" \
                  --extra sitename="Testsite" \
                  --extra siteslogan="Anticopyright" \
                  --extra logo=mylogo \
                  --extra cover=mycover.pdf \
                  --extra opening=any \
                  file.muse

See muse-compile.pl --help --verbose for the full description of
meaning of these options.

=item --recursive <directory>

Using this options, the target directory and a recursive compiling is
started, finding all the .muse files without a newer status file, and
compiling them accordingly to the options.

No target files can be specified.

=item --run-timeout INTEGER

Set the latex/xindy max time execution in seconds.

=item --dry-run

For recursive compile, you can pass this option to just list the files
which would be compiled.

=item --luatex

Use lualatex instead of xelatex.

=item --purge

Purge old files before compiling. Not supported for recursive compilation.

=item --coverpage-only-if-toc

Create a coverpage only if there is a table of contents, otherwise use
an article class.

=back

=cut

my %args;

my $output_templates = delete $options{'output-templates'};
my $logfile = delete $options{log};
my $purge = delete $options{purge};

if ($options{extra}) {
    my $extras = delete $options{extra};
    foreach my $k (keys %$extras) {
        $extras->{$k} = decode('utf-8', $extras->{$k});
    }
    # also handle the booleans here
    foreach my $boolean (qw/oneside twoside nocoverpage
                            notoc/) {
        if (exists $extras->{$boolean}) {
            if ($extras->{$boolean} and
                $extras->{$boolean} !~ m/^(no|false)$/i) {
                $extras->{$boolean} = 1;
            }
            else {
                $extras->{$boolean} = 0;
            }
        }
    }
    $args{extra} = $extras;
}

# manage some dependencies

if ($options{zip}) {
    $options{tex} = $options{html} = 1;
}

if ($options{pdf}) {
    $options{tex} = 1;
}
if ($options{slides}) {
    $options{'sl-pdf'} = delete $options{slides};
}

my $recursive  = delete $options{recursive};
my $cleanup = 1;
my $dry_run = delete $options{'dry-run'};

if ($dry_run && !$recursive) {
    die "dry-run is supported only for recursive compile\n";
}


if (delete($options{'no-cleanup'}) || $recursive) {
    $cleanup = 0;
}

foreach my $k (keys %options) {
    my $newk = $k;
    $newk =~ s/-/_/g;
    $args{$newk} = $options{$k};
}


if ($output_templates and exists $options{ttdir}) {
    if (! -d $options{ttdir}) {
        mkpath($options{ttdir}) or die "Couldn't create $options{ttdir} $!";
    }
}

# at the CLI, we don't validate the font names if no fontspec is
# passed. However, epub will not be able to embed the fonts, as their
# location is unknown and for that you do need a fontspec file.

if ($args{fontspec}) {
    $args{fontspec} = path($args{fontspec})->absolute->stringify;
}
else {
    my @list = @{Text::Amuse::Compile::Fonts->default_font_list};
    my %map = (
               mainfont => 'serif',
               sansfont => 'sans',
               monofont => 'mono',
              );
    foreach my $font (qw/mainfont sansfont monofont/) {
        if (my $name = $args{extra}{$font}) {
            my $type = $map{$font} or die;
            unless (grep { $_->{name} eq $name && $_->{type} eq $type } @list) {
                print "$type font $name is unknown, trying to use it anyway\n";
                push @list, {
                             name => $name,
                             desc => $name,
                             type => $type,
                            };
            }
        }
    }
    $args{fontspec} = \@list;
}

if ($args{include_path}) {
    $args{include_paths} = [ map { path($_)->absolute->stringify } @{delete $args{include_path}} ];
}


my $compiler = Text::Amuse::Compile->new(%args, cleanup => $cleanup);

$compiler->report_failure_sub(sub {
                                  print "Failure to compile $_[0]\n";
                              });

if ($logfile) {
    if ($logfile !~ m/\.log$/) {
        warn "Appending log to $logfile\n";
    }
    print "Logging output in $logfile\n";
    $compiler->logger(sub { print @_; append_file($logfile, @_ ) });
}

print $compiler->version;

if ($output_templates) {
    my $viewdir = $compiler->ttdir;
    my $templates = Text::Amuse::Compile::Templates->new(ttdir => $viewdir);
    if (defined $viewdir) {
        foreach my $template ($templates->names) {
            my $target = catfile($viewdir, $template . '.tt');
            if (-f $target) {
                warn "Refusing to overwrite $target\n";
            }
            else {
                warn "Creating $target\n";
                open (my $fh, '>:encoding(utf-8)', $target)
                  or die "Couldn't open $target $!";
                print $fh ${ $templates->$template };
                close $fh or die "Couldn't close $target $!";
            }
        }
    }
    else {
        warn "You didn't specify a directory for the templates! Ignoring\n";
    }
}

if ($recursive) {
    die "Too many arguments passed with compile!" if @ARGV;
    die "$recursive is not a directory" unless -d $recursive;
    print "Starting recursive compilation against $recursive\n";
    my @results;
    if ($dry_run) {
        @results = $compiler->find_new_muse_files($recursive);
        print "[dry-run mode, nothing will be done]\n";
    }
    else {
        @results = $compiler->recursive_compile($recursive);
    }
    if (@results) {
        print "Found and compiled the following files:\n"
          . join("\n", @results) . "\n";
    }
    else {
        print "Nothing to do\n";
    }
}
else {
    $compiler->purge(@ARGV) if $purge;
    $compiler->compile(@ARGV);
}

if (@{$compiler->errors}) {
    $logfile ||= "above";
    die "Compilation finished with errors, see $logfile!\n";
}