"""The fake file system implementation."""

from dfvfs.lib import definitions
from dfvfs.lib import errors
from dfvfs.path import fake_path_spec
from dfvfs.vfs import file_system
from dfvfs.vfs import fake_file_entry


class FakeFileSystem(file_system.FileSystem):
    """Fake file system."""

    TYPE_INDICATOR = definitions.TYPE_INDICATOR_FAKE

    def __init__(self, resolver_context, path_spec):
        """Initializes a file system.

        Args:
          resolver_context (Context): a resolver context.
          path_spec (PathSpec): a path specification.
        """
        super().__init__(resolver_context, path_spec)
        self._paths = {}

        self.AddFileEntry(
            self.LOCATION_ROOT, file_entry_type=definitions.FILE_ENTRY_TYPE_DIRECTORY
        )

    def _Close(self):
        """Closes the file system.

        Raises:
          OSError: if the close failed.
        """
        return

    def _Open(self, mode="rb"):
        """Opens the file system defined by path specification.

        Args:
          mode (Optional[str]): file access mode. The default is 'rb' which
              represents read-only binary.

        Raises:
          AccessError: if the access to open the file was denied.
          OSError: if the file system could not be opened.
          PathSpecError: if the path specification is incorrect.
          ValueError: if the path specification is invalid.
        """
        if self._path_spec.HasParent():
            raise errors.PathSpecError("Unsupported path specification with parent.")

    def AddFileEntry(
        self,
        path,
        file_entry_type=definitions.FILE_ENTRY_TYPE_FILE,
        file_data=None,
        link_data=None,
    ):
        """Adds a fake file entry.

        Args:
          path (str): path of the file entry.
          file_entry_type (Optional[str]): type of the file entry object.
          file_data (Optional[bytes]): data of the fake file-like object.
          link_data (Optional[bytes]): link data of the fake file entry object.

        Raises:
          KeyError: if the path already exists.
          ValueError: if the file data is set but the file entry type is not a file
              or if the link data is set but the file entry type is not a link.
        """
        if path in self._paths:
            raise KeyError(f"File entry already set for path: {path:s}.")

        if file_data and file_entry_type != definitions.FILE_ENTRY_TYPE_FILE:
            raise ValueError("File data set for non-file file entry type.")

        if link_data and file_entry_type != definitions.FILE_ENTRY_TYPE_LINK:
            raise ValueError("Link data set for non-link file entry type.")

        if file_data is not None:
            path_data = file_data
        elif link_data is not None:
            path_data = link_data
        else:
            path_data = None

        self._paths[path] = (file_entry_type, path_data)

    def FileEntryExistsByPath(self, path):
        """Determines if a file entry for a path exists.

        Args:
          path (str): path of the file entry.

        Returns:
          bool: True if the file entry exists.
        """
        return path and path in self._paths

    def FileEntryExistsByPathSpec(self, path_spec):
        """Determines if a file entry for a path specification exists.

        Args:
          path_spec (PathSpec): path specification.

        Returns:
          bool: True if the file entry exists.
        """
        location = getattr(path_spec, "location", None)
        return self.FileEntryExistsByPath(location)

    def GetDataByPath(self, path):
        """Retrieves the data associated to a path.

        Args:
          path (str): path of the file entry.

        Returns:
          bytes: data or None if not available.
        """
        _, path_data = self._paths.get(path, (None, None))
        return path_data

    def GetFileEntryByPath(self, path):
        """Retrieves a file entry for a path.

        Args:
          path (str): path of the file entry.

        Returns:
          FakeFileEntry: a file entry or None if not available.
        """
        if path is None:
            return None

        file_entry_type, _ = self._paths.get(path, (None, None))
        if not file_entry_type:
            return None

        path_spec = fake_path_spec.FakePathSpec(location=path)
        is_root = bool(path == self.LOCATION_ROOT)
        return fake_file_entry.FakeFileEntry(
            self._resolver_context,
            self,
            path_spec,
            file_entry_type=file_entry_type,
            is_root=is_root,
        )

    def GetFileEntryByPathSpec(self, path_spec):
        """Retrieves a file entry for a path specification.

        Args:
          path_spec (PathSpec): path specification.

        Returns:
          FakeFileEntry: a file entry or None if not available.
        """
        location = getattr(path_spec, "location", None)
        return self.GetFileEntryByPath(location)

    def GetPaths(self):
        """Retrieves the paths dictionary.

        Returns:
          dict[str, FileIO]: file-like object per path.
        """
        return self._paths

    def GetRootFileEntry(self):
        """Retrieves the root file entry.

        Returns:
          FakeFileEntry: a file entry or None if not available.
        """
        path_spec = fake_path_spec.FakePathSpec(location=self.LOCATION_ROOT)
        return self.GetFileEntryByPathSpec(path_spec)
