#!/usr/bin/env perl
#
# Copyright © 2012,2019-2021 Nick Bowler
#
# Send commands to MPD.  Each command-line argument is quoted as necessary
# so it appears as a single argument at the protocol level.  The result is
# printed to standard output.
#
# License GPLv3+: GNU General Public License version 3 or any later version.
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

use strict;

use utf8;

use Encode qw(decode encode);
use Encode::Locale qw(decode_argv);
decode_argv(Encode::FB_CROAK);

binmode(STDOUT, ":utf8");
binmode(STDIN, ":utf8");

use Getopt::Long qw(:config gnu_getopt);

use FindBin;
use lib "$FindBin::Bin";
use MPDHacks;

my ($quiet, $verbose, $binary, $ignore_errors, $download);

sub print_version {
	print <<EOF
mpdexec.pl 0.9
Copyright © 2021 Nick Bowler
License GPLv3+: GNU General Public License version 3 or any later version.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
EOF
}

sub print_usage {
	my ($fh) = (@_, *STDERR);

	print $fh "Usage: $0 [options] [command ...]\n";
	print $fh "Try $0 --help for more information.\n" unless (@_ > 0);
}

sub print_help {
	print_usage(*STDOUT);
	print <<EOF
This is "mpdexec": a tool to send simple commands to MPD.

Options:
  -h, --host=HOST   Connect to the MPD server on HOST, overriding defaults.
  -p, --port=PORT   Connect to the MPD server on PORT, overriding defaults.
  -q, --quiet       Do not output any response messages.  Only errors (on
                    standard error) or binary data (if enabled) are output.
  -v, --verbose     Print commands as they are submitted.  If both --quiet
                    and --verbose are specified, --verbose has no effect.
  -b, --binary[=FILE]
                    Output raw binary response data, which is normally not
                    written.  If FILE is specified, the data is written there.
                    Otherwise, --quiet is automatically enabled and the data
                    goes to standard output.
  --download        Enable automatic sequencing of albumart and readpicture
                    commands; if this option is specified, such commands
                    without offsets will be expanded into multiple commands
                    in order to download the entire file.
  --ignore-errors   In batch mode, continue submitting commands after errors.
  -V, --version     Print a version message and then exit.
  -H, --help        Print this message and then exit.

Report bugs to <nbowler\@draconx.ca>.
EOF
}

GetOptions(
	'host|h=s'         => \$MPD::host,
	'port|p=s'         => \$MPD::port,

	'quiet|q'          => \$quiet,
	'no-quiet'         => sub { $quiet = 0; },
	'verbose|v'        => \$verbose,
	'no-verbose'       => sub { $verbose = 0 },
	'binary|b:s'       => \$binary,
	'no-binary'        => sub { $binary = undef; },

	'download'         => \$download,
	'no-download'      => sub { $download = 0; },

	'ignore-errors'    => \$ignore_errors,
	'no-ignore-errors' => sub { $ignore_errors = 0; },

	'V|version'        => sub { print_version(); exit },
	'H|help'           => sub { print_help(); exit },
) or do { print_usage; exit 1 };

my $binfile = *STDOUT;
if ($binary) {
	if ($binary ne "-") {
		open(my $fh, ">", $binary) or die "failed to open $binary: $!";
		$binfile = $fh;
	}
}
$quiet = 1 if (defined($binary) && $binary eq "");
$verbose = 0 if ($quiet);

my $sock = MPD::connect(binmode => ":raw");

sub read_binary {
	my ($count) = @_;
	my $buf;

	binmode($binfile);

	return 0 unless ($count);
	my $rc = $sock->read($buf, $count) or die "$!";
	if (defined($binary)) {
		$binfile->write($buf) or die "$!";
	}

	return $rc;
}

sub mpd_send {
	my $cmd = encode('UTF-8', join(' ', @_), Encode::FB_QUIET);
	print "$cmd\n" if ($verbose);
	print $sock $cmd, $/;
}

my %downloadcmds = map { $_ => 1 } ( "albumart", "readpicture" );
sub mpd_exec {
	my $downloadseq;

	# special case for "albumart" and "readpicture"; if no offset is
	# specified (invalid command) we synthesize a sequence of albumart
	# commands to retrieve the entire file.
	if ($download && $downloadcmds{$_[0]} && @_ == 2) {
		$_[2] = 0;
		$downloadseq = 2;
	}

	mpd_send(@_);
	while (<$sock>) {
		$_ = decode('UTF_8', $_, Encode::FB_QUIET);

		if (/^OK/) {
			last unless ($downloadseq);
			print $sock encode('UTF-8',
			                   join(' ', @_),
					   Encode::FB_QUIET), $/;
			next;
		}

		if (/^binary: ([0-9]+)$/) {
			print unless ($quiet);
			read_binary($1);

			if ($downloadseq) {
				$downloadseq = 0 unless ($1);
				$_[$downloadseq] += $1;
			}
		} elsif (/^ACK/) {
			*STDOUT->flush;
			print STDERR;
			last if ($ignore_errors);
			exit 1;
		} else {
			print unless ($quiet);
		}
	}
}

if (@ARGV) {
	mpd_exec(map { MPD::escape } @ARGV)
} elsif ($ignore_errors) {
	while (<>) { chomp; mpd_exec($_); }
} else {
	mpd_send("command_list_begin");
	while (<>) { chomp; mpd_send($_); }
	mpd_exec("command_list_end");
}

print $sock "close\n";
close $sock;
