"""Logic for traversing directory objects and generating output.
"""
import quixote
from quixote.errors import TraversalError


class DirectoryClass(type):
    """A meta-class for Directory.  Its purpose is to process methods
    that are exported using the export() and subdir() decorators.
    """

    def __new__(meta, classname, bases, classdict):
        cls = type.__new__(meta, classname, bases, classdict)
        exports = []
        for k, v in classdict.items():
            if isinstance(v, property):
                # might be a property from subdir(), get the original method
                v = v.fget
            if hasattr(v, '_q_name'):
                if v._q_name == k:
                    exports.append(k)
                else:
                    exports.append((v._q_name, k))
        if exports:
            # Only monkey with _q_exports if names are exported using the
            # decorators.
            exports += getattr(cls, '_q_exports', [])
            cls._q_exports = exports
        return cls


class Directory(object, metaclass=DirectoryClass):
    """
    Instance attributes: none
    """

    # A list containing strings or 2-tuples of strings that map external
    # names to internal names.  Note that the empty string will be
    # implicitly mapped to '_q_index'.
    _q_exports = []

    def _q_translate(self, component):
        """(component : string) -> string | None

        Translate a path component into a Python identifier.  Returning
        None signifies that the component does not exist.
        """
        if component in self._q_exports:
            if component == '':
                return '_q_index'  # implicit mapping
            else:
                return component
        else:
            # check for an explicit external to internal mapping
            for value in self._q_exports:
                if isinstance(value, tuple):
                    if value[0] == component:
                        return value[1]
            else:
                return None

    def _q_lookup(self, component):
        """(component : string) -> object

        Lookup a path component and return the corresponding object (usually
        a Directory, a method or a string).  Returning None signals that the
        component does not exist.
        """
        return None

    def _q_traverse(self, path):
        """(path: [string]) -> object

        Traverse a path and return the result.
        """
        assert len(path) > 0
        component = path[0]
        path = path[1:]
        name = self._q_translate(component)
        if name is not None:
            obj = getattr(self, name)
        else:
            obj = self._q_lookup(component)
        if obj is None:
            raise TraversalError(
                private_msg=(
                    'directory %r has no component ' '%r' % (self, component)
                )
            )
        if path:
            if hasattr(obj, '_q_traverse'):
                return obj._q_traverse(path)
            else:
                raise TraversalError
        elif callable(obj):
            return obj()
        else:
            return obj

    def __call__(self):
        if "" in self._q_exports and not quixote.get_request().form:
            # Fix missing trailing slash.
            path = quixote.get_path()
            print("Adding slash to: %r " % path)
            return quixote.redirect(path + "/")
        else:
            raise TraversalError(
                private_msg=('directory %r is not ' 'callable' % self)
            )


class AccessControlled(object):
    """
    A mix-in class that calls the _q_access() method before traversing
    into the directory.
    """

    def _q_access(self):
        pass

    def _q_traverse(self, path):
        self._q_access()
        return super(AccessControlled, self)._q_traverse(path)


class Resolving(object):
    """
    A mix-in class that provides the _q_resolve() method.  _q_resolve()
    is called if a component name appears in the _q_exports list but is
    not an instance attribute.  _q_resolve is expected to return the
    component object.
    """

    def _q_resolve(self, name):
        return None

    def _q_translate(self, component):
        name = super(Resolving, self)._q_translate(component)
        if name is not None and not hasattr(self, name):
            obj = self._q_resolve(name)
            setattr(self, name, obj)
        return name


def export(func=None, name=None):
    """Export a function that generates a page.  If 'name' is not
    provided then the name of the page defaults to the name of the
    function (method).
    """

    def do_export(func):
        if name is None:
            func._q_name = func.__name__
        else:
            func._q_name = name
        return func

    if func is None:
        return do_export
    else:
        return do_export(func)


def subdir(func=None, name=None):
    """Export a function that returns a sub-directory object.  If 'name'
    is not provided then the name of the directory defaults to the name
    of the function (method).
    """

    def do_export(func):
        if name is None:
            func._q_name = func.__name__
        else:
            func._q_name = name
        return property(func)

    if func is None:
        return do_export
    else:
        return do_export(func)
