"""The Virtual File System (VFS) volume system interface."""

import abc

from dfvfs.lib import errors
from dfvfs.resolver import resolver


class VolumeAttribute:
    """The VFS volume attribute."""

    def __init__(self, identifier, value):
        """Initializes the volume attribute object.

        Args:
          identifier (str): identifier of the attribute within the volume.
          value : value of the attribute.
        """
        super().__init__()
        self.identifier = identifier
        self.value = value


class VolumeExtent:
    """The VFS volume extent."""

    EXTENT_TYPE_DATA = 0
    EXTENT_TYPE_SPARSE = 1

    def __init__(self, offset, size, extent_type=EXTENT_TYPE_DATA):
        """Initializes a volume extent.

        Args:
          offset (int): start offset of the extent, in bytes.
          size (int): size of the extent, in bytes.
          extent_type (Optional[str]): type of extent.
        """
        super().__init__()
        self.offset = offset
        self.size = size
        self.extent_type = extent_type


class Volume:
    """The VFS volume interface."""

    def __init__(self, identifier):
        """Initializes a volume.

        Args:
          identifier (str): identifier of the attribute within the volume.
        """
        super().__init__()
        self.identifier = identifier
        self._attributes = {}
        self._extents = []
        self._is_parsed = False

    def _AddAttribute(self, attribute):
        """Adds an attribute.

        Args:
          attribute (VolumeAttribute): a volume attribute.

        Raises:
          KeyError: if volume attribute is already set for the corresponding volume
              attribute identifier.
        """
        if attribute.identifier in self._attributes:
            raise KeyError(
                (
                    f"Volume attribute object already set for volume attribute "
                    f"identifier: {attribute.identifier:s}."
                )
            )

        self._attributes[attribute.identifier] = attribute

    @abc.abstractmethod
    def _Parse(self):
        """Extracts attributes and extents from the volume."""

    @property
    def attributes(self):
        """Retrieves attributes.

        Returns:
          generator[VolumeAttribute]: volume attributes generator.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return iter(self._attributes.values())

    @property
    def extents(self):
        """Retrieves extents.

        Returns:
          list[VolumeExtent]: volume extents.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return self._extents

    @property
    def number_of_attributes(self):
        """Retrieves the number of attributes.

        Returns:
          int: number of attributes.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return len(self._attributes)

    @property
    def number_of_extents(self):
        """Retrieves the number of extents.

        Returns:
          int: number of extents.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return len(self._extents)

    def GetAttribute(self, identifier):
        """Retrieves a specific attribute.

        Args:
          identifier (str): identifier of the attribute within the volume.

        Returns:
          VolumeAttribute: volume attribute or None if not available.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        if identifier not in self._attributes:
            return None

        return self._attributes[identifier]

    def HasExternalData(self):
        """Determines if the volume has external stored data.

        Returns:
          bool: True if the volume has external stored data.
        """
        return False


class VolumeSystem:
    """The VFS volume system interface."""

    TYPE_INDICATOR = None

    VOLUME_IDENTIFIER_PREFIX = "v"

    def __init__(self):
        """Initializes a volume system."""
        super().__init__()
        self._file_system = None
        self._is_parsed = False
        self._sections = []
        self._volumes = {}
        self._volume_identifiers = []

    def _AddVolume(self, volume):
        """Adds a volume.

        Args:
          volume (Volume): a volume.

        Raises:
          KeyError: if volume is already set for the corresponding volume
              identifier.
        """
        if volume.identifier in self._volumes:
            raise KeyError(
                (
                    f"Volume object already set for volume identifier: "
                    f"{volume.identifier:s}"
                )
            )

        self._volumes[volume.identifier] = volume
        self._volume_identifiers.append(volume.identifier)

    @abc.abstractmethod
    def _Parse(self):
        """Extracts sections and volumes from the volume system."""

    @property
    def number_of_sections(self):
        """Retrieves the number of sections.

        Returns:
          int: number of sections.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return len(self._sections)

    @property
    def number_of_volumes(self):
        """Retrieves the number of volumes.

        Returns:
          int: number of volumes.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return len(self._volumes)

    @property
    def sections(self):
        """Retrieves sections.

        Returns:
          list[VolumeExtent]: sections.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return self._sections

    @property
    def volume_identifiers(self):
        """Retrieves volume identifiers.

        Returns:
          list[str]: volume identifiers.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return list(self._volume_identifiers)

    @property
    def volumes(self):
        """Retrieves volumes.

        Returns:
          generator(Volume): volumes generator.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return iter(self._volumes.values())

    def GetSectionByIndex(self, section_index):
        """Retrieves a specific section based on the index.

        Args:
          section_index (int): index of the section.

        Returns:
          VolumeExtent: a volume extent or None if not available.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        if section_index < 0 or section_index >= len(self._sections):
            return None

        return self._sections[section_index]

    def GetVolumeByIdentifier(self, volume_identifier):
        """Retrieves a specific volume based on the identifier.

        Args:
          volume_identifier (str): identifier of the volume within
              the volume system.

        Returns:
          Volume: a volume.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        return self._volumes[volume_identifier]

    def GetVolumeByIndex(self, volume_index):
        """Retrieves a specific volume based on the index.

        Args:
          volume_index (int): index of the volume.

        Returns:
          Volume: a volume or None if not available.
        """
        if not self._is_parsed:
            self._Parse()
            self._is_parsed = True

        if volume_index < 0 or volume_index >= len(self._volume_identifiers):
            return None

        volume_identifier = self._volume_identifiers[volume_index]
        return self._volumes[volume_identifier]

    def Open(self, path_spec):
        """Opens a volume defined by path specification.

        Args:
          path_spec (PathSpec): a path specification.

        Raises:
          VolumeSystemError: if the virtual file system representing the volume
              system could not be resolved.
        """
        self._file_system = resolver.Resolver.OpenFileSystem(path_spec)
        if self._file_system is None:
            raise errors.VolumeSystemError("Unable to resolve path specification.")

        if self._file_system.type_indicator != self.TYPE_INDICATOR:
            raise errors.VolumeSystemError("Unsupported type indicator.")
