#!/usr/bin/perl
# jukebox.pl - GTK2 jukebox wrapper for ogg123
# Copyright (C) 2002-2003  Toby A Inkster
# 
# ver 2.01
#    
# 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

use threads;
use threads::shared;
use Thread::Queue;

use Glib;
use Gtk2 '-init';
use Gtk2::SimpleList;

use constant TRUE  => 1;
use constant FALSE => 0;

# path
$path = $ENV{'JUKEBOX'} || '/usr/share/jukebox';

# queue for songs (and textual representation of it!)
my @queue : shared = ();
my $tQueue : shared = '';

# list of all songs
my @allSongs : shared = &getSongList;

# current song
my $nowSong : shared = '';
my $nowSongDetails : shared = '';
my $nowSongPid : shared = -1;
my $pidLock : shared = 0;
        
# InstaQuit
my $instaQuit : shared = 0;

$guiThread = threads->create(\&drawGui);

# main loop
while ($nowSong ne '*') {
        $nowSong = &qPop;
  if ($instaQuit == 0) {
          if (!defined $nowSong) {
                        $nowSong = &randomSong;
                }
          if ($nowSong ne '*') {
                        $nowSongDetails = &getDetails($nowSong);
                        &playSong($nowSong);
                }
        } else {
                exit;
        }
}

exit;

###################
## Player::Queue
###################

sub qPush {
        $song = shift @_;
  push @queue, $song;
        $tQueue = ppQueue(@queue);
}

sub qPop {
        $song = shift @queue;
        $tQueue = ppQueue(@queue);
  return $song;
}

sub ppQueue {
        $r = '';
  while ($s = shift) {
                $r .= '.' . $s . "\n" unless ($s eq '*');
                $r .= "and exit.\n" if ($s eq '*');
        }
        chomp $r;
  return $r;
}

###################
## Player::Play
###################

sub playSong {
        $song = shift @_;
        $attempts = 0;
        $success = 0;
  while ( ($attempts < 5) && ($success == 0) ) {
                sleep 1;
                $r = system("ogg123 -d esd -q '" . $path . $song . "'");
          # $r will be 0 if the song was played through successfully,
          # 15 if skipped, 256 if there was an ESD problem.
          if (($r == 0)||($r == 15)) {
                        $success++;
                } else {
                        $attempts++;
                }
        }
  if ($success == 0) {
                die "Couldn't play $song\n";
        }
}

sub killSong {
  # not great, but better'an nowt.
  system("killall ogg123");
}

sub getDetails {
        $song = shift @_;
        @l = split(/\n/, `vorbiscomment '$path$song'`);
        $r = '';
        %details = ();
  foreach $l (@l) {
                ($k,$v) = split(/=/, $l);
                $k =~ tr/[A-Z]/[a-z]/;
                $details{$k} = $v;
        }
  if ($details{'artist'}) {
                $r .= 'Artist: ' . $details{'artist'} . "\n";
        }
  if ($details{'title'}) {
                $r .= 'Title: ' . $details{'title'} . "\n";
        }
  if ($details{'album'}) {
                $r .= 'Album: ' . $details{'album'} . "\n";
        }
  if ($details{'date'}) {
                $r .= 'Date: ' . $details{'date'} . "\n";
        }
        $r .= 'File: .' . $song;
}

###################
## Player::Songlist
###################

sub randomSong {
        $r = int(rand($#allSongs + 1));
  return $allSongs[$r];
}

sub getSongList {
  # this bit should really be rewritten to use native perl
        @f = split(/\n/, `find $path | grep ogg | sort`);
        $l = length($path);
        @r = ();
  foreach $f (@f) {
                $s = substr($f,$l);
                @r = (@r, $s);
        }
  return @r;
}

###################
## GUI::Draw
###################

sub drawGui {

  # create main window
        $window = Gtk2::Window->new;
        $window->set_title('Jukebox');
        $window->signal_connect(destroy => \&eventExit);
        $window->set_border_width(3);

  # list of files
        $flist = Gtk2::SimpleList->new('File' => 'text');
  foreach $s (@allSongs) {
          push @{$flist->{data}}, [ $s ];
        }
        $sw1 = Gtk2::ScrolledWindow->new(undef, undef);
        $sw1->set_shadow_type ('etched-in');
        $sw1->set_policy ('automatic', 'automatic');
        $sw1->add($flist);

  # status field
        $statusfield = Gtk2::Label->new;
  Glib::Timeout->add (500, sub {
                $statusfield->set_text($nowSongDetails);
                $queuefield->set_text($tQueue);
          return 1;
        });

  # queue field
        $queuefield = Gtk2::Label->new('q');


  # boxes and frames for arranging things
        $vbox = Gtk2::VBox->new;
        $vbox->set_border_width(3);
        $frame = Gtk2::Frame->new('Controls');
        $frame->set_border_width(3);
        $frame->add($vbox);
        $hbox = Gtk2::HBox->new;
        $hbox->set_border_width(3);
        $hbox->pack_start($sw1, TRUE, TRUE, 0);
        $hbox->pack_start($frame, FALSE, FALSE, 0);
        $hr = Gtk2::HSeparator->new;
        $outerbox = Gtk2::VBox->new;
        $outerbox->pack_start($hbox, TRUE, TRUE, 0);
        $outerbox->pack_start($statusfield, FALSE, FALSE, 0);
        $outerbox->pack_start($hr, FALSE, FALSE, 0);
        $outerbox->pack_start($queuefield, FALSE, FALSE, 0);
        $window->add($outerbox);

  # buttons for controls
        $button{'add'} = Gtk2::Button->new('En_queue');
        $vbox->pack_start($button{'add'}, FALSE, FALSE, 0);
        $button{'add'}->signal_connect(clicked => \&eventBtnAdd);

        $button{'skip'} = Gtk2::Button->new('_Skip');
        $vbox->pack_start($button{'skip'}, FALSE, FALSE, 0);
        $button{'skip'}->signal_connect(clicked => \&eventBtnSkip);

        $button{'end'} = Gtk2::Button->new('_Delayed Exit');
        $vbox->pack_start($button{'end'}, FALSE, FALSE, 0);
        $button{'end'}->signal_connect(clicked => \&eventBtnEnd);

        $button{'quit'} = Gtk2::Button->new('E_xit');
        $button{'quit'}->signal_connect(clicked => \&eventExit);
        $vbox->pack_end($button{'quit'}, FALSE, FALSE, 0);

  # show window and enter GTK+2 loop
        $window->set_default_size( 640, 480 );
        $window->show_all;
        Gtk2->main;
        
        $instaQuit = 1;

}

###################
## GUI::EventHandlers
###################

sub eventBtnAdd {
        @indices = $flist->get_selected_indices();
  foreach $i (@indices) {
                &qPush($allSongs[$i]);
        }
}

sub eventBtnEnd {
        &qPush('*');
}

sub eventBtnSkip {
        &killSong;
}

sub eventExit {
        $instaQuit = 1;
        &killSong;
        Gtk2->main_quit;     
}