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


from builtins import hex
from builtins import object
# Copyright (C) 2007 Samuel Abels
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA
import logging

from ..specs import StartTask

LOG = logging.getLogger(__name__)


class WorkflowSpec(object):

    """
    This class represents the specification of a workflow.
    """

    def __init__(self, name=None, filename=None, nostart=False):
        """
        Constructor.
        """
        self.name = name or ''
        self.description = ''
        self.file = filename
        self.task_specs = dict()
        self.start = None
        if not nostart:
            self.start = StartTask(self)

    def _add_notify(self, task_spec):
        """
        Called by a task spec when it was added into the workflow.
        """
        if task_spec.name in self.task_specs:
            raise KeyError('Duplicate task spec name: ' + task_spec.name)
        self.task_specs[task_spec.name] = task_spec
        task_spec.id = self.name + '_' + str(len(self.task_specs))

    def get_task_spec_from_name(self, name):
        """
        Returns the task with the given name.

        :type  name: str
        :param name: The name of the task spec.
        :rtype:  TaskSpec
        :returns: The task spec with the given name.
        """
        return self.task_specs.get(name)

    def get_task_spec_from_id(self, id):
        """
        Returns the task with the given name.

        :type  name: str
        :param name: The name of the task spec.
        :rtype:  TaskSpec
        :returns: The task spec with the given name.
        """
        ret_spec = None
        for x in self.task_specs:
            if self.task_specs[x].id == id:
                ret_spec = self.task_specs[x]
        return ret_spec


    def validate(self):
        """Checks integrity of workflow and reports any problems with it.

        Detects:
        - loops (tasks that wait on each other in a loop)
        :returns: empty list if valid, a list of errors if not
        """
        results = []
        from ..specs import Join

        def recursive_find_loop(task, history):
            current = history[:]
            current.append(task)
            if isinstance(task, Join):
                if task in history:
                    msg = "Found loop with '%s': %s then '%s' again" % (
                        task.name, '->'.join([p.name for p in history]),
                        task.name)
                    raise Exception(msg)
                for predecessor in task.inputs:
                    recursive_find_loop(predecessor, current)

            for parent in task.inputs:
                recursive_find_loop(parent, current)

        for task_id, task in list(self.task_specs.items()):
            # Check for cyclic waits
            try:
                recursive_find_loop(task, [])
            except Exception as exc:
                results.append(exc.__str__())

            # Check for disconnected tasks
            if not task.inputs and task.name not in ['Start', 'Root']:
                if task.outputs:
                    results.append("Task '%s' is disconnected (no inputs)" %
                                   task.name)
                else:
                    LOG.debug("Task '%s' is not being used" % task.name)

        return results

    def serialize(self, serializer, **kwargs):
        """
        Serializes the instance using the provided serializer.

        :type  serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
        :param serializer: The serializer to use.
        :type  kwargs: dict
        :param kwargs: Passed to the serializer.
        :rtype:  object
        :returns: The serialized object.
        """
        return serializer.serialize_workflow_spec(self, **kwargs)

    @classmethod
    def deserialize(cls, serializer, s_state, **kwargs):
        """
        Deserializes a WorkflowSpec instance using the provided serializer.

        :type  serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
        :param serializer: The serializer to use.
        :type  s_state: object
        :param s_state: The serialized workflow specification object.
        :type  kwargs: dict
        :param kwargs: Passed to the serializer.
        :rtype:  WorkflowSpec
        :returns: The resulting instance.
        """

        return serializer.deserialize_workflow_spec(s_state, **kwargs)

    def get_dump(self, verbose=False):
        done = set()

        def recursive_dump(task_spec, indent):
            if task_spec in done:
                return '[shown earlier] %s (%s:%s)' % (
                    task_spec.name, task_spec.__class__.__name__,
                    hex(id(task_spec))) + '\n'

            done.add(task_spec)
            dump = '%s (%s:%s)' % (
                task_spec.name,
                task_spec.__class__.__name__, hex(id(task_spec))) + '\n'
            if verbose:
                if task_spec.inputs:
                    dump += indent + '-  IN: ' + \
                        ','.join(['%s (%s)' % (t.name, hex(id(t)))
                                  for t in task_spec.inputs]) + '\n'
                if task_spec.outputs:
                    dump += indent + '- OUT: ' + \
                        ','.join(['%s (%s)' % (t.name, hex(id(t)))
                                  for t in task_spec.outputs]) + '\n'
            sub_specs = ([task_spec.spec.start] if hasattr(
                task_spec, 'spec') else []) + task_spec.outputs
            for i, t in enumerate(sub_specs):
                dump += indent + '   --> ' + \
                    recursive_dump(
                        t, indent + ('   |   ' if i + 1 < len(sub_specs) else
                                     '       '))
            return dump

        dump = recursive_dump(self.start, '')

        return dump

    def dump(self):
        print(self.get_dump())
