__all__ = ('Stats', )

from scarletio import RichAttributeErrorBaseType

from .constants import (
    LAVALINK_KEY_STATS_CPU, LAVALINK_KEY_STATS_CPU_CORES, LAVALINK_KEY_STATS_CPU_LAVALINK_LOAD,
    LAVALINK_KEY_STATS_CPU_SYSTEM_LOAD, LAVALINK_KEY_STATS_FRAME, LAVALINK_KEY_STATS_FRAME_DEFICIT,
    LAVALINK_KEY_STATS_FRAME_NULLED, LAVALINK_KEY_STATS_FRAME_SENT, LAVALINK_KEY_STATS_MEMORY,
    LAVALINK_KEY_STATS_MEMORY_ALLOCATED, LAVALINK_KEY_STATS_MEMORY_FREE, LAVALINK_KEY_STATS_MEMORY_RESERVABLE,
    LAVALINK_KEY_STATS_MEMORY_USED, LAVALINK_KEY_STATS_PLAYER_COUNT, LAVALINK_KEY_STATS_PLAYING_PLAYER_COUNT,
    LAVALINK_KEY_STATS_UPTIME
)


class Stats(RichAttributeErrorBaseType):
    """
    Represents the stats of a Lavalink node.
    
    Attributes
    ----------
    _total_penalty : `None`, `float`
        Total resource penalty of the respective node.
    cpu_cores : `int`
        The amount of cpu cores the system of the node has.
    cpu_lavalink_load : `int`
        The CPU load generated by Lavalink.
    cpu_system_load : `int`
        The overall CPU load of the system.
    frame_sent : `int`
        The number of frames sent.
    frame_nulled : `int`
        The number of null frames.
    frame_deficit : `int`
        The number of missing frames.
    memory_allocated : `int`
        The amount of memory allocated to the node.
    memory_free : `int`
        The amount of memory free to the node.
    memory_reservable : `int`
        The amount of memory reservable to the node.
    memory_used : `int`
        The amount of memory that is used by the node.
    player_count : `int`
        The amount of players connected to the node.
    playing_player_count : `int`
        The amount of players that are playing in the node.
    uptime : `float`
        How long the node has been running for in seconds.
    """
    __slots__ = (
        '_total_penalty', 'cpu_cores', 'cpu_lavalink_load', 'cpu_system_load', 'frame_deficit', 'frame_nulled',
        'frame_sent', 'memory_allocated', 'memory_free', 'memory_reservable', 'memory_used', 'player_count',
        'playing_player_count', 'uptime'
    )
    
    def __init__(self, data):
        """
        Creates a new stats instance from the given data.
        
        Parameters
        ----------
        data : `dict` of (`str`, `object`) items
            Deserialized json.
        """
        self.uptime = data[LAVALINK_KEY_STATS_UPTIME]*1000.0
        self.player_count = data[LAVALINK_KEY_STATS_PLAYER_COUNT]
        self.playing_player_count = data[LAVALINK_KEY_STATS_PLAYING_PLAYER_COUNT]
        
        memory_data = data[LAVALINK_KEY_STATS_MEMORY]
        self.memory_allocated = memory_data[LAVALINK_KEY_STATS_MEMORY_ALLOCATED]
        self.memory_free = memory_data[LAVALINK_KEY_STATS_MEMORY_FREE]
        self.memory_reservable = memory_data[LAVALINK_KEY_STATS_MEMORY_RESERVABLE]
        self.memory_used = memory_data[LAVALINK_KEY_STATS_MEMORY_USED]
        
        cpu_data = data[LAVALINK_KEY_STATS_CPU]
        self.cpu_cores = cpu_data[LAVALINK_KEY_STATS_CPU_CORES]
        self.cpu_lavalink_load = cpu_data[LAVALINK_KEY_STATS_CPU_SYSTEM_LOAD]
        self.cpu_system_load = cpu_data[LAVALINK_KEY_STATS_CPU_LAVALINK_LOAD]
        
        try:
            frame_data = data[LAVALINK_KEY_STATS_FRAME]
        except KeyError:
            frame_sent = 0
            frame_nulled = 0
            frame_deficit = 0
        else:
            frame_sent = frame_data.get(LAVALINK_KEY_STATS_FRAME_SENT, 0)
            frame_nulled = frame_data.get(LAVALINK_KEY_STATS_FRAME_NULLED, 0)
            frame_deficit = frame_data.get(LAVALINK_KEY_STATS_FRAME_DEFICIT, 0)
        self.frame_sent = frame_sent
        self.frame_nulled = frame_nulled
        self.frame_deficit = frame_deficit
        
        self._total_penalty = None
    
    
    @property
    def total_penalty(self):
        """
        Returns the resource penalty of the node.
        
        Returns
        -------
        penalty : `float`
        """
        total_penalty = self._total_penalty
        if (total_penalty is None):
            total_penalty = self._get_total_penalty()
        
        return total_penalty
    
    
    def _get_total_penalty(self):
        """
        Returns the resource penalty of the node.
        
        Used by the ``.total_penalty`` property, if penalty was not yet set.
        
        Returns
        -------
        penalty : `float`
        """
        player_penalty = self.playing_player_count
        cpu_penalty = 1.05 ** (100 * self.cpu_system_load) * 10 - 10
        
        frame_nulled = self.frame_nulled
        if frame_nulled:
            null_frame_penalty = ((1.03 ** (frame_nulled / 6)) * 300 - 300) * 2
        else:
            null_frame_penalty = 0
        
        frame_deficit = self.frame_deficit
        if frame_deficit:
            deficit_frame_penalty = (1.03 ** ((frame_deficit / 6))) * 600 - 600
        else:
            deficit_frame_penalty = 0.0
        
        total_penalty = player_penalty + cpu_penalty + null_frame_penalty + deficit_frame_penalty
        self._total_penalty = total_penalty
        return total_penalty
    
    
    def __repr__(self):
        """Returns the stat's representation."""
        repr_parts = ['<', self.__class__.__name__]
        
        repr_parts.append(', cpu_cores = ')
        repr_parts.append(repr(self.cpu_cores))
        
        repr_parts.append(', cpu_lavalink_load = ')
        repr_parts.append(repr(self.cpu_lavalink_load))
        
        repr_parts.append(', cpu_system_load = ')
        repr_parts.append(repr(self.cpu_system_load))
        
        repr_parts.append(', frame_sent = ')
        repr_parts.append(repr(self.frame_sent))
        
        repr_parts.append(', frame_nulled = ' )
        repr_parts.append(repr(self.frame_nulled))
        
        repr_parts.append(', frame_deficit = ')
        repr_parts.append(repr(self.frame_deficit))
        
        repr_parts.append(', memory_allocated = ')
        repr_parts.append(repr(self.memory_allocated))
        
        repr_parts.append(', memory_free = ')
        repr_parts.append(repr(self.memory_free))
        
        repr_parts.append(', memory_reservable = ')
        repr_parts.append(repr(self.memory_reservable))
        
        repr_parts.append(', memory_used = ')
        repr_parts.append(repr(self.memory_used))
        
        repr_parts.append(', player_count = ')
        repr_parts.append(repr(self.player_count))
        
        repr_parts.append(', playing_player_count = ')
        repr_parts.append(repr(self.playing_player_count))
        
        repr_parts.append(', uptime = ')
        repr_parts.append(self.uptime.__format__('.3f'))
        repr_parts.append('s')
        
        repr_parts.append('>')
        
        return ''.join(repr_parts)
