"""Pruebas de los modelos en papel de los apuntes: no levantan ninguna red."""
import unittest

import cubic_curve
import phase
import reno_trace


def windows(rows):
    return [row[1] for row in rows]


class RenoTrace(unittest.TestCase):
    def test_worked_example_of_the_notes(self):
        rows = reno_trace.trace(26, 16, dup_rounds=[10], timeout_rounds=[19])
        self.assertEqual(windows(rows)[:10], [1, 2, 4, 8, 16, 17, 18, 19, 20, 21])
        # Tres ACK duplicados en cwnd = 21: ssthresh baja a 10 y se sigue en esa ventana.
        self.assertEqual(rows[10][1:3], (10, 10))
        self.assertEqual(windows(rows)[10:19], [10, 11, 12, 13, 14, 15, 16, 17, 18])
        # Timeout en cwnd = 18: ssthresh baja a 9 y se vuelve a un paquete.
        self.assertEqual(rows[19][1:3], (1, 9))
        self.assertEqual(windows(rows)[19:], [1, 2, 4, 8, 9, 10, 11])

    def test_review_question_of_the_guide(self):
        rows = reno_trace.trace(10, 8, dup_at_cwnd=12)
        self.assertEqual(windows(rows)[:8], [1, 2, 4, 8, 9, 10, 11, 12])
        self.assertEqual(rows[8][1:3], (6, 6))
        self.assertEqual(rows[9][1], 7)

    def test_slow_start_does_not_pass_ssthresh(self):
        self.assertEqual(windows(reno_trace.trace(4, 6))[:4], [1, 2, 4, 6])

    def test_ssthresh_has_a_floor_of_two(self):
        rows = reno_trace.trace(3, 8, timeout_rounds=[1])
        self.assertEqual(rows[1][1:3], (1, 2))


class ChiuJain(unittest.TestCase):
    START = (10.0, 70.0)

    def test_only_aimd_converges_to_fair(self):
        x1, x2 = phase.walk("aimd", *self.START)[-1]
        self.assertAlmostEqual(x1, x2, places=3)
        self.assertGreater(phase.jain(x1, x2), 0.999)

    def test_aiad_keeps_the_gap(self):
        for x1, x2 in phase.walk("aiad", *self.START)[:3]:
            self.assertAlmostEqual(abs(x1 - x2), 60)

    def test_mimd_keeps_the_ratio(self):
        for x1, x2 in phase.walk("mimd", *self.START):
            self.assertAlmostEqual(x2 / x1, 7)

    def test_miad_starves_one_flow(self):
        x1, x2 = phase.walk("miad", *self.START)[-1]
        self.assertEqual(x1, 0)
        self.assertGreater(x2, 0)

    def test_all_stay_around_capacity(self):
        for rule in phase.RULES:
            totals = [x1 + x2 for x1, x2 in phase.walk(rule, *self.START)[100:]]
            self.assertGreater(max(totals), 100, rule)
            self.assertLess(min(totals), 100, rule)


class CubicCurve(unittest.TestCase):
    def test_starts_at_beta_and_returns_at_k(self):
        wmax = 1000
        self.assertAlmostEqual(cubic_curve.window(0, wmax), 0.7 * wmax)
        self.assertAlmostEqual(cubic_curve.window(cubic_curve.k_seconds(wmax), wmax), wmax)

    def test_slows_down_near_wmax(self):
        wmax, k = 1000, cubic_curve.k_seconds(1000)
        early = cubic_curve.window(1, wmax) - cubic_curve.window(0, wmax)
        late = cubic_curve.window(k, wmax) - cubic_curve.window(k - 1, wmax)
        self.assertGreater(early, 5 * late)

    def test_k_does_not_depend_on_rtt_but_reno_does(self):
        self.assertAlmostEqual(cubic_curve.reno_recovery_seconds(1000, 200),
                               5 * cubic_curve.reno_recovery_seconds(1000, 40))
        self.assertLess(cubic_curve.k_seconds(83333), 45)
        self.assertGreater(cubic_curve.reno_recovery_seconds(83333, 100), 3600)

    def test_bdp_of_the_review_question(self):
        self.assertAlmostEqual(cubic_curve.packets_for(100, 50), 416.67, places=1)


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