class ApiAttribute:
    """A data descriptor that sets and returns values."""

    def __init__(self, name):
        """Create an instance of ApiAttribute.

        :param name: name of this attribute.
        :type name: str.
        """
        self.name = name

    def __get__(self, obj, type=None):
        """Accesses value of this attribute."""
        return obj.attr.get(self.name)

    def __set__(self, obj, value):
        """Write value of this attribute."""
        obj.attr[self.name] = value
        if obj.dirty.get(self.name) is not None:
            obj.dirty[self.name] = True

    def __del__(self, obj=None):
        """Delete value of this attribute."""
        if not obj:
            return

        del obj.attr[self.name]
        if obj.dirty.get(self.name) is not None:
            del obj.dirty[self.name]


class ApiAttributeMixin:
    """Mixin to initialize required global variables to use ApiAttribute."""

    def __init__(self):
        self.attr = {}
        self.dirty = {}
        self.http = None  # Any element may make requests and will require this
        # field.


class ApiResource(dict):
    """Super class of all api resources.

    Inherits and behaves as a python dictionary to handle api resources.
    Save clean copy of metadata in self.metadata as a dictionary.
    Provides changed metadata elements to efficiently update api resources.
    """

    auth = ApiAttribute("auth")

    def __init__(self, *args, **kwargs):
        """Create an instance of ApiResource."""
        super().__init__()
        self.update(*args, **kwargs)
        self.metadata = dict(self)

    def __getitem__(self, key):
        """Overwritten method of dictionary.

        :param key: key of the query.
        :type key: str.
        :returns: value of the query.
        """
        return dict.__getitem__(self, key)

    def __setitem__(self, key, val):
        """Overwritten method of dictionary.

        :param key: key of the query.
        :type key: str.
        :param val: value of the query.
        """
        dict.__setitem__(self, key, val)

    def __repr__(self):
        """Overwritten method of dictionary."""
        dict_representation = dict.__repr__(self)
        return f"{type(self).__name__}({dict_representation})"

    def update(self, *args, **kwargs):
        """Overwritten method of dictionary."""
        BAD_URL_PREFIX = "https://www.googleapis.comhttps:"
        for k, v in dict(*args, **kwargs).items():
            if k == "downloadUrl" and v.startswith(BAD_URL_PREFIX):
                v = v.replace(BAD_URL_PREFIX, "https://www.googleapis.com", 1)
            self[k] = v

    def UpdateMetadata(self, metadata=None):
        """Update metadata and mark all of them to be clean."""
        if metadata:
            self.update(metadata)
        self.metadata = dict(self)

    def GetChanges(self):
        """Returns changed metadata elements to update api resources efficiently.

        :returns: dict -- changed metadata elements.
        """
        dirty = {}
        for key in self:
            if self.metadata.get(key) is None:
                dirty[key] = self[key]
            elif self.metadata[key] != self[key]:
                dirty[key] = self[key]
        return dirty


class ApiResourceList(ApiAttributeMixin, ApiResource):
    """Abstract class of all api list resources.

    Inherits ApiResource and builds iterator to list any API resource.
    """

    metadata = ApiAttribute("metadata")

    def __init__(self, auth=None, metadata=None):
        """Create an instance of ApiResourceList.

        :param auth: authorized GoogleAuth instance.
        :type auth: GoogleAuth.
        :param metadata: parameter to send to list command.
        :type metadata: dict.
        """
        ApiAttributeMixin.__init__(self)
        ApiResource.__init__(self)
        self.auth = auth
        self.UpdateMetadata()
        if metadata:
            self.update(metadata)

    def __iter__(self):
        """Returns iterator object.

        :returns: ApiResourceList -- self
        """
        return self

    def __next__(self):
        """Make API call to list resources and return them.

        Auto updates 'pageToken' every time it makes API call and
        raises StopIteration when it reached the end of iteration.

        :returns: list -- list of API resources.
        :raises: StopIteration
        """
        if "pageToken" in self and self["pageToken"] is None:
            raise StopIteration
        result = self._GetList()
        self["pageToken"] = self.metadata.get("nextPageToken")
        return result

    def GetList(self):
        """Get list of API resources.

        If 'maxResults' is not specified, it will automatically iterate through
        every resources available. Otherwise, it will make API call once and
        update 'pageToken'.

        :returns: list -- list of API resources.
        """
        if self.get("maxResults") is None:
            self["maxResults"] = 1000
            result = []
            for x in self:
                result.extend(x)
            del self["maxResults"]
            return result
        else:
            return next(self)

    def _GetList(self):
        """Helper function which actually makes API call.

        Should be overwritten.

        :raises: NotImplementedError
        """
        raise NotImplementedError

    def Reset(self):
        """Resets current iteration"""
        if "pageToken" in self:
            del self["pageToken"]
