import ast
from collections import Iterable, Sequence, MutableSequence
from functools import reduce
from pathlib import Path
from os import chdir
from os.path import normpath, abspath

import astunparse

from mochi import GE_PYTHON_34, IS_PYPY
from mochi.parser import Symbol, Keyword, parse, lex, get_temp_name
from .utils import issequence_except_str
from .constants import *
from .exceptions import MochiSyntaxError, DuplicatedDefError
from .global_env import global_env


if GE_PYTHON_34:
    expr_and_stmt_ast = (ast.Expr, ast.If, ast.For, ast.FunctionDef, ast.Assign, ast.Delete, ast.Try, ast.Raise,
                         ast.With, ast.While, ast.Break, ast.Return, ast.Continue, ast.ClassDef,
                         ast.Import, ast.ImportFrom, ast.Pass)
else:
    expr_and_stmt_ast = (ast.Expr, ast.If, ast.For, ast.FunctionDef, ast.Assign, ast.Delete, ast.TryFinally,
                         ast.TryExcept, ast.Raise, ast.With, ast.While, ast.Break, ast.Return, ast.Continue,
                         ast.ClassDef, ast.Import, ast.ImportFrom, ast.Pass)

syntax_table = {}


#-- double builtins :(
def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]

def tuple_it(obj):
    return tuple(map(tuple_it, obj)) if isinstance(obj, MutableSequence) else obj

#---

def syntax(*names):
    def _syntax(func):
        for name in names:
            syntax_table[name] = func
            if name not in global_env.keys():
                global_env[name] = 'syntax_' + name
        return func

    return _syntax

required_filenames_stack = [set()]


def lexical_scope_for_require(func):
    def _lexical_scope(*args, **kwargs):
        try:
            required_filenames_stack.append(set())
            return func(*args, **kwargs)
        finally:
            required_filenames_stack.pop()
    return _lexical_scope


def lexical_scope(quasi=False):
    def _lexical_scope(func):
        def __lexical_scope(*args, **kwargs):
            try:
                scope = Scope(quasi)
                if quasi:
                    binding_name_set_stack[-1].add_child(scope)
                binding_name_set_stack.append(scope)
                return func(*args, **kwargs)
            finally:
                if binding_name_set_stack[-1] is scope:
                    binding_name_set_stack.pop()

        return __lexical_scope

    return _lexical_scope


def flatten_list(lis):
    i = 0
    while i < len(lis):
        while isinstance(lis[i], Iterable):
            if not lis[i]:
                lis.pop(i)
                i -= 1
                break
            else:
                lis[i:i + 1] = lis[i]
        i += 1
    return lis


def add_binding_name(binding_name, file_name):
    return binding_name_set_stack[-1].add_binding_name(binding_name, file_name)


class Translator(object):
    def __init__(self, macro_table={}, filename='<string>'):
        self.hidden_vars = []
        self.macro_table = macro_table
        self.filename = filename

    def translate_block(self, mochi_block, filename='<string>',
                        show_tokens=False):
        """Translate sexpressions into a Python AST.
        """
        sexps = parse(lex(mochi_block, debug=show_tokens), filename)
        body = []
        for sexp in sexps:
            if isinstance(sexp, MutableSequence):
                sexp = tuple_it(sexp)
            if sexp is COMMENT:
                continue
            pre, value = self.translate(sexp)
            body.extend([self.enclose(exp, True) for exp in pre])
            body.append(self.enclose(value, True))
        return ast.Module(body=body)

    def translate_file(self, filename, show_tokens=False):
        """Translate a Mochi file into a Python AST.
        """
        self.filename = filename
        with open(filename, 'r') as fobj:
            mochi_block = fobj.read()
        # TODO: Why this change of directory is needed?
        #chdir(normpath(str(Path(filename).parent)))
        return self.translate_block(mochi_block, filename=filename,
                                    show_tokens=show_tokens)

    def translate_loaded_file(self, filename, show_tokens=False):
        body = []
        self.filename = filename
        with open(filename, 'r') as f:
            sexps = parse(lex(f.read(), debug=show_tokens), filename)
            for sexp in sexps:
                if isinstance(sexp, MutableSequence):
                    sexp = tuple_it(sexp)
                if sexp is COMMENT:
                    continue
                pre, value = self.translate(sexp)
                body.extend(pre)
                body.append(value)
        return body

    def translate_required_file(self, filename):
        required_filenames = required_filenames_stack[-1]
        if filename in required_filenames:
            return self.translate_ref(NONE_SYM)[1],
        else:
            required_filenames.add(filename)
            return self.translate_loaded_file(filename)

    def translate_loaded_files(self, file_paths):
        body = []
        for file_path in file_paths:
            if not isinstance(file_path, str):
                raise MochiSyntaxError(file_path, self.filename)
            body.extend(self.translate_loaded_file(abspath(file_path)))
        return body, self.translate_ref(NONE_SYM)[1]

    def translate_required_files(self, file_paths):
        body = []
        for file_path in file_paths:
            if not isinstance(file_path, str):
                raise MochiSyntaxError(file_path, self.filename)
            body.extend(self.translate_required_file(abspath(file_path)))
        return body, self.translate_ref(NONE_SYM)[1]

    def translate_sexp_to_interact(self, sexp):
        pre, value = self.translate(sexp, False)
        body = [self.enclose(exp, True) for exp in pre]
        write_value = ast.Call(func=ast.Name(id='write',
                                             ctx=ast.Load(),
                                             lineno=1,
                                             col_offset=0),
                               ctx=ast.Load(),
                               args=[value],
                               keywords=[],
                               starargs=None,
                               kwargs=None,
                               lineno=1,
                               col_offset=0)
        body.append(self.enclose(write_value, True))
        return ast.Module(body=body)

    def translate_sexp(self, sexp):
        # TODO value
        pre, value = self.translate(sexp, False)
        body = [self.enclose(exp, True) for exp in pre]
        return ast.Module(body=body)

    def is_self_evaluating(self, exp):
        return isinstance(exp, (int, float, complex, str, bool, type(None),
                          Keyword))

    def translate_self_evaluating(self, exp):
        if isinstance(exp, (int, float, complex)):
            if hasattr(exp, 'value'):
                return EMPTY, ast.Num(exp.value,
                                      lineno=exp.lineno,
                                      col_offset=0)
            else:
                return EMPTY, ast.Num(exp,
                                      lineno=0,
                                      col_offset=0)

        expType = type(exp)

        if expType is str:
            return EMPTY, ast.Str(exp,
                                  lineno=0,
                                  col_offset=0)

        if expType is Keyword:
            #raise SyntaxError('keyword!', self.filename)
            modast = ast.parse(
                'Keyword("{name}", {lineno})'.format(name=exp.name, lineno=exp.lineno))
            return EMPTY, modast.body[0].value

    def _translate_items(self, items):
        return flatten_list([self.translate(item, False) for item in items])

    def is_variable(self, exp):
        return type(exp) is Symbol

    def is_get_attrs(self, exp):
        return type(exp) is Symbol and len(exp.name.split('.')) > 1

    def translate_ref(self, exp):
        return EMPTY, ast.Name(id=exp.name,
                               ctx=ast.Load(),
                               lineno=exp.lineno,
                               col_offset=0)

    def translate_atom(self, exp):
        if self.is_self_evaluating(exp):
            return self.translate_self_evaluating(exp)
        elif self.is_get_attrs(exp):
            parts = exp.name.split('.')
            parts[0] = Symbol(parts[0])
            return self.translate(
                reduce(lambda a, b: (Symbol('getattr'), a, b),
                       parts), False)
        elif self.is_variable(exp):
            return self.translate_ref(exp)

    @syntax('_val')
    def translate_def(self, exp):
        if len(exp) != 3:
            raise MochiSyntaxError(exp, self.filename)
        return self.translate_assign(exp)

    def create_assign_target(self, symbol):
        return ast.Name(id=symbol.name,
                        ctx=ast.Store(),
                        lineno=symbol.lineno,
                        col_offset=0)

    def create_assign_targets(self, seq):
        elts = []
        for index, item in enumerate(seq):
            if isinstance(item, Symbol):
                # TODO errorcheck index + 1 != lastIndex
                if item == VARG:
                    elts.append(
                        ast.Starred(
                            value=self.create_assign_target(
                                seq[index + 1]),
                            ctx=ast.Store(),
                            lineno=0,  # TODO
                            col_offset=0))
                    break
                elts.append(self.create_assign_target(item))
            elif issequence_except_str(item):
                if len(item) == 0:
                    raise MochiSyntaxError("can't bind to ()", self.filename)
                #if item[0] == 'getattr':
                #    attr_exp = self.translate_getattr(item)[1]
                #    attr_exp.ctx = ast.Store()
                #    elts.append(attr_exp)
                #else:
                elts.append(self.create_assign_targets(item))
            else:
                raise MochiSyntaxError(item, self.filename)
        return ast.Tuple(elts=elts,
                         ctx=ast.Store(),
                         lineno=0,  # TODO
                         col_offset=0)

    #@syntax('set')
    def translate_assign(self, exp, visible=True):
        if len(exp) != 3:
            raise MochiSyntaxError(exp, self.filename)

        left = exp[1]
        left_type = type(left)
        if left_type is Symbol:
            targets = [self.create_assign_target(left)]
            ref_symbol = left
            if not visible:
                self.hidden_vars.append(ref_symbol.name)
        elif issequence_except_str(left):
            targets = [self.create_assign_targets(left)]
            ref_symbol = NONE_SYM
        else:
            raise MochiSyntaxError(exp, self.filename)

        pre = []
        right_value_builder, right_value = self.translate(exp[2], False)
        if type(right_value) is ast.Expr:
            right_value = right_value.value
        assign = ast.Assign(targets=targets,
                            value=right_value,
                            lineno=right_value.lineno,
                            col_offset=0)
        pre.extend(right_value_builder)
        pre.append(assign)
        _, ref = self.translate_ref(ref_symbol)
        return pre, ref

    #@syntax('del')
    #def translate_del(self, exp):
    #    return (ast.Delete(targets=[ast.Name(id=exp[1].name,
    #                                         lineno=exp[1].lineno,
    #                                         col_offset=exp[1].col_offset,
    #                                         ctx=ast.Del())],
    #                       lineno=exp[0].lineno,
    #                       col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1]

    @syntax('yield')
    def translate_yield(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)

        pre, value = self.translate(exp[1], False)
        if type(value) is ast.Expr:
            value = value.value
        yield_node = ast.Yield(value=value,
                               lineno=exp[0].lineno,
                               col_offset=0)
        return pre, yield_node

    @syntax('yield_from')
    def translate_yield_from(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)

        pre, value = self.translate(exp[1], False)
        if type(value) is ast.Expr:
            value = value.value
        yield_from_node = ast.YieldFrom(value=value,
                                        lineno=exp[0].lineno,
                                        col_offset=0)
        return pre, yield_from_node

    def _translate_args(self, args):
        return [ast.arg(arg=arg.name,
                        annotation=None,
                        lineno=arg.lineno,
                        col_offset=0) for arg in args]

    def _translate_sequence(self, exps, enclose=True):
        seq = []
        for exp in exps:
            pre, value = self.translate(exp, enclose)
            seq.extend(pre)
            seq.append(value)
        return seq

    @syntax('do')
    def translate_do(self, exp, enclose=False):
        seq = self._translate_sequence(exp[1:], enclose)
        return seq[:-1], seq[-1]

    @syntax('if')
    def translate_if(self, exp):
        if not len(exp) >= 3:
            raise MochiSyntaxError(exp, self.filename)
        temp_var_symbol = get_temp_name()
        return self._translate_if(exp, temp_var_symbol)

    def _translate_if(self, exp, temp_var_symbol):
        if_ast = None
        cur_if_ast = None
        pre = []
        for i, v in enumerate(exp):
            if i == 0:
                pass
            elif (i % 2) == 0:
                pre_test, test = self.translate(exp[i - 1], False)
                pre.extend(pre_test)
                if if_ast is None:
                    if issequence_except_str(v) and v[0] == Symbol('return'):
                        body, ref = self.translate_return(v)
                        if_ast = ast.If(test=test,
                                        body=body,
                                        orelse=[],
                                        lineno=exp[0].lineno,
                                        col_offset=0)
                    else:
                        body, ref = self.translate_assign(('dummy', temp_var_symbol, v), visible=False)
                        if_ast = ast.If(test=test,
                                        body=body,
                                        orelse=[],
                                        lineno=exp[0].lineno,
                                        col_offset=0)
                    cur_if_ast = if_ast
                else:
                    else_if = [Symbol('if'), exp[i - 1], v]
                    else_body, ref = self._translate_if(else_if, temp_var_symbol)
                    cur_if_ast.orelse = else_body
                    cur_if_ast = else_body[0]
            elif i == (len(exp) - 1):
                if issequence_except_str(v) and v[0] == Symbol('return'):
                    else_body, ref = self.translate_return(v)
                    cur_if_ast.orelse = else_body
                else:
                    else_body, ref = self.translate_assign(
                        ('dummy', temp_var_symbol, v), visible=False)
                    cur_if_ast.orelse = else_body

        if len(if_ast.orelse) == 0:
            else_body, ref = self.translate_assign(
                ('dummy', temp_var_symbol, EMPTY_SYM), visible=False)
            if_ast.orelse = else_body

        pre.append(if_ast)
        return pre, ref

    @syntax('+', '-', '*', '/', '%', '**',
            '<<', '>>', '|', '^^', '&&', '//')
    def translate_bin_op(self, exp):
        if not len(exp) >= 2:
            raise MochiSyntaxError(exp, self.filename)

        if len(exp) == 2:
            return self.translate(exp[1], False)

        op_symbol = exp[0]
        op_name = op_symbol.name
        op = op_ast_map[op_name]

        if len(exp) == 3:
            left, right = exp[1], exp[2]
            left_pre, left_value = self.translate(left, False)
            right_pre, right_value = self.translate(right, False)
            pre = left_pre + right_pre
            return pre, ast.BinOp(op=op,
                                  left=left_value,
                                  right=right_value,
                                  lineno=op_symbol.lineno,
                                  col_offset=0)
        else:
            rest, right = exp[0:-1], exp[-1]
            rest_pre, rest_value = self.translate(rest, False)
            right_pre, right_value = self.translate(right, False)
            pre = rest_pre + right_pre
            return pre, ast.BinOp(op=op,
                                  left=rest_value,
                                  right=right_value,
                                  lineno=op_symbol.lineno,
                                  col_offset=0)

    @syntax('and', 'or')
    def translate_bool_op(self, exp):
        if not len(exp) >= 3:
            raise MochiSyntaxError(exp, self.filename)

        op_symbol = exp[0]
        op_name = op_symbol.name
        op = op_ast_map[op_name]

        pre = []
        values = []
        for value in exp[1:]:
            pre_value, value_value = self.translate(value, False)
            pre += pre_value
            values.append(value_value)

        return pre, ast.BoolOp(op=op,
                               values=values,
                               lineno=op_symbol.lineno,
                               col_offset=0)

    @syntax('==', '!=', '<', '<=', '>', '>=',
            'is', 'is_not', 'in', 'not_in')
    def translate_compare(self, exp):
        if len(exp) < 3:
            raise MochiSyntaxError(exp, self.filename)

        op_symbol = exp[0]
        op_name = op_symbol.name

        left, rights = exp[1], exp[2:]
        ops = [op_ast_map[op_name]] * len(rights)
        pre, left_value = self.translate(left, False)
        right_values = []
        for right in rights:
            right_pre, right_value = self.translate(right, False)
            pre = pre + right_pre
            right_values.append(right_value)

        return pre, ast.Compare(ops=ops,
                                left=left_value,
                                comparators=right_values,
                                lineno=op_symbol.lineno,
                                col_offset=0)

    def make_return(self, exp):
        pre, value = self.translate(exp, False)
        if type(value) is ast.Expr:
            value = value.value
        ret = ast.Return(value=value,
                         lineno=0,  # exp.lineno,
                         col_offset=0)
        return pre, ret

    @syntax('for', 'each')
    def translate_foreach(self, exp):
        if not (len(exp) >= 4):
            raise MochiSyntaxError(exp, self.filename)

        target_exp = exp[1]
        if isinstance(target_exp, Symbol):
            target = self.create_assign_target(target_exp)
        elif issequence_except_str(target_exp):
            target = self.create_assign_targets(target_exp)
        else:
            raise MochiSyntaxError(exp, self.filename)

        pre = []
        iter_pre, iter_value = self.translate(exp[2], False)
        pre.extend(iter_pre)

        body = self._translate_sequence(exp[3:])
        pre.append(ast.For(target=target,
                           iter=iter_value,
                           body=body,
                           orelse=[],
                           lineno=exp[0].lineno,
                           col_offset=0))

        _, ref = self.translate_ref(NONE_SYM)
        return pre, ref

    @syntax('while')
    def translate_while(self, exp):
        if not (len(exp) >= 3):
            raise SyntaxError(exp, self.filename)

        test_exp = exp[1]
        body_exps = exp[2:]

        pre = []

        test_pre, test_value = self.translate(test_exp, False)
        pre.extend(test_pre)

        body = self._translate_sequence(body_exps)
        pre.append(ast.While(test=test_value,
                             body=body,
                             orelse=[],
                             lineno=exp[0].lineno,
                             col_offset=0))

        _, ref = self.translate_ref(NONE_SYM)
        return pre, ref

    @syntax('break')
    def translate_break(self, exp):
        if len(exp) > 1:
            raise MochiSyntaxError(exp, self.filename)

        return (), ast.Break(lineno=exp[0].lineno,
                             col_offset=0)

    @syntax('continue')
    def translate_continue(self, exp):
        if len(exp) > 1:
            raise MochiSyntaxError(exp, self.filename)

        return (), ast.Continue(lineno=exp[0].lineno,
                                col_offset=0)

    def _translate_get_index(self, exp):
        pre = []
        target_pre, target_value = self.translate(exp[1], False)
        pre.extend(target_pre)
        index_pre, index_value = self.translate(exp[2], False)
        pre.extend(index_pre)
        return pre, ast.Subscript(value=target_value,
                                  slice=ast.Index(value=index_value),
                                  ctx=ast.Load(),
                                  lineno=exp[0].lineno,
                                  col_offset=0)

    def _translate_get_slice(self, lineno, target, start=None, stop=None, step=None):
        pre = []
        target_pre, target_value = self.translate(target, False)
        pre.extend(target_pre)
        start_pre, start_value = self.translate(start, False) if start is not None else ((), None)
        pre.extend(start_pre)
        stop_pre, stop_value = self.translate(stop, False) if stop is not None else ((), None)
        pre.extend(stop_pre)
        step_pre, step_value = self.translate(step, False) if step is not None else ((), None)
        pre.extend(step_pre)
        return pre, ast.Subscript(value=target_value,
                                  slice=ast.Slice(lower=start_value,
                                                  upper=stop_value,
                                                  step=step_value),
                                  ctx=ast.Load(),
                                  lineno=lineno,
                                  col_offset=0)

    @syntax('get')
    def translate_get(self, exp):
        exp_length = len(exp)
        if exp_length == 3:
            return self._translate_get_index(exp)
        elif exp_length == 4:
            return self._translate_get_slice(exp[0].lineno, exp[1], exp[2], exp[3])
        elif exp_length == 5:
            return self._translate_get_slice(exp[0].lineno, exp[1], exp[2], exp[3], exp[4])
        else:
            raise MochiSyntaxError(exp, self.filename)

    @syntax('car', 'first')
    def translate_car(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        return self._translate_get_index((GET, exp[1], 0))

    @syntax('cdr', 'rest')
    def translate_cdr(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        return self._translate_get_slice(exp[0].lineno, exp[1], 1)

    @syntax('last')
    def translate_last(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        return self._translate_get_index((GET, exp[1], -1))

    @syntax('cadr')
    def translate_cadr(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        return self._translate_get_index((GET, exp[1], 1))

    @syntax('getattr')
    def translate_getattr(self, exp):
        pre, value = self.translate(exp[1], False)
        return pre, ast.Attribute(value=value,
                                  attr=exp[2],
                                  lineno=exp[0].lineno,
                                  col_offset=exp[0].col_offset,
                                  ctx=ast.Load())

    @syntax('with_decorator')
    def translate_with_decorator(self, exp):
        if not (len(exp) >= 3):
            raise MochiSyntaxError(exp, self.filename)
        return self.translate_defun(exp[-1], decorator_exps=exp[1:-1])

    class DuplicatedBindingChecker(ast.NodeTransformer):
        def __init__(self, filename):
            self.filename = filename

        @lexical_scope()
        def visit_ClassDef(self, node):
            add_binding_name(node.name, self.filename)
            return self.generic_visit(node)

        @lexical_scope(quasi=True)
        def visit_If(self, node):
            self.visit(node.test)
            for body_item in node.body:
                self.visit(body_item)
            binding_name_set_stack.pop()
            for orelse_item in node.orelse:
                if not isinstance(orelse_item, ast.If):
                    new_scope = Scope(quasi=True)
                    binding_name_set_stack[-1].add_child(new_scope)
                    binding_name_set_stack.append(new_scope)
                    self.visit(orelse_item)
                    binding_name_set_stack.pop()
                else:
                    self.visit(orelse_item)
            return node

        def create_init_assign(self, node):
            targets = []
            arg_names = [arg if isinstance(arg, str) else arg.arg for arg in node.args.args]
            if node.args.vararg is not None:
                arg_names.append(node.args.vararg if isinstance(node.args.vararg, str) else node.args.vararg.arg)
            if node.args.kwarg is not None:
                arg_names.append(node.args.kwarg if isinstance(node.args.kwarg, str) else node.args.kwarg.arg)
            for binding_name in node.binding_name_set:
                if binding_name == node.name:
                    continue
                if binding_name in arg_names:
                    continue
                targets.append(ast.Name(id=binding_name,
                                        ctx=ast.Store(),
                                        lineno=0,
                                        col_offset=0))
            if len(targets) > 0:
                if hasattr(ast, 'NameConstant'):
                    right_value = ast.NameConstant(value=None)
                else:
                    right_value = ast.Name(id=NONE_SYM.name,
                                           ctx=ast.Load(),
                                           lineno=0,
                                           col_offset=0)
                return [ast.Assign(targets=targets,
                                   value=right_value,
                                   lineno=0,
                                   col_offset=0)]
            else:
                return []

        @lexical_scope()
        def visit_FunctionDef(self, node):
            if hasattr(node, 'is_visited') and node.is_visited:
                return node
            add_binding_name(node.name, self.filename)
            for arg in node.args.args:
                add_binding_name(arg if isinstance(arg, str) else arg.arg, self.filename)
            if node.args.vararg is not None:
                add_binding_name(node.args.vararg if isinstance(node.args.vararg, str) else node.args.vararg.arg,
                                  self.filename)
            if node.args.kwarg is not None:
                add_binding_name(node.args.kwarg if isinstance(node.args.kwarg) else node.args.kwarg.arg,
                                  self.filename)
            for stmt in node.body:
                self.visit(stmt)
            binding_name_set_stack[-1].merge_children()
            node.binding_name_set = binding_name_set_stack[-1]
            node.body = self.create_init_assign(node) + node.body
            node.is_visited = True
            return node

        def visit_Assign(self, node):
            if not (hasattr(node, 're_assign') and node.re_assign):
                for target in node.targets:
                    add_binding_name(target.id, self.filename)
            return node

        def visit_ExceptHandler(self, node):
            if hasattr(node, 'name'):
                add_binding_name(node.name, self.filename)
            return node

    def _check_duplicated_binding(self, func_ast):
        checker = self.DuplicatedBindingChecker(self.filename)
        return checker.visit(func_ast)

    class SelfTailRecursiveCallTransformer(ast.NodeTransformer):

        optimized = False

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

        def visit_FunctionDef(self, node):
            if node is not self.target_func:
                return node
            new_body = []
            for stmt in node.body:
                new_stmt = self.__class__(self.target_func).visit(stmt)
                if isinstance(new_stmt, Sequence):
                    new_body.extend(new_stmt)
                else:
                    new_body.append(new_stmt)
            node.body = new_body
            ast.fix_missing_locations(node)
            return node

        def visit_Return(self, node):
            if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name):
                if node.value.func.id == self.target_func.name:
                    self.__class__.optimized = True
                    tmp_result = []
                    result = []

                    default_values = [] + self.target_func.args.defaults
                    for arg in self.target_func.args.args:
                        arg_name = ast.Name(id=arg.arg,
                                            ctx=ast.Store(),
                                            lineno=0,
                                            col_offset=0)
                        try:
                            arg_value = node.value.args.pop(0)
                        except IndexError:
                            arg_value = default_values.pop(0)
                        tmp_arg_sym = get_temp_name()
                        tmp_arg_name = ast.Name(id=tmp_arg_sym.name,
                                                ctx=ast.Store(),
                                                lineno=0,
                                                col_offset=0)
                        arg_value_assign = ast.Assign(targets=[tmp_arg_name],
                                                      value=arg_value,
                                                      lineno=0,
                                                      col_offset=0)
                        tmp_result.append(arg_value_assign)
                        arg_value_ref = translator.translate_ref(tmp_arg_sym)[1]
                        arg_re_assign = ast.Assign(targets=[arg_name],
                                                   value=arg_value_ref,
                                                   lineno=0,
                                                   col_offset=0)
                        arg_re_assign.re_assign = True
                        result.append(arg_re_assign)
                    if self.target_func.args.vararg is not None:
                        vararg = self.target_func.args.vararg
                        arg_name = ast.Name(id=vararg.arg if GE_PYTHON_34 else vararg,
                                            ctx=ast.Store(),
                                            lineno=0,
                                            col_offset=0)
                        arg_value = ast.Tuple(elts=node.value.args,
                                              lineno=0,
                                              col_offset=0,
                                              ctx=ast.Load())
                        arg_re_assign = ast.Assign(targets=[arg_name],
                                                   value=arg_value,
                                                   lineno=0,
                                                   col_offset=0)
                        arg_re_assign.re_assign = True
                        result.append(arg_re_assign)
                    result.append(ast.Continue(lineno=0, col_offset=0))
                    return tmp_result + result
            return node

    def _tail_recursion_optimize(self, func_ast):
        transformer = self.SelfTailRecursiveCallTransformer(func_ast)
        optimized_func_ast = transformer.visit(func_ast)

        if self.SelfTailRecursiveCallTransformer.optimized:
            if IS_PYPY:
                optimized_func_ast.body = [ast.While(test=ast.Name(id='True',
                                                                   ctx=ast.Load(),
                                                                   lineno=0,
                                                                   col_offset=0),
                                                     body=optimized_func_ast.body + [ast.Break(lineno=0,
                                                                                               col_offset=0)],
                                                     orelse=[],
                                                     lineno=0,
                                                     col_offset=0)]
            else:
                optimized_func_ast.body = [ast.While(test=ast.Num(n=1,
                                                                  lineno=0,
                                                                  col_offset=0),
                                                     body=optimized_func_ast.body + [ast.Break(lineno=0,
                                                                                               col_offset=0)],
                                                     orelse=[],
                                                     lineno=0,
                                                     col_offset=0)]
            self.SelfTailRecursiveCallTransformer.optimized = False
            return optimized_func_ast
        else:
            return func_ast

    def _tail_to_return_s(self, exps):

        def except_tail_to_return(except_exp):
            if issequence_except_str(except_exp) and isinstance(except_exp[0], Symbol)\
                    and except_exp[0].name == 'except':
                return (except_exp[0],) + self._tail_to_return_s(except_exp[1:])
            return except_exp

        exp = exps[-1]
        if not issequence_except_str(exp):
            exp = (Symbol('return'), exp)
        elif not isinstance(exp[0], Symbol):
            exp = (Symbol('return'), exp)
        elif exp[0].name in {'val', '_val', 'return', 'yield', 'yield_from', 'pass', 'raise'}:
            pass
        elif exp[0].name == 'if':
            if_exp = exp
            if len(if_exp) == 3:
                if_sym, test_cls, true_cls = if_exp
                exp = (if_sym, test_cls, self._tail_to_return_s([true_cls])[0])
            elif len(if_exp) == 4:
                if_sym, test_cls, true_cls, else_cls = if_exp
                exp = (if_sym, test_cls, self._tail_to_return_s([true_cls])[0], self._tail_to_return_s([else_cls])[0])
            else:
                new_if_exp = list(if_exp)
                i = 2
                exp_len = len(if_exp)
                while i < exp_len:
                    new_if_exp[i] = self._tail_to_return_s([if_exp[i]])[0]
                    i += 2
                if exp_len % 2 == 0:
                    new_if_exp[-1] = self._tail_to_return_s([if_exp[-1]])[0]
                exp = tuple(new_if_exp)
        elif exp[0].name in {'do', 'with', 'finally', 'except'}:
            exp = (exp[0], ) + self._tail_to_return_s(exp[1:])
        elif exp[0].name == 'try':
            exp = (exp[0], ) + self._tail_to_return_s(tuple(filter(lambda item: not (issequence_except_str(item)
                                                                                     and isinstance(item[0], Symbol)
                                                                                     and item[0].name in {'except',
                                                                                                          'finally'}),
                                                                   exp[1:]))) + \
                tuple(map(except_tail_to_return, filter(lambda item: issequence_except_str(item) and
                                                                     isinstance(item[0], Symbol) and
                                                                     item[0].name in {'except', 'finally'}, exp[1:])))
        elif exp[0].name == 'match':
            new_exp = list(exp)
            i = 3
            exp_len = len(exp)
            while i < exp_len:
                new_exp[i] = self._tail_to_return_s([exp[i]])[0]
                i += 2
            exp = tuple(new_exp)
        else:
            exp = (Symbol('return'), exp)
        return tuple(exps[:-1]) + (exp,)

    @syntax('return')
    def translate_return(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)

        pre, value = self.translate(exp[1], False)
        if type(value) is ast.Expr:
            value = value.value
        return pre + [ast.Return(value=value,
                                 lineno=exp[0].lineno,
                                 col_offset=0)], self.translate(NONE_SYM)[1]

    def _translate_handler(self, exp):
        type_expr, var, *body = exp
        return ast.ExceptHandler(type=self.translate(type_expr, False)[1],
                                 name=var.name,
                                 body=self._translate_sequence(body, True),
                                 lineno=var.lineno,
                                 col_offset=0)

    def _translate_handlers(self, exps):
        handlers = []
        for exp in exps:
            handlers.append(self._translate_handler(exp))
        return handlers

    @syntax('try')
    def translate_try(self, exp):
        if len(exp) < 2:
            raise MochiSyntaxError(exp, self.filename)
        body_exp = []
        handler_exps = []
        orelse_exp = []
        final_body_exp = []
        for expr in exp:
            if issequence_except_str(expr) and len(expr) > 1 and isinstance(expr[0], Symbol):
                expr_name = expr[0].name
                if expr_name == 'finally':
                    final_body_exp = expr[1:]
                    continue
                elif expr_name == 'orelse':
                    orelse_exp = expr[1:]
                    continue
                elif expr_name == 'except':
                    handler_exps.append(expr[1:])
                    continue
            body_exp.append(expr)

        body = self._translate_sequence(body_exp, True)
        handlers = self._translate_handlers(handler_exps)
        orelse = self._translate_sequence(orelse_exp, True)
        final_body = self._translate_sequence(final_body_exp, True)
        if GE_PYTHON_34:
            return (ast.Try(body=body,
                            handlers=handlers,
                            orelse=orelse,
                            finalbody=final_body,
                            lineno=exp[0].lineno,
                            col_offset=0),), self.translate(EMPTY_SYM, False)[1]
        else:
            if len(handlers) == 0:
                return (ast.TryFinally(body=body,
                                       finalbody=final_body,
                                       lineno=exp[0].lineno,
                                       col_offset=0),), self.translate(EMPTY_SYM, False)[1]
            else:
                return (ast.TryFinally(body=[ast.TryExcept(body=body,
                                                           handlers=handlers,
                                                           orelse=orelse,
                                                           lineno=exp[0].lineno,
                                                           col_offset=0)],
                                       finalbody=final_body,
                                       lineno=exp[0].lineno,
                                       col_offset=0),), self.translate(EMPTY_SYM, False)[1]

    @syntax('with')
    def translate_with(self, exp):
        if len(exp) < 3:
            raise MochiSyntaxError(exp, self.filename)

        if GE_PYTHON_34:
            return self.translate_with_34(exp)
        else:
            return self.translate_with_old(exp)

    def translate_with_34(self, exp):
        keyword_with, items, *body = exp
        pre = []
        items_py = []
        for item in items:
            item_pre, item_value = self.translate(item[0], False)
            pre.extend(item_pre)
            var = item[1]
            items_py.append(ast.withitem(context_expr=item_value,
                                         optional_vars=ast.Name(id=var.name,
                                                                ctx=ast.Store(),
                                                                lineno=var.lineno,
                                                                col_offset=0),
                                         lineno=var.lineno,
                                         col_offset=0))

        body_py = self._translate_sequence(body, True)
        pre.append(ast.With(items=items_py,
                            body=body_py,
                            lineno=keyword_with.lineno,
                            col_offset=0))
        return pre, self.translate(NONE_SYM, False)[1]

    def translate_with_old(self, exp):
        keyword_with, items, *body = exp
        pre = []
        first_with_py = None
        with_py = None
        for item in items:
            item_pre, item_value = self.translate(item[0], False)
            pre.extend(item_pre)
            var = item[1]
            if with_py is None:
                with_py = ast.With(context_expr=item_value,
                                   optional_vars=ast.Name(id=var.name,
                                                          ctx=ast.Store(),
                                                          lineno=var.lineno,
                                                          col_offset=0),
                                   lineno=var.lineno,
                                   col_offset=0)
                first_with_py = with_py
            else:
                inner_with_py = ast.With(context_expr=item_value,
                                         optional_vars=ast.Name(id=var.name,
                                                                ctx=ast.Store(),
                                                                lineno=var.lineno,
                                                                col_offset=0),
                                         lineno=var.lineno,
                                         col_offset=0)
                with_py.body = [inner_with_py]
                with_py = inner_with_py

        with_py.body = self._translate_sequence(body, True)
        pre.append(first_with_py)
        return pre, self.translate(NONE_SYM, False)[1]

    @syntax('raise')
    def translate_raise(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        return (ast.Raise(exc=self.translate(exp[1], False)[1],
                          cause=None,
                          lineno=exp[0].lineno,
                          col_offset=0),), self.translate(EMPTY_SYM, False)[1]

    @syntax('def')
    @lexical_scope_for_require
    def translate_defun(self, exp, decorator_exps=None, visible=True):
        if not (len(exp) >= 4 and type(exp[1]) is Symbol):
            raise MochiSyntaxError(exp, self.filename)

        id = exp[1].name
        arg = exp[2]
        vararg_exp = None
        varkwarg_exp = None

        if not visible:
            self.hidden_vars.append(id)

        if type(arg) is Symbol:
            arg_exps = []
            vararg_exp = arg
        else:
            vararg_count = arg.count(VARG)
            varkwarg_count = arg.count(VKWARG)
            if vararg_count > 1 or varkwarg_count > 1:
                raise MochiSyntaxError(exp, self.filename)
            elif vararg_count and varkwarg_count:
                VARGIndex = arg.index(VARG)
                VKWARGIndex = arg.index(VKWARG)
                if (VARGIndex > VKWARGIndex):
                    raise MochiSyntaxError(exp, self.filename)
                arg_exps = arg[:VARGIndex]
                vararg_exp = arg[VARGIndex + 1]
                varkwarg_exp = arg[VKWARGIndex + 1:]
                if len(varkwarg_exp) == 1:
                    varkwarg_exp = varkwarg_exp[0]
                else:
                    raise MochiSyntaxError(exp, self.filename)
            elif vararg_count:
                VARGIndex = arg.index(VARG)
                arg_exps = arg[:VARGIndex]
                vararg_exp = arg[VARGIndex + 1:]
                if len(vararg_exp) == 1:
                    vararg_exp = vararg_exp[0]
                else:
                    raise MochiSyntaxError(exp, self.filename)
            elif varkwarg_count:
                VKWARGIndex = arg.index(VKWARG)
                arg_exps = arg[:VKWARGIndex]
                varkwarg_exp = arg[VKWARGIndex + 1:]
                if len(varkwarg_exp) == 1:
                    varkwarg_exp = varkwarg_exp[0]
                else:
                    raise MochiSyntaxError(exp, self.filename)
            else:
                arg_exps = arg

        args = self._translate_args(arg_exps)
        if vararg_exp is None:
            vararg = None
        else:
            vararg = ast.arg(arg=vararg_exp.name,
                             annotation=None) if GE_PYTHON_34 else vararg_exp.name

        if varkwarg_exp is None:
            varkwarg = None
        else:
            varkwarg = ast.arg(arg=varkwarg_exp.name,
                               annotation=None) if GE_PYTHON_34 else varkwarg_exp.name

        # 末尾のS式をreturnに変換する。
        # 末尾がif式やdo式の場合はその中まで変換する。
        exp = tuple(exp[:3]) + self._tail_to_return_s(exp[3:])  # TODO 最後が変数参照だと戻り値がNoneになる。
        body = self._translate_sequence(exp[3:])

        if varkwarg is not None:
            node = ast.parse('{0} = pmap({0})'.format(varkwarg.arg if GE_PYTHON_34 else varkwarg))
            body = node.body + body

        pre = []
        decorator_list = []
        if decorator_exps is not None:
            for decoratorExp in decorator_exps:
                pre_deco, value_deco = self.translate(decoratorExp, False)
                pre.extend(pre_deco)
                decorator_list.append(value_deco)

        defn = ast.FunctionDef(name=id,
                               args=ast.arguments(args=args,
                                                  vararg=vararg,
                                                  varargannotation=None,
                                                  kwonlyargs=[], kwarg=varkwarg,
                                                  kwargannotation=None,
                                                  defaults=[], kw_defaults=[],
                                                  lineno=exp[1].lineno,
                                                  col_offset=0),
                               body=body,
                               decorator_list=decorator_list,
                               returns=None,
                               lineno=exp[1].lineno,
                               col_offset=0)

        defn = self._tail_recursion_optimize(self._check_duplicated_binding(defn))
        pre.append(defn)
        _, ref = self.translate_ref(exp[1])
        return pre, ref

    def _generate_new_method_def(self, cls_name, members_exp):
        member_names = tuple(map(lambda member_exp: member_exp if isinstance(member_exp, Symbol) else member_exp[0],
                                 members_exp))
        check_type_sexpr = ()
        for member_exp in members_exp:
            if issequence_except_str(member_exp):
                member_name_exp, member_type_exp = member_exp
                check_type_sexpr += ((Symbol('if'), (Symbol('not'),
                                                     (Symbol('isinstance'), member_name_exp, member_type_exp)),
                                      (Symbol('raise'), (Symbol('TypeError'),
                                                         member_name_exp.name +
                                                         ' is not an instance of ' +
                                                         str(member_type_exp)))),)
        return (Symbol('def'), Symbol('__new__'), (Symbol('cls'),) + member_names,
                (Symbol('val'), Symbol('super_cls'), (Symbol('super'), Symbol('cls'), Symbol('cls')))) + \
               check_type_sexpr + ((Symbol('super_cls.__new__'), Symbol('cls')) + member_names,)

    @syntax('vector')
    def translate_vector(self, exp):
        if not (len(exp) == 3 and isinstance(exp[1], Symbol)):
            raise MochiSyntaxError(exp, self.filename)

        _, vector_name, item_type = exp
        pre = []
        item_type_pre, item_type_value = self.translate(item_type, False)
        pre.extend(item_type_pre)
        lineno = vector_name.lineno
        col_offset = 0
        vector_def = ast.ClassDef(name=vector_name.name,
                                  bases=[ast.Name(id='CheckedPVector',
                                                  ctx=ast.Load(),
                                                  lineno=lineno,
                                                  col_offset=col_offset)],
                                  keywords=[],
                                  starargs=None,
                                  kwargs=None,
                                  body=[ast.Assign(targets=[ast.Name(id='__type__',
                                                                     ctx=ast.Store(),
                                                                     lineno=lineno,
                                                                     col_offset=col_offset)],
                                                   value=item_type_value,
                                                   lineno=lineno,
                                                   col_offset=col_offset)],
                                  decorator_list=[],
                                  lineno=lineno,
                                  col_offset=col_offset)

        pre.append(vector_def)
        _, def_ref = self.translate_ref(vector_name)
        return pre, def_ref

    @syntax('record')
    def translate_record(self, exp):
        exp_length = len(exp)
        if not (exp_length > 2 and isinstance(exp[1], Symbol)):
            raise MochiSyntaxError(exp, self.filename)

        if not ((isinstance(exp[2], Symbol) and issequence_except_str(exp[3])) or issequence_except_str(exp[2])):
            raise MochiSyntaxError(exp, self.filename)

        record_name = exp[1]  # Symbol
        parent_record = exp[2] if isinstance(exp[2], Symbol) else RECORD_SYM  # Symbol
        members_exp = exp[3] if isinstance(exp[2], Symbol) else exp[2]  # Sequence
        body_exps = exp[4:] if isinstance(exp[2], Symbol) else exp[3:]  # Sequence
        lineno = record_name.lineno
        col_offset = record_name.col_offset
        pre = []
        members = []
        body = [ast.Assign(targets=[ast.Name(id='__slots__',
                                             lineno=record_name.lineno,
                                             col_offset=record_name.col_offset,
                                             ctx=ast.Store())],
                           value=ast.Tuple(elts=[],
                                           lineno=record_name.lineno,
                                           col_offset=record_name.col_offset,
                                           ctx=ast.Load()),
                           lineno=record_name.lineno,
                           col_offset=record_name.col_offset)]

        for member_exp in members_exp:
            if issequence_except_str(member_exp):
                if len(member_exp) != 2 and not isinstance(member_exp[0], Symbol):
                    raise MochiSyntaxError(member_exp, self.filename)
                member_name_exp, member_type_exp = member_exp
                member_pre, member = self.translate(member_name_exp.name, False)
                pre.extend(member_pre)
                members.append(member)
            else:
                member_pre, member = self.translate(member_exp.name, False)
                pre.extend(member_pre)
                members.append(member)

        new_method_def = self._generate_new_method_def(record_name, members_exp)
        if new_method_def is not None:
            body_exps = (new_method_def,) + body_exps

        for body_exp in body_exps:
            if isinstance(body_exp, (tuple, list)) and body_exp[0] == DEF:
                defun_pre, _ = self.translate_defun(body_exp)
                pre.extend(defun_pre[:-1])
                body.append(defun_pre[-1])
            else:
                body_pre, body_py_exp = self.translate(body_exp)
                pre.extend(body_pre)
                body.append(body_py_exp)

        if parent_record is not RECORD_SYM:
            record_def = ast.ClassDef(name=record_name.name,
                                      bases=[ast.Call(func=ast.Name(id='immutable',
                                                                    lineno=lineno,
                                                                    col_offset=col_offset,
                                                                    ctx=ast.Load()),
                                                      args=[
                                                          ast.Tuple(elts=members,
                                                                    lineno=parent_record.lineno,
                                                                    col_offset=parent_record.col_offset,
                                                                    ctx=ast.Load()),
                                                          ast.Str(s=record_name.name,
                                                                  lineno=lineno,
                                                                  col_offset=col_offset)],
                                                      keywords=[],
                                                      starargs=None,
                                                      kwargs=None,
                                                      lineno=parent_record.lineno,
                                                      col_offset=parent_record.col_offset),
                                             ast.Name(id=parent_record.name,
                                                      lineno=parent_record.lineno,
                                                      col_offset=parent_record.col_offset,
                                                      ctx=ast.Load())],
                                      keywords=[],
                                      starargs=None,
                                      kwargs=None,
                                      lineno=lineno,
                                      col_offset=col_offset,
                                      body=body,
                                      decorator_list=[])
        else:
            record_def = ast.ClassDef(name=record_name.name,
                                      bases=[ast.Call(func=ast.Name(id='immutable',
                                                                    lineno=lineno,
                                                                    col_offset=col_offset,
                                                                    ctx=ast.Load()),
                                                      args=[
                                                          ast.Tuple(elts=members,
                                                                    lineno=parent_record.lineno,
                                                                    col_offset=parent_record.col_offset,
                                                                    ctx=ast.Load()),
                                                          ast.Str(s=record_name.name,
                                                                  lineno=lineno,
                                                                  col_offset=col_offset)],
                                                      keywords=[],
                                                      starargs=None,
                                                      kwargs=None,
                                                      lineno=parent_record.lineno,
                                                      col_offset=parent_record.col_offset)],
                                      keywords=[],
                                      starargs=None,
                                      kwargs=None,
                                      lineno=lineno,
                                      col_offset=col_offset,
                                      body=body,
                                      decorator_list=[])

        pre.append(record_def)
        _, def_ref = self.translate_ref(record_name)
        return pre, def_ref

    @syntax('class')
    def translate_class(self, exp):
        exp_length = len(exp)
        if not (exp_length > 2 and isinstance(exp[1], Symbol)):
            raise MochiSyntaxError(exp, self.filename)

        if not ((isinstance(exp[2], Symbol) and issequence_except_str(exp[3])) or issequence_except_str(exp[2])):
            raise MochiSyntaxError(exp, self.filename)

        class_name = exp[1]  # Symbol
        parent_class = exp[2] if isinstance(exp[2], Symbol) else OBJECT_SYM  # Symbol
        members_exp = exp[3] if isinstance(exp[2], Symbol) else exp[2]  # Sequence
        body_exps = exp[4:] if isinstance(exp[2], Symbol) else exp[3:]  # Sequence
        lineno = class_name.lineno
        col_offset = class_name.col_offset
        pre = []
        members = []

        for member_exp in members_exp:
            member_pre, member = self.translate(member_exp.name, False)
            pre.extend(member_pre)
            members.append(member)

        body = [ast.Assign(targets=[ast.Name(id='__slots__',
                                             lineno=lineno,
                                             col_offset=col_offset,
                                             ctx=ast.Store())],
                           value=ast.Tuple(elts=members,
                                           lineno=lineno,
                                           col_offset=col_offset,
                                           ctx=ast.Load()),
                           lineno=class_name.lineno,
                           col_offset=class_name.col_offset)]

        # check_duplicated_binding_name_outer(class_name, self.filename) # TODO

        for body_exp in body_exps:
            if isinstance(body_exp, (tuple, list)) and body_exp[0] == DEF:
                defun_pre, _ = self.translate_defun(body_exp)
                pre.extend(defun_pre[:-1])
                body.append(defun_pre[-1])
            else:
                body_pre, body_py_exp = self.translate(body_exp)
                pre.extend(body_pre)
                body.append(body_py_exp)

        class_def = ast.ClassDef(name=class_name.name,
                                 bases=[ast.Name(id=parent_class.name,
                                                 lineno=parent_class.lineno,
                                                 col_offset=parent_class.col_offset,
                                                 ctx=ast.Load())],
                                 keywords=[],
                                 starargs=None,
                                 kwargs=None,
                                 lineno=lineno,
                                 col_offset=col_offset,
                                 body=body,
                                 decorator_list=[])
        pre.append(class_def)
        _, def_ref = self.translate_ref(class_name)
        return pre, def_ref

    @syntax('import')
    def translate_import(self, exp):
        if len(exp) < 2:
            raise MochiSyntaxError(exp, self.filename)
        names = [ast.alias(name=import_sym.name,
                           asname=None,
                           lineno=import_sym.lineno,
                           col_offset=import_sym.col_offset) for import_sym in exp[1:]]
        return (ast.Import(names=names,
                           lineno=exp[0].lineno,
                           col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1]

    @syntax('from_import')
    def translate_from(self, exp):
        if len(exp) < 3:
            raise MochiSyntaxError(exp, self.filename)
        names = [ast.alias(name=import_sym.name,
                           asname=None,
                           lineno=import_sym.lineno,
                           col_offset=import_sym.col_offset) for import_sym_names in exp[2:]
                                                             for import_sym in import_sym_names]
        return (ast.ImportFrom(module=exp[1].name,
                               names=names,
                               level=0,
                               lineno=exp[0].lineno,
                               col_offset=exp[0].col_offset),), self.translate_ref(NONE_SYM)[1]

    def is_macro(self, exp):
        if not issequence_except_str(exp):
            return False
        if not isinstance(exp[0], Symbol):
            return False
        return translator.has_macro(exp[0].name)

    def has_macro(self, name):
        return name in self.macro_table

    def add_macro(self, name, macro_func):
        self.macro_table[name] = macro_func

    def expand_macro(self, name, args):
        if name not in global_env:
            code = compile(
                ast.Module(body=[self.macro_table[name]]), self.filename, 'exec', optimize=2)
            exec(code, global_env)
        macro_func = global_env[name]
        keyword_params = {}
        newargs = []
        for arg in args:
            # if type(arg) is Keyword:
            #    keyword_params = arg
            #    continue
            newargs.append(arg)
            args = newargs
        return tuple_it(macro_func(*args, **keyword_params))

    @syntax('mac')
    def translate_def_macro(self, exp):
        macro_func_ast_seq, ref = self.translate_defun(exp)
        self.add_macro(exp[1].name, macro_func_ast_seq[0])
        return macro_func_ast_seq, ref

    def translate_apply(self, exp):
        pre = []
        callable_pre, callable_value = self.translate(exp[0], False)
        pre.extend(callable_pre)

        args = []
        keyword_args = []
        keyword_arg_exps = []
        arg_exps = exp[1:]
        for i, argexp in enumerate(arg_exps):
            if type(argexp) is Keyword:
                keyword_arg_exps = arg_exps[i:]
                arg_exps = arg_exps[:i]
                break

        for argexp in arg_exps:
            arg_pre, arg_value = self.translate(argexp, False)
            pre.extend(arg_pre)
            args.append(arg_value)

        for argKey, argExp in chunks(keyword_arg_exps, 2):
            if type(argKey) is not Keyword:
                raise MochiSyntaxError(argKey, self.filename)
            arg_pre, arg_value = self.translate(argExp, False)
            pre.extend(arg_pre)
            keyword_args.append(ast.keyword(arg=argKey.name,
                                            value=arg_value))

        value = ast.Call(func=callable_value,
                         args=args,
                         keywords=keyword_args,
                         starargs=None,
                         kwargs=None,
                         lineno=callable_value.lineno,
                         col_offset=0)
        return pre, value

    @syntax('fn')
    def translate_fn(self, exp):
        if not (len(exp) >= 3):
            raise MochiSyntaxError(exp, self.filename)
        name_symbol = get_temp_name()
        defexp = []
        defexp.append('dummy')
        defexp.append(name_symbol)
        defexp.extend(exp[1:])
        return self.translate_defun(defexp, visible=False)

    @syntax('make_tuple')
    @syntax('make_pvector')
    def translate_make_pvector(self, exp):
        make_tuple_symbol, *items = exp
        pre = []
        elts = []
        lineno = make_tuple_symbol.lineno
        col_offset = make_tuple_symbol.col_offset
        for item in items:
            item_pre, item_value = self.translate(item, False)
            pre.extend(item_pre)
            elts.append(item_value)
        tuple_ast = ast.Tuple(elts=elts,
                              ctx=ast.Load(),
                              lineno=lineno,
                              col_offset=col_offset)
        return pre, ast.Call(func=ast.Name(id='pvector',
                                           ctx=ast.Load(),
                                           lineno=lineno,
                                           col_offset=col_offset),
                             args=[tuple_ast],
                             keywords=[],
                             starargs=None,
                             kwargs=None,
                             lineno=lineno,
                             col_offset=col_offset)

    @syntax('make_list')
    def translate_make_list(self, exp):
        make_list_symbol, *items = exp
        pre = []
        elts = []
        lineno = make_list_symbol.lineno
        col_offset = make_list_symbol.col_offset
        for item in items:
            item_pre, item_value = self.translate(item, False)
            pre.extend(item_pre)
            elts.append(item_value)
        return pre, ast.List(elts=elts,
                             ctx=ast.Load(),
                             lineno=lineno,
                             col_offset=col_offset)

    @syntax('quote')
    def translate_quote(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        quote_symbol, value = exp
        if issequence_except_str(value):
            pre = []
            elts = []
            lineno = quote_symbol.lineno
            col_offset = quote_symbol.col_offset
            for item in value:
                item_pre, item_value = self.translate(item, False, True)
                pre.extend(item_pre)
                elts.append(item_value)
            return pre, ast.Tuple(elts=elts,
                                  ctx=ast.Load(),
                                  lineno=lineno,
                                  col_offset=col_offset)
        elif isinstance(value, Symbol):
            lineno = value.lineno
            col_offset = value.col_offset
            return (EMPTY, ast.Call(func=ast.Name(id='Symbol',
                                                  ctx=ast.Load(),
                                                  lineno=lineno,
                                                  col_offset=col_offset),
                                    args=[ast.Str(s=value.name,
                                                  lineno=lineno,
                                                  col_offset=col_offset)],
                                    keywords=[],
                                    starargs=None,
                                    kwargs=None,
                                    lineno=lineno,
                                    col_offset=col_offset))
        else:
            return self.translate_atom(value)

    def call_unquote_splicing(self, list_ast):
        lineno = list_ast.lineno
        col_offset = list_ast.col_offset
        return ast.Call(func=ast.Name(id='unquote_splice',
                                      ctx=ast.Load(),
                                      lineno=lineno,
                                      col_offset=col_offset),
                        args=[list_ast],
                        keywords=[],
                        starargs=None,
                        kwargs=None,
                        lineno=lineno,
                        col_offset=col_offset)

    @syntax('quasiquote')
    def translate_quasi_quote(self, exp):
        if len(exp) != 2:
            raise MochiSyntaxError(exp, self.filename)
        quote_symbol, value = exp
        if issequence_except_str(value):
            if len(value) >= 1 and type(value[0]) is Symbol:
                if value[0].name == 'unquote':
                    return self.translate(value[1], False)
                elif value[0].name == 'unquote_splicing':
                    value1_pre, value1_body = self.translate(value[1], False)
                    return (tuple(value1_pre),
                            [self.translate((Symbol('quote'),
                                             Symbol('_mochi_unquote_splicing')), False), value1_body])
            lineno = quote_symbol.lineno
            col_offset = quote_symbol.col_offset
            pre = []
            elts = []
            for item in value:
                item_pre, item_value = self.translate(item, True, False, True)
                pre.extend(item_pre)
                elts.append(item_value)
            return (pre, self.call_unquote_splicing(ast.Tuple(elts=flatten_list(elts),
                                                              ctx=ast.Load(),
                                                              lineno=lineno,
                                                              col_offset=col_offset)))
        elif isinstance(value, Symbol):
            lineno = value.lineno
            col_offset = value.col_offset
            return (EMPTY, ast.Call(func=ast.Name(id='Symbol',
                                                  ctx=ast.Load(),
                                                  lineno=lineno,
                                                  col_offset=col_offset),
                                    args=[ast.Str(s=value.name,
                                                  lineno=lineno,
                                                  col_offset=col_offset)],
                                    keywords=[],
                                    starargs=None,
                                    kwargs=None,
                                    lineno=lineno,
                                    col_offset=col_offset))
        else:
            return self.translate_atom(value)

    def enclose(self, py_ast, flag):

        if isinstance(py_ast, expr_and_stmt_ast):
            return py_ast

        if issequence_except_str(py_ast):
            ast_list = []
            for item in py_ast:
                if isinstance(item, expr_and_stmt_ast):
                    ast_list.append(item)
                elif issequence_except_str(item):
                    # ((pre_foo ...), value_bar) => ((enclose(pre_foo) ...), value_bar)
                    newitem = []
                    for itemitem in item:
                        if isinstance(itemitem, expr_and_stmt_ast):
                            newitem.append(itemitem)
                        else:
                            newitem.append(
                                ast.Expr(value=itemitem, lineno=itemitem.lineno, col_offset=0))
                    ast_list.append(newitem)
                else:
                    if flag:
                        ast_list.append(ast.Expr(value=item, lineno=item.lineno, col_offset=0))
                    else:
                        ast_list.append(item)
            return ast_list
        else:
            if flag and (not isinstance(py_ast, expr_and_stmt_ast)):
                return ast.Expr(value=py_ast, lineno=py_ast.lineno, col_offset=py_ast.col_offset)
            else:
                return py_ast

    def translate(self, exp, enclose=True, quoted=False, quasi_quoted=False):
        if quoted:
            quoted_exp = [Symbol('quote'), exp]
            return self.translate_quote(quoted_exp)

        if quasi_quoted:
            quoted_exp = [Symbol('quote'), exp]
            return self.translate_quasi_quote(quoted_exp)

        if not issequence_except_str(exp):
            return self.enclose(self.translate_atom(exp), enclose)

        if type(exp[0]) is Symbol:
            if exp[0].name == 'load':
                tl = Translator(self.macro_table)
                return tl.translate_loaded_files(exp[1:])
            elif exp[0].name == 'require':
                tl = Translator(self.macro_table)
                return tl.translate_required_files(exp[1:])
            elif exp[0].name in self.macro_table:
                return self.translate(self.expand_macro(exp[0].name, exp[1:]), enclose)
            elif exp[0].name in syntax_table:
                return self.enclose(syntax_table[exp[0].name](self, exp), enclose)
        return self.enclose(self.translate_apply(exp), enclose)


class Scope(dict):
    def __init__(self, quasi=False):
        self.quasi = quasi
        self.children = []

    def add_child(self, child):
        self.children.append(child)

    def merge_children(self):
        merge_map = {}
        for child in self.children:
            merge_map.update(child)
        for binding_name in merge_map.keys():
            self.add_binding_name(binding_name, merge_map[binding_name])

    def add_binding_name(self, binding_name, file_name):
        # TODO _gs
        if not binding_name.startswith('_gs') and binding_name in self:
            raise DuplicatedDefError(binding_name, file_name)
        self[binding_name] = file_name
        return binding_name

# 'translator' and 'bindingNameSetStack' is a global variable.
translator = Translator()
global_scope = Scope()
for name in global_env.keys():
    global_scope.add_binding_name(name, "<builtin>")
binding_name_set_stack = [global_scope]


INIT_CODE = """
from mochi.core import init

init()
"""


def ast2py(ast, mochi_env='', add_init=False):
    """Translate Python AST to Python code.

    :param ast: Python AST
    :param env: Mochi environment such monkey patch
    :param add_init: Add Mochi intialization or not
    :return: Python source code
    """
    if mochi_env:
        env_ast = translator.translate_block(mochi_env)
        ast.body = env_ast.body + ast.body
    source = astunparse.unparse(ast)
    if add_init:
        source = INIT_CODE + source
    return source
