from abc import abstractmethod
from typing import Generator, Iterable, Optional

from cerulean.file_system import FileSystem
from cerulean.path import AbstractPath, EntryType, Path, Permission


class FileSystemImpl(FileSystem):
    """Abstract class for file system implementations.

    This class exists to document the (internal!) interface and to \
    help keep it consistent between the different kinds of file \
    systems, as well as to implement some shared convenience functions.

    You will want to use a  concrete file system, like LocalFileSystem \
    or SftpFileSystem.
    """

    # Feature support

    _features = ['symlinks', 'permissions', 'devices']

    @abstractmethod
    def _supports(self, feature: str) -> bool:
        """Whether the file system supports the given feature.

        See _features for supported features.

        Args:
            feature: The feature to check.

        Returns:
            Whether it is supported.

        """
        pass

    # Existence and contents

    @abstractmethod
    def _exists(self, path: AbstractPath) -> bool:
        pass

    @abstractmethod
    def _mkdir(
            self, path: AbstractPath, mode: Optional[int] = None,
            parents: bool = False, exists_ok: bool = False) -> None:
        pass

    @abstractmethod
    def _iterdir(
            self, path: AbstractPath) -> Generator[AbstractPath, None, None]:
        pass

    @abstractmethod
    def _rmdir(self, path: AbstractPath, recursive: bool = False) -> None:
        pass

    @abstractmethod
    def _touch(self, path: AbstractPath) -> None:
        pass

    @abstractmethod
    def _streaming_read(
            self, path: AbstractPath) -> Generator[bytes, None, None]:
        pass

    @abstractmethod
    def _streaming_write(
            self, path: AbstractPath, data: Iterable[bytes]) -> None:
        pass

    # Convenience functions are implemented here, on top of the internal API
    def _read_bytes(self, path: AbstractPath) -> bytes:
        data = bytearray()
        for chunk in self._streaming_read(path):
            data += chunk
        return bytes(data)

    def _read_text(self, path: AbstractPath, encoding: str = 'utf-8',
                  errors: str = 'strict') -> str:
        return self._read_bytes(path).decode(encoding, errors)

    def _write_bytes(self, path: AbstractPath, data: bytes) -> None:
        self._streaming_write(path, [data])

    def _write_text(self,
                   path: AbstractPath,
                   data: str,
                   encoding: str = 'utf-8',
                   errors: str = 'strict') -> None:
        self._write_bytes(path, data.encode(encoding, errors))

    @abstractmethod
    def _rename(self, path: AbstractPath, target: AbstractPath) -> None:
        pass

    @abstractmethod
    def _unlink(self, path: AbstractPath) -> None:
        pass

    # File type and size

    @abstractmethod
    def _is_dir(self, path: AbstractPath) -> bool:
        pass

    @abstractmethod
    def _is_file(self, path: AbstractPath) -> bool:
        pass

    @abstractmethod
    def _is_symlink(self, path: AbstractPath) -> bool:
        pass

    @abstractmethod
    def _entry_type(self, path: AbstractPath) -> EntryType:
        pass

    @abstractmethod
    def _size(self, path: AbstractPath) -> int:
        pass

    # Permissions

    @abstractmethod
    def _uid(self, path: AbstractPath) -> int:
        pass

    @abstractmethod
    def _gid(self, path: AbstractPath) -> int:
        pass

    @abstractmethod
    def _has_permission(
            self, path: AbstractPath, permission: Permission) -> bool:
        pass

    @abstractmethod
    def _set_permission(
            self, path: AbstractPath, permission: Permission,
            value: bool = True) -> None:
        pass

    @abstractmethod
    def _chmod(self, path: AbstractPath, mode: int) -> None:
        pass

    @abstractmethod
    def _symlink_to(self, path: AbstractPath, target: AbstractPath) -> None:
        pass

    @abstractmethod
    def _readlink(self, path: AbstractPath, recursive: bool) -> Path:
        pass
