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

from abc import ABCMeta, abstractmethod

from shapely.affinity import translate

from image import ImageWindow
from logging import Loggable, SilentLogger
from information import ChainInformation, WorkflowInformationCollection, WorkflowInformation

__author__ = "Romain Mormont <romainmormont@hotmail.com>"
__version__ = "0.1"


class ImageProvider(Loggable):
    """An image provider is a class which generates a set of images to be processed by a workflow chein
    """
    __metaclass__ = ABCMeta

    def __init__(self, silent_fail=False, logger=SilentLogger()):
        """Constructs instances of ImageProvider

        Parameters
        ----------
        silent_fail: bool
            True for putting the image provider in silent fail mode. In this situation, when an image cannot be
            extracted, the provider simply ignore the error and skip the image. Otherwise, when set to False,
            the provider raises an error when an image extraction fails.
        logger: Logger (optional, default: SilentLogger)
            A logger object
        """
        Loggable.__init__(self, logger)
        self._silent_fail = silent_fail

    @abstractmethod
    def get_images(self):
        """
        Return the images to be processed by instances of the workflow

        Returns
        -------
        images: iterable (subtype: Image)
            An iterable of images

        Exceptions
        ----------
        ImageExtractionException:
            Raised when an image cannot be extracted. This error is never raised when the image provider is in
            silent_fail mode. In this situation, the provider fetches as many images as possible and returns only the
            successfully fetched images in the array.
        """
        pass


class WorkflowExecutor(Loggable):
    """A class for encapsulating the execution of a workflow. It provides two abstract methods to implement for
    generating the images to process and to post-process the generated data after each run of the workflow.
    The execution of the workflow executor is something like :

        .execute(image, info) :
            {get_images} -- images --> [ image --> {workflow.process} -- data --> {after} ] --> workflow_info
    """
    __metaclass__ = ABCMeta

    def __init__(self, workflow, logger=SilentLogger()):
        """Constructs a WorkflowExecutor for the given workflow object

        Parameters
        ----------
        workflow: SLDCWorkflow
            The workflow to execute
        logger: Logger (optional, default: a SilentLogger instance)
            A logger object
        """
        Loggable.__init__(self, logger)
        self._workflow = workflow

    def execute(self, image, workflow_info_collection):
        """Execute the workflow with the images generated by get_images

        Parameters
        ----------
        image: Image
            The base image from which must be extracted the sub images to process
        workflow_info_collection: WorkflowInformationCollection
            The information generated by the previous workflow in a workflow chain

        Returns
        -------
        workflow_information: WorkflowInformation
            The workflow information object containing the data generated by all the executions of the stored workflow
        """
        workflow_information = WorkflowInformation([], [], [], [], None)
        images = self.get_images(image, workflow_info_collection)
        for sub_image in images:
            returned_info = self._workflow.process(sub_image)
            self.after(sub_image, returned_info)
            workflow_information.merge(returned_info)
        return workflow_information

    @abstractmethod
    def get_images(self, image, workflow_info_collection):
        """Given the workflow_info_collection produces by execution of sldc workflows, produces images objects for the
        next execution

        Parameters
        ----------
        image: Image
            The image processed by the previous step
        workflow_info_collection: WorkflowInformationCollection
            The information about the execution of the workflow until now

        Returns
        -------
        images: list of Image
            The list of images to be processed by the workflow
        """
        pass

    @abstractmethod
    def after(self, sub_image, workflow_information):
        """A callback to execute after each execution of the workflow. May update the workflow information object.
        Parameters
        ----------
        sub_image: Image (or subclass)
            The image processed by the workflow
        workflow_information: WorkflowInformation
            The workflow information produced by the workflow execution
        """
        pass


class FullImageWorkflowExecutor(WorkflowExecutor):
    """A workflow executor which simply process the full image it is passed and doesn't post-process the generated data.
    """
    def __init__(self, workflow, logger=SilentLogger()):
        WorkflowExecutor.__init__(self, workflow, logger)

    def get_images(self, image, workflow_info_collection):
        return [image]

    def after(self, sub_image, workflow_information):
        return


class PolygonTranslatorWorkflowExecutor(WorkflowExecutor):
    """A workflow executor that moves the polygons generated by the workflow in the base image coordinate system by
    translating them.
    """
    __metaclass__ = ABCMeta

    def __init__(self, workflow, logger=SilentLogger()):
        WorkflowExecutor.__init__(self, workflow, logger)

    def after(self, sub_image, workflow_information):
        if not isinstance(sub_image, ImageWindow):  # check if there is an offset
            return
        # translate all the polygons so that their reference system if the base image and not the window
        offset_x, offset_y = sub_image.abs_offset
        polygons = workflow_information.polygons
        for i, polygon in enumerate(polygons):
            polygons[i] = translate(polygon, offset_x, offset_y)


class PostProcessor(Loggable):
    """A post processor is a class encapsulating the processing of the results of several SLDCWorkflow executions.
    """
    __metaclass__ = ABCMeta

    def __init__(self, logger=SilentLogger()):
        """Builds a PostProcessor object
        Parameters
        ----------
        logger: Logger (optional, default: a SilentLogger instance)
            A logger object
        """
        Loggable.__init__(self, logger)

    @abstractmethod
    def post_process(self, image, workflow_info_collection):
        """Actually process the results

        Parameters
        ----------
        image: Image
            The image processed by the previous step
        workflow_info_collection: WorkflowInformationCollection
            The information about the execution of the workflow
        """
        pass


class WorkflowChain(Loggable):
    """This class encapsulates the sequential execution of several instances of the sldc workflow on the same image.
    A processing chain might look like this :

    {ImageProvider} --images--> {WorkflowExecutor1} -- workflow information
            --> {WorkflowExecutor2} - ... -> {PostProcessor}
    """

    def __init__(self, image_provider, executors, post_processor, n_jobs=1, logger=SilentLogger()):
        """Constructor for WorkflowChain objects

        Parameters
        ----------
        image_provider: ImageProvider
            An image provider that will provide the images to be processed by the first workflow
        executors: iterable (subtype: WorkflowExecutors)
            The first instance of the workflow to be applied
        post_processor: PostProcessor
            The post-processor to execute when an image has gone through the whole processing chain
        n_jobs: int (optional, default: 1)
            The number of jobs that can be used to process the images in parallel, -1 for using the number of available
            cores
        logger: Logger (optional, default: a SilentLogger instance)
            A logger object
        """
        Loggable.__init__(self, logger)
        self._post_processor = post_processor
        self._image_provider = image_provider
        self._workflow_executors = executors
        self._chain_information = ChainInformation()
        self._n_jobs = n_jobs

    def execute(self):
        """Execute the workflow chain over the images generated by the image provider
        """
        images = self._image_provider.get_images()
        for i, image in enumerate(images):
            self._process_image(image, i)

    def _process_image(self, image, image_nb):
        """Execute the processing of the image_nb th image
        Parameters
        ----------
        image: Image
            The image to process
        image_nb: int
            The number of the image to be processed
        """
        self.logger.info("WorkflowChain : start processing image #{}.".format(image_nb + 1))
        collection = WorkflowInformationCollection()
        for i, executor in enumerate(self._workflow_executors):
            self.logger.info("WorkflowChain : start workflow {} for image #{}".format(i + 1, image_nb + 1))
            collection.append(executor.execute(image, collection))
        self.logger.info("WorkflowChain : post-processing generated data for image #{}".format(image_nb + 1))
        self._post_processor.post_process(image, collection)
        # self._chain_information.register_workflow_collection(collection, image_nb)  # TODO thread safe

@@ -9490,109 +9490,4 @@
 on)%0A
-        # self._chain_information.register_workflow_collection(collection, image_nb)  # TODO thread safe%0A

remove comment