from __future__ import annotations

import copy
import operator
from functools import reduce

from .util import asjson, is_list


class AST(dict):
    _frozen = False

    def __init__(self, *args, **kwargs):
        super().__init__()
        self.update(*args, **kwargs)
        self._frozen = True

    @property
    def frozen(self):
        return self._frozen

    @property
    def parseinfo(self):
        try:
            return super().__getitem__('parseinfo')
        except KeyError:
            pass

    def set_parseinfo(self, value):
        super().__setitem__('parseinfo', value)

    def copy(self):
        return copy.copy(self)

    def asjson(self):
        return asjson(self)

    def _set(self, key, value, force_list=False):
        key = self._safekey(key)
        previous = self.get(key)

        if previous is None and force_list:
            value = [value]
        elif previous is None:
            pass
        elif is_list(previous):
            value = [*previous, value]
        else:
            value = [previous, value]

        super().__setitem__(key, value)

    def _setlist(self, key, value):
        return self._set(key, value, force_list=True)

    def __copy__(self):
        return AST(self)

    def __getitem__(self, key):
        if key in self:
            return super().__getitem__(key)
        key = self._safekey(key)
        if key in self:
            return super().__getitem__(key)
        return None

    def __setitem__(self, key, value):
        self._set(key, value)

    def __delitem__(self, key):
        key = self._safekey(key)
        super().__delitem__(key)

    def __setattr__(self, name, value):
        if self._frozen and name not in vars(self):
            raise AttributeError(
                f'{type(self).__name__} attributes are fixed. '
                f' Cannot set attribute "{name}".',
            )
        super().__setattr__(name, value)

    def __getattr__(self, name):
        key = self._safekey(name)
        if key in self:
            return self[key]
        elif name in self:
            return self[name]

        try:
            return super().__getattribute__(name)
        except AttributeError:
            return None

    def __hasattribute__(self, name):
        try:
            super().__getattribute__(name)
        except (TypeError, AttributeError):
            return False
        else:
            return True

    def __reduce__(self):
        return (AST, (list(self.items()),))

    def _safekey(self, key):
        while self.__hasattribute__(key):
            key += '_'
        return key

    def _define(self, keys, list_keys=None):
        for key in (self._safekey(k) for k in keys):
            if key not in self:
                super().__setitem__(key, None)

        for key in (self._safekey(k) for k in list_keys or []):
            if key not in self:
                super().__setitem__(key, [])

    def __json__(self, seen=None):
        return {name: asjson(value, seen=seen) for name, value in self.items()}

    def __repr__(self):
        return repr(self.asjson())

    def __str__(self):
        return str(self.asjson())

    def __hash__(self):
        # NOTE: objects are actually mutable during creation
        return reduce(
            operator.xor,
            (hash((name, id(value))) for name, value in self.items()),
            0,
        )
