# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/

__authors__ = ["H.Payno"]
__license__ = "MIT"
__date__ = "29/05/2017"

import functools
import logging
import traceback
from collections import namedtuple
import inspect

_logger = logging.getLogger(__file__)

global next_node_free_idF
next_node_free_id = 0


def get_next_node_free_id():
    global next_node_free_id
    _id = next_node_free_id
    next_node_free_id += 1
    return _id


_callback_info = namedtuple("_callback_info",
                            ["callback", "handler", "need_instanciation"])


def trace_unhandled_exceptions(func):
    @functools.wraps(func)
    def wrapped_func(*args, **kwargs):
        try:
            outData = func(*args, **kwargs)
        except Exception as e:
            _logger.exception(e)
            errorMessage = '{0}'.format(e)
            traceBack = traceback.format_exc()
            return WorkflowException(
                    msg=errorMessage,
                    traceBack=traceBack,
                    data=args[1]
            )
        return outData

    return wrapped_func


class Node(object):
    """
    Node in the `.Scheme`. Will be associated to a tomwer process.

    :param callback: pointer to a class or a function or str defining the
                     callback. If the callback is a class then the handler
                     should be defined or the class should have a default
                     'process' function that will be called by default.
    :param int id: unique id of the node.
    :param dict properties: properties of the node
    :param str luigi_task: luigi task associate to this node
    """

    need_stop_join = False
    """flag to stop the node only when receive the 'stop' signal"""

    def __init__(self, callback, id=None, properties=None,
                 error_handler=None):
        self.id = id or get_next_node_free_id()
        """int of the node id"""
        self.properties = properties or {}
        """dict of the node properties"""
        self.upstream_nodes = set()
        """Set of upstream nodes"""
        self.downstream_nodes = set()
        """Set of downstream nodes"""
        self.__process_instance = None
        """"""
        self.callback = callback
        """process instance"""
        self._error_handler = error_handler
        self.outData = None

    @property
    def callback(self):
        return self.__callback

    @callback.setter
    def callback(self, callback):
        need_instanciation = type(callback) is str or inspect.isclass(callback)
        self.__callback = _callback_info(callback=callback, handler=None,
                                         need_instanciation=need_instanciation)

    def isfinal(self):
        return len(self.downstream_nodes) is 0

    def isstart(self):
        return len(self.upstream_nodes) is 0


class WorkflowException(Exception):
    def __init__(self, traceBack="", data=None, msg=None):
        if data is None:
            data = {}
        super(WorkflowException, self).__init__(msg)
        self.errorMessage = msg
        self.data = data
        self.traceBack = traceBack
