import copy, logging, threading, uuid
from typing import Dict, List, Set, Tuple, Union
from common.database.engines._DatabaseEngine import _DatabaseEngine

LOGGER = logging.getLogger(__name__)

def get_or_create_dict(keys : Dict[str, Union[Dict, List, Set]], key_name : str) -> Dict:
    container = keys.get(key_name, None)
    if container is None: container = keys.setdefault(key_name, dict())
    if not isinstance(container, dict): raise Exception('Key({}) is not a dict'.format(key_name))
    return container

def get_or_create_list(keys : Dict[str, Union[Dict, List, Set]], key_name : str) -> List:
    container = keys.get(key_name, None)
    if container is None: container = keys.setdefault(key_name, list())
    if not isinstance(container, list): raise Exception('Key({}) is not a list'.format(key_name))
    return container

def get_or_create_set(keys : Dict[str, Union[Dict, List, Set]], key_name : str) -> Set:
    container = keys.get(key_name, None)
    if container is None: container = keys.setdefault(key_name, set())
    if not isinstance(container, set): raise Exception('Key({}) is not a set'.format(key_name))
    return container

class InMemoryDatabaseEngine(_DatabaseEngine):
    def __init__(self, **settings):
        self._internal_lock = threading.Lock()
        self._external_lock = threading.Lock()
        self._owner_key = None
        self._keys = {} # name => set/list/dict/string

    def lock(self) -> Tuple[bool, str]:
        owner_key = str(uuid.uuid4())
        with self._internal_lock:
            acquired = self._external_lock.acquire()
            if not acquired: return False, None
            self._owner_key = owner_key
            return True, owner_key

    def unlock(self, owner_key : str) -> bool:
        with self._internal_lock:
            if self._owner_key != owner_key: return False
            self._external_lock.release()
            self._owner_key = None
            return True

    def keys(self) -> list:
        with self._internal_lock:
            return copy.deepcopy(list(self._keys.keys()))

    def exists(self, key_name : str) -> bool:
        with self._internal_lock:
            return key_name in self._keys

    def delete(self, key_name : str) -> bool:
        with self._internal_lock:
            if key_name not in self._keys: return False
            del self._keys[key_name]
            return True

    def dict_get(self, key_name : str, fields : List[str] = []) -> Dict[str, str]:
        with self._internal_lock:
            container = get_or_create_dict(self._keys, key_name)
            if len(fields) == 0: fields = container.keys()
            return copy.deepcopy({
                field_name : field_value for field_name,field_value in container.items() if field_name in fields
            })

    def dict_update(self, key_name : str, update_fields : Dict[str,str] = {}, remove_fields : Set[str] = set()) -> None:
        with self._internal_lock:
            container = get_or_create_dict(self._keys, key_name)
            for field in list(remove_fields): container.pop(field, None)
            container.update(update_fields)

    def dict_delete(self, key_name : str, fields : List[str] = []) -> None:
        with self._internal_lock:
            container = get_or_create_dict(self._keys, key_name)
            if len(fields) == 0: fields = container.keys()
            for field in list(fields): container.pop(field, None)

    def list_get_all(self, key_name : str) -> List[str]:
        with self._internal_lock:
            container = get_or_create_list(self._keys, key_name)
            return copy.deepcopy(container)

    def list_push_last(self, key_name : str, item : str) -> None:
        with self._internal_lock:
            container = get_or_create_list(self._keys, key_name)
            container.append(item)

    def list_remove_first_occurrence(self, key_name : str, item: str) -> None:
        with self._internal_lock:
            container = get_or_create_list(self._keys, key_name)
            container.remove(item)

    def set_add(self, key_name : str, item : str) -> None:
        with self._internal_lock:
            container = get_or_create_set(self._keys, key_name)
            container.add(item)

    def set_has(self, key_name : str, item : str) -> bool:
        with self._internal_lock:
            container = get_or_create_set(self._keys, key_name)
            return item in container

    def set_remove(self, key_name : str, item : str) -> None:
        with self._internal_lock:
            container = get_or_create_set(self._keys, key_name)
            container.discard(item)

    def dump(self) -> List[Tuple[str, str, str]]:
        with self._internal_lock:
            entries = []
            for key_name,key_value in self._keys.items():
                entries.append((key_name, type(key_value).__name__, str(key_value)))
        return entries
