"""Comprueba los programas de la clase y el módulo de imágenes."""
import pathlib
import subprocess
import tempfile
import unittest

HERE = pathlib.Path(__file__).parent
BUILD = HERE / "build"
FLAGS = ["-Wall", "-Wextra", "-std=c17", "-g", "-fsanitize=address,undefined"]
WITH_MODULE = {"image_demo", "show_sun"}
PLAIN = ["frames", "blocks", "static_counter", "global_constant", "by_value",
         "array_param", "struct_copy"]


def run(program):
    done = subprocess.run(
        [str(BUILD / program)], cwd=HERE, text=True, encoding="utf-8",
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=20,
    )
    return done.stdout, done.returncode


def run_driver(body, files=None):
    """Compila un main propio con el módulo y lo corre en una carpeta temporal."""
    with tempfile.TemporaryDirectory() as name:
        folder = pathlib.Path(name)
        for file_name, content in (files or {}).items():
            (folder / file_name).write_text(content, encoding="utf-8")
        source = folder / "driver.c"
        source.write_text(
            '#include <stdio.h>\n#include "image.h"\nint main(void) {\n' + body + "\nreturn 0;\n}\n",
            encoding="utf-8",
        )
        binary = folder / "driver"
        subprocess.run(
            ["gcc", *FLAGS, "-Werror", f"-I{HERE / 'lab'}", str(source),
             str(HERE / "lab" / "image.c"), "-o", str(binary)],
            check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
        )
        done = subprocess.run(
            [str(binary)], cwd=folder, text=True, encoding="utf-8",
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=20,
        )
        return done.stdout, done.returncode


def load_status(content):
    body = 'struct image img = image_load("in.pgm"); printf("%d\\n", image_is_valid(img));'
    out, code = run_driver(body, {"in.pgm": content})
    return out.strip(), code


class Programs(unittest.TestCase):
    def test_every_program_compiles_without_warnings(self):
        for name in PLAIN:
            with self.subTest(program=name):
                subprocess.run(
                    ["gcc", *FLAGS, "-Werror", "-c", str(HERE / f"{name}.c"), "-o", "/dev/null"],
                    check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                )
        for name in WITH_MODULE:
            with self.subTest(program=name):
                subprocess.run(
                    ["gcc", *FLAGS, "-Werror", "-c", str(HERE / f"{name}.c"), "-o", "/dev/null"],
                    cwd=HERE, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                )

    def test_frames_keep_callers_variables(self):
        self.assertEqual(run("frames")[0].strip(), "a = 2, b = 5, c = 17")

    def test_static_survives_and_local_does_not(self):
        lines = run("static_counter")[0].splitlines()
        self.assertEqual(lines, ["local: 1, static: 1", "local: 1, static: 2", "local: 1, static: 3"])

    def test_struct_is_copied_but_array_is_not(self):
        out = run("struct_copy")[0]
        self.assertIn("en main: cells[0] = 1", out)
        self.assertIn("a.cells[1] = 77", out)

    def test_lab_frame_programs_run(self):
        for name in ("frames_calls", "frames_static", "frames_array"):
            with self.subTest(program=name), tempfile.TemporaryDirectory() as folder:
                binary = pathlib.Path(folder) / name
                subprocess.run(
                    ["gcc", *FLAGS, "-Werror", str(HERE / "lab" / f"{name}.c"), "-o", str(binary)],
                    check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                )
                done = subprocess.run([str(binary)], stdout=subprocess.PIPE, timeout=10)
                self.assertEqual(done.returncode, 0)


class ImageModule(unittest.TestCase):
    def test_reads_comments_anywhere_between_numbers(self):
        out, code = run_driver(
            'image_print(image_load("in.pgm"));',
            {"in.pgm": "P2\n# uno\n2 # dos\n1\n# tres\n9\n3#cuatro\n4\n"},
        )
        self.assertEqual(code, 0, out)
        self.assertEqual(out.split(), ["2", "x", "1,", "max", "9", "3", "4"])

    def test_good_file_is_valid(self):
        self.assertEqual(load_status("P2\n1 1\n255\n7\n"), ("1", 0))

    def test_largest_size_is_valid(self):
        pixels = " ".join(["1"] * (64 * 64))
        self.assertEqual(load_status(f"P2\n64 64\n255\n{pixels}\n"), ("1", 0))

    def test_bad_files_give_an_invalid_image(self):
        bad = {
            "formato binario": "P5\n1 1\n255\n7\n",
            "vacío": "",
            "ancho cero": "P2\n0 1\n255\n",
            "demasiado ancho": "P2\n65 1\n255\n" + "0 " * 65,
            "demasiado alto": "P2\n1 65\n255\n" + "0 " * 65,
            "max_value cero": "P2\n1 1\n0\n0\n",
            "max_value grande": "P2\n1 1\n256\n0\n",
            "faltan píxeles": "P2\n2 2\n255\n1 2 3\n",
            "píxel mayor que max": "P2\n1 1\n100\n101\n",
            "píxel negativo": "P2\n1 1\n255\n-1\n",
            "letra": "P2\n1 1\n255\nabc\n",
            "número enorme": "P2\n99999999999999999999 1\n255\n0\n",
        }
        for why, content in bad.items():
            with self.subTest(why=why):
                self.assertEqual(load_status(content), ("0", 0))

    def test_missing_file_gives_an_invalid_image(self):
        out, code = run_driver('printf("%d\\n", image_is_valid(image_load("nada.pgm")));')
        self.assertEqual((out.strip(), code), ("0", 0))

    def test_save_and_load_give_the_same_image(self):
        body = """
        int values[64 * 3];
        for (int i = 0; i < 64 * 3; i++) { values[i] = i % 256; }
        struct image img = image_from_values(64, 3, 255, values);
        printf("%d\\n", image_save(img, "out.pgm"));
        printf("%d\\n", image_equal(image_load("out.pgm"), img));
        """
        out, code = run_driver(body)
        self.assertEqual((out.split(), code), (["1", "1"], 0))

    def test_saved_lines_are_at_most_70_characters(self):
        body = """
        struct image img = image_new(64, 2, 255, 255);
        printf("%d\\n", image_save(img, "out.pgm"));
        FILE *file = fopen("out.pgm", "r");
        if (file == NULL) { return 1; }
        char line[200];
        int longest = 0;
        while (fgets(line, sizeof(line), file) != NULL) {
            int length = 0;
            while (line[length] != '\\0') { length++; }
            if (length > longest) { longest = length; }
        }
        fclose(file);
        printf("%d\\n", longest <= 71);
        """
        out, code = run_driver(body)
        self.assertEqual((out.split(), code), (["1", "1"], 0))

    def test_new_checks_its_arguments(self):
        body = """
        printf("%d", image_is_valid(image_new(1, 1, 255, 0)));
        printf("%d", image_is_valid(image_new(0, 1, 255, 0)));
        printf("%d", image_is_valid(image_new(1, 65, 255, 0)));
        printf("%d", image_is_valid(image_new(1, 1, 256, 0)));
        printf("%d", image_is_valid(image_new(1, 1, 255, 256)));
        printf("%d", image_is_valid(image_new(1, 1, 255, -1)));
        """
        out, code = run_driver(body)
        self.assertEqual((out.strip(), code), ("100000", 0))

    def test_equal_ignores_unused_cells(self):
        body = """
        struct image a = image_new(2, 2, 255, 7);
        struct image b = a;
        b.pixels[5][5] = 1;
        printf("%d", image_equal(a, b));
        b.pixels[1][1] = 1;
        printf("%d", image_equal(a, b));
        """
        out, code = run_driver(body)
        self.assertEqual((out.strip(), code), ("10", 0))


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