"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/property/test/utest_property_template.py $
$Id: utest_property_template.py 28932 2006-10-12 20:29:05Z dbinger $
"""
from dulcinea.common import CommonTest
from dulcinea.material import Material, get_material_db
from dulcinea.property import get_property_db
from dulcinea.property.errors import PropertyTypeError, ConstraintError
from dulcinea.property.property_template import MasterTemplate
from dulcinea.property.property_type import PropertyType
from dulcinea.property.property_type import get_material_property_type
from dulcinea.property.property_type import get_physical_value_property_type
from dulcinea.property.property_type import get_string_property_type
from dulcinea.physical_unit import get_standard_unit
from dulcinea.physical_value import PhysicalValue
from dulcinea.range_value import RangeValue
from sancho.utest import raises


class TestTemplateProperty (CommonTest):

    def _pre(self):
        CommonTest._pre(self)
        if get_property_db() is None:
            return
        self.plib = get_property_db()
        get_type = self.plib.get_property_type
        self.int_type = get_type(PropertyType.ATOMIC,
                                 PropertyType.INT)
        self.material_type = get_type(PropertyType.ATOMIC,
                                      PropertyType.MATERIAL)
        pv1 = PhysicalValue(100, "degC")
        pv2 = PhysicalValue(1, "mm")
        pv3 = PhysicalValue(1, "min")
        self.pv_type = get_type(PropertyType.ATOMIC,
                                   PropertyType.PHYSICAL_VALUE,
                                   element_sub_type=pv1.unit)
        self.pv_len_type = get_type(PropertyType.ATOMIC,
                                    PropertyType.PHYSICAL_VALUE,
                                    element_sub_type=pv2.unit)
        self.pv_time_type = get_type(PropertyType.ATOMIC,
                                     PropertyType.PHYSICAL_VALUE,
                                     element_sub_type=pv3.unit)

        self.upv_type = get_type(PropertyType.ATOMIC,
                                    PropertyType.PHYSICAL_VALUE)
        self.pv_list_type = get_type(PropertyType.LIST,
                                     PropertyType.PHYSICAL_VALUE,
                                     element_sub_type=pv2.unit)
        self.pv_time_list_type = get_type(PropertyType.LIST,
                                          PropertyType.PHYSICAL_VALUE,
                                          element_sub_type=pv3.unit)
        self.mat_list_type = get_type(PropertyType.LIST,
                                      PropertyType.MATERIAL)
        self.str_pv_table_type = get_type(PropertyType.TABLE,
                                          PropertyType.PHYSICAL_VALUE,
                                          key_type=PropertyType.STRING,
                                          element_sub_type=pv2.unit)
        self.str_mat_table_type = get_type(PropertyType.TABLE,
                                           PropertyType.MATERIAL,
                                           key_type=PropertyType.STRING)


    def check_basic(self):
        if get_property_db() is None:
            return
        template = MasterTemplate("foo", self.int_type, description="bar")

        assert template.get_name() == "foo"
        assert template.get_description() == "bar"
        assert template.get_title() == "Foo"
        template.set_description(None)
        template.title=None

        template.name = "foo_bar"
        assert template.get_title() == None

        # illegal property name
        raises(TypeError, MasterTemplate, '123', self.int_type)

        # unknown type
        raises(TypeError, MasterTemplate, 'blah', 'xint')


    def check_unconstrained(self):
        if get_property_db() is None:
            return
        nocon_template = MasterTemplate('foo', self.int_type)
        assert nocon_template.get_constraint() == None
        assert nocon_template.format() == "(unconstrained)"
        assert nocon_template.explain_constraint() == None
        assert not nocon_template.is_discrete()
        nocon_template.check_value(-23432)
        nocon_template.check_value(124321431)
        raises(PropertyTypeError, nocon_template.check_value, 'foo')

    def check_simple_set_constr (self):
        if get_property_db() is None:
            return
        # material constraints are a special case of strings ...
        silicon = get_material_db().get_material('silicon')
        boron = get_material_db().get_material('boron')
        gold = get_material_db().get_material('gold')
        silicon_or_boron = Material('silicon_or_boron')
        silicon_or_boron.set_label('silicon or boron')
        silicon_or_boron.add_child(silicon)
        silicon_or_boron.add_child(boron)
        mat_template = MasterTemplate(
            'substrate', self.material_type,
            constraint=[silicon_or_boron])
        assert mat_template.get_constraint_unit() == None
        assert set(mat_template.get_constraint()) == set([silicon_or_boron])
        assert mat_template.is_discrete()
        mat_template.check_value(silicon)
        raises(ConstraintError, mat_template.check_value, gold)

        assert mat_template.explain_constraint() == "must be silicon or boron"
        assert mat_template.format() == "silicon or boron"

        int_template = MasterTemplate('foo', self.int_type,
                                      constraint=[10, 20])
        assert int_template.get_constraint_range() == RangeValue(10, 20)
        assert int_template.is_discrete()
        assert int_template.format() == "10, 20"
        assert int_template.explain_constraint() == "must be 10 or 20"

    def check_pv_set_constr (self):
        if get_property_db() is None:
            return
        # Try PhysicalValues with unit (this time, three values in the set,
        # because that's a different case in explain_constraint()).
        pv1 = PhysicalValue(100, "degC")
        pv2 = PhysicalValue(150, "degC")
        pv3 = PhysicalValue(200, "degC")
        pv4 = PhysicalValue(RangeValue(250, 300), "degC")
        pv5 = PhysicalValue(10, "degF")

        temp_template = MasterTemplate('temperature', self.pv_type,
                                       constraint=[pv1, pv2, pv3, pv4])
        assert set(temp_template.get_constraint()) == set([pv1, pv2, pv3, pv4])
        assert not temp_template.is_discrete()
        temp_template.check_value(PhysicalValue(100, 'degC'))
        temp_template.check_value(PhysicalValue(212, 'degF'))
        temp_template.check_value(PhysicalValue(270, 'degC'))
        temp_template.check_value(PhysicalValue(550, 'degF'))
        raises(ConstraintError,
               temp_template.check_value, PhysicalValue(101, 'degC'))

        assert temp_template.format(html=1) == (
                      "100, 150, 200, 250 .. 300&nbsp;&deg;C")
        assert temp_template.format(html=0) == (
                      "100, 150, 200, 250 .. 300 degC")
        assert temp_template.explain_constraint() == (
                    "must be one of 100, 150, 200, 250 .. 300 degC")
        assert temp_template.explain_constraint(html=1) == (
                    "must be one of 100, 150, 200, 250 .. 300&nbsp;&deg;C")

        # Now unitless PhysicalValues because the formatting is different.
        pv1 = PhysicalValue(50)
        pv2 = PhysicalValue(60)
        foo_template = MasterTemplate('foo', self.upv_type,
                                      constraint=[pv1, pv2])
        assert set(foo_template.get_constraint()) == set([pv1, pv2])
        foo_template.check_value(PhysicalValue(50))
        raises(PropertyTypeError, foo_template.check_value, 50)
        assert foo_template.format() == "50, 60"
        assert foo_template.explain_constraint() == "must be 50 or 60"

    def check_aggr_set_constr (self):
        if get_property_db() is None:
            return
        depth_100 = PhysicalValue(100, "um")
        depth_150 = PhysicalValue(150, "um")
        depth_200 = PhysicalValue(200, "um")

        gold = get_material_db().get_material('gold')
        aluminum = get_material_db().get_material('aluminum')
        silver = get_material_db().get_material('silver')
        boron = get_material_db().get_material('boron')
        three_metals = Material('three_metals')
        three_metals.add_child(gold)
        three_metals.add_child(aluminum)
        three_metals.add_child(silver)
        valid_depths = [depth_100, depth_200]
        matl_template = MasterTemplate("material", self.mat_list_type,
                                       constraint=[three_metals])
        depth_template = MasterTemplate("depth",
                                        self.pv_list_type,
                                        constraint=valid_depths)

        raises(PropertyTypeError, matl_template.check_value, gold)
        raises(PropertyTypeError, matl_template.check_value, boron)
        matl_template.check_value([gold])
        raises(ConstraintError, matl_template.check_value, [boron])
        raises(ConstraintError, matl_template.check_value, [gold, boron])
        matl_template.check_value([gold, silver])

        raises(PropertyTypeError, depth_template.check_value, depth_100)
        depth_template.check_value([depth_100])
        raises(ConstraintError,
               depth_template.check_value, [depth_100, depth_150])
        depth_template.check_value([depth_200, depth_100])


    def check_allowed_units(self):
        if get_property_db() is None:
            return
        min = get_standard_unit("min")
        sec = get_standard_unit("s")
        hour = get_standard_unit("hour")

        int_template = MasterTemplate('foo', self.int_type)
        assert int_template.get_default_unit() == None

        pv_template = MasterTemplate('duration', self.pv_time_type)
        assert min in pv_template.get_allowed_units()
        assert sec in pv_template.get_allowed_units()
        assert pv_template.get_default_unit() == min
        assert pv_template.is_allowed_unit(min)
        assert pv_template.is_allowed_unit('min')
        assert pv_template.is_allowed_unit(hour)

        # Ensure that check_value() enforces allowed_units
        pv1 = PhysicalValue(2, "min")
        pv2 = PhysicalValue(3, "day")
        pv3 = PhysicalValue(1.5, "m")
        pv4 = PhysicalValue(1.5)

        raises(PropertyTypeError, int_template.check_value, pv1)
        pv_template.check_value(pv1)
        raises(PropertyTypeError, pv_template.check_value, pv3)
        raises(PropertyTypeError, pv_template.check_value, pv4)
        raises(PropertyTypeError, pv_template.check_value, 1.5)

        list_template = MasterTemplate('durations',
                                       self.pv_time_list_type)
        list_template.check_value([])
        list_template.check_value([pv1])
        list_template.check_value([pv1, pv1*3])

        raises(PropertyTypeError, list_template.check_value, [pv1, pv3])
        raises(PropertyTypeError, list_template.check_value, [pv4])

    def check_consistent_units (self):
        if get_property_db() is None:
            return
        pv1 = PhysicalValue(1, "mm")
        pv2 = PhysicalValue(1.5, "mm")
        pv3 = PhysicalValue(1000, "um")
        mm_um = [pv1.unit, pv3.unit]
        templ1 = MasterTemplate('pvlist',
                                self.pv_list_type)
        templ2 = MasterTemplate('pvtable',
                                self.str_pv_table_type)

        templ1.check_value([])
        templ1.check_value([pv1])
        templ1.check_value([pv1, pv2])

        value = {'foo': pv1, 'bar': pv2}
        templ2.check_value(value)
        del value['foo']
        templ2.check_value(value)


    def check_factories (self):
        if get_property_db() is None:
            return
        templ = MasterTemplate('duration', self.pv_time_type)
        pv1 = PhysicalValue(20.0, "min")
        pv2 = PhysicalValue(25.0, "min")
        raises(PropertyTypeError, templ.create_value, 10)
        property1 = templ.create_value(pv1)
        assert property1.get_name() == templ.get_name()
        assert property1.get_type() == templ.get_type()
        assert property1.get_value() == pv1
        assert property1.template is templ

        property2 = templ.create_input_template(constraint=[pv1, pv2])
        assert property2.get_constraint() == [pv1, pv2]
        assert property2.get_name() == templ.get_name()
        assert property2.get_type() == templ.get_type()
        assert property2.template == templ

        pv3 = PhysicalValue(30.0, "min")
        assert property2.get_default_value() == None
        raises(PropertyTypeError, property2.set_default_value, 'bad')
        raises(ConstraintError, property2.set_default_value, pv3)
        property2.set_default_value(pv2)
        assert property2.get_default_value() == pv2

        set_templ = MasterTemplate("spam", self.int_type, constraint=[1, 10])
        set_templ.create_input_template(constraint=[1,10])

    def check_copy (self):
        if get_property_db() is None:
            return
        foo1 = MasterTemplate("foo", self.int_type, constraint=[1, 5])
        foo2 = foo1.copy()
        assert foo1 is not foo2
        assert foo1.name == foo2.name
        assert foo1.type is foo2.type
        assert foo1.title == foo2.title
        assert foo1.description is foo2.description is None
        assert foo1.constraint == foo2.constraint

        bar1 = MasterTemplate("bar", self.pv_len_type,
                              constraint=[PhysicalValue(1, "um"),
                              PhysicalValue(2, "um")])
        bar2 = bar1.copy()
        assert bar1 is not bar2
        assert bar1.name == bar2.name
        assert bar1.type is bar2.type
        assert bar1.constraint == bar2.constraint
        assert bar1.constraint[0] == bar2.constraint[0]
        assert bar1.constraint[0].unit is bar2.constraint[0].unit

    def check_simplify (self):
        # physical value
        if get_property_db() is None:
            return
        constraint = [PhysicalValue(RangeValue(0, 10), 'um')]
        pv_um = get_physical_value_property_type('um')
        depth_templ = MasterTemplate("depth", pv_um, constraint=constraint)
        assert depth_templ.simplify() == RangeValue(0, 10)
        depth_property = depth_templ.create_value(PhysicalValue(5300, "nm"))
        assert depth_property.simplify() == 5.3

        # material
        material_db = get_material_db()
        photoresist = material_db.get_material('gold')
        resist = material_db.get_material("gold")
        material_templ = MasterTemplate("material",
                                        get_material_property_type(),
                                        constraint=[photoresist])
        material_property = material_templ.create_value(resist)
        assert material_property.simplify() == resist.get_name()

        # string
        string_templ = MasterTemplate("letter",
                                      get_string_property_type(),
                                      constraint=["a", "b", "c"])
        string_property = string_templ.create_value("a")
        assert string_property.simplify() == "a"


if __name__ == "__main__":
    TestTemplateProperty()
