#!/usr/bin/python

#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2012  Brian Langenberger

#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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

import audiotools
import audiotools.player
import sys
import gettext
import time
import select
import os
import tty
import termios

gettext.install("audiotools", unicode=True)

try:
    import urwid

    class MappedButton(urwid.Button):
        def __init__(self, label, on_press=None, user_data=None,
                     key_map={}):
            urwid.Button.__init__(self, label=label,
                                  on_press=on_press,
                                  user_data=user_data)
            self.__key_map__ = key_map

        def keypress(self, size, key):
            return urwid.Button.keypress(self,
                                         size,
                                         self.__key_map__.get(key, key))

    class MappedRadioButton(urwid.RadioButton):
        def __init__(self, group, label, state='first True',
                     on_state_change=None, user_data=None,
                     key_map={}):
            urwid.RadioButton.__init__(self, group=group,
                                       label=label,
                                       state=state,
                                       on_state_change=on_state_change,
                                       user_data=user_data)
            self.__key_map__ = key_map

        def keypress(self, size, key):
            return urwid.RadioButton.keypress(self,
                                              size,
                                              self.__key_map__.get(key, key))

    class AudioProgressBar(urwid.ProgressBar):
        def __init__(self, normal, complete, sample_rate, current=0, done=100,
                     satt=None):
            urwid.ProgressBar.__init__(self,
                                       normal=normal,
                                       complete=complete,
                                       current=current,
                                       done=done,
                                       satt=satt)
            self.sample_rate = sample_rate

        def render(self, size, focus=False):
            """
            Render the progress bar.
            """

            #leeched from the orignal implementation
            #only the txt formatting differs

            (maxcol,) = size

            try:
                txt = urwid.Text("%d:%2.2d" % \
                                     ((self.current / self.sample_rate) / 60,
                                      (self.current / self.sample_rate) % 60),
                                 'center', 'clip')
            except ZeroDivisionError:
                txt = urwid.Text("0:00", 'center', 'clip')
            c = txt.render((maxcol,))

            cf = float(self.current) * maxcol / self.done
            ccol = int(cf)
            cs = 0
            if self.satt is not None:
                cs = int((cf - ccol) * 8)
            if ccol < 0 or (ccol == 0 and cs == 0):
                c._attr = [[(self.normal, maxcol)]]
            elif ccol >= maxcol:
                c._attr = [[(self.complete, maxcol)]]
            elif cs and c._text[0][ccol] == " ":
                t = c._text[0]
                cenc = self.eighths[cs].encode("utf-8")
                c._text[0] = t[:ccol] + cenc + t[ccol + 1:]
                a = []
                if ccol > 0:
                    a.append((self.complete, ccol))
                a.append((self.satt, len(cenc)))
                if maxcol - ccol - 1 > 0:
                    a.append((self.normal, maxcol - ccol - 1))
                c._attr = [a]
                c._cs = [[(None, len(c._text[0]))]]
            else:
                c._attr = [[(self.complete, ccol),
                    (self.normal, maxcol - ccol)]]
            return c

    class TrackplayGUI(urwid.Frame):
        def __init__(self, track_list, audio_output,
                     replay_gain=audiotools.player.RG_NO_REPLAYGAIN):
            def track_name(track):
                metadata = track.get_metadata()
                if (metadata is not None):
                    return metadata.track_name
                else:
                    return track.filename

            self.track_list = track_list
            self.audio_output = audio_output
            self.player = audiotools.player.Player(
                audio_output=audio_output,
                replay_gain=replay_gain,
                next_track_callback=self.next_track)

            self.track_name = urwid.Text("")
            self.album_name = urwid.Text("")
            self.artist_name = urwid.Text("")
            self.tracknum = urwid.Text("")
            self.length = urwid.Text("", align='right')
            self.channels = urwid.Text("", align='right')
            self.sample_rate = urwid.Text("", align='right')
            self.bits_per_sample = urwid.Text("", align='right')
            self.play_pause_button = MappedButton("Play",
                                                  on_press=self.play_pause,
                                                  key_map={'tab': 'right'})
            self.progress = AudioProgressBar(normal='pg normal',
                                             complete='pg complete',
                                             sample_rate=0,
                                             current=0,
                                             done=100)

            self.track_group = []

            track_len = sum([track.seconds_length() for track in track_list])

            if (len(self.track_list) == 1):
                self.status = urwid.Text(
                    "1 track, %(min)d:%(sec)2.2d minutes" % {
                        "count": len(self.track_list),
                        "min": int(track_len) / 60,
                        "sec": int(track_len) % 60},
                    align='center')
            else:
                self.status = urwid.Text(
                    "%(count)d tracks, %(min)d:%(sec)2.2d minutes" % {
                        "count": len(self.track_list),
                        "min": int(track_len) / 60,
                        "sec": int(track_len) % 60},
                    align='center')

            header = urwid.Pile([
                    urwid.Columns([
                            ('fixed', 9, urwid.Text(('header', u"Name : "),
                                                    align='right')),
                            ('weight', 1, self.track_name)]),
                    urwid.Columns([
                            ('fixed', 9, urwid.Text(('header', u"Album : "),
                                                    align='right')),
                            ('weight', 1, self.album_name)]),
                    urwid.Columns([
                            ('fixed', 9, urwid.Text(('header', u"Artist : "),
                                                    align='right')),
                            ('weight', 1, self.artist_name)]),
                    urwid.Columns([
                            ('fixed', 9, urwid.Text(('header', u"Track : "),
                                                    align='right')),
                            ('fixed', 7, self.tracknum),
                            ('fixed', 7, self.length),
                            ('fixed', 5, self.channels),
                            ('fixed', 10, self.sample_rate),
                            ('fixed', 7, self.bits_per_sample)]),
                    self.progress])

            controls = urwid.GridFlow([
                    MappedButton("Prev", on_press=self.previous_track,
                                 key_map={'tab': 'right'}),
                    self.play_pause_button,
                    MappedButton("Next", on_press=self.next_track,
                                 key_map={'tab': 'down'})],
                                      9, 4, 1, 'center')
            controls.set_focus(1)

            track_list = urwid.ListBox([
                    MappedRadioButton(group=self.track_group,
                                      label=track_name(track),
                                      user_data=(track, track.get_metadata()),
                                      on_state_change=self.select_track,
                                      key_map={'tab': 'down'})
                    for track in self.track_list])
            body = urwid.Pile([('fixed', 2, urwid.Filler(urwid.Pile([
                                controls, urwid.Divider(div_char='-')]))),
                               ('weight', 1, track_list)])
            footer = urwid.Pile([urwid.Divider(div_char='-'),
                                 self.status])

            urwid.Frame.__init__(self, body=body, header=header, footer=footer)

        def next_track(self, user_data=None):
            track_index = [g.state for g in self.track_group].index(True)
            try:
                self.track_group[track_index + 1].set_state(True)
            except IndexError:
                pass

        def previous_track(self, user_data=None):
            track_index = [g.state for g in self.track_group].index(True)
            try:
                if (track_index == 0):
                    raise IndexError()
                else:
                    self.track_group[track_index - 1].set_state(True)
            except IndexError:
                pass

        def select_track(self, radio_button, new_state, user_data,
                         auto_play=True):
            if (new_state == True):
                (track, metadata) = user_data
                if (metadata is not None):
                    self.track_name.set_text(metadata.track_name)
                    self.album_name.set_text(metadata.album_name)
                    self.artist_name.set_text(metadata.artist_name)
                    track_number = track.track_number()
                    track_total = metadata.track_total
                    if (track_number > 0):
                        if (track_total > 0):
                            self.tracknum.set_text(u"%d / %d" % \
                                                       (track_number,
                                                        track_total))
                        else:
                            self.tracknum.set_text(unicode(track_number))
                    else:
                        self.tracknum.set_text(u"")
                else:
                    self.track_name.set_text(u"")
                    self.album_name.set_text(u"")
                    self.artist_name.set_text(u"")
                    track_number = track.track_number()
                    if (track_number > 0):
                        self.tracknum.set_text(unicode(track_number))
                    else:
                        self.tracknum.set_text(u"")

                track_length = track.seconds_length()
                self.length.set_text("%d:%2.2d" % (int(track_length) / 60,
                                                  int(track_length) % 60))
                self.channels.set_text("%dch" % (track.channels()))
                self.sample_rate.set_text("%dHz" % (track.sample_rate()))
                self.bits_per_sample.set_text(
                    "%dbps" % (track.bits_per_sample()))

                self.progress.current = 0
                self.progress.done = track.total_frames()
                self.progress.sample_rate = track.sample_rate()

                self.player.open(track)
                if (auto_play):
                    self.player.play()
                    self.play_pause_button.set_label("Pause")

        def update_status(self):
            self.progress.set_completion(self.player.progress()[0])

        def play_pause(self, user_data):
            self.player.toggle_play_pause()
            if (self.play_pause_button.get_label() == "Play"):
                self.play_pause_button.set_label("Pause")
            elif (self.play_pause_button.get_label() == "Pause"):
                self.play_pause_button.set_label("Play")

        def stop(self):
            self.player.stop()
            self.play_pause_button.set_label("Play")

        def handle_text(self, i):
            if ((i == 'esc') or (i == 'q') or (i == 'Q')):
                self.perform_exit()
            elif ((i == 'n') or (i == 'N')):
                self.next_track()
            elif ((i == 'p') or (i == 'P')):
                self.previous_track()
            elif ((i == 's') or (i == 'S')):
                self.stop()

        def perform_exit(self, *args):
            self.player.close()
            raise urwid.ExitMainLoop()

    def timer(main_loop, trackplay):
        trackplay.update_status()
        loop.set_alarm_at(tm=time.time() + 1,
                          callback=timer,
                          user_data=trackplay)

    interactive_available = True
except ImportError:
    interactive_available = False


class TrackplayTTY:
    OUTPUT_FORMAT = (u"%(track_number)d/%(track_total)d " +
                     u"[%(sent_minutes)d:%(sent_seconds)2.2d / " +
                     u"%(total_minutes)d:%(total_seconds)2.2d] " +
                     u"%(channels)dch %(sample_rate)dHz " +
                     u"%(bits_per_sample)d-bit")

    def __init__(self, track_list, audio_output,
                 replay_gain=audiotools.player.RG_NO_REPLAYGAIN):
        self.track_list = track_list
        self.player = audiotools.player.Player(
            audio_output=audio_output,
            replay_gain=replay_gain,
            next_track_callback=self.next_track)
        self.track_index = -1
        self.current_track = None
        self.seconds_total = 0
        self.channels = 0
        self.sample_rate = 0
        self.bits_per_sample = 0
        self.track_number = 0
        self.track_total = len(track_list)

    def quit(self):
        if (self.current_track is not None):
            self.current_track = None
            self.player.close()

    def toggle_play_pause(self):
        if (self.current_track is not None):
            self.player.toggle_play_pause()

    def play(self):
        self.next_track()

    def stop(self):
        self.player.stop()

    def previous_track(self):
        if (self.track_index > 0):
            self.track_index -= 1
            self.current_track = self.track_list[self.track_index]
            self.channels = self.current_track.channels()
            self.sample_rate = self.current_track.sample_rate()
            self.bits_per_sample = self.current_track.bits_per_sample()
            self.track_number = self.track_index + 1
            self.player.open(self.current_track)
            self.player.play()

    def next_track(self):
        try:
            self.track_index += 1
            self.current_track = self.track_list[self.track_index]
            self.channels = self.current_track.channels()
            self.sample_rate = self.current_track.sample_rate()
            self.bits_per_sample = self.current_track.bits_per_sample()
            self.track_number = self.track_index + 1
            self.player.open(self.current_track)
            self.player.play()
        except IndexError:
            self.current_track = None
            self.player.close()

    def progress(self):
        return self.player.progress()

    def progress_line(self):
        return (self.OUTPUT_FORMAT %
                {"track_number": self.track_number,
                 "track_total": self.track_total,
                 "sent_minutes":
                     (frames_sent / self.sample_rate) / 60,
                 "sent_seconds":
                     (frames_sent / self.sample_rate) % 60,
                 "total_minutes":
                     (frames_total / self.sample_rate) / 60,
                 "total_seconds":
                     (frames_total / self.sample_rate) % 60,
                 "channels": self.channels,
                 "sample_rate": self.sample_rate,
                 "bits_per_sample": self.bits_per_sample})


if (__name__ == '__main__'):
    parser = audiotools.OptionParser(
        usage=_(u"%prog <track 1> [track 2] ..."),
        version="Python Audio Tools %s" % (audiotools.VERSION))

    parser.add_option(
        '-V', '--verbose',
        action='store',
        dest='verbosity',
        choices=audiotools.VERBOSITY_LEVELS,
        default=audiotools.DEFAULT_VERBOSITY,
        help=_(u'the verbosity level to execute at'))

    parser.add_option(
        '-I', '--interactive',
        action='store_true',
        default=False,
        dest='interactive',
        help=_(u'run in interactive mode'))

    parser.add_option(
        '-T', '--track-replaygain',
        action='store_true',
        default=False,
        dest='track_replaygain',
        help=_(u'apply track ReplayGain during playback, if present'))

    parser.add_option(
        '-A', '--album-replaygain',
        action='store_true',
        default=False,
        dest='album_replaygain',
        help=_(u'apply album ReplayGain during playback, if present'))

    players_map = dict([(player.NAME, player)
                        for player in audiotools.player.AUDIO_OUTPUT])

    parser.add_option(
        '-o', '--output',
        action='store',
        dest='output',
        choices=players_map.keys(),
        default=[player.NAME for player in audiotools.player.AUDIO_OUTPUT
                 if player.available()][0],
        help=_(u"the method to play audio (choose from: %s)") % \
            u", ".join([u"\"%s\"" % (player.NAME.decode('ascii'))
                        for player in audiotools.player.AUDIO_OUTPUT
                        if player.available()]))

    parser.add_option(
        '--shuffle', action='store_true', dest='shuffle', default=False,
        help='shuffle tracks')

    (options, args) = parser.parse_args()
    msg = audiotools.Messenger("trackplay", options)

    if (options.interactive and (not interactive_available)):
        msg.error(_(u"urwid is required for interactive mode"))
        msg.output(_(u"Please download and install " +
                     u"urwid from http://excess.org/urwid/"))
        msg.output(_(u"or your system's package manager."))
        sys.exit(1)

    audiofiles = audiotools.open_files(args, sorted=False, messenger=msg)
    if (options.shuffle):
        import random

        random.shuffle(audiofiles)

    if (options.track_replaygain):
        replay_gain = audiotools.player.RG_TRACK_GAIN
    elif (options.album_replaygain):
        replay_gain = audiotools.player.RG_ALBUM_GAIN
    else:
        replay_gain = audiotools.player.RG_NO_REPLAYGAIN

    if (options.interactive):
        trackplay = TrackplayGUI(track_list=audiofiles,
                                 audio_output=players_map[options.output](),
                                 replay_gain=replay_gain)

        if (len(audiofiles) > 0):
            trackplay.select_track(None, True,
                                   (audiofiles[0],
                                    audiofiles[0].get_metadata()), False)

        loop = urwid.MainLoop(trackplay,
                              [('header', 'default,bold', 'default', ''),
                               ('pg normal', 'white', 'black', 'standout'),
                               ('pg complete', 'white', 'dark blue'),
                               ('pg smooth', 'dark blue', 'black')],
                              unhandled_input=trackplay.handle_text)

        loop.set_alarm_at(tm=time.time() + 1,
                          callback=timer,
                          user_data=trackplay)

        try:
            loop.run()
        except termios.error:
            msg.error(_(u"Unable to get tty settings"))
            msg.info(_(u"If piping arguments via xargs(1), " +
                       u"try using its -o option"))
            sys.exit(1)
    else:
        try:
            original_terminal_settings = termios.tcgetattr(0)
        except termios.error:
            msg.error(_(u"Unable to get tty settings"))
            msg.info(_(u"If piping arguments via xargs(1), " +
                       u"try using its -o option"))
            sys.exit(1)

        trackplay = TrackplayTTY(track_list=audiofiles,
                                 audio_output=players_map[options.output](),
                                 replay_gain=replay_gain)
        trackplay.play()
        output_line_len = 0

        try:
            tty.setcbreak(sys.stdin.fileno())
            while (trackplay.current_track is not None):
                (frames_sent, frames_total) = trackplay.progress()
                output_line = trackplay.progress_line()
                msg.ansi_clearline()
                if (len(output_line) > output_line_len):
                    output_line_len = len(output_line)
                    msg.partial_output(output_line)
                else:
                    msg.partial_output(output_line +
                                       (u" " * (output_line_len -
                                                len(output_line))))

                (r_list, w_list, x_list) = select.select([sys.stdin.fileno()],
                                                         [], [], 1)
                if (len(r_list) > 0):
                    char = os.read(sys.stdin.fileno(), 1)
                    if ((char == 'q') or
                        (char == 'Q') or
                        (char == '\x1B')):
                        trackplay.quit()
                    elif (char == ' '):
                        trackplay.toggle_play_pause()
                    elif ((char == 'n') or
                          (char == 'N')):
                        trackplay.next_track()
                    elif ((char == 'p') or
                          (char == 'P')):
                        trackplay.previous_track()
                    elif ((char == 's') or
                          (char == 'S')):
                        trackplay.stop()
                    else:
                        pass

            msg.ansi_clearline()
        finally:
            termios.tcsetattr(0, termios.TCSADRAIN, original_terminal_settings)
