#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Name:           youplay.py
Description:    search, play and save audio files from Youtube
Usage:          start it with: './youplay.py --help'
Author:         Ralf Hersel - ralf.hersel@gmx.net
Licence:        GPL3
GUI-Library:    GTK4, LibAdwaita
Repository:     https://codeberg.org/ralfhersel/youplay
More infos:     see constants and readme.md
'''

# === Todo =====================================================================

'''
- Table header alignment. Waiting for Python-Gtk4 documentation.
- Upload Flatpak on Flathub: not possible from Codeberg. Refuse.
'''

# === Libraries ================================================================
from youplay import *

# Third party imports                                                           # for the music play in GUI mode
import gi                                                                       # for GTK Gui
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GLib

# === GUI ======================================================================

class MainWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # === Player ===========================================================
        import locale
        locale.setlocale(locale.LC_NUMERIC, "C")
        from mpv import MPV
        self.player = mpv.MPV(ytdl=True, input_default_bindings=True, input_vo_keyboard=True) # initialize mpv internal player
        @self.player.property_observer('time-pos')
        def time_observer(_name, value):                                        # get playback timer
            if value is not None:                                               # avoid Type Error when started
                sec = int(value)                                                # get seconds from flaot
                sec_time_object = time.gmtime(sec)                              # convert to time object
                hms = time.strftime("%H:%M:%S",sec_time_object)                 # convert seconds to hour:minute:second
                title_time_text = self.status_text.strip() + '  -  ' + hms      # song titel - play time
                self.show_status_text(title_time_text)                          # update status bar     
        
        # === Widgets ==========================================================
        
        # Window
            # Box (vertical)
                # Entry
                # ScrolledWindow
                    # TreeView
                        # TreeViewColumn
                # Box (horizontal)
                    # Buttons
                # StatusBar
                # ProgressBar
        
        self.set_title(PROG_NAME + ' it from Youtube')
        self.set_icon_name('youplay.svg')                                       # does not work
        self.set_default_size(WIN_WIDTH, WIN_HEIGHT)                            # width, height
        
        self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)           # Vertical Box
        self.set_child(self.main_box)
        
        self.entry = Gtk.Entry()                                                # Entry
        self.entry.connect('activate', self.on_entry_return)                    # Return pressed
        icon_name = "system-search-symbolic"
        self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, icon_name)
        self.main_box.append(self.entry)
        
        self.liststore = Gtk.ListStore(str, str, str)                           # Treeview and Liststore
        self.treeview = Gtk.TreeView(model=self.liststore)
        self.treeview.set_property('activate-on-single-click', False)           # Click means double click
        self.treeview.connect('row_activated', self.on_row_activated)           # Treeview item double clicked
        for col_num, column_title in enumerate(['Nr', 'Title', 'Duration']):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=col_num)
            if col_num == 0:                                                    # Nr
                column.set_property('alignment', 0.5)							# center Nr heading. Does not work. Use https://docs.gtk.org/gtk4/class.ColumnView.html
                renderer.set_property('xalign', 0.5)                            # center Nr content
            if col_num == 2:                                                    # Duration
                column.set_property('alignment', 1.0)							# right align column heading. Does not work. Use https://docs.gtk.org/gtk4/class.ColumnView.html
                renderer.set_property('xalign', 1.0)                            # right align column content
            self.treeview.append_column(column)            

        self.scrollable_treelist = Gtk.ScrolledWindow()
        self.treeview.set_property('vexpand', True)                             # expand Treeview to max
        self.scrollable_treelist.set_child(self.treeview)
        self.main_box.append(self.scrollable_treelist)
        
        self.button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)       # Horizontal Button Box
        self.button_box.set_property('spacing', 5)
        self.button_box.set_property('homogeneous', True)
        
        self.button_play = Gtk.Button(label='Play')                             # PLAY button
        self.button_play.set_margin_start(5)
        self.button_play.connect('clicked', self.on_button_play_clicked)
        self.button_box.append(self.button_play)

        self.button_pause = Gtk.Button(label='Pause')                           # PAUSE button
        self.button_pause.connect('clicked', self.on_button_pause_clicked)
        self.button_box.append(self.button_pause)

        self.button_back = Gtk.Button(label='Back')                             # BACK button
        self.button_back.connect('clicked', self.on_button_back_clicked)
        self.button_box.append(self.button_back)

        self.button_forward = Gtk.Button(label='Forward')                       # FORWARD button
        self.button_forward.connect('clicked', self.on_button_forward_clicked)
        self.button_box.append(self.button_forward)

        self.button_songs = Gtk.Button(label='Songs')                           # SONGS button
        self.button_songs.connect('clicked', self.on_button_songs_clicked)
        self.button_box.append(self.button_songs)
        
        self.button_about = Gtk.Button(label='About')                           # ABOUT button
        self.button_about.connect('clicked', self.on_button_about_clicked)
        self.button_box.append(self.button_about)
        
        self.button_quit = Gtk.Button(label='Quit')                             # QUIT button
        self.button_quit.set_margin_end(5)
        self.button_quit.connect('clicked', self.on_button_quit_clicked)
        self.button_box.append(self.button_quit)

        self.main_box.append(self.button_box)
        
        self.status_bar = Gtk.Statusbar()
        self.main_box.append(self.status_bar)

        self.progressbar = Gtk.ProgressBar()
        self.progressbar.set_pulse_step(0.2)
        self.timeout_id = GLib.timeout_add(200, self.on_timeout, None)          # progress bar speed
        self.main_box.append(self.progressbar)
        
        self.progressbar.hide()

# === Event Handler ============================================================

    def on_entry_return(self, widget):                                          # Return pressed in entry field
        label = self.button_play.get_label()                                    # Enable new search when song is playing
        if label == 'Stop':
            self.player.stop()
            self.show_status_text('Stopped playing')
            
        self.progressbar.hide()
        self.button_play.set_label('Play')
        self.progressbar.show()
        self.show_status_text('Retrieving song list from Youtube ... please wait')
        song = widget.get_text()
        song = sanitize_song(song)                                              # remove unwanted chars from song title
        song_dict = get_songlist(song)
        song_list = song_dict.items()                                           # convert dict to list
        if len(song_list) == 0:                                                 # nothing found
            self.show_status_text('Nothing found on Youtube, try another search.')
        else:
            self.liststore.clear()
            for key, item in song_dict.items():
                number = key
                title = item[0]
                duration = item[1]
                self.liststore.append([number, title, duration])
            self.show_status_text('Select a song and click Play')
        self.progressbar.hide()

    def on_row_activated(self, widget, event, tv_column):                       # double click on treeview item
        self.player.stop()                                                      # stop already playing song
        selected = self.get_tree_selection(self.treeview)
        self.do_play(selected)

    def on_button_play_clicked(self, widget):                                   # PLAY/STOP clicked
        label = widget.get_label()
        if label == 'Play':
            selected = self.get_tree_selection(self.treeview)
            self.do_play(selected)
        if label == 'Stop':
            widget.set_label('Play')
            self.player.stop()
            self.show_status_text('Stopped playing')

    def on_button_pause_clicked(self, widget):                                  # PAUSE/CONTINUE clicked
        label = widget.get_label()
        if label == 'Pause':
            widget.set_label('Continue')
            self.player.command('cycle', 'pause')
        if label == 'Continue':
            widget.set_label('Pause')
            self.player.command('cycle', 'pause')

    def on_button_back_clicked(self, widget):                                   # BACK clicked
        self.player.command('seek', '-5')

    def on_button_forward_clicked(self, widget):                                # FORWARD clicked
        self.player.command('seek', '5')

    def on_button_songs_clicked(self, widget):                                  # show list of downloaded songs
        music_path = get_music_dir()
        files=glob.glob(music_path + '*.mp3')
        try: filename = files[0]
        except IndexError:
            self.show_status_text('No songs in music folder: ' + music_path)
            return

        self.liststore.clear()
        number = 0
        for filepath in files:
            title = os.path.basename(filepath)                                  # get filename from path
            title = title[:-4]                                                  # cut '.mp3' from filename
            title = title[:MAX_TITLE_LENGHT].ljust(MAX_TITLE_LENGHT, ' ')       # cut and fit title length
            number += 1
            duration = get_mp3_duration(filepath)
            self.liststore.append([str(number), title, duration])
        self.show_status_text('Select a song and click Play')

    def on_button_about_clicked(self, widget):                                  # ABOUT clicked
        about = Adw.AboutWindow()
        about.set_application_name(PROG_NAME)
        about.set_version(VERSION)
        about.set_license_type(Gtk.License(Gtk.License.GPL_3_0))
        about.set_developer_name(AUTHOR)
        about.set_comments(ABOUT)
        about.set_website("https://codeberg.org/ralfhersel/youplay")
        about.set_issue_url("https://codeberg.org/ralfhersel/youplay/issues")
        about.show()
        
    def on_button_quit_clicked(self, widget):                                   # QUIT clicked
        self.player.stop()
        del self.player
        self.destroy()


    def on_timeout(self, user_data):                                            # update value on progress bar
        self.progressbar.pulse()
        return True


# === Methods ==================================================================

    def do_play(self, selected):                                                # play song from GUI
        if selected == None:
            self.show_status_text('Nothing selected')
        else:
            self.progressbar.show()
            self.show_status_text('Downloading song from Youtube ... please wait')
            song = selected[1]
            audio_path = get_song(song)
            self.progressbar.hide()
            self.status_text = selected[1]                                      # status bar will be updated by mpv time_observer
            self.button_play.set_label('Stop')
            self.player.play(audio_path)

    def get_tree_selection(self, tree):                                         # get selected tree item
        selection = tree.get_selection()
        model, treeiter = selection.get_selected()
        if treeiter != None:
            result = model[treeiter]
        else:
            result = None
        return result
    
    def show_status_text(self, text):                                           # show status text
        self.status_bar.push(0, text)
        main_context = GLib.MainContext.default()
        while main_context.pending():
            main_context.iteration(False)

# === App ======================================================================

class MyApp(Adw.Application):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect('activate', self.on_activate)

    def on_activate(self, app):
        self.win = MainWindow(application=app)
        self.win.present()

# === Main =====================================================================
def main():
    app = MyApp(application_id="org.codeberg.ralfhersel.youplay")
    sm = app.get_style_manager()
    sm.set_color_scheme(Adw.ColorScheme.DEFAULT)
    app.run()                                                           # param: (sys.argv)

# === Main =====================================================================
if __name__ == '__main__':    
    main()
