"""
RouteFS is a base class for developing read-only FUSE filesystems that
lets you focus on the directory tree instead of the system calls.

RouteFS uses the Routes library developed for Pylons. URLs were
inspired by filesystems, and now you can have filesystems inspired by
URLs.
"""


import errno
import stat

import fuse
import routes


fuse.fuse_python_api = (0, 2)


class RouteStat(fuse.Stat):
    """
    RouteStat is a descendent of fuse.Stat, defined to make sure that
    all of the necessary attributes are always defined
    """
    def __init__(self):
        self.st_mode = 0
        self.st_ino = 0
        self.st_dev = 0
        self.st_nlink = 0
        self.st_uid = 0
        self.st_gid = 0
        self.st_size = 0
        self.st_atime = 0
        self.st_mtime = 0
        self.st_ctime = 0


class RouteFS(fuse.Fuse):
    """
    RouteFS: Web 2.0 for filesystems
    """
    def __init__(self, *args, **kwargs):
        super(RouteFS, self).__init__(*args, **kwargs)

        self.map = self.make_map()

    def make_map(self):
        """
        This method should be overridden by descendents of RouteFS to
        define the routing for the filesystem
        """
        m = routes.Mapper()

        m.connect('{controller}')

        return m

    def _get_file(self, path):
        """
        Find the filesystem entry object for a given path
        """
        match = self.map.match(path)
        if match is None:
            return NoEntry()
        controller = match.pop('controller')
        result = getattr(self, controller)(**match)
        if result is None:
            return NoEntry()
        if type(result) is str:
            result = File(result)
        if type(result) is list:
            result = Directory(result)
        return result

    def readdir(self, path, offset):
        """
        If the path referred to is a directory, return the elements of
        that diectory
        """
        return self._get_file(path).readdir(offset)

    def getattr(self, path):
        """
        Return the stat information for a path

        The stat information for a directory, symlink, or file is
        predetermined based on which it is.
        """
        return self._get_file(path).getattr()

    def read(self, path, length, offset):
        """
        If the path specified is a file, return the requested portion
        of the file
        """
        return self._get_file(path).read(length, offset)

    def readlink(self, path):
        """
        If the path specified is a symlink, return the target
        """
        return self._get_file(path).readlink()

    def write(self, path, buf, offset):
        """
        If the path specified is a file, call the appropriate member 
        on the file
        """
        return self._get_file(path).write(buf, offset)


class TreeKey(object):
    def getattr(self):
        return -errno.EINVAL
    def readdir(self, offset):
        return -errno.EINVAL
    def read(self, length, offset):
        return -errno.EINVAL
    def readlink(self):
        return -errno.EINVAL
    def write(self, length, offset):
        return -errno.EINVAL


class NoEntry(TreeKey):
    def getattr(self):
        return -errno.ENOENT
    def readdir(self, offset):
        return -errno.ENOENT
    def read(self, length, offset):
        return -errno.ENOENT
    def readlink(self):
        return -errno.ENOENT
    def write(self, length, offset):
        return -errno.ENOENT


class TreeEntry(TreeKey):
    default_mode = 0444

    def __new__(cls, contents, mode=None):
        return super(TreeEntry, cls).__new__(cls, contents)

    def __init__(self, contents, mode=None):
        if mode is None:
            self.mode = self.default_mode
        else:
            self.mode = mode

        super(TreeEntry, self).__init__(contents)


class Directory(TreeEntry, list):
    """
    A dummy class representing a filesystem entry that should be a
    directory
    """
    default_mode = 0555

    def getattr(self):
        st = RouteStat()
        st.st_mode = stat.S_IFDIR | self.mode
        st.st_nlink = 2
        return st

    def readdir(self, offset):
        for member in ['.', '..'] + self:
            yield fuse.Direntry(str(member))


class Symlink(TreeEntry, str):
    """
    A dummy class representing something that should be a symlink
    """
    default_mode = 0777

    def getattr(self):
        st = RouteStat()
        st.st_mode = stat.S_IFLNK | self.mode
        st.st_nlink = 1
        st.st_size = len(self)
        return st

    def readlink(self):
        return self


class File(TreeEntry, str):
    """
    A dummy class representing something that should be a file
    """
    default_mode = 0444

    def getattr(self):
        st = RouteStat()
        st.st_mode = stat.S_IFREG | self.mode
        st.st_nlink = 1
        st.st_size = len(self)
        return st

    def read(self, length, offset):
        return self[offset:offset + length]


def main(cls):
    """
    A convenience function for initializing a RouteFS filesystem
    """
    server = cls(version="%prog " + fuse.__version__,
                 usage=fuse.Fuse.fusage,
                 dash_s_do='setsingle')
    server.parse(values=server, errex=1)
    server.main()


from dictfs import DictFS

__all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main']
