# CookieJar ADT: Python Dictionary Implementation (Extended Mutable)
#
# 345678901234567890123456789012345678901234567890123456789012345678901234567890
#
# 2018-10-10: Initial Python version loosely based on Scala version
# 2018-10-12: Corrected/improved statement of invariant
#             Added methods count() and cardinalty()
# 2022-04-20: Improved comments. Reformatted with black.

from typing import Dict, Generic
from dataclasses import dataclass, field  # 3.7+

from cookiejar2 import CookieJarABC, CookieType

# Class CookieJarDict implements the CookieJar ADT using the Python dictionary
# data type to implement the jar (mathematical bag) of cookies.  The keys of
# the dictionary are the cookie types and the value is the count of cookies of
# that type in the jar.  If the count is zero, then that key does not appear in
# the map.
#
#   IMPLEMENTATION INVARIANT:
#     (ForAll c, i : c IN CookieType && i==OCCURRENCES(c,Bag(self)) ::
#         (i > 0  => self.jar[c] == i) &&
#         (i == 0 => c not in self.jar) )
#
# Once verified: self.count(c) == OCCURRENCES(c,Bag(self))

# The @dataclass decorator generates default __init__ and __repr__ methods for
# the CookieJarList class. Setting the intial value of jar to
# field(default_factory=dict) causes the initial value to be an empty dict.

@dataclass
class CookieJarDict(CookieJarABC, Generic[CookieType]):
    jar: Dict[CookieType, int] = field(default_factory=dict)

    def put_in(self, cookie: CookieType) -> None:
        """Insert "cookie" into this cookie jar"""
        if cookie in self.jar:
            self.jar[cookie] += 1
        else:
            self.jar[cookie] = 1

    def eat(self, cookie: CookieType) -> None:
        """Remove "cookie" from this cookie jar"""
        if cookie in self.jar:  # Assumes value is int
            if self.jar[cookie] > 1:
                self.jar[cookie] -= 1
            else:
                del self.jar[cookie]
        else:
            print(f"Error: No cookie of type {cookie} in jar to eat")

    def is_empty(self) -> bool:
        """Is this cookie jar empty?"""
        return len(self.jar) == 0

    def has(self, cookie: CookieType) -> bool:
        """Is "cookie" in this cookie jar?"""
        return cookie in self.jar

    def count(self, cookie: CookieType) -> int:
        """How many instances of "cookie" in cookie jar?"""
        return self.jar.get(cookie, 0)

    def cardinality(self) -> int:
        """How many cookies in cookie jar?"""
        return len(self.jar)
