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

import tarfile
import zipfile
import gzip
from contextlib import closing
import email

from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.utils.common import open_no_inherit
from plesk_migrator.compatibility.utils import stringio

logger = get_logger(__name__)


class BaseArchiveFileAdapter(object):
    def close(self):
        raise NotImplementedError()

    def get_names(self):
        raise NotImplementedError()

    def get_members(self):
        raise NotImplementedError()

    def extract(self, name):
        raise NotImplementedError()


class PlainFileAdapter(BaseArchiveFileAdapter):
    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_no_inherit(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(BaseArchiveFileAdapter):
    """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 add_file(self, name, size, fileobj):
        raise NotImplementedError()

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

    def _open(self):
        raise NotImplementedError()


class TarFileAdapter(ArchiveAdapter):
    """Provide common interface for TAR archives."""
    def __init__(self, file_name=None, fileobj=None, mode='r'):
        super(TarFileAdapter, self).__init__(file_name, fileobj, mode)
        self._fp = None

    def _open(self):
        if self._fileobj:
            return tarfile.open(fileobj=self._fileobj, mode=self._mode)
        else:
            self._fp = open_no_inherit(self._file_name, 'rb')
            return tarfile.open(fileobj=self._fp, mode=self._mode)

    def close(self):
        super(TarFileAdapter, self).close()
        if self._fp:
            self._fp.close()

    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:
            return zipfile.ZipFile(self._fileobj, self._mode)
        else:
            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):
            # 0x10 is a directory attribute
            return self.info.external_attr & 0x10 == 0

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

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


class Plesk8UnixBackupFileAdapter(BaseArchiveFileAdapter):
    def __init__(self, file_name):
        with open_no_inherit(file_name, 'rb') as fp_file:
            with closing(gzip.GzipFile(fileobj=fp_file)) 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.items()
        ]

    def extract(self, name):
        return stringio(self.files[name].get_payload(), is_unicode=False)

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

        @property
        def is_file(self):
            return True
