#!usr/bin/env python3

import json
import sys
import time

import requests
from bs4 import BeautifulSoup
from slugify import slugify

_base_url = "https://www.tunefind.com/show/{}/{}/{}"
_youtube_url = "https://www.youtube.com/results?search_query={}"


try:
    def getSoup(_url, _name, _season="", _episode_link=""):
        """
        Returns the html-parsed-BeautifulSoup object for the given URL.

        Parameters:
            _url: String: the http(s) link to get html from
            _name: String: name of the show|movie
            _season: Integer: season number of the show|movie
            _episode_link: Integer: Unique number for the episode to form its hyperlink.
        """
        if _season:
            _season = 'season-' + str(_season)

        try:
            r = requests.get(_url.format(_name, _season, _episode_link))
        except Exception:
            sys.exit("\n CONNECTION ERROR: Check your Internet connection again.\n")

        return BeautifulSoup(r.text, 'html.parser').get_text()


    def checkLimit(_entered, _limit):
        """
        Returns True if entered season/episode number is within the limit for the show. False otherwise.

        Parameters:
            _entered: String: the string entered by user when asked for season/episode number.
            _limit: Integer: Maximum number of seasons/episodes in the show/season.
        """
        _num = _entered.strip()
        if _num.lower() == 'exit' or _num.lower() == 'quit':
            sys.exit()
        try:
            _num = int(_num)
            if isinstance(_num, int):
                if 0 <= _num <= _limit:
                    return True
        except ValueError:
            return False
        
        return False


    def getYoutubeLink(_song, _artist):
        """
        Returns the link to YouTube search page for the artist's song
        """
        term = _song if len(_song) > 50 else _song + " " + _artist
        query = slugify(term).replace('-', '+')
        return _youtube_url.format(query)


    def main():
        # show level
        _show_cont = 'y'
        while _show_cont.lower() == 'y':
            while True:
                _entered_name = input(" Name of the Show: ").strip()
                if _entered_name:
                    break
            print(" \n Searching...")
            _input_name = slugify(_entered_name)
            _soup = getSoup(_base_url, _input_name)
            _js = "{" + _soup[_soup.find('"alertMessage"'): _soup.find('}};') + 2]
            # main source of data
            _json = json.loads(_js)

            _season_cont = 'n'
            _episode_cont = 'n'

            try:
                _json_show = _json['show']['result']
                _show_name = _json_show['show']['name']
                _season_id_list = _json_show['seasons']
                _season_count = len(_season_id_list)

                print("\n {}\n Total Season(s): {}".format(_show_name, _season_count))
                _season_song_count_list = [0] * _season_count
                _season_cont = 'y'

                # each season's details
                for season_ct, _season_id in enumerate(_season_id_list):
                    _season_id = str(_season_id)
                    _season_name = _json['entities']['seasons'][_season_id]['group_name']
                    _season_num = _json['entities']['seasons'][_season_id]['group_sequence']
                    _season_episode_count = _json['entities']['seasons'][_season_id]['episodes_count']
                    _season_song_count_list[season_ct] = _json['entities']['seasons'][_season_id]['songs_count']
                    _season_aired = time.strftime('%b %Y', time.localtime(_json['entities']['seasons'][_season_id]['air_date_start'])) + \
                                    " to " + time.strftime('%b %Y', time.localtime(_json['entities']['seasons'][_season_id]['air_date_end']))

                    print("\n {}    Episode(s): {}    Aired: {}\n Song(s): {}".format(_season_name, _season_episode_count,
                                                                                      _season_aired, _season_song_count_list[season_ct]))
                if _season_count > 1:
                    print("\n Total Song(s) in {}: {}".format(_show_name, sum(_season_song_count_list)))
            except KeyError:
                print(" \n {} was not found.\n Type 'tvsongs help' for help ".format(_entered_name))

            # season level
            while _season_cont.lower() == 'y':
                while True:
                    _season_wanted = input("\n Choose Season of {}. 1 to {} (0 for all): ".format(_show_name, _season_count))
                    if checkLimit(_season_wanted, _season_count):
                        _season_wanted = int(_season_wanted)
                        break
                _season_start = 1 if _season_wanted == 0 else _season_wanted
                _season_end = _season_count + 1 if _season_wanted == 0 else _season_wanted + 1

                for curr_season in range(_season_start, _season_end):
                    if _season_song_count_list[curr_season - 1] == 0:
                        print("\n No song(s) in {} S{}".format(_show_name, curr_season))
                        continue
                    _season_soup = getSoup(_base_url, _input_name, curr_season)
                    _season_js = "{" + _season_soup[_season_soup.find('"alertMessage"'): _season_soup.find('}};') + 2]
                    _season_json = json.loads(_season_js)

                    _episode_id_list = _season_json['season']['episodes']
                    _episode_count = len(_episode_id_list)
                    _episode_song_count_list = [0] * _episode_count
                    _episode_cont = 'y'

                    for episode_ct, _episode_id in enumerate(_episode_id_list):
                        _episode_id = str(_episode_id)
                        _episode_name = _season_json['entities']['episodes'][_episode_id]['name']
                        _episode_num = _season_json['entities']['episodes'][_episode_id]['number']
                        _episode_aired = _season_json['entities']['episodes'][_episode_id]['airdate_day'] + \
                                        " " + _season_json['entities']['episodes'][_episode_id]['airdate_month_short'] + \
                                        " " + _season_json['entities']['episodes'][_episode_id]['airdate_year']
                        _episode_song_count_list[episode_ct] = _season_json['entities']['episodes'][_episode_id]['song_count']
                        _episode_description = _season_json['entities']['episodes'][_episode_id]['episode_description']

                        _episode_description = _episode_description if _episode_description else "No description"

                        if _season_wanted:
                            print("\n S{}.E{} {}    Aired: {}    Song(s): {}\n {}".format(curr_season, _episode_num,
                                                                                          _episode_name, _episode_aired,
                                                                                          _episode_song_count_list[episode_ct],
                                                                                          _episode_description))

                    # episode level
                    while _episode_cont.lower() == 'y':
                        if _season_wanted:
                            while True:
                                _episode_wanted = input("\n Choose Episode of {} S{}. 1 to {} (0 for all): ".format(_show_name,
                                                                                                                    curr_season,
                                                                                                                    _episode_count))
                                if checkLimit(_episode_wanted, _episode_count):
                                    _episode_wanted = int(_episode_wanted)
                                    break
                        else:
                            _episode_wanted = 0
                        _episode_start = 1 if _episode_wanted == 0 else _episode_wanted
                        _episode_end = _episode_count + 1 if _episode_wanted == 0 else _episode_wanted + 1

                        for curr_episode in range(_episode_start, _episode_end):
                            if _episode_song_count_list[curr_episode - 1] == 0:
                                print("\n No song(s) in {} S{}.E{}".format(_show_name, curr_season, curr_episode))
                                continue
                            _episode_soup = getSoup(_base_url, _input_name, curr_season, _episode_id_list[curr_episode - 1])
                            _episode_js = "{" + _episode_soup[_episode_soup.find('"alertMessage"'): _episode_soup.find('}};') + 2]
                            _episode_json = json.loads(_episode_js)
                            _episode_song_big = _episode_json['entities']['songs']
                            _episode_song_true = _episode_json['entities']['song_event']
                            _episode_artist = _episode_json['entities']['artists']

                            print("\n\n {}\n S{}.E{} - {} Song(s)".format(_show_name, curr_season, curr_episode,
                                                                          _episode_song_count_list[curr_episode - 1]))
                            # song level
                            for song_ct, curr_song in enumerate(_episode_song_true):
                                _song_id = str(_episode_song_true[curr_song]['song_id'])
                                _song_name = _episode_song_big[_song_id]['name']
                                _song_artist = _episode_artist[_episode_song_big[_song_id]['artist']]['name']
                                _song_album = _episode_song_big[_song_id]['album']
                                _song_description = _episode_song_true[curr_song]['description']

                                _song_youtube_link = getYoutubeLink(_song_name, _song_artist)
                                _song_artist = _song_artist if _song_artist else "No artist"
                                _song_album = _song_album if _song_album else "No album"
                                _song_description = _song_description if _song_description else "No description"

                                print("\n {}. Song: {}\n Artist: {}\n Album: {}\n Listen: {}\n {}".format(song_ct + 1, _song_name,
                                                                                                          _song_artist, _song_album,
                                                                                                          _song_youtube_link,
                                                                                                          _song_description))

                        if not _season_wanted or not _episode_wanted:
                            break
                        _episode_cont = input("\n Search songs in another Episode of {} S{}? y/n : ".format(_show_name, curr_season))
                if not _season_wanted:
                    break
                _season_cont = input("\n Search songs in another Season of {}? y/n : ".format(_show_name))
            _show_cont = input("\n Search songs in another Show? y/n : ")

    if __name__ == "__main__":
        if len(sys.argv) > 1:
            if sys.argv[1] == 'help':
                print("""

    >>>>>>>>>>>>>>>>>>>>>>>
    >>>>    tvsongs    <<<<
    <<<<<<<<<<<<<<<<<<<<<<<

Find and listen to songs featured in TV shows.


    >>>>    Quick How-To:

$ tvsongs
 Name of the Show: daredevil

 Searching...

 Daredevil
 Total Seasons: 2

 Season 1    Episode(s): 13    Aired: Apr 2015 to Apr 2015
 Song(s): 26

 Season 2    Episode(s): 13    Aired: Mar 2016 to Mar 2016
 Song(s): 17

 Total Song(s) in Daredevil: 43

 Choose Season of Daredevil. 1 to 2 (0 for all): 0


    >>>>    Features:

All song data is collected in real-time, therefore up to date. That's why Internet connection is required.

Typing 'exit' or 'quit' at most input prompts will exit the program. Type Ctrl-C to force quit anytime.

Typing '0' when asked for Season will list songs from all seasons from that show.


    >>>>    Documentation:

https://github.com/zvovov/tvsongs


    >>>>    License:

The MIT License (MIT)

Copyright (c) 2016 Chirag Khatri

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

                """)
            elif sys.argv[1] == 'about':
                print("Created by Chirag Khatri")
            else:
                print("Type 'tvsongs help' for help")
        main()
except KeyboardInterrupt:
    sys.exit("\n Have a good day, Friend!")
