"""The preprocess mediator."""

import pytz

from plaso.containers import warnings
from plaso.helpers.windows import eventlog_providers
from plaso.helpers.windows import time_zones
from plaso.preprocessors import logger


class PreprocessMediator:
    """Preprocess mediator.

    Attributes:
      code_page (str): code page.
      hostname (HostnameArtifact): hostname.
      language (str): language.
      time_zone (datetime.tzinfo): time zone.
    """

    def __init__(self, storage_writer):
        """Initializes a preprocess mediator.

        Args:
          storage_writer (StorageWriter): storage writer, to store preprocessing
              information in.
        """
        super().__init__()
        self._available_time_zones = {}
        self._environment_variables = {}
        self._file_entry = None
        self._storage_writer = storage_writer
        self._windows_eventlog_providers_helper = (
            eventlog_providers.WindowsEventLogProvidersHelper()
        )
        self._windows_eventlog_providers = {}
        self._windows_eventlog_providers_by_identifier = {}
        self._values = {}

        self.code_page = None
        self.hostname = None
        self.language = None
        self.time_zone = None

    def AddArtifact(self, artifact_attribute_container):
        """Adds a pre-processing artifact attribute container.

        Args:
          artifact_attribute_container (ArtifactAttributeContainer): artifact
              attribute container.
        """
        if self._storage_writer:
            self._storage_writer.AddAttributeContainer(artifact_attribute_container)

    def AddEnvironmentVariable(self, environment_variable_artifact):
        """Adds an environment variable.

        Args:
          environment_variable_artifact (EnvironmentVariableArtifact): environment
              variable artifact.

        Raises:
          KeyError: if the environment variable already exists.
        """
        logger.debug(
            (
                f"setting environment variable: {environment_variable_artifact.name:s} "
                f'to: "{environment_variable_artifact.value:s}"'
            )
        )

        name = environment_variable_artifact.name.upper()
        if name in self._environment_variables:
            raise KeyError(
                (
                    f"Environment variable: {environment_variable_artifact.name:s} "
                    f"already exists."
                )
            )

        self._environment_variables[name] = environment_variable_artifact

        if self._storage_writer:
            self._storage_writer.AddAttributeContainer(environment_variable_artifact)

    def AddHostname(self, hostname_artifact):
        """Adds a hostname.

        Args:
          hostname_artifact (HostnameArtifact): hostname artifact.
        """
        # TODO: change storage and pre-processor to handle more than 1 hostname.
        self.hostname = hostname_artifact

    def AddTimeZoneInformation(self, time_zone_artifact):
        """Adds a time zone defined by the operating system.

        Args:
          time_zone_artifact (TimeZoneArtifact): time zone artifact.

        Raises:
          KeyError: if the time zone already exists.
        """
        if time_zone_artifact.name in self._available_time_zones:
            raise KeyError(f"Time zone: {time_zone_artifact.name:s} already exists.")

        self._available_time_zones[time_zone_artifact.name] = time_zone_artifact

        if self._storage_writer:
            self._storage_writer.AddAttributeContainer(time_zone_artifact)

    def AddUserAccount(self, user_account):
        """Adds an user account.

        Args:
          user_account (UserAccountArtifact): user account artifact.

        Raises:
          KeyError: if the user account already exists.
        """
        if not user_account.username:
            logger.debug(f"adding user account: {user_account.identifier:s}")
        else:
            logger.debug(
                f"adding user account: {user_account.username:s} "
                f"({user_account.identifier:s})"
            )

        if self._storage_writer:
            self._storage_writer.AddAttributeContainer(user_account)

    def AddWindowsEventLogProvider(self, windows_eventlog_provider):
        """Adds a Windows EventLog provider.

        Args:
          windows_eventlog_provider (WindowsEventLogProviderArtifact): Windows
              EventLog provider.

        Raises:
          KeyError: if the Windows EventLog provider already exists.
        """
        existing_provider = None
        provider_identifier = windows_eventlog_provider.identifier
        log_source = windows_eventlog_provider.log_sources[0]

        if provider_identifier:
            existing_provider = self._windows_eventlog_providers_by_identifier.get(
                provider_identifier, None
            )

        if not existing_provider:
            existing_provider = self._windows_eventlog_providers.get(log_source)

        if existing_provider:
            self._windows_eventlog_providers_helper.Merge(
                existing_provider, windows_eventlog_provider
            )

            if self._storage_writer:
                self._storage_writer.UpdateAttributeContainer(existing_provider)

        else:
            self._windows_eventlog_providers_helper.NormalizeMessageFiles(
                windows_eventlog_provider
            )

            self._windows_eventlog_providers[log_source] = windows_eventlog_provider

            if self._storage_writer:
                self._storage_writer.AddAttributeContainer(windows_eventlog_provider)

            if provider_identifier:
                self._windows_eventlog_providers_by_identifier[provider_identifier] = (
                    windows_eventlog_provider
                )

    def GetEnvironmentVariable(self, name):
        """Retrieves an environment variable.

        Args:
          name (str): name of the environment variable.

        Returns:
          EnvironmentVariableArtifact: environment variable artifact or None
              if there was no value set for the given name.
        """
        name = name.upper()
        return self._environment_variables.get(name)

    def GetEnvironmentVariables(self):
        """Retrieves the environment variables.

        Returns:
          list[EnvironmentVariableArtifact]: environment variable artifacts.
        """
        return self._environment_variables.values()

    def GetValue(self, identifier):
        """Retrieves a value by identifier.

        Args:
          identifier (str): case insensitive unique identifier for the value.

        Returns:
          object: value or None if not available.
        """
        identifier = identifier.lower()
        return self._values.get(identifier)

    def GetValues(self):
        """Retrieves the values.

        Returns:
          list[tuple[str, object]]: values.
        """
        return self._values.items()

    def ProducePreprocessingWarning(self, plugin_name, message):
        """Produces a preprocessing warning.

        Args:
          plugin_name (str): name of the preprocess plugin.
          message (str): message of the warning.
        """
        if self._storage_writer:
            path_spec = None
            if self._file_entry:
                path_spec = self._file_entry.path_spec

            warning = warnings.PreprocessingWarning(
                message=message, path_spec=path_spec, plugin_name=plugin_name
            )
            self._storage_writer.AddAttributeContainer(warning)

        logger.debug(f"[{plugin_name:s}] {message:s}")

    def Reset(self):
        """Resets the values stored in the mediator."""
        self._available_time_zones = {}
        self._environment_variables = {}
        self._file_entry = None
        self._windows_eventlog_providers = {}
        self._windows_eventlog_providers_by_identifier = {}
        self._values = {}

        self.code_page = None
        self.hostname = None
        self.language = None
        self.time_zone = None

    def SetCodePage(self, code_page):
        """Sets the code page.

        Args:
          code_page (str): code_page.

        Raises:
          ValueError: if the code page is not supported.
        """
        logger.debug(f'setting code page to: "{code_page:s}"')
        self.code_page = code_page

    def SetFileEntry(self, file_entry):
        """Sets the active file entry.

        Args:
          file_entry (dfvfs.FileEntry): file entry.
        """
        self._file_entry = file_entry

    def SetLanguage(self, language):
        """Sets the language.

        Args:
          language (str): language.

        Raises:
          ValueError: if the language is not supported.
        """
        self.language = language

    def SetTimeZone(self, time_zone):
        """Sets the time zone.

        Args:
          time_zone (str): time zone.

        Raises:
          ValueError: if the time zone is not supported.
        """
        # Get the "normalized" name of a Windows time zone name.
        if time_zone.startswith("@tzres.dll,"):
            mui_form_time_zones = {
                time_zone_artifact.mui_form: time_zone_artifact.name
                for time_zone_artifact in self._available_time_zones.values()
            }

            time_zone = mui_form_time_zones.get(time_zone, time_zone)
        else:
            localized_time_zones = {
                time_zone_artifact.localized_name: time_zone_artifact.name
                for time_zone_artifact in self._available_time_zones.values()
            }

            time_zone = localized_time_zones.get(time_zone, time_zone)

        # Map a Windows time zone name to a Python time zone name.
        time_zone = time_zones.WINDOWS_TIME_ZONES.get(time_zone, time_zone)

        try:
            self.time_zone = pytz.timezone(time_zone)
        except pytz.UnknownTimeZoneError:
            raise ValueError(f"Unsupported time zone: {time_zone!s}")

    def SetValue(self, identifier, value):
        """Sets a value by identifier.

        Args:
          identifier (str): case insensitive unique identifier for the value.
          value (object): value.

        Raises:
          TypeError: if the identifier is not a string type.
        """
        identifier = identifier.lower()
        if identifier not in self._values:
            self._values[identifier] = value
