# (c) 2005 Ian Bicking and contributors; written for Paste
# (http://pythonpaste.org) Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php

import re
import sys
import os

from pyramid.compat import native_, bytes_

from pyramid.scaffolds import copydir

fsenc = sys.getfilesystemencoding()


class Template(object):
    """ Inherit from this base class and override methods to use the Pyramid
    scaffolding system."""

    copydir = copydir  # for testing
    _template_dir = None

    def __init__(self, name):
        self.name = name

    def render_template(self, content, vars, filename=None):
        """ Return a bytestring representing a templated file based on the
        input (content) and the variable names defined (vars).  ``filename``
        is used for exception reporting."""
        # this method must not be named "template_renderer" fbo of extension
        # scaffolds that need to work under pyramid 1.2 and 1.3, and which
        # need to do "template_renderer =
        # staticmethod(paste_script_template_renderer)"
        content = native_(content, fsenc)
        try:
            return bytes_(
                substitute_escaped_double_braces(
                    substitute_double_braces(content, TypeMapper(vars))
                ),
                fsenc,
            )
        except Exception as e:
            _add_except(e, ' in file %s' % filename)
            raise

    def module_dir(self):
        mod = sys.modules[self.__class__.__module__]
        return os.path.dirname(mod.__file__)

    def template_dir(self):
        """ Return the template directory of the scaffold.  By default, it
        returns the value of ``os.path.join(self.module_dir(),
        self._template_dir)`` (``self.module_dir()`` returns the module in
        which your subclass has been defined).  If ``self._template_dir`` is
        a tuple this method just returns the value instead of trying to
        construct a path.  If _template_dir is a tuple, it should be a
        2-element tuple: ``(package_name, package_relative_path)``."""
        assert self._template_dir is not None, (
            "Template %r didn't set _template_dir" % self
        )
        if isinstance(self._template_dir, tuple):
            return self._template_dir
        else:
            return os.path.join(self.module_dir(), self._template_dir)

    def run(self, command, output_dir, vars):
        self.pre(command, output_dir, vars)
        self.write_files(command, output_dir, vars)
        self.post(command, output_dir, vars)

    def pre(self, command, output_dir, vars):  # pragma: no cover
        """
        Called before template is applied.
        """
        pass

    def post(self, command, output_dir, vars):  # pragma: no cover
        """
        Called after template is applied.
        """
        pass

    def write_files(self, command, output_dir, vars):
        template_dir = self.template_dir()
        if not self.exists(output_dir):
            self.out("Creating directory %s" % output_dir)
            if not command.args.simulate:
                # Don't let copydir create this top-level directory,
                # since copydir will svn add it sometimes:
                self.makedirs(output_dir)
        self.copydir.copy_dir(
            template_dir,
            output_dir,
            vars,
            verbosity=command.verbosity,
            simulate=command.args.simulate,
            interactive=command.args.interactive,
            overwrite=command.args.overwrite,
            indent=1,
            template_renderer=self.render_template,
        )

    def makedirs(self, dir):  # pragma: no cover
        return os.makedirs(dir)

    def exists(self, path):  # pragma: no cover
        return os.path.exists(path)

    def out(self, msg):  # pragma: no cover
        print(msg)

    # hair for exit with usage when paster create is used under 1.3 instead
    # of pcreate for extension scaffolds which need to support multiple
    # versions of pyramid; the check_vars method is called by pastescript
    # only as the result of "paster create"; pyramid doesn't use it.  the
    # required_templates tuple is required to allow it to get as far as
    # calling check_vars.
    required_templates = ()

    def check_vars(self, vars, other):
        raise RuntimeError(
            'Under Pyramid 1.3, you should use the "pcreate" command rather '
            'than "paster create"'
        )


class TypeMapper(dict):
    def __getitem__(self, item):
        options = item.split('|')
        for op in options[:-1]:
            try:
                value = eval_with_catch(op, dict(self.items()))
                break
            except (NameError, KeyError):
                pass
        else:
            value = eval(options[-1], dict(self.items()))
        if value is None:
            return ''
        else:
            return str(value)


def eval_with_catch(expr, vars):
    try:
        return eval(expr, vars)
    except Exception as e:
        _add_except(e, 'in expression %r' % expr)
        raise


double_brace_pattern = re.compile(r'{{(?P<braced>.*?)}}')


def substitute_double_braces(content, values):
    def double_bracerepl(match):
        value = match.group('braced').strip()
        return values[value]

    return double_brace_pattern.sub(double_bracerepl, content)


escaped_double_brace_pattern = re.compile(
    r'\\{\\{(?P<escape_braced>[^\\]*?)\\}\\}'
)


def substitute_escaped_double_braces(content):
    def escaped_double_bracerepl(match):
        value = match.group('escape_braced').strip()
        return "{{%(value)s}}" % locals()

    return escaped_double_brace_pattern.sub(escaped_double_bracerepl, content)


def _add_except(exc, info):  # pragma: no cover
    if not hasattr(exc, 'args') or exc.args is None:
        return
    args = list(exc.args)
    if args:
        args[0] += ' ' + info
    else:
        args = [info]
    exc.args = tuple(args)
    return
