"""
Cement generate extension module.
"""

import re
import os
import inspect
import yaml
import shutil
from .. import Controller, minimal_logger, shell
from ..utils.version import VERSION, get_version

LOG = minimal_logger(__name__)


class GenerateTemplateAbstractBase(Controller):
    class Meta:
        pass

    def _generate(self, source, dest):
        msg = 'Generating %s %s in %s' % (
                   self.app._meta.label, self._meta.label, dest
               )
        self.app.log.info(msg)
        data = {}

        # builtin vars
        maj_min = float('%s.%s' % (VERSION[0], VERSION[1]))
        data['cement'] = {}
        data['cement']['version'] = get_version()
        data['cement']['major_version'] = VERSION[0]
        data['cement']['minor_version'] = VERSION[1]
        data['cement']['major_minor_version'] = maj_min

        f = open(os.path.join(source, '.generate.yml'))
        yaml_load = yaml.full_load if hasattr(yaml, 'full_load') else yaml.load
        g_config = yaml_load(f)
        f.close()

        vars = g_config.get('variables', {})
        exclude_list = g_config.get('exclude', [])
        ignore_list = g_config.get('ignore', [])

        # default ignore the .generate.yml config
        g_config_yml = r'^(.*)[\/\\\\]%s[\/\\\\]\.generate\.yml$' % \
                       self._meta.label
        ignore_list.append(g_config_yml)

        var_defaults = {
            'name': None,
            'prompt': None,
            'validate': None,
            'case': None,
            'default': None,
        }

        for defined_var in vars:
            var = var_defaults.copy()
            var.update(defined_var)
            for key in ['name', 'prompt']:
                assert var[key] is not None, \
                    "Required generate config key missing: %s" % key

            val = None
            if var['default'] is not None and self.app.pargs.defaults:
                val = var['default']

            elif var['default'] is not None:
                default_text = ' [%s]' % var['default']

            else:
                default_text = ''   # pragma: nocover

            if val is None:
                class MyPrompt(shell.Prompt):
                    class Meta:
                        text = "%s%s:" % (var['prompt'], default_text)
                        default = var.get('default', None)

                p = MyPrompt()
                val = p.prompt()    # pragma: nocover

            if var['case'] in ['lower', 'upper', 'title']:
                val = getattr(val, var['case'])()
            elif var['case'] is not None:
                self.app.log.warning(
                    "Invalid configuration for variable " +
                    "'%s': " % var['name'] +
                    "case must be one of lower, upper, or title."
                )

            if var['validate'] is not None:
                assert re.match(var['validate'], val), \
                    "Invalid Response (must match: '%s')" % var['validate']

            data[var['name']] = val

        try:
            self.app.template.copy(source, dest, data,
                                   force=self.app.pargs.force,
                                   ignore=ignore_list,
                                   exclude=exclude_list)
        except AssertionError as e:
            if re.match('(.*)already exists(.*)', e.args[0]):
                raise AssertionError(e.args[0] + ' (try: --force)')
            else:
                raise  # pragma: nocover

    def _clone(self, source, dest):
        msg = 'Cloning %s %s template to %s' % (
                   self.app._meta.label, self._meta.label, dest
               )
        self.app.log.info(msg)

        if os.path.exists(dest) and self.app.pargs.force is True:
            shutil.rmtree(dest)
        elif os.path.exists(dest):
            msg = "Destination path already exists: %s (try: --force)" % dest
            raise AssertionError(msg)

        shutil.copytree(source, dest)

    def _default(self):
        source = self._meta.source_path
        dest = self.app.pargs.dest

        if self.app.pargs.clone is True:
            self._clone(source, dest)
        else:
            self._generate(source, dest)


def setup_template_items(app):
    template_dirs = []
    template_items = []

    # look in app template dirs
    for path in app._meta.template_dirs:
        subpath = os.path.join(path, 'generate')
        if os.path.exists(subpath) and subpath not in template_dirs:
            template_dirs.append(subpath)

    # use app template module, find it's path on filesystem
    if app._meta.template_module is not None:
        mod_parts = app._meta.template_module.split('.')
        mod = mod_parts.pop()
        try:
            mod = app.__import__(mod, from_module='.'.join(mod_parts))
            mod_path = os.path.dirname(inspect.getfile(mod))
            subpath = os.path.join(mod_path, 'generate')

            if os.path.exists(subpath) and subpath not in template_dirs:
                template_dirs.append(subpath)

        # FIXME: not exactly sure how to test for this so not covering
        except AttributeError:                                # pragma: nocover
            msg = 'unable to load template module' + \
                  '%s from %s' % (mod, '.'.join(mod_parts))   # pragma: nocover
            app.log.debug(msg)                                # pragma: nocover

    for path in template_dirs:
        for item in os.listdir(path):
            if item not in template_items:
                template_items.append(item)

            class GenerateTemplate(GenerateTemplateAbstractBase):
                class Meta:
                    label = item
                    stacked_on = 'generate'
                    stacked_type = 'nested'
                    help = 'generate %s from template' % item
                    arguments = [
                        # ------------------------------------------------------
                        (['dest'],
                         {'help': 'destination directory path'}),
                        # ------------------------------------------------------
                        (['-f', '--force'],
                         {'help': 'force operation if destination exists',
                          'dest': 'force',
                          'action': 'store_true'}),
                        # ------------------------------------------------------
                        (['-D', '--defaults'],
                         {'help': 'use all default variable values',
                          'dest': 'defaults',
                          'action': 'store_true'}),
                        # ------------------------------------------------------
                        (['--clone'],
                         {'help': 'clone this template to destination path',
                          'dest': 'clone',
                          'action': 'store_true'}),
                    ]
                    source_path = os.path.join(path, item)

            app.handler.register(GenerateTemplate)


class Generate(Controller):
    class Meta:
        label = 'generate'
        stacked_on = 'base'
        stacked_type = 'nested'
        config_section = 'generate'

    def _setup(self, app):
        super(Generate, self)._setup(app)

    def _default(self):
        self._parser.print_help()


def load(app):
    app.handler.register(Generate)
    app.hook.register('pre_run', setup_template_items)

[((121, 8, 121, 37), 'shutil.copytree', 'shutil.copytree', ({(121, 24, 121, 30): 'source', (121, 32, 121, 36): 'dest'}, {}), '(source, dest)', False, 'import shutil\n'), ((139, 18, 139, 48), 'os.path.join', 'os.path.join', ({(139, 31, 139, 35): 'path', (139, 37, 139, 47): '"""generate"""'}, {}), "(path, 'generate')", False, 'import os\n'), ((162, 20, 162, 36), 'os.listdir', 'os.listdir', ({(162, 31, 162, 35): 'path'}, {}), '(path)', False, 'import os\n'), ((35, 17, 35, 54), 'os.path.join', 'os.path.join', ({(35, 30, 35, 36): 'source', (35, 38, 35, 53): '""".generate.yml"""'}, {}), "(source, '.generate.yml')", False, 'import os\n'), ((115, 11, 115, 31), 'os.path.exists', 'os.path.exists', ({(115, 26, 115, 30): 'dest'}, {}), '(dest)', False, 'import os\n'), ((116, 12, 116, 31), 'shutil.rmtree', 'shutil.rmtree', ({(116, 26, 116, 30): 'dest'}, {}), '(dest)', False, 'import shutil\n'), ((117, 13, 117, 33), 'os.path.exists', 'os.path.exists', ({(117, 28, 117, 32): 'dest'}, {}), '(dest)', False, 'import os\n'), ((140, 11, 140, 34), 'os.path.exists', 'os.path.exists', ({(140, 26, 140, 33): 'subpath'}, {}), '(subpath)', False, 'import os\n'), ((150, 22, 150, 56), 'os.path.join', 'os.path.join', ({(150, 35, 150, 43): 'mod_path', (150, 45, 150, 55): '"""generate"""'}, {}), "(mod_path, 'generate')", False, 'import os\n'), ((93, 23, 93, 53), 're.match', 're.match', ({(93, 32, 93, 47): "var['validate']", (93, 49, 93, 52): 'val'}, {}), "(var['validate'], val)", False, 'import re\n'), ((104, 15, 104, 60), 're.match', 're.match', ({(104, 24, 104, 48): '"""(.*)already exists(.*)"""', (104, 50, 104, 59): 'e.args[0]'}, {}), "('(.*)already exists(.*)', e.args[0])", False, 'import re\n'), ((149, 39, 149, 59), 'inspect.getfile', 'inspect.getfile', ({(149, 55, 149, 58): 'mod'}, {}), '(mod)', False, 'import inspect\n'), ((152, 15, 152, 38), 'os.path.exists', 'os.path.exists', ({(152, 30, 152, 37): 'subpath'}, {}), '(subpath)', False, 'import os\n'), ((192, 34, 192, 58), 'os.path.join', 'os.path.join', ({(192, 47, 192, 51): 'path', (192, 53, 192, 57): 'item'}, {}), '(path, item)', False, 'import os\n')]