#!/usr/bin/env python
""" pygame.examples.music_drop_fade
Fade in and play music from a list while observing several events

Adds music files to a playlist whenever played by one of the following methods
Music files passed from the commandline are played
Music files and filenames are played when drag and dropped onto the pygame window
Polls the clipboard and plays music files if it finds one there

Keyboard Controls:
* Press space or enter to pause music playback
* Press up or down to change the music volume
* Press left or right to seek 5 seconds into the track
* Press escape to quit
* Press any other button to skip to the next music file in the list
"""

from typing import List
import pygame as pg
import os, sys

VOLUME_CHANGE_AMOUNT = 0.02  # how fast should up and down arrows change the volume?


def add_file(filename):
    """
    This function will check if filename exists and is a music file
    If it is the file will be added to a list of music files(even if already there)
    Type checking is by the extension of the file, not by its contents
    We can only discover if the file is valid when we mixer.music.load() it later

    It looks in the file directory and its data subdirectory
    """
    if filename.rpartition(".")[2].lower() not in music_file_types:
        print(f"{filename} not added to file list")
        print("only these files types are allowed: ", music_file_types)
        return False
    elif os.path.exists(filename):
        music_file_list.append(filename)
    elif os.path.exists(os.path.join(main_dir, filename)):
        music_file_list.append(os.path.join(main_dir, filename))
    elif os.path.exists(os.path.join(data_dir, filename)):
        music_file_list.append(os.path.join(data_dir, filename))
    else:
        print("file not found")
        return False
    print(f"{filename} added to file list")
    return True


def play_file(filename):
    """
    This function will call add_file and play it if successful
    The music will fade in during the first 4 seconds
    set_endevent is used to post a MUSIC_DONE event when the song finishes
    The main loop will call play_next() when the MUSIC_DONE event is received
    """
    global starting_pos

    if add_file(filename):
        try:  # we must do this in case the file is not a valid audio file
            pg.mixer.music.load(music_file_list[-1])
        except pg.error as e:
            print(e)  # print description such as 'Not an Ogg Vorbis audio stream'
            if filename in music_file_list:
                music_file_list.remove(filename)
                print(f"{filename} removed from file list")
            return
        pg.mixer.music.play(fade_ms=4000)
        pg.mixer.music.set_volume(volume)

        if filename.rpartition(".")[2].lower() in music_can_seek:
            print("file supports seeking")
            starting_pos = 0
        else:
            print("file does not support seeking")
            starting_pos = -1
        pg.mixer.music.set_endevent(MUSIC_DONE)


def play_next():
    """
    This function will play the next song in music_file_list
    It uses pop(0) to get the next song and then appends it to the end of the list
    The song will fade in during the first 4 seconds
    """

    global starting_pos
    if len(music_file_list) > 1:
        nxt = music_file_list.pop(0)

        try:
            pg.mixer.music.load(nxt)
        except pg.error as e:
            print(e)
            print(f"{nxt} removed from file list")

        music_file_list.append(nxt)
        print("starting next song: ", nxt)
    else:
        nxt = music_file_list[0]
    pg.mixer.music.play(fade_ms=4000)
    pg.mixer.music.set_volume(volume)
    pg.mixer.music.set_endevent(MUSIC_DONE)

    if nxt.rpartition(".")[2].lower() in music_can_seek:
        starting_pos = 0
    else:
        starting_pos = -1


def draw_text_line(text, y=0):
    """
    Draws a line of text onto the display surface
    The text will be centered horizontally at the given y position
    The text's height is added to y and returned to the caller
    """
    screen = pg.display.get_surface()
    surf = font.render(text, 1, (255, 255, 255))
    y += surf.get_height()
    x = (screen.get_width() - surf.get_width()) / 2
    screen.blit(surf, (x, y))
    return y


def change_music_position(amount):
    """
    Changes current playback position by amount seconds.
    This only works with OGG and MP3 files.
    music.get_pos() returns how many milliseconds the song has played, not
    the current position in the file. We must track the starting position
    ourselves. music.set_pos() will set the position in seconds.
    """
    global starting_pos

    if starting_pos >= 0:  # will be -1 unless play_file() was OGG or MP3
        played_for = pg.mixer.music.get_pos() / 1000.0
        old_pos = starting_pos + played_for
        starting_pos = old_pos + amount
        pg.mixer.music.play(start=starting_pos)
        print(f"jumped from {old_pos} to {starting_pos}")


MUSIC_DONE = pg.event.custom_type()  # event to be set as mixer.music.set_endevent()
main_dir = os.path.split(os.path.abspath(__file__))[0]
data_dir = os.path.join(main_dir, "data")

starting_pos = 0  # needed to fast forward and rewind
volume = 0.75
music_file_list: List[str] = []
music_file_types = ("mp3", "ogg", "mid", "mod", "it", "xm", "wav")
music_can_seek = ("mp3", "ogg", "mod", "it", "xm")


def main():
    global font  # this will be used by the draw_text_line function
    global volume, starting_pos
    running = True
    paused = False

    # we will be polling for key up and key down events
    # users should be able to change the volume by holding the up and down arrows
    # the change_volume variable will be set by key down events and cleared by key up events
    change_volume = 0

    pg.init()
    pg.display.set_mode((640, 480))
    font = pg.font.SysFont("Arial", 24)
    clock = pg.time.Clock()

    pg.scrap.init()
    pg.SCRAP_TEXT = pg.scrap.get_types()[0]  # TODO remove when scrap module is fixed
    scrap_get = pg.scrap.get(pg.SCRAP_TEXT)
    clipped = "" if scrap_get is None else scrap_get.decode("UTF-8")
    # store the current text from the clipboard TODO remove decode

    # add the command line arguments to the  music_file_list
    for arg in sys.argv[1:]:
        add_file(arg)
    play_file("house_lo.ogg")  # play default music included with pygame

    # draw instructions on screen
    y = draw_text_line("Drop music files or path names onto this window", 20)
    y = draw_text_line("Copy file names into the clipboard", y)
    y = draw_text_line("Or feed them from the command line", y)
    y = draw_text_line("If it's music it will play!", y)
    y = draw_text_line("SPACE to pause or UP/DOWN to change volume", y)
    y = draw_text_line("LEFT and RIGHT will skip around the track", y)
    draw_text_line("Other keys will start the next track", y)

    """
    This is the main loop
    It will respond to drag and drop, clipboard changes, and key presses
    """
    while running:
        for ev in pg.event.get():
            if ev.type == pg.QUIT:
                running = False
            elif ev.type == pg.DROPTEXT:
                play_file(ev.text)
            elif ev.type == pg.DROPFILE:
                play_file(ev.file)
            elif ev.type == MUSIC_DONE:
                play_next()
            elif ev.type == pg.KEYDOWN:
                if ev.key == pg.K_ESCAPE:
                    running = False  # exit loop
                elif ev.key in (pg.K_SPACE, pg.K_RETURN):
                    if paused:
                        pg.mixer.music.unpause()
                        paused = False
                    else:
                        pg.mixer.music.pause()
                        paused = True
                elif ev.key == pg.K_UP:
                    change_volume = VOLUME_CHANGE_AMOUNT
                elif ev.key == pg.K_DOWN:
                    change_volume = -VOLUME_CHANGE_AMOUNT
                elif ev.key == pg.K_RIGHT:
                    change_music_position(+5)
                elif ev.key == pg.K_LEFT:
                    change_music_position(-5)

                else:
                    play_next()

            elif ev.type == pg.KEYUP:
                if ev.key in (pg.K_UP, pg.K_DOWN):
                    change_volume = 0

        # is the user holding up or down?
        if change_volume:
            volume += change_volume
            volume = min(max(0, volume), 1)  # volume should be between 0 and 1
            pg.mixer.music.set_volume(volume)
            print("volume:", volume)

        # TODO remove decode when SDL2 scrap is fixed
        scrap_get = pg.scrap.get(pg.SCRAP_TEXT)
        new_text = "" if scrap_get is None else scrap_get.decode("UTF-8")

        if new_text != clipped:  # has the clipboard changed?
            clipped = new_text
            play_file(clipped)  # try to play the file if it has

        pg.display.flip()
        clock.tick(9)  # keep CPU use down by updating screen less often

    pg.quit()


if __name__ == "__main__":
    main()
