import pandas as pd
from functools import wraps
from lxml.etree import ParserError, XMLSyntaxError
from pyquery import PyQuery as pq
from .. import utils
from .constants import BOXSCORE_RETRY, PLAYER_SCHEME
from six.moves.urllib.error import HTTPError


def _int_property_decorator(func):
    @property
    @wraps(func)
    def wrapper(*args):
        index = args[0]._index
        prop = func(*args)
        try:
            return int(prop[index])
        except (ValueError, TypeError, IndexError):
            # If there is no value, default to None
            return None
    return wrapper


def _float_property_decorator(func):
    @property
    @wraps(func)
    def wrapper(*args):
        index = args[0]._index
        prop = func(*args)
        try:
            return float(prop[index])
        except (ValueError, TypeError, IndexError):
            # If there is no value, default to None
            return None
    return wrapper


class AbstractPlayer(object):
    """
    Get player information and stats for all seasons.

    Given a player ID, such as 'zettehe01' for Henrik Zetterberg, capture all
    relevant stats and information like name, team, height/weight, career
    goals, single-season assits, penalty minutes, and much more.

    By default, the class instance will return the player's career stats, but
    single-season stats can be found by calling the instance with the requested
    season as denoted on sports-reference.com.

    Parameters
    ----------
    player_id : string
        A player's ID according to hockey-reference.com, such as 'zettehe01'
        for Henrik Zetterberg. The player ID can be found by navigating to the
        player's stats page and getting the string between the final slash and
        the '.html' in the URL. In general, the ID is in the format 'lllllffnn'
        where 'lllll' is the first five letters of the player's last name, 'ff'
        is the first two letters of the player's first name, and 'nn' is a
        number starting at '01' for the first time that player ID has been used
        and increments by 1 for every successive player.
    player_name : string
        A string representing the player's first and last name, such as 'Henrik
        Zetterberg'.
    player_data : string
        A string representation of the player's HTML data from the Boxscore
        page. If the player appears in multiple tables, all of their
        information will appear in one single string concatenated together.
    """
    def __init__(self, player_id, player_name, player_data):
        self._player_id = player_id
        self._name = player_name
        self._goals = None
        self._assists = None
        self._points = None
        self._plus_minus = None
        self._penalties_in_minutes = None
        self._even_strength_goals = None
        self._power_play_goals = None
        self._short_handed_goals = None
        self._game_winning_goals = None
        self._even_strength_assists = None
        self._power_play_assists = None
        self._short_handed_assists = None
        self._shots_on_goal = None
        self._shooting_percentage = None
        self._time_on_ice = None
        self._blocks_at_even_strength = None
        self._hits_at_even_strength = None
        # Possession Metrics
        self._corsi_for_percentage = None
        self._relative_corsi_for_percentage = None
        self._offensive_zone_start_percentage = None
        # Goalie Metrics
        self._goals_against = None
        self._shots_against = None
        self._saves = None
        self._save_percentage = None
        self._shutouts = None

        self._parse_player_data(player_data)

    def _parse_value(self, stats, field):
        """
        Pull the specified value from the HTML contents.

        Given a field, find the corresponding HTML tag for that field and parse
        its value before returning the value as a string.

        Parameters
        ----------
        stats : PyQuery object
            A PyQuery object containing all stats in HTML format for a
            particular player.
        field : string
            A string of the field to parse from the HTML.

        Returns
        -------
        string
            Returns the desired value as a string.
        """
        value = utils._parse_field(PLAYER_SCHEME, stats, field)
        if not value and field in BOXSCORE_RETRY:
            value = utils._parse_field(BOXSCORE_RETRY, stats, field)
        return value

    def _parse_player_data(self, player_data):
        """
        Parse all player information and set attributes.

        Iterate through each class attribute to parse the data from the HTML
        page and set the attribute value with the result.

        Parameters
        ----------
        player_data : dictionary or string
            If this class is inherited from the ``Player`` class, player_data
            will be a dictionary where each key is a string representing the
            season and each value contains the HTML data as a string. If this
            class is inherited from the ``BoxscorePlayer`` class, player_data
            will be a string representing the player's game statistics in HTML
            format.
        """
        for field in self.__dict__:
            short_field = str(field)[1:]
            if short_field == 'player_id' or \
               short_field == 'index' or \
               short_field == 'most_recent_season' or \
               short_field == 'name' or \
               short_field == 'weight' or \
               short_field == 'height' or \
               short_field == 'season':
                continue
            field_stats = []
            if type(player_data) == dict:
                for year, data in player_data.items():
                    stats = pq(data['data'])
                    value = self._parse_value(stats, short_field)
                    field_stats.append(value)
            else:
                stats = pq(player_data)
                value = self._parse_value(stats, short_field)
                field_stats.append(value)
            setattr(self, field, field_stats)

    @property
    def player_id(self):
        """
        Returns a ``string`` of the player's ID on hockey-reference, such as
        'zettehe01' for Henrik Zetterberg.
        """
        return self._player_id

    @property
    def name(self):
        """
        Returns a ``string`` of the player's name, such as 'Henrik Zetterberg'.
        """
        return self._name

    @_int_property_decorator
    def goals(self):
        """
        Returns an ``int`` of the number of goals the player scored.
        """
        return self._goals

    @_int_property_decorator
    def assists(self):
        """
        Returns an ``int`` of the number of goals the player has assisted.
        """
        return self._assists

    @_int_property_decorator
    def points(self):
        """
        Returns an ``int`` of the number of points the player has gained.
        """
        return self._points

    @_int_property_decorator
    def plus_minus(self):
        """
        Returns an ``int`` representing the relative presence the player has on
        the outcome of the game.
        """
        return self._plus_minus

    @_int_property_decorator
    def penalties_in_minutes(self):
        """
        Returns an ``int`` of the number of minutes the player has served as a
        result of penalties.
        """
        return self._penalties_in_minutes

    @_int_property_decorator
    def even_strength_goals(self):
        """
        Returns an ``int`` of the number of goals the player has scored at even
        strength.
        """
        return self._even_strength_goals

    @_int_property_decorator
    def power_play_goals(self):
        """
        Returns an ``int`` of the number of goals the player has scored while
        on a power play.
        """
        return self._power_play_goals

    @_int_property_decorator
    def short_handed_goals(self):
        """
        Returns an ``int`` of the number of goals the player has scored while
        short handed.
        """
        return self._short_handed_goals

    @_int_property_decorator
    def game_winning_goals(self):
        """
        Returns an ``int`` of the number of game-winning goals the player has
        scored.
        """
        return self._game_winning_goals

    @_int_property_decorator
    def even_strength_assists(self):
        """
        Returns an ``int`` of the number of goals the player has assisted while
        at even strength.
        """
        return self._even_strength_assists

    @_int_property_decorator
    def power_play_assists(self):
        """
        Returns an ``int`` of the number of goals the player has assisted while
        on a power play.
        """
        return self._power_play_assists

    @_int_property_decorator
    def short_handed_assists(self):
        """
        Returns an ``int`` of the number of goals the player has assisted while
        short handed.
        """
        return self._short_handed_assists

    @_int_property_decorator
    def shots_on_goal(self):
        """
        Returns an ``int`` of the number of shots on goal the player has made.
        """
        return self._shots_on_goal

    @_float_property_decorator
    def shooting_percentage(self):
        """
        Returns a ``float`` of the percentage of the player's shots that go in
        the goal. Percentage ranges from 0-100.
        """
        return self._shooting_percentage

    @_int_property_decorator
    def blocks_at_even_strength(self):
        """
        Returns an ``int`` of the number of shots the player blocks while at
        even strength.
        """
        return self._blocks_at_even_strength

    @_int_property_decorator
    def hits_at_even_strength(self):
        """
        Returns an ``int`` of the number of hits the player makes while at even
        strength.
        """
        return self._hits_at_even_strength

    @_float_property_decorator
    def corsi_for_percentage(self):
        """
        Returns a ``float`` of the 'Corsi For' percentage, equal to corsi_for /
        (corsi_for + corsi_against). Percentage ranges from 0-100.
        """
        return self._corsi_for_percentage

    @_float_property_decorator
    def relative_corsi_for_percentage(self):
        """
        Returns a ``float`` of the player's relative 'Corsi For' percentage,
        equal to the difference between the player's on and off-ice Corsi For
        percentage.
        """
        return self._relative_corsi_for_percentage

    @_float_property_decorator
    def offensive_zone_start_percentage(self):
        """
        Returns a ``float`` of the percentage of faceoffs that occur in the
        offensive zone while the player is on ice. Percentage ranges from
        0-100.
        """
        return self._offensive_zone_start_percentage

    @_int_property_decorator
    def goals_against(self):
        """
        Returns an ``int`` of the number of goals the opponent scored on the
        player while in goal.
        """
        return self._goals_against

    @_int_property_decorator
    def shots_against(self):
        """
        Returns an ``int`` of the number of shots the opponent took while the
        player is in goal.
        """
        return self._shots_against

    @_int_property_decorator
    def saves(self):
        """
        Returns an ``int`` of the number of shots the player has saved while in
        goal.
        """
        return self._saves

    @_float_property_decorator
    def save_percentage(self):
        """
        Returns a ``float`` of the percentage of shots the player has saved.
        Percentage ranges from 0-1.
        """
        return self._save_percentage

    @_int_property_decorator
    def shutouts(self):
        """
        Returns an ``int`` of the number of shutouts the player has registered
        while in goal.
        """
        return self._shutouts
