#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2013 The Plaso Project Authors.
# Please see the AUTHORS file for details on individual authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This file contains a console, the CLI friendly front-end to plaso."""

import argparse
import logging
import os
import random
import sys
import tempfile

from dfvfs.lib import definitions
from dfvfs.path import factory as path_spec_factory
from dfvfs.resolver import resolver as path_spec_resolver

try:
  # Support version 1.X of IPython.
  # pylint: disable=no-name-in-module
  from IPython.terminal.embed import InteractiveShellEmbed
except ImportError:
  # Support version older than 1.X of IPython.
  # pylint: disable=no-name-in-module
  from IPython.frontend.terminal.embed import InteractiveShellEmbed

from IPython.config.loader import Config

# pylint: disable=unused-import
from plaso import analysis
from plaso import filters
from plaso import formatters
from plaso import output
from plaso import parsers
from plaso import preprocessors

from plaso.engine import collector
from plaso.engine import scanner
from plaso.engine import utils as engine_utils
from plaso.engine import engine

from plaso.frontend import frontend
from plaso.frontend import rpc_proxy
from plaso.frontend import utils as frontend_utils

from plaso.lib import binary
from plaso.lib import bufferlib
from plaso.lib import errors
from plaso.lib import event
from plaso.lib import eventdata
from plaso.lib import filter_interface
from plaso.lib import foreman
from plaso.lib import lexer
from plaso.lib import objectfilter
from plaso.lib import output as output_lib
from plaso.lib import pfilter
from plaso.lib import process_info
from plaso.lib import proxy
from plaso.lib import putils
from plaso.lib import queue
from plaso.lib import registry as class_registry
from plaso.lib import storage
from plaso.lib import timelib
from plaso.lib import utils

from plaso.output import helper as output_helper

from plaso.parsers import manager as parsers_manager
from plaso.parsers import plugins
from plaso.parsers import text_parser
from plaso.proto import plaso_storage_pb2

from plaso.serializer import interface as serializer_interface
from plaso.serializer import json_serializer
from plaso.serializer import protobuf_serializer

from plaso.unix import bsmtoken

from plaso.winnt import environ_expand
from plaso.winnt import known_folder_ids

from plaso.winreg import cache as win_registry_cache
from plaso.winreg import interface as win_registry_interface
from plaso.winreg import path_expander
from plaso.winreg import utils as win_registry_utils
from plaso.winreg import winpyregf
from plaso.winreg import winregistry


class PshellFrontend(frontend.ExtractionFrontend):
  """Class that implements the pshell front-end."""

  _BYTES_IN_A_MIB = 1024 * 1024

  def __init__(self):
    """Initializes the front-end object."""
    input_reader = frontend.StdinFrontendInputReader()
    output_writer = frontend.StdoutFrontendOutputWriter()

    super(PshellFrontend, self).__init__(input_reader, output_writer)


def FindAllOutputs():
  """FindAllOutputs() - All available outputs."""
  return putils.FindAllOutputs()


def GetEventData(event_proto, before=0):
  """Prints a hexdump of the event data."""
  return frontend_utils.OutputWriter.GetEventDataHexDump(event_proto, before)


def GetFileEntryFromEventObject(event_object):
  """Return a file entry object from a pathspec object.

  Args:
    event_object: An event object (an instance of EventObject).

  Returns:
    A file entry object (instance of vfs.file_entry.FileEntry) or
    None if the event object doesn't have a defined path spec.
  """
  path_spec = getattr(event_object, 'pathspec', None)

  if not path_spec:
    return

  return path_spec_resolver.Resolver.OpenFileEntry(path_spec)


def GetParserNames(parser_filter_string=None):
  """Retrieves the parser names.

  Args:
    parser_filter_string: Optional parser filter string. The default is None.

  Returns:
    A list of parser names.
  """
  return parsers_manager.ParsersManager.GetParserNames(
      parser_filter_string=parser_filter_string)


def GetParserObjects(parser_filter_string=None):
  """Retrieves the parser objects.

  Args:
    parser_filter_string: Optional parser filter string. The default is None.

  Returns:
    A list of parser objects (instances of BaseParser).
  """
  return parsers_manager.ParsersManager.GetParserObjects(
      parser_filter_string=parser_filter_string)


def OpenOSFile(path):
  """Opens a file entry from the OS."""
  if not os.path.isfile(path):
    logging.error(u'File: {0:s} does not exist.'.format(path))
    return

  path_spec = path_spec_factory.Factory.NewPathSpec(
      definitions.TYPE_INDICATOR_OS, location=path)
  return path_spec_resolver.Resolver.OpenFileEntry(path_spec)


def OpenStorageFile(storage_path):
  """Opens a storage file and returns the storage file object."""
  if not os.path.isfile(storage_path):
    return

  try:
    store = storage.StorageFile(storage_path, read_only=True)
  except IOError:
    print 'Unable to load storage file, not a storage file?'

  return store


def OpenTskFile(image_path, image_offset, path=None, inode=None):
  """Opens a file entry of a file inside an image file."""
  path_spec = path_spec_factory.Factory.NewPathSpec(
      definitions.TYPE_INDICATOR_OS, location=image_path)

  if image_offset > 0:
    volume_path_spec = path_spec_factory.Factory.NewPathSpec(
        definitions.TYPE_INDICATOR_TSK_PARTITION, start_offset=image_offset,
        parent=path_spec)
  else:
    volume_path_spec = path_spec

  if inode is not None:
    if path is None:
      path = u''
    path_spec = path_spec_factory.Factory.NewPathSpec(
        definitions.TYPE_INDICATOR_TSK, inode=inode, location=path,
        parent=volume_path_spec)
  else:
    path_spec = path_spec_factory.Factory.NewPathSpec(
        definitions.TYPE_INDICATOR_TSK, location=path, parent=volume_path_spec)

  return path_spec_resolver.Resolver.OpenFileEntry(path_spec)


def OpenVssFile(path, image_path, store_number, image_offset):
  """Opens a file entry inside a VSS inside an image file."""
  path_spec = path_spec_factory.Factory.NewPathSpec(
      definitions.TYPE_INDICATOR_OS, location=image_path)

  if image_offset > 0:
    volume_path_spec = path_spec_factory.Factory.NewPathSpec(
        definitions.TYPE_INDICATOR_TSK_PARTITION, start_offset=image_offset,
        parent=path_spec)
  else:
    volume_path_spec = path_spec

  store_number -= 1

  path_spec = path_spec_factory.Factory.NewPathSpec(
      definitions.TYPE_INDICATOR_VSHADOW, store_index=store_number,
      parent=volume_path_spec)
  path_spec = path_spec_factory.Factory.NewPathSpec(
      definitions.TYPE_INDICATOR_TSK, location=path, parent=path_spec)

  return path_spec_resolver.Resolver.OpenFileEntry(path_spec)


def ParseFile(file_entry):
  """Parse a file given a file entry or path and return a list of results.

  Args:
    file_entry: Either a file entry object (instance of dfvfs.FileEntry)
                or a string containing a path (absolute or relative) to a
                local file.

  Returns:
    A list of event object (instance of EventObject) that were extracted from
    the file (or an empty list if no events were extracted).
  """
  if not file_entry:
    return

  if isinstance(file_entry, basestring):
    file_entry = OpenOSFile(file_entry)

  # Set up the engine.
  collection_queue = queue.SingleThreadedQueue()
  storage_queue = queue.SingleThreadedQueue()
  parse_error_queue = queue.SingleThreadedQueue()
  engine_object = engine.Engine(
      collection_queue, storage_queue, parse_error_queue)

  # Create a worker.
  worker_object = engine_object.CreateExtractionWorker('0')
  # TODO: add support for parser_filter_string.
  worker_object.InitalizeParserObjects()
  worker_object.ParseFileEntry(file_entry)

  collection_queue.SignalEndOfInput()
  engine_object.SignalEndOfInputStorageQueue()

  results = []
  while True:
    try:
      item = storage_queue.PopItem()
    except errors.QueueEmpty:
      break

    if isinstance(item, queue.QueueEndOfInput):
      break

    results.append(item)
  return results


def Pfile2File(file_object, path):
  """Saves a file-like object to the path."""
  return frontend_utils.OutputWriter.WriteFile(file_object, path)


def PrintTimestamp(timestamp):
  """Prints a human readable timestamp from a timestamp value."""
  return frontend_utils.OutputWriter.GetDateTimeString(timestamp)


def PrintTimestampFromEvent(event_object):
  """Prints a human readable timestamp from values stored in an event object."""
  return PrintTimestamp(getattr(event_object, 'timestamp', 0))


def Main():
  """Start the tool."""
  temp_location = tempfile.gettempdir()

  options = putils.Options()

  # Set the default options.
  options.buffer_size = 0
  options.debug = False
  options.filename = '.'
  options.file_filter = ''
  options.filter = ''
  options.image = False
  options.image_offset = None
  options.image_offset_bytes = None
  options.old_preprocess = False
  options.open_files = False
  options.output = os.path.join(temp_location, 'wheredidmytimelinego.dump')
  options.output_module = ''
  options.parsers = ''
  options.parse_vss = False