#!/usr/bin/perl
#
# MPD perl module
# Written for MPD 0.12.0 (SVN)
#
# Written by: Tue Abrahamsen (twoface@wtf.dk)
# This project's homepage is: http://www.musicpd.org
# Report bugs at: http://www.musicpd.org/mantis/
#
# 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
#
package Audio::MPD;
use IO::Socket;
use warnings;
use strict;

our $VERSION = '0.12.2';



###############################################################
#                        CONFIGURATION                        #
#-------------------------------------------------------------#
#   Only holds the hash specifying different configuration-   #
#  values used by the module. These may not be changed during #
#   runtime, but can be altered for the programmers wishes.   #
###############################################################

my %config = (
	# Overwrites old playlist, if a playlist is saved with the same
	# name. Otherwise, an error is returned. Default: yes
	OVERWRITE_PLAYLIST => 1,
	# Allows toggling repeat and random states by not specifying
	# parameteres. Default: yes
	ALLOW_TOGGLE_STATES => 1,
	# The default host to connect to, if no other host is specified.
	DEFAULT_MPD_HOST => 'localhost',
	# The default port to connect to, if no other port is specified.
	DEFAULT_MPD_PORT => 6600,
);


###############################################################
#                       BASIC METHODS                         #
#-------------------------------------------------------------#
#  This section contains all basic methods for the module to  #
#     function, internal methods and methods not returning    #
#      or altering information about playback and alike.      #
###############################################################

sub new
{
	my $class = shift;
	my($mpd_host,$mpd_port) = @_;
	
	my $self = {
		# Version of MPD server
		server_version => undef,
		password => undef,
		# Variables for ACK error
		ack_error_id => undef,
		ack_error_command_id => undef,
		ack_error_command => undef,
		ack_error => undef,
		# MPD connection information
		mpd_host => $mpd_host || $ENV{'MPD_HOST'} || $config{'DEFAULT_MPD_HOST'},
		mpd_port => $mpd_port || $ENV{'MPD_PORT'} || $config{'DEFAULT_MPD_PORT'},
		# Socket handle
		sock => undef,
		# Array holding playlist-entries in hashes
		playlistref => [],
		# Variables set by command 'status'
		volume => undef,
		repeat => undef,
		random => undef,
		state => undef,
		playlist => -1,
		playlistlength => undef,
		bitrate => undef,
		xfade => undef,
		audio => undef,
		error => undef,
		song => undef,
		time => undef,
		# Variables set by command 'stats'
		artists => undef,
		albums => undef,
		songs => undef,
		uptime => undef,
		db_playtime => undef,
		db_update => undef,
		playtime => undef,
		# 0.12.0 stuff
		outputs => undef,
		commands => undef,
		notcommands => undef,
	};
	bless($self,$class);
	
	# Check for password in host name
	if($self->{mpd_host} =~ /@/)
	{
		($self->{password},$self->{mpd_host}) = split('@',$self->{mpd_host});
	}
	
	$self->_connect;
	$self->send_password if $self->{password};
	return $self;
}

sub is_connected
{
	my($self) = shift;
	# No need to check, if socket has not been initialized
	if($self->{sock})
	{
		$self->{sock}->print("ping\n");
		if($self->{sock}->getline() =~ /^OK/)
		{
			return 1;
		} else {
			return undef;
		}
	}
	return undef;
}

sub close_connection
{
	my($self) = shift;
	$self->_disconnect();
	return 1;
}

sub kill_mpd
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("kill\n");
	return 1;
}

sub send_password
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("password ".$self->{password}."\n");
	$self->_process_feedback;
	return 1;
}

sub get_urlhandlers
{
	my($self) = shift;
	$self->_connect;
	my @handlers;
	$self->{sock}->print("urlhandlers\n");
	foreach($self->_process_feedback)
	{
		push(@handlers, $1) if /^handler: (.+)$/;
	}
	return @handlers;
}

sub get_error
{
	my($self) = shift;
	return ( 							# Let's return an array
		$self->{ack_error_id},			# [0] What is the ID of the error?
		$self->{ack_error},				# [1] Human readable error-message
		$self->{ack_error_command},		# [2] The command that caused the error
		$self->{ack_error_command_id}	# [3] What number the command was in the command_list (if used)
	);
}

sub get_server_version
{
	my($self) = shift;
	return $self->{server_version};
}


###############################################################
#               METHODS FOR ALTERING SETTINGS                 #
#-------------------------------------------------------------#
#  This section contains methods used for altering different  #
#                     settings in MPD.                        #
###############################################################

sub set_repeat
{
	my($self,$mode) = @_;
	$self->_connect;
	
	# If the mode is not set, and ALLOW_TOGGLE_STATUS is, return false!
	return undef if((!defined($mode) && !$config{'ALLOW_TOGGLE_STATUS'}) || $mode !~ /^(0|1)$/); 
	
	# If mode is not set, shift the current status
	$mode = ($self->{repeat} == 1 ? 0 : 1) if !defined($mode);
	
	$self->{sock}->print("repeat $mode\n");
	$self->{repeat} = $mode;
	return $self->_process_feedback;
}

sub set_random
{
	my($self,$mode) = @_;
	$self->_connect;

	# If the mode is not set, and ALLOW_TOGGLE_STATUS is, return false!
	return undef if((!defined($mode) && !$config{'ALLOW_TOGGLE_STATUS'}) || $mode !~ /^(0|1)$/); 
	
	# If mode is not set, shift the current status
	$mode = ($self->{random} == 1 ? 0 : 1) if !defined($mode);
	
	$self->{sock}->print("random $mode\n");
	$self->{random} = $mode;
	return $self->_process_feedback;
}

sub set_fade
{
	my($self,$fade_value) = @_;
	$self->_connect;
	$fade_value = 0 if !defined($fade_value);
	$self->{sock}->print("crossfade $fade_value\n");
	$self->{xfade} = $fade_value;
	return $self->_process_feedback;
}

sub set_volume
{
	my($self,$volume) = @_;
	$self->_connect;

	if($volume =~ /^(-|\+)(\d+)/ && defined($self->{volume}))
	{
		$volume = $self->{volume} + $2 if $1 eq '+';
		$volume = $self->{volume} - $2 if $1 eq '-';
	}
	
	return undef if !defined($volume) || $volume < 0 || $volume > 100;

	$self->{sock}->print("setvol $volume\n");
	$self->{volume} = $volume;
	return $self->_process_feedback;
}

sub output_enable
{
	my($self,$output) = @_;
	$self->_connect;
	return undef if(!defined($output) || $output !~ /^\d+$/);
	$self->{sock}->print("enableoutput $output\n");
	my @tmp = $self->_process_feedback;
	$self->_get_outputs;
	return @tmp;
}

sub output_disable
{
	my($self,$output) = @_;
	$self->_connect;
	return undef if(!defined($output) || $output !~ /^\d+$/);
	$self->{sock}->print("disableoutput $output\n");
	my @tmp = $self->_process_feedback;
	$self->_get_outputs;
	return @tmp;
}

###############################################################
#                METHODS FOR COMMON PLAYBACK                  #
#-------------------------------------------------------------#
#   This section contains the most commonly used methods for  #
#                    altering playback.                       #
###############################################################

sub play
{
	my($self,$number,$from_id) = @_;
	$self->_connect;
	$number = '' if !defined($number);
	my $command = (defined($from_id) && $from_id == 1 ? 'playid' : 'play');
	$self->{sock}->print("$command $number\n");
	return $self->_process_feedback;
}

sub playid
{
	my($self,$number) = @_;
	$number = '' if !defined($number);
	return $self->play($number,1);
}

sub pause
{
	my($self,$state) = @_;
	
	# Default is to pause
	$state = 1 unless (defined $state);
	
	$self->_connect;
	$self->{sock}->print("pause $state\n");
	return $self->_process_feedback;
}

sub stop
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("stop\n");
	return $self->_process_feedback;
}

sub next
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("next\n");
	return $self->_process_feedback;
}

sub prev
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("previous\n");
	return $self->_process_feedback;
}

sub seek
{
	my($self,$position,$song,$from_id) = @_;
	$self->_connect;
	my $command = (defined($from_id) && $from_id == 1 ? 'seekid' : 'seek');
	$position = int($position) if(defined($position)); # Go INT!
	if(defined($song) && defined($position) && $song =~ /^\d+$/ && $position =~ /^\d+$/)
	{
		$self->{sock}->print("$command $song $position\n");
	} elsif(defined($position) && $position =~ /^\d+$/ && defined($self->{song})) {
		$self->{sock}->print("$command ".$self->{song}." $position\n");
	} else {
		return undef;
	}
	return $self->_process_feedback;
}

sub seekid
{
	my($self,$position,$songid) = @_;
	return undef if !defined($position) || !defined($songid);
	return $self->seek($position,$songid,1);
}

###############################################################
#               METHODS FOR PLAYLIST-HANDLING                 #
#-------------------------------------------------------------#
#  This section contains all methods which has anything to do #
#            with the current or saved playlists.             #
###############################################################

sub clear
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("clear\n");
	return $self->_process_feedback;
}

sub add
{
	my($self,$path) = @_;
	$self->_connect;
	$path = '' if !defined($path);
	$self->{sock}->print("add \"$path\"\n");
	return $self->_process_feedback;
}

sub delete
{
	my($self,$song,$from_id) = @_;
	$self->_connect;
	return undef if !defined($song);
	my $command = (defined($from_id) && $from_id == 1 ? 'deleteid' : 'delete');
	if($song =~ /^(\d+)-(\d+)$/)
	{
		for(my $i = $2 ; $i >= $1 ; $i--)
		{
			$self->$command($i);
		}
	} else {
		$self->{sock}->print("$command $song\n");
		return $self->_process_feedback;
	}
	return 1;
}

sub deleteid
{
	my($self,$songid) = @_;
	return undef if !defined($songid) || $songid !~ /^\d+$/;
	return $self->delete($songid,1);
}

sub load
{
	my($self,$playlist) = @_;
	return undef if !defined($playlist);
	$self->_connect;
	$self->{sock}->print("load \"$playlist\"\n");
	return $self->_process_feedback;
}

sub updatedb
{
	my($self, $path) = shift;
	
	$path = '' unless (defined $path);
	
	$self->_connect;
	$self->{sock}->print("update $path\n");
	return $self->_process_feedback;
}

sub swap
{
	my($self,$song_from,$song_to,$from_id) = @_;
	$self->_connect;
	if(defined($song_from) && defined($song_to) && $song_from =~ /^\d+$/ && $song_to =~ /^\d+$/)
	{
		my $command = (defined($from_id) && $from_id == 1 ? 'swapid' : 'swap');
		$self->{sock}->print("$command $song_from $song_to\n");
	} else {
		return undef;
	}
	return $self->_process_feedback;
}

sub swapid
{
	my($self,$songid_from,$songid_to) = @_;
	return undef if !defined($songid_from) || !defined($songid_to) || $songid_from !~ /^\d+$/ || $songid_to !~ /^\d+$/;
	return $self->swap($songid_from,$songid_to,1);
}

sub shuffle
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("shuffle\n");
	return $self->_process_feedback;
}

sub move
{
	my($self,$song,$new_pos,$from_id) = @_;
	$self->_connect;
	if(defined($song) && defined($new_pos) && $song =~ /^\d+$/ && $new_pos =~ /^\d+$/)
	{
		my $command = (defined($from_id) && $from_id == 1 ? 'moveid' : 'move');
		$self->{sock}->print("$command $song $new_pos\n");
	} else {
		return undef;
	}
	return $self->_process_feedback;
}

sub moveid
{
	my($self,$songid,$new_pos) = @_;
	return undef if !defined($songid) || !defined($new_pos) || $songid !~ /^\d+$/ || $new_pos !~ /^\d+$/;
	return $self->move($songid,$new_pos,1);
}

sub rm
{
	my($self,$playlist) = @_;
	return undef if !defined($playlist);
	$self->_connect;
	$self->{sock}->print("rm \"$playlist\"\n");
	return $self->_process_feedback;
}

sub save
{
	my($self,$playlist) = @_;
	return undef if !defined($playlist);
	$self->_connect;
	$self->{sock}->print("save \"$playlist\"\n");
	if(!$self->_process_feedback)
	{
		# Does the playlist already exist?
		if(${$self->get_error}[0] eq '56' && $config{'OVERWRITE_PLAYLIST'})
		{
			$self->rm($playlist);
			$self->save($playlist);
			return 1;
		}
	}
	return 1;
}

sub search
{
	my($self,$type,$string,$strict) = @_;
	return undef if !defined($type) || !defined($string) || $type !~ /^(artist|album|title|filename)$/;
	$self->_connect;
	my $command = (!defined($strict) || $strict == 0 ? 'search' : 'find');
	$self->{sock}->print("$command $type \"$string\"\n");

	my @list;
	my %hash;
	foreach($self->_process_feedback)
	{
		if(/^(.[^:]+):\s(.+)$/)
		{
			if($1 eq 'file')
			{
				push @list, { %hash } if %hash;
				%hash = ();
			}
			$hash{$1} = $2;
		}
	}
	push @list, { %hash } if %hash; # Remember the last entry
	return @list;
}

sub list
{
	my($self,$type,$artist) = @_;
	return undef if !defined($type) || $type !~ /^(artist|album)$/;
	$self->_connect;
	$artist = '' if !defined($artist);
	print $self->{sock} ($type eq 'album' ? "list album \"$artist\"\n" : "list artist\n");
	
	#	Strip unneccesary information
	my @tmp;
	foreach($self->_process_feedback)
	{
		push @tmp, $1 if /^(?:Artist|Album):\s(.+)$/;
	}
	return @tmp;
}

sub listall
{
	my($self,$path) = @_;
	$self->_connect;
	$path = '' if !defined($path);
	$self->{sock}->print("listall \"$path\"\n");
	return $self->_process_feedback;
}

sub listallinfo
{
	my($self,$path) = @_;
	$self->_connect;
	$path = '' if !defined($path);
	$self->{sock}->print("listallinfo \"$path\"\n");
	my @results;
	my %element;
	foreach($self->_process_feedback)
	{
		if(/^(.[^:]+):\s(.+)$/)
		{
			if($1 eq 'file')
			{
				push @results, { %element } if %element;
				%element = ();
			}
			$element{$1} = $2
		}
	}
	push @results, { %element } if %element;
	return @results;
}

sub lsinfo
{
	my($self,$path) = @_;
	$self->_connect;
	$path = '' if !defined($path);
	$self->{sock}->print("lsinfo \"$path\"\n");
	my @results;
	my %element;
	foreach($self->_process_feedback)
	{
		if(/^(.[^:]+):\s(.+)$/)
		{
			#if($1 =~ /^(?:file|playlist|directory)$/)
			if($1 eq 'file' || $1 eq 'playlist' || $1 eq 'directory')
			{
				push @results, { %element } if %element;
				%element = ();
			}
			$element{$1} = $2;
		}
	}
	push @results, { %element } if %element;
	return @results;
}

###############################################################
#                     CUSTOM METHODS                          #
#-------------------------------------------------------------#
#   This section contains all methods not directly accessing  #
#   MPD, but may be useful for most people using the module.  #
###############################################################

sub get_song_info
{
	my($self,$song,$from_id) = @_;
	if(!defined($song)) {
		$self->_connect;
		$self->_get_status;
		$song = $self->{song};
	}
	$self->{sock}->print("playlist".(defined($from_id) && $from_id == 1 ? 'id' : 'info')." $song\n");
	my %metadata;
	foreach($self->_process_feedback)
	{
		$metadata{$1} = $2 if /^(.[^:]+):\s(.+)$/;
	}
	return %metadata;
}

sub get_current_song_info
{
	my($self) = @_;
 	$self->{sock}->print("currentsong\n");
	my %metadata;
	foreach($self->_process_feedback)
	{
		$metadata{$1} = $2 if /^(.[^:]+):\s(.+)$/;
	}
	return %metadata;
}

sub get_song_info_from_id
{
	my($self,$song) = @_;
	# No reason to write it all again :)
	$self->get_song_info($song,1);
}

sub searchadd
{
	my($self,$type,$string) = @_;
	return undef if !defined($type) || !defined($string);
	$self->_connect;
	my @results = $self->search($type, $string);
	if($#results > -1)
	{
		$self->{sock}->print("command_list_begin\n");
		foreach(@results)
		{
			my %hash = %$_;
			$self->{sock}->print("add \"".$hash{'file'}."\"\n");
		}
		$self->{sock}->print("command_list_end\n");
		if($self->_process_feedback)
		{
			$self->{playlist} = $self->{playlist} + $#results + 1;
		}
	}
	return 1;
}


sub crop
{
	my($self) = shift;
	$self->{sock}->print("command_list_begin\n");
	for(my $i = ($self->{playlistlength}-1) ; $i >= ($self->{song}+1) ; $i--)
	{
		$self->{sock}->print("delete $i\n");
	}
	for(my $i = ($self->{song}-1) ; $i >= 0 ; $i--)
	{
		$self->{sock}->print("delete $i\n");
	}
	$self->{sock}->print("command_list_end\n");
	$self->_process_feedback;
}


sub playlist
{
	my($self) = shift;
	$self->_connect;
	$self->_get_playlist if !defined($self->{playlistref}->[0]);
	return $self->{playlistref};
}

sub get_title
{
	my($self,$song) = @_;
	my %metadata;
	if(defined($song)) {
		$self->_connect;
		$self->_get_status;
		my $info;
		$info = $self->{song} unless !defined($self->{song}) || $self->{song} =~ /^\D+$/;
		$info = $song unless $song =~ /^\D+$/;
		return 'n/a' if !defined($info);
		return '' if !defined($self->{playlistlength}) || $info eq 'false' || ($info ne 'false' && $self->{playlistlength}-1 < $info);
		%metadata = $self->get_song_info($info);
	} else {
		%metadata = $self->get_current_song_info();
	}
	return $metadata{'Artist'}.' - '.$metadata{'Title'} if $metadata{'Artist'} && $metadata{'Title'};
	return $metadata{'Title'} if $metadata{'Title'};
	return $metadata{'file'};
}

sub get_time_format
{
	my($self) = shift;
	
	$self->_get_status;
	return '' if !defined($self->{playlistlength}) || !defined($self->{song});

	#Get the time from MPD; example: 49:395 (seconds so far:total seconds)
	my($psf,$tst) = split /:/, $self->{'time'};
	return sprintf("%d:%02d/%d:%02d",
		($psf / 60), # minutes so far
		($psf % 60), # seconds - minutes so far
		($tst / 60), # minutes total
		($tst % 60));# seconds - minutes total
}

sub get_time_info
{
	my ($self) = shift;
	
	return '' if !defined($self->{playlistlength}) || !defined($self->{song});

	#The return variable
	my $rv = {};

	#Get the time from MPD; example: 49:395 (seconds so far:total seconds)
	$self->_get_status;
	my($so_far,$total) = split(/:/, $self->{'time'});
	my $left = $total-$so_far;

	#Store seconds for everything
	$rv->{seconds_so_far} = $so_far;
	$rv->{seconds_total}  = $total;
	$rv->{seconds_left}   = $left;

	#Store the percentage; use one decimal point
	$rv->{percentage} =
    $rv->{seconds_total}
    ? 100*$rv->{seconds_so_far}/$rv->{seconds_total}
    : 0;
	$rv->{percentage} = sprintf("%.1f",$rv->{percentage});

	
	#Parse the time so far
	my $min_so_far = ($so_far / 60);
	my $sec_so_far = ($so_far % 60);
	
	$rv->{time_so_far} = sprintf("%d:%02d", $min_so_far, $sec_so_far);
	$rv->{minutes_so_far} = sprintf("%00d", $min_so_far);
	$rv->{seconds_so_far} = sprintf("%00d", $sec_so_far);


	#Parse the total time
	my $min_tot = ($total / 60);
	my $sec_tot = ($total % 60);

	$rv->{time_total} = sprintf("%d:%02d", $min_tot, $sec_tot);
	$rv->{minutes} = $min_tot;
	$rv->{seconds} = $sec_tot;

	#Parse the time left
	my $min_left = ($left / 60);
	my $sec_left = ($left % 60);
	$rv->{time_left} = sprintf("-%d:%02d", $min_left, $sec_left);

	return $rv;
}

sub playlist_changes
{
	my($self,$old_playlist_id) = @_;
	$old_playlist_id = -1 if !defined($old_playlist_id);
	my %changeset;
	
	$self->_connect;
	$self->{sock}->print("plchanges $old_playlist_id\n");
	my $changedEntry; # hash reference
	foreach($self->_process_feedback)
	{
		if(/^(.[^:]+):\s(.+)$/)
		{
			my($key, $value) = ($1, $2);
			# create a new hash for the start of each entry
			$changedEntry = {} if($key eq 'file');
			# save a ref to the entry as soon as we know where it goes
			$changeset{$value} = $changedEntry if $key eq 'Pos';
			# save all attributes of the entry
			$changedEntry->{$key} = $value;
		}
	}
	
	return %changeset;
}



#-------------------------------------------#
#             INTERNAL METHODS              #
#-------------------------------------------#
# This sub-section is only used for methods #
# not meant to be accessed from the outside.#
#-------------------------------------------#

sub _connect
{
	my($self) = shift;
	return 1 if $self->is_connected;
	$self->{sock} = new IO::Socket::INET
	(
		PeerAddr => $self->{mpd_host},
		PeerPort => $self->{mpd_port},
		Proto => 'tcp',
	);
	die("Could not create socket: $!\n") unless $self->{sock};
	
	if($self->{sock}->getline() =~ /^OK MPD (.+)$/)
	{
		$self->{sever_version} = $1;
	} else {
		die("Could not connect: $!\n");
	}
	$self->send_password if $self->{password};
	$self->_get_status;
	$self->_get_outputs;
	$self->_get_commands;
	return 1;
}

sub _disconnect 
{
	my($self) = shift;
	
	if ($self->{sock}) {
		$self->{sock}->print("close\n") ;
		$self->{sock}->close();
	}
}

sub _process_feedback
{
	my($self) = shift;
	my @output;
	while(my $line = $self->{sock}->getline()) 
	{
		chomp($line);
		
		# Did we cause an error? Save the data!
		if($line =~ /^ACK \[(\d+)\@(\d+)\] {(.*)} (.+)$/)
		{
			$self->{ack_error_id} = $1;
			$self->{ack_error_command_id} = $2;
			$self->{ack_error_command} = $3;
			$self->{ack_error} = $4;
			return undef;
		}
		
		last if ($line =~ /^OK/);
		push(@output, $line);
	}
	
	# Let's return the output for post-processing
	return @output;
}

sub _get_status
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("status\n");
	foreach($self->_process_feedback)
	{
		if(/^(.[^:]+):\s(.+)$/) {
			$self->{$1} = $2;
		}
	}
	return 1;
}

sub _get_stats
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("stats\n");
	foreach($self->_process_feedback)
	{
		$self->{$1} = $2 if /^(.[^:]+):\s(.+)$/;
	}
	return 1;
}

sub _get_playlist
{
	my($self,$old_playlist_id) = @_;
	$self->_connect;
	my %changes = $self->playlist_changes($old_playlist_id);
	for my $pos (keys %changes)
	{
		$self->{playlistref}->[$pos] = $changes{$pos};
	}

	# Deletes songs no longer in the playlist
	while($#{$self->{playlistref}} > $self->{playlistlength} - 1) {
		pop @{$self->{playlistref}};
	}
	
	return 1;
}

sub _get_outputs
{
	my($self) = shift;
	$self->_connect;
	$self->{sock}->print("outputs\n");
	my @outputs;
	my %output;
	foreach($self->_process_feedback)
	{
		next if !defined;
		if(/^outputid:/)
		{
			push @outputs, { %output } if %output;
			%output = ();
		}
		$output{$1} = $2 if /^output(.+): (.+)$/;
	}
	push @outputs, { %output } if %output;
	$self->{outputs} = \@outputs;
	return 1;
}

sub _get_commands
{
	my($self) = shift;
	$self->_connect;
	my(@commands,@notcommands);
	$self->{sock}->print("commands\n");
	foreach($self->_process_feedback)
	{
		next if !defined;
		push @commands, $1 if /^command: (.+)$/;
	}
	$self->{sock}->print("notcommands\n");
	foreach($self->_process_feedback)
	{
		next if !defined;
		push @notcommands, $1 if /^command: (.+)$/;
	}
	$self->{commands} = \@commands;
	$self->{notcommands} = \@notcommands;
	return 1;
}


sub DESTROY
{
	my($self) = shift;
	
	$self->_disconnect();
}

#-------------------------------------------#
#           INTERNAL METHODS - END          #
#-------------------------------------------#

1;



__END__

=pod

=head1 NAME

Audio::MPD - Class for talking to MPD (Music Player Daemon) servers

=head1 SYNOPSIS

  use Audio::MPD;

  my $mpd = new Audio::MPD();
  $mpd->play();
  sleep 10;
  $mpd->next();


=head1 DESCRIPTION

Audio::MPD gives a clear object-oriented interface for talking to and 
controlling MPD (Music Player Daemon) servers. A connection to the MPD 
server is established as soon as a new Audio::MPD object is created. 
Commands are then send to the server as the class's methods are called.

=head1 METHODS

=head2 $mpd = new Audio::MPD( [[$password@]$host], [$port] )

The new() method is the constructor for the C<Audio::MPD> class.
You may specify a hostname and port, if none is specified then 
the enviroment variables 'MPD_HOST' and 'MPD_PORT' are checked.
Finally if all else fails the defaults 'localhost' and '6600' are used.

An optional  password can be specified by prepending it to the 
hostname, seperated an '@' character.


=head2 $mpd->is_connected()

Checks to see if there is a valid connection to the MPD server. 
First checks that the socket is connected and then sends a Ping command 
checks that the replyis 'OK'. Returns '1' if connected and undef if not.


=head2 $mpd->close_connection()

Closes the connection to the MPD server.


=head2 $mpd->kill_mpd()

Send a message to the MPD server telling it to shut down.


=head2 $mpd->send_password( password )

Send a plaintext password to the server, 
which can enable optionally password protected functionality.


=head2 $mpd->get_urlhandlers()

Returns an array of supported URL schemes.


=head2 $mpd->get_error()

Returns an array containing information about the last error that occured.

 - Item 0: The ID number of the error
 - Item 1: Human readable error message
 - Item 2: The command that caused the error
 - Item 3: The position in the command_list of the command (if used)
 

=head2 $mpd->get_server_version()

Returns the version number for the server we are connected to.


=head2 $mpd->set_repeat( [$repeat] )

Set the repeat mode to $repeat (1 or 0). If $repeat is not specified then 
the repeat mode is toggled.


=head2 $mpd->set_random( [$random] )

Set the random mode to $random (1 or 0). If $random is not specified then 
the random mode is toggled.


=head2 $mpd->set_fade( [$seconds] ) 

Enabled and sets the duration of crossfade between songs. 
If $seconds is not sepcified or $seconds is 0, then crossfading is disabled. 


=head2 $mpd->set_volume( [+][-]$volume ) 

Sets the audio output volume percentage to absolute $volume.
If $volume is prefixed by '+' or '-' then the volume is changed relativly 
by that value.


=head2 $mpd->output_enable( $output ) 

Enables the specified audio output. $output is the ID of the audio output.


=head2 $mpd->output_disable( $output ) 

Disables the specified audio output. $output is the ID of the audio output.


=head2 $mpd->play( [$number], [$fromid] ) 

Begin playing playlist at song number $number. 
If $fromid is true then begin playing at song with ID $number.


=head2 $mpd->playid( [$songid] ) 

Begin playing playlist at song ID $songid. 


=head2 $mpd->pause( [$state] ) 

Pause playback. If $state is 0 then the current track is unpaused, 
if $state is 1 then the current track is paused.


=head2 $mpd->stop() 

Stop playback.


=head2 $mpd->next() 

Play next song in playlist.


=head2 $mpd->prev()

Plays previous song in playlist.


=head2 $mpd->seek( $position, [$song], [$fromid] )

Seek to $position seconds.
If $song number is not specified then the perl module will try and 
seek to $position in the current song. If $fromid is true then 
$song is the ID of the song to seek in.


=head2 $mpd->seekid( $position, $songid )

Seek to $position seconds in song ID $songid.


=head2 $mpd->clear()

Removed all the songs from the current playlist.


=head2 $mpd->add( $path )

Adds a song specified by $path 
(relative to MPD's music directory) to the current playlist.


=head2 $mpd->delete( $song, [$fromid] )

Removes song number $song from the current playlist. 
If $fromid is true, then $song is the ID of the song to be removed.


=head2 $mpd->deleteid( $songid )

Removed the specified songid from the current playlist.


=head2 $mpd->load( $playlist )

Loads list of songs from specified playlist file.


=head2 $mpd->swap( $song1, $song2, [$fromid] )

Swaps positions of song number $song1 and $song2 on the current playlist. 
If $fromid is true, then $song1 and $song are the IDs of the songs.


=head2 $mpd->swapid( $songid1, $songid2 )

Swaps the postions of song ID $songid1 with song ID $songid2 
on the current playlist.

=head2 $mpd->shuffle()

Shuffles the current playlist.


=head2 $mpd->move( $song, $newpos, [$fromid] )

Moves the postion of song number $song to $newpos.
If $fromid is true, then $song is the ID of the song.


=head2 $mpd->moveid( $songid, $newpos )

Moves the postion of song ID $songid to $newpos.


=head2 $mpd->rm( $playlist )

Deletes playlist named $playlist from MPD's playlist directory.


=head2 $mpd->save( $playlist )

Saves the current playlist to a file called $playlist in 
MPD's playlist directory.


=head2 $mpd->search( $type, $string, [$strict] )

Searches through MPD's database of music for matching songs.
Where $type is the field to search in: 
"title","artist","album", or "filename".
And $string is the keyword(s) to seach for. If $strict is 
true then only exact matches are returned.

Returns an array of matching file paths.


=head2 $mpd->list( $type, [$artist] )

Returns an array of all the "album" or "artist" in 
the music database (as chosen by $type). $artist is an 
optional parameter, which will only return albums by the 
specified $artist when $type is "album".


=head2 $mpd->listall( [$path] )

Returns an array of all the songs in the music database.
If $path is specified, then it only returns songs matching 
the directory/path.


=head2 $mpd->listallinfo( [$path] )

Returns an array of hashes containing all the paths and metadata about 
songs in the music database.  If $path is specified, then it only 
returns songs matching the directory/path.


=head2 $mpd->lsinfo( [$directory] )

Returns an array of hashes containing all the paths and metadata about 
songs in the specified directory. If no directory is specified, then only 
the songs/directories in the root directory are listed.


=head2 $mpd->crop()

Removes all of the songs from the current playlist *except* the 
currently playing song.


=head2 $mpd->playlist( )

Returns a arrayref, containing a hashref of metadata for each of the 
songs in the current playlist.


=head2 $mpd->get_song_info( $song, $fromid )

Returns an a hash containing information about song number $song.
If $fromid is true, then $song is the ID of the song.


=head2 $mpd->get_song_info_from_id( $songid )

Returns an a hash containing information about song ID $songid.


=head2 $mpd->searchadd( $type, $string )

Performs the same action as $mpd->search(), only it adds any 
matching songs to the current playlist, instead of just returning 
information about them.


=head2 $mpd->get_title( [$song] )

Returns the 'title string' of song number $song. The 'title' is the artist and 
title of the song. If the artist isn't available, then just the title is 
returned. If there is no title available, then the filename is returned. 

If $song is not specified, then the 'title' of the current song is returned.


=head2 $mpd->get_time_format( [$song] )

Returns the current position and duration of the current song.
String is formatted at "M:SS/M:SS". With the current time first.


=head2 $mpd->get_time_info( )

Return current timing information in various different formats
contained in a hashref with the following keys:

=over 4

=item minutes_so_far

=item seconds_so_far

=item time_so_far

=item minutes

=item seconds

=item percentage

=item time_total

=item seconds_total

=item seconds_left

=item time_left 

=back


=head1 AUTHOR

Written by Tue Abrahamsen (twoface@wtf.dk)

Documented by Nicholas J. Humfrey, njh@aelius.com

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 Tue Abrahamsen

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.

=cut
