"""
File adapters that allow to read Plesk backup files (including backup XML files)
from Plesk backup archives of different versions and OSes
"""

from parallels.core import messages
import tarfile
import zipfile
import gzip
from contextlib import closing
from StringIO import StringIO
import email

import logging

logger = logging.getLogger(__name__)


class PlainFileAdapter:
	def __init__(self, file_name):
		self.file_name = file_name

	def close(self):
		pass

	def get_names(self):
		return [self.file_name]

	def get_members(self):
		return [
			PlainFileAdapter.FileInfo(self.file_name, 0)
		]

	def extract(self, name):
		assert name == self.file_name
		return open(self.file_name)

	class FileInfo:
		def __init__(self, name, size):
			self.name = name
			self.size = size

		@property
		def is_file(self):
			return True


class ArchiveAdapter:
	"""Common interface for archives in tar, zip format."""
	def __init__(self, file_name=None, fileobj=None, mode='r'):
		self._file_name = file_name
		self._fileobj = fileobj
		self._mode = mode
		self._archive = None
	
	def close(self):
		if self._archive:
			self._archive.close()
			self._archive = None
		logger.debug(messages.DEBUG_CLOSED_ARCHIVE)

	def _get_archive(self):
		if not self._archive:
			self._archive = self._open()
		return self._archive


class TarFileAdapter(ArchiveAdapter):
	"""Provide common interface for TAR archives."""
	def _open(self):
		if self._fileobj:
			logger.debug(messages.OPENED_TAR_FILE_FROM_FILEOBJECT)
			return tarfile.open(fileobj=self._fileobj, mode=self._mode) 
		else:
			logger.debug(messages.DEBUG_OPENED_TAR, self._file_name)
			return tarfile.open(name=self._file_name, mode=self._mode)

	def get_names(self):
		return self._get_archive().getnames()

	def get_members(self):
		return map(TarFileAdapter.FileInfo, self._get_archive().getmembers())

	def extract(self, name):
		return self._get_archive().extractfile(name)

	def add_file(self, name, size, fileobj):
		tarinfo = tarfile.TarInfo(name)
		tarinfo.size = size
		self._get_archive().addfile(tarinfo, fileobj)

	class FileInfo:
		def __init__(self, member):
			self.member = member

		@property
		def is_file(self):
			return self.member.isfile()

		@property
		def name(self):
			return self.member.name

		@property
		def size(self):
			return self.member.size


class ZipFileAdapter(ArchiveAdapter):
	"""Provide common interface for ZIP archives."""
	def _open(self):
		if self._fileobj:
			logger.debug(messages.OPENED_ZIP_FILE_FROM_FILEOBJECT)
			return zipfile.ZipFile(self._fileobj, self._mode)
		else:
			logger.debug(messages.DEBUG_OPENED_ZIP, self._file_name)
			return zipfile.ZipFile(self._file_name, self._mode)

	def get_names(self):
		return self._get_archive().namelist()

	def get_members(self):
		return map(ZipFileAdapter.FileInfo, self._get_archive().infolist())

	def extract(self, name):
		return self._get_archive().open(name)

	def add_file(self, name, size, fileobj):
		self._get_archive().writestr(name, fileobj.read(size))

	class FileInfo:
		def __init__(self, info):
			self.info = info

		@property
		def is_file(self): return self.info.external_attr & 0x10 == 0 # 0x10 is a directory attribute

		@property
		def name(self): return self.info.filename

		@property
		def size(self): return self.info.file_size


class Plesk8UnixBackupFileAdapter:
	def __init__(self, file_name):
		with closing(gzip.open(file_name)) as fp:
			self.msg = email.message_from_file(fp)
			self.files = self.parse(self.msg)

	@classmethod
	def parse(cls, msg):
		files = {}

		if msg.is_multipart():
			for submsg in msg.get_payload():
				files.update(cls.parse(submsg))
		else:
			files[msg.get_param('name')] = msg

		return files

	def close(self):
		pass

	def get_names(self):
		return self.files.keys()

	def get_members(self):
		return [
			Plesk8UnixBackupFileAdapter.FileInfo(name, len(value.get_payload()))
			for name, value in self.files.iteritems()
		]

	def extract(self, name):
		return StringIO(self.files[name].get_payload())

	class FileInfo:
		def __init__(self, name, size):
			self.name = name
			self.size = size

		@property
		def is_file(self): return True

