# -*- coding: utf-8 -*-
"""This file contains the event formatters interface classes.

The l2t_csv and other formats are dependent on a message field,
referred to as description_long and description_short in l2t_csv.

Plaso no longer stores these field explicitly.

A formatter, with a format string definition, is used to convert
the event object values into a formatted string that is similar
to the description_long and description_short field.
"""

import logging
import re

from plaso.lib import errors
from plaso.lib import py2to3


class EventFormatter(object):
  """Base class to format event type specific data using a format string.

  Define the (long) format string and the short format string by defining
  FORMAT_STRING and FORMAT_STRING_SHORT. The syntax of the format strings
  is similar to that of format() where the place holder for a certain
  event object attribute is defined as {attribute_name}.
  """

  # The data type is a unique identifier for the event data. The current
  # approach is to define it as human readable string in the format
  # root:branch: ... :leaf, e.g. a page visited entry inside a Chrome History
  # database is defined as: chrome:history:page_visited.
  DATA_TYPE = u'internal'

  # The format string.
  FORMAT_STRING = u''
  FORMAT_STRING_SHORT = u''

  # The source short and long strings.
  SOURCE_SHORT = u'LOG'
  SOURCE_LONG = u''

  # The format string can be defined as:
  # {name}, {name:format}, {name!conversion}, {name!conversion:format}
  _FORMAT_STRING_ATTRIBUTE_NAME_RE = re.compile(
      u'{([a-z][a-zA-Z0-9_]*)[!]?[^:}]*[:]?[^}]*}')

  def __init__(self):
    """Initializes an event formatter object."""
    super(EventFormatter, self).__init__()
    self._format_string_attribute_names = None

  def _FormatMessage(self, format_string, event_values):
    """Determines the formatted message string.

    Args:
      format_string (str): message format string.
      event_values (dict[str, object]): event values.

    Returns:
      str: formatted message string.
    """
    if not isinstance(format_string, py2to3.UNICODE_TYPE):
      logging.warning(u'Format string: {0:s} is non-Unicode.'.format(
          format_string))

      # Plaso code files should be in UTF-8 any thus binary strings are
      # assumed UTF-8. If this is not the case this should be fixed.
      format_string = format_string.decode(u'utf-8', errors=u'ignore')

    try:
      message_string = format_string.format(**event_values)

    except KeyError as exception:
      error_message = (
          u'unable to format string: "{0:s}" event object is missing required '
          u'attributes: {1:s}').format(format_string, exception)
      error_message = (
          u'Event: {0:s} data type: {1:s} display name: {2:s} '
          u'parser chain: {3:s} with error: {4:s}').format(
              event_values[u'uuid'], event_values[u'data_type'],
              event_values[u'display_name'], event_values[u'parser'],
              error_message)
      logging.error(error_message)

      attribute_values = []
      for attribute, value in iter(event_values.items()):
        attribute_values.append(u'{0:s}: {1!s}'.format(attribute, value))

      message_string = u' '.join(attribute_values)

    except UnicodeDecodeError as exception:
      error_message = u'Unicode decode error: {0:s}'.format(exception)
      error_message = (
          u'Event: {0:s} data type: {1:s} display name: {2:s} '
          u'parser chain: {3:s} with error: {4:s}').format(
              event_values[u'uuid'], event_values[u'data_type'],
              event_values[u'display_name'], event_values[u'parser'],
              error_message)
      logging.error(error_message)

      message_string = u''

    # Strip carriage return and linefeed form the message strings.
    # Using replace function here because it is faster than re.sub() or
    # string.strip().
    return message_string.replace(u'\r', u'').replace(u'\n', u'')

  def _FormatMessages(self, format_string, short_format_string, event_values):
    """Determines the formatted message strings.

    Args:
      format_string (str): message format string.
      short_format_string (str): short message format string.
      event_values (dict[str, object]): event values.

    Returns:
      tuple(str, str): formatted message string and short message string.
    """
    message_string = self._FormatMessage(format_string, event_values)

    if short_format_string:
      short_message_string = self._FormatMessage(
          short_format_string, event_values)
    else:
      short_message_string = message_string

    # Truncate the short message string if necessary.
    if len(short_message_string) > 80:
      short_message_string = u'{0:s}...'.format(short_message_string[0:77])

    return message_string, short_message_string

  def GetFormatStringAttributeNames(self):
    """Retrieves the attribute names in the format string.

    Returns:
      set(str): attribute names.
    """
    if self._format_string_attribute_names is None:
      self._format_string_attribute_names = (