from durus.utils import BytesIO
from qp.fill.static import FileStream
from qp.pub.common import not_found
import tarfile
import zipfile

class ArchiveWrapper (object):

    def __init__(self, path_to_archive):
        self.path_to_archive = path_to_archive
        self.archive = None

    def get_archive(self):
        if not self.archive:
            self.archive = self.open_archive(self.path_to_archive)
        return self.archive

    def open_archive(self, path_to_archive):
        """(path_to_archive : str)

        Return a reference to archive reading class saved on self.archive
        """
        raise NotImplementedError

    def get_names(self):
        """() -> [str]

        Return a list of file and directory names in the order they appear in
        the archive.  Directory names end with a slash '/' character.
        """
        raise NotImplementedError

    def has_member(self, member_name):
        """(name : str) -> bool

        True if member is in the archive
        """
        raise NotImplementedError

    def get_member_file(self, member_name):
        """(member_name : str) -> file
        """
        raise NotImplementedError

    def get_member_response(self, member_name):
        """(member_name : str) -> str | Stream

        Return the member named by member_name as a string or if member is
        streamable as a Stream
        """
        raise NotImplementedError


class TarFileWrapper (ArchiveWrapper):

    def open_archive(self, path_to_archive):
        try:
            return tarfile.open(path_to_archive, 'r:gz')
        except tarfile.ReadError:
            try:
                return tarfile.open(path_to_archive, 'r:')
            except:
                not_found("Can't open tar file")

    def get_names(self):
        fixed_names = []
        for tarinfo in self.get_archive().getmembers():
            name = tarinfo.name
            if name.endswith('//'):
                fixed_names.append(name[:-1])
            else:
                if not name.endswith('/') and tarinfo.isdir():
                    name += '/'
                fixed_names.append(name)
        return fixed_names

    def has_member(self, member_name):
        if member_name.endswith('/'):
            try:
                member = self.get_archive().getmember(member_name)
            except (KeyError, NameError):
                member = None
            if member is None:
                try:
                    member = self.get_archive().getmember(member_name[:-1])
                except (KeyError, NameError):
                    member = None
            if member is None:
                try:
                    member = self.get_archive().getmember(member_name + '/')
                except (KeyError, NameError):
                    member = None
            return member and member.isdir()
        else:
            try:
                member = self.get_archive().getmember(member_name)
                return member.isfile()
            except (KeyError, NameError):
                return False

    def get_member_file(self, member_name):
        try:
            return self.get_archive().extractfile(member_name)
        except KeyError:
            return None
        return None

    def get_member_response(self, member_name):
        return FileStream(self.get_member_file(member_name))


class ZipFileWrapper (ArchiveWrapper):

    def open_archive(self, path_to_archive):
        if not zipfile.is_zipfile(path_to_archive):
            not_found('Not a zip file')
        try:
            return zipfile.ZipFile(path_to_archive, 'r')
        except zipfile.error:
            not_found("Can't open zip file")

    def get_names(self):
        names = self.get_archive().namelist()
        directories = set()
        for name in names:
            parts = name.split('/')
            for j in range(len(parts)):
                directories.add('/'.join(parts[:j]) + '/')
        for d in directories:
            if d not in names:
                names.append(d)
        return names

    def has_member(self, member_name):
        try:
            return bool(self.get_archive().getinfo(member_name))
        except KeyError:
            return False

    def get_member_file(self, member_name):
        return BytesIO(self.get_member_response(member_name))

    def get_member_response(self, member_name):
        return self.get_archive().read(member_name)
