# -*- coding: utf-8 -*-
"""The VHD image file-like object."""

import pyvhdi

from dfvfs import dependencies
from dfvfs.file_io import file_object_io
from dfvfs.lib import errors
from dfvfs.path import factory as path_spec_factory
from dfvfs.resolver import resolver


dependencies.CheckModuleVersion(u'pyvhdi')


class VHDIFile(file_object_io.FileObjectIO):
  """Class that implements a file-like object using pyvhdi."""

  def __init__(self, resolver_context, file_object=None):
    """Initializes the file-like object.

    Args:
      resolver_context: the resolver context (instance of resolver.Context).
      file_object: optional file-like object.
    """
    super(VHDIFile, self).__init__(resolver_context, file_object=file_object)
    self._parent_vhdi_files = []
    self._sub_file_objects = []

  def _Close(self):
    """Closes the file-like object."""
    super(VHDIFile, self)._Close()

    for vhdi_file in self._parent_vhdi_files:
      vhdi_file.close()

    for file_object in self._sub_file_objects:
      file_object.close()

    self._parent_vhdi_files = []
    self._sub_file_objects = []

  def _OpenFileObject(self, path_spec):
    """Opens the file-like object defined by path specification.

    Args:
      path_spec: the path specification (instance of PathSpec).

    Returns:
      A file-like object.

    Raises:
      PathSpecError: if the path specification is incorrect.
    """
    if not path_spec.HasParent():
      raise errors.PathSpecError(
          u'Unsupported path specification without parent.')

    file_object = resolver.Resolver.OpenFileObject(
        path_spec.parent, resolver_context=self._resolver_context)

    vhdi_file = pyvhdi.file()
    vhdi_file.open_file_object(file_object)

    if vhdi_file.parent_identifier:
      file_system = resolver.Resolver.OpenFileSystem(
          path_spec.parent, resolver_context=self._resolver_context)

      try:
        self._OpenParentFile(file_system, path_spec.parent, vhdi_file)
      finally:
        file_system.Close()

    self._sub_file_objects.append(file_object)

    self._parent_vhdi_files.reverse()
    self._sub_file_objects.reverse()

    return vhdi_file

  def _OpenParentFile(self, file_system, path_spec, vhdi_file):
    """Opens the parent file.

    Args:
      file_system: the file system (instance of FileSystem).
      path_spec: the path specification of the VHDI file (instance of
                 PathSpec).
      vhdi_file: the VHDI file (instance of pyvhdi.file).

    Raises:
      PathSpecError: if the path specification is incorrect.
    """
    location = getattr(path_spec, u'location', None)
    if not location:
      raise errors.PathSpecError(
          u'Unsupported path specification without location.')

    location_path_segments = file_system.SplitPath(location)

    parent_filename = vhdi_file.parent_filename
    _, _, parent_filename = parent_filename.rpartition(u'\\')

    location_path_segments.pop()
    location_path_segments.append(parent_filename)
    parent_file_location = file_system.JoinPath(location_path_segments)

    # Note that we don't want to set the keyword arguments when not used
    # because the path specification base class will check for unused
    # keyword arguments and raise.
    kwargs = path_spec_factory.Factory.GetProperties(path_spec)

    kwargs[u'location'] = parent_file_location
    if path_spec.parent is not None:
      kwargs[u'parent'] = path_spec.parent

    parent_file_path_spec = path_spec_factory.Factory.NewPathSpec(
        path_spec.type_indicator, **kwargs)

    if not file_system.FileEntryExistsByPathSpec(parent_file_path_spec):
      return

    file_object = resolver.Resolver.OpenFileObject(
        parent_file_path_spec, resolver_context=self._resolver_context)

    vhdi_parent_file = pyvhdi.file()
    vhdi_parent_file.open_file_object(file_object)

    if vhdi_parent_file.parent_identifier:
      self._OpenParentFile(
          file_system, parent_file_path_spec, vhdi_parent_file)

    vhdi_file.set_parent(vhdi_parent_file)

    self._parent_vhdi_files.append(vhdi_parent_file)
    self._sub_file_objects.append(file_object)

  def get_size(self):
    """Returns the size of the file-like object.

    Raises:
      IOError: if the file-like object has not been opened.
    """
    if not self._is_open:
      raise IOError(u'Not opened.')

    return self._file_object.get_media_size()
