# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
#
# This file is part of Poezio.
#
# Poezio is free software: you can redistribute it and/or modify
# it under the terms of the GPL-3.0+ license. See the COPYING file.
"""
Defines the Resource and Contact classes, which are used in
the roster.
"""

from collections import defaultdict
import logging
from typing import (
    Any,
    Dict,
    Iterator,
    List,
)

from slixmpp import InvalidJID, JID
from slixmpp.roster import RosterItem
from slixmpp.types import ResourceDict

log = logging.getLogger(__name__)


class Resource:
    """
    Defines a roster item.
    It's a precise resource.
    """

    def __init__(self, jid: str, data: ResourceDict):
        """
        data: the dict to use as a source
        """
        # Full JID
        self._jid: str = jid
        self._data: ResourceDict = data

    @property
    def jid(self) -> str:
        """JID of this resource."""
        return self._jid

    @property
    def priority(self) -> int:
        """Value of the priority received for this resource."""
        try:
            return int(self._data.get('priority', 0))
        except Exception:
            return 0

    @property
    def presence(self) -> str:
        """Value of the latest "show" for this resource."""
        return str(self._data.get('show')) or ''

    @property
    def status(self) -> str:
        """Value of the latest status for this resource."""
        return str(self._data.get('status')) or ''

    def __repr__(self) -> str:
        return '<%s>' % self._jid

    def __eq__(self, value: object) -> bool:
        if not isinstance(value, Resource):
            return False
        return self.jid == value.jid and self._data == value._data


class Contact:
    """
    This a way to gather multiple resources from the same bare JID.
    This class contains zero or more Resource object and useful methods
    to get the resource with the highest priority, etc
    """

    def __init__(self, item: RosterItem):
        """
        item: a slixmpp RosterItem pointing to that contact
        """
        self.__item = item
        self.folded_states: Dict[str, bool] = defaultdict(lambda: True)
        self._name = ''
        self.avatar = None
        self.error = None
        self.rich_presence: Dict[str, Any] = defaultdict(lambda: None)

    @property
    def groups(self) -> List[str]:
        """Name of the groups the contact is in"""
        return self.__item['groups'] or ['none']

    @property
    def bare_jid(self) -> JID:
        """The bare jid of the contact"""
        return JID(self.__item.jid)

    @property
    def name(self) -> str:
        """The name of the contact or an empty string."""
        return self.__item['name'] or self._name or ''

    @name.setter
    def name(self, value:  str) -> None:
        """Set the name of the contact with user nickname"""
        self._name = value

    @property
    def ask(self) -> str | None:
        """If we asked the contact for presence or not."""
        if self.__item['pending_out']:
            return 'asked'
        return None

    @property
    def pending_in(self) -> bool:
        """We received a subscribe stanza from this contact."""
        return self.__item['pending_in']

    @pending_in.setter
    def pending_in(self, value: bool) -> None:
        self.__item['pending_in'] = value

    @property
    def pending_out(self) -> bool:
        """We sent a subscribe stanza to this contact."""
        return self.__item['pending_out']

    @pending_out.setter
    def pending_out(self, value: bool) -> None:
        self.__item['pending_out'] = value

    @property
    def resources(self) -> Iterator[Resource]:
        """List of the available resources as Resource objects"""
        return (Resource('%s%s' % (self.bare_jid, ('/' + key)
                                   if key else ''), self.__item.resources[key])
                for key in self.__item.resources.keys())

    @property
    def subscription(self) -> str:
        """Status of our subscription to this contact."""
        return self.__item['subscription']

    def __contains__(self, value:  str | JID) -> bool:
        try:
            resource = JID(value).resource
        except InvalidJID:
            resource = None
        return value in self.__item.resources or \
            (resource is not None and resource in self.__item.resources)

    def __len__(self) -> int:
        """Number of resources"""
        return len(self.__item.resources)

    def __bool__(self) -> bool:
        """This contact exists even when he has no resources"""
        return True

    def __getitem__(self, key: str | JID) -> Resource | None:
        """Return the corresponding Resource object, or None"""
        try:
            res = JID(key).resource
        except InvalidJID:
            return None
        resources = self.__item.resources
        item = resources.get(res, None) or resources.get(str(key), None)
        return Resource(str(key), item) if item else None

    def subscribe(self) -> None:
        """Subscribe to this JID"""
        self.__item.subscribe()

    def authorize(self) -> None:
        """Authorize this JID"""
        self.__item.authorize()

    def unauthorize(self) -> None:
        """Unauthorize this JID"""
        self.__item.unauthorize()

    def unsubscribe(self) -> None:
        """Unsubscribe from this JID"""
        self.__item.unsubscribe()

    def get(self, key: str,
            default: Resource | None = None) -> Resource | None:
        """Same as __getitem__, but with a configurable default"""
        return self[key] or default

    def get_resources(self) -> list[Resource]:
        """Return all resources, sorted by priority """
        def compare_resources(res: Resource) -> int:
            return res.priority
        return sorted(self.resources, key=compare_resources, reverse=True)

    def get_highest_priority_resource(self) -> Resource | None:
        """Return the resource with the highest priority"""
        resources = self.get_resources()
        if resources:
            return resources[0]
        return None

    def folded(self, group_name: str = 'none') -> bool:
        """
        Return the Folded state of a contact for this group
        """
        return self.folded_states[group_name]

    def toggle_folded(self, group: str = 'none') -> None:
        """
        Fold if it's unfolded, and vice versa
        """
        self.folded_states[group] = not self.folded_states[group]

    def __repr__(self) -> str:
        ret = '<Contact: %s' % self.bare_jid
        for resource in self.resources:
            ret += '\n\t\t%s' % resource
        return ret + ' />\n'
