#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Dota 2 API wrapper and parser in Python"""

__author__ = "Joshua Duffy, Evaldo Bratti"
__date__ = "29/10/2014"
__version__ = "1.3.3"
__licence__ = "GPL"

import json
import collections

try:
    from urllib import urlencode
except ImportError:
    from urllib.parse import urlencode

import os
import requests

from .src import urls, exceptions, response, parse


class Initialise(object):
    """When calling this you need to provide the ``api_key``
    You can also specify a ``language``

    :param api_key: (str) string with the ``api key``
    :param logging: (bool, optional) set this to True for logging output
    :param raw_mode: (bool, optional) get the raw data from dota2 API without parsing it into human-readable string
    """

    def __init__(self, api_key=None, executor=None, language=None, logging=None, raw_mode=None):
        if api_key:
            self.api_key = api_key
        elif 'D2_API_KEY' in os.environ:
            self.api_key = os.environ['D2_API_KEY']
        else:
            raise exceptions.APIAuthenticationError()

        if not language:
            self.language = "en_us"
        else:
            self.language = language

        if not executor:
            self.executor = requests.get
        else:
            self.executor = executor

        if logging:
            self.logger = _setup_logger()
        else:
            self.logger = None

        if raw_mode:
            self.raw_mode = True
        else:
            self.raw_mode = False

        self.__format = "json"

    def set_raw_mode(self, raw_mode):
        self.raw_mode = raw_mode

    def get_match_history(self, account_id=None, **kwargs):
        """Returns a dictionary containing a list of the most recent Dota matches

        :param account_id: (int, optional)
        :param hero_id: (int, optional)
        :param game_mode: (int, optional) see ``ref/modes.json``
        :param skill: (int, optional) see ``ref/skill.json``
        :param min_players: (int, optional) only return matches with minimum
            amount of players
        :param league_id: (int, optional) for ids use ``get_league_listing()``
        :param start_at_match_id: (int, optional) start at matches equal to or
            older than this match id
        :param matches_requested: (int, optional) defaults to ``100``
        :param tournament_games_only: (str, optional) limit results to
            tournament matches only
        :return: dictionary of matches, see :doc:`responses </responses>`
        """
        if 'account_id' not in kwargs:
            kwargs['account_id'] = account_id
        url = self.__build_url(urls.GET_MATCH_HISTORY, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_match_history_by_seq_num(self, start_at_match_seq_num=None, **kwargs):
        """Returns a dictionary containing a list of Dota matches in the order they were recorded

        :param start_at_match_seq_num: (int, optional) start at matches equal to or
            older than this match id
        :param matches_requested: (int, optional) defaults to ``100``
        :return: dictionary of matches, see :doc:`responses </responses>`
        """
        if 'start_at_match_seq_num' not in kwargs:
            kwargs['start_at_match_seq_num'] = start_at_match_seq_num
        url = self.__build_url(urls.GET_MATCH_HISTORY_BY_SEQ_NUM, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_match_details(self, match_id=None, **kwargs):
        """Returns a dictionary containing the details for a Dota 2 match

        :param match_id: (int, optional)
        :return: dictionary of matches, see :doc:`responses </responses>`
        """
        if 'match_id' not in kwargs:
            kwargs['match_id'] = match_id
        url = self.__build_url(urls.GET_MATCH_DETAILS, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_league_listing(self):
        """Returns a dictionary containing a list of all ticketed leagues

        :return: dictionary of ticketed leagues, see :doc:`responses </responses>`
        """
        url = self.__build_url(urls.GET_LEAGUE_LISTING)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_live_league_games(self):
        """Returns a dictionary containing a list of ticked games in progress

        :return: dictionary of live games, see :doc:`responses </responses>`
        """
        url = self.__build_url(urls.GET_LIVE_LEAGUE_GAMES)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_team_info_by_team_id(self, start_at_team_id=None, **kwargs):
        """Returns a dictionary containing a in-game teams

        :param start_at_team_id: (int, optional)
        :param teams_requested: (int, optional)
        :return: dictionary of teams, see :doc:`responses </responses>`
        """
        if 'start_at_team_id' not in kwargs:
            kwargs['start_at_team_id'] = start_at_team_id
        url = self.__build_url(urls.GET_TEAM_INFO_BY_TEAM_ID, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_player_summaries(self, steamids=None, **kwargs):
        """Returns a dictionary containing a player summaries

        :param steamids: (list) list of ``32-bit`` or ``64-bit`` steam ids, notice
                                that api will convert if ``32-bit`` are given
        :return: dictionary of player summaries, see :doc:`responses </responses>`
        """
        if not isinstance(steamids, collections.Iterable):
            steamids = [steamids]

        base64_ids = list(map(convert_to_64_bit, filter(lambda x: x is not None, steamids)))

        if 'steamids' not in kwargs:
            kwargs['steamids'] = base64_ids
        url = self.__build_url(urls.GET_PLAYER_SUMMARIES, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_heroes(self, **kwargs):
        """Returns a dictionary of in-game heroes, used to parse ids into localised names

        :return: dictionary of heroes, see :doc:`responses </responses>`
        """
        url = self.__build_url(urls.GET_HEROES, language=self.language, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_game_items(self, **kwargs):
        """Returns a dictionary of in-game items, used to parse ids into localised names

        :return: dictionary of items, see :doc:`responses </responses>`
        """
        url = self.__build_url(urls.GET_GAME_ITEMS, language=self.language, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_tournament_prize_pool(self, leagueid=None, **kwargs):
        """Returns a dictionary that includes community funded tournament prize pools

        :param leagueid: (int, optional)
        :return: dictionary of prize pools, see :doc:`responses </responses>`
        """
        if 'leagueid' not in kwargs:
            kwargs['leagueid'] = leagueid
        url = self.__build_url(urls.GET_TOURNAMENT_PRIZE_POOL, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def get_top_live_games(self, partner='', **kwargs):
        """Returns a dictionary that includes top MMR live games

        :param partner: (int, optional)
        :return: dictionary of prize pools, see :doc:`responses </responses>`
        """
        if 'partner' not in kwargs:
            kwargs['partner'] = partner
        url = self.__build_url(urls.GET_TOP_LIVE_GAME, **kwargs)
        req = self.executor(url)
        if self.logger:
            self.logger.info('URL: {0}'.format(url))
        if not self.__check_http_err(req.status_code):
            return response.build(req, url, self.raw_mode)

    def update_game_items(self):
        """
        Update the item reference data via the API
        """
        _save_dict_to_file(self.get_game_items(), "items.json")

    def update_heroes(self):
        """
        Update the hero reference data via the API
        """
        _save_dict_to_file(self.get_heroes(), "heroes.json")

    def __build_url(self, api_call, **kwargs):
        """Builds the api query"""
        kwargs['key'] = self.api_key
        if 'language' not in kwargs:
            kwargs['language'] = self.language
        if 'format' not in kwargs:
            kwargs['format'] = self.__format
        api_query = urlencode(kwargs)

        return "{0}{1}?{2}".format(urls.BASE_URL,
                                   api_call,
                                   api_query)

    def __check_http_err(self, status_code):
        """Raises an exception if we get a http error"""
        if status_code == 403:
            raise exceptions.APIAuthenticationError(self.api_key)
        elif status_code == 503:
            raise exceptions.APITimeoutError()
        else:
            return False


def convert_to_64_bit(number):
    min64b = 76561197960265728
    if number < min64b:
        return number + min64b
    return number


def _setup_logger():
    import logging
    logging.basicConfig(level=logging.NOTSET)  # Will log all
    return logging.getLogger(__name__)


def _save_dict_to_file(json_dict, file_name):
    out_file = open(parse.load_json_file(file_name), "w")
    json.dump(json_dict, out_file, indent=4)
    out_file.close()