from __future__ import annotations

import re
import unittest
from dataclasses import dataclass

from Aspidites._vendor.apm import *


class TerseStyleTest(unittest.TestCase):

    def test_fibonacci(self):
        def fibonacci(n):
            return match(n,
                         1, 1,
                         2, 1,
                         _, lambda x: fibonacci(x - 1) + fibonacci(x - 2)
                         )

        self.assertEqual(1, fibonacci(1))
        self.assertEqual(1, fibonacci(2))
        self.assertEqual(2, fibonacci(3))
        self.assertEqual(3, fibonacci(4))
        self.assertEqual(5, fibonacci(5))
        self.assertEqual(8, fibonacci(6))

    def test_underscore_as_key(self):
        pet = {'type': 'dog', 'details': {'age': 3}}

        self.assertEqual(
            3,
            match(
                pet,
                {'details': {'age': _}}, lambda age: age
            )
        )
        self.assertEqual(
            ('details', 3),
            match(
                pet,
                {_: {'age': _}}, lambda a, b: (a, b)
            )
        )
        self.assertEqual(
            ('details', 3),
            match(
                pet,
                {_ >> 'key': {'age': _}}, lambda a, b: (a, b)
            )
        )

    def test_match_class_hierarchies(self):
        class Animal:
            pass

        class Hippo(Animal):
            pass

        class Zebra(Animal):
            pass

        class Horse(Animal):
            pass

        def what_is(x):
            return match(x,
                         Hippo, 'hippo',
                         Zebra, 'zebra',
                         Animal, 'some other animal',
                         _, 'not at all an animal',
                         )

        self.assertEqual('zebra', what_is(Zebra()))
        self.assertEqual('hippo', what_is(Hippo()))
        self.assertEqual('some other animal', what_is(Horse()))
        self.assertEqual('some other animal', what_is(Animal()))
        self.assertEqual('not at all an animal', what_is(42))

    def test_dataclasses(self):
        @dataclass
        class Pet:
            name: str
            age: int

        pet = Pet('rover', 7)

        self.assertEqual(7, match(pet, Pet('rover', _), lambda age: age))
        self.assertEqual('rover', match(pet, Pet(_, 7), lambda name: name))
        self.assertEqual(('rover', 7), match(pet, Pet(_, _), lambda name, age: (name, age)))

    def test_so_many_things(self):
        expectations = [
            (3, "this matches the number 3"),
            (4, "matches any integer"),
            (("k", 2), "a tuple (a, b) you can use in a function"),
            ([1, 2, 7], "any list of 3 elements that begins with [1, 2]"),
            ({'x': 3}, "any dict with a key 'x' and any value associated"),
            ({'x': 4, 'y': 4}, "any dict with a key 'x' and any value associated"),
            ("xyz", "anything else"),
        ]

        for val, expected in expectations:
            result = match(val,
                           3, "this matches the number 3",
                           int, "matches any integer",
                           (str, int), lambda a, b: "a tuple (a, b) you can use in a function",
                           [1, 2, _], "any list of 3 elements that begins with [1, 2]",
                           {'x': _}, "any dict with a key 'x' and any value associated",
                           _, "anything else"
                           )
            self.assertEqual(expected, result)

    def test_regex(self):
        def what_is(pet):
            return match(pet,
                         re.compile(r'(\w+)-(\w+)-cat$'), lambda name, my: 'cat ' + name,
                         re.compile(r'(\w+)-(\w+)-dog$'), lambda name, my: 'dog ' + name,
                         _, "something else"
                         )

        self.assertEqual('dog fuffy', what_is('fuffy-my-dog'))
        self.assertEqual('dog puffy', what_is('puffy-her-dog'))
        self.assertEqual('cat carla', what_is('carla-your-cat'))
        self.assertEqual('something else', what_is('roger-my-hamster'))

    def test_lambda_with_too_little_parameters(self):
        self.assertEqual("anything", match(1, _, lambda: "anything"))

    def test_lambda_with_too_many_parameters(self):
        self.assertEqual("anything(1, None, None)", match(1, _, lambda a, b, c: f"anything({a}, {b}, {c})"))

    def test_default_value(self):
        self.assertEqual("fallback", match(1, 2, lambda: "something", "fallback"))

    def test_default_action(self):
        self.assertEqual("fallback", match(1, 2, lambda: "something", lambda: "fallback"))
