#!/usr/bin/env python
##############################################################################
#
# diffpy.morph      by DANSE Diffraction group
#                   Simon J. L. Billinge
#                   (c) 2010 Trustees of the Columbia University
#                   in the City of New York.  All rights reserved.
#
# File coded by:    Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
"""MorphChain -- Chain of morphs executed in order.
"""
[docs]
class MorphChain(list):
    """Class for chaining morphs together.
    This class is a queue of morphs that get executed in order via the 'morph'
    method. This class derives from the built-in list, and list methods are
    used to modify the queue.
    This derives from list and relies on its methods where possible.
    Instance Attributes
    -------------------
    config: dict
        All configuration variables.
    Properties
    ----------
    x_morph_in
        Last morph input x data.
    y_morph_in
        Last morph input y data.
    x_morph_out
        Last morph result x data.
    y_morph_out
        Last morph result y data.
    x_target_in
        Last target input x data.
    y_target_in
        Last target input y data.
    x_target_out
        Last target result x data.
    y_target_out
        Last target result y data.
    xy_morph_in
        Tuple of (x_morph_in, y_morph_in) from first morph.
    xy_morph_out
        Tuple of (x_morph_out, y_morph_out) from last morph.
    xy_target_in
        Tuple of (x_target_in, y_target_in) from first morph.
    xy_target_out
        Tuple of (x_target_out, y_target_out) from last morph.
    xyallout
        Tuple of (x_morph_out, y_morph_out, x_target_out, y_target_out)
        from last morph.
    parnames
        Names of parameters collected from morphs (Read only).
    Notes
    -----
        The properties return tuples of None if there are no morphs.
    """
    x_morph_in = property(
        lambda self: None if len(self) == 0 else self[0].x_morph_in
    )
    y_morph_in = property(
        lambda self: None if len(self) == 0 else self[0].y_morph_in
    )
    x_target_in = property(
        lambda self: None if len(self) == 0 else self[0].x_target_in
    )
    y_target_in = property(
        lambda self: None if len(self) == 0 else self[0].y_target_in
    )
    x_morph_out = property(
        lambda self: None if len(self) == 0 else self[-1].x_morph_out
    )
    y_morph_out = property(
        lambda self: None if len(self) == 0 else self[-1].y_morph_out
    )
    x_target_out = property(
        lambda self: None if len(self) == 0 else self[-1].x_target_out
    )
    y_target_out = property(
        lambda self: None if len(self) == 0 else self[-1].y_target_out
    )
    xy_morph_in = property(
        lambda self: (None, None) if len(self) == 0 else self[0].xy_morph_in
    )
    xy_morph_out = property(
        lambda self: (None, None) if len(self) == 0 else self[-1].xy_morph_out
    )
    xy_target_in = property(
        lambda self: (None, None) if len(self) == 0 else self[0].xy_target_in
    )
    xy_target_out = property(
        lambda self: (None, None) if len(self) == 0 else self[-1].xy_target_out
    )
    xyallout = property(
        lambda self: (
            (None, None, None, None) if len(self) == 0 else self[-1].xyallout
        )
    )
    parnames = property(lambda self: set(p for m in self for p in m.parnames))
    def __init__(self, config, *args):
        """Initialize the configuration.
        config: dict
            Configuration dictionary.
        Notes
        -----
            Additional arguments are morphs that will extend the queue of
            morphs.
        """
        self.config = config
        self.extend(args)
        return
[docs]
    def morph(self, x_morph, y_morph, x_target, y_target):
        """Apply the chain of morphs to the input data.
        Parameters
        ----------
        x_morph, y_morph
            Morphed arrays.
        x_target, y_target
            Target arrays.
        Returns
        -------
        tuple
            A tuple of numpy arrays
            (x_morph_out, y_morph_out, x_target_out, y_target_out).
        Notes
        -----
            Config may be altered by the morphs.
        """
        xyall = (x_morph, y_morph, x_target, y_target)
        for morph in self:
            morph.applyConfig(self.config)
            xyall = morph(*xyall)
        return xyall 
    def __call__(self, x_morph, y_morph, x_target, y_target):
        """Alias for morph."""
        return self.morph(x_morph, y_morph, x_target, y_target)
    def __getattr__(self, name):
        """Obtain the value from self.config, when normal lookup fails.
        Parameters
        ----------
        name
            Name of the attribute to be recovered.
        Returns
        -------
        self.config.get(name).
        Raises
        ------
        AttributeError
            Name is not available from self.config.
        """
        if name in self.config:
            return self.config[name]
        else:
            emsg = "Object has no attribute %r" % name
            raise AttributeError(emsg)
    def __setattr__(self, name, val):
        """Set configuration variables to config.
        Parameters
        ----------
        name
            Name of the attribute.
        val
            Value of the attribute.
        """
        if name in self.parnames:
            self.config[name] = val
        else:
            object.__setattr__(self, name, val)
        return 
# End class MorphChain