#!/usr/bin/perl

# most of this file is dedicated to work around the braindead ReadLine
# implementation.

BEGIN { $ENV{PERL_RL} |= "Perl o=0" }
BEGIN { eval "use Time::HiRes 'time'" }

$RC = "$ENV{HOME}/.mpg123shrc";

use Audio::Play::MPG123;
use Term::ReadLine;
use Fcntl; # required by MPG123 anyway
use Cwd;
use File::Glob;

$|=1;

do $RC;

sub write_rc {
   require Data::Dumper;
   print "writing $RC... ";
   open RC, ">$RC" or die "$RC: $!";
   print RC Data::Dumper->Dump([\%conf], ['*conf']);
   close RC;
   print "ok\n";
}

sub oconf {
   my ($cmd, $value) = @_;
   if ($cmd eq "log") {
      if ($value =~ /^\s*$/) {
         delete $conf{log};
      } else {
         $conf{log} = $value;
      }
      write_rc;
   } elsif ($cmd eq "conf") {
      while (my ($k, $v) = each %conf) {
         printf "%-10s => %s\n", $k, $v;
      }
   } else {
      print "unknown o command, use <log> or <conf>.\n";
   }
}

# do uri-style escaping PLUS escape space to  and back (sorry for that :()
sub uri_esc($) {
   local $_ = shift;
   s/([^\x21-\x24\x26-\x7e\xa0-\xb6\xb8-\xff])/sprintf "%c%02x", 0x25, ord($1)/ge;
   s/%20//g;
   $_;
}

sub uri_unesc($) {
   local $_ = shift;
   s//%20/g;
   s/%([0-9a-f][0-9a-f])/chr(hex($1))/gei;
   $_;
}

sub mp3log {
   my ($cmd, @args) = @_;
   if (defined $conf{log}) {
      if (open LOG, ">>$conf{log}") {
         print LOG "$cmd @args\n";
         close LOG;
      } else {
         warn "$conf{log}: $!\n";
      }
   }
}

my $current_url;
my $current_time;

sub load_url {
   my $url = shift;
   if (defined $current_url) {
      mp3log "T", $current_url, sprintf ("%0.2f", time - $current_time);
   }
   $current_url = $url;
   if (defined $url) {
      $current_time = time;
      $player->load(uri_unesc($url));
   }
}

END {
   load_url();
}

$SIG{INT} =
$SIG{HUP} =
$SIG{PIPE} =
$SIG{TERM} = sub { exit };

# terribly fool the Term::ReadLine packages..
sub Tk::DoOneEvent { }
sub Term::ReadLine::Tk::Tk_loop { &event_loop }
sub Term::ReadLine::Tk::register_Tk { }

$rl = new Term::ReadLine "mpg123sh";
$rl->tkRunning(1);

# contains tuples of the form [url, repeat]
@playlist=();
$p_url;
$p_repeat;

$player = new Audio::Play::MPG123;

sub file_completion {
   File::Glob::glob "$_[0]*", GLOB_MARK|GLOB_TILDE;
}

sub next_song {
   if ($p_repeat<=0 && @playlist) {
      my $p=shift @playlist;
      push @playlist,$p if $p;
      $p_url=$playlist[0][0];
      $p_repeat=$playlist[0][1];
   }
   if ($p_url) {
      $p_repeat--;
      load_url($p_url);
      mp3log("-", $p_url);
      $player->stat;
   }
}

$rl->Attribs->{completion_function} = sub {
   my($word,$line,$pos)=@_;
   $word ||= ""; $line ||= ""; $pos ||= 0;
   $p = "";
   $c = $word;
   $rl->Attribs->{completer_terminator_character}="";
   if ($pos==0) {
      if ($word=~/^(l(?:oad)?|a(?:dd)?|cd?)(\S.*)?/) {
         $p = $1." ";
         $c = $2;
      }
   }
   @r = file_completion uri_unesc($c);
   if (@r == 1) {
      if (-f $r[0]) {
         $rl->Attribs->{completer_terminator_character}=" ";
      } elsif (-d $r[0]) {
         $rl->Attribs->{completer_terminator_character}="/";
      }
   }
   #print "\n<$word|$line|$pos> = ",join(":",@r)," #",scalar@r,"\n";
   map $p.uri_esc($_),@r;
};

sub event_loop {
   my $r;
   my $rlin = $rl->IN;
   # most ugly workaround for perl-readline bug
   if ($rl->ReadLine eq "Term::ReadLine::Perl") {
      require IO::Handle;
      my $o = (fcntl $rlin,F_GETFL,0) & (O_APPEND | O_NONBLOCK);
      fcntl $rlin,F_SETFL,$o | O_NONBLOCK;
      my $eof = eof($rlin);
      fcntl $rlin,F_SETFL,$o;
      return unless $eof;
   }
   do {
      next_song if $player->state == 0;
      vec($r,fileno($rlin),1)=1;
      vec($r,fileno($player->IN),1)=1;
      select($r,undef,undef,undef);
      $player->poll(1) if vec($r,fileno($player->IN),1);
   } until vec($r,fileno($rlin),1);
}

# roughly every two seconds in the normal case
$player->statfreq(5);

print "\nmpg123sh, version $Audio::Play::MPG123::VERSION\n";
print "enter 'help' for a command list\n\n";

for(;;) {
   my $prompt=fastcwd." ";
   if ($player->state) {
      $player->stat;
      $prompt.=$player->title." ".$player->{frame}[2]."/".($player->{frame}[2]+$player->{frame}[3]);
   } else {
      $prompt.=$p_url;
   }
   $_=$rl->readline("$prompt> ");
   if (/^l(?:oad)?\s*(.*?)\s*$/i) {
      my $url = $player->canonicalize_url($1);
      load_url($url) or print "ERROR: ",$player->error,"\n";
      mp3log("l", $url);
      $player->stat;
   } elsif (/^a(?:dd)?\s*(.*?)\s*$/i) {
      push(@playlist,[$player->canonicalize_url($1),1]);
      next_song if $player->state == 0;
      mp3log("a", $playlist[-1][0]);
   } elsif (/^r(?:epeat)?\s*(\d+)\s*$/) {
      $playlist[-1][1] = $1;
   } elsif (/^p/i) {
      $player->pause;
   } elsif (/^d(?:el)?\s*(\d*)\s*$/i) {
      for (do {
         if ($1) {
            splice @playlist,$1-1,1;
         } else {
            $p_repeat=0;
            $player->stop;
            next_song;
            pop @playlist;
         }
      }) {
         mp3log("d", $_->[0]);
      }
   } elsif (/^s/i) {
      $p_repeat=0;
      $player->stop;
      mp3log("s", $playlist[0][0]);
      next_song;
   } elsif (/^c(?:d)?\s*(.*?)\s*$/i) {
      chdir $1 or print "Unable to change to '$1': $!\n";
   } elsif (/^j(?:ump)?\s*([0-9.]+)\s*$/i) {
      eval { $player->jump(int($1/$player->tpf)) };
   } elsif (/^o\s*(\w+)\s*(.*)/i) {
      oconf($1,$2);
   } elsif (/^q/i) {
      last;
   } elsif (/^i(nfo)?/i) {
      print "\n";
      if ($player->state) {
         print "currently playing: ",$player->url,"\n";
         printf "title:   %-32s artist: %-30s\n",$player->title,$player->artist;
         printf "album:   %-32s year:   %-30s\n",$player->album,$player->year;
         printf "comment: %-32s genre:  %-30s\n",$player->comment,$player->genre;
         print "\n";
         printf "MPEG %s layer %s, %d samples/s, %s, mode_extension is %d, %d bytes/frame\n".
                "%d channels, %s, %s, emphasis is %s, %d kbit/s\n",
                "I" x $player->type, $player->layer, $player->samplerate, $player->mode, $player->mode_extension,
                $player->bpf, $player->channels, $player->copyrighted ? "copyrighted" : "not copyrighted",
                $player->error_protected ? "error protection" : "no error protection", $player->emphasis ? "on" : "off",
                $player->bitrate;
         print "\n";
      }
      for (my $i=0; $i<=$#playlist; $i++) {
         printf "%2d: %-30s repeat %d\n",$i+1,"'$playlist[$i][0]'",$playlist[$i][1];
      }
      print "\n";
   } elsif (/^h(help)?/i) {
      print <<EOF;

load <file or url>     loads the specified file and plays it immediately.
add <file or url>      pushes the specified song to the end of the playlist
quit                   quits the program
info                   print information about the song and the playlist.
pause                  pause/unpause
stop                   stop current song (and play next)
cd <path>              change current directory
del <num>              remove song number <num> from the playlist
del                    remove the currently playing song
jump <second>          seek to the specified position
repeat <count>         repeat the last recently added song <count times>
help                   this listing
o                      manipulate configuration
o conf                 show current configuration
o log <path>           log all playing actions into file <path>

- most commands can be shortened to a one-letter form
- most whitespace between command and arguments is optional

EOF
   } elsif (/\S/) {
      print "unknown command, try 'help'\n";
   }
}


