"""Corre los ejemplos de la semana y comprueba lo que se puede contar: nunca tiempos."""
import filecmp
import math
import pathlib
import subprocess
import unittest

HERE = pathlib.Path(__file__).parent


def run(*command):
    return subprocess.run(
        list(command), cwd=HERE, text=True, encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=300,
    )


class Examples(unittest.TestCase):
    def test_structure_tests_pass(self):
        done = run("build/test_structures")
        self.assertEqual(done.returncode, 0, done.stdout)

    def test_programs_exit_cleanly(self):
        for name in ["heap_demo", "where_is_max", "heapsort_demo", "pq_demo", "huffman_demo"]:
            with self.subTest(program=name):
                done = run(f"build/{name}")
                self.assertEqual(done.returncode, 0, done.stdout)

    def test_questions_one_and_two(self):
        lines = run("build/heap_demo").stdout.splitlines()
        self.assertEqual(lines[4], "push 4: [1 3 8 5 4]")
        self.assertEqual(lines[5], "pop  1: [3 4 8 5]")

    def test_question_four(self):
        lines = run("build/huffman_demo").stdout.splitlines()
        self.assertEqual(lines[3:8], [
            "a (5): 1", "b (2): 00", "c (1): 010", "d (1): 011",
            "bits: 15, suma de los pesos unidos: 15, con codigo fijo de 2 bits: 18",
        ])

    def test_greatest_is_always_a_leaf_with_distinct_keys(self):
        lines = run("build/where_is_max").stdout.splitlines()
        self.assertEqual(lines[0], "permutaciones: 5040, son heaps: 80")
        rows = [line.split() for line in lines[2:9]]
        self.assertEqual([int(row[1]) for row in rows], [80, 0, 0, 0, 0, 0, 0])
        self.assertEqual([int(row[2]) for row in rows[:3]], [0, 0, 0])
        self.assertEqual(sum(int(row[2]) for row in rows), 80)

    def test_build_counts(self):
        lines = run("build/build_counts").stdout.splitlines()
        for row in lines[1:13]:
            fields = row.split()
            n, order, bottom_up, pushes = int(fields[0]), fields[1], int(fields[2]), int(fields[4])
            self.assertLessEqual(bottom_up, 2 * n)
            if order == "asc":
                self.assertEqual(pushes, n - 1)
            if order == "azar":
                self.assertLess(pushes, 3 * n)
            if order == "desc" and n > 1000:
                self.assertGreater(pushes, 0.7 * n * math.log2(n))
        self.assertEqual(lines[-1], "suma: 1013, niveles que bajaron de verdad: 1013")

    def test_heapsort_counts(self):
        lines = run("build/heapsort_counts").stdout.splitlines()
        for row in lines[1:]:
            fields = row.split()
            n, kind, comparisons = int(fields[0]), fields[1], int(fields[2])
            self.assertLessEqual(comparisons, 2 * n * math.log2(n))
            if kind == "iguales":
                self.assertLessEqual(comparisons, 3 * n)

    def test_heapsort_is_not_stable(self):
        lines = run("build/heapsort_demo").stdout.splitlines()
        self.assertEqual(lines[-1].strip(), "salida:  1c 2b 2a".strip())

    def test_cache_model(self):
        rows = [line.split() for line in run("build/heapsort_cache").stdout.splitlines()[1:]]
        by_size = {(int(row[0]), row[1]): int(row[3]) for row in rows}
        self.assertGreater(by_size[(4000000, "heapsort")], 2 * by_size[(4000000, "mergesort")])

    def test_library_bounds(self):
        lines = run("build/pq_demo").stdout.splitlines()
        self.assertEqual(lines[0].split()[-5:], ["8", "5", "4", "3", "1"])
        self.assertEqual(lines[1].split()[-5:], ["1", "3", "4", "5", "8"])
        self.assertLessEqual(int(lines[2].split()[1]), 300000)
        self.assertLessEqual(int(lines[3].split()[4].rstrip(",")), 32)
        self.assertLessEqual(int(lines[4].split()[4].rstrip(",")), 16)

    def test_top_k_counts(self):
        rows = [line.split() for line in run("build/top_k_bench").stdout.splitlines()[2:]]
        self.assertEqual(len(rows), 4)
        n = 2000000
        for row in rows:
            k, heap_comparisons = int(row[0]), int(row[1])
            self.assertLessEqual(heap_comparisons, n * (1 + 3 * math.log2(k)))
        self.assertLess(int(rows[0][1]), int(rows[0][2]) / 10)


@unittest.skipUnless((HERE / "solution").is_dir(), "la solución no se reparte")
class Solution(unittest.TestCase):
    def test_given_programs_match(self):
        for name in ["compress.cpp", "decompress.cpp"]:
            with self.subTest(file=name):
                self.assertTrue(filecmp.cmp(HERE / "lab" / name, HERE / "solution" / name, shallow=False))

    def test_interface_of_week_15(self):
        """El comentario de semana 15 y las firmas no se pueden mover sin avisar."""
        for folder in ["lab", "solution"]:
            text = (HERE / folder / "min_heap.h").read_text(encoding="utf-8")
            for signature in [
                "template <typename T, typename Less = std::less<T>>",
                "MinHeap() = default;",
                "explicit MinHeap(Less less) : less_(std::move(less)) {}",
                "explicit MinHeap(std::vector<T> items, Less less = Less())",
                "void push(T value) {",
                "void pop() {",
                "const T& top() const {",
                "bool empty() const { return items_.empty(); }",
                "std::size_t size() const { return items_.size(); }",
                "void check_invariants() const",
            ]:
                with self.subTest(folder=folder, signature=signature):
                    self.assertIn(signature, text)


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