#-*- coding: utf-8 -*-
u"""

.. moduleauthor:: Jordi Fernández <jordi.feca@gmail.com>
"""
import sqlalchemy
from sqlalchemy import (
    Column,
    Integer,
    Unicode,
    Enum,
    ForeignKey
)
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declared_attr

from .meta import Base
from . import ItemMixin


class Location(ItemMixin, Base):
    __tablename__ = 'locations'

    location_name = Column(Unicode(255), nullable=False, index=True)
    location_type = Column(
        Enum(
            "continent",
            "country",
            "autonomous_community",
            "province",
            "town",
            "zone"
        ),
        nullable=False,
        index=True
    )
    code = Column(Unicode(255), nullable=False, index=True)
    parent_id = Column(Integer, ForeignKey('locations.id'))

    @declared_attr
    def parent(cls):
        return relationship(
            "propfinder.models.location.Location",
            lazy='joined',
            backref=backref("locations"),
            remote_side=[cls.id]
        )

    def ascend(self, include_self=False):
        """Iterate over the location's ancestors.

        :param include_self: Indicates if the location itself should be included
            in the iteration.
        :type include_self: bool

        :return: An iterable sequence of locations.
        :rtype: `Location`
        """
        ascendant = self if include_self else self.parent
        while ascendant is not None:
            yield ascendant
            ascendant = ascendant.parent

    def descend(self, include_self=False):
        """Iterates over the location's descendants.

        :param include_self: If set to True, the location itself is yielded as
            the first element in the iteration.
        :type include_self: bool

        :return: An iterable sequence containing the location's flattened tree.
        :rtype: `Location`
        """
        if include_self:
            yield self
        for location in self.locations:
            for descendant in location.descend(True):
                yield descendant

    def descends_from(self, ascendant):
        """Indicates if the location descends from a given location.

        :param ascendant: The hypothetical container of the location.
        :type ascendant: `Location`

        :return: True if the location is, or is contained within the given
            location or one of its descendants, False otherwise.
        :rtype: bool
        """
        location = self

        while location is not None:
            if location is ascendant:
                return True
            else:
                location = location.parent

        return False

    def get_ancestor_of_type(self, location_type, include_self=False):
        """Obtains the location's first ancestor of the given type.

        :param location_type: The type of location to obtain; should match one
            of the values accepted by the `location_type` member.
        :type location_type: str

        :param include_self: If set to True, the location itself will be
            returned if it matches the indicated type. Otherwise, the location
            will be skipped and only its ancestors will be considered as
            possible matches.

        :return: The first ancestor of the location that matches the indicated
            type, or None if no such location can be found on the way to the
            tree root.
        :rtype: `Location`
        """
        location = self

        if not include_self:
            location = location.parent

        while location is not None:
            if location.location_type == location_type:
                return location
            location = location.parent

    def get_child_location(self, code, recursive=True):
        """Retrieves the contained location that matches the indicated code.

        :param code: The code of the location to retrieve.
        :type code: str

        :return: The specified location, or None if it wasn't found.
        :rtype: `Location`
        """
        for child in self.locations:
            if child.code == code:
                return child
            if recursive:
                for descendant in child.locations:
                    match = descendant.get_child_location(code)
                    if match:
                        return match

    @classmethod
    def get_from_zone_code(cls, dbsession, code, parent=None):
        zone = None
        if code:
            zone = dbsession.query(cls).filter(
                cls.location_type=="zone",
                sqlalchemy.func.find_in_set(code, cls.code),
                cls.parent==parent
            ).first()

        if zone is None:
            raise Exception()

        return zone

