# Copyright 2018-2020 Jakub Kuczys (https://github.com/jack1142)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import logging
from math import ceil
from typing import TYPE_CHECKING, Optional, Union

from .utils import AlwaysGreaterOrEqual

if TYPE_CHECKING:
    from .player import Playlist

log = logging.getLogger(__name__)

__all__ = ("TierEstimates",)


class TierEstimates:
    """TierEstimates()
    Represents Rocket League playlist's tier estimates.

    Attributes
    ----------
    playlist: `Playlist`
        Playlist object which these estimates are about.
    tier: int
        Estimated tier on this playlist.
    division: int
        Estimated division on this playlist.
    div_down: int, optional
        Estimated amount of points for player to go a division down.
    div_up: int, optional
        Estimated amount of points for player to go a division up.
    tier_down: int, optional
        Estimated amount of points for player to go a tier down.
    tier_up: int, optional
        Estimated amount of points for player to go a tier up.

    """

    __slots__ = (
        "playlist",
        "tier",
        "division",
        "div_down",
        "div_up",
        "tier_down",
        "tier_up",
    )

    def __init__(self, playlist: Playlist):
        self.playlist = playlist
        self.tier: int
        self.division: int
        if playlist.tier == 0:
            self._estimate_current_tier()
        else:
            self.tier = playlist.tier
            self.division = playlist.division
        self.div_down = self._estimate_div_down()
        self.div_up = self._estimate_div_up()
        self.tier_down = self._estimate_tier_down()
        self.tier_up = self._estimate_tier_up()

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__}"
            f" for playlist {self.playlist.key};"
            f" tier={self.tier}"
            f" division={self.division}"
            f" tier_down={self.tier_down}"
            f" div_down={self.div_down}"
            f" div_up={self.div_up}"
            f" tier_up={self.tier_up}"
            f">"
        )

    def _estimate_div_down(self) -> Optional[int]:
        playlist = self.playlist
        if self.tier == 1 and self.division == 0 or self.tier == 0:
            return None
        try:
            divisions = playlist.breakdown[self.tier]
            div_down = int(ceil(divisions[self.division][0] - playlist.skill))
        except KeyError as e:
            log.debug(str(e))
            return None
        if div_down > 0:
            div_down = -1
        return div_down

    def _estimate_div_up(self) -> Optional[int]:
        playlist = self.playlist
        if self.tier == playlist.tier_max or self.tier == 0:
            return None
        try:
            divisions = playlist.breakdown[self.tier]
            if self.tier == self.division == 0:
                value = divisions[1][0]
            else:
                value = divisions[self.division][1]
            div_up = int(ceil(value - playlist.skill))
        except KeyError as e:
            log.debug(str(e))
            return None
        if div_up < 0:
            div_up = 1
        return div_up

    def _estimate_tier_down(self) -> Optional[int]:
        playlist = self.playlist
        if self.tier in {0, 1}:
            return None
        try:
            divisions = playlist.breakdown[self.tier]
            tier_down = int(ceil(divisions[0][0] - playlist.skill))
        except KeyError as e:
            log.debug(str(e))
            return None
        if tier_down > 0:
            tier_down = -1
        return tier_down

    def _estimate_tier_up(self) -> Optional[int]:
        playlist = self.playlist
        if self.tier in {0, playlist.tier_max}:
            return None
        try:
            divisions = playlist.breakdown[self.tier]
            tier_up = int(ceil(divisions[3][1] - playlist.skill))
        except KeyError as e:
            log.debug(str(e))
            return None
        if tier_up < 0:
            tier_up = 1
        return tier_up

    def _estimate_current_tier(self) -> None:
        playlist = self.playlist
        if not playlist.breakdown:
            self.tier = playlist.tier
            self.division = playlist.division
            return

        lowest_diff: Union[float, int, AlwaysGreaterOrEqual] = AlwaysGreaterOrEqual()
        for tier, divisions in playlist.breakdown.items():
            for division, (begin, end) in divisions.items():
                if begin <= playlist.skill <= end:
                    self.tier = tier
                    self.division = division
                    return
                diff, incr = min(
                    (abs(playlist.skill - begin), -1), (abs(playlist.skill - end), 1)
                )
                condition = diff <= lowest_diff
                if condition:
                    lowest_diff = diff
                    lowest_diff_tier = tier
                    lowest_diff_division = division + incr

        if lowest_diff_division == -1:
            self.tier = lowest_diff_tier - 1
            self.division = 3
            if self.tier < 1:
                self.tier = 1
                self.division = 0
        elif lowest_diff_division == 4:
            self.tier = lowest_diff_tier + 1
            self.division = 0
            if self.tier > playlist.tier_max:
                self.tier = playlist.tier_max
        else:
            self.tier = lowest_diff_tier
            self.division = lowest_diff_division
