# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import unittest
from collections import OrderedDict
from io import StringIO

import mozunit

import mozpack.path as mozpath
from mozpack.files import FileFinder
from mozpack.mozjar import (
    Deflater,
    JarLog,
    JarReader,
    JarReaderError,
    JarStruct,
    JarWriter,
    JarWriterError,
)
from mozpack.test.test_files import MockDest

test_data_path = mozpath.abspath(mozpath.dirname(__file__))
test_data_path = mozpath.join(test_data_path, "data")


class TestJarStruct(unittest.TestCase):
    class Foo(JarStruct):
        MAGIC = 0x01020304
        STRUCT = OrderedDict(
            [
                ("foo", "uint32"),
                ("bar", "uint16"),
                ("qux", "uint16"),
                ("length", "uint16"),
                ("length2", "uint16"),
                ("string", "length"),
                ("string2", "length2"),
            ]
        )

    def test_jar_struct(self):
        foo = TestJarStruct.Foo()
        self.assertEqual(foo.signature, TestJarStruct.Foo.MAGIC)
        self.assertEqual(foo["foo"], 0)
        self.assertEqual(foo["bar"], 0)
        self.assertEqual(foo["qux"], 0)
        self.assertFalse("length" in foo)
        self.assertFalse("length2" in foo)
        self.assertEqual(foo["string"], "")
        self.assertEqual(foo["string2"], "")

        self.assertEqual(foo.size, 16)

        foo["foo"] = 0x42434445
        foo["bar"] = 0xABCD
        foo["qux"] = 0xEF01
        foo["string"] = "abcde"
        foo["string2"] = "Arbitrarily long string"

        serialized = (
            b"\x04\x03\x02\x01\x45\x44\x43\x42\xcd\xab\x01\xef"
            + b"\x05\x00\x17\x00abcdeArbitrarily long string"
        )
        self.assertEqual(foo.size, len(serialized))
        foo_serialized = foo.serialize()
        self.assertEqual(foo_serialized, serialized)

    def do_test_read_jar_struct(self, data):
        self.assertRaises(JarReaderError, TestJarStruct.Foo, data)
        self.assertRaises(JarReaderError, TestJarStruct.Foo, data[2:])

        foo = TestJarStruct.Foo(data[1:])
        self.assertEqual(foo["foo"], 0x45444342)
        self.assertEqual(foo["bar"], 0xCDAB)
        self.assertEqual(foo["qux"], 0x01EF)
        self.assertFalse("length" in foo)
        self.assertFalse("length2" in foo)
        self.assertEqual(foo["string"], b"012345")
        self.assertEqual(foo["string2"], b"67")

    def test_read_jar_struct(self):
        data = (
            b"\x00\x04\x03\x02\x01\x42\x43\x44\x45\xab\xcd\xef"
            + b"\x01\x06\x00\x02\x0001234567890"
        )
        self.do_test_read_jar_struct(data)

    def test_read_jar_struct_memoryview(self):
        data = (
            b"\x00\x04\x03\x02\x01\x42\x43\x44\x45\xab\xcd\xef"
            + b"\x01\x06\x00\x02\x0001234567890"
        )
        self.do_test_read_jar_struct(memoryview(data))


class TestDeflater(unittest.TestCase):
    def wrap(self, data):
        return data

    def test_deflater_no_compress(self):
        deflater = Deflater(False)
        deflater.write(self.wrap(b"abc"))
        self.assertFalse(deflater.compressed)
        self.assertEqual(deflater.uncompressed_size, 3)
        self.assertEqual(deflater.compressed_size, deflater.uncompressed_size)
        self.assertEqual(deflater.compressed_data, b"abc")
        self.assertEqual(deflater.crc32, 0x352441C2)

    def test_deflater_compress_no_gain(self):
        deflater = Deflater(True)
        deflater.write(self.wrap(b"abc"))
        self.assertFalse(deflater.compressed)
        self.assertEqual(deflater.uncompressed_size, 3)
        self.assertEqual(deflater.compressed_size, deflater.uncompressed_size)
        self.assertEqual(deflater.compressed_data, b"abc")
        self.assertEqual(deflater.crc32, 0x352441C2)

    def test_deflater_compress(self):
        deflater = Deflater(True)
        deflater.write(self.wrap(b"aaaaaaaaaaaaanopqrstuvwxyz"))
        self.assertTrue(deflater.compressed)
        self.assertEqual(deflater.uncompressed_size, 26)
        self.assertNotEqual(deflater.compressed_size, deflater.uncompressed_size)
        self.assertEqual(deflater.crc32, 0xD46B97ED)
        # The CRC is the same as when not compressed
        deflater = Deflater(False)
        self.assertFalse(deflater.compressed)
        deflater.write(self.wrap(b"aaaaaaaaaaaaanopqrstuvwxyz"))
        self.assertEqual(deflater.crc32, 0xD46B97ED)

    def test_deflater_empty(self):
        deflater = Deflater(False)
        self.assertFalse(deflater.compressed)
        self.assertEqual(deflater.uncompressed_size, 0)
        self.assertEqual(deflater.compressed_size, deflater.uncompressed_size)
        self.assertEqual(deflater.compressed_data, b"")
        self.assertEqual(deflater.crc32, 0)


class TestDeflaterMemoryView(TestDeflater):
    def wrap(self, data):
        return memoryview(data)


class TestJar(unittest.TestCase):
    def test_jar(self):
        s = MockDest()
        with JarWriter(fileobj=s) as jar:
            jar.add("foo", b"foo")
            self.assertRaises(JarWriterError, jar.add, "foo", b"bar")
            jar.add("bar", b"aaaaaaaaaaaaanopqrstuvwxyz")
            jar.add("baz/qux", b"aaaaaaaaaaaaanopqrstuvwxyz", False)
            jar.add("baz\\backslash", b"aaaaaaaaaaaaaaa")

        files = [j for j in JarReader(fileobj=s)]

        self.assertEqual(files[0].filename, "foo")
        self.assertFalse(files[0].compressed)
        self.assertEqual(files[0].read(), b"foo")

        self.assertEqual(files[1].filename, "bar")
        self.assertTrue(files[1].compressed)
        self.assertEqual(files[1].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

        self.assertEqual(files[2].filename, "baz/qux")
        self.assertFalse(files[2].compressed)
        self.assertEqual(files[2].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

        if os.sep == "\\":
            self.assertEqual(
                files[3].filename,
                "baz/backslash",
                "backslashes in filenames on Windows should get normalized",
            )
        else:
            self.assertEqual(
                files[3].filename,
                "baz\\backslash",
                "backslashes in filenames on POSIX platform are untouched",
            )

        s = MockDest()
        with JarWriter(fileobj=s, compress=False) as jar:
            jar.add("bar", b"aaaaaaaaaaaaanopqrstuvwxyz")
            jar.add("foo", b"foo")
            jar.add("baz/qux", b"aaaaaaaaaaaaanopqrstuvwxyz", True)

        jar = JarReader(fileobj=s)
        files = [j for j in jar]

        self.assertEqual(files[0].filename, "bar")
        self.assertFalse(files[0].compressed)
        self.assertEqual(files[0].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

        self.assertEqual(files[1].filename, "foo")
        self.assertFalse(files[1].compressed)
        self.assertEqual(files[1].read(), b"foo")

        self.assertEqual(files[2].filename, "baz/qux")
        self.assertTrue(files[2].compressed)
        self.assertEqual(files[2].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

        self.assertTrue("bar" in jar)
        self.assertTrue("foo" in jar)
        self.assertFalse("baz" in jar)
        self.assertTrue("baz/qux" in jar)
        self.assertTrue(jar["bar"], files[1])
        self.assertTrue(jar["foo"], files[0])
        self.assertTrue(jar["baz/qux"], files[2])

        s.seek(0)
        jar = JarReader(fileobj=s)
        self.assertTrue("bar" in jar)
        self.assertTrue("foo" in jar)
        self.assertFalse("baz" in jar)
        self.assertTrue("baz/qux" in jar)

        files[0].seek(0)
        self.assertEqual(jar["bar"].filename, files[0].filename)
        self.assertEqual(jar["bar"].compressed, files[0].compressed)
        self.assertEqual(jar["bar"].read(), files[0].read())

        files[1].seek(0)
        self.assertEqual(jar["foo"].filename, files[1].filename)
        self.assertEqual(jar["foo"].compressed, files[1].compressed)
        self.assertEqual(jar["foo"].read(), files[1].read())

        files[2].seek(0)
        self.assertEqual(jar["baz/qux"].filename, files[2].filename)
        self.assertEqual(jar["baz/qux"].compressed, files[2].compressed)
        self.assertEqual(jar["baz/qux"].read(), files[2].read())

    def test_rejar(self):
        s = MockDest()
        with JarWriter(fileobj=s) as jar:
            jar.add("foo", b"foo")
            jar.add("bar", b"aaaaaaaaaaaaanopqrstuvwxyz")
            jar.add("baz/qux", b"aaaaaaaaaaaaanopqrstuvwxyz", False)

        new = MockDest()
        with JarWriter(fileobj=new) as jar:
            for j in JarReader(fileobj=s):
                jar.add(j.filename, j)

        jar = JarReader(fileobj=new)
        files = [j for j in jar]

        self.assertEqual(files[0].filename, "foo")
        self.assertFalse(files[0].compressed)
        self.assertEqual(files[0].read(), b"foo")

        self.assertEqual(files[1].filename, "bar")
        self.assertTrue(files[1].compressed)
        self.assertEqual(files[1].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

        self.assertEqual(files[2].filename, "baz/qux")
        self.assertTrue(files[2].compressed)
        self.assertEqual(files[2].read(), b"aaaaaaaaaaaaanopqrstuvwxyz")

    def test_add_from_finder(self):
        s = MockDest()
        with JarWriter(fileobj=s) as jar:
            finder = FileFinder(test_data_path)
            for p, f in finder.find("test_data"):
                jar.add("test_data", f)

        jar = JarReader(fileobj=s)
        files = [j for j in jar]

        self.assertEqual(files[0].filename, "test_data")
        self.assertFalse(files[0].compressed)
        self.assertEqual(files[0].read(), b"test_data")


class TestPreload(unittest.TestCase):
    def test_preload(self):
        s = MockDest()
        with JarWriter(fileobj=s) as jar:
            jar.add("foo", b"foo")
            jar.add("bar", b"abcdefghijklmnopqrstuvwxyz")
            jar.add("baz/qux", b"aaaaaaaaaaaaanopqrstuvwxyz")

        jar = JarReader(fileobj=s)
        self.assertEqual(jar.last_preloaded, None)

        with JarWriter(fileobj=s) as jar:
            jar.add("foo", b"foo")
            jar.add("bar", b"abcdefghijklmnopqrstuvwxyz")
            jar.add("baz/qux", b"aaaaaaaaaaaaanopqrstuvwxyz")
            jar.preload(["baz/qux", "bar"])

        jar = JarReader(fileobj=s)
        self.assertEqual(jar.last_preloaded, "bar")
        files = [j for j in jar]

        self.assertEqual(files[0].filename, "baz/qux")
        self.assertEqual(files[1].filename, "bar")
        self.assertEqual(files[2].filename, "foo")


class TestJarLog(unittest.TestCase):
    def test_jarlog(self):
        s = StringIO(
            "\n".join(
                [
                    "bar/baz.jar first",
                    "bar/baz.jar second",
                    "bar/baz.jar third",
                    "bar/baz.jar second",
                    "bar/baz.jar second",
                    "omni.ja stuff",
                    "bar/baz.jar first",
                    "omni.ja other/stuff",
                    "omni.ja stuff",
                    "bar/baz.jar third",
                ]
            )
        )
        log = JarLog(fileobj=s)
        self.assertEqual(
            set(log.keys()),
            set(
                [
                    "bar/baz.jar",
                    "omni.ja",
                ]
            ),
        )
        self.assertEqual(
            log["bar/baz.jar"],
            [
                "first",
                "second",
                "third",
            ],
        )
        self.assertEqual(
            log["omni.ja"],
            [
                "stuff",
                "other/stuff",
            ],
        )


if __name__ == "__main__":
    mozunit.main()
