#!/usr/bin/env python

# Some of this is from here:
# https://github.com/bvanheu/pytoutv/blob/master/toutvcli/app.py

# Copyright (c) 2012, Benjamin Vanheuverzwijn <bvanheu@gmail.com>
# Copyright (c) 2014, Philippe Proulx <eepp.ca>
# All rights reserved.
#
# Thanks to Marc-Etienne M. Leveille
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of pytoutv nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL Benjamin Vanheuverzwijn OR Philippe Proulx
# BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import datetime
import distutils.version
import json
import locale
import os
import os.path
import sys

import toutv.dl
import toutv.exceptions
import toutv.transport
import toutvcli.app


# Bitrates less than this value will be considered more or less the same when downloading
MAX_BITRATE_DIFFERENCE = 2000
# The maximum number of times an emission will be listed as new
MAX_NEW_COUNT = 3
# The maximum number of times per operation timeout errors will be ignored
MAX_TIMEOUTS = 10


def retry_function(function, *args, **kwargs):
    for n in range(MAX_TIMEOUTS):
        try:
            result = function(*args, **kwargs)
            break
        except toutv.exceptions.RequestTimeoutError:
            pass

        if n == MAX_TIMEOUTS - 1:
            sys.exit('Error: max timeout attempts reached\n')

    return result


class App(toutvcli.app.App):
    # Override
    def run(self):
        locale.setlocale(locale.LC_ALL, '')

        # Override help messages
        for action in self._argparser._actions[1]._choices_actions:
            if action.dest == 'list':
                action.help = 'List new emissions or episodes since last run'

        for action in self._argparser._actions[1].choices['list']._actions:
            if action.dest == 'all':
                action.help = 'List all emissions or episodes'

        super().run()

    # Override
    def _build_toutv_client(self, *args, **kwargs):
        print('Please wait...\n')

        client = super()._build_toutv_client(*args, **kwargs)
        client._transport = JsonTransport()

        return client

    # Override
    def _command_fetch(self, *args, **kwargs):
        self._store = DataStore()

        super()._command_fetch(*args, **kwargs)

        self._store.write()

    # Override
    def _fetch_emission_episodes(self, emission, output_dir, bitrate, quality,
                                 overwrite):
        episodes = self._toutvclient.get_emission_episodes(emission)

        if not episodes:
            title = emission.get_title()
            print('No episodes available for emission "{}"'.format(title))
            return

        # Sort episodes by season and episode so they're downloaded in order
        def episode_sort_func(episode_id):
            return distutils.version.LooseVersion(episodes[episode_id].SeasonAndEpisode)

        episode_ids = list(episodes.keys())
        episode_ids.sort(key=episode_sort_func)

        for episode_id in episode_ids:
            episode = episodes[episode_id]
            title = episode.get_title()

            if self._stop:
                raise toutv.dl.CancelledByUserError()
            try:
                self._fetch_episode(episode, output_dir, bitrate, quality,
                                    overwrite)
            except toutv.exceptions.RequestTimeoutError:
                tmpl = 'Error: cannot fetch "{}": request timeout'
                print(tmpl.format(title), file=sys.stderr)
            except toutv.exceptions.UnexpectedHttpStatusCodeError:
                tmpl = 'Error: cannot fetch "{}": unexpected HTTP status code'
                print(tmpl.format(title), file=sys.stderr)
            except toutv.exceptions.NetworkError as e:
                tmpl = 'Error: cannot fetch "{}": {}'
                print(tmpl.format(title, e), file=sys.stderr)
            except toutv.dl.FileExistsError as e:
                tmpl = 'Error: cannot fetch "{}": destination file exists'
                print(tmpl.format(title), file=sys.stderr)
            except toutv.dl.CancelledByUserError as e:
                raise e
            except toutv.dl.DownloadError as e:
                tmpl = 'Error: cannot fetch "{}": {}'
                print(tmpl.format(title, e), file=sys.stderr)
            except Exception as e:
                tmpl = 'Error: cannot fetch "{}": {}'
                print(tmpl.format(title, e), file=sys.stderr)

    # Override
    def _fetch_episode(self, episode, output_dir, bitrate, quality, overwrite):
        def remove_special_chars(s):
            # http://superuser.com/a/358861/93066
            special_chars = ['\\', '/', ':', '*', '?', '"', '<', '>', '|']
            for c in special_chars:
                s = s.replace(c, '')

            return s

        def download_episode():
            nonlocal store_episode

            sys.stdout.write('\n')

            # Create downloader
            opu = self._on_dl_progress_update
            self._dl = Downloader(episode, bitrate=bitrate,
                                  output_dir=output_dir,
                                  on_dl_start=self._on_dl_start,
                                  on_progress_update=opu,
                                  overwrite=overwrite)

            # Start download
            self._dl.download()

            sys.stdout.flush()

            # Rename downloaded file
            filepath = os.path.join(output_dir, self._dl.filename)
            if os.path.isfile(filepath):
                # Base new filename on whether it's a film/documentary or not
                if episode._emission.Genre.Title == 'Films et documentaires':
                    new_filepath = os.path.join(
                        output_dir,
                        '{} ({}).mp4'.format(
                            remove_special_chars(episode.Title),
                            remove_special_chars(episode.Year),
                        )
                    )

                else:
                    new_filepath = os.path.join(
                        output_dir,