# applet.py - A GNOME applet that shows what Moosic is currently playing.
#
# Copyright (C) 2003 Daniel Pearson <daniel.pearson@umbc.edu>
# Copyright (C) 2003, 2004 Paramjit Oberoi <param@cs.wisc.edu>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import socket, errno, os, os.path, re
import gtk

import xmlrpclib
import moosic.client.factory

from ui import MoosicAppletUI
from config import MoosicAppletConfig

# Class representing the applet
# An instance of this class is instantiated for each applet

class MoosicApplet:
    def refresh(self):
        call_array = [
                      {'methodName':'length','params':[]},              # [0]
                      {'methodName':'current','params':[]},             # [1]
                      {'methodName':'is_paused','params':[]},           # [2]
                      {'methodName':'is_queue_running','params':[]},    # [3]
                      {'methodName':'list','params':[[0,1]]},           # [4]
                      {'methodName':'is_looping','params':[]},          # [5]
                      {'methodName':'current_time','params':[]},        # [6]
                     ]
        try: result_array = self.moosic.system.multicall(call_array)
        except socket.error, e:
            if e[0] in (errno.ECONNREFUSED, errno.ENOENT):
                self.ui.update_tooltip('[moosicd not running]')
            else:
                self.ui.update_tooltip('[unexpected socket error]')
            self.update_song_name("[not connected]", False)
            self.connected = False
            return True

        update_display = False
        if not self.connected:
            self.connected = True
            update_display = True

        current_song = result_array[1][0].data.decode('latin-1').encode('utf-8')
        current_time = int(round(result_array[6][0]))
        paused = result_array[2][0]
        looping = result_array[5][0]
        stopped = not current_song
        queue_length = result_array[0][0]
        queue_running = result_array[3][0]
        if queue_length:
            first_in_queue = result_array[4][0][0].data.decode('latin-1').encode('utf-8')
        else:
            first_in_queue = None

        if self.paused != paused:
            self.paused = paused
            if paused: self.ui.paused()
            else: self.ui.unpaused()
            update_display = True

        if self.stopped != stopped:
            self.stopped = stopped;
            update_display = True

        if self.queue_running != queue_running:
            self.queue_running = queue_running
            if queue_running: self.ui.queue_running()
            else: self.ui.queue_not_running()

        if self.looping != looping:
            self.looping = looping
            if looping: self.ui.loop_mode_enabled()
            else: self.ui.loop_mode_disabled()

        if self.queue_length != queue_length:
            self.queue_length = queue_length
            if self.config.display_qlength:
                #self.ui.qlen_label.set_text("%d" % queue_length)
                self.ui.qlen_label.set_markup("<b>%d</b>" % queue_length)
            update_display = True

        if self.current_song != current_song:
            self.current_song = current_song
            update_display = True

        if self.first_in_queue != first_in_queue:
            self.first_in_queue = first_in_queue
            update_display = True

        if self.config.display_clock:
            self.update_clock(current_time)

        if update_display:
            # update stop/play/add button
            if stopped:
                if self.queue_length: self.ui.stopped()
                else: self.ui.empty()
            else: self.ui.playing()

            # update song name and tooltip
            if stopped:
                if queue_length:
                    self.ui.update_tooltip("[next item] " + first_in_queue)
                else:
                    self.ui.update_tooltip("[queue empty]")
                if self.config.display_qlength or queue_length:
                    self.update_song_name("[nothing playing]", False)
                else:
                    self.update_song_name("[queue empty]", False)
            elif paused:
                self.ui.update_tooltip("[paused] " + current_song)
                self.update_song_name(current_song, True)
            else:
                self.ui.update_tooltip(current_song)
                self.update_song_name(current_song, True)

        #pdb.set_trace()
        return True

    def update_song_name(self, s, munge):
        if munge:
            if (self.config.strip_dir): s = os.path.basename(s)
            if (self.config.strip_ext): s = os.path.splitext(s)[0]
            if (self.config.strip_prefix):
                dirname, filename = os.path.split(s)
                for pfx in self.config.prefixes:
                    n = filename.rfind(pfx)
                    if n != -1: filename = filename[n+len(pfx):]
                s = os.path.join(dirname,filename)
            if (self.config.replace_underscores): s = s.replace("_"," ")
            s = s.strip()

        self.ui.label.set_text(s)

    def update_clock(self, t):
        # s = "%02d:%02d" % divmod(t, 60)
        s = "<tt>%02d:%02d</tt>" % divmod(t, 60)
        self.ui.clock.set_markup(s)

    # ---- Utility functions for controlling moosicd

    def read_playlist(self, fname):
        # try to read playlist; on failure return
        # the name of the playlist file iteself
        try:
            itemlist = []
            dirname, filename = os.path.split(fname)
            playlist = open(fname, 'r')
            for line in playlist.readlines():
                # The algorithm is Slightly different from
                # the one in moosic_dispatcher.read_playlists()
                # Entries with non-absolute paths are treated as
                # relative to the path to the playlist itself

                if line[0] == '#': continue                 # skip comments
                if re.search(r'^\s*$', line): continue      # skip empty lines
                if line[-1] == '\n': line = line[:-1]       # remove trailing newline

                # os.path.join automatically discards prefixes
                # if one of the path components is absolute
                line = os.path.join(dirname, line)
                itemlist.append(line)
        except:
            itemlist = [fname]

        try: playlist.close()
        except: pass

        return itemlist

    def construct_file_list(self, lst, result=None):
        if result is None: item_list = []
        else: item_list = result
        for item in lst:
            # Recursive call for directories
            if os.path.isdir(item):
                dirlist = os.listdir(item)
                # ignore dotfiles and dotdirs?
                dirlist = [os.path.join(item,x) for x in dirlist]
                self.construct_file_list(dirlist, item_list)
            # Playlists
            elif self.config.expand_playlist and item[-4:].lower() == '.m3u':
                for i in self.read_playlist(item):
                    item_list.append(xmlrpclib.Binary(i))
            # Individual files
            else:
                item_list.append(xmlrpclib.Binary(item))
        return item_list

    def add(self, run_queue=False):
        def callback(dlg,response):
            if response==gtk.RESPONSE_OK:
                selections = dlg.get_selections()
                self.moosic.append(self.construct_file_list(selections))
                if run_queue: self.moosic.run_queue()
                last_sel = selections[-1]
                if not os.path.isdir(last_sel):
                    last_sel = os.path.dirname(last_sel)
                os.chdir(last_sel)
            dlg.destroy()

        f = gtk.FileSelection("Add Entries to Moosic Queue")
        f.set_select_multiple(True)
        f.hide_fileop_buttons()
        f.connect("response",callback)
        f.show_all()


    def play_stop_add(self):
        if self.stopped:
            if not self.queue_length: self.add(True)
            else: self.moosic.run_queue()
        else:
            self.moosic.stop()

    def pause_toggle(self):
        self.moosic.toggle_pause()

    def prev(self):
        self.moosic.previous()

    def next(self):
        self.moosic.next()

    def sort(self):
        self.moosic.sort()

    def shuffle(self):
        self.moosic.shuffle()

    def qstop(self):
        self.moosic.halt_queue()

    def clear(self):
        self.moosic.clear()

    def wipe(self):
        self.moosic.halt_queue()
        self.moosic.next()
        self.moosic.clear()

    def toggle_queue_running(self):
        if self.queue_running:
            self.moosic.halt_queue()
        else:
            self.moosic.run_queue()

    def toggle_loop_mode(self):
        self.moosic.toggle_loop_mode()

    def __init__(self, applet, iid):
        self.stopped = True
        self.paused = False
        self.looping = None
        self.queue_running = False
        self.queue_length = None
        self.current_song = None
        self.first_in_queue = None
        self.connected = False

        self.moosic = moosic.client.factory.LocalMoosicProxy()
        self.config = MoosicAppletConfig(self)
        self.ui = MoosicAppletUI(applet, self)

