# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/core.data.ipynb (unless otherwise specified).

__all__ = ['Observable', 'ObservableList', 'DatasetDescriptor', 'StringDescriptor', 'GenericDataset']

# Cell
from typing import Union, Optional, Any, Iterable, Callable
import os
import shutil
from abc import ABC, abstractmethod

# Cell
class Observable(ABC):
    """Simple implementation of the observer pattern."""
    def __init__(self, callbacks=None):
        self._callbacks = [] if callbacks is None else callbacks

    def register_callback(self, callback: Callable):
        self._callbacks.append(callback)

    def trigger_callbacks(self):
        for callback in self._callbacks:
            callback(self)

# Cell
class ObservableList(Observable):
    """List with observer pattern. The internal list prepresentation can be accessed with the list attribute"""
    def __init__(self, observable_list: list, callbacks=None):
        self._list = observable_list
        super().__init__(callbacks)

    @property
    def list(self):
        return self._list

    @list.setter
    def list(self, value: Any):
        self._list = value
        self.trigger_callbacks()

    def __repr__(self):
        return self._list.__repr__()

    def __iter__(self):
        for item in self._list:
            yield item

    def __len__(self):
        return len(self._list)

    def __getitem__(self, index: int):
        return self._list[index]

    def __setitem__(self, index: int, value: Any):
        self._list[index] = value
        self.trigger_callbacks()

    def __add__(self, other):
        # turn the callbacks first into a set and than back into a list to avoid callbacks being triggered multiple times
        new_callbacks = list(set(self._callbacks+other._callbacks))
        return ObservableList(self.list + other.list, new_callbacks)

    def append(self, item: Any):
        self._list.append(item)
        self.trigger_callbacks()

    def remove(self, item: Any):
        self._list.remove(item)
        self.trigger_callbacks()

    def insert(self, index: int, item: Any):
        self._list.insert(index, item)
        self.trigger_callbacks()

    def pop(self, index: int = -1):
        poped_item = self._list.pop(index)
        self.trigger_callbacks()
        return poped_item

    def extend(self, iterable: Iterable):
        self._list.extend(iterable)
        self.trigger_callbacks()

    def clear(self):
        self._list = []

    def count(self, item):
        return self._list.count(item)

    def index(self, item, start=0, stop=9223372036854775807):
        return self._list.index(item, start, stop)

    def reverse(self):
        self._list.reverse()

    def sort(self, key=float, reverse=False):
        self._list.sort(key=key, reverse=reverse)

# Cell
class DatasetDescriptor(ABC):
    """Abstract base class for descriptors of datasets.
    The private name of the descriptor is the defined name with a prefix _.
    The __get__ function will call the calculate_description function if the value of the descriptor is None and then return the value else it will just return the value of the descriptor.
    The __set__ function only allows for the attribute to be set to None, which will trigger a recomputation the next time the __get__ function is called.
    When inheriting this class the function calculate_description needs to be implemented, which defines how the private value should be calculated.
    """
    def __set_name__(self, owner, name):
        owner._descriptors.append(self)
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        "Attribute will be recomputed if it is None else the befor computed version will be returned."
        if getattr(obj, self.private_name) is None:
            value = self.calculate_description(obj)
            setattr(obj, self.private_name, value)
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        "Attribute can only be set to None externaly, otherwise an ValueError will be raised."
        if value is None:
            setattr(obj, self.private_name, value)
        else:
            raise ValueError("Attribute can only be set to None externaly.")

    @abstractmethod
    def calculate_description(self, obj):
        pass

# Cell
class StringDescriptor:
    """Descriptor for strings"""
    def __set_name__(self, owner, name):
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        setattr(obj, self.private_name, value)

# Cell
class GenericDataset:
    """A generic datset that has a name and description. Data is stored under the attribute base_data. The class provides a function `reset_infered_data` which can be called to reset all descriptors."""
    _descriptors = []

    name = StringDescriptor()
    description = StringDescriptor()

    def __init__(self, base_data, name: Optional[str] = None, description: Optional[str] = None):
        self.base_data = base_data
        self.name = name
        self.description = description

    def reset_infered_data(self, new_data=None):
        """Takes an argument to be compatible with callbacks."""
        for descriptor in self._descriptors:
            descriptor.__set__(self, None)