"""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)
