"""The date-less log format helper mix-in."""

from dfvfs.lib import definitions as dfvfs_definitions
from dfvfs.resolver import resolver as path_spec_resolver

from plaso.containers import events


class DateLessLogFormatHelper:
  """Date-less log format helper mix-in."""

  _MONTH_DICT = {
      'jan': 1,
      'feb': 2,
      'mar': 3,
      'apr': 4,
      'may': 5,
      'jun': 6,
      'jul': 7,
      'aug': 8,
      'sep': 9,
      'oct': 10,
      'nov': 11,
      'dec': 12}

  _VALID_MONTHS = frozenset(range(1, 13))

  # The date-less log format only supports time.
  _GRANULARITY_NO_DATE = 'd'

  # The date-less log format only supports month and day of month.
  _GRANULARITY_NO_YEAR = 'y'

  def __init__(self):
    """Initializes the date-less log format helper mix-in."""
    super().__init__()
    self._base_date = None
    self._date = (0, 0, 0)
    self._granularity = self._GRANULARITY_NO_YEAR
    self._maximum_date = None
    self._relative_date = (0, 0, 0)

  def _GetDatesFromFileEntry(self, file_entry):
    """Retrieves the dates from the file entry date and time values.

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

    Returns:
      set[tuple[int, int, int]]: dates, as tuple of year, month, day, of the
          file entry.
    """
    if file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_GZIP:
      # Ignore a gzip file that contains a modification timestamp of 0.
      if (file_entry.modification_time and
          file_entry.modification_time.timestamp > 0):
        date_tuple = file_entry.modification_time.GetDate()
        return set([date_tuple])

    dates = set()

    for attribute_name in ('change_time', 'creation_time', 'modification_time'):
      date_time = getattr(file_entry, attribute_name, None)
      if date_time:
        date_tuple = date_time.GetDate()

        if (date_tuple == (1970, 1, 1) and
            file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_GZIP):
          continue

        dates.add(date_tuple)

    return dates

  def _GetMonthFromString(self, month_string):
    """Retrieves a numeric month value from a string.

    Args:
      month_string (str): month formatted as a string.

    Returns:
      int: month formatted as an integer, where January is 1.
    """
    # TODO: add support for localization.
    return self._MONTH_DICT.get(month_string.lower())

  def _GetRelativeYear(self):
    """Retrieves the relative year.

    Returns:
      int: relative year.
    """
    return self._relative_date[0]

  def _GetYear(self):
    """Retrieves the year.

    Returns:
      int: year.
    """
    return self._date[0]

  def _SetEstimatedDate(self, parser_mediator):
    """Estimate the date based on the file entry dates.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
    """
    self._base_date = None
    self._date = (0, 0, 0)
    self._granularity = self._GRANULARITY_NO_DATE
    self._maximum_date = None
    self._relative_date = (0, 0, 0)

    dates = set()

    file_entry = parser_mediator.GetFileEntry()
    if file_entry:
      dates = self._GetDatesFromFileEntry(file_entry)

    if not dates and file_entry.type_indicator in (
        dfvfs_definitions.TYPE_INDICATOR_COMPRESSED_STREAM,
        dfvfs_definitions.TYPE_INDICATOR_GZIP):

      parent_file_entry = path_spec_resolver.Resolver.OpenFileEntry(
          file_entry.path_spec.parent,
          resolver_context=parser_mediator.resolver_context)
      if parent_file_entry:
        dates = self._GetDatesFromFileEntry(parent_file_entry)

    if dates:
      self._base_date = min(dates)
      self._date = self._base_date
      self._maximum_date = max(dates)

  def _SetEstimatedYear(self, parser_mediator):
    """Estimate the year based on the file entry dates.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
    """
    self._SetEstimatedDate(parser_mediator)

    self._date = (self._date[0], 0, 0)
    self._granularity = self._GRANULARITY_NO_YEAR

  def _SetMonthAndYear(self, month, year):
    """Sets the month and year.

    Args:
      month (int): month.
      year (int): year.

    Raises:
      ValueError: if month contains an unsupported value.
    """
    if month not in self._VALID_MONTHS:
      raise ValueError(f'Invalid month: {month!s}')

    self._date = (year, month, 0)
    self._granularity = self._GRANULARITY_NO_YEAR
    self._relative_date = (0, 0, 0)

  def _UpdateYear(self, month):
    """Updates the year based on the month observed in the log format.

    Args:
      month (int): month observed in the log format, where January is 1.

    Raises:
      ValueError: if month contains an unsupported value.
    """
    if month not in self._VALID_MONTHS:
      raise ValueError(f'Invalid month: {month!s}')

    last_year, last_month, _ = self._date

    if last_month:
      relative_year, relative_month, relative_day_of_month = self._relative_date

      # Account for log formats that allow out-of-order date and time values
      # (Apr->May->Apr) such as rsyslog with the RepeatedMsgReduction setting
      # enabled.
      if month + 1 < last_month:
        self._relative_date = (
            relative_year + 1, relative_month, relative_day_of_month)
        last_year += 1

      # Account for out-of-order Jan->Dec->Jan with the exception of the start
      # of the log file.
      elif relative_year > 0 and last_month == 1 and month == 12:
        self._relative_date = (
            relative_year - 1, relative_month, relative_day_of_month)
        last_year -= 1

    self._date = (last_year, month, 0)

  def GetDateLessLogHelper(self):
    """Retrieves a date-less log helper attribute container.

    Returns:
      DateLessLogHelper: date-less log helper.
    """
    date_less_log_helper = events.DateLessLogHelper()
    date_less_log_helper.earliest_date = self._base_date
    date_less_log_helper.granularity = self._granularity
    date_less_log_helper.last_relative_date = self._relative_date
    date_less_log_helper.latest_date = self._maximum_date

    return date_less_log_helper
