"""File system stat object parser."""

import pytsk3

from dfvfs.lib import definitions as dfvfs_definitions

from plaso.containers import events
from plaso.parsers import interface
from plaso.parsers import manager


class FileStatEventData(events.EventData):
    """File system stat event data.

    Attributes:
      access_time (dfdatetime.DateTimeValues): file entry last access date
          and time.
      added_time (dfdatetime.DateTimeValues): file entry added date and time.
      attribute_names ([str]): extended attribute names.
      backup_time (dfdatetime.DateTimeValues): file entry backup date and time.
      change_time (dfdatetime.DateTimeValues): file entry inode change
          (or metadata last modification) date and time.
      creation_time (dfdatetime.DateTimeValues): file entry creation date
          and time.
      deletion_time (dfdatetime.DateTimeValues): file entry deletion date
          and time.
      display_name (str): display name.
      file_entry_type (int): dfVFS file entry type.
      file_size (int): file size in bytes.
      file_system_type (str): file system type.
      filename (str): name of the file.
      group_identifier (int): group identifier (GID), equivalent to st_gid.
      inode (int): inode of the file.
      is_allocated (bool): True if the file is allocated.
      mode (int): access mode, equivalent to st_mode & 0x0fff.
      modification_time (dfdatetime.DateTimeValues): file entry last modification
          date and time.
      number_of_links (int): number of hard links, equivalent to st_nlink.
      owner_identifier (int): user identifier (UID) of the owner, equivalent to
          st_uid.
    """

    DATA_TYPE = "fs:stat"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.access_time = None
        self.added_time = None
        self.attribute_names = None
        self.backup_time = None
        self.change_time = None
        self.creation_time = None
        self.deletion_time = None
        self.display_name = None
        self.file_entry_type = None
        self.file_size = None
        self.file_system_type = None
        self.filename = None
        self.group_identifier = None
        self.inode = None
        self.is_allocated = None
        self.mode = None
        self.modification_time = None
        self.number_of_links = None
        self.owner_identifier = None


class FileStatParser(interface.FileEntryParser):
    """Parses file system stat object."""

    NAME = "filestat"
    DATA_FORMAT = "file system stat information"

    _TSK_FS_TYPE_MAP = {}

    # Maps SleuthKit file system type enumeration values to a string.
    _TSK_FS_TYPE_MAP = {
        pytsk3.TSK_FS_TYPE_EXFAT: "exFAT",
        pytsk3.TSK_FS_TYPE_EXT2: "EXT2",
        pytsk3.TSK_FS_TYPE_EXT3: "EXT3",
        pytsk3.TSK_FS_TYPE_EXT4: "EXT4",
        pytsk3.TSK_FS_TYPE_EXT_DETECT: "EXT",
        pytsk3.TSK_FS_TYPE_FAT12: "FAT12",
        pytsk3.TSK_FS_TYPE_FAT16: "FAT16",
        pytsk3.TSK_FS_TYPE_FAT32: "FAT32",
        pytsk3.TSK_FS_TYPE_FAT_DETECT: "FAT",
        pytsk3.TSK_FS_TYPE_FFS1B: "FFS1b",
        pytsk3.TSK_FS_TYPE_FFS1: "FFS1",
        pytsk3.TSK_FS_TYPE_FFS2: "FFS2",
        pytsk3.TSK_FS_TYPE_FFS_DETECT: "FFS",
        pytsk3.TSK_FS_TYPE_HFS: "HFS",
        pytsk3.TSK_FS_TYPE_HFS_DETECT: "HFS",
        pytsk3.TSK_FS_TYPE_ISO9660: "ISO9660",
        pytsk3.TSK_FS_TYPE_ISO9660_DETECT: "ISO9660",
        pytsk3.TSK_FS_TYPE_NTFS: "NTFS",
        pytsk3.TSK_FS_TYPE_NTFS_DETECT: "NTFS",
        pytsk3.TSK_FS_TYPE_YAFFS2: "YAFFS2",
        pytsk3.TSK_FS_TYPE_YAFFS2_DETECT: "YAFFS2",
    }

    def _GetFileSystemTypeFromFileEntry(self, file_entry):
        """Retrieves the file system type indicator of a file entry.

        Args:
          file_entry (dfvfs.FileEntry): a file entry.

        Returns:
          str: file system type.
        """
        if file_entry.type_indicator != dfvfs_definitions.TYPE_INDICATOR_TSK:
            type_string = file_entry.type_indicator
        else:
            file_system = file_entry.GetFileSystem()
            tsk_fs_type = file_system.GetFsType()

            try:
                type_string = self._TSK_FS_TYPE_MAP.get(tsk_fs_type)
            except TypeError:
                # Older version of pytsk3 can raise:
                # TypeError: unhashable type: 'pytsk3.TSK_FS_TYPE_ENUM'
                type_string = f"{tsk_fs_type!s}"
                if type_string.startswith("TSK_FS_TYPE_"):
                    type_string = type_string[12:]
                if type_string.endswith("_DETECT"):
                    type_string = type_string[:-7]

        if not type_string:
            type_string = "UNKNOWN"
        return type_string

    def ParseFileEntry(self, parser_mediator, file_entry):
        """Parses a file entry.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          file_entry (dfvfs.FileEntry): a file entry.
        """
        file_system_type = self._GetFileSystemTypeFromFileEntry(file_entry)

        stat_attribute = file_entry.GetStatAttribute()

        attribute_names = []
        for attribute in file_entry.attributes:
            attribute_name = getattr(attribute, "name", None)
            if isinstance(attribute_name, bytes):
                attribute_name = attribute_name.decode("utf-8")

            if file_system_type != "NTFS" and attribute_name:
                attribute_names.append(attribute_name)

        event_data = FileStatEventData()
        event_data.access_time = file_entry.access_time
        event_data.added_time = file_entry.added_time
        event_data.attribute_names = attribute_names or None
        event_data.backup_time = file_entry.backup_time
        event_data.creation_time = file_entry.creation_time
        event_data.change_time = file_entry.change_time
        event_data.deletion_time = file_entry.deletion_time
        event_data.display_name = parser_mediator.GetDisplayNameForPathSpec(
            file_entry.path_spec
        )
        event_data.file_entry_type = file_entry.entry_type
        event_data.file_size = file_entry.size
        event_data.file_system_type = file_system_type
        event_data.filename = parser_mediator.GetRelativePathForPathSpec(
            file_entry.path_spec
        )
        event_data.is_allocated = file_entry.IsAllocated()
        event_data.modification_time = file_entry.modification_time

        if stat_attribute:
            event_data.group_identifier = stat_attribute.group_identifier
            event_data.inode = stat_attribute.inode_number
            if stat_attribute.mode is not None:
                event_data.mode = stat_attribute.mode & 0x0FFF
            event_data.number_of_links = stat_attribute.number_of_links
            event_data.owner_identifier = stat_attribute.owner_identifier

        parser_mediator.ProduceEventData(event_data)


manager.ParsersManager.RegisterParser(FileStatParser)
