#!/bin/python

from __future__ import print_function

import os
import sys
import tempfile
import shutil

from argparse import ArgumentParser, RawDescriptionHelpFormatter

import six

import logging
logging.basicConfig(format='dg: %(levelname)-8s: %(message)s')

from distgen.generator import Generator
from distgen.commands import CommandsConfig
from distgen.multispec import Multispec
from distgen.version import dg_version
from distgen.distro_version import detect_default_distro
from distgen.err import fatal


description = \
"""
Generate script using predefined metadata about distribution and
templates.

As an example of 'dg' usage, to generate _Dockerfile_ for Fedora
21 64-bit system, you may use command(s):

 $ cd project/directory
 $ dg --spec      docker-data.yaml      \\
      --template  docker.tpl
"""

parser = ArgumentParser(
    description=description,
    formatter_class=RawDescriptionHelpFormatter,
)

# Solely for the purpose of manpage generator
parser.man_short_description = "templating system/generator for distributions"

parser.add_argument(
    '--version',
    action='version',
    version="dg (distgen) {0}".format(dg_version)
)

parser.add_argument(
    '--projectdir',
    metavar='PROJECTDIR',
    type=str,
    help='Directory with project (defaults to CWD)',
    default="."
)

parser.add_argument(
    '--distro',
    metavar='DIST',
    type=str,
    help='Use DIST distribution configuration.  Either that DIST.yaml needs '
         'to exist in the distgen installation, or it can be any file '
         'within PROJECTDIR (relative or absolute file name).',
)

parser.add_argument(
    '--multispec',
    metavar='MULTISPEC',
    type=str,
    help='Use MULTISPEC yaml file to fill the TEMPLATE file',
)

parser.add_argument(
    '--multispec-selector',
    metavar='MULTISPEC_SELECTOR',
    type=str,
    help='Selectors for the multispec file',
    action='append',
    default=[],
)

parser.add_argument(
    '--spec',
    metavar='SPEC',
    type=str,
    help='Use SPEC yaml file to fill the TEMPLATE file',
    action='append',
)

parser.add_argument(
    '--output',
    metavar='OUTPUT',
    type=str,
    help='Write result to OUTPUT file instead of stdout',
)

parser.add_argument(
    '--macros-from',
    metavar='PROJECTDIR',
    type=str,
    action='append',
    help='Load variables from PROJECTDIR',
)

parser.add_argument(
    '--container',
    metavar='CONTAINER_TYPE',
    type=str,
    help='Container type, e.g. \'docker\'',
    default=False,
)

parser.add_argument(
    '--macro',
    metavar='MACRO',
    type=str,
    action='append',
    help='Define distgen\'s macro',
)

parser.add_argument(
    '--max-passes',
    metavar='PASSES',
    type=int,
    default=32,
    help='Maximum number of rendering passes, defaults to 32',
)

parser.add_argument(
    '--keep-block-whitespaces',
    help="Disable the jinja2 trim_blocks and lstrip_blocks options",
    action="store_true",
)

tpl_or_combinations = parser.add_mutually_exclusive_group(required=True)

tpl_or_combinations.add_argument(
    '--template',
    metavar='TEMPLATE',
    type=str,
    help='Use TEMPLATE file, e.g. docker.tpl or a template string, '
    'e.g. "{{ config.docker.from }}"'
)

tpl_or_combinations.add_argument(
    '--multispec-combinations',
    action='store_true',
    help='Print available multispec combinations',
)

def print_multispec_combinations(args):
    ms = Multispec.from_path(args.projectdir, args.multispec)
    for c in ms.get_all_combinations():
        to_print = ['--distro {0}'.format(c.pop('distro'))]
        [to_print.append('--multispec-selector {0}={1}'.format(k, v)) for k, v in c.items()]
        print(' '.join(to_print))


def render_template(args):
    temp_filename = False
    if six.PY2:
        output = sys.stdout
    else:
        output = sys.stdout.buffer
    try:
        if args.output:
            _, temp_filename = tempfile.mkstemp(prefix="distgen-")
            output = open(temp_filename, 'wb')
    except:
        fatal("can't create temporary file for '{0}'".format(args.output))

    cmd_cfg = CommandsConfig()
    cmd_cfg.container = args.container

    explicit_macros = {}
    if args.macro:
        for i in args.macro:
            key, value = i.split(' ', 1)
            explicit_macros[key] = value

    if args.template == '-':
        args.template = "/proc/self/fd/0"

    global_jinja_args = None
    if not args.keep_block_whitespaces:
        global_jinja_args = {
            'trim_blocks': True,
            'lstrip_blocks': True,
        }

    generator = Generator(global_jinja_args=global_jinja_args)
    generator.load_project(args.projectdir)

    distro = args.distro
    if not distro:
        distro = detect_default_distro()
    if not distro:
        fatal("distribution not detected, use --distro option")

    generator.render(
        args.spec,
        args.multispec,
        args.multispec_selector,
        args.template,
        distro,
        cmd_cfg,
        output,
        args.macros_from,
        explicit_macros,
        args.max_passes,
    )

    if temp_filename:
        try:
            output.close()
            shutil.move(temp_filename, args.output)
            # there's no way in Python to get umask without setting it
            # to some value, therefore we have to get while setting
            # to 0 and then re-set to original value
            umask = os.umask(0)
            os.umask(umask)
            os.chmod(args.output, 0o666 &~umask)
        except:
            fatal("can't move '{0}' into '{1}'".format(temp_filename, args.output))


def main():
    args = parser.parse_args()
    if args.multispec_combinations:
        print_multispec_combinations(args)
    else:
        render_template(args)


if __name__ == "__main__":
    main()
