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

from aiida.orm.implementation.general.calculation.inline import InlineCalculation

from aiida.orm.data import Data
from aiida.common.exceptions import ModificationNotAllowed
from aiida.common.links import LinkType
from aiida.utils.calculation import add_source_info

__copyright__ = u"Copyright (c), This file is part of the AiiDA platform. For further information please visit http://www.aiida.net/. All rights reserved."
__license__ = "MIT license, see LICENSE.txt file."
__authors__ = "The AiiDA team."
__version__ = "0.7.0.1"


def make_inline(func):
    def wrapped_function(*args, **kwargs):
        """
        This wrapper function is the actual function that is called.
        """
        from django.db import transaction

        # Note: if you pass a lambda function, the name will be <lambda>; moreover
        # if you define a function f, and then do "h=f", h.__name__ will
        # still return 'f'!
        function_name = func.__name__
        if not function_name.endswith('_inline'):
            raise ValueError("The function name that is wrapped must end "
                             "with '_inline', while its name is '{}'".format(
                function_name))

        if args:
            print args
            raise ValueError("Arguments of inline function should be "
                             "passed as key=value")

        # Check the input values
        for k, v in kwargs.iteritems():
            if not isinstance(v, Data):
                raise TypeError("Input data to a wrapped inline calculation "
                                "must be Data nodes")
                # kwargs should always be strings, no need to check
                # if not isinstance(k, basestring):
                #    raise TypeError("")

        # Create the calculation (unstored)
        c = InlineCalculation()
        # Add data input nodes as links
        for k, v in kwargs.iteritems():
            c.add_link_from(v, label=k)

        # Try to get the source code
        add_source_info(c, func)

        # Run the wrapped function
        retval = func(**kwargs)

        # Check the output values
        if not isinstance(retval, dict):
            raise TypeError("The wrapped function did not return a dictionary")
        for k, v in retval.iteritems():
            if not isinstance(k, basestring):
                raise TypeError("One of the key of the dictionary returned by "
                                "the wrapped function is not a string: "
                                "'{}'".format(k))
            if not isinstance(v, Data):
                raise TypeError("One of the values (for key '{}') of the "
                                "dictionary returned by the wrapped function "
                                "is not a Data node".format(k))
            if v.is_stored:
                raise ModificationNotAllowed(
                    "One of the values (for key '{}') of the "
                    "dictionary returned by the wrapped function "
                    "is already stored! Note that this node (and "
                    "any other side effect of the function) are "
                    "not going to be undone!".format(k))

        # Add link to output data nodes
        for k, v in retval.iteritems():
            v.add_link_from(c, label=k, link_type=LinkType.RETURN)

        with transaction.commit_on_success():
            # I call store_all for the Inline calculation;
            # this will store also the inputs, if needed.
            c.store_all(with_transaction=False)
            # As c is already stored, I just call store (and not store_all)
            # on each output
            for v in retval.itervalues():
                v.store(with_transaction=False)

        # Return the calculation and the return values
        return c, retval

    return wrapped_function
