#------------------------------------------------------------------------------
#  Copyright (c) 2011, Enthought, Inc.
#  All rights reserved.
#------------------------------------------------------------------------------
from abc import ABCMeta, abstractmethod

from casuarius import ConstraintVariable, LinearSymbolic, Strength, STRENGTH_MAP

from ..component import Component


# XXX make default space computed better
DEFAULT_SPACE = 10


class Constrainable(object):
    """ Abstract base class for objects that can be laid out by these helpers.

    Minimally, they need to have `top`, `bottom`, `left`, `right` attributes
    that are `LinearSymbolic` instances. They also need a `visible` attribute
    that is a `bool`.

    """
    __metaclass__ = ABCMeta

Constrainable.register(Component)


#------------------------------------------------------------------------------
# Symbolic Constraint Factories
#------------------------------------------------------------------------------

def is_spacer(item):
    """ Return True if a layout item is to be interpreted as a Spacer.

    """
    return isinstance(item, (BaseSpacer, int))


def clear_invisible(items):
    """ Take a list of Components and other layout items and remove invisible
    Components.

    This takes into account redundant spacer objects that may surround invisible
    objects. Spacer objects that appear before an invisible Component will be
    removed.

    Lists that consist solely of anchors and spacers will result in an empty
    list.

    """
    visible = []
    for item in items:
        if isinstance(item, Constrainable) and not item.visible:
            if len(visible) > 0 and is_spacer(visible[-1]):
                visible.pop()
        else:
            visible.append(item)
    if all(is_spacer(item) or isinstance(item, LinearSymbolic) for item in visible):
        visible = []
    return visible


class DeferredConstraints(object):
    """ Abstract base class for objects that will yield lists of constraints
    upon request.

    """
    __metaclass__ = ABCMeta

    def __init__(self, *args, **kwds):
        # __or__() will set these default strength and weight. If provided, they
        # will be combined with the constraints generated by the
        # DeferredConstraints.
        self.default_strength = None
        self.default_weight = None

    @abstractmethod
    def _get_constraint_list(self, container):
        """ Return a list of LinearConstraints.

        Subclasses should override this method to actually yield their
        constraints. Users of instances should call the `get_constraint_list()`
        method which will combine these constraints with the `default_strength`
        and/or the `default_weight` if one or both are provided.

        Parameters
        ----------
        container : Container or None
            The container that owns this DeferredConstraints. Sometimes it can
            be None for contexts in which there may be no Container, as in
            certain nested DeferredConstraints.

        Returns
        -------
        constraint_list : list of LinearConstraints

        """
        raise NotImplementedError

    def get_constraint_list(self, container):
        """ Return a list of LinearConstraints.

        Parameters
        ----------
        container : Container or None
            The container that owns this DeferredConstraints. Sometimes it can
            be None for contexts in which there may be no Container, as in
            certain nested DeferredConstraints.

        Returns
        -------
        constraint_list : list of LinearConstraints

        """
        cn_list = self._get_constraint_list(container)
        if self.default_strength is not None:
            cn_list = [cn | self.default_strength for cn in cn_list]
        if self.default_weight is not None:
            cn_list = [cn | self.default_weight for cn in cn_list]
        return cn_list

    def __or__(self, other):
        """ Set the strength of all of the constraints to a common strength.

        """
        if isinstance(other, (float, int, long)):
            self.default_weight = float(other)
        elif isinstance(other, basestring):
            mapping = STRENGTH_MAP
            if other not in mapping:
                raise ValueError('Invalid strength `%s`' % other)
            self.default_strength = STRENGTH_MAP[other]
        elif isinstance(other, Strength):
            self.default_strength = other
        else:
            msg = 'Strength must be a string. Got %s instead.' % type(other)
            raise TypeError(msg)
        return self


class DeferredConstraintsFunction(DeferredConstraints):
    """ Call a function to get the constraint list upon request.

    """

    def __init__(self, func, *args, **kwds):
        super(DeferredConstraintsFunction, self).__init__(func, *args, **kwds)
        self.func = func
        self.args = args
        self.kwds = kwds

    def _get_constraint_list(self, container):
        return self.func(*self.args, **self.kwds)


class AbutmentHelper(DeferredConstraints):
    """ A deferred constraints helper class that lays out Components
    abutting each other in a given orientation.

    """

    def __init__(self, orientation, *items):
        super(AbutmentHelper, self).__init__(orientation, *items)
        self.orientation = orientation
        self.items = items

    def _get_constraint_list(self, container):
        items = clear_invisible(self.items)
        cns = []
        for f in AbutmentCn.from_items(items, self.orientation):
            cns.extend(f.constraints())
        return cns

    def __repr__(self):
        return '{0}({1})'.format(self.orientation,
            ', '.join(map(repr, self.items)))


class AlignmentHelper(DeferredConstraints):
    """ A deferred constraints helper class that lays out with a given
    anchor to align.

    """

    def __init__(self, anchor, *items):
        super(AlignmentHelper, self).__init__(anchor, *items)
        self.anchor = anchor
        self.items = items

    def _get_constraint_list(self, container):
        cns = []
        for f in AlignmentCn.from_items(self.items, self.anchor):
            cns.extend(f.constraints())
        return cns

    def __repr__(self):
        return 'align({0!r}, {1})'.format(self.anchor,
            ', '.join(map(repr, self.items)))


class LinearBoxHelper(DeferredConstraints, Constrainable):
    """ A DeferredConstraints helper class that lays out Components either
    horizontally or vertically and taking up all of the space in the orthogonal
    direction.

    LinearBoxHelpers can be nested.

    """

    #: A mapping from direction to the order of anchor names on the parent
    #: container to lookup for a pair of items in order to make the constraints
    #: between the border of the container and the first/last items.
    container_direction_map = {
        'horizontal': ('left', 'right'),
        'vertical': ('top', 'bottom'),
    }

    def __init__(self, orientation, *items):
        super(LinearBoxHelper, self).__init__(orientation, *items)
        self.orientation = orientation
        if orientation == 'horizontal':
            self.ortho_orientation = 'vertical'
        elif orientation == 'vertical':
            self.ortho_orientation = 'horizontal'
        else:
            msg = ("Expected 'horizontal' or 'vertical' for orientation. "
                "Got {!r} instead.").format(orientation)
            raise ValueError(msg)
        self.items = items

        for attr in ('top', 'bottom', 'left', 'right'):
            label = '{0}_{1:x}'.format(self.orientation[0]+'box', id(self))
            setattr(self, attr, ConstraintVariable('{0}_{1}'.format(attr, label)))

    @property
    def visible(self):
        """ True if there is a visible item in the list.

        This helps satisfy the `Constrainable` interface.

        """
        visible = clear_invisible(self.items) != []
        return visible

    def _get_constraint_list(self, container):
        items = clear_invisible(self.items)
        if len(items) == 0:
            return []
        first_name, last_name = self.container_direction_map[self.orientation]
        first_boundary = getattr(self, first_name)
        last_boundary = getattr(self, last_name)
        first_name, last_name = self.container_direction_map[self.ortho_orientation]
        first_ortho_boundary = getattr(self, first_name)
        last_ortho_boundary = getattr(self, last_name)
        if container is not None:
            # We have a real container, not just nested inside of another
            # LinearBoxHelper.
            # Check for .contents_top, etc. attributes. If those exist, use them
            # instead.
            if hasattr(container, 'contents_top'):
                constraints = [
                    getattr(self, attr) == getattr(container, 'contents_'+attr)
                        for attr in ('top', 'bottom', 'left', 'right')]
            else:
                constraints = [getattr(self, attr) == getattr(container, attr)
                    for attr in ('top', 'bottom', 'left', 'right')]
            ortho_amount = DEFAULT_SPACE
        else:
            constraints = []
            ortho_amount = 0

        margin_spacer = EqSpacer(ortho_amount)

        if not is_spacer(items[0]):
            pre_along_args = [first_boundary, margin_spacer]
        else:
            # Cannot have two spacers in a row.
            pre_along_args = [first_boundary]

        if not is_spacer(items[-1]):
            post_along_args = [margin_spacer, last_boundary]
        else:
            # Cannot have two spacers in a row.
            post_along_args = [last_boundary]

        along_args = pre_along_args + items + post_along_args
        # FIXME: we should do something intelligent about the alignment along
        # the orientation.
        helpers = [AbutmentHelper(self.orientation, *along_args)]
        for item in items:
            if isinstance(item, Constrainable):
                helpers.append(AbutmentHelper(self.ortho_orientation,
                    first_ortho_boundary,
                    FlexSpacer(ortho_amount),
                    item,
                    FlexSpacer(ortho_amount),
                    last_ortho_boundary))
            if isinstance(item, DeferredConstraints):
                # This is a nested helper, probably another LinearBoxLayout.
                helpers.append(item)

        for helper in helpers:
            constraints.extend(helper.get_constraint_list(None))

        return constraints

    def __repr__(self):
        return '{0}box({1})'.format(self.orientation[0],
            ', '.join(map(repr, self.items)))


class AbstractCnFactory(object):
    """ An abstract constraint factory class. This class is not meant to
    be used directly, but should be subclassed an have it 'constraint'
    method implement which will return a symbolics.LinearConstraint
    instance.

    """
    __metaclass__ = ABCMeta

    @staticmethod
    def validate(items):
        """ A validator staticmethod that insures a sequence of items is 
        appropriate for generating a sequence of linear constraints. The 
        following conditions are verified of the sequence of given items:

            * There are either 0 or else two or more items in the sequence.

            * The first and last items are instances of either
              LinearSymbolic or Constrainable.

            * All of the items in the sequence are instances of 
              LinearSymbolic, Constrainable, Spacer, or int.
    
        If any of the above conditions do not hold, an exception is 
        raised with a (hopefully) useful error message.

        """
        if len(items) == 0:
            return

        if len(items) < 2:
            msg = 'Two or more items required to setup abutment constraints.'
            raise ValueError(msg)
        
        extrema_types = (LinearSymbolic, Constrainable)
        def extrema_test(item):
            return isinstance(item, extrema_types)
        
        item_types = (LinearSymbolic, Constrainable, BaseSpacer, int)
        def item_test(item):
            return isinstance(item, item_types)

        if not all(extrema_test(item) for item in (items[0], items[-1])):
            msg = ('The first and last items of an abutment constraint '
                   'sequence must be anchors or Components. Got %s')
            args = [type(items[0]), type(items[1])]
            raise TypeError(msg % args)
        
        if not all(map(item_test, items)):
            msg = ('The allowed items for an abutment constraint sequence '
                   'are anchors, Components, BaseSpacers, and ints. Got %s')
            args = [type(item) for item in items]
            raise TypeError(msg % args)

    @abstractmethod
    def constraints(self):
        """ An abstract method which must be implemented by subclasses.
        It should return a list of symbolics.LinearConstraint.

        """
        raise NotImplementedError


class BaseCnFactory(AbstractCnFactory):
    """ A base constraint factory class that implements some
    basic common logic. It is not meant to be used directly but
    should rather be subclassed to be useful.

    """
    def __init__(self, first_anchor, spacer, second_anchor):
        """ Create an base constraint instance.

        Parameters
        ----------
        first_anchor : LinearSymbolic
            A symbolic object that can be used in a constraint expression.
        
        spacer : BaseSpacer
            A spacer instance to put space between the items.
        
        second_anchor : LinearSymbolic
            The second anchor for the constraint expression.


        """
        self.first_anchor = first_anchor
        self.spacer = spacer
        self.second_anchor = second_anchor

    def constraints(self):
        """ Returns LinearConstraint instance which is formed through
        an appropriate linear expression for the given space between 
        the anchors.

        """
        return self.spacer.constrain(self.first_anchor, self.second_anchor)


class AbutmentCn(BaseCnFactory):
    """ A CnFactory subclass that represents an abutment constraint, 
    which is a constraint between two anchors of different components
    separated by some amount of space. It provides a 'from_items'
    classmethod which will create a sequence of abutment constraints
    from a sequence of items and a direction.

    """
    #: A mapping from direction to the order of anchor names to 
    #: lookup for a pair of items in order to make the constraint.
    direction_map = {
        'horizontal': ('right', 'left'),
        'vertical': ('bottom', 'top'),
    }

    @classmethod
    def from_items(cls, items, direction):
        """ A classmethod that will create a seqence of abutment 
        constraints given a sequence of items and a direction. The 
        direction must be 'horizontal' or 'vertical'. The order of
        abutment is left-to-right for horizontal direction and 
        top-to-bottom for vertical direction.

        """
        # Make sure the items we'll be dealing with are valid
        # for the algorithm. This is a basic validation. Further
        # error handling is done the _handle_item method.
        cls.validate(items)

        # Grab the tuple of anchor names to lookup for each 
        # pair of items in order to make the connection.
        first_name, second_name = cls.direction_map[direction]
    
        # The list of constraints we'll be creating for the
        # given sequence of items.
        cns = []

        # The list of items is treated as a stack. So we want
        # to first reverse it so the first items are at the top
        # of the stack.
        items = list(reversed(items))

        while items:
            # Grab the item that will provide the first anchor in
            # constraint pair
            first_item = items.pop()

            # first_item will be either a Component or a LinearSymbolic.
            # For the first iteration, this is enfored by the staticmethod
            # 'validate'. For subsequent iterations, this condition is 
            # enforced by the fact that this loop only pushes those types 
            # back onto the stack.
            if isinstance(first_item, Constrainable):
                first_anchor = getattr(first_item, first_name)
            elif isinstance(first_item, LinearSymbolic):
                first_anchor = first_item
            else:
                raise TypeError('This should never happen')
            
            # Grab the next item off the stack. It is either a 
            # Constrainable, LinearSymbolic, Spacer, or int. If it
            # can't provide an anchor, we grab the after that
            # which *should* be able to provide one. If no space
            # is given, we compute a default space.
            next_item = items.pop()
            if isinstance(next_item, BaseSpacer):
                spacer = next_item
                second_item = items.pop()
            elif isinstance(next_item, int):
                spacer = EqSpacer(next_item)
                second_item = items.pop()
            elif isinstance(next_item, Constrainable):
                spacer = EqSpacer()
                second_item = next_item
            elif isinstance(next_item, LinearSymbolic):
                spacer = EqSpacer()
                second_item = next_item
            else:
                raise ValueError('This should never happen')
            
            # If the second_item can't provide an anchor, such as
            # two spacers next to each other, then this is an error
            # condition and we raise an appropriate exception.
            if isinstance(second_item, Constrainable):
                second_anchor = getattr(second_item, second_name)   
            elif isinstance(second_item, LinearSymbolic):
                second_anchor = second_item
            else:
                msg = 'Expected anchor or Constrainable. Got %r instead.'
                raise TypeError(msg % second_item)
            
            # Create the class instance for this constraint
            factory = cls(first_anchor, spacer, second_anchor)

            # If there are still items on the stack, then the second_item
            # will be used as the first_item in the next iteration. 
            # Otherwise, we have exhausted all constraints and can 
            # exit the loop.
            if items:
                items.append(second_item)

            # Finally, store away the created constraint to return
            # to the caller.
            cns.append(factory)

        return cns


class AlignmentCn(BaseCnFactory):
    """ A CnFactory subclass that represents an alignmnent constraint, 
    which is a constraint between two anchors of different components
    which are aligned but may be separated by some amount of space.
    It provides a 'from_items' classmethod which will create a sequence 
    of alignment constraints from a sequence of items and an anchor name.

    """
    @classmethod
    def from_items(cls, items, anchor_name):
        """ A classmethod that will create a seqence of alignment
        constraints given a sequence of items and an anchor name. The 
        anchor name must be 'left', 'right', 'top', 'bottom', 'v_center',
        or 'h_center'. For every item in the sequence, if the item is a
        component, then anchor for the given anchor_name on that component
        will be used. If a LinearSymbolic is given, then that symbolic
        will be used and the anchor_name will be ignored. Specifying space
        between items is allowed.

        """
        # Make sure the items we'll be dealing with are valid
        # for the algorithm. This is a basic validation. Further
        # error handling is done the _handle_item method.
        cls.validate(items)

        # The list of constraints we'll be creating for the
        # given sequence of items.
        cns = []

        # The list of items is treated as a stack. So we want
        # to first reverse it so the first items are at the top
        # of the stack.
        items = list(reversed(items))

        while items:
            # Grab the item that will provide the first anchor in
            # constraint pair
            first_item = items.pop()

            # first_item will be either a Constrainable or a LinearSymbolic.
            # For the first iteration, this is enforced by the staticmethod
            # 'validate'. For subsequent iterations, this condition is 
            # enforced by the fact that this loop only pushes those types 
            # back onto the stack.
            if isinstance(first_item, Constrainable):
                first_anchor = getattr(first_item, anchor_name)
            elif isinstance(first_item, LinearSymbolic):
                first_anchor = first_item
            else:
                raise TypeError('This should never happen')
            
            # Grab the next item off the stack. It is either a 
            # Constrainable, LinearSymbolic, Spacer, or int. If it
            # can't provide an anchor, we grab the after that
            # which *should* be able to provide one. If no space
            # is given, we compute a default space of zero since
            # that is what is typically wanted out of an alignment.
            item = items.pop()
            if isinstance(item, BaseSpacer):
                spacer = item
                second_item = items.pop()
            elif isinstance(item, int):
                spacer = EqSpacer(item)
                second_item = items.pop()
            elif isinstance(item, Constrainable):
                spacer = EqSpacer(0)
                second_item = item
            elif isinstance(item, LinearSymbolic):
                spacer = EqSpacer(0)
                second_item = item
            else:
                raise ValueError('This should never happen')
            
            # If the second_item can't provide an anchor, such as
            # two spacers next to each other, then this is an error
            # condition and we raise an appropriate exception.
            if isinstance(second_item, Constrainable):
                second_anchor = getattr(second_item, anchor_name)   
            elif isinstance(second_item, LinearSymbolic):
                second_anchor = second_item
            else:
                msg = 'Invalid object for second abutment constraint: %s'
                raise TypeError(msg % second_item)

            # Create the class instance for this constraint
            factory = cls(first_anchor, spacer, second_anchor)

            # If there are still items on the stack, then the second_item
            # will be used as the first_item in the next iteration. 
            # Otherwise, we have exhausted all constraints and can 
            # exit the loop.
            if items:
                items.append(second_item)

            # Finally, store away the created constraint to return
            # to the caller.
            cns.append(factory)

        return cns



#------------------------------------------------------------------------------
# Spacers
#------------------------------------------------------------------------------
class BaseSpacer(object):
    """ An abstract base spacer class. Subclasses must implement the
    'constrain' method.

    """
    __metaclass__ = ABCMeta

    strength = None
    weight = None

    @abstractmethod
    def _constrain(self, first_anchor, second_anchor):
        """ An abstract method. Subclasses should implement this method to
        return a list of symbolics.LinearConstraint objects that are appropriate
        to separate the two anchors according to the amount of space represented
        by the spacer.

        """
        raise NotImplementedError

    def constrain(self, first_anchor, second_anchor):
        """ Return list of LinearConstraint objects that are appropriate to
        separate the two anchors according to the amount of space represented by
        the spacer.

        The LinearConstraint will have any strength or weight that has been
        provided by the | operator.

        """
        constraints = self._constrain(first_anchor, second_anchor)
        if self.strength is not None:
            constraints = [cn | self.strength for cn in constraints]
        if self.weight is not None:
            constraints = [cn | self.weight for cn in constraints]
        return constraints


class Spacer(BaseSpacer):
    """ A spacer base class which adds support for storing a value
    which represent the amount of space to use in the constraint.

    """
    def __init__(self, amt=DEFAULT_SPACE, strength=None, weight=None):
        self.amt = max(0, amt)
        self.strength = strength
        self.weight = weight

    def __or__(self, other):
        """ Set the strength of all of the constraints to a common strength.

        """
        strength = self.strength
        weight = self.weight
        if isinstance(other, (float, int, long)):
            weight = float(other)
        elif isinstance(other, basestring):
            mapping = STRENGTH_MAP
            if other not in mapping:
                raise ValueError('Invalid strength `%s`' % other)
            strength = STRENGTH_MAP[other]
        elif isinstance(other, Strength):
            strength = other
        else:
            msg = 'Strength must be a string. Got %s instead.' % type(other)
            raise TypeError(msg)
        return type(self)(amt=self.amt, strength=strength, weight=weight)


class FlexSpacer(Spacer):
    """ A spacer which represents a space with a hard minimum, but also a weaker
    preference for being that minimum.

    """
    def __init__(self, amt=DEFAULT_SPACE, min_strength='medium', min_weight=1.5, eq_strength='medium', eq_weight=1.25):
        self.amt = max(0, amt)
        self.min_strength = min_strength
        self.min_weight = min_weight
        self.eq_strength = eq_strength
        self.eq_weight = eq_weight

    def __or__(self, other):
        """ Do not allow resetting of the strength or weight since it is
        ambiguous.

        """
        raise TypeError("FlexSpacers must have their strengths set in the "
            "constructor.")

    def _constrain(self, first_anchor, second_anchor):
        """ Constraints of the form (anchor_1 + space <= anchor_2) and
        (anchor_1 + space == anchor_2)

        """
        return [
            ((first_anchor + self.amt) <= second_anchor) | self.min_strength | self.min_weight,
            ((first_anchor + self.amt) == second_anchor) | self.eq_strength | self.eq_weight,
        ]

    def constrain(self, first_anchor, second_anchor):
        """ Return list of LinearConstraint objects that are appropriate to
        separate the two anchors according to the amount of space represented by
        the spacer.

        """
        return self._constrain(first_anchor, second_anchor)


class EqSpacer(Spacer):
    """ An spacer which represents a fixed amount of space.

    """
    def _constrain(self, first_anchor, second_anchor):
        """ A constraint of the form (anchor_1 + space == anchor_2)

        """
        return [(first_anchor + self.amt) == second_anchor]


class LeSpacer(Spacer):
    """ A spacer which represents a flexible space with a maximum value.

    """
    def _constrain(self, first_anchor, second_anchor):
        """ A constraint of the form (anchor_1 + space >= anchor_2)
        That is, the visible space must be less than or equal to the
        given amount.

        """
        return [(first_anchor + self.amt) >= second_anchor]


class GeSpacer(Spacer):
    """ A spacer which represents a flexible space with a minimum value.

    """
    def _constrain(self, first_anchor, second_anchor):
        """ A constraint of the form (anchor_1 + space >= anchor_2)
        That is, the visible space must be greater than or equal to
        the given amount.

        """
        return [(first_anchor + self.amt) <= second_anchor]


class _space_(BaseSpacer):
    """ A special singleton symbolic, spacer that, alone, represents a 
    flexible space with a minimum value of the default space, but can be 
    combined with the ==, <=, and >= operators to create different types 
    of space.

    """
    def __eq__(self, other):
        if not isinstance(other, int):
            raise TypeError('space can only be created from ints')
        return EqSpacer(other)
    
    def __le__(self, other):
        if not isinstance(other, int):
            raise TypeError('space can only be created from ints')
        return LeSpacer(other)
    
    def __ge__(self, other):
        if not isinstance(other, int):
            raise TypeError('space can only be created from ints')
        return GeSpacer(other)
    
    def _constrain(self, first_anchor, second_anchor):
        """ Returns a constraint that is a flexible amount of space
        with a minimum equal to the default system space.

        """
        spacer = GeSpacer()
        return spacer._constrain(first_anchor, second_anchor)


# The singleton _space_ instance. There is no need for more than one.
_space_ = _space_()


#------------------------------------------------------------------------------
# Layout factory functions
#------------------------------------------------------------------------------
def horizontal(*items):
    """ Create a DeferredConstraints object composed of horizontal abutments
    for the given sequence of items.

    """
    return AbutmentHelper('horizontal', *items)


def vertical(*items):
    """ Create a DeferredConstraints object composed of vertical abutments
    for the given sequence of items.

    """
    return AbutmentHelper('vertical', *items)


def hbox(*items):
    """ Create a DeferredConstraints object composed of horizontal abutments for
    a given sequence of items.

    """
    return LinearBoxHelper('horizontal', *items)


def vbox(*items):
    """ Create a DeferredConstraints object composed of vertical abutments for
    a given sequence of items.

    """
    return LinearBoxHelper('vertical', *items)


def align_left(*items):
    """ Align the left anchors of the given components. Inter-component
    spacing is allowed.

    """
    return AlignmentHelper('left', *items)


def align_right(*items):
    """ Align the right anchors of the given components. Inter-component
    spacing is allowed.
    
    """
    return AlignmentHelper('right', *items)


def align_top(*items):
    """ Align the top anchors of the given components. Inter-component
    spacing is allowed.
    
    """
    return AlignmentHelper('top', *items)


def align_bottom(*items):
    """ Align the bottom anchors of the given components. Inter-component
    spacing is allowed.
    
    """
    return AlignmentHelper('bottom', *items)


def align_v_center(*items):
    """ Align the v_center anchors of the given components. Inter-component
    spacing is allowed.
    
    """
    return AlignmentHelper('v_center', *items)


def align_h_center(*items):
    """ Align the h_center anchors of the given components. Inter-component
    spacing is allowed.
    
    """
    return AlignmentHelper('h_center', *items)


def align(anchor, *items):
    """ Align the given anchors of the given components. Inter-component spacing
    is allowed.

    """
    return AlignmentHelper(anchor, *items)


#------------------------------------------------------------------------------
# Toolkit items
#------------------------------------------------------------------------------
LAYOUT_HELPERS = {
    'horizontal': horizontal,
    'vertical': vertical,
    'hbox': hbox,
    'vbox': vbox,
    'align_left': align_left,
    'align_right': align_right,
    'align_top': align_top,
    'align_bottom': align_bottom,
    'align_v_center': align_v_center,
    'align_h_center': align_h_center,
    'align': align,
    '_space_': _space_,
}

