"""
MIT License

Copyright (c) 2017-present Devoxin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from typing import TYPE_CHECKING, Final

if TYPE_CHECKING:
    from .node import Node

__all__ = (
    "Penalty",
    "Stats",
)


class Penalty:
    """
    Represents the penalty of the stats of a Node.

    Attributes
    ----------
    player_penalty: :class:`int`
        The number of playing players. 1 player = 1 penalty point.
    cpu_penalty: :class:`float`
        The penalty incurred from system CPU usage.
    null_frame_penalty: :class:`float`
        The penalty incurred from the average number of null frames per minute.
    deficit_frame_penalty: :class:`float`
        The penalty incurred from the average number of deficit frames per minute.
    total: :class:`float`
        The overall penalty, as a sum of all other penalties.
    """
    __slots__ = ('player_penalty', 'cpu_penalty', 'null_frame_penalty', 'deficit_frame_penalty', 'total')

    def __init__(self, stats):
        self.player_penalty: Final[int] = stats.playing_players
        self.cpu_penalty: Final[float] = 1.05 ** (100 * stats.system_load) * 10 - 10
        self.null_frame_penalty: Final[float] = ((1.03 ** (500 * (stats.frames_nulled / 3000))) * 300 - 300) * 2
        self.deficit_frame_penalty: Final[float] = (1.03 ** (500 * (stats.frames_deficit / 3000))) * 600 - 600
        self.total: Final[float] = self.player_penalty + self.cpu_penalty + self.null_frame_penalty + self.deficit_frame_penalty


class Stats:
    """
    Encapsulates the 'Statistics' emitted by Lavalink, usually every minute.

    Attributes
    ----------
    is_fake: :class:`bool`
        Whether or not the stats are accurate. This should only be True when
        the node has not yet received any statistics from the Lavalink server.
    uptime: :class:`int`
        How long the node has been running for, in milliseconds.
    players: :class:`int`
        The number of players connected to the node.
    playing_players: :class:`int`
        The number of players that are playing in the node.
    memory_free: :class:`int`
        The amount of memory free to the node, in bytes.
    memory_used: :class:`int`
        The amount of memory that is used by the node, in bytes.
    memory_allocated: :class:`int`
        The amount of memory allocated to the node, in bytes.
    memory_reservable: :class:`int`
        The amount of memory reservable to the node, in bytes.
    cpu_cores: :class:`int`
        The amount of cpu cores the system of the node has.
    system_load: :class:`float`
        The overall CPU load of the system. This is a number between 0-1,
        but can be multiplied by 100 for the percentage (0-100).
    lavalink_load: :class:`float`
        The CPU load generated by Lavalink This is a number between 0-1,
        but can be multiplied by 100 for the percentage (0-100).
    frames_sent: :class:`int`
        The number of frames sent to Discord.

        Warning
        -------
        Given that audio packets are sent via UDP, this number may not be 100% accurate due to packets dropped in transit.
    frames_nulled: :class:`int`
        The number of frames that yielded null, rather than actual data.
    frames_deficit: :class:`int`
        The number of missing frames. Lavalink generates this figure by calculating how many packets to expect
        per minute, and deducting ``frames_sent``. Deficit frames could mean the CPU is overloaded, and isn't
        generating frames as quickly as it should be.
    penalty: :class:`Penalty`
    """
    __slots__ = ('_node', 'is_fake', 'uptime', 'players', 'playing_players', 'memory_free', 'memory_used', 'memory_allocated',
                 'memory_reservable', 'cpu_cores', 'system_load', 'lavalink_load', 'frames_sent', 'frames_nulled',
                 'frames_deficit', 'penalty')

    def __init__(self, node, data):
        self._node: Final['Node'] = node

        self.is_fake: Final[bool] = data.get('isFake', False)
        self.uptime: Final[int] = data['uptime']

        self.players: Final[int] = data['players']
        self.playing_players: Final[int] = data['playingPlayers']

        memory = data['memory']
        self.memory_free: Final[int] = memory['free']
        self.memory_used: Final[int] = memory['used']
        self.memory_allocated: Final[int] = memory['allocated']
        self.memory_reservable: Final[int] = memory['reservable']

        cpu = data['cpu']
        self.cpu_cores: Final[int] = cpu['cores']
        self.system_load: Final[float] = cpu['systemLoad']
        self.lavalink_load: Final[float] = cpu['lavalinkLoad']

        frame_stats = data.get('frameStats') or {}
        self.frames_sent: Final[int] = frame_stats.get('sent', 0)
        self.frames_nulled: Final[int] = frame_stats.get('nulled', 0)
        self.frames_deficit: Final[int] = frame_stats.get('deficit', 0)
        self.penalty: Final[Penalty] = Penalty(self)

    @classmethod
    def empty(cls, node):
        data = {
            'isFake': True,
            'uptime': 0,
            'players': 0,
            'playingPlayers': 0,
            'memory': {
                'free': 0,
                'used': 0,
                'allocated': 0,
                'reservable': 0
            },
            'cpu': {
                'cores': 0,
                'systemLoad': 0,
                'lavalinkLoad': 0
            }
        }

        return cls(node, data)
