# -*- coding: utf-8 -*-
"""
A Kodi addon/skin for NFL Game Pass
"""
import calendar
from datetime import datetime
import os
import sys
import time
from traceback import format_exc

import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs

from resources.lib.pigskin import pigskin

addon = xbmcaddon.Addon()
language = addon.getLocalizedString
ADDON_PATH = xbmc.translatePath(addon.getAddonInfo('path'))
ADDON_PROFILE = xbmc.translatePath(addon.getAddonInfo('profile'))
LOGGING_PREFIX = '[%s-%s]' % (addon.getAddonInfo('id'), addon.getAddonInfo('version'))

if not xbmcvfs.exists(ADDON_PROFILE):
    xbmcvfs.mkdir(ADDON_PROFILE)

cookie_file = os.path.join(ADDON_PROFILE, 'cookie_file')
username = addon.getSetting('email')
password = addon.getSetting('password')

proxy_config = None
if addon.getSetting('proxy_enabled') == 'true':
    proxy_config = {
        'scheme': addon.getSetting('proxy_scheme'),
        'host': addon.getSetting('proxy_host'),
        'port': addon.getSetting('proxy_port'),
        'auth': {
            'username': addon.getSetting('proxy_username'),
            'password': addon.getSetting('proxy_password'),
        },
    }
    if addon.getSetting('proxy_auth') == 'false':
        proxy_config['auth'] = None

gp = pigskin(proxy_config, cookie_file=cookie_file, debug=True)


def addon_log(string):
    msg = '%s: %s' % (LOGGING_PREFIX, string)
    xbmc.log(msg=msg, level=xbmc.LOGDEBUG)


class GamepassGUI(xbmcgui.WindowXML):
    def __init__(self, *args, **kwargs):
        self.season_list = None
        self.season_items = []
        self.clicked_season = -1
        self.weeks_list = None
        self.weeks_items = []
        self.clicked_week = -1
        self.games_list = None
        self.games_items = []
        self.clicked_game = -1
        self.live_list = None
        self.live_items = []
        self.selected_season = ''
        self.selected_week = ''
        self.main_selection = None
        self.player = None
        self.list_refill = False
        self.focusId = 100
        self.seasons_and_weeks = gp.get_seasons_and_weeks()

        xbmcgui.WindowXML.__init__(self, *args, **kwargs)
        self.action_previous_menu = (9, 10, 92, 216, 247, 257, 275, 61467, 61448)

    def onInit(self):  # pylint: disable=invalid-name
        self.window = xbmcgui.Window(xbmcgui.getCurrentWindowId())
        self.season_list = self.window.getControl(210)
        self.weeks_list = self.window.getControl(220)
        self.games_list = self.window.getControl(230)
        self.live_list = self.window.getControl(240)

        if gp.subscription == 'domestic':
            self.window.setProperty('domestic', 'true')

        if self.list_refill:
            self.season_list.reset()
            self.season_list.addItems(self.season_items)
            self.weeks_list.reset()
            self.weeks_list.addItems(self.weeks_items)
            self.games_list.reset()
            self.games_list.addItems(self.games_items)
            self.live_list.reset()
            self.live_list.addItems(self.live_items)
        else:
            self.window.setProperty('NW_clicked', 'false')
            self.window.setProperty('GP_clicked', 'false')

        xbmc.executebuiltin("Dialog.Close(busydialog)")

        try:
            self.setFocus(self.window.getControl(self.focusId))
        except:
            addon_log('Focus not possible: %s' % self.focusId)

    def coloring(self, text, meaning):
        """Return the text wrapped in appropriate color markup."""
        if meaning == "disabled":
            color = "FF000000"
        elif meaning == "disabled-info":
            color = "FF111111"
        colored_text = "[COLOR=%s]%s[/COLOR]" % (color, text)
        return colored_text

    def display_seasons(self):
        """List seasons"""
        self.season_items = []
        for season in sorted(self.seasons_and_weeks.keys(), reverse=True):
            listitem = xbmcgui.ListItem(season)
            self.season_items.append(listitem)

        self.season_list.addItems(self.season_items)

    def display_nfln_seasons(self):
        """List seasons"""
        self.season_items = []
        # sort so that years are first (descending) followed by text
        for season in sorted(gp.nflnSeasons, key=lambda x: (x[0].isdigit(), x), reverse=True):
            listitem = xbmcgui.ListItem(season)
            self.season_items.append(listitem)

        self.season_list.addItems(self.season_items)

    def display_nfl_network_archive(self):
        """List shows for a given season"""
        self.weeks_items = []
        shows = gp.get_shows(self.selected_season)
        for show_name in shows:
            listitem = xbmcgui.ListItem(show_name)
            self.weeks_items.append(listitem)

        self.weeks_list.addItems(self.weeks_items)

    def display_weeks_games(self):
        """Show games for a given season/week"""
        self.games_items = []
        games = gp.get_weeks_games(self.selected_season, self.selected_week)

        date_time_format = '%Y-%m-%dT%H:%M:%S.000'
        if games:
            for game in games:
                if game['homeTeam']['id'] is None:  # sometimes the first item is empty
                    continue

                game_info = ''
                game_id = game['id']
                game_versions = []
                isPlayable = 'true'
                isBlackedOut = 'false'
                home_team = game['homeTeam']
                away_team = game['awayTeam']

                # Pro-bowl doesn't have a team "name" only a team city, which is the
                # team name... wtf
                if game['homeTeam']['name'] is None:
                    game_name_shrt = '[B]%s[/B] at [B]%s[/B]' % (away_team['city'], home_team['city'])
                    game_name_full = game_name_shrt
                else:
                    game_name_shrt = '[B]%s[/B] at [B]%s[/B]' % (away_team['name'], home_team['name'])
                    game_name_full = '[B]%s %s[/B] at [B]%s %s[/B]' % (away_team['city'], away_team['name'], home_team['city'], home_team['name'])

                for key, value in {'Condensed': 'condensedId', 'Full': 'programId'}.items():
                    if value in game:
                        game_versions.append(key)

                if 'isLive' in game:
                    game_versions.append('Live')

                if 'gameEndTimeGMT' in game:
                    # Show game duration only if user wants to see it
                    if addon.getSetting('hide_game_length') == 'false':
                        try:
                            start_time = datetime(*(time.strptime(game['gameTimeGMT'], date_time_format)[0:6]))
                            end_time = datetime(*(time.strptime(game['gameEndTimeGMT'], date_time_format)[0:6]))
                            game_info = 'Final [CR] Duration: %s' % time.strftime('%H:%M:%S', time.gmtime((end_time - start_time).seconds))
                        except:
                            addon_log(format_exc())
                            if 'result' in game:
                                game_info = 'Final'
                    else:
                        game_info = 'Final'
                else:
                    if 'isLive' in game:
                        game_info = '» Live «'

                    try:
                        if addon.getSetting('local_tz') == '0':  # don't localize
                            game_datetime = datetime(*(time.strptime(game['date'], date_time_format)[0:6]))
                            game_info = game_datetime.strftime('%A, %b %d - %I:%M %p')
                        else:
                            game_gmt = time.strptime(game['gameTimeGMT'], date_time_format)
                            secs = calendar.timegm(game_gmt)
                            game_local = time.localtime(secs)

                            if addon.getSetting('local_tz') == '1':  # localize and use 12-hour clock
                                game_info = time.strftime('%A, %b %d - %I:%M %p', game_local)
                            else:  # localize and use 24-hour clock
                                game_info = time.strftime('%A, %b %d - %H:%M', game_local)
                    except:  # all else fails, just use their raw date value
                        game_datetime = game['date'].split('T')
                        game_info = game_datetime[0] + '[CR]' + game_datetime[1].split('.')[0] + ' ET'

                    if 'hasProgram' not in game:  # if subscription doesn't allow
                        isPlayable = 'false'
                        game_name_full = self.coloring(game_name_full, "disabled")
                        game_name_shrt = self.coloring(game_name_shrt, "disabled")
                        game_info = self.coloring(game_info, "disabled-info")

                    try:
                        if game['blocked'] == 'true':
                            isPlayable = 'false'
                            isBlackedOut = 'true'
                            game_info = '» Blacked Out «'
                            game_name_full = self.coloring(game_name_full, "disabled")
                            game_name_shrt = self.coloring(game_name_shrt, "disabled")
                            game_info = self.coloring(game_info, "disabled-info")
                    except KeyError:
                        pass

                listitem = xbmcgui.ListItem(game_name_shrt, game_name_full)
                listitem.setProperty('away_thumb', 'http://i.nflcdn.com/static/site/7.4/img/logos/teams-matte-144x96/%s.png' % away_team['id'])
                listitem.setProperty('home_thumb', 'http://i.nflcdn.com/static/site/7.4/img/logos/teams-matte-144x96/%s.png' % home_team['id'])
                listitem.setProperty('game_info', game_info)
                listitem.setProperty('is_game', 'true')
                listitem.setProperty('is_show', 'false')
                listitem.setProperty('isPlayable', isPlayable)
                listitem.setProperty('isBlackedOut', isBlackedOut)
                listitem.setProperty('game_id', game_id)
                listitem.setProperty('game_date', game['date'].split('T')[0])
                listitem.setProperty('game_versions', ' '.join(game_versions))
                self.games_items.append(listitem)

            self.games_list.addItems(self.games_items)

        else:
            dialog = xbmcgui.Dialog()
            dialog.ok(language(30021), language(30046))

    def display_seasons_weeks(self):
        """List weeks for a given season"""
        weeks = self.seasons_and_weeks[self.selected_season]

        for week_code, week in sorted(weeks.iteritems()):
            future = 'false'
            try:
                # convert EST to GMT by adding 6 hours
                week_date = week['@start'] + ' 06:00'
                # avoid super annoying bug http://forum.kodi.tv/showthread.php?tid=112916
                week_datetime = datetime(*(time.strptime(week_date, '%Y%m%d %H:%M')[0:6]))
                now_datetime = datetime.utcnow()

                if week_datetime > now_datetime:
                    future = 'true'
            except KeyError:  # some old seasons don't provide week dates
                pass

            listitem = xbmcgui.ListItem(week['@label'].title())
            listitem.setProperty('week_code', week_code)
            listitem.setProperty('future', future)
            self.weeks_items.append(listitem)
        self.weeks_list.addItems(self.weeks_items)

    def display_shows_episodes(self, show_name, season):
        """Show episodes for a given season/show"""
        self.games_items = []
        items = gp.get_shows_episodes(show_name, season)

        for i in items:
            try:
                listitem = xbmcgui.ListItem('[B]%s[/B]' % show_name)
                listitem.setProperty('game_info', i['name'])
                listitem.setProperty('away_thumb', gp.image_url + i['image'])
                listitem.setProperty('url', i['publishPoint'])
                listitem.setProperty('id', i['id'])
                listitem.setProperty('type', i['type'])
                listitem.setProperty('is_game', 'false')
                listitem.setProperty('is_show', 'true')
                listitem.setProperty('isPlayable', 'true')
                self.games_items.append(listitem)
            except:
                addon_log('Exception adding archive directory: %s' % format_exc())
                addon_log('Directory name: %s' % i['name'])
        self.games_list.addItems(self.games_items)

    def play_url(self, url):
        xbmc.executebuiltin("Dialog.Close(busydialog)")
        self.list_refill = True
        xbmc.Player().play(url)

    def init(self, level):
        if level == 'season':
            self.weeks_items = []
            self.weeks_list.reset()
            self.games_list.reset()
            self.clicked_week = -1
            self.clicked_game = -1

            if self.clicked_season > -1:  # unset previously selected season
                self.season_list.getListItem(self.clicked_season).setProperty('clicked', 'false')

            self.season_list.getSelectedItem().setProperty('clicked', 'true')
            self.clicked_season = self.season_list.getSelectedPosition()
        elif level == 'week/show':
            self.games_list.reset()
            self.clicked_game = -1

            if self.clicked_week > -1:  # unset previously selected week/show
                self.weeks_list.getListItem(self.clicked_week).setProperty('clicked', 'false')

            self.weeks_list.getSelectedItem().setProperty('clicked', 'true')
            self.clicked_week = self.weeks_list.getSelectedPosition()
        elif level == 'game/episode':
            if self.clicked_game > -1:  # unset previously selected game/episode
                self.games_list.getListItem(self.clicked_game).setProperty('clicked', 'false')

            self.games_list.getSelectedItem().setProperty('clicked', 'true')
            self.clicked_game = self.games_list.getSelectedPosition()

    def ask_bitrate(self, bitrates):
        """Presents a dialog for user to select from a list of bitrates.
        Returns the value of the selected bitrate.
        """
        options = []
        for bitrate in bitrates:
            options.append(bitrate + ' Kbps')
        dialog = xbmcgui.Dialog()
        xbmc.executebuiltin("Dialog.Close(busydialog)")
        ret = dialog.select(language(30003), options)
        if ret > -1:
            return bitrates[ret]
        else:
            return None

    def select_bitrate(self, manifest_bitrates=None):
        """Returns a bitrate, while honoring the user's /preference/."""
        bitrate_setting = int(addon.getSetting('preferred_bitrate'))
        bitrate_values = ['4500', '3000', '2400', '1600', '1200', '800', '400']

        highest = False
        preferred_bitrate = None
        if bitrate_setting == 0:  # 0 === "highest"
            highest = True
        elif 0 < bitrate_setting and bitrate_setting < 8:  # a specific bitrate. '8' === "ask"
            preferred_bitrate = bitrate_values[bitrate_setting - 1]

        if manifest_bitrates:
            manifest_bitrates.sort(key=int, reverse=True)
            if highest:
                return manifest_bitrates[0]
            elif preferred_bitrate and preferred_bitrate in manifest_bitrates:
                return preferred_bitrate
            else:  # ask user
                return self.ask_bitrate(manifest_bitrates)
        else:
            if highest:
                return bitrate_values[0]
            elif preferred_bitrate:
                return preferred_bitrate
            else:  # ask user
                return self.ask_bitrate(bitrate_values)

    def select_version(self, game_versions):
        """Returns a game version, while honoring the user's /preference/.
        Note: the full version is always available but not always the condensed.
        """
        preferred_version = int(addon.getSetting('preferred_game_version'))

        # user wants to be asked to select version
        if preferred_version == 2:
            versions = [language(30014)]
            if 'Condensed' in game_versions:
                versions.append(language(30015))
            if 'Coach' in game_versions:
                versions.append(language(30032))
            dialog = xbmcgui.Dialog()
            xbmc.executebuiltin("Dialog.Close(busydialog)")
            preferred_version = dialog.select(language(30016), versions)

        if preferred_version == 1 and 'Condensed' in game_versions:
            game_version = 'condensed'
        elif preferred_version == 2 and 'Coach' in game_versions:
            game_version = 'coach'
        else:
            game_version = 'archive'

        if preferred_version > -1:
            return game_version
        else:
            return None

    def onFocus(self, controlId):  # pylint: disable=invalid-name
        # save currently focused list
        if controlId in [210, 220, 230, 240]:
            self.focusId = controlId

    def onClick(self, controlId):  # pylint: disable=invalid-name
        try:
            xbmc.executebuiltin("ActivateWindow(busydialog)")
            if controlId in [110, 120, 130]:
                self.games_list.reset()
                self.weeks_list.reset()
                self.season_list.reset()
                self.live_list.reset()
                self.games_items = []
                self.weeks_items = []
                self.live_items = []
                self.clicked_game = -1
                self.clicked_week = -1
                self.clicked_season = -1

                if controlId in [110, 120]:
                    self.main_selection = 'GamePass'
                    self.window.setProperty('NW_clicked', 'false')
                    self.window.setProperty('GP_clicked', 'true')

                    # display games of current week for usability purposes
                    cur_s_w = gp.get_current_season_and_week()
                    self.selected_season = cur_s_w.keys()[0]
                    self.selected_week = cur_s_w.values()[0]
                    self.display_seasons()

                    try:
                        self.display_seasons_weeks()
                        self.display_weeks_games()
                    except:
                        addon_log('Error while reading seasons weeks and games')
                elif controlId == 130:
                    self.main_selection = 'NFL Network'
                    self.window.setProperty('NW_clicked', 'true')
                    self.window.setProperty('GP_clicked', 'false')

                    listitem = xbmcgui.ListItem('NFL Network - Live', 'NFL Network - Live')
                    self.live_items.append(listitem)

                    if gp.redzone_on_air():
                        listitem = xbmcgui.ListItem('NFL RedZone - Live', 'NFL RedZone - Live')
                        self.live_items.append(listitem)

                    self.live_list.addItems(self.live_items)
                    self.display_nfln_seasons()

                xbmc.executebuiltin("Dialog.Close(busydialog)")
                return

            if self.main_selection == 'GamePass':
                if controlId == 210:  # season is clicked
                    self.init('season')
                    self.selected_season = self.season_list.getSelectedItem().getLabel()

                    self.display_seasons_weeks()
                elif controlId == 220:  # week is clicked
                    self.init('week/show')
                    self.selected_week = self.weeks_list.getSelectedItem().getProperty('week_code')

                    self.display_weeks_games()
                elif controlId == 230:  # game is clicked
                    selectedGame = self.games_list.getSelectedItem()
                    if selectedGame.getProperty('isPlayable') == 'true':
                        self.init('game/episode')
                        game_id = selectedGame.getProperty('game_id')
                        game_versions = selectedGame.getProperty('game_versions')

                        if 'Live' in game_versions:
                            if 'Final' in selectedGame.getProperty('game_info'):
                                game_version = self.select_version(game_versions)
                                if game_version == 'archive':
                                    game_version = 'dvr'
                            else:
                                game_version = 'live'
                        else:
                            # Check for coaches film availability
                            if gp.check_for_coachestape(game_id, self.selected_season):
                                game_versions = game_versions + ' Coach'

                            game_version = self.select_version(game_versions)
                        if game_version:
                            if game_version == 'coach':
                                xbmc.executebuiltin("ActivateWindow(busydialog)")
                                coachesItems = []
                                game_date = selectedGame.getProperty('game_date').replace('-', '/')
                                self.playBackStop = False

                                play_stream = gp.get_coaches_url(game_id, game_date, 'dummy')
                                plays = gp.get_coaches_playIDs(game_id, self.selected_season)
                                for playID in sorted(plays, key=int):
                                    cf_url = str(play_stream).replace('dummy', playID)
                                    item = xbmcgui.ListItem(plays[playID])
                                    item.setProperty('url', cf_url)
                                    coachesItems.append(item)

                                self.list_refill = True
                                xbmc.executebuiltin("Dialog.Close(busydialog)")
                                coachGui = CoachesFilmGUI('script-gamepass-coach.xml', ADDON_PATH, plays=coachesItems)
                                coachGui.doModal()
                                del coachGui
                            else:
                                game_streams = gp.get_publishpoint_streams(game_id, 'game', game_version)
                                bitrate = self.select_bitrate(game_streams.keys())
                                if bitrate:
                                    game_url = game_streams[bitrate]
                                    self.play_url(game_url)

            elif self.main_selection == 'NFL Network':
                if controlId == 210:  # season is clicked
                    self.init('season')
                    self.selected_season = self.season_list.getSelectedItem().getLabel()

                    self.display_nfl_network_archive()
                elif controlId == 220:  # show is clicked
                    self.init('week/show')
                    show_name = self.weeks_list.getSelectedItem().getLabel()

                    self.display_shows_episodes(show_name, self.selected_season)
                elif controlId == 230:  # episode is clicked
                    self.init('game/episode')
                    video_id = self.games_list.getSelectedItem().getProperty('id')
                    video_streams = gp.get_publishpoint_streams(video_id, 'video')
                    if video_streams:
                        addon_log('Video-Streams: %s' % video_streams)
                        bitrate = self.select_bitrate(video_streams.keys())
                        if bitrate:
                            video_url = video_streams[bitrate]
                            self.play_url(video_url)
                    else:
                        dialog = xbmcgui.Dialog()
                        dialog.ok(language(30043), language(30045))
                elif controlId == 240:  # Live content (though not games)
                    show_name = self.live_list.getSelectedItem().getLabel()
                    if show_name == 'NFL RedZone - Live':
                        rz_live_streams = gp.get_publishpoint_streams('redzone')
                        if rz_live_streams:
                            bitrate = self.select_bitrate(rz_live_streams.keys())
                            if bitrate:
                                rz_live_url = rz_live_streams[bitrate]
                                self.play_url(rz_live_url)
                        else:
                            dialog = xbmcgui.Dialog()
                            dialog.ok(language(30043), language(30045))
                    elif show_name == 'NFL Network - Live':
                        nw_live_streams = gp.get_publishpoint_streams('nfl_network')
                        if nw_live_streams:
                            bitrate = self.select_bitrate(nw_live_streams.keys())
                            if bitrate:
                                nw_live_url = nw_live_streams[bitrate]
                                self.play_url(nw_live_url)
                        else:
                            dialog = xbmcgui.Dialog()
                            dialog.ok(language(30043), language(30045))
            xbmc.executebuiltin("Dialog.Close(busydialog)")
        except Exception:  # catch anything that might fail
            xbmc.executebuiltin("Dialog.Close(busydialog)")
            addon_log(format_exc())

            dialog = xbmcgui.Dialog()
            if self.main_selection == 'NFL Network' and controlId == 230:  # episode
                # inform that not all shows will work
                dialog.ok(language(30043), language(30044))
            else:
                # generic oops
                dialog.ok(language(30021), language(30024))


class CoachesFilmGUI(xbmcgui.WindowXML):
    def __init__(self, xmlFilename, scriptPath, plays, defaultSkin="Default", defaultRes="720p"):  # pylint: disable=invalid-name
        self.playsList = None
        self.playsItems = plays

        xbmcgui.WindowXML.__init__(self, xmlFilename, scriptPath, defaultSkin, defaultRes)
        self.action_previous_menu = (9, 10, 92, 216, 247, 257, 275, 61467, 61448)

    def onInit(self):  # pylint: disable=invalid-name
        self.window = xbmcgui.Window(xbmcgui.getCurrentWindowId())
        if addon.getSetting('coach_lite') == 'true':
            self.window.setProperty('coach_lite', 'true')

        self.playsList = self.window.getControl(110)
        self.window.getControl(99).setLabel(language(30032))
        self.playsList.addItems(self.playsItems)
        self.setFocus(self.playsList)
        url = self.playsList.getListItem(0).getProperty('url')
        xbmc.executebuiltin("Dialog.Close(busydialog)")
        xbmc.executebuiltin('PlayMedia(%s,False,1)' % url)

    def onClick(self, controlId):  # pylint: disable=invalid-name
        if controlId == 110:
            url = self.playsList.getSelectedItem().getProperty('url')
            xbmc.executebuiltin('PlayMedia(%s,False,1)' % url)

if __name__ == "__main__":
    addon_log('script starting')
    xbmc.executebuiltin("Dialog.Close(busydialog)")

    try:
        gp.login(username, password)
    except gp.LoginFailure as error:
        dialog = xbmcgui.Dialog()
        if error.value == 'Game Pass Domestic Blackout':
            addon_log('Game Pass Domestic is in blackout.')
            dialog.ok(language(30021),
                      language(30022))
        else:
            addon_log('login failed')
            dialog.ok(language(30021),
                      language(30023))
        sys.exit(0)
    except:
        addon_log(format_exc())
        dialog = xbmcgui.Dialog()
        dialog.ok('Epic Failure',
                  language(30024))
        sys.exit(0)

    gui = GamepassGUI('script-gamepass.xml', ADDON_PATH)
    gui.doModal()
    del gui

addon_log('script finished')
