import random
import unittest

from practice.stack_queue_lab import TwoStackQueue, decode_jobs, rolling_maximum, run_batch


class BatchTests(unittest.TestCase):
    def test_worked_example(self):
        self.assertEqual(run_batch("2[A:3 B:1] C:4", 3), {
            "completed": [("A", 3), ("B", 4), ("A", 7), ("B", 8), ("C", 12)],
            "rolling_max": [3, 3, 4]})

    def test_nested(self):
        self.assertEqual(decode_jobs("2[A:2 2[B:1]]"), [("A", 2), ("B", 1), ("B", 1)] * 2)

    def test_empty(self):
        self.assertEqual(run_batch("  "), {"completed": [], "rolling_max": []})

    def test_invalid_grammar(self):
        for program in ["0[A:1]", "A:0", "2[]", "A:", "2[A:1", "A:1]", "[A:1]", "A:1!", "A:1, B:2"]:
            with self.subTest(program=program), self.assertRaises(ValueError):
                decode_jobs(program)

    def test_expansion_limit(self):
        self.assertEqual(len(decode_jobs("2[A:1]", 2)), 2)
        with self.assertRaises(ValueError):
            decode_jobs("2[A:1]", 1)
        with self.assertRaises(ValueError):
            decode_jobs("A:1 B:1", 1)

    def test_depth_limit(self):
        with self.assertRaises(ValueError):
            decode_jobs("1[" * 101 + "A:1" + "]" * 101)

    def test_fifo_after_interleaving(self):
        queue = TwoStackQueue()
        queue.push(1); queue.push(2)
        self.assertEqual(queue.pop(), 1)
        queue.push(3)
        self.assertEqual([queue.pop(), queue.pop()], [2, 3])
        self.assertFalse(queue)
        with self.assertRaises(IndexError):
            queue.pop()

    def test_window_edges(self):
        self.assertEqual(rolling_maximum([2, 2, 1], 2), [2, 2])
        self.assertEqual(rolling_maximum([3, 1], 1), [3, 1])
        self.assertEqual(rolling_maximum([3, 1], 4), [])
        with self.assertRaises(ValueError):
            run_batch("A:1", 0)

    def test_multi_digit_and_spacing(self):
        self.assertEqual(run_batch(" Work : 12 Next : 2 ", 2), {
            "completed": [("Work", 12), ("Next", 14)], "rolling_max": [12]})

    def test_random_windows_against_brute_force(self):
        rng = random.Random(20)
        for _ in range(150):
            values = [rng.randrange(1, 10) for _ in range(rng.randrange(12))]
            size = rng.randrange(1, 8)
            expected = [max(values[start:start + size]) for start in range(len(values) - size + 1)]
            self.assertEqual(rolling_maximum(values, size), expected)


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