"""Parser for locate database (updatedb) files."""

import os

from dfdatetime import posix_time

from plaso.containers import events
from plaso.lib import definitions
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.lib import specification
from plaso.parsers import interface
from plaso.parsers import manager


class LocateDatabaseEvent(events.EventData):
    """Linux locate database (updatedb) event data.

    Attributes:
      entries (list[str]): contents of the locate database (updatedb) entry.
      path (str): path of the locate database (updatedb) entry.
      written_time (dfdatetime.DateTimeValues): entry written date and time.
    """

    DATA_TYPE = "linux:locate_database:entry"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.entries = None
        self.path = None
        self.written_time = None


class LocateDatabaseParser(interface.FileObjectParser, dtfabric_helper.DtFabricHelper):
    """Parser for locate database (updatedb) files."""

    NAME = "locate_database"
    DATA_FORMAT = "Locate database file (updatedb)"

    _DEFINITION_FILE = os.path.join(os.path.dirname(__file__), "locate.yaml")

    def __init__(self):
        """Initializes a locate database (updatedb) file parser."""
        super().__init__()
        self._cstring_map = self._GetDataTypeMap("cstring")
        self._directory_entry_header_map = self._GetDataTypeMap(
            "directory_entry_header"
        )
        self._directory_header_map = self._GetDataTypeMap("directory_header")

    def _ParseDirectoryEntry(self, file_object, file_offset):
        """Parses a locate database (updatedb) directory entry.

        Args:
          file_object (dfvfs.FileIO): file-like object to be parsed.
          file_offset (int): offset of the directory entry relative to the start of the
              file.

        Returns:
          tuple[list[str], int]: names of sub directory entries and total number of
              bytes read.
        """
        sub_entry_names = []
        total_data_size = 0

        # TODO: determine why "condition: directory_entry.type != 2" in dtFabric
        # definitions is currently not working and clean up code once fixed.
        directory_entry_type = 0
        while directory_entry_type != 2:
            directory_entry_header, data_size = self._ReadStructureFromFileObject(
                file_object, file_offset, self._directory_entry_header_map
            )

            file_offset += data_size
            total_data_size += data_size

            directory_entry_type = directory_entry_header.type
            if directory_entry_type != 2:
                directory_entry_path, data_size = self._ReadStructureFromFileObject(
                    file_object, file_offset, self._cstring_map
                )

                sub_entry_names.append(directory_entry_path)

                file_offset += data_size
                total_data_size += data_size

        return sub_entry_names, total_data_size

    @classmethod
    def GetFormatSpecification(cls):
        """Retrieves the format specification.

        Returns:
          FormatSpecification: format specification.
        """
        format_specification = specification.FormatSpecification(cls.NAME)
        format_specification.AddNewSignature(b"\x00mlocate", offset=0)
        return format_specification

    # pylint: disable=unused-argument
    def ParseFileObject(self, parser_mediator, file_object, **kwargs):
        """Parses a locate database (updatedb) file-like object.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          file_object (dfvfs.FileIO): file-like object to be parsed.

        Raises:
          WrongParser: when the file cannot be parsed, this will signal
              the event extractor to apply other parsers.
        """
        locate_database_header_map = self._GetDataTypeMap("locate_database_header")

        try:
            locate_database_header, file_offset = self._ReadStructureFromFileObject(
                file_object, 0, locate_database_header_map
            )
        except (ValueError, errors.ParseError) as exception:
            raise errors.WrongParser(
                f"Unable to parse locate database header with error: {exception!s}"
            )

        # Skip configuration block for now.
        file_offset += locate_database_header.configuration_block_size

        file_size = file_object.get_size()
        while file_offset + 16 < file_size:
            try:
                directory_header, data_size = self._ReadStructureFromFileObject(
                    file_object, file_offset, self._directory_header_map
                )
            except (ValueError, errors.ParseError) as exception:
                parser_mediator.ProduceWarning(
                    f"unable to parse locate directory header at offset: "
                    f"0x{file_offset:08x} with error: {exception!s}"
                )
                return

            file_offset += data_size

            corrupted = False

            timestamp = directory_header.nanoseconds + (
                directory_header.seconds * definitions.NANOSECONDS_PER_SECOND
            )
            try:
                entries, data_size = self._ParseDirectoryEntry(file_object, file_offset)

                file_offset += data_size

            except (ValueError, errors.ParseError) as exception:
                parser_mediator.ProduceWarning(
                    f"unable to parse directory entry at offset: 0x{file_offset:08x} "
                    f"with error: {exception!s}"
                )
                entries = None
                corrupted = True

            event_data = LocateDatabaseEvent()
            event_data.entries = entries or None
            event_data.path = directory_header.path
            event_data.written_time = posix_time.PosixTimeInNanoseconds(
                timestamp=timestamp
            )
            parser_mediator.ProduceEventData(event_data, corrupted=corrupted)


manager.ParsersManager.RegisterParser(LocateDatabaseParser)
