"""Text parser plugin for viminfo files."""

import pyparsing

from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.containers import events
from plaso.lib import errors
from plaso.parsers import text_parser
from plaso.parsers.text_plugins import interface


class VimInfoEventData(events.EventData):
  """VimInfo event data.

  Attributes:
    filename (str): the name of the file that was opened/edited.
    history_type (str): the Vim history type.
    history_value (str): the Vim history value.
    item_number (int): the item number of the history type.
    recorded_time (dfdatetime.DateTimeValues): date and time the log entry
        was recorded.
  """

  DATA_TYPE = 'viminfo:history'

  def __init__(self):
    """Initializes event data."""
    super().__init__(data_type=self.DATA_TYPE)
    self.filename = None
    self.history_type = None
    self.history_value = None
    self.item_number = None
    self.recorded_time = None


class VimInfoTextPlugin(interface.TextPlugin,):
  """Parses events from Viminfo files."""

  NAME = 'viminfo'
  DATA_FORMAT = 'Viminfo file'

  ENCODING = 'utf-8'

  _FILENAME = '.viminfo'

  _INTEGER = pyparsing.Word(pyparsing.nums).set_parse_action(
      lambda tokens: int(tokens[0], 10))

  _END_OF_LINE = pyparsing.Suppress(pyparsing.LineEnd())

  _HEADER_1 = (
      pyparsing.Literal('# This viminfo file was generated by Vim ') +
      pyparsing.Word(pyparsing.nums + '.') +
      _END_OF_LINE)

  _HEADER_2 = (
      pyparsing.Literal('# You may edit it if ') + (
          pyparsing.Literal('you\'re careful!') ^
          pyparsing.Literal('you are careful!')) + _END_OF_LINE)

  _VERSION_HEADER = (
      pyparsing.Literal('# Viminfo version') +
      _END_OF_LINE)

  _VERSION_VALUE = (
      pyparsing.Literal('|') +
      pyparsing.Word(pyparsing.nums + ',').set_results_name('version') +
      _END_OF_LINE)

  _ENCODING_HEADER = (
      pyparsing.Literal('# Value of \'encoding\' when this file was written') +
      _END_OF_LINE)

  _ENCODING_VALUE = (
      pyparsing.Literal('*') +
      pyparsing.Literal('encoding=') +
      pyparsing.Word(pyparsing.alphanums + '-').set_results_name('encoding') +
      _END_OF_LINE)

  _PREAMBLE = (
      _HEADER_1 + _HEADER_2 + _VERSION_HEADER + _VERSION_VALUE +
      _ENCODING_HEADER + _ENCODING_VALUE)

  _HLSEARCH = (
      pyparsing.Literal('# hlsearch on (H) or off (h):') +
      _END_OF_LINE +
      pyparsing.Word('~/hH').set_results_name('hlsearch') +
      _END_OF_LINE)

  # TODO: https://github.com/vim/vim/blob/master/src/viminfo.c#L1525
  _SEARCH_PATTERN = (
      pyparsing.Literal('# Last Search Pattern:') +
      _END_OF_LINE +
      pyparsing.restOfLine().set_results_name('search_pattern') +
      _END_OF_LINE +
      _END_OF_LINE)

  # TODO: https://github.com/vim/vim/blob/master/src/viminfo.c#L1525
  _SUBSTITUTE_SEARCH_PATTERN = (
      pyparsing.Literal('# Last Substitute Search Pattern:') +
      _END_OF_LINE +
      pyparsing.restOfLine().set_results_name('substitute_search_pattern') +
      _END_OF_LINE +
      _END_OF_LINE)

  _SUBSTITUTE_STRING = (
      pyparsing.Literal('# Last Substitute String:') +
      _END_OF_LINE +
      pyparsing.restOfLine().set_results_name('last_substitute_string') +
      _END_OF_LINE +
      _END_OF_LINE)

  _BAR_ITEM = (
      pyparsing.Literal('|') +
      pyparsing.Word(pyparsing.nums, exact=1) + pyparsing.Suppress(',') +
      pyparsing.Word(pyparsing.nums, exact=1) + pyparsing.Suppress(',') +
      pyparsing.Word(pyparsing.nums, exact=10) + pyparsing.Suppress(',') +
      pyparsing.Optional(_INTEGER) +
      pyparsing.Suppress(',') +
      pyparsing.restOfLine() + _END_OF_LINE)

  _REGISTER_CONTINUATION = (
      pyparsing.Literal('|<"') +
      pyparsing.restOfLine() + _END_OF_LINE)

  _REGISTER_ITEM = (
      pyparsing.Literal('|') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      pyparsing.Word(pyparsing.nums, exact=10) + pyparsing.Suppress(',') +
      pyparsing.Group(
          pyparsing.restOfLine() + _END_OF_LINE +
          pyparsing.ZeroOrMore(_REGISTER_CONTINUATION)))

  _FILEMARK_ITEM = pyparsing.Group(
      pyparsing.Literal('|') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      _INTEGER + pyparsing.Suppress(',') +
      pyparsing.Word(pyparsing.nums, exact=10) + pyparsing.Suppress(',') +
      pyparsing.restOfLine() + _END_OF_LINE)

  _COMMAND_LINE_ITEM = pyparsing.Group(
      pyparsing.Literal(':') +
      pyparsing.restOfLine() + _END_OF_LINE +
      _BAR_ITEM).set_results_name('command_line_items*')

  _COMMAND_LINE_HISTORY = (
      pyparsing.Literal('# Command Line History (newest to oldest):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_COMMAND_LINE_ITEM) +
      _END_OF_LINE)

  _SEARCH_STRING_ITEM = pyparsing.Group(
      pyparsing.Literal('?') +
      pyparsing.restOfLine() +
      _END_OF_LINE +
      _BAR_ITEM).set_results_name('search_string_items*')

  _SEARCH_STRING_HISTORY = (
      pyparsing.Literal('# Search String History (newest to oldest):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_SEARCH_STRING_ITEM) +
      _END_OF_LINE)

  _EXPRESSION_ITEM = pyparsing.Group(
      pyparsing.Literal('=') +
      pyparsing.Word(pyparsing.alphas + '/:') +
      _END_OF_LINE +
      _BAR_ITEM).set_results_name('expression_history_items*')

  _EXPRESSION_HISTORY = (
      pyparsing.Literal('# Expression History (newest to oldest):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_EXPRESSION_ITEM))

  _INPUT_LINE_ITEM = pyparsing.Group(
      pyparsing.Literal('@') +
      pyparsing.Word(pyparsing.alphas + '/:') +
      _END_OF_LINE +
      _BAR_ITEM).set_results_name('input_line_history_items*')

  _INPUT_LINE_HISTORY = (
      pyparsing.Literal('# Input Line History (newest to oldest):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_INPUT_LINE_ITEM))

  _DEBUG_LINE_ITEM = pyparsing.Group(
      pyparsing.Literal('@') +
      pyparsing.Word(pyparsing.alphas + '/:') +
      _END_OF_LINE +
      _BAR_ITEM).set_results_name('debug_line_history_items*')

  _DEBUG_LINE_HISTORY = (
      pyparsing.Literal('# Debug Line History (newest to oldest):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_DEBUG_LINE_ITEM))

  _REGISTERS_CONTENT = (
      pyparsing.Suppress(pyparsing.White('\t')) +
      pyparsing.restOfLine() +
      _END_OF_LINE)

  # http://vimdoc.sourceforge.net/htmldoc/change.html#registers
  _REGISTERS_ITEM = pyparsing.Group(
      pyparsing.Literal('"') +
      pyparsing.Or([_INTEGER, pyparsing.Word(pyparsing.printables)]) +
      pyparsing.Suppress(pyparsing.White('\t')) +
      pyparsing.one_of(['BLOCK', 'CHAR', 'LINE']) +
      pyparsing.Suppress(pyparsing.White('\t')) +
      _INTEGER + _END_OF_LINE +
      pyparsing.Group(pyparsing.ZeroOrMore(_REGISTERS_CONTENT)) +
      _REGISTER_ITEM).set_results_name('registers_items*')

  _REGISTERS_HISTORY = (
      pyparsing.Literal('# Registers:') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_REGISTERS_ITEM))

  _FILEMARKS_ITEM = pyparsing.Group(
      pyparsing.Literal('\'') +
      _INTEGER +
      _INTEGER +
      _INTEGER +
      pyparsing.restOfLine() +
      _END_OF_LINE +
      _FILEMARK_ITEM).set_results_name('filemarks_items*')

  _FILEMARKS_HISTORY = (
      pyparsing.Literal('# File marks:') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_FILEMARKS_ITEM) +
      _END_OF_LINE)

  _JUMPLIST_ITEM = pyparsing.Group(
      pyparsing.Word('-\'') +
      _INTEGER +
      _INTEGER +
      pyparsing.restOfLine() +
      _END_OF_LINE +
      _FILEMARK_ITEM).set_results_name('jumplist_items*')

  _JUMPLIST_HISTORY = (
      pyparsing.Literal('# Jumplist (newest first):') +
      _END_OF_LINE +
      pyparsing.ZeroOrMore(_JUMPLIST_ITEM) +
      _END_OF_LINE)

  _HISTORY_MARKS_HISTORY = (
      pyparsing.Literal('# History of marks within files (newest to oldest):') +
      _END_OF_LINE)

  _LINE_STRUCTURES = [
      ('command_line_history', _COMMAND_LINE_HISTORY),
      ('debug_line_history', _DEBUG_LINE_HISTORY),
      ('expression_history', _EXPRESSION_HISTORY),
      ('filemarks_history', _FILEMARKS_HISTORY),
      ('history_marks_history', _HISTORY_MARKS_HISTORY),
      ('hlsearch', _HLSEARCH),
      ('input_line_history', _INPUT_LINE_HISTORY),
      ('jumplist_history', _JUMPLIST_HISTORY),
      ('preamble', _PREAMBLE),
      ('registers_history', _REGISTERS_HISTORY),
      ('search_pattern', _SEARCH_PATTERN),
      ('search_string_history', _SEARCH_STRING_HISTORY),
      ('substitute_search', _SUBSTITUTE_SEARCH_PATTERN),
      ('substitute_string', _SUBSTITUTE_STRING)]

  VERIFICATION_GRAMMAR = _PREAMBLE

  def _ParseCommandLineHistory(self, parser_mediator, structure):
    """Parses command line history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a command line history
    """
    for index, item in enumerate(structure.get('command_line_items', [])):
      event_data = VimInfoEventData()
      event_data.history_value = item[1]
      event_data.history_type = 'Command Line History'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseSearchStringHistory(self, parser_mediator, structure):
    """Parses search string history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a search string history
    """
    for index, item in enumerate(structure.get('search_string_items', [])):
      event_data = VimInfoEventData()
      event_data.history_value = item[1]
      event_data.history_type = 'Search String History'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseExpressionHistory(self, parser_mediator, structure):
    """Parses expression history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match an expression history
    """
    for index, item in enumerate(structure.get('expression_history_items', [])):
      event_data = VimInfoEventData()
      event_data.history_value = item[1]
      event_data.history_type = 'Expression History'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseInputLineHistory(self, parser_mediator, structure):
    """Parses input line history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match an input line history
    """
    for index, item in enumerate(structure.get('input_line_history_items', [])):
      event_data = VimInfoEventData()
      event_data.history_value = item[1]
      event_data.history_type = 'Input Line History'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseDebugLineHistory(self, parser_mediator, structure):
    """Parses debug line history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a debug line history
    """
    for index, item in enumerate(structure.get('debug_line_history_items', [])):
      event_data = VimInfoEventData()
      event_data.history_value = item[1]
      event_data.history_type = 'Debug Line History'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseRegistersHistory(self, parser_mediator, structure):
    """Parses register history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a Register history
    """
    for item in structure.get('registers_items', []):
      event_data = VimInfoEventData()
      event_data.history_value = '\n'.join(item[4])
      event_data.history_type = 'Register'
      event_data.item_number = item[1]
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[12])

      parser_mediator.ProduceEventData(event_data)

  def _ParseFilemarksHistory(self, parser_mediator, structure):
    """Parses filemarks history items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a Filemarks history
    """
    for index, item in enumerate(structure.get('filemarks_items', [])):
      event_data = VimInfoEventData()
      event_data.filename = item[4].strip()
      event_data.history_type = 'File mark'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[5][5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseJumplistHistory(self, parser_mediator, structure):
    """Parses jumplist items and creates VimInfoEventData objects.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      structure (pyparsing.ParseResults): structure of tokens derived from
          lines of text that match a JumpList history
    """
    for index, item in enumerate(structure.get('jumplist_items', [])):
      event_data = VimInfoEventData()
      event_data.filename = item[3].strip()
      event_data.history_type = 'Jumplist'
      event_data.item_number = index
      event_data.recorded_time = dfdatetime_posix_time.PosixTime(
           timestamp=item[4][5])

      parser_mediator.ProduceEventData(event_data)

  def _ParseRecord(self, parser_mediator, key, structure):
    """Parses a pyparsing structure.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      key (str): name of the parsed structure.
      structure (pyparsing.ParseResults): tokens from a parsed log line.

    Raises:
      ParseError: if the structure cannot be parsed.
    """
    if key == 'command_line_history':
      self._ParseCommandLineHistory(parser_mediator, structure)
    elif key == 'search_string_history':
      self._ParseSearchStringHistory(parser_mediator, structure)
    elif key == 'expression_history':
      self._ParseExpressionHistory(parser_mediator, structure)
    elif key == 'input_line_history':
      self._ParseInputLineHistory(parser_mediator, structure)
    elif key == 'debug_line_history':
      self._ParseDebugLineHistory(parser_mediator, structure)
    elif key == 'registers_history':
      self._ParseRegistersHistory(parser_mediator, structure)
    elif key == 'filemarks_history':
      self._ParseFilemarksHistory(parser_mediator, structure)
    elif key == 'jumplist_history':
      self._ParseJumplistHistory(parser_mediator, structure)
    # TODO(sydp): add support for history marks history lines
    # elif key == 'history_marks_history':
    #   self._ParseHistoryMarksHistory(self, parser_mediator, structure)

  def CheckRequiredFormat(self, parser_mediator, text_reader):
    """Check if the log record has the minimal structure required by the parser.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      text_reader (EncodedTextReader): text reader.

    Returns:
      bool: True if this is the correct plugin, False otherwise.
    """
    try:
      self._VerifyString(text_reader.lines)
    except errors.ParseError:
      return False

    return True


text_parser.TextLogParser.RegisterPlugin(VimInfoTextPlugin)
