#!/usr/bin/env python
import PySimpleGUI as sg
import os
import mido
import time

PLAYER_COMMAND_NONE = 0
PLAYER_COMMAND_EXIT = 1
PLAYER_COMMAND_PAUSE = 2
PLAYER_COMMAND_NEXT = 3
PLAYER_COMMAND_RESTART_SONG = 4

# ---------------------------------------------------------------------- #
#    PlayerGUI             CLASS                                         #
# ---------------------------------------------------------------------- #


class PlayerGUI():
    '''
    Class implementing GUI for both initial screen but the player itself
    '''

    def __init__(self):
        self.Window = None
        self.TextElem = None
        # use to get the list of midi ports
        self.PortList = mido.get_output_names()
        # reverse the list so the last one is first
        self.PortList = self.PortList[::-1]

    # ---------------------------------------------------------------------- #
    #  PlayerChooseSongGUI                                                   #
    #   Show a GUI get to the file to playback                               #
    # ---------------------------------------------------------------------- #
    def PlayerChooseSongGUI(self):

        # ---------------------- DEFINION OF CHOOSE WHAT TO PLAY GUI ----------------------------

        helv = ("Helvetica", 15)
        layout = [[sg.Text('MIDI File Player', font=helv15, size=(20, 1), text_color='green')],
                  [sg.Text('File Selection', font=helv15, size=(20, 1))],
                  [sg.Text('Single File Playback', justification='right'),
                      sg.InputText(size=(65, 1), key='midifile'),
                      sg.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
                  [sg.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'),
                      sg.InputText(size=(65, 1), key='folder'),
                      sg.FolderBrowse(size=(10, 1))],
                  [sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
                  [sg.Text('Choose MIDI Output Device', size=(22, 1)),
                   sg.Listbox(values=self.PortList, size=(30, len(self.PortList) + 1), key='device')],
                  [sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
                  [sg.SimpleButton('PLAY', size=(12, 2), button_color=('red', 'white'), font=helv15, bind_return_key=True),
                      sg.Text(' ' * 2, size=(4, 1)),
                      sg.Cancel(size=(8, 2), font=helv15)]]

        window = sg.Window('MIDI File Player', layout, auto_size_text=False,
                           default_element_size=(30, 1), font=helv)
        self.Window = window
        return window.read()

    def PlayerPlaybackGUIStart(self, NumFiles=1):
        # -------  Make a new FlexForm  ------- #

        image_pause = './ButtonGraphics/Pause.png'
        image_restart = './ButtonGraphics/Restart.png'
        image_next = './ButtonGraphics/Next.png'
        image_exit = './ButtonGraphics/Exit.png'

        self.TextElem = sg.Text('Song loading....',
            size=(70, 5 + NumFiles),
            font=("Helvetica", 14), auto_size_text=False)
        self.SliderElem = sg.Slider(range=(1, 100),
            size=(50, 8),
            orientation='h', text_color='#f0f0f0')
        css = {
            'image_size': (50, 50),
            'image_subsample': 2,
            'border_width': 0,
            'button_color': sg.TRANSPARENT_BUTTON
        }
        layout = [
            [sg.Text('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
            [self.TextElem],
            [self.SliderElem],
            [sg.Button('', image_filename=image_pause, **css, key='PAUSE'), sg.Text(' '),
             sg.Button('', image_filename=image_next, **css, key='NEXT'), sg.Text(' '),
             sg.Button('', image_filename=image_restart, **css, key='Restart Song'), sg.Text(' '),
             sg.Button('', image_filename=image_exit, **css, key='EXIT')]
        ]

        window = sg.Window('MIDI File Player', layout, default_element_size=(
            30, 1), font=("Helvetica", 25), finalize=True)
        self.Window = window

    # ------------------------------------------------------------------------- #
    #  PlayerPlaybackGUIUpdate                                                  #
    #   Refresh the GUI for the main playback interface (must call periodically #
    # ------------------------------------------------------------------------- #

    def PlayerPlaybackGUIUpdate(self, DisplayString):
        window = self.Window
        # if the widnow has been destoyed don't mess with it
        if 'window' not in locals() or window is None:
            return PLAYER_COMMAND_EXIT
        self.TextElem.update(DisplayString)
        event, (values) = window.read(timeout=0)
        if event is None:
            return PLAYER_COMMAND_EXIT
        if event == 'PAUSE':
            return PLAYER_COMMAND_PAUSE
        elif event == 'EXIT':
            return PLAYER_COMMAND_EXIT
        elif event == 'NEXT':
            return PLAYER_COMMAND_NEXT
        elif event == 'Restart Song':
            return PLAYER_COMMAND_RESTART_SONG
        return PLAYER_COMMAND_NONE


# ---------------------------------------------------------------------- #
# MAIN - our main program... this is it                                  #
#   Runs the GUI to get the file / path to play                          #
#   Decodes the MIDI-Video into a MID file                               #
#   Plays the decoded MIDI file                                          #
# ---------------------------------------------------------------------- #
def main():
    def GetCurrentTime():
        '''
        Get the current system time in milliseconds
        :return: milliseconds
        '''
        return int(round(time.time() * 1000))

    pback = PlayerGUI()

    button, values = pback.PlayerChooseSongGUI()
    if button != 'PLAY':
        sg.popup_cancel('Cancelled...\nAutoclose in 2 sec...',
                        auto_close=True, auto_close_duration=2)
        return
    if values['device']:
        midi_port = values['device'][0]
    else:
        sg.popup_cancel('No devices found\nAutoclose in 2 sec...',
                        auto_close=True, auto_close_duration=2)

    batch_folder = values['folder']
    midi_filename = values['midifile']
    # ------ Build list of files to play --------------------------------------------------------- #
    if batch_folder:
        filelist = os.listdir(batch_folder)
        filelist = [batch_folder+'/' +
                    f for f in filelist if f.endswith(('.mid', '.MID'))]
        filetitles = [os.path.basename(f) for f in filelist]
    elif midi_filename:       # an individual filename
        filelist = [midi_filename, ]
        filetitles = [os.path.basename(midi_filename), ]
    else:
        sg.popup_error('*** Error - No MIDI files specified ***')
        return

    # ------ LOOP THROUGH MULTIPLE FILES --------------------------------------------------------- #
    pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <= 10 else 10)
    port = None
    
    # Loop through the files in the filelist
    for now_playing_number, current_midi_filename in enumerate(filelist):
        display_string = 'Playing Local File...\n{} of {}\n{}'.format(
            now_playing_number+1, len(filelist), current_midi_filename)
        midi_title = filetitles[now_playing_number]
        # --------------------------------- REFRESH THE GUI ----------------------------------------- #
        pback.PlayerPlaybackGUIUpdate(display_string)

        # ---===--- Output Filename is .MID --- #
        midi_filename = current_midi_filename

        # --------------------------------- MIDI - STARTS HERE ----------------------------------------- #
        if not port:            # if the midi output port not opened yet, then open it
            port = mido.open_output(midi_port if midi_port else None)

        try:
            mid = mido.MidiFile(filename=midi_filename)
        except:
            print(' Fail at playing Midi file = {}****'.format(midi_filename))
            sg.popup_error('Exception trying to play MIDI file:',
                           midi_filename, 'Skipping file')
            continue

        # Build list of data contained in MIDI File using only track 0
        midi_length_in_seconds = mid.length
        display_file_list = '>> ' + \
            '\n'.join([f for i, f in enumerate(
                filetitles[now_playing_number:]) if i < 10])
        paused = cancelled = next_file = False
        ######################### Loop through MIDI Messages ###########################
        while True :
            start_playback_time = GetCurrentTime()
            port.reset()

            for midi_msg_number, msg in enumerate(mid.play()):
                #################### GUI - read values ##################
                if not midi_msg_number % 4:              # update the GUI every 4 MIDI messages
                    t = (GetCurrentTime() - start_playback_time)//1000
                    display_midi_len = '{:02d}:{:02d}'.format(
                        *divmod(int(midi_length_in_seconds), 60))
                    display_string = 'Now Playing {} of {}\n{}\n              {:02d}:{:02d} of {}\nPlaylist:'.\
                        format(now_playing_number+1, len(filelist), midi_title, *divmod(t, 60), display_midi_len)
                    # display list of next 10 files to be played.
                    pback.SliderElem.update(t, range=(1, midi_length_in_seconds))
                    rc = pback.PlayerPlaybackGUIUpdate(
                        display_string + '\n' + display_file_list)
                else:       # fake rest of code as if GUI did nothing
                    rc = PLAYER_COMMAND_NONE
                if paused:
                    rc = PLAYER_COMMAND_NONE
                    while rc == PLAYER_COMMAND_NONE:        # TIGHT-ASS loop waiting on a GUI command
                        rc = pback.PlayerPlaybackGUIUpdate(display_string)
                        time.sleep(.25)

                ####################################### MIDI send data ##################################
                port.send(msg)

                # -------  Execute GUI Commands  after sending MIDI data ------- #
                if rc == PLAYER_COMMAND_EXIT:
                    cancelled = True
                    break
                elif rc == PLAYER_COMMAND_PAUSE:
                    paused = not paused
                    port.reset()
                elif rc == PLAYER_COMMAND_NEXT:
                    next_file = True
                    break
                elif rc == PLAYER_COMMAND_RESTART_SONG:
                    break

            if cancelled or next_file:
                break
        #------- DONE playing the song   ------- #
        port.reset()                    # reset the midi port when done with the song

        if cancelled:
            break


# ---------------------------------------------------------------------- #
#  LAUNCH POINT -- program starts and ends here                          #
# ---------------------------------------------------------------------- #
if __name__ == '__main__':
    main()