#!/usr/bin/perl
####################################################################################################################################
# doc.pl - PgBackRest Doc Builder
####################################################################################################################################

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

$SIG{__DIE__} = sub { Carp::confess @_ };

use Cwd qw(abs_path);
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptions);
use Pod::Usage qw(pod2usage);
use Storable;

use lib dirname($0) . '/lib';
use lib dirname(dirname($0)) . '/lib';
use lib dirname(dirname($0)) . '/build/lib';
use lib dirname(dirname($0)) . '/test/lib';

use BackRestDoc::Common::Doc;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Common::DocRender;
use BackRestDoc::Html::DocHtmlSite;
use BackRestDoc::Latex::DocLatex;
use BackRestDoc::Markdown::DocMarkdown;

use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Storage::Local;
use pgBackRest::Storage::Posix::Driver;
use pgBackRest::Version;

####################################################################################################################################
# Usage
####################################################################################################################################

=head1 NAME

doc.pl - Generate pgBackRest documentation

=head1 SYNOPSIS

doc.pl [options]

 General Options:
   --help           Display usage and exit
   --version        Display pgBackRest version
   --quiet          Sets log level to ERROR
   --log-level      Log level for execution (e.g. ERROR, WARN, INFO, DEBUG)
   --deploy         Write exe.cache into resource for persistence
   --no-exe         Should commands be executed when building help? (for testing only)
   --no-cache       Don't use execution cache
   --cache-only     Only use the execution cache - don't attempt to generate it
   --var            Override variables defined in the XML
   --doc-path       Document path to render (manifest.xml should be located here)
   --out            Output types (html, pdf, markdown)
   --require        Require only certain sections of the document (to speed testing)
   --include        Include source in generation (links will reference website)
   --exclude        Exclude source from generation (links will reference website)

Keyword Options:
   --keyword        Keyword used to filter output
   --keyword-add    Add keyword without overriding 'default' keyword
   --dev            Add 'dev' keyword
   --debug          Add 'debug' keyword
   --pre            Add 'pre' keyword
=cut

####################################################################################################################################
# Load command line parameters and config (see usage above for details)
####################################################################################################################################
my $bHelp = false;
my $bVersion = false;
my $bQuiet = false;
my $strLogLevel = 'info';
my $bNoExe = false;
my $bNoCache = false;
my $bCacheOnly = false;
my $oVariableOverride = {};
my $strDocPath;
my @stryOutput;
my @stryKeyword;
my @stryKeywordAdd;
my @stryRequire;
my @stryInclude;
my @stryExclude;
my $bDeploy = false;
my $bDev = false;
my $bDebug = false;
my $bPre = false;

GetOptions ('help' => \$bHelp,
            'version' => \$bVersion,
            'quiet' => \$bQuiet,
            'log-level=s' => \$strLogLevel,
            'out=s@' => \@stryOutput,
            'keyword=s@' => \@stryKeyword,
            'keyword-add=s@' => \@stryKeywordAdd,
            'require=s@' => \@stryRequire,
            'include=s@' => \@stryInclude,
            'exclude=s@' => \@stryExclude,
            'no-exe', \$bNoExe,
            'deploy', \$bDeploy,
            'no-cache', \$bNoCache,
            'dev', \$bDev,
            'debug', \$bDebug,
            'pre', \$bPre,
            'cache-only', \$bCacheOnly,
            'var=s%', $oVariableOverride,
            'doc-path=s', \$strDocPath)
    or pod2usage(2);

####################################################################################################################################
# Run in eval block to catch errors
####################################################################################################################################
eval
{
    # Display version and exit if requested
    if ($bHelp || $bVersion)
    {
        print BACKREST_NAME . ' ' . BACKREST_VERSION . " Documentation Builder\n";

        if ($bHelp)
        {
            print "\n";
            pod2usage();
        }

        exit 0;
    }

    # Disable cache when no exe
    if ($bNoExe)
    {
        $bNoCache = true;
    }

    # Make sure options are set correctly for deploy
    if ($bDeploy)
    {
        my $strError = 'cannot be specified for deploy';

        !$bNoExe
            or confess "--no-exe ${strError}";

        !@stryRequire
            or confess "--require ${strError}";
    }

    # one --include must be specified when --required is
    if (@stryRequire && @stryInclude != 1)
    {
        confess "one --include is required when --require is specified";
    }

    # Set console log level
    if ($bQuiet)
    {
        $strLogLevel = 'error';
    }

    # If no keyword was passed then use default
    if (@stryKeyword == 0)
    {
        @stryKeyword = ('default');
    }

    # Push added keywords
    push(@stryKeyword, @stryKeywordAdd);

    # If --dev passed then add the dev keyword
    if ($bDev)
    {
        push(@stryKeyword, 'dev');
    }

    # If --debug passed then add the debug keyword
    if ($bDebug)
    {
        push(@stryKeyword, 'debug');
    }

    # If --pre passed then add the pre keyword
    if ($bPre)
    {
        push(@stryKeyword, 'pre');
    }

    # Doesn't make sense to pass include and exclude
    if (@stryInclude > 0 && @stryExclude > 0)
    {
        confess "cannot specify both --include and --exclude";
    }

    logLevelSet(undef, uc($strLogLevel), OFF);

    # Get the base path
    my $strBasePath = abs_path(dirname($0));

    my $oStorageDoc = new pgBackRest::Storage::Local(
        $strBasePath, new pgBackRest::Storage::Posix::Driver({bFileSync => false, bPathSync => false}));

    if (!defined($strDocPath))
    {
        $strDocPath = $strBasePath;
    }

    my $strOutputPath = "${strDocPath}/output";

    # Create the out path if it does not exist
    if (!-e $strOutputPath)
    {
        mkdir($strOutputPath)
            or confess &log(ERROR, "unable to create path ${strOutputPath}");
    }

    # Load the manifest
    my $oManifest = new BackRestDoc::Common::DocManifest(
        $oStorageDoc, \@stryKeyword, \@stryRequire, \@stryInclude, \@stryExclude, $oVariableOverride, $strDocPath, $bDeploy,
        $bCacheOnly);

    if (!$bNoCache)
    {
        $oManifest->cacheRead();
    }

    # If no outputs were given
    if (@stryOutput == 0)
    {
        @stryOutput = $oManifest->renderList();

        if ($oManifest->isBackRest())
        {
            push(@stryOutput, 'help');
            push(@stryOutput, 'man');
        }
    }

    for my $strOutput (@stryOutput)
    {
        if (!(($strOutput eq 'help' || $strOutput eq 'man') && $oManifest->isBackRest()))
        {
            $oManifest->renderGet($strOutput);
        }

        &log(INFO, "render ${strOutput} output");

        if ($strOutput eq 'markdown')
        {
            my $oMarkdown =
                new BackRestDoc::Markdown::DocMarkdown
                (
                    $oManifest,
                    "${strBasePath}/xml",
                    "${strOutputPath}/markdown",
                    !$bNoExe
                );

            $oMarkdown->process();
        }
        elsif (($strOutput eq 'help' || $strOutput eq 'man') && $oManifest->isBackRest())
        {
            # Generate the command-line help
            my $oRender = new BackRestDoc::Common::DocRender('text', $oManifest, !$bNoExe);
            my $oDocConfig =
                new BackRestDoc::Common::DocConfig(
                    new BackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);

            if ($strOutput eq 'help')
            {
                $oDocConfig->helpDataWrite($oManifest);
            }
            else
            {
                $oStorageDoc->pathCreate(
                    "${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true});
                $oStorageDoc->put("${strBasePath}/output/man/" . lc(BACKREST_NAME) . '.1.txt', $oDocConfig->manGet($oManifest));
            }
        }
        elsif ($strOutput eq 'html')
        {
            my $oHtmlSite =
                new BackRestDoc::Html::DocHtmlSite
                (
                    $oManifest,
                    "${strBasePath}/xml",
                    "${strOutputPath}/html",
                    "${strBasePath}/resource/html/default.css",
                    defined($oManifest->variableGet('project-favicon')) ?
                        "${strBasePath}/resource/html/" . $oManifest->variableGet('project-favicon') : undef,
                    defined($oManifest->variableGet('project-logo')) ?
                        "${strBasePath}/resource/" . $oManifest->variableGet('project-logo') : undef,
                    !$bNoExe
                );

            $oHtmlSite->process();
        }
        elsif ($strOutput eq 'pdf')
        {
            my $oLatex =
                new BackRestDoc::Latex::DocLatex
                (
                    $oManifest,
                    "${strBasePath}/xml",
                    "${strOutputPath}/latex",
                    "${strBasePath}/resource/latex/preamble.tex",
                    !$bNoExe
                );

            $oLatex->process();
        }
    }

    # Cache the manifest (mostly useful for testing rendering changes in the code)
    if (!$bNoCache && !$bCacheOnly)
    {
        $oManifest->cacheWrite();
    }

    # Exit with success
    exit 0;
}

####################################################################################################################################
# Check for errors
####################################################################################################################################
or do
{
    # If a backrest exception then return the code
    exit $EVAL_ERROR->code() if (isException(\$EVAL_ERROR));

    # Else output the unhandled error
    print $EVAL_ERROR;
    exit ERROR_UNHANDLED;
};

# It shouldn't be possible to get here
&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
exit ERROR_ASSERT;
