# -*- coding: utf8 -*-
"""
.. module:: lesscpy.plib.deferred
    :synopsis: Deferred mixin call.

    Copyright (c)
    See LICENSE for details.
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
"""
from .node import Node


class Deferred(Node):
    def __init__(self, mixin, args, lineno=0):
        """This node represents mixin calls. The calls
        to these mixins are deferred until the second
        parse cycle. lessc.js allows calls to mixins not
        yet defined or known.
        args:
            mixin (Mixin): Mixin object
            args (list): Call arguments
        """
        self.tokens = [mixin, args]
        self.lineno = lineno

    def parse(self, scope, error=False, depth=0):
        """ Parse function. We search for mixins
        first within current scope then fallback
        to global scope. The special scope.deferred
        is used when local scope mixins are called
        within parent mixins.
        If nothing is found we fallback to block-mixin
        as lessc.js allows calls to blocks and mixins to
        be inter-changable.
        clx: This method is a HACK that stems from
        poor design elsewhere. I will fix it
        when I have more time.
        args:
            scope (Scope): Current scope
        returns:
            mixed
        """
        res = False
        ident, args = self.tokens
        ident.parse(scope)
        mixins = scope.mixins(ident.raw())

        if not mixins:
            ident.parse(None)
            mixins = scope.mixins(ident.raw())

        if depth > 64:
            raise SyntaxError('NameError `%s`' % ident.raw(True))

        if not mixins:
            if scope.deferred:
                store = [t for t in scope.deferred.parsed[-1]]
                i = 0
                while scope.deferred.parsed[-1]:
                    scope.current = scope.deferred
                    ident.parse(scope)
                    mixins = scope.mixins(ident.raw())
                    scope.current = None
                    if mixins or i > 64:
                        break
                    scope.deferred.parsed[-1].pop()
                    i += 1
                scope.deferred.parsed[-1] = store

        if not mixins:
            # Fallback to blocks
            block = scope.blocks(ident.raw())
            if not block:
                ident.parse(None)
                block = scope.blocks(ident.raw())
            if block:
                scope.current = scope.real[-1] if scope.real else None
                res = block.copy_inner(scope)
                scope.current = None

        if mixins:
            for mixin in mixins:
                scope.current = scope.real[-1] if scope.real else None
                res = mixin.call(scope, args)
                if res:
                    # Add variables to scope to support
                    # closures
                    [scope.add_variable(v) for v in mixin.vars]
                    scope.deferred = ident
                    break

        if res:
            store = [t for t in scope.deferred.parsed[-1]
                     ] if scope.deferred else False
            tmp_res = []
            for p in res:
                if p:
                    if isinstance(p, Deferred):
                        tmp_res.append(p.parse(scope, depth=depth + 1))
                    else:
                        tmp_res.append(p.parse(scope))
            res = tmp_res
            #res = [p.parse(scope, depth=depth+1) for p in res if p]
            while (any(t for t in res if isinstance(t, Deferred))):
                res = [p.parse(scope) for p in res if p]
            if store:
                scope.deferred.parsed[-1] = store

        if error and not res:
            raise SyntaxError('NameError `%s`' % ident.raw(True))
        return res

    def copy(self):
        """ Returns self (used when Block objects are copy'd)
        returns:
            self
        """
        return self
