#!/usr/bin/env python3

from __future__ import division
from __future__ import print_function

import unittest
import time

from caching import caching


@caching
def simple():
    """Wastes 0.01 seconds doing nothing"""
    time.sleep(0.1)
    return 0


@caching
def fibo(index):
    """Fibonacci suite computer"""
    if index == 0:
        return 0
    if index == 1:
        return 1
    else:
        return fibo(index - 2) + fibo(index - 1)


@caching
def identity(item):
    """Simple Identity function."""
    return item


class CachingTests(unittest.TestCase):
    """
    Tests for Caching.
    """

    def test_function_definition_is_still_working(self):
        self.assertEqual(simple(), 0)

    def test_function_with_args_still_working(self):
        self.assertEqual(fibo(0), 0)
        self.assertEqual(fibo(1), 1)
        self.assertEqual(fibo(2), 1)
        self.assertEqual(fibo(9), 34)

    def test_mixed_function_calls(self):
        self.assertEqual(identity(4), 4)
        self.assertEqual(identity(5), 5)
        self.assertEqual(fibo(4), 3)
        self.assertEqual(fibo(5), 5)

    def test_exceptions(self):
        with self.assertRaises(TypeError):
            fibo("a")

    def test_huge_recursion(self):
        # Just a little one to not break computation
        # time if cache is not in place
        start_time = time.time()
        fibo(28)
        duration = time.time() - start_time
        self.assertLessEqual(duration, 0.2)

        # here come the huge one!
        start_time = time.time()
        fibo(280)
        duration = time.time() - start_time
        self.assertLessEqual(duration, 0.2)

    # Comment the following line to force the check the bonus code
    @unittest.expectedFailure
    def test_bonus1(self):
        self.assertEqual(fibo.computed_count, 0)
        self.assertEqual(fibo.served_count, 0)
        self.assertEqual(fibo.missed_count, 0)
        self.assertEqual(fibo(0), 0)
        self.assertEqual(fibo.computed_count, 1)
        self.assertEqual(fibo.served_count, 0)
        self.assertEqual(fibo.missed_count, 0)

        self.assertEqual(fibo(7), 13)
        self.assertEqual(fibo.computed_count, 8)
        self.assertEqual(fibo.served_count, 6)

        self.assertEqual(identity({"a": 123}), {"a": 123})
        self.assertEqual(identity.computed_count, 1)
        self.assertEqual(identity.served_count, 0)
        self.assertEqual(identity.missed_count, 1)
        for _ in range(15):
            self.assertEqual(identity({"a": 123}), {"a": 123})
        self.assertEqual(identity.computed_count, 16)
        self.assertEqual(identity.served_count, 0)
        self.assertEqual(identity.missed_count, 16)

    # Comment the following line to force the check the bonus code
    @unittest.expectedFailure
    def test_bonus2(self):
        self.assertEqual(identity.__doc__, "Simple Identity function.")
        self.assertEqual(fibo.__doc__, "Fibonacci suite computer")

    # Comment the following line to force the check the bonus code
    @unittest.expectedFailure
    def test_bonus3(self):

        @caching(3)
        def identity_not_too_cached(item):
            """Simple Identity function."""
            return item

        for i in range(5):
            identity_not_too_cached(i)
        self.assertEqual(identity_not_too_cached.computed_count, 5)
        self.assertEqual(identity_not_too_cached.served_count, 0)

        for i in range(3, 5):
            identity_not_too_cached(i)
        self.assertEqual(identity_not_too_cached.computed_count, 5)
        self.assertEqual(identity_not_too_cached.served_count, 2)

        for i in range(5):
            identity_not_too_cached(i)
        self.assertEqual(identity_not_too_cached.computed_count, 10)
        self.assertEqual(identity_not_too_cached.served_count, 2)


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