import io
import os

import pytest
from PIL import Image

import yoga.image
from yoga.image.encoders.jpeg import is_jpeg
from yoga.image.encoders.png import is_png
from yoga.image.encoders.webp import is_lossy_webp
from yoga.image.encoders.webp_lossless import is_lossless_webp


class Test_optimize(object):
    @pytest.mark.parametrize(
        "input_",
        [
            "test/images/alpha.png",
            open("test/images/alpha.png", "rb"),
            io.BytesIO(open("test/images/alpha.png", "rb").read()),
        ],
    )
    def test_input_file(self, input_):
        output = io.BytesIO()
        yoga.image.optimize(input_, output)
        output.seek(0)
        assert is_png(output.read())

    def test_output_path(self, tmpdir):
        output_path = os.path.join(str(tmpdir), "output1.png")
        yoga.image.optimize("test/images/alpha.png", output_path)
        output = open(output_path, "rb")
        assert is_png(output.read())

    def test_output_file(self, tmpdir):
        output_path = os.path.join(str(tmpdir), "output2.png")
        output = open(output_path, "wb")
        yoga.image.optimize("test/images/alpha.png", output)
        output.close()
        output = open(output_path, "rb")
        assert is_png(output.read())

    def test_output_bytesio(self):
        output = io.BytesIO()
        yoga.image.optimize("test/images/alpha.png", output)
        output.seek(0)
        assert is_png(output.read())

    @pytest.mark.parametrize(
        "image_path,format_checker",
        [
            ("test/images/image1.jpg", is_jpeg),
            ("test/images/unused-alpha.png", is_png),
            ("test/images/alpha.lossy.webp", is_lossy_webp),
            ("test/images/alpha.lossless.webp", is_lossless_webp),
        ],
    )
    def test_option_output_format_default(self, image_path, format_checker):
        output = io.BytesIO()
        yoga.image.optimize(image_path, output)
        output.seek(0)
        assert format_checker(output.read())

    @pytest.mark.parametrize(
        "image_path,format_,format_checker",
        [
            # fmt: off
            ("test/images/image1.jpg",          "orig",  is_jpeg),
            ("test/images/unused-alpha.png",    "orig",  is_png),
            ("test/images/alpha.png",           "auto",  is_png),
            ("test/images/unused-alpha.png",    "auto",  is_jpeg),
            ("test/images/image1.jpg",          "auto",  is_jpeg),
            ("test/images/image1.jpg",          "jpeg",  is_jpeg),
            ("test/images/unused-alpha.png",    "jpeg",  is_jpeg),
            ("test/images/image1.jpg",          "png",   is_png),
            ("test/images/unused-alpha.png",    "png",   is_png),
            ("test/images/alpha.lossy.webp",    "webp",  is_lossy_webp),
            ("test/images/alpha.lossy.webp",    "orig",  is_lossy_webp),
            ("test/images/alpha.lossless.webp", "webpl", is_lossless_webp),
            ("test/images/alpha.lossless.webp", "orig",  is_lossless_webp),
            # fmt: on
        ],
    )
    def test_option_output_format(self, image_path, format_, format_checker):
        output = io.BytesIO()
        yoga.image.optimize(image_path, output, {"output_format": format_})
        output.seek(0)
        assert format_checker(output.read())

    def test_option_output_format_orig_with_unsuported_output_format(self):
        output = io.BytesIO()
        with pytest.raises(ValueError):
            yoga.image.optimize(
                "test/images/image.gif", output, {"output_format": "orig"}
            )

    @pytest.mark.parametrize(
        "image_path,options,output_image_size",
        [
            # fmt: off
            # IMAGE                       OPTIONS               OUT IMG SIZE
            # orig
            ["test/images/image1.jpg",    {"resize": "orig"},   (256, 256)],
            # size < image
            ["test/images/image1.jpg",    {"resize": 128},      (128, 128)],
            ["test/images/image1.jpg",    {"resize": 96},       (96, 96)],
            # size > image
            ["test/images/image1.jpg",    {"resize": 512},      (256, 256)],
            # width, height
            ["test/images/image1.jpg",    {"resize": "96x200"}, (96, 96)],
            ["test/images/landscape.png", {"resize": [64, 64]}, (64, 32)],
            ["test/images/landscape.png", {"resize": [96, 64]}, (96, 48)],
            ["test/images/landscape.png", {"resize": [96, 32]}, (64, 32)],
            ["test/images/portrait.png",  {"resize": [64, 64]}, (32, 64)],
            ["test/images/portrait.png",  {"resize": [64, 96]}, (48, 96)],
            ["test/images/portrait.png",  {"resize": [32, 96]}, (32, 64)],
            # fmt: on
        ],
    )
    def test_option_resize(self, image_path, options, output_image_size):
        output = io.BytesIO()
        yoga.image.optimize(image_path, output, options)
        output.seek(0)
        image = Image.open(output)
        assert image.width == output_image_size[0]
        assert image.height == output_image_size[1]

    def test_jpeg_quality(self):
        output1 = io.BytesIO()
        yoga.image.optimize(
            "test/images/image1.jpg", output1, {"jpeg_quality": 1.00}
        )
        output1.seek(0)

        output2 = io.BytesIO()
        yoga.image.optimize(
            "test/images/image1.jpg", output2, {"jpeg_quality": 0.50}
        )
        output2.seek(0)

        assert len(output2.read()) < len(output1.read())

    def test_webp_quality(self):
        output1 = io.BytesIO()
        yoga.image.optimize(
            "test/images/alpha.lossy.webp", output1, {"webp_quality": 1.00}
        )
        output1.seek(0)

        output2 = io.BytesIO()
        yoga.image.optimize(
            "test/images/alpha.lossy.webp", output2, {"webp_quality": 0.50}
        )
        output2.seek(0)

        assert len(output2.read()) < len(output1.read())

    @pytest.mark.parametrize(
        "image_path,threshold,format_checker",
        [
            # fmt: off
            ("test/images/alpha.png",      254,  is_png),
            ("test/images/alpha.png",        0,  is_jpeg),
            ("test/images/threshold.png",  254,  is_png),
            ("test/images/threshold.png",  255,  is_png),
            ("test/images/threshold.png",    0,  is_jpeg),
            ("test/images/threshold.png",  230,  is_png),
            ("test/images/threshold.png",  229,  is_jpeg),
            # fmt: on
        ],
    )
    def test_opacity_threshold(self, image_path, threshold, format_checker):
        output = io.BytesIO()
        yoga.image.optimize(
            image_path,
            output,
            {
                "output_format": "auto",
                "opacity_threshold": threshold,
            },
        )
        output.seek(0)
        assert format_checker(output.read())

    @pytest.mark.parametrize(
        "quantization_max_colors,expected_colors_count",
        [
            (256, 22),
            (128, 22),
            (22, 22),
            (20, 20),
            (8, 8),
            (1, 1),
        ],
    )
    def test_quantization_colors(
        self, quantization_max_colors, expected_colors_count
    ):
        output = io.BytesIO()
        yoga.image.optimize(
            "test/images/quantization-colors22.png",
            output,
            {
                "enable_quantization": True,
                "quantization_max_colors": quantization_max_colors,
            },
        )
        output.seek(0)
        image = Image.open(output)
        assert len(image.getcolors()) == expected_colors_count

    def test_quantization_dithering_disabled(self):
        output = io.BytesIO()
        yoga.image.optimize(
            "test/images/quantization-dithering.png",
            output,
            {
                "enable_quantization": True,
                "quantization_max_colors": 3,
                "quantization_dithering_level": 0.0,
            },
        )
        output.seek(0)
        image = Image.open(output)
        colors = []
        for z in range(5):
            colors.append({})
            for y in range(8):
                for x in range(8):
                    colors[z][image.getpixel((z * 8 + x, y))] = True
        assert max([len(c) for c in colors]) == 1

    def test_quantization_dithering_enabled(self):
        output = io.BytesIO()
        yoga.image.optimize(
            "test/images/quantization-dithering.png",
            output,
            {
                "enable_quantization": True,
                "quantization_max_colors": 3,
                "quantization_dithering_level": 1.0,
            },
        )
        output.seek(0)
        image = Image.open(output)
        colors = []
        for z in range(5):
            colors.append({})
            for y in range(8):
                for x in range(8):
                    colors[z][image.getpixel((z * 8 + x, y))] = True
        assert max([len(c) for c in colors]) > 1

    # TODO test wrong image / fuzzy inputs
