#!/usr/bin/perl -w
use IO::Socket;
use Gtk;
use strict;

$| = 1;

my $version    = "0.02";
my $configfile = ".mpservrc";

my $conf = read_conf();
($$conf{server}[0] = shift) || ($$conf{server}[0] = "127.0.0.1");
($$conf{port}[0] = 1010) unless (defined $$conf{port}[0]);

print "\n=[ $0 v$version ]=\n";
print 
  "connecting to ", $$conf{server}[0], 
  " on port ", $$conf{port}[0],
  "\n";

my $db = BuildDB($conf->{server}[0],$conf->{port}[0]);
my $hack;
my $queue;			# gtk list widget for mp3 queue
my $tree;			# tree of bands albums songs
my $bas_node;			# band-album-song node;

init Gtk;
&top_window;
main Gtk;

sub caseless {
  uc($a) cmp uc($b);
}

sub top_window {

  my $win = new Gtk::Window "toplevel";
  $win->realize;
  $win->signal_connect("destroy", \&Gtk::main_quit);
  my $mainbox = new Gtk::VBox(0,0);
  $win->add($mainbox);
  my $pane = new Gtk::VPaned;
  my $swin = new Gtk::ScrolledWindow(undef, undef);
  $swin->set_policy('automatic', 'automatic');
  $swin->set_usize(200,400);
  $tree = new Gtk::Tree;
  $swin->add_with_viewport($tree);
  $pane->add1($swin);
  $mainbox->pack_start($pane,1,1,0);

  my $buttonbox = new Gtk::HBox(0, 0);
  my $style = Gtk::Widget->get_default_style;

  my $upbutton = new Gtk::Button;
  $upbutton->signal_connect("clicked", \&q_promote);
  my ($pixmap,$mask) = Gtk::Gdk::Pixmap->create_from_xpm($win->window, $style->bg('normal'), "up.xpm");
  die "couldn't open up.xpm\n" unless (defined $pixmap);
  my $p = new Gtk::Pixmap $pixmap, $mask;
  $upbutton->add($p);
  $p->show;
  $upbutton->set_usize(28,28);
  $buttonbox->pack_start($upbutton, 0, 1, 0);

  my $downbutton = new Gtk::Button;
  $downbutton->signal_connect("clicked", \&q_demote);
  my ($pixmap2,$mask2) = Gtk::Gdk::Pixmap->create_from_xpm($win->window, $style->bg('normal'), "down.xpm");
  die "couldn't open down.xpm\n" unless (defined $pixmap2);
  my $p2 = new Gtk::Pixmap $pixmap2, $mask2;
  $downbutton->add($p2);
  $p2->show;
  $downbutton->set_usize(28,28);
  $buttonbox->pack_start($downbutton, 0, 1, 0);
  
  my $deletebutton = new Gtk::Button;
  $deletebutton->signal_connect("clicked", \&q_remove);
  my ($pixmap3,$mask3) = Gtk::Gdk::Pixmap->create_from_xpm($win->window, $style->bg('normal'), "x.xpm");
  die "couldn't open x.xpm\n" unless (defined $pixmap3);
  my $p3 = new Gtk::Pixmap $pixmap3, $mask3;
  $deletebutton->add($p3);
  $p3->show;
  $deletebutton->set_usize(28,28);
  $buttonbox->pack_start($deletebutton, 0, 1, 0);

  my $button = new Gtk::Button('Refresh Q');
  $button->signal_connect("clicked", \&refresh_queue);
  $buttonbox->pack_start($button, 0, 1, 0);

  my $refresh_button = new Gtk::Button('Refresh Tree');
  $refresh_button->signal_connect("clicked", \&refresh_tree);
  $buttonbox->pack_start($refresh_button, 0, 1, 0);

  $mainbox->pack_start($buttonbox, 0, 1, 0);
  $buttonbox->show;
  $mainbox->show_all();

  &build_tree;

  $tree->show;
  $swin->show;
  my $swin2 = new Gtk::ScrolledWindow(undef, undef);
  $queue = new_with_titles Gtk::CList('artist', 'album', 'song');
#  $queue->set_shadow_type('etched_in');
  $queue->column_titles_passive;
#  $queue->column_titles_hide;
  $queue->set_column_width(0,30);
  
  &refresh_queue();
  
  $swin2->add_with_viewport($queue);
  $swin2->set_policy('automatic', 'automatic');
  $swin2->set_usize(200,100);
  $swin2->show();
  $pane->add2($swin2);
  $queue->show;
  $pane->show;
  $win->show;
} #end top window

sub build_tree {
  
  my ($bands,$songs);
  
  # Files come in these flavors:
  # Various=Album=track=Artist=Song
  # Artist=Album=track=Song
  # Artist=Album=Song
  # Artist=Song
  # Song
  
  foreach my $mp3 (keys %$db) {	# sort mp3's by band
#    if(defined $db->{$mp3}{'artist'} && defined $db->{$mp3}{'album'} && defined $db->{$mp3}{'title'}) {
      unless($db->{$mp3}{'artist'} eq 'unknown') {
	if(defined $db->{$mp3}{'track'} && $db->{$mp3}{'track'} ne 'unknown') {
	  $bands->{$db->{$mp3}{'artist'}}{$db->{$mp3}{'album'}}{'__tracks__'}[$db->{$mp3}{'track'}-1] = $mp3;
	} else {
	  $bands->{$db->{$mp3}{'artist'}}{$db->{$mp3}{'album'}}{$db->{$mp3}{'title'}} = $mp3;
	}
      } else {
	$songs->{$db->{$mp3}{'title'}} = $mp3;
      }
#    }
  }
  my $new_item;
  
  $bas_node = new_with_label Gtk::TreeItem "band-album-song";
  $tree->append($bas_node);
  my $tree3 = new Gtk::Tree;
  $bas_node->set_subtree($tree3);
		        
  foreach my $band (sort caseless keys %$bands) {
    my $node = new_with_label Gtk::TreeItem $band;
    $tree3->append($node);
    my $subtree = new Gtk::Tree;
    
    foreach my $album (sort caseless keys %{$bands->{$band}}) {

      my $album_node;
      my $sub_album;
      unless($album  eq 'unknown') {
	$album_node = new_with_label Gtk::TreeItem $album;
	$subtree->append($album_node);
	$sub_album = new Gtk::Tree;
      } else {
	$sub_album = $subtree;
      }
      foreach my $song (sort caseless keys %{$bands->{$band}{$album}}) {
	my $song_node;
	unless($song eq '__tracks__') {
	  $song_node = new_with_label Gtk::TreeItem $song;
	  $sub_album->append($song_node);
	  $song_node->show;
	  $hack->{$song_node} = $bands->{$band}{$album}{$song};
	  $song_node->signal_connect('select', \&q_mp3);
	} else {
	  my $i = 0;
	  foreach my $track (@{$bands->{$band}{$album}{$song}}) {
	    if(defined $track) {
	      $song_node = new_with_label Gtk::TreeItem $db->{$track}{'title'};
	      $sub_album->append($song_node);
	      $song_node->show;
	      $song_node->signal_connect('select', \&q_mp3);
	      $hack->{$song_node} = $track;
	    }
	  }
	}
      }
      unless($album  eq 'unknown') {
	$album_node->set_subtree($sub_album);
	$album_node->show;
      }
    }
    $node->set_subtree($subtree);
    $node->show;
  }

  my $node = new_with_label Gtk::TreeItem 'misc';
  $tree3->append($node);
  my $subtree = new Gtk::Tree;
  $node->set_subtree($subtree);
  
  foreach my $song (sort caseless keys %$songs) {
    my $song_node = new_with_label Gtk::TreeItem $song;
    $subtree->append($song_node);
    $hack->{$song_node} = $songs->{$song};
    $song_node->signal_connect('select', \&q_mp3);
    $song_node->show;
  }

  $node->show;
  $bas_node->show;
}

sub refresh_queue() {
  
  my $select = shift;
  
  my $fh = ServerCMD($conf->{server}[0], $conf->{port}[0], "q\nquit\n");
  $queue->clear;
  while(<$fh>) {
    s/\[[0-9]+\]\W+//;
    
    if(/^Various/) {
      my ($hack, $album, $hack2, $artist, $song) = split /=/;
      $queue->append($artist, $album, $song);
    } else {
      my ($artist, $album, $track, $song) = split /=/;
      if(not defined $song) {
	if(not defined $track) {
	  if(not defined $album) { # Song
	    $queue->append('','',$artist);
	  } else {		# Artist=Song
	    $queue->append($artist, '', $album);
	  }
	} else {		# Artist=Album=Song
	  $queue->append($artist, $album, $track);
	}
      } else {			# Artist=Album=track=song
	$queue->append($artist, $album, $song);
      }
    }
  }
  $queue->select_row($select, 0) if(defined $select);
  close $fh;
}

sub refresh_tree {
  ServerCMD($conf->{server}[0], $conf->{port}[0], "refresh\nquit\n");
  $tree->remove_items($bas_node);
  $db = undef;
  $db = &BuildDB($conf->{server}[0], $conf->{port}[0]);
  &build_tree;
}

sub q_promote {
  my $pos = $queue->selection;
  if(defined $pos) {
    ServerCMD($conf->{server}[0], $conf->{port}[0], "qup $pos\nquit\n");
    &refresh_queue($pos-1);
  }
}

sub q_demote {
  my $pos = $queue->selection;
  if(defined $pos) {
    ServerCMD($conf->{server}[0], $conf->{port}[0], "qdown $pos\nquit\n");
    &refresh_queue($pos+1);
  }
}

sub q_remove {
  my $pos = $queue->selection;
  if(defined $pos) {
    print "pos is $pos";
    ServerCMD($conf->{server}[0], $conf->{port}[0], "qrm $pos\nquit\n");
    &refresh_queue();
  }
}

sub q_mp3 {
  my $widget = shift;
  print "\nrequesting: $hack->{$widget}\n";
  ServerCMD($conf->{server}[0], $conf->{port}[0], "play $hack->{$widget}\nquit\n");
  &refresh_queue();  
}

sub ServerCMD {
  my ($host, $port, $cmd) = @_;
  my $remote = IO::Socket::INET->new(Proto     => "tcp",
				     PeerAddr  => $host,
				     PeerPort  => $port);
  unless ($remote) { die "cannot connect to http daemon on $host" }
  $remote->autoflush(1);
  print $remote $cmd;
  return $remote;
}

sub BuildDB {
  my ($server, $port) = @_;
  my %db;

  my ($LIST, $mp3);
  $LIST = ServerCMD($server, $port, "list\nquit\n");
  while ($mp3 = <$LIST>) {
    chomp $mp3;
    
    # Songnames come in these flavors:
    # Various=Album=track=Artist=Song
    # Artist=Album=track=Song
    # Artist=Album=Song
    # Artist=Song
    # Song
    
    # break the filename into fields
    my ($f1, $f2, $f3, $f4, $f5) = split /=/, $mp3;
    # start out not knowing anything
    foreach my $key ('artist','album','track','title') {
      $db{$mp3}{$key} = "unknown";
    }
    # now deduce what we can from the filename
    if ($f1 =~ /Various/i) {
      $db{$mp3}{'artist'} = "Various";
      $db{$mp3}{'album'}  = $f2;
      $db{$mp3}{'track'}  = $f3;
      $db{$mp3}{'title'}  = "$f4 / $f5";
    } elsif (defined $f2) {
      $db{$mp3}{'artist'} = $f1;
      if (defined $f3) {
	$db{$mp3}{'album'} = $f2;
	if (defined $f4) {
	  $db{$mp3}{'track'} = $f3;
	  $db{$mp3}{'title'} = $f4;
	} else { 
	  $db{$mp3}{'title'} = $f3;
	}
      } else { 
	$db{$mp3}{'title'} = $f2; 
      }
    } else { $db{$mp3}{'title'} = $f1; }
  }
  return \%db;
}


sub read_conf {
  my (@line,%conf);
  open(CONFIG,"$ENV{HOME}/$configfile") 
    or die("\nCouldn't open configuration file \"$ENV{HOME}/$configfile\"!\n");
  while (<CONFIG>) {
    next if /^\#/;
    next if /^\s+$/;
    chomp;
    @line = split /=/;
    push @{$conf{$line[0]}}, $line[1];
  }
  return(\%conf);
}
