from sqlalchemy.orm import descriptor_props, aliased
from sqlalchemy.orm.interfaces import PropComparator
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.sql import column
from sqlalchemy import Column, Integer, func, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.util import partial
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import eq_


class TestDescriptor(descriptor_props.DescriptorProperty):
    def __init__(self, cls, key, descriptor=None, doc=None,
                 comparator_factory=None):
        self.parent = cls.__mapper__
        self.key = key
        self.doc = doc
        self.descriptor = descriptor
        if comparator_factory:
            self._comparator_factory = partial(comparator_factory, self)
        else:
            self._comparator_factory = lambda mapper: None


class DescriptorInstrumentationTest(fixtures.ORMTest):
    def _fixture(self):
        Base = declarative_base()

        class Foo(Base):
            __tablename__ = 'foo'
            id = Column(Integer, primary_key=True)

        return Foo

    def test_fixture(self):
        Foo = self._fixture()

        d = TestDescriptor(Foo, 'foo')
        d.instrument_class(Foo.__mapper__)

        assert Foo.foo

    def test_property_wrapped_classlevel(self):
        Foo = self._fixture()
        prop = property(lambda self: None)
        Foo.foo = prop

        d = TestDescriptor(Foo, 'foo')
        d.instrument_class(Foo.__mapper__)

        assert Foo().foo is None
        assert Foo.foo is not prop

    def test_property_subclass_wrapped_classlevel(self):
        Foo = self._fixture()

        class myprop(property):
            attr = 'bar'

            def method1(self):
                return "method1"

        prop = myprop(lambda self: None)
        Foo.foo = prop

        d = TestDescriptor(Foo, 'foo')
        d.instrument_class(Foo.__mapper__)

        assert Foo().foo is None
        assert Foo.foo is not prop
        assert Foo.foo.attr == 'bar'
        assert Foo.foo.method1() == 'method1'

    def test_comparator(self):
        class Comparator(PropComparator):
            __hash__ = None

            attr = 'bar'

            def method1(self):
                return "method1"

            def method2(self, other):
                return "method2"

            def __getitem__(self, key):
                return 'value'

            def __eq__(self, other):
                return column('foo') == func.upper(other)

        Foo = self._fixture()
        d = TestDescriptor(Foo, 'foo', comparator_factory=Comparator)
        d.instrument_class(Foo.__mapper__)
        eq_(Foo.foo.method1(), "method1")
        eq_(Foo.foo.method2('x'), "method2")
        assert Foo.foo.attr == 'bar'
        assert Foo.foo['bar'] == 'value'
        eq_(
            (Foo.foo == 'bar').__str__(),
            "foo = upper(:upper_1)"
        )

    def test_aliased_comparator(self):
        class Comparator(ColumnProperty.Comparator):
            __hash__ = None

            def __eq__(self, other):
                return func.foobar(self.__clause_element__()) ==\
                    func.foobar(other)

        Foo = self._fixture()
        Foo._name = Column('name', String)

        def comparator_factory(self, mapper):
            prop = mapper._props['_name']
            return Comparator(prop, mapper)

        d = TestDescriptor(Foo, 'foo', comparator_factory=comparator_factory)
        d.instrument_class(Foo.__mapper__)

        eq_(
            str(Foo.foo == 'ed'),
            "foobar(foo.name) = foobar(:foobar_1)"
        )
        eq_(
            str(aliased(Foo).foo == 'ed'),
            "foobar(foo_1.name) = foobar(:foobar_1)"
        )
