<reponame>BFriedrichs/motorturbine
from .. import errors, updateset, utils, document
from . import base_field, string_field
import copy


class DictWrapper(dict):
    def __init__(self, *args, dict_field=None, **kwargs):
        self.dict_field = dict_field
        super().__init__(*args, **kwargs)

    def __eq__(self, other):
        return self.as_value() == other

    def update(self, values):
        if values is None:
            return

        self.dict_field.validate(values)
        for key, value in values.items():
            self[key] = value

    def _update(self, values):
        if values is None:
            return

        self.dict_field.validate(values)
        for key, value in values.items():
            field = self.to_field(key, value, sync_field=False)
            dict.__setitem__(self, key, field)

    def __setitem__(self, key, value):
        self.dict_field.validate_key(key)

        field = self.to_field(key, value)

        self.dict_field.pseudo_operators.pop(key, None)
        super().__setitem__(key, field)

    def to_field(self, key, value, sync_field=True):
        name = '{}.{}'.format(self.dict_field.name, key)
        new_field = self.dict_field.value_field.clone(
            document=self.dict_field.document,
            name=name,
            sync_enabled=sync_field)

        old_field = self.get(key, None)
        if old_field is not None:
            new_field.set_value(old_field.get_value())
            new_field.synced()

            # new_field.operators = old_field.operators
        new_field.set_value(value)
        new_field.sync_enabled = True

        return new_field

    def __getitem__(self, index):
        item = super().__getitem__(index)
        if hasattr(item, 'value'):
            item = item.value
        return item

    def __delitem__(self, index):
        item = super().__getitem__(index)

        update = {
            'op': updateset.Unset(index),
            'old_value': item.value
        }
        self.dict_field.pseudo_operators[index] = [update]

        field_name = self.dict_field.name + '.' + index
        self.dict_field.document.update_sync(field_name)
        super().__delitem__(index)

    def as_value(self):
        return {
            name: dict.__getitem__(self, name).get_value()
            for name in self
        }


class MapField(base_field.BaseField):
    """__init__(value_field, key_field=StringField(), *, \
    default={}, required=False, unique=False)

    This field only allows a `dict` type to be set as its value.

    If an entire dict is set instead of singular values each key-value pair in
    the new dict has to match the subfields that were set when initialising
    the field.

    :param BaseField value_field:
        Sets the field type that will be used for the values of the dict.
    """
    def __init__(self,
                 value_field,
                 **kwargs):
        kwargs['default'] = kwargs.pop('default', {})
        super().__init__(**kwargs)
        self.pseudo_operators = {}
        self.key_field = string_field.StringField()
        self.value_field = value_field
        self.value = DictWrapper(dict_field=self)

    def clone(self, *args, **kwargs):
        new_field = super().clone(self.value_field, **kwargs)
        new_field.value.update(self.default)
        return new_field

    def synced(self):
        super().synced()
        self.pseudo_operators = {}

        if self.value is None:
            return

        for name in self.value:
            field = dict.__getitem__(self.value, name)
            field.synced()

    def set_value(self, new_value):
        old_val = None
        if self.value is not None:
            old_val = self.value.copy()

        next_operator = updateset.to_operator(self.value, new_value)
        next_operator.set_original_value(old_val)

        new_value = next_operator.apply()
        self.validate(new_value)

        update = {
            'op': next_operator,
            'old_value': old_val
        }
        if isinstance(next_operator, updateset.Set):
            self.updates = [update]
            self.pseudo_operators = {}
        else:
            self.updates.append(update)

        if new_value is None:
            self.value = new_value
        else:
            self.value.clear()
            self.value._update(new_value)

        if self.sync_enabled:
            self.document.update_sync(self.name)

    def get_updates(self, path):
        if path == '':
            # we need to make sure that if there is a set update on the map
            # each update is actually a value representation of its field
            # instead of the field itself (or document)
            for update in self.updates:
                op = update['op']
                for key in op.update.keys():
                    field = op.update[key]
                    as_field = self.value_field.clone(sync_enabled=False)
                    as_field.set_value(field)
                    op.update[key] = as_field.get_value()

                old = update['old_value']
                for key in old.keys():
                    field = old[key]
                    if isinstance(field, base_field.BaseField):
                        old[key] = field.get_value()

            return self.updates

        sub_path, cutoff = utils.get_sub_path(path, 1)
        name = cutoff[0]

        field = dict.get(self.value, name)

        if field is None:
            return self.pseudo_operators.get(name, [])
        return field.get_updates(sub_path)

    def __getattr__(self, attr):
        path_split = attr.split('.')
        field_name = path_split[0]
        if len(path_split) == 1:
            return super().__getattribute__(attr)

        return self.value[path_split[1]]

    def validate_field(self, value):
        if not isinstance(value, dict):
            raise errors.TypeMismatch(dict, type(value))

        for key, item in value.items():
            self.validate_key(key)

    def validate_key(self, value):
        self.key_field.validate(value)

    def get_value(self):
        if self.value is None:
            return None
        return self.value.as_value()
