"""General SFTP storage. Subclass it the way you want!"""

import os
import itertools

from pysftpserver.abstractstorage import SFTPAbstractServerStorage
from pysftpserver.futimes import futimes
from pysftpserver.stat_helpers import stat_to_longname


class SFTPServerStorage(SFTPAbstractServerStorage):
    """Simple storage class. Subclass it and override the methods."""

    def __init__(self, home, umask=None):
        """Home sweet home.

        Set your home to something comfortable and chdir to it.
        You should support umask changing too.
        """
        self.home = os.path.realpath(home)
        os.chdir(self.home)
        if umask:
            os.umask(umask)

    def verify(self, filename):
        """Verify that requested filename is accessible.

        In this simple storage class this is always True
        (and thus possibly insecure).
        """
        return True

    def stat(self, filename, lstat=False, fstat=False, parent=None):
        """stat, lstat and fstat requests.

        Return a dictionary of stats.
        Filename is an handle in the fstat variant.
        If parent is not None, then filename is inside parent,
        and a join is needed.
        This happens in case of readdir responses:
        a filename (not a path) has to be returned,
        but the stat call need (obviously) a full path.
        """
        if not lstat and fstat:
            # filename is an handle
            _stat = os.fstat(filename)
        elif lstat:
            _stat = os.lstat(filename)
        else:
            try:
                _stat = os.stat(
                    filename if not parent
                    else os.path.join(parent, filename)
                )
            except:
                # we could have a broken symlink
                # but lstat could be false:
                # this happens in case of readdir responses
                _stat = os.lstat(
                    filename if not parent
                    else os.path.join(parent, filename)
                )

        if fstat:
            longname = None  # not needed in case of fstat
        else:
            longname = stat_to_longname(  # see stat_helpers.py
                _stat, filename
            )

        return {
            b'size': _stat.st_size,
            b'uid': _stat.st_uid,
            b'gid': _stat.st_gid,
            b'perm': _stat.st_mode,
            b'atime': _stat.st_atime,
            b'mtime': _stat.st_mtime,
            b'longname': longname
        }

    def setstat(self, filename, attrs, fsetstat=False):
        """setstat and fsetstat requests.

        Filename is an handle in the fstat variant.
        If you're using Python < 3.3,
        you could find useful the futimes file / function.
        """
        if not fsetstat:
            f = os.open(filename, os.O_WRONLY)
            chown = os.chown
            chmod = os.chmod
        else:  # filename is a fd
            f = filename
            chown = os.fchown
            chmod = os.fchmod

        if b'size' in attrs:
            os.ftruncate(f, attrs[b'size'])
        if all(k in attrs for k in (b'uid', b'gid')):
            chown(filename, attrs[b'uid'], attrs[b'gid'])
        if b'perm' in attrs:
            chmod(filename, attrs[b'perm'])

        if all(k in attrs for k in (b'atime', b'mtime')):
            if not fsetstat:
                os.utime(filename, (attrs[b'atime'], attrs[b'mtime']))
            else:
                futimes(filename, (attrs[b'atime'], attrs[b'mtime']))

    def opendir(self, filename):
        """Return an iterator over the files in filename."""
        return itertools.chain(iter([b'.', b'..']), iter(os.listdir(filename)))

    def open(self, filename, flags, mode):
        """Return the file handle."""
        return os.open(filename, flags, mode)

    def mkdir(self, filename, mode):
        """Create directory with given mode."""
        os.mkdir(filename, mode)

    def rmdir(self, filename):
        """Remove directory."""
        os.rmdir(filename)

    def rm(self, filename):
        """Remove file."""
        os.remove(filename)

    def rename(self, oldpath, newpath):
        """Move/rename file."""
        os.rename(oldpath, newpath)

    def symlink(self, linkpath, targetpath):
        """Symlink file."""
        os.symlink(targetpath, linkpath)

    def readlink(self, filename):
        """Readlink of filename."""
        return os.readlink(filename)

    def write(self, handle, off, chunk):
        """Write chunk at offset of handle."""
        os.lseek(handle, off, os.SEEK_SET)
        rlen = os.write(handle, chunk)
        if rlen == len(chunk):
            return True

    def read(self, handle, off, size):
        """Read from the handle size, starting from offset off."""
        os.lseek(handle, off, os.SEEK_SET)
        return os.read(handle, size)

    def close(self, handle):
        """Close the file handle."""
        try:
            handle.close()
        except AttributeError:
            pass