#!/usr/bin/env python3
"""
    * ytmdl.py - A script to download songs.

----------------------------------------------------
     A simple script to download songs in mp3 format
     from Youtube.
     Users pass the song name as arguments.
----------------------------------------------------
    --> Deepjyoti Barman
    --> deepjyoti30@github.com
"""

from __future__ import unicode_literals
import sys
from colorama import init
from colorama import Fore, Style
import argparse
from ytmdl import (
            dir,
            song,
            yt,
            defaults,
            prepend,
            setupConfig,
            cache,
            utility,
            metadata,
            manual,
            logger,
            stringutils,
)

# init colorama for windows
init()

logger = logger.Logger('ytmdl')


def arguments():
    """Parse the arguments."""
    parser = argparse.ArgumentParser()

    parser.add_argument('SONG_NAME', help="Name of the song to download.\
                         Can be an URL to a playlist as well. It will be\
                         automatically recognized.",
                        type=str, nargs="*")
    parser.add_argument('-q', '--quiet',
                        help="Don't ask the user to select songs\
                        if more than one search result.\
                        The first result in each case will be considered.",
                        action='store_true')
    metadata_group = parser.add_argument_group("Metadata")
    metadata_group.add_argument(
                        '--song', help="The song to search in Metadata. \
                        Particularly useful for songs that have the names in a\
                        different language in YouTube. For Example, greek songs.",
                        metavar='SONG-METADATA', default=None)
    metadata_group.add_argument(
                        '--choice', help="The choice that the user wants\
                        to go for. Usefull to pass along with --quiet.\
                        Choices start at 1", choices=range(1,50),
                        type=int, default=None, metavar="CHOICE")
    metadata_group.add_argument(
                        '--artist', help="The name of the song's artist.\
                        Pass it with a song name.")
    metadata_group.add_argument(
                        '--album', help="The name of the song's album.\
                        Pass it with a song name.")
    metadata_group.add_argument('--disable-metaadd', help="Disable addition of\
                        passed artist and album keyword to the youtube search\
                        in order to get a more accurate result. (Default: false)",
                        action="store_true")
    metadata_group.add_argument('--skip-meta', help="Skip setting the metadata and\
                        just copy the converted song to the destination directory.\
                        '--manual-meta' will override this option, pass only one\
                        of them.",
                        action="store_true")
    metadata_group.add_argument('-m', '--manual-meta', help="Manually enter song\
                        details.", action="store_true")

    parser.add_argument('--proxy', help='Use the specified HTTP/HTTPS/SOCKS proxy. To enable '
                        'SOCKS proxy, specify a proper scheme. For example '
                        'socks5://127.0.0.1:1080/. Pass in an empty string (--proxy "") '
                        'for direct connection', default=None, metavar='URL')
    parser.add_argument('--url',
                        help="Youtube song link.")
    parser.add_argument('--list', help="Download list of songs.\
                        The list should have one song name in every line.",
                        default=None, metavar="path to list".upper())
    parser.add_argument('--nolocal',
                        help='Don\'t search locally for the song before\
                        downloading.',
                        action='store_true')
    parser.add_argument('--format',
                        help="The format in which the song should be downloaded.\
                        Default is [MP3]. Available options are [m4a]", default='mp3',
                        type=str)
    parser.add_argument('--trim', '-t', help="Trim out the audio from the song. Use \
                        underlying speech and music segmentation engine to determine \
                        and keep only the music in the file. Useful in songs where there \
                        are speeches, noise etc before/after the start of the song. Default \
                        is false.", action='store_true')
    parser.add_argument('--version', action='version', version='2020.07.09',
                        help='show the program version number and exit')

    playlist_group = parser.add_argument_group("Playlist")
    playlist_group.add_argument(
        "--pl-start",
        help="Playlist video to start at (default is 1)",
        default=None,
        metavar="NUMBER",
        type=int
    )
    playlist_group.add_argument(
        "--pl-end",
        help="Playlist video to end at (default is last)",
        default=None,
        metavar="NUMBER",
        type=int
    )
    playlist_group.add_argument(
        "--pl-items",
        help="Playlist video items to download. \
             Specify indices of the videos present in the\
             playlist seperated by commas like: '--playlist-items\
              1, 2, 4, 6' if you want to download videos indexed\
             1, 2, 4 and 6. Range can also be passed like:\
             '--playlist-items 1-3, 5-7' to download the videos\
             indexed at 1, 2, 3, 5, 6, 7.",
        type=str,
        metavar="item_spec".upper(),
        default=None
    )
    playlist_group.add_argument(
        "--ignore-errors",
        help="Ignore if downloading any video fails in a playlist.\
             If passed, the execution will move to the next video in the\
             passed playlist.",
        action="store_true"
    )
    
    logger_group = parser.add_argument_group("Logger")
    logger_group.add_argument(
        "--level",
        help="The level of the logger that will be used while verbosing.\
            Use `--list-level` to check available options." + "\n",
        default="INFO",
        type=str
    )
    logger_group.add_argument(
        "--disable-file",
        help="Disable logging to files",
        default=False,
        action="store_true",
    )
    logger_group.add_argument(
        "--list-level",
        help="List all the available logger levels.",
        action="store_true"
    )

    args = parser.parse_args()

    return args


def main(args):
    """Run on program call."""

    if args.list_level:
        logger.list_available_levels()
        exit(0)

    # Update the logger flags, in case those are not the default ones.
    if args.level.lower != "info":
        logger.update_level(args.level.upper())

    if args.disable_file:
        logger.update_disable_file(True)
        logger.debug("Writing logs to file disabled")

    # Just a message to make the user aware of the current running state
    logger.debug("Logger running in DEBUG mode")

    # Check if --setup is passed
    if not setupConfig.check_config_setup():
        logger.debug("Config not present, creating default.")
        setupConfig.make_config()
        logger.debug("Config created")
        logger.info("Created new config since none was present")

    if not args.SONG_NAME and not args.url:
        logger.critical("Song Name is required. Check 'ytmdl --help' for help.")

    logger.debug(len(args.SONG_NAME))
    if len(args.SONG_NAME) > 1:
        song_name = ' '.join(args.SONG_NAME)
    elif not args.SONG_NAME:
        # Fetch the title of the song
        song_name = yt.get_title(args.url)
    else:
        song_name = args.SONG_NAME[0]

    logger.debug(song_name)
    logger.hold()

    if not args.nolocal:
        # Search for the song locally
        if not cache.main(song_name):
            return 0

    # Check if ffmpeg is installed.
    if not utility.is_present('ffmpeg'):
        logger.critical("ffmpeg is not installed. Please install it!")

    # Check if youtube-dl is installed.
    if not utility.is_present('youtube-dl'):
        logger.critical("youtube-dl is not installed. Please install it!")

    # Check if we need to import trim. Importing it is realy inefficient if
    # the user is not going to use it.
    if args.trim:
        logger.debug("Need to trim the song, importing the trim module.")
        from ytmdl import trim


    is_quiet = args.quiet
    url = args.url
    passed_choice = args.choice
    passed_format = args.format.lower()

    logger.debug("proxy passed: {}".format(args.proxy))
    logger.debug("Passed format: {}".format(passed_format))

    # Check if passed format is support, if not exit.
    if passed_format not in defaults.FORMAT.valid_formats:
        logger.critical("Passed format is not supported yet!")

    if args.song is not None:
        song_metadata = args.song
    else:
        song_metadata = song_name

    # If the url is passed then get the data
    if url is not None:
        data = []

        # Strip unwanted stuff from the URL
        url = stringutils.srtip_unwanted_words_from_url(url)

        # Get video data from youtube
        temp_data = yt.scan_video(yt.get_href(url), args.proxy)

        # Sometimes the temp_data may be returned as unauthorized, skip that
        if type(temp_data) is str and temp_data.lower() == "Unauthorized".lower():
            if args.ignore_errors:
                logger.warning("{}: is unauthorized".format(url))
                return
            else:
                logger.critical("{}: is unauthorized".format(url))

        data.append(temp_data)

        # link to dw the song
        link = url

        # In this case choice will be 0
        choice = 0
    else:
        if is_quiet:
            logger.info('Quiet is enabled')

        logger.info('Searching Youtube for {}{}{}'.format(
                Fore.LIGHTYELLOW_EX,
                song_name,
                Style.RESET_ALL
        ))

        data = yt.search(song_name, not args.disable_metaadd,
                                args.proxy,
                                kw=[args.artist, args.album])
        
        # Handle the exception if urls has len 0
        if len(data) == 0:
            logger.critical("No song found. Please try again with a different keyword.")

        if not is_quiet:
            # Ask for a choice
            choice = song.getChoice(data, 'mp3')
        else:
            if passed_choice is not None and passed_choice <= len(data):
                choice = passed_choice - 1
                logger.info("Using {} as choice".format(passed_choice))
            else:
                choice = 0

        link = 'https://youtube.com{}'.format(data[int(choice)]['href'])

    # Declare a var to store the name of the yt video
    logger.debug("{}".format(data[choice]))
    yt_title = data[choice]['title']

    logger.info('Downloading {}{}{} in {}{}kbps{}'.format(
        Fore.LIGHTMAGENTA_EX,
        yt_title,
        Style.RESET_ALL,
        Fore.LIGHTYELLOW_EX,
        defaults.DEFAULT.SONG_QUALITY,
        Style.RESET_ALL
    ))
    path = yt.dw(link, args.proxy, yt_title, passed_format)

    if type(path) is not str:
        if not args.ignore_errors:
            logger.critical("ERROR: {}".format(path))
        else:
            logger.info("--ignore-errors passed. Skipping this song!")
            return
    else:
        logger.info('Downloaded!')

    if passed_format == 'mp3':
        logger.info('Converting to mp3...')
        conv_name = utility.convert_to_mp3(path)
    elif passed_format == 'm4a':
        conv_name = path

    if type(conv_name) is not str:
        logger.critical('ERROR: {}'.format(conv_name))


    # Trim the song if the trim option is passed.
    if args.trim:
        logger.info("Passing the song to get trimmed.")
        trim.Trim(conv_name)

    logger.debug("Skip Meta: {}".format(args.skip_meta))

    if args.manual_meta:
        # Read the values from the user.
        TRACK_INFO = manual.get_data(song_name)
        option = 0

        option = song.setData(TRACK_INFO, is_quiet, conv_name, passed_format, choice)

        if type(option) is not int:
            logger.critical('ERROR: {}'.format(option))

        # Get the status of moving the song
        move_status = dir.cleanup(TRACK_INFO, option, passed_format)
    elif not args.skip_meta:
        logger.info('Getting song data for {}...'.format(song_metadata))

        TRACK_INFO = metadata.SEARCH_SONG(song_metadata, filters=[args.artist, args.album])

        # declare a variable to store the option
        option = 0

        if len(TRACK_INFO) == 0:
            logger.critical('No data was found!\a')
        else:
            prepend.PREPEND(1)
            print('Setting data...')

            option = song.setData(TRACK_INFO, is_quiet, conv_name, passed_format, choice)

            if type(option) is not int:
                logger.critical('ERROR: {}'.format(option))

        # Get the status of moving the song
        move_status = dir.cleanup(TRACK_INFO, option, passed_format)
    else:
        # If meta data is supposed to be skipped, go for a dry cleanup.
        move_status = dir.dry_cleanup(conv_name, song_name)

    if move_status:
        # If move_status would be False, program will exit from there
        # after showing the error.
        logger.info('Done')


def extract_data():
    """Extract the arguments and act accordingly."""
    args = arguments()

    if args.list is not None:
        songs = utility.get_songs(args.list)
        logger.debug(str(songs))
        if len(songs) != 0:
            logger.info("Downloading songs in {}".format(args.list))
            for song_name in songs:
                logger.debug(song_name)
                args.SONG_NAME = [song_name]
                main(args)
        else:
            logger.info("{}: is empty".format(args.list))
    elif args.SONG_NAME and yt.is_playlist(args.SONG_NAME[0]):
        logger.info("Youtube playlist passed...extracting!")
        songs, playlist_name = yt.get_playlist(
                                    args.SONG_NAME[0],
                                    args.proxy,
                                    args.pl_start,
                                    args.pl_end,
                                    args.pl_items
                                )

        # Check if data is actually returned
        if songs is None:
            logger.error("Couldn't extract playlist data!")

        logger.info("Playlist: {}".format(playlist_name))
        logger.info("{} songs found".format(len(songs)))

        # Iterate and work on the data.
        url_base = "https://www.youtube.com/watch?v="
        for song in songs:
            args.url = url_base + song["url"]

            # Try to pass the title as well, if it's not there
            # that will be handled by ytmdl
            try:
                args.SONG_NAME = [stringutils.remove_yt_words(song["title"])]
            except KeyError:
                pass

            main(args)
    else:
        main(args)


if __name__ == '__main__':
    try:
        extract_data()
    except KeyboardInterrupt:
        logger.info("\nExiting..!")
