#!/usr/bin/perl

# Copyright (c) 2009 Landry Breuil <landry@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use strict;
use warnings;

my $m = OpenBSD::CMixer->new();
$m->run();

package OpenBSD::CMixer;
use Curses;
use Curses::UI;
use Curses::UI::Common;

sub new {
	my($class) = @_;
	my $self = bless {}, $class;
	$self->{name} ="CMixer v0.1";
	return $self;
}

sub read_mixerctl {
	my $self = shift;
	foreach (`mixerctl -v 2>&1`) {
		#mixerctl: /dev/mixer: Device not configured
		die "No mixer device found" if (/^mixerctl: \/dev\/mixer.: Device not configured$/);
		#outputs.master=255,255 volume
		if (/^(outputs|inputs)\.([^\.]*)=(\d+),(\d+)\s+/) {
			$self->{mixer}{$1}{$2}{volume}{left} = $3;
			$self->{mixer}{$1}{$2}{volume}{right} = $4;
		# outputs.mono=255 volume
		} elsif (/^(outputs|inputs)\.([^\.]*)=(\d+)\s+/) {
			$self->{mixer}{$1}{$2}{volume}{center} = $3;
		# outputs.master.mute=off  [ off on ]
		} elsif (/^(outputs|inputs)\.(.*)\.mute=(on|off)\s+/) {
			$self->{mixer}{$1}{$2}{mute} = $3;
		}
		#todo: record.source=mic  [ mic cd video aux line mixerout mixeroutmono phone ]
	}
	die "No outputs found" unless (exists $self->{mixer}{outputs});
}

sub update_view {
	my $self = shift;
	foreach my $dir (keys %{$self->{mixer}}) {
		foreach my $wid (keys %{$self->{mixer}{$dir}}) {
			my $v = $self->{mixer}{$dir}{$wid}{volume}{center} // $self->{mixer}{$dir}{$wid}{volume}{right};
			$self->{wid}{"$dir.$wid.progress"}->pos($v);
			if (	(exists $self->{mixer}{$dir}{$wid}{mute} && $self->{mixer}{$dir}{$wid}{mute} eq "on") ||
				(!exists $self->{mixer}{$dir}{$wid}{mute} && $v == 0))
				{ $self->{wid}{"$dir.$wid.mute"}->uncheck; }
			else
				{ $self->{wid}{"$dir.$wid.mute"}->check; }
		}
	}
}

sub update_vol {
	my ($self, $off, $dir, $wid) = @_;
	my $v = $self->{mixer}{$dir}{$wid}{volume}{center} // $self->{mixer}{$dir}{$wid}{volume}{right};
	# save previous value, needed when muting a widget without mute control
	$self->{mixer}{$dir}{$wid}{volume}{previous} = $v;
	$v += $off;
	$self->{wid}{"$dir.$wid.progress"}->pos($v);
	qx/mixerctl -q $dir.$wid=$v/;
	# update vol with new value
	my $new = qx/mixerctl $dir.$wid/;
	if ($new =~ /^$dir.$wid=(\d+),(\d+)\s+/) {
		$self->{mixer}{$dir}{$wid}{volume}{left} = $1;
		$self->{mixer}{$dir}{$wid}{volume}{right} = $2;
	} elsif ($new =~ /^$dir.$wid=(\d+)\s+/) {
		$self->{mixer}{$dir}{$wid}{volume}{center} = $1;
	}
}

sub toggle_mute {
	my ($self, $dir, $wid) = @_;
	$self->{wid}{"$dir.$wid.mute"}->toggle;
	if (exists $self->{mixer}{$dir}{$wid}{mute})
		{ qx/mixerctl -t $dir.$wid.mute/; }
	else {
		my $v = $self->{mixer}{$dir}{$wid}{volume}{center} // $self->{mixer}{$dir}{$wid}{volume}{right};
		# if vol is not 0 & now unchecked, and update_vol(0)
		if ($v != 0 && !$self->{wid}{"$dir.$wid.mute"}->get) {
			$self->update_vol(-$v,$dir,$wid);
		} elsif (defined $self->{mixer}{$dir}{$wid}{volume}{previous}){
		# else update_vol(prev value)
			$self->update_vol($self->{mixer}{$dir}{$wid}{volume}{previous},$dir,$wid);
		}
	}
}

sub loose_focus {
	my ($self, $off) = @_;
	$self->{checkbox_current} += $off;
	$self->{checkbox_current} %= @{$self->{checkbox_list}};
	$self->{checkbox_list}[$self->{checkbox_current}]->focus();
}

sub create_view {
	my $self = shift;
	$self->{wid}{cui} = new Curses::UI(
		-clear_on_exit => 1,
		-color_support => 1);
	$self->{wid}{main_win} = $self->{wid}{cui}->add(
		'main_win', 'Window',
		-ipad => 1,
		-title => $self->{name},
		-titlereverse => 0,
		-border => 1);

	my $y = 0;
	foreach my $dir (keys %{$self->{mixer}}) {
		# create container for this dir list
		$self->{wid}{"$dir.label"} = $self->{wid}{main_win}->add(
			"$dir.label", "Label",
			-x => 2, -y => $y,
			-reverse => 1, -bold => 1,
			-text => "$dir");
		$y += 2;

		foreach my $wid (keys %{$self->{mixer}{$dir}}) {
			$self->{wid}{"$dir.$wid.label"} = $self->{wid}{main_win}->add(
				"$dir.$wid.label", "Label",
				-y => $y,
				-text => "$wid");
			$self->{wid}{"$dir.$wid.mute"} = $self->{wid}{main_win}->add(
				"$dir.$wid.mute", "Checkbox",
				-y => $y, -x => 8);
			$self->{wid}{"$dir.$wid.mute"}->set_binding( sub {$self->update_vol(16, $dir,$wid) }, ("l", KEY_RIGHT()));
			$self->{wid}{"$dir.$wid.mute"}->set_binding( sub {$self->update_vol(-16, $dir,$wid) }, ("h", KEY_LEFT()));
			$self->{wid}{"$dir.$wid.mute"}->clear_binding('loose-focus');
			$self->{wid}{"$dir.$wid.mute"}->set_binding( sub {$self->loose_focus(1) }, ("j", KEY_DOWN(), CUI_TAB()));
			$self->{wid}{"$dir.$wid.mute"}->set_binding( sub {$self->loose_focus(-1) }, ("k", KEY_UP()));
			$self->{wid}{"$dir.$wid.mute"}->clear_binding('toggle');
			$self->{wid}{"$dir.$wid.mute"}->set_binding( sub {$self->toggle_mute($dir,$wid) }, CUI_SPACE());
			push @{$self->{checkbox_list}}, $self->{wid}{"$dir.$wid.mute"};
			$self->{checkbox_current} = 0;

			$self->{wid}{"$dir.$wid.progress"} = $self->{wid}{main_win}->add(
				"$dir.$wid.progress", 'Progressbar', -border=>0, -sbborder =>1,
				-max => 255, -showvalue => 1, -x => 12,  -y => $y);
			$y += 2;
		}
	}
	$self->{wid}{"help_label"} = $self->{wid}{main_win}->add(
		"help_label", "Label",
		-x => 2, -y => $y,
		-bold => 1,
		-text => "[up/down] prev/next [space] toggle mute [left/right] down/up volume [q] quit");
	$self->{wid}{cui}->set_binding( sub { exit}, "q");
}

sub run {
	my $self = shift;
	$self->read_mixerctl();
	$self->create_view();
	$self->update_view();
	# create timer & run C::UI
	$self->{wid}{cui}->set_timer('timer', sub { $self->read_mixerctl; $self->update_view }, 1);
	$self->{wid}{cui}->mainloop();
}

