#!/usr/bin/env perl
use strict;
use warnings;
use Curses::UI;
use MP3::Info;
use Ogg::Vorbis::Header::PurePerl;
use Audio::FLAC::Header;
use Storable qw(nstore retrieve);
use File::Find;
use IPC::Open2;
use Curses qw(KEY_LEFT KEY_RIGHT KEY_F getmaxyx initscr endwin);
use File::MimeInfo qw(mimetype describe);
use Digest::MD5 qw(md5_hex);

# project:          whistle
# author:           see AUTHORS
# license:          see LICENSE
# purpose:          see README

# data location, hashref declaratiion, global vars
our $STORFILE = $ENV{'HOME'} . "/.whistle.dat";
our $STORAGE;               # storage hashref
our $SHUFFLE = 0;           # 0 = normal, 1 = random
our $CUR_LIST = 'ALL';      # current playlist
our $LAST_PLAYED = 0;

# vars for mplayer's 10band equalizer (allowed: -12 <-> 12)
our @EQ = (0.0 ,0.0 ,0.0 ,0.0 ,0.0 ,0.0 ,0.0 ,0.0 ,0.0 ,0.0);

my ($dir_rem, $pl_add, $sel_pl);
my $num_songs = 0;
our $MAX_PL;
our $STATE = 0;

# turn on autoflush
$| = 1;

# don't hard code mplayer location
open( my $WHICH, "which mplayer 2>/dev/null |" );
chomp( my( $mplayer ) = <$WHICH> );
close( $WHICH );

# open read and write handles for mpv
my $MPLPID = open2(my $Mout, my $Min, "$mplayer -idle -slave &> /dev/null") or die "mplayer hates me :(";


# read in data if available, otherwise initialise STORAGE hashref
if (-e $STORFILE && (stat $STORFILE)[7] > 5) {
    $STORAGE = retrieve $STORFILE;
}
else {
    $STORAGE = { 'DIRS' => [], 'PLAYLIST' => {}};
    $STORAGE->{'PLAYLIST'}{'ALL'} = {};
}

# check for screen size
initscr();
my ($row, $col);
getmaxyx($row, $col);
endwin();

print "your terminal windows' height is too low! (26 rows needed, you have $row)\n" and exit(0) if $row < 26;
print "your terminal windows' length is too low! (105 cols needed, you have $col)\n" and exit(0) if $col < 105;

# create the main ui and main window
my $cui = new Curses::UI ( -clear_on_exit => 0, -color_support => 1);
my $win = $cui->add('win', 'Window', -border => 1, -title => "whistle");


# create current playlist 'window'
my $curlist = $win->add( 
    'curlist', 'Listbox',
    -fg => 'white',
    -multi => 1,
    -title => "Playlist: $CUR_LIST",
    -readonly => 1,
    -y => 5,
    -x => int ($win->width() / 3) + 1,
    -focusable => 1,
    -nocursor => 0,
    -vscrollbar => 'right',
    -wrapping => 1,
    -width => int ($win->width() * 0.66),
    -intellidraw => 1,
    -border => 1,
    -ipad => 1,
    -htmltext => 1,
) or die "curlist";

# create controls 'window'
my $equalizer = $win->add( 
    'controls', 'TextViewer',
    -fg => 'white',
    -title => ".: Equalizer :. (Use F1-F10 to configure)",
    -readonly => 1,
    -y => 1,
    -x => 1,
    -focusable => 0,
    -wrapping => 0,
    -height => 4,
    -width => int ($win->width() / 2),
    -intellidraw => 1,
    -border => 1,
) or die "controls";

# create playlist 'window'
my $playlists = $win->add( 
    'playlists', 'TextViewer',
    -fg => 'white',
    -title => ".: Playlists :. ('h' -> help)",
    -readonly => 1,
    -y => 5,
    -x => 1,
    -focusable => 0,
    -wrapping => 1,
    -height => 14,
    -width => int ($win->width() / 3),
    -intellidraw => 1,
    -border => 1,
    -ipad => 1,
) or die "playlists";

# create directory 'window'
my $directories = $win->add( 
    'directories', 'TextViewer',
    -fg => 'white',
    -title => ".: Directories :.",
    -readonly => 1,
    -y => 19,
    -x => 1,
    -focusable => 0,
    -wrapping => 1,
    -height => 8,
    -width => int ($win->width() / 3),
    -intellidraw => 1,
    -border => 1,
    -ipad => 1,
) or die "directories";

my $progressbar = $win->add(
    'progress', 'Progressbar',
    -max       => 100,
    -pos       => 0,
    -y => 1,
    -x => int($win->width() / 2 + 1),
    -focusable => 0,
    -wrapping => 1,
    -height => 3,
    -width => int ($win->width() / 2 - 1),
    -intellidraw => 1,
    -title => "--:-- / --:--",
    -border => 1,
) or die "progressbar";

# pretty nasty shit, but neccessary for continuous playing
our $TIMER = {'rest_time' => 0, 'max_time' => 0};
$SIG{ALRM} = sub { 
    $TIMER->{'rest_time'}--;
    my $one_perc = $TIMER->{'max_time'} / 100;
    my $new_pro = int(($TIMER->{'max_time'} - $TIMER->{'rest_time'}) / $one_perc);
    my $diff = int($TIMER->{'max_time'} - $TIMER->{'rest_time'});
    my $M_rest = int($diff / 60);
    my $S_rest = int($diff % 60);
    my $M_max = int($TIMER->{'max_time'} / 60);
    my $S_max = int($TIMER->{'max_time'} % 60);
    my $time_line = sprintf("%02d:%02d / %02d:%02d", $M_rest, $S_rest, $M_max, $S_max);
    $progressbar->title($time_line);
    $progressbar->pos($new_pro);
    $progressbar->draw();
    if ($TIMER->{'rest_time'} <= 0) {
        &play('NEXT'); 
    }
    else {
        alarm(1);
    }
};

# draw the menu
sub draw_menu {
    $curlist->clear_selection();
    my $dirtxt;
    if (scalar @{$STORAGE->{'DIRS'}}) {
        for (@{$STORAGE->{'DIRS'}}) {
            $dirtxt .= "$_\n";
        }
    }
    else {
        $dirtxt = "no directories given";
    }
    $directories->text($dirtxt);
    $directories->draw();
    my $pltxt;
    $playlists->text('');
    $playlists->draw();
    $pltxt .= $CUR_LIST eq 'ALL' ? "[*] " : "[ ] ";
    $pltxt .= "ALL";
    $pltxt .= sprintf("  (%d)\n", scalar keys %{$STORAGE->{'PLAYLIST'}{'ALL'}});
    for (grep {!/ALL/} sort keys %{$STORAGE->{'PLAYLIST'}}) {
        $pltxt .= $CUR_LIST eq $_ ? "[*] " : "[ ] ";
        $pltxt .= "\'-> $_";
        $pltxt .= sprintf("  (%d)\n", scalar keys %{$STORAGE->{'PLAYLIST'}{$_}});
    }
    $playlists->text($pltxt);
    $playlists->draw();
    my %pl;
    $curlist->values('');
    my $pl_w = int($curlist->width() / 9);
    my ($pl_t, $pl_a, $pl_al, $pl_d) = (int($pl_w * 3) , int($pl_w * 1), int($pl_w * 2), int ($pl_w));
    $pl_t = "$pl_t.$pl_t" . "s";
    $pl_a = "$pl_a.$pl_a" . "s";
    $pl_al = "$pl_al.$pl_al" . "s";
    $pl_d = "$pl_d.$pl_d" . "s";
    my $j = 0;
    if (%{$STORAGE->{'PLAYLIST'}{$CUR_LIST}}) {


        for (sort {$STORAGE->{'PLAYLIST'}{$CUR_LIST}{$a}{'artist'} cmp $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$b}{'artist'}} 
             sort {$STORAGE->{'PLAYLIST'}{$CUR_LIST}{$a}{'album'} cmp $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$b}{'album'}} keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}}) {
                my $full_line = sprintf(" %$pl_t | %$pl_a | %$pl_al | %$pl_d",  $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'title'}, 
                                                      $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'artist'}, 
                                                      $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'album'}, 
                                                      $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'duration'});
                $pl{$j} = $full_line;
                $curlist->insert_at($j, $pl{$j});
                $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} = $j;
                $j++;
        }
    }
    else {
        $curlist->values("nothing found");
    }
    $curlist->values(sort { $a <=> $b } keys %pl);
    $curlist->labels(\%pl);
    $curlist->draw();
    $equalizer->text(sprintf("%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", 1 .. 10) .
                     sprintf("%4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f\n", @EQ));
    $equalizer->draw();
    $progressbar->draw();
}

# store the storage hashref to .whistle.dat
sub store_data {
    nstore($STORAGE, $STORFILE);
}


# quit the program, close filehandles, store data
sub quit_program {
    store_data();
    print $Min "quit\n";
    close $Min;
    close $Mout;
    kill $MPLPID;
    exit(0);
}

# find-function for found mp3's. writes returned data to storage hashref
sub found_mp3 {
    my $mime = mimetype($File::Find::name);
    my $mime_long = describe($mime);
    if ($mime_long =~ m/^MP3 audio$/) {
        my $md5sum = md5_hex($File::Find::name);
        my $taghash = get_mp3tag($File::Find::name);
        my $infohash = get_mp3info($File::Find::name);
        my $esc_name = $File::Find::name;
        $esc_name =~ s/ /\\ /g;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'filename'} = $esc_name;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'artist'} = $taghash->{ARTIST} || $taghash->{'artist'}|| "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'album'} = $taghash->{ALBUM} || $taghash->{'album'} || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'title'} = $taghash->{TITLE} || $taghash->{'title'} || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration'} = sprintf("%02d:%02d", 
                                                                        $infohash->{MM} || 0, $infohash->{SS} || 0);
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration_s'} = sprintf("%d", ($infohash->{MM} || 0) * 60 + ($infohash->{SS} || 0));
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} = $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} || "0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0";
        $num_songs++;
    }
    elsif ($mime_long =~ m/^Ogg Audio$/) {
        my $md5sum = md5_hex($File::Find::name);
        my $ogg = Ogg::Vorbis::Header::PurePerl->new($File::Find::name);
        my $esc_name = $File::Find::name;
        $esc_name =~ s/ /\\ /g;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'filename'} = $esc_name;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'artist'} = ($ogg->comment('artist'))[0] || ($ogg->comment('ARTIST'))[0] || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'album'} = ($ogg->comment('album'))[0] || ($ogg->comment('ALBUM'))[0] || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'title'} = ($ogg->comment('title'))[0] || ($ogg->comment('TITLE'))[0] || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration'} = sprintf("%02d:%02d", int($ogg->info('length') / 60), int($ogg->info('length') % 60));
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration_s'} = int($ogg->info('length'));
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} = $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} || "0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0";
        $num_songs++;
    }
    elsif ($mime_long =~ m/^FLAC audio$/) {
        my $md5sum = md5_hex($File::Find::name);
        my $flac = Audio::FLAC::Header->new($File::Find::name);
        my $info = $flac->info();
        my $tags = $flac->tags();
        my $esc_name = $File::Find::name;
        $esc_name =~ s/ /\\ /g;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'filename'} = $esc_name;
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'artist'} = $tags->{'ARTIST'} || $tags->{'artist'} || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'album'} = $tags->{'ALBUM'} || $tags->{'album'} || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'title'} = $tags->{'TITLE'} || $tags->{'title'} || "N/A";
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration'} = sprintf("%02d:%02d", $flac->{'trackLengthMinutes'}, $flac->{'trackLengthSeconds'});
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'duration_s'} = int($flac->{'trackTotalLengthSeconds'});
        $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} = $STORAGE->{'PLAYLIST'}{'ALL'}{$md5sum}{'eq'} || "0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0:0.0";
        $num_songs++;
    }
}

# func: add_dir
# adds a new directory to the STORAGE hashref.
sub add_dir {
    my $file = $cui->dirbrowser();
    if (defined $file && -d $file && length $file) {
        unless (grep {/^$file$/} @{$STORAGE->{'DIRS'}}) {
            push @{$STORAGE->{'DIRS'}}, $file;
            &rescan_dirs();
        }
    }
}

# rescan collection
my $wait;
sub rescan_dirs {
    $cui->status('\m/ ... scanning directories ... \m/');
    $num_songs = 0;
    $STORAGE->{'PLAYLIST'}{'ALL'} = undef;
    if (scalar @{$STORAGE->{'DIRS'}}) {
        find(\&found_mp3, @{$STORAGE->{'DIRS'}});
    }
    $cui->nostatus();
    &store_data();
    &draw_menu();
}

# func: new_pl
# creates a new playlist with an empty hashref ready for filling
sub new_pl {
    $pl_add = $cui->question('NEW playlist: choose name (a-zA-Z0-9_)');
    if (defined $pl_add) {
        if ($pl_add =~ m/^[a-zA-Z0-9_]+$/ && length $pl_add > 0 && $pl_add ne 'ALL') {
            unless (grep {/^$pl_add$/} keys %{$STORAGE->{'PLAYLIST'}}) {
                $STORAGE->{'PLAYLIST'}{$pl_add} = {};
            }
        }
    }
    &store_data();
    &draw_menu();
}

# func: rem_dir
# removes a music directory from the STORAGE hashref.
sub rem_dir {
    my $rem = $win->add(
            'rem', 'Listbox',
            -multi     => 0,
            -y => int ($win->height() / 2) - 4,
            -x => int ($win->width() / 3) + 1,
            -focusable => 1,
            -height => 15,
            -width => 60,
            -border => 1,
            -ipad => 3,
    );
    $rem->values(@{$STORAGE->{'DIRS'}}, 'CANCEL');
    $rem->title('DELETE directory: choose dir (or cancel) and press TAB');
    $rem->draw();
    $rem->modalfocus();
    my $selected = $rem->get();
    $rem->loose_focus();
    $win->delete('rem');
    if ($selected) {
        if ($selected ne 'CANCEL') {
            my @new_dirs = grep {!/^$selected$/} @{$STORAGE->{'DIRS'}};
            @{$STORAGE->{'DIRS'}} = @new_dirs;
            &rescan_dirs();
        }
    }
    for my $plist (grep {!/^ALL$/} keys %{$STORAGE->{'PLAYLIST'}}) {
        for my $md (keys %{$STORAGE->{'PLAYLIST'}{$plist}}) {
            unless (grep {/$md/} keys %{$STORAGE->{'PLAYLIST'}{'ALL'}}) {
                delete $STORAGE->{'PLAYLIST'}{$plist}{$md};
            }
        }
    }
    &draw_menu();
}

# func: del_pl
# deletes a playlist. the 'ALL' playlist must not be deleted!
sub del_pl {
    my $del = $win->add(
            'del', 'Listbox',
            -values    => [grep {!/^ALL$/} keys %{$STORAGE->{'PLAYLIST'}}, 'CANCEL'],
            -multi     => 0,
            -y => int ($win->height() / 2) - 4,
            -x => int ($win->width() / 3) + 1,
            -focusable => 1,
            -height => 15,
            -width => 60,
            -border => 1,
            -ipad => 3,
    );
    $del->title('DELETE playlist: choose list (or cancel) and press TAB');
    $del->draw();
    $del->modalfocus();
    my $pl_del = $del->get();
    $del->loose_focus();
    $win->delete('del');
    if (defined $pl_del && exists $STORAGE->{'PLAYLIST'}{$pl_del}) {
        delete $STORAGE->{'PLAYLIST'}{$pl_del};
    }
    &store_data();
    &draw_menu();
}

our ($art, $track, $file, $state, $cur_pos);
$state = 'Stopped';

# plays the selected item or next/prev if given
sub play {
    print $Min "stop\n";
    $MAX_PL = (scalar (keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}})) - 1;
    return if $MAX_PL < 0;
    my $next = shift || '';
    my ($index, $cur);
    if ($next eq 'NEXT') {
        $LAST_PLAYED = (($curlist->id())[0] || "$LAST_PLAYED");
        $index = $SHUFFLE ? int(rand($MAX_PL)) : (($curlist->id())[0] || "$LAST_PLAYED") + 1;
        $index = $index > $MAX_PL ? 0 : $index;
        $curlist->clear_selection();
        $curlist->set_selection($index);
        $cur = ($curlist->get())[0];
        $LAST_PLAYED = $index;
    }
    elsif ($next eq 'PREV') {
        $index = $SHUFFLE ? int(rand($MAX_PL)) : (($curlist->id())[0] || "$LAST_PLAYED") - 1;
        $LAST_PLAYED = $index;
        $index = $index < 0 ? $MAX_PL : $index;
        $curlist->clear_selection();
        $curlist->set_selection(($LAST_PLAYED && $SHUFFLE) ? "$LAST_PLAYED" : $index);
        $cur = ($curlist->get())[0];
        $LAST_PLAYED = ($curlist->id())[0] || "$LAST_PLAYED";
    }
    else {
        $index = ($curlist->id())[0] || '0';
        $curlist->set_selection($index);
        $cur = ($curlist->get())[0];
        $curlist->clear_selection();
        $curlist->set_selection($index);
        $LAST_PLAYED = $index;
    }
    my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$cur$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
    if (defined $msum) {
        $file = $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'filename'};
        $track = $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'title'} || '';
        $art = $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'artist'} || '';
        $cur_pos = $index;
    }
    if ($file) {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: Playing: " . $track . " by $art");
        $curlist->clear_selection();
        $curlist->draw();
        print $Min "af equalizer=" . $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'eq'} . "\n";
        @EQ = split /:/, $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'eq'};
        $equalizer->text(sprintf("%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", 1 .. 10) .
                         sprintf("%4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f\n", @EQ));
        $equalizer->draw();
        print $Min "loadfile $file\n";
        my $altime = $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}{'duration_s'} || '0';
        $TIMER->{'rest_time'} = $altime;
        $TIMER->{'max_time'} = $altime;
        alarm(1);
    }
    else {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: ERROR - Could not find file for position $cur");
        $curlist->clear_selection();
        $curlist->draw();
    }
    $state = 'Playing';
}

# stop the current track, deactivate alarm
sub stop {
    print $Min "stop\n";
    alarm(0);
    $state = 'Stopped';
    $TIMER->{'rest_time'} = 0;
    $TIMER->{'max_time'} = 0;
    $progressbar->title("--:-- / --:--");
    $progressbar->pos(0);
    $progressbar->draw();
    ($file, $track, $art) = (undef, undef, undef);
    $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state");
    $curlist->draw();
}

# pause the track, save alarm time, restart alarm if unpaused
our $rest_altime;
sub pause {
    if (defined $track) {
    	print $Min "pause\n";
    	$STATE = $STATE ? 0 : 1;
    	if ($STATE == 1) {
        	$rest_altime = $TIMER->{'rest_time'};
        	alarm(0);
        	$state = 'Paused';
        	$curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state");
    	}
    	else {
            $TIMER->{'rest_time'} = $rest_altime;
            alarm(1);
            $state = 'Playing';
	    $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state: $track by $art");
        }
    }
    $curlist->draw();
}


# add currently selected track to playlist
sub add_to_pl {
    my @num = $curlist->get();
    return unless @num;
    $sel_pl = $win->add(
            'sel_pl', 'Listbox',
            -values    => [grep {!/ALL/} keys %{$STORAGE->{'PLAYLIST'}}],
            -radio     => 1,
            -y => int ($win->height() / 2),
            -x => int ($win->width() / 3) + 1,
            -focusable => 1,
            -height => 10,
            -width => 60,
            -border => 1,
            -ipad => 2,
    );
    $sel_pl->title('ADD to playlist: choose playlist and press TAB');
    $sel_pl->draw();
    $sel_pl->modalfocus();
    my $selected = $sel_pl->get();
    $sel_pl->loose_focus();
    $win->delete('sel_pl');
    if (defined $selected) {
        for my $entry (@num) {
        my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$entry$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
            $STORAGE->{'PLAYLIST'}{$selected}{$msum} = $STORAGE->{'PLAYLIST'}{'ALL'}{$msum};
        }
    }
    &store_data();
    my $pltxt;
    $playlists->text('');
    $playlists->draw();
    $pltxt .= $CUR_LIST eq 'ALL' ? "[*] " : "[ ] ";
    $pltxt .= "ALL";
    $pltxt .= sprintf("  (%d)\n", scalar keys %{$STORAGE->{'PLAYLIST'}{'ALL'}});
    for (grep {!/ALL/} sort keys %{$STORAGE->{'PLAYLIST'}}) {
        $pltxt .= $CUR_LIST eq $_ ? "[*] " : "[ ] ";
        $pltxt .= "\'-> $_";
        $pltxt .= sprintf("  (%d)\n", scalar keys %{$STORAGE->{'PLAYLIST'}{$_}});
    }
    $playlists->text($pltxt);
    $playlists->draw();
}

# switch playlist
sub switch_pl {
    $sel_pl = $win->add(
            'sel_pl', 'Listbox',
            -values    => [keys %{$STORAGE->{'PLAYLIST'}}],
            -radio     => 1,
            -y => int ($win->height() / 2),
            -x => int ($win->width() / 3) + 1,
            -focusable => 1,
            -height => 10,
            -width => 60,
            -border => 1,
            -ipad => 2,
    );
    $sel_pl->title('SWITCH playlist: choose and press TAB');
    $sel_pl->draw();
    $sel_pl->modalfocus();
    my $selected = $sel_pl->get();
    $sel_pl->loose_focus();
    $win->delete('sel_pl');
    $CUR_LIST = $selected  || 'ALL'; 
    if ($state ne 'Playing') {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state");
    }
    else {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state: $track by $art");
    }
    &draw_menu();
}

# play next item in list
sub next { &play('NEXT'); }

# play previous item in list 
sub prev { &play('PREV'); }

# toggle shuffle, 1 = shuffle, 0 = normal playlist order
sub toggle_shuffle { 
    $SHUFFLE = $SHUFFLE ? 0 : 1; 
    if ($state ne 'Playing') {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state"); 
    }
    else {
        $curlist->title("Playlist: $CUR_LIST ( S: $SHUFFLE ) :: $state: $track by $art"); 
    }
    $curlist->draw();
}

# seek 10 seconds forward in music stream
sub seek_right {
    print $Min "seek 10\n";
    my $newal = $TIMER->{'rest_time'};
    if ($newal > 10) {
        $newal -= 10 ;
    }
    else { $newal = 1; }
    $TIMER->{'rest_time'} = $newal;
}

# seek 10 seconds back in music stream
sub seek_left {
    my $newal = $TIMER->{'rest_time'};
    if ($newal > 10) {
        print $Min "seek -10\n";
        $newal += 10;
    }
    $TIMER->{'rest_time'} = $newal;
}

# delete the currently selected item from the currently selected playlist
sub del_from_pl {
    my @to_del = $curlist->get();
    return unless @to_del;
    for my $del (@to_del) {
        my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$del$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
        if (exists $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum}) {
            delete $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$msum};
        }
    }
    &draw_menu();
    &store_data();
}

sub band_switch {
    my ($band) = @_;
    my @freq = ("31.25Hz", "62.50Hz", "125.0Hz", "250.0Hz", "500.0Hz",
                "1.0kHz",  "2.0kHz",  "4.0kHz",  "8.0kHz",  "16.0kHz");

    my $new_val = $cui->question("Please enter the new value for freq: $freq[$band] (currently $EQ[$band]dB)");
    $EQ[$band] = sprintf ("%4.1f", $new_val) if ((defined $new_val && $new_val == 0 ) || (defined $new_val && $new_val <= 12.0 && $new_val >= -12.0));

    $equalizer->text(sprintf("%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", 1 .. 10) .
                     sprintf("%4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f %4.1f\n", @EQ));
    $equalizer->draw();
    my $eq_str = join ":", @EQ;
    print $Min "af equalizer=$eq_str\n";
}

sub b1_switch  { &band_switch(0); }
sub b2_switch  { &band_switch(1); }
sub b3_switch  { &band_switch(2); }
sub b4_switch  { &band_switch(3); }
sub b5_switch  { &band_switch(4); }
sub b6_switch  { &band_switch(5); }
sub b7_switch  { &band_switch(6); }
sub b8_switch  { &band_switch(7); }
sub b9_switch  { &band_switch(8); }
sub b10_switch { &band_switch(9); }

sub help {
    $cui->dialog("P - play selected song\n" .
                 "p - pause\n" . "n - play next song\n" . "r - play previous song\n" .
                 "j - jump to currently playing song\n" . 
                 "S - stop playing\n". "t - toggle shuffle\n" . "e - rescan directories\n" .
                 "Q - quit\n\n" . "a - add to playlist\n" . "d - delete from playlist\n" .
                 "s - switch to playlist\n" . "A - add directory\n" . "R - remove directory\n" .
                 "N - new playlist\n" . "D - delete playlist\n\n" .
                 "v - save equalizer for artist/album/song\n\n" .
                 "SPACE or ENTER to select, TAB to confirm\nLEFT / RIGHT Arrow Key seek +-10s"
    );
}

# save equalizer settings
sub save_eq {
    my @songs = $curlist->get();
    my %choo = ( 1 => 'selected songs only', 2 => 'all songs on this album', 3 => 'all songs from this artist' );
    my $choose = $win->add(
            'sel_pl', 'Listbox',
            -values    => [""],
            -radio     => 1,
            -y => int ($win->height() / 2),
            -x => int ($win->width() / 3) + 1,
            -focusable => 1,
            -height => 9,
            -width => 60,
            -border => 1,
            -ipad => 2,
    );
    $choose->title('SAVE eq settings for: (press TAB afterwards):');

    $choose->values(sort keys %choo);
    $choose->labels(\%choo);
    $choose->draw();
    $choose->modalfocus();
    my $selected = $choose->get();
    $choose->loose_focus();
    $win->delete('sel_pl');
    if (defined $selected) {
        if ($selected == 1) {
            for my $song (@songs) {
                my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$song$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
                $STORAGE->{'PLAYLIST'}{'ALL'}{$msum}{'eq'} = join ":", @EQ;
            }
        }
        elsif ($selected == 2) {
            for my $song (@songs) {
                my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$song$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
                my $album = $STORAGE->{'PLAYLIST'}{'ALL'}{$msum}{'album'};
                my $artist = $STORAGE->{'PLAYLIST'}{'ALL'}{$msum}{'artist'};
                for my $k (keys %{$STORAGE->{'PLAYLIST'}{'ALL'}}) {
                    if ($STORAGE->{'PLAYLIST'}{'ALL'}{$k}{'album'} eq $album && $STORAGE->{'PLAYLIST'}{'ALL'}{$k}{'artist'} eq $artist) {
                        $STORAGE->{'PLAYLIST'}{'ALL'}{$k}{'eq'} = join ":", @EQ;
                    }
                }
            }
        }
        elsif ($selected == 3) {
            for my $song (@songs) {
                my ($msum) = grep { $STORAGE->{'PLAYLIST'}{$CUR_LIST}{$_}{'index'} =~ /^$song$/ } keys %{$STORAGE->{'PLAYLIST'}{$CUR_LIST}};
                my $artist = $STORAGE->{'PLAYLIST'}{'ALL'}{$msum}{'artist'};
                for my $k (keys %{$STORAGE->{'PLAYLIST'}{'ALL'}}) {
                    if ($STORAGE->{'PLAYLIST'}{'ALL'}{$k}{'artist'} eq $artist) {
                        $STORAGE->{'PLAYLIST'}{'ALL'}{$k}{'eq'} = join ":", @EQ;
                    }
                }
            }
        }
    }
    &store_data();
    &draw_menu();
}


sub jump {
    if (defined $file && $cur_pos <= $curlist->{-max_selected}) {
    	$curlist->{-ypos} = $cur_pos;
    	$curlist->draw();
    }
}


# define keybindings, draw menu for first time, start main loop :)
$cui->set_binding(\&help, "h");
$cui->set_binding(\&quit_program, "Q");
$cui->set_binding(\&add_dir, "A");
$cui->set_binding(\&rem_dir, "R");
$cui->set_binding(\&del_pl, "D");
$cui->set_binding(\&new_pl, "N");
$cui->set_binding(\&play, "P" );
$cui->set_binding(\&pause, "p" );
$cui->set_binding(\&add_to_pl, "a" );
$cui->set_binding(\&del_from_pl, "d" );
$cui->set_binding(\&switch_pl, "s" );
$cui->set_binding(\&next, "n" );
$cui->set_binding(\&prev, "r" );
$cui->set_binding(\&toggle_shuffle, "t" );
$cui->set_binding(\&stop, "S" );
$cui->set_binding(\&rescan_dirs, "e" );
$cui->set_binding(\&save_eq, "v" );
$cui->set_binding(\&jump, "j" );

$cui->set_binding(\&seek_right, KEY_RIGHT );
$cui->set_binding(\&seek_left, KEY_LEFT );

$cui->set_binding(\&b1_switch, KEY_F(1) );
$cui->set_binding(\&b2_switch, KEY_F(2) );
$cui->set_binding(\&b3_switch, KEY_F(3) );
$cui->set_binding(\&b4_switch, KEY_F(4) );
$cui->set_binding(\&b5_switch, KEY_F(5) );
$cui->set_binding(\&b6_switch, KEY_F(6) );
$cui->set_binding(\&b7_switch, KEY_F(7) );
$cui->set_binding(\&b8_switch, KEY_F(8) );
$cui->set_binding(\&b9_switch, KEY_F(9) );
$cui->set_binding(\&b10_switch, KEY_F(10) );

&draw_menu();
$cui->mainloop();
