"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/property/property.py $
$Id: property.py 30544 2008-03-07 20:43:18Z dbinger $
"""
from dulcinea.base import DulcineaPersistent
from dulcinea.property.property_template import PropertyTemplate
from dulcinea.tolerance import Tolerance
from qp.lib.spec import anything, spec, specify, add_getters_and_setters
from qpy import xml, stringify


class Property (DulcineaPersistent):

    template_is = spec(
        PropertyTemplate,
        "defines attributes of this Property such as name, type and "
        "the constraints on the value")
    value_is = spec(
        anything,
        "the value this Property has (may be None which means no value)")
    tolerance_is = spec(
        (None, Tolerance),
        "the relative tolerance of this Property (in percent).  May be "
        "None.")

    def __init__(self, template, value=None):
        specify(self, template=template, tolerance=None)
        self.set_value(value)

    def __str__(self):
        if self.value is None:
            return "%s (no value)" % self.get_name()
        elif not self.value:            # empty list/string/etc.
            return "%s (empty value)" % self.get_name()
        else:
            return "%s = %s" % (self.get_name(), self.format())

    def __eq__(self, other):
        if not isinstance(other, Property):
            return False
        return (self.value == other.value and
                    self.tolerance == other.tolerance and
                    self.get_master() is other.get_master())

    def __ne__(self, other):
        return not (self == other)

    def is_required(self):
        return self.template.is_required()

    def is_hidden(self):
        return self.template.is_hidden()

    def is_input(self):
        return 0

    def is_property(self):
        return 1

    def set_value(self, value):
        """(any)

        May raise TypeError.
        """
        self.template.check_type(value)
        self.value = value

    def allow_tolerance(self):
        return self.template.get_type().allow_tolerance()

    def check_value(self):
        self.template.check_value(self.value)

    def format(self, html=0, show_tolerance=0):
        """Return the Property's value (and unit, if any) as a useful,
        standalone string.  Return None if the value is undefined."""
        if self.value is None:
            return None

        property_type = self.template.get_type()
        s = property_type.format_value(self.value, html=html)
        if show_tolerance and self.tolerance is not None:
            s += ' ' + xml(self.tolerance.format(html=html))
        return s

    def check_type(self):
        """()

        Ensure that 'self.value' is compatible with the Property type.
        Raise PropertyTypeError if not.
        """
        self.get_type().check_value(self.value)

    def simplify(self):
        """() -> (string|int|float|None)
        Return a simplified version of this Property's value. Physical
        values are converted to use the default unit from the master
        template and are returned as floats. Materials are returned as
        strings (using their names).  If self is an InputTemplate,
        simplify the default value
        """
        return self.get_type().simplify_value(self.value)

    # -- Delegates to our template -------------------------------------

    def is_discrete(self):
        return self.template.is_discrete()

    def is_atomic(self):
        return self.template.is_atomic()

    def is_list(self):
        return self.template.is_list()

    def is_table(self):
        return self.template.is_table()

    def is_array(self):
        return self.template.is_array()

    def is_aggregate(self):
        return self.template.is_aggregate()

    def is_time(self):
        return self.template.is_time()

    def get_master(self):
        return self.template.get_master()

    def get_name(self):
        return self.template.get_name()

    def get_type(self):
        return self.template.get_type()

    def get_title(self):
        return self.template.get_title()

    def get_description(self):
        return self.template.get_description()

    def get_hint(self, html=0):
        if html:
            texttype = xml
        else:
            texttype = stringify
        hint = texttype(self.get_description() or "")
        if not (self.is_discrete() or self.get_constraint() is None):
            if hint:
                hint += ', '
            hint += self.template.explain_constraint(html=html)
        return hint

    def get_constraint(self):
        constraint = self.template.get_constraint()
        if self.get_type().is_material():
            materials = set()
            for material in constraint:
                materials.update(material.get_leaves())
            return list(materials)
        else:
            return constraint

    def get_material_constraint(self):
        assert self.get_type().is_material()
        materials = set()
        for material in self.template.get_constraint():
            materials.add(material)
            materials.update(material.get_descendants())
        return list(materials)

    def get_allowed_units(self):
        return self.template.get_allowed_units()

add_getters_and_setters(Property)


def get_example_properties(property_list):
    """() -> [Property]

    Return a list of example value Property instances mirroring 
    the property_list, except that the elements lacking values (e.g. input) 
    are duplicated with values created using the default value of the Property.
    """
    example_properties = []
    if property_list:
        for property in property_list:
            if property.is_input():
                example_properties.append(property.create_value(
                    value=property.get_default_value()))
            else:
                example_properties.append(property.copy())
    return example_properties
