#!/usr/bin/perl -w

# Copyright 2007-2009 Robert ("Bob") Igo of StormLogic, LLC and mythic.tv.
#
# 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/>.

use Switch;
use Tweaker::Script;
package Tweaker::Script;

set_known_options( 'analogstereo', 'analogsurround', 'digital' );

# Poll the system to find the digital output device.
sub poll_for_digital_output_device {
    my $card=-1;
    my $device=-1;
    my $poll_command = "aplay -l | grep card";

    my @digital_matches = ( "digital", "IEC958" );
    
    my $results = execute_shell_command($poll_command);
    if ($results) {
	foreach my $digital_match (@digital_matches) {
	    if ($results =~ /card (\d):.*device (\d).*$digital_match.*/i) {
		$card = $1;
		$device = $2;
	    }
	}
    } else {
	recommendation_level("not available", "No audio devices detected.");
	exit(-1);
    }
    return ($card, $device);
}

# Try to implement the given option.
sub implement_option {
    my($option) = @_;
    use vars qw($card);
    use vars qw($device);
    ($card, $device) = poll_for_digital_output_device;
    use vars qw($asound_conf);
    $asound_conf = "/etc/asound.conf";
    use vars qw($mplayer_conf);
    $mplayer_conf = "/etc/mplayer/mplayer.conf";
    use vars qw($xine_conf);
    $xine_conf = "/home/mythtv/.xine/config";
    
    sub generate_asound_conf {
	my($option) = @_;
	my $command1;
	my $command2;

	switch ($option) {
	    case "analogstereo" {
		# Analog stereo usually needs no asound.conf, but sometimes it needs to be there to
		# support more than one app using sound simultaneously.
		$command1 = "[ -e $asound_conf ] && /bin/cp $asound_conf $asound_conf-TWEAKERBACKUP";
		# Assumption: The proper analog device is always device 0.
		if ($card >= 0) {
		    $command2 = "[ -e $asound_conf ] && sed -i 's/hw:.,./hw:$card,0/g' $asound_conf";
		} else {
		    $command2 = "[ -e $asound_conf ] && sed -i 's/hw:.,./hw:0,0/g' $asound_conf";
		}
	    }
	    case "analogsurround" {
		# Not supported yet.
	    }
	    case "digital" {
		# Digital audio starts with an asound.conf that references the
		# hardware address on the sound device corresponding to digital
		# output.
		$command1 = "/bin/cp \$TWEAKER_ROOT/fs$asound_conf /etc/";
		if (($card >= 0) && ($device >= 0)) {
		    $command2 = "[ -e $asound_conf ] && sed -i 's/hw:.,./hw:$card,$device/g' $asound_conf";
		} else {
		    my $logger = get_logger('tweaker.script');
		    $logger->error("ERROR: Unable to poll for digital sound output device.");
		    exit(-1);
		}
	    }
	}
	if (my $error = execute_shell_command($command1)) {
	    my $logger = get_logger('tweaker.script');
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}
	if (my $error = execute_shell_command($command2)) {
	    my $logger = get_logger('tweaker.script');
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}
	my $logger = get_logger('tweaker.script');
	$logger->info("Generated $asound_conf for $option audio.");
    }

    sub edit_mplayer_conf {
	my($option) = @_;
	# delete any old entries that Tweaker made, relevant to this particular edit
	my $delete_old_tweaker_edits = "[ -e $mplayer_conf ] && sed -i '/^.*a[o,c,f].*=.*#TWEAKER/d' $mplayer_conf && sed -i '/^speed.*=.*#TWEAKER/d' $mplayer_conf";
	# comment out old entries that some other process may have made
	my $comment_out_external_edits = "[ -e $mplayer_conf ] && sed -i 's/^\\(a[o,c,f].*=.*\\)/#\\1/g' $mplayer_conf && sed -i 's/^\\(speed.*=.*\\)/#\\1/g' $mplayer_conf";
	my $command1;
	my $command2="";
	($card, $device) = poll_for_digital_output_device;
	my $logger = get_logger('tweaker.script');

	if (my $error = execute_shell_command("$delete_old_tweaker_edits && $comment_out_external_edits")) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}

	switch($option) {
	    case "analogstereo" {
		# No additional work needed; the above commands comment out ao and ac entries,
		# leaving a baseline which works with analog stereo.
	    }
	    case "analogsurround" {
		# Not supported yet.
	    }
	    case "digital" {
		$command2 = "echo -e 'ac=hwac3,hwdts, #TWEAKER\nao=alsa:device=plughw=$card.$device #TWEAKER' >> $mplayer_conf";
		if (my $error = execute_shell_command($command2)) {
		    $logger->error("ERROR: $error");
		    $logger->error("ERROR: Unable to implement option $option.");
		    exit(-1);
		}

		my @digital_audio_device_patterns = (
		    [
		     # Asus T3-M2NC51PV onboard audio
		     ".*0403.*10de.*026c.*1043.*821f", "T3-M2NC51PV", "[ -e $mplayer_conf ] && sed -i 's/plughw.* #TWEAKER/spdif #TWEAKER/g' $mplayer_conf"
		    ]
		    );

		foreach my $pattern_and_workaround_command (@digital_audio_device_patterns) {
		    my $pattern=@$pattern_and_workaround_command[0];
		    my $name=@$pattern_and_workaround_command[1];
		    my $workaround_command=@$pattern_and_workaround_command[2];
		    
		    if (execute_shell_command('lspci -mn | grep -e "'.$pattern.'"')) {
			$logger->info("Applying workaround for $name audio");
			execute_shell_command($workaround_command);
			last;
		    }
		}
	    }
	}
	# for all options
	$command2 = "echo -e 'af=scaletempo=stride=30:overlap=.50:search=10 #TWEAKER\nspeed=1.0 #TWEAKER' >> $mplayer_conf";
	if (my $error = execute_shell_command($command2)) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}
	
	$logger->info("Edited $mplayer_conf for $option audio.");
    }

    sub edit_xine_conf {
	my($option)=@_;
	# delete any old entries that Tweaker made, relevant to this particular edit
	my $delete_old_tweaker_edits = "[ -e $xine_conf ] && sed -i -e '/^.*audio.output.speaker_arrangement.*#TWEAKER/d' -e '/^.*audio.synchronization.passthrough_offset.*#TWEAKER/d' $xine_conf";
	# comment out entries that some other process may have made
	my $comment_out_external_edits = "[ -e $xine_conf ] && sed -i -e 's/^\\(audio.output.speaker_arrangement.*\\)/#\\1/g' -e 's/^\\(audio.synchronization.passthrough_offset.*\\)/#\\1/g' $xine_conf";
	my $logger = get_logger('tweaker.script');

	if (my $error = execute_shell_command("$delete_old_tweaker_edits && $comment_out_external_edits")) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}

	my $command1;

	switch($option) {
	    case "analogstereo" {
		$command1 = "echo 'audio.output.speaker_arrangement:Stereo 2.0 #TWEAKER' >> $xine_conf";
	    }
	    case "analogsurround" {
		# Not supported yet.
	    }
	    case "digital" {
		$command1 = "echo -e 'audio.output.speaker_arrangement:Pass Through #TWEAKER\naudio.synchronization.passthrough_offset:$device #TWEAKER' >> $xine_conf";
	    }
	}
	if (my $error = execute_shell_command($command1)) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}
	$logger->info("Edited $xine_conf for $option audio.");
    }

    sub reload_modules {
	my($option) = @_;
	my $command = "update-modules; depmod -a; rmmod snd-pcm-oss; modprobe snd-pcm-oss";
	my $logger = get_logger('tweaker.script');

	if (my $error = execute_shell_command($command)) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}
	$logger->info("Reloaded sound modules for $option audio.");
    }
    
    sub set_mixer_values {
	my($option) = @_;
	my $logger = get_logger('tweaker.script');

	# Some sound devices work poorly in their default state, so we will load in a known-good
	# state for them.

	my @digital_audio_device_patterns = (
	    [
	     # Chaintech AV-710
	     ".*0401.*1412.*1724.*1412.*1724", "AV710"
	    ],
	    [
	     # Realtek ALC888 High Definition onboard Audio
	     ".*0403.*10de.*055c.*1565.*820c", "ALC888"
	    ]
	    );
	
	foreach my $pattern_and_name_pairing (@digital_audio_device_patterns) {
	    my $name=@$pattern_and_name_pairing[1];
	    my $pattern=@$pattern_and_name_pairing[0];
	    
	    if (execute_shell_command('lspci -mn | grep -e "'.$pattern.'"')) {
		$logger->info("Applying ALSA state workaround for $name audio");
		execute_shell_command("/bin/cp \$TWEAKER_ROOT/fs/var/lib/alsa/$name.asound.state /var/lib/alsa/asound.state");
		execute_shell_command("alsactl restore");
		last;
	    }
	}

	# Now, do what works for all sound devices.
	
	my $command = "su - mythtv -c \"aumix -v 70 -m 0 -l 0 -l R -w 70\""; # ok for analog and digital

	if (my $error = execute_shell_command($command)) {
	    $logger->error("ERROR: $error");
	    $logger->error("ERROR: Unable to implement option $option.");
	    exit(-1);
	}

	if ("$option" eq "digital") {
	    # Poll system for IEC958 controls, and try to turn them all 'on'
	    $command = "amixer scontrols | grep IEC958";
	    my @results = split('\n', execute_shell_command($command));
	    foreach my $line (@results) {
		if ($line =~ /Simple mixer control (.*),.*/i) {
		    $command = "su - mythtv -c \"amixer set $1 on\""; # Tries to set all IEC958 devices to 'on'
		    # but some are just placeholders and can't be turned 'on', therefore don't error out if we fail
		    execute_shell_command($command);
		}
	    }
	}
	execute_shell_command("alsactl store"); # persist the above change(s)
	$logger->info("Reset mixer volume levels for $option audio.");
    }

    sub edit_mythtv_configuration {
	my($option)=@_;

	$dbconnectionstring = get_mythtv_connection_string();
	
	if (connect_to_db("DBI:mysql:$dbconnectionstring")) {
	    switch ($option) {
		case "analogstereo" {
		    change_or_make_setting('AC3PassThru', '0') || exit -1;
		    change_or_make_setting('DTSPassThru', '0') || exit -1;
		    change_or_make_setting('MTDac3Flag', '0') || exit -1;
		    change_or_make_setting('MythControlsVolume', '1') || exit -1;
		}
		case "analogsurround" {
		    # Not supported yet.
		}
		case "digital" {
		    change_or_make_setting('AC3PassThru', '1') || exit -1;
		    change_or_make_setting('DTSPassThru', '1') || exit -1;
		    change_or_make_setting('MTDac3Flag', '1') || exit -1;
		    change_or_make_setting('MythControlsVolume', '0') || exit -1;
		}
	    }
	    change_or_make_setting('AudioOutputDevice', 'ALSA:default') || exit -1;
	    change_or_make_setting('MixerDevice', 'ALSA:default') || exit -1;
	    change_or_make_setting('MusicAudioDevice', 'default') || exit -1;
	} else {
	    exit -1;
	}
	disconnect_from_db();
    }

    generate_asound_conf($option);
    edit_mplayer_conf($option);
    #edit_xine_conf($option);
    #reload_modules($option);
    edit_mythtv_configuration($option);
    set_mixer_values($option);
}

# Try to get a Recommendation Level for $option.
sub poll_options {
    my($option) = @_;
    my @digital_audio_device_patterns = (
	[
	 # Pattern matches for PCI IDs of perfect devices, comma-separated within the brackets
	 [ ".*0403.*8086.*284b.*8086.*2504", # Intel DG965WH onboard audio
	   ".*0403.*10de.*026c.*1043.*821f", # Asus T3-M2NC51PV onboard audio
	   ".*0403.*8086.*293e.*8086.*3001"  # Intel AusDragon onboard audio
	 ],
	 "optional|Your sound device works very well in digital mode.  Unless you don't have a receiver that can accept digital inputs, you should use digital mode.",
	],
	[
	 # Pattern matches for PCI IDs of suboptimal devices, comma-separated within the brackets
	 [ ".*0401.*1412.*1724.*1412.*1724", # Chaintech AV-710 PCI card (regressions in R5.5)
	 ],
	 "inadvisable|Your sound device is known to have some problems in digital mode, but you may try it if you like, starting at low volume in your receiver.",
	],
	[
	 [ ], # Leave blank for unknown devices
	 "optional|Your sound device may or may not work well in digital mode.  Please tell us if it works, and how well.",
	]
	);

    switch ($option) {
	case "analogstereo" {
	    my ($card, $device) = poll_for_digital_output_device;
	    if (($card >= 0) && ($device >= 0)) { # A digital output device was detected.
		recommendation_level("optional");
	    } else {
		recommendation_level("recommended", "You seem to have no digital output option.  If this is not true, please tell us.");
	    }
	}
	case "analogsurround" {
	    my ($card, $device) = poll_for_digital_output_device;
	    recommendation_level("unsupported", "No configuration data exists yet for this option.");
	}
	case "digital" {
	    my ($card, $device) = poll_for_digital_output_device;
	    if (($card >= 0) && ($device >= 0)) { # A digital output device was detected.
		my $recommendation_return_string;
		foreach my $pattern_and_recommendation_pairing (@digital_audio_device_patterns) {
		    $recommendation_return_string=@$pattern_and_recommendation_pairing[1];
		    foreach my $patterns (@$pattern_and_recommendation_pairing[0]) {
			foreach my $pattern (@$patterns) {
			    if (execute_shell_command('lspci -mn | grep -e "'.$pattern.'"')) {
				recommendation_level($recommendation_return_string);
				return;
			    }
			}
		    }
		}
		# Because we didn't return a recommendation level above, return a default recommendation level.
		recommendation_level($recommendation_return_string);
		return;
	    } else { # No digital output device was detected.
		recommendation_level("not available");
	    }
	}
    }
}

# Unimplemented in 0.7
sub check_option {
    help;
}

# Unimplemented in 0.7
sub count_iterations {
    help;
}

process_parameters;