# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
from itertools import chain
import re

from .util import gen_name, flattener, window
from nine import nine


def generate_python(ir):
    cur_indent = 0
    for node in flattener(ir):
        if isinstance(node, IndentNode):
            cur_indent += 4
        elif isinstance(node, DedentNode):
            cur_indent -= 4
        for line in node.py():
            yield line.indent(cur_indent)


class Node(object):
    def __init__(self):
        self.filename = '<string>'
        self.lineno = 0

    def py(self):  # pragma no cover
        return []

    def __iter__(self):
        yield self

    def line(self, text):
        return PyLine(self.filename, self.lineno, text)


class PassNode(Node):
    def py(self):
        # 'pass' would result in: TypeError: 'NoneType' object is not iterable
        yield self.line('yield ""')


class HierNode(Node):
    '''Base for nodes that contain an indented Python block (def, for, if etc.)
    '''
    def __init__(self, body):
        super(HierNode, self).__init__()
        self.body = tuple(x for x in body if x is not None)

    def body_iter(self):
        for x in optimize(flattener(map(flattener, self.body))):
            yield x

    def __iter__(self):
        yield self
        yield IndentNode()
        for x in self.body_iter():
            yield x
        yield DedentNode()


class IndentNode(Node):
    pass


class DedentNode(Node):
    pass


class TemplateNode(HierNode):
    """Represents the root Intermediate Representation node of a template.

    Iterating over this will generate the Python code for the class
    that provides all the functions that are part of the template including
    the ``__main__`` function that represents the template code itself.

    The generated class will then be passed to :meth:`kajiki.template.Template` to
    create a :class:`kajiki.template._Template` subclass that has the
    ``render`` method to render the template.
    """
    class TemplateTail(Node):
        def py(self):
            yield self.line('template = kajiki.Template(template)')

    def __init__(self, mod_py=None, defs=None):
        super(TemplateNode, self).__init__(defs)
        if mod_py is None:
            mod_py = []
        if defs is None:
            defs = []
        self.mod_py = [x for x in mod_py if x is not None]

    def py(self):
        yield self.line('class template:')

    def __iter__(self):
        for x in flattener(self.mod_py):
            yield x
        for x in super(TemplateNode, self).__iter__():
            yield x
        yield self.TemplateTail()


class ImportNode(Node):
    def __init__(self, tpl_name, alias=None):
        super(ImportNode, self).__init__()
        self.tpl_name = tpl_name
        self.alias = alias

    def py(self):
        yield self.line(
            'local.__kj__.import_(%r, %r, self.__globals__)' % (
                self.tpl_name, self.alias))


class IncludeNode(Node):
    def __init__(self, tpl_name):
        super(IncludeNode, self).__init__()
        self.tpl_name = tpl_name

    def py(self):
        yield self.line(
            'yield local.__kj__.import_(%r, None, self.__globals__).__main__()' % (
                self.tpl_name))


class ExtendNode(Node):
    def __init__(self, tpl_name):
        super(ExtendNode, self).__init__()
        self.tpl_name = tpl_name

    def py(self):
        yield self.line(
            'yield local.__kj__.extend(%r).__main__()' % (
                self.tpl_name))


class DefNode(HierNode):
    prefix = '@kajiki.expose'

    def __init__(self, decl, *body):
        super(DefNode, self).__init__(body)
        self.decl = decl

    def py(self):
        yield self.line(self.prefix)
        yield self.line('def %s:' % (self.decl))

    def __iter__(self):
        yield self
        yield IndentNode()
        is_empty = True
        for x in self.body_iter():
            yield x
            is_empty = False
        if is_empty:  # In Python, a function without a body is a SyntaxError.
            yield PassNode()  # Prevent creation of a function without a body
        yield DedentNode()


class InnerDefNode(DefNode):
    prefix = '@__kj__.flattener.decorate'


class CallNode(HierNode):
    class CallTail(Node):
        def __init__(self, call):
            super(CallNode.CallTail, self).__init__()
            self.call = call

        def py(self):
            yield self.line('yield ' + self.call)

    def __init__(self, caller, callee, *body):
        super(CallNode, self).__init__(body)
        fname = gen_name()
        self.decl = caller.replace('$caller', fname)
        self.call = callee.replace('$caller', fname)

    def py(self):
        yield self.line('@__kj__.flattener.decorate')
        yield self.line('def %s:' % (self.decl))

    def __iter__(self):
        yield self
        yield IndentNode()
        for x in self.body_iter():
            yield x
        yield DedentNode()
        yield self.CallTail(self.call)


class ForNode(HierNode):
    def __init__(self, decl, *body):
        super(ForNode, self).__init__(body)
        self.decl = decl

    def py(self):
        yield self.line('for %s:' % (self.decl))


class WithNode(HierNode):

    assignment_pattern = re.compile(r'(?:^|;)\s*([^;=]+)=(?!=)', re.M)

    class WithTail(Node):
        def __init__(self, var_names):
            super(WithNode.WithTail, self).__init__()
            self.var_names = var_names

        def py(self):
            yield self.line('(%s,) = local.__kj__.pop_with()' %
                            (','.join(self.var_names),))
            # yield self.line('if %s == (): del %s' % (v, v))

    def __init__(self, vars, *body):
        super(WithNode, self).__init__(body)
        assignments = []
        matches = self.assignment_pattern.finditer(vars)
        for m1, m2 in window(chain(matches, [None]), 2):
            lhs = m1.group(1).strip()
            rhs = vars[m1.end():(m2.start() if m2 else len(vars))]
            assignments.append((lhs, rhs))

        self.vars = assignments
        self.var_names = [l for l, _ in assignments]

    def py(self):
        yield self.line(
            'local.__kj__.push_with(locals(), [%s])' % (
                ','.join('"%s"' % k for k in self.var_names),))
        for k, v in self.vars:
            yield self.line('%s = %s' % (k, v))

    def __iter__(self):
        yield self
        for x in self.body_iter():
            yield x
        yield self.WithTail(self.var_names)


class SwitchNode(HierNode):
    class SwitchTail(Node):
        def py(self):
            yield self.line('local.__kj__.pop_switch()')

    def __init__(self, decl, *body):
        super(SwitchNode, self).__init__(body)
        self.decl = decl

    def py(self):
        yield self.line('local.__kj__.push_switch(%s)' % self.decl)
        yield self.line('if False: pass')

    def __iter__(self):
        yield self
        for x in self.body_iter():
            yield x
        yield self.SwitchTail()


class CaseNode(HierNode):
    def __init__(self, decl, *body):
        super(CaseNode, self).__init__(body)
        self.decl = decl

    def py(self):
        yield self.line('elif local.__kj__.case(%s):' % self.decl)


class IfNode(HierNode):
    def __init__(self, decl, *body):
        super(IfNode, self).__init__(body)
        self.decl = decl

    def py(self):
        yield self.line('if %s:' % self.decl)


class ElseNode(HierNode):
    def __init__(self, *body):
        super(ElseNode, self).__init__(body)

    def py(self):
        yield self.line('else:')


class TextNode(Node):
    '''Node that outputs Python literals.'''
    def __init__(self, text, guard=None):
        super(TextNode, self).__init__()
        self.text = text
        self.guard = guard

    def py(self):
        s = 'yield %r' % self.text
        if self.guard:
            yield self.line('if %s: %s' % (self.guard, s))
        else:
            yield self.line(s)


class TranslatableTextNode(TextNode):
    def py(self):
        text = self.text.strip()
        if text:
            s = 'yield local.__kj__.gettext(%r)' % self.text
        else:
            s = 'yield %r' % self.text
        if self.guard:
            yield self.line('if %s: %s' % (self.guard, s))
        else:
            yield self.line(s)


class ExprNode(Node):
    '''Node that contains a Python expression to be evaluated when the template
    is executed.
    '''
    def __init__(self, text, safe=False):
        super(ExprNode, self).__init__()
        self.text = text
        self.safe = safe

    def py(self):
        if self.safe:
            yield self.line('yield %s' % self.text)
        else:
            yield self.line('yield self.__kj__.escape(%s)' % self.text)


class AttrNode(HierNode):
    '''Node that renders HTML/XML attributes.'''
    class AttrTail(Node):
        def __init__(self, parent):
            super(AttrNode.AttrTail, self).__init__()
            self.p = parent

        def py(self):
            gen = self.p.genname
            x = gen_name()
            yield self.line("%s = self.__kj__.collect(%s())" % (gen, gen))
            yield self.line(
                'for %s in self.__kj__.render_attrs({%r:%s}, %r):'
                % (x, self.p.attr, gen, self.p.mode))
            yield self.line('    yield %s' % x)

    def __init__(self, attr, value, guard=None, mode='xml'):
        super(AttrNode, self).__init__(value)
        self.attr = attr
        self.guard = guard
        self.mode = mode
        self.genname = gen_name()

    def py(self):
        yield self.line('def %s():' % self.genname)

    def __iter__(self):
        if self.guard:
            new_body = IfNode(
                self.guard,
                AttrNode(self.attr, value=self.body, mode=self.mode))
            for x in new_body:
                yield x
        else:
            yield self
            yield IndentNode()
            if self.body:
                for part in self.body_iter():
                    yield part
            else:
                yield TextNode('')
            yield DedentNode()
            yield self.AttrTail(self)


class AttrsNode(Node):
    def __init__(self, attrs, guard=None, mode='xml'):
        super(AttrsNode, self).__init__()
        self.attrs = attrs
        self.guard = guard
        self.mode = mode

    def py(self):
        x = gen_name()

        def _body():
            yield self.line(
                'for %s in self.__kj__.render_attrs(%s, %r):' % (
                    x, self.attrs, self.mode))
            yield self.line('    yield %s' % x)
        if self.guard:
            yield self.line('if %s:' % self.guard)
            for l in _body():
                yield l.indent()
        else:
            for l in _body():
                yield l


class PythonNode(Node):
    def __init__(self, *body):
        super(PythonNode, self).__init__()
        self.module_level = False
        blocks = []
        for b in body:
            assert isinstance(b, TextNode)
            blocks.append(b.text)
        text = ''.join(blocks)
        if text[0] == '%':
            self.module_level = True
            text = text[1:]
        self.lines = list(self._normalize(text))

    def py(self):
        for line in self.lines:
            yield self.line(line)

    def _normalize(self, text):
        if text.startswith('#\n'):
            text = text[2:]
        prefix = None
        for line in text.splitlines():
            if prefix is None:
                rest = line.lstrip()
                prefix = line[:len(line) - len(rest)]
            assert line.startswith(prefix)
            yield line[len(prefix):]


def optimize(iter_node):
    last_node = None
    for node in iter_node:
        if (type(node) == TextNode and type(last_node) == TextNode
                and last_node.guard == node.guard):
            last_node.text += node.text
            # Erase this node by not yielding it.
            continue
        if last_node is not None:
            yield last_node
        last_node = node
    if last_node is not None:
        yield last_node


@nine
class PyLine(object):
    def __init__(self, filename, lineno, text, indent=0):
        self._filename = filename
        self._lineno = lineno
        self._text = text
        self._indent = indent

    def indent(self, sz=4):
        return PyLine(self._filename, self._lineno, self._text,
                      self._indent + sz)

    def __str__(self):
        return (' ' * self._indent) + self._text
        if self._lineno:
            return (' ' * self._indent) + self._text + '\t# %s:%d' % (
                self._filename, self._lineno)
        else:
            return (' ' * self._indent) + self._text

    def __repr__(self):
        return '%s:%s %s' % (self._filename, self._lineno, self)
