"""
This module provides a method to dynamically compile a simple expression into
a valid function which can be called.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import ast

import six


class Validator(ast.NodeVisitor):
    def __init__(self):
        super(Validator, self).__init__()
        self.uses_msg = False

    def generic_visit(self, node):
        whitelist = (
            ast.Module,
            ast.Expression,
            ast.Expr,
            ast.Load,
            ast.Param,
            ast.slice,
            ast.boolop,
            ast.operator,
            ast.unaryop,
            ast.cmpop,
            ast.expr)

        if isinstance(node, whitelist):
            if isinstance(node, ast.Name) and node.id == 'msg':
                self.uses_msg = True
        else:
            raise ValueError(
                'Syntax of type {!r} is not supported!'.format(type(node)))


def parse(expression_string):
    """
    Takes an expression string as input, validates it, and then transforms it
    into a function which executes the expression.

    EXAMPLE:
    > functor = parse('msg.trajectory.states.x')
    > functor(stamped_msg)
    """
    assert isinstance(expression_string, six.string_types)
    root = ast.parse(expression_string)
    if len(root.body) != 1:
        raise ValueError(
            'Expected a single expression! got={}'.format(len(root.body)))

    validator = Validator()
    for node in ast.walk(root):
        validator.visit(node)
    if not validator.uses_msg:
        raise ValueError(
            'Your expression is provided a single input, `msg`, of type '
            'StampedMessageBase. You must use it!')

    expr_node = root.body[0]
    if isinstance(expr_node, ast.Expr):
        expr_node = expr_node.value
    msg_node = ast.Name('msg', ast.Param())
    func_node = ast.FunctionDef(
        name='functor',
        args=ast.arguments([msg_node], None, None, []),
        body=[ast.Return(expr_node)],
        decorator_list=[])
    root.body[0] = func_node
    ast.fix_missing_locations(root)

    code = compile(root, '<codegen>', 'exec')
    namespace = {}
    exec(code, namespace)
    return namespace['functor']
