#!/usr/bin/env python3

"""
Builds PDF files from (intermediate fo and) XML files.

This script is part of the PenText framework
                           https://pentext.org

   Copyright (C) 2015-2018 Radically Open Security
                           https://www.radicallyopensecurity.com

                Author(s): Peter Mosmans

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, either version 3 of the License, or
(at your option) any later version.

"""

from __future__ import absolute_import
from __future__ import print_function

import argparse
import os
import random
import subprocess
from subprocess import PIPE
import sys
import textwrap


GITREV = 'GITREV'  # Magic tag which gets replaced by the git short commit hash
OFFERTE = 'generate_offerte.xsl'  # XSL for generating waivers
WAIVER = 'waiver_'  # prefix for waivers
EXECSUMMARY = 'execsummary'  # generating an executive summary instead of a report


def parse_arguments():
    """
    Parses command line arguments.
    """
    global verboseprint
    global verboseerror
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent('''\
Builds PDF files from (intermediate fo and) XML files.

Copyright (C) 2015-2018  Radically Open Security (Peter Mosmans)

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, either version 3 of the License, or
(at your option) any later version.'''))
    parser.add_argument('-c', '--clobber', action='store_true',
                        help='overwrite output file if it already exists')
    parser.add_argument('-date', action='store',
                        help='the invoice date')
    parser.add_argument('--execsummary', action='store_true',
                        help="""create an executive summary as well as a report (true/false).
                        Default: false """)
    parser.add_argument('--pw', action='store_true',
                        help="""password-protect the pdf.
                        Default: false """)
    parser.add_argument('--fop-config', action='store',
                        default='/etc/docbuilder/fop.xconf',
                        help="""fop configuration file (default
                        /etc/docbuilder/fop.xconf""")
    parser.add_argument('-f', '--fop', action='store',
                        default='../target/report.fo',
                        help="""intermediate fop output file (default:
                        ../target/report.fo)""")
    parser.add_argument('--fop-binary', action='store',
                        default='/usr/local/bin/fop',
                        help='fop binary (default /usr/local/bin/fop')
    parser.add_argument('-i', '--input', action='store',
                        default='report.xml',
                        help="""input file (default: report.xml)""")
    parser.add_argument('-invoice', action='store',
                        help="""invoice number""")
    parser.add_argument('--saxon', action='store',
                        default='/usr/local/bin/saxon/saxon9he.jar',
                        help="""saxon JAR file (default
                        /usr/local/bin/saxon/saxon9he.jar)""")
    parser.add_argument('-x', '--xslt', action='store',
                        default='../xslt/generate_report.xsl',
                        help='input file (default: ../xslt/generate_report.xsl)')
    parser.add_argument('-o', '--output', action='store',
                        default='../target/report-latest.pdf',
                        help="""output file name (default:
                        ../target/report-latest.pdf""")
    parser.add_argument('--tag', action='store_true',
                        help="""Modify GITREV with git commit hash""")
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='increase output verbosity')
    parser.add_argument('-w', '--warnings', action='store_true',
                        help='show warnings')
    args = parser.parse_args()
    if args.verbose:
        def verboseprint(*args):  # pylint: disable=missing-docstring
            for arg in args:
                print(arg, end="")
            print()

        def verboseerror(*args):  # pylint: disable=missing-docstring
            for arg in args:
                print(arg, end="", file=sys.stderr)
            print(file=sys.stderr)
    else:
        verboseprint = lambda *a: None
        verboseerror = lambda *a: None
    return vars(args)


def print_output(stdout, stderr):
    """
    Prints out standard out and standard err using the verboseprint function.
    """
    if stdout:
        verboseprint('[+] stdout: {0}'.format(stdout))
    if stderr:
        verboseerror('[-] stderr: {0}'.format(stderr))


def change_tag(fop):
    """
    Replaces GITREV in document by git commit shorttag.
    """
    cmd = ['git', 'log', '--pretty=format:%h', '-n', '1']
    try:
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        shorttag, _stderr = process.communicate()
        if not process.returncode:
            fop_file = open(fop).read()
            if GITREV in fop_file:
                fop_file = fop_file.replace(GITREV, shorttag)
                with open(fop, 'w') as new_file:
                    new_file.write(fop_file)
                print('[+] Embedding git version information into document')
    except OSError:
        print('[-] could not execute git - is git installed ?')


def generate_pw():
    s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()?"
    length = 12
    p = "".join(random.sample(s, length))
    return(p)


def to_fo(options):
    """
    Creates a fo output file based on a XML file.
    Returns True if successful
    """
    cmd = ['java', '-jar', options['saxon'],
           '-s:' + options['input'], '-xsl:' + options['xslt'],
           '-o:' + options['fop'], '-xi']
    if options['invoice']:
        cmd.append('INVOICE_NO=' + options['invoice'])
    if options['date']:
        cmd.append('DATE=' + options['date'])
    if options['execsummary']:
        cmd.append('EXEC_SUMMARY=true')
    process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE)
    stdout, stderr = process.communicate()
    print_output(stdout, stderr)
    if process.returncode:
        print_exit('[-] Error creating fo file from XML input',
                   process.returncode)
    else:
        if options['tag']:
            change_tag(options['fop'])
    return True


def to_pdf(options):
    """
    Creates a PDF file based on a fo file.
    Returns True if successful
    """
    cmd = [options['fop_binary'], '-c', options['fop_config'], options['fop'],
           options['output']]
    if options['pw']:
        pw = generate_pw()
        cmd = cmd + ['-u', pw]
    try:
        verboseprint('Converting {0} to {1}'.format(options['fop'],
                                                    options['output']))
        process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE)
        stdout, stderr = process.communicate()
        result = process.returncode
        print_output(stdout, stderr)
        if result == 0:
            print('[+] Succesfully built ' + options['output'])
            if options['pw']:
                print('\n[+] Password for this pdf is ' + pw)
    except OSError as exception:
        print_exit('[-] ERR: {0}'.format(exception.strerror), exception.errno)
    return result == 0


def print_exit(text, result):
    """
    Prints error message and exits with result code.
    """
    print(text, file=sys.stderr)
    sys.exit(result)


def main():
    """
    The main program.
    """
    global verboseerror
    global verboseprint
    result = False
    options = parse_arguments()
    if not os.path.isfile(options['input']):
        print_exit('[-] Cannot find input file {0}'.
                   format(options['input']), result)
    try:
        if os.path.isfile(options['output']):
            if not options['clobber']:
                print_exit('[-] Output file {0} already exists. '.
                           format(options['output']) +
                           'Use -c (clobber) to overwrite',
                           result)
            os.remove(options['output'])
    except OSError as exception:
        print_exit('[-] Could not remove/overwrite file {0} ({1})'.
                   format(options['output'], exception.strerror), result)
    result = to_fo(options)
    if result:
        if OFFERTE in options['xslt']:  # an offerte can generate multiple fo's
            report_output = options['output']
            verboseprint('generating separate waivers detected')
            output_dir = os.path.dirname(options['output'])
            fop_dir = os.path.dirname(options['fop'])
            try:
                for fop in [os.path.splitext(x)[0] for x in
                            os.listdir(fop_dir) if x.endswith('fo')]:
                    if WAIVER in fop:
                        options['output'] = output_dir + os.sep + fop + '.pdf'
                    else:
                        options['output'] = report_output
                    options['fop'] = fop_dir + os.sep + fop + '.fo'
                    result = to_pdf(options) and result
            except OSError as exception:
                print_exit('[-] ERR: {0}'.format(exception.strerror),
                           exception.errno)
        if options['execsummary']:  # we're generating a summary as well as a report
            report_output = options['output']
            verboseprint('generating additional executive summary')
            output_dir = os.path.dirname(options['output'])
            fop_dir = os.path.dirname(options['fop'])
            try:
                for fop in [os.path.splitext(x)[0] for x in
                            os.listdir(fop_dir) if x.endswith('fo')]:
                    if EXECSUMMARY in fop:
                        options['output'] = output_dir + os.sep + fop + '.pdf'
                    else:
                        options['output'] = report_output
                    options['fop'] = fop_dir + os.sep + fop + '.fo'
                    result = to_pdf(options) and result
            except OSError as exception:
                print_exit('[-] ERR: {0}'.format(exception.strerror),
                           exception.errno)
        else:
            result = to_pdf(options)

    else:
        print_exit('[-] Unsuccessful (error {0})'.format(result), result)
    sys.exit(not result)


if __name__ == "__main__":
    main()
