import zlib  # implied prerequisite
import zipfile
import os
import StringIO
import tempfile

try:
    from test.test_support import TestFailed
except ImportError:
    class TestFailed(Exception):
        pass

from translate.misc import zipfileext


BrokenStringIO = StringIO.StringIO
class FixedStringIO(BrokenStringIO):

    def truncate(self, size=None):
        BrokenStringIO.truncate(self, size)
        self.len = len(self.buf)

StringIO.StringIO = FixedStringIO


def zipTest(srcname, f, compression, srccontents):
    zip = zipfileext.ZipFileExt(f, "w", compression)   # Create the ZIP archive
    zip.write(srcname, "another"+os.extsep+"name")
    zip.write(srcname, srcname)
    zip.close()

    zip = zipfileext.ZipFileExt(f, "r", compression)   # Read the ZIP archive
    readData2 = zip.read(srcname)
    readData1 = zip.read("another"+os.extsep+"name")
    zip.close()

    if readData1 != srccontents or readData2 != srccontents:
        raise TestFailed("Written data doesn't equal read data.")


def deleteTest(srcname, f, compression, srccontents):
    zip = zipfileext.ZipFileExt(f, "w", compression)   # Create the ZIP archive
    othername = "another"+os.extsep+"name"
    finalname = "adifferent"+os.extsep+"name"
    leftname, deletenames = othername, [srcname, finalname]
    zip.write(srcname, srcname)
    zip.write(srcname, othername)
    zip.write(srcname, finalname)
    zip.close()

    zip = zipfileext.ZipFileExt(f, "a", compression)   # Modify the ZIP archive
    for deletename in deletenames:
        zip.delete(deletename)
    zip.close()

    zip = zipfileext.ZipFileExt(f, "r", compression)   # Read the ZIP archive
    testfailed = zip.testzip()
    readData = zip.read(leftname)
    zip.close()

    if testfailed:
        raise TestFailed("zip file didn't pass test")
    if readData != srccontents:
        raise TestFailed("Written data doesn't equal read data.")


class TestZipfile:

    def setup_method(self, method):
        print repr(method), dir(method)
        self.srcname = self.__class__.__name__ + "_" + method.__name__ + os.extsep + "tmp"
        self.zipname = self.__class__.__name__ + "_" + method.__name__ + os.extsep + "zip"
        if os.path.exists(self.srcname):
            os.remove(self.srcname)
        if os.path.exists(self.zipname):
            os.remove(self.zipname)

    def teardown_method(self, method):
        if os.path.exists(self.srcname):           # Remove temporary files
            os.unlink(self.srcname)
        if os.path.exists(self.zipname):
            os.unlink(self.zipname)

    def test_consistent(self):
        fp = open(self.srcname, "wb")               # Make a source file with some lines
        for i in range(0, 1000):
            fp.write("Test of zipfile line %d.\n" % i)
        fp.close()

        fp = open(self.srcname, "rb")
        writtenData = fp.read()
        fp.close()

        for file in (self.zipname, tempfile.TemporaryFile(), StringIO.StringIO()):
            zipTest(self.srcname, file, zipfile.ZIP_STORED, writtenData)

        for file in (self.zipname, tempfile.TemporaryFile(), StringIO.StringIO()):
            zipTest(self.srcname, file, zipfile.ZIP_DEFLATED, writtenData)

    def test_delete(self):
        fp = open(self.srcname, "wb")               # Make a source file with some lines
        for i in range(0, 1000):
            fp.write("Test of zipfile line %d.\n" % i)
        fp.close()

        fp = open(self.srcname, "rb")
        writtenData = fp.read()
        fp.close()

        for file in (self.zipname, tempfile.TemporaryFile(), StringIO.StringIO()):
            deleteTest(self.srcname, file, zipfile.ZIP_STORED, writtenData)

        for file in (self.zipname, tempfile.TemporaryFile(), StringIO.StringIO()):
            deleteTest(self.srcname, file, zipfile.ZIP_DEFLATED, writtenData)

    def test_closes(self):
        # This test checks that the ZipFile constructor closes the file object
        # it opens if there's an error in the file.  If it doesn't, the traceback
        # holds a reference to the ZipFile object and, indirectly, the file object.
        # On Windows, this causes the os.unlink() call to fail because the
        # underlying file is still open.  This is SF bug #412214.
        #
        fp = open(self.srcname, "w")
        fp.write("this is not a legal zip file\n")
        fp.close()
        try:
            zf = zipfileext.ZipFileExt(self.srcname)
        except zipfile.BadZipfile:
            os.unlink(self.srcname)

    def test_403871(self):
        # make sure we don't raise an AttributeError when a partially-constructed
        # ZipFile instance is finalized; this tests for regression on SF tracker
        # bug #403871.
        try:
            zipfileext.ZipFileExt(self.srcname)
        except IOError:
            # The bug we're testing for caused an AttributeError to be raised
            # when a ZipFile instance was created for a file that did not
            # exist; the .fp member was not initialized but was needed by the
            # __del__() method.  Since the AttributeError is in the __del__(),
            # it is ignored, but the user should be sufficiently annoyed by
            # the message on the output that regression will be noticed
            # quickly.
            pass
        else:
            raise TestFailed("expected creation of readable ZipFile without\n"
                             "  a file to raise an IOError.")

    def test_closedthrow(self):
        # Verify that testzip() doesn't swallow inappropriate exceptions.
        data = StringIO.StringIO()
        zipf = zipfileext.ZipFileExt(data, mode="w")
        zipf.writestr("foo.txt", "O, for a Muse of Fire!")
        zipf.close()
        zipf = zipfileext.ZipFileExt(data, mode="r")
        zipf.close()
        try:
            zipf.testzip()
        except RuntimeError:
            # This is correct; calling .read on a closed ZipFile should throw
            # a RuntimeError, and so should calling .testzip.  An earlier
            # version of .testzip would swallow this exception (and any other)
            # and report that the first file in the archive was corrupt.
            pass
        else:
            raise TestFailed("expected calling .testzip on a closed ZipFile"
                             " to raise a RuntimeError")
        del data, zipf
