#!/usr/bin/perl
use strict;
use POSIX;
use Socket;
use Pod::Usage;
use Getopt::Long;
use Cwd qw(realpath);

my $cfgdir		= sprintf('%s/.pjam', $ENV{HOME});
my $cfgfile		= sprintf('%s/config', $cfgdir);
my $pjam_version	= '1.2.0';
my $pjam_desc		= "\npjam-remote version $pjam_version\nA console remote control for PerlJammer\n";
my %opts;

if (GetOptions(\%opts,
               'debug|d',
               'help|usage|?',
               'man',
               'silent',
               'version'))
{
    if ($opts{version})
    {
        pod2usage(-message => $pjam_desc,
                  -exitstatus => 0,
                  -verbose => 0);
    }
    elsif ($opts{help})
    {
        pod2usage(-message => $pjam_desc,
                  -exitstatus => 0,
                  -verbose => 1);
    }
    elsif ($opts{man})
    {
        pod2usage(-exitstatus => 0,
                  -verbose => 2);
    }
    else
    {
        print "DEBUGGING\n" if ($opts{debug});
        remote();
    }
}
else
{
    pod2usage(-message => $pjam_desc,
              -exitstatus => -1,
              -verbose => 1);
}

exit (0);


sub remote
{
    my @cmds = qw(quit stop wait play pause unpause resume play_or_pause next next_song
                  prev previous prev_song restart restart_song first first_song start last
                  last_song end delete drop drop_song disable disable_song disc play_disc
                  insert_next nowplaying status);

    my @filecmds = qw(insert instant replace);

    if (-d $cfgdir)
    {
        if (-f $cfgfile)
        {
            get_prefs(\%opts, $cfgfile);

            unshift (@ARGV, 'insert') if ($0 =~ m'/pjam-insert$');

            if (scalar @ARGV == 0)
            {
                print "No command.  Doing nothing!\n";
            }
            elsif (scalar @ARGV == 1)
            {
                if (grep(/^$ARGV[0]$/i, @cmds))
                {
                    send_socket_cmd($ARGV[0]);
                }
                elsif (grep(/^$ARGV[0]$/i, @filecmds))
                {
                    printf("pjam-remote: '%s' requires one or more file arguments.\n",
                           $ARGV[0]);
                }
                else
                {
                    printf("pjam-remote: '%s' is not a known command\n",
                           $ARGV[0]);
                }
            }
            elsif (scalar @ARGV == 2)
            {
                if ($ARGV[0] =~ /^status$/i)
                {
                    if ($ARGV[1] =~ /^\d+$/)
                    {
                        send_socket_cmd(@ARGV);
                    }
                    else
                    {
                        print "pjam-remote: 'status' accepts only a single (optional) numeric argument.\n";
                    }
                }
                elsif (grep(/^$ARGV[0]$/i, @cmds))
                {
                    printf("pjam-remote warning: '%s' does not accept arguments.\n",
                           $ARGV[0]);
                    send_socket_cmd(@ARGV);
                }
                elsif (grep(/^$ARGV[0]$/i, @filecmds))
                {
                    send_socket_cmd(@ARGV);
                }
                elsif (grep(/^$ARGV[1]$/i, @cmds))
                {
                    $opts{remotehost} = $ARGV[0];
                    send_socket_cmd($ARGV[1]);
                }
                elsif (grep(/^$ARGV[1]$/i, @filecmds))
                {
                    printf("pjam-remote: '%s' requires one or more file arguments.\n",
                           $ARGV[1]);
                }
                else
                {
                    printf("pjam-remote: '%s' is not a known command\n",
                           $ARGV[1]);
                }
            }
            else
            {
                if ($ARGV[0] =~ /^status$/i)
                {
                    if ($ARGV[1] =~ /^\d+$/ && scalar @ARGV == 2)
                    {
                        send_socket_cmd(@ARGV);
                    }
                    else
                    {
                        print "pjam-remote warning: 'status' accepts only a single (optional) numeric argument.\n";
                    }
                }
                elsif (grep(/^$ARGV[0]$/i, @cmds))
                {
                    printf("pjam-remote warning: '%s' does not accept arguments.\n",
                           $ARGV[0]);
                    send_socket_cmd(@ARGV);
                }
                elsif (grep(/^$ARGV[0]$/i, @filecmds))
                {
                    print "pjam-remote warning: 'replace' accepts only a single argument.\n"
                           if ($ARGV[0] =~ /^replace$/i);
                    send_socket_cmd(@ARGV);
                }
                elsif ($ARGV[1] =~ /^status$/i)
                {
                    if ($ARGV[2] =~ /^\d+$/ && scalar @ARGV == 3)
                    {
                        $opts{remotehost} = shift(@ARGV);
                        send_socket_cmd(@ARGV);
                    }
                    else
                    {
                        print "pjam-remote: 'status' accepts only a single (optional) numeric argument.\n";
                    }
                }
                elsif (grep(/^$ARGV[1]$/i, @cmds))
                {
                    $opts{remotehost} = shift(@ARGV);
                    if ($ARGV[0] =~ /^status$/i)
                    {
                        if ($ARGV[1] =~ /^\d+$/)
                        {
                            print "pjam-remote warning: 'status' accepts only a single (optional) numeric argument.\n" if (scalar @ARGV > 2);
                            send_socket_cmd(@ARGV);
                        }
                        else
                        {
                            print "pjam-remote: 'status' accepts only a single (optional) numeric argument.\n";
                        }
                    }
                    else
                    {
                        printf("pjam-remote warning: '%s' does not accept arguments.\n",
                               $ARGV[0]);
                        send_socket_cmd($ARGV[0]);
                    }
                }
                elsif (grep(/^$ARGV[1]$/i, @filecmds))
                {
                    $opts{remotehost} = shift(@ARGV);
                    print "pjam-remote warning: 'replace' accepts only a single argument.\n"
                           if ($ARGV[0] =~ /^replace$/i && scalar(@ARGV) > 2);
                    send_socket_cmd(@ARGV);
                }
                else
                {
                    print "pjam-remote: No known command found\n";
                }
            }
        }
        else
        {
            print "Configuration file $cfgfile not found; cannot continue.\n";
        }
    }
    else
    {
        print "Configuration directory $cfgdir not found; cannot continue.\n";
    }
}


sub get_prefs
{
    my $o = $_[0];

    open(PREFS, $_[0]);
    while (<PREFS>)
    {
        next if (/^#/);
        if (/^(\S+)\s*[:=]\s+(yes|true)$/i)
        {
            $o->{$1} = 1;
        }
        elsif (/^(\S+)\s*[:=]\s+(no|false)$/i)
        {
            $o->{$1} = 0;
        }
        elsif (/^(\S+)\s*[:=]\s+"?([^"]+)"\s*$/)        # " reset
        {
            $o->{$1} = $2;
        }
        elsif (/^(\S+)\s*[:=]\s+(\S.*\S)\s*$/)
        {
            $o->{$1} = $2;
        }
        elsif (/^(\S+)\s*[:=]\s+(\d+)\s*$/)
        {
            $o->{$1} = $2;
        }
    }

    $o->{remoteport} = 16384 unless (defined $o->{remoteport});
    $o->{remotehost} = 'localhost' unless (defined $o->{remotehost});
}


sub send_socket_cmd
{
    $_[0] =~ tr/A-Z/a-z/;

    # Normalize all file paths
    for (my $i=1; $i < scalar @_; $i++)
    {
        if ($_[$i] =~ /\.(ogg|mp3)$/)
        {
            my $p = realpath($_[$i]);
            printf("%s -> %s\n", $_[$i], $p) if ($opts{debug});
            $_[$i] = $p unless ($_[$i] eq $p);
        }
    }

    chomp(my $data = join(' ', @_));
    my ($proto, $iaddr, $paddr);

    if ($opts{remotehost} =~ /:\d+$/)
    {
        ($opts{remotehost}, $opts{remoteport}) = split(/:/, ($opts{remotehost}));
    }

    $proto  = getprotobyname('tcp');
    if ($iaddr  = inet_aton($opts{remotehost}))
    {
        $paddr  = sockaddr_in($opts{remoteport}, $iaddr);

        if (socket(SOCK, PF_INET, SOCK_STREAM, $proto))
        {
            if (connect(SOCK, $paddr))
            {
                select((select(SOCK), $| = 1)[0]);
                print SOCK "$data\n";

                print "Sent command: $data\n" if ($opts{debug});

                if ($_[0] eq 'nowplaying' || $_[0] eq 'status')
                {
                    my ($rin, $rout);

                    while (!eof(SOCK))
                    {
                        $rin = '';
                        vec($rin, fileno(SOCK), 1) = 1;
                        select($rout = $rin, undef, undef, 0.1);
                        if (vec($rout, fileno(SOCK), 1))
                        {
                            $data = <SOCK>;
                            if (length $data)
                            {
                                print $data;
                            }
                        }
                    }
                }
            }
            else
            {
                die "connect: $!" unless ($opts{silent});
            }
            close (SOCK);
        }
        else
        {
            die "socket: $!" unless ($opts{silent});
        }
    }
    else
    {
        printf("pjam-remote: host '%s' is unknown\n",
               $opts{remotehost});
    }

}

__END__

=head1 NAME

B<pjam-remote> - A console remote control for B<PerlJammer>

=head1 VERSION

Version 1.2.0

=head1 SYNOPSIS

pjam-remote [remotehost[:remoteport]] command [songfile [songfile ...]]
pjam-remote [options]

  Options:
    -help, -usage, -?
    -man
    -silent
    -version

=head1 OPTIONS

=over 4

=item B<-help, -usage, -?>

List command-line options and usage, then exit

=item B<-man>

Display full documentation and exit

=item B<-silent>

Suppress failure messages.  It is advised that this option be used with care, and only
in cases when PerlJammer may not be running and you don't care if pjam-remote fails.

=item B<-version>

Display version string and exit

=back

=head1 DESCRIPTION

B<pjam-remote> is the console remote-control tool for B<PerlJammer>.  It sends commands
to B<PerlJammer> via its network socket (unless, obviously, B<PerlJammer> was started
using -nonet).  It reads the B<PerlJammer> config file, $HOME/.pjam/config, to determine
the correct network port (default: 16384) and host (default: localhost) at which to contact
B<PerlJammer>.  The target host may optionally be specified as the first argument on the
command line.  A reasonable attempt is made to sanely parse varying commandline arguments,
but it does not attempt to intercept all possible problems between chair and keyboard.

=head1 COMMANDS

B<PerlJammer> and B<pjam-remote> recognize the following list of case-insensitive commands:

=over 4

=item B<Unary commands:>

quit, stop, wait, play, pause/unpause/resume, play_or_pause, next/next_song, prev/previous/prev_song,
restart/restart_song. first/first_song/start, last/last_song/end, delete/drop/drop_song,
disable/disable_song, disc/play_disc, insert_next, nowplaying/status

=item B<Commands with arguments>

insert, instant, replace, status

=back

The B<replace> command takes a single full pathname to an MP3 file as an argument.  The
B<insert> and B<instant> commands take a list of one or more MP3 file pathnames.  The B<status>
command accepts, but does not require, a single numeric argument.  All other commands accept
no arguments.  B<pjam-remote> will emit a warning if excess arguments are supplied, but will
continue anyway.  B<pjam-remote> will attempt to normalize relative file paths to real
(absolute) paths, but does not guarantee success.

See the B<PerlJammer> manual for full documentation on these commands.

B<pjam-remote> may also be linked to, and invoked as, B<pjam-insert>.  In this case, no
command or host should be specified; it is B<assumed> that the command is 'insert' and the
host is the default host specified as 'remotehost' in the $HOME/.pjam/config file.

=head1 REPORTING BUGS

Please send all bug reports to the author.

=head1 LICENSE

B<pjam-remote>, part of the B<PerlJammer> package, is copyright (C) 2003 Phil Stracchino.

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 3 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, see <http://www.gnu.org/licenses/>.

=head1 OBTAINING PerlJammer

B<PerlJammer> can be downloaded from http://co.ordinate.org/perljammer/.

=head1 AUTHOR

B<PerlJammer> and its supporting tools are written and maintained by Phil Stracchino
(phil@co.ordinate.org).

=cut

