#!/usr/bin/perl
###########################################
# rateplay - Rate MP3s and play them
# Mike Schilli, 2004 (m@perlmeister.com)
###########################################
use strict; use warnings;

our $DB_NAME  = "/data/rp.dat";
our $SONG_DIR = "/ms1/SONGS/pods";
our $FIND     = "/usr/bin/find";

use Gtk; use POE;
use Class::DBI;
use POE::Component::Player::Musicus;
use Algorithm::Numerical::Shuffle 
                               qw(shuffle);
my (%GUI, %RATED, $TAG, $SONG, @PLAYLIST, 
    @MP3S);
my @PLAY_ENERG = (0, 0, 0, 0, 0);
my @PLAY_SCHMO = (0, 0, 0, 0, 0);
my $RATE_ENERG = 0;
my $RATE_SCHMO = 0;
my @RATE_ENERG_BUTTONS = ();
my @RATE_SCHMO_BUTTONS = ();

###########################################
package Rateplay::DBI;
###########################################
use base q(Class::DBI);
use Class::DBI::AbstractSearch;

__PACKAGE__->set_db('Main',
  "dbi:SQLite:$main::DB_NAME", 'root', '');

if(! -e "$main::DB_NAME") {
    __PACKAGE__->set_sql(create => q{
        CREATE TABLE rated_songs (
            path VARCHAR(256) 
                 PRIMARY KEY NOT NULL,
            energize INT, schmoop  INT
        )
    });
    __PACKAGE__->sql_create()->execute();
}

###########################################
package Rateplay::Song;
###########################################
use base q(Rateplay::DBI);

__PACKAGE__->table('rated_songs');
__PACKAGE__->columns(
    All => qw(path energize schmoop));

###########################################
package main;
###########################################

my $PLAYER = 
    POE::Component::Player::Musicus->new();

POE::Session->create(
   package_states => [ "main" => [ 
     qw(getpos getinfocurr mp3_stdout
        song   scan_mp3s)]],

   inline_states => {
     _start      => \&my_gtk_init,
     poll_player => sub { 
      $PLAYER->getpos();
      $poe_kernel->delay('poll_player', 1);
   }});

$poe_kernel->post("main", "poll_player");
$poe_kernel->post("main", "scan_mp3s");
$poe_kernel->run();

###########################################
sub getpos {
###########################################
  our $POS;

  next_in_playlist() if defined $POS and 
       $POS > 0 and $_[ARG0] < 0;
  $POS = $_[ARG0];
}

###########################################
sub getinfocurr {
###########################################
    $TAG = $_[ARG0];
    $GUI{artist}->set($TAG->{artist});
    $GUI{title}->set($TAG->{title});
}

###########################################
sub song {
###########################################
    $SONG = $_[ARG0];
    $PLAYER->stop();
    $PLAYER->play($SONG);
    $PLAYER->getinfocurr();
    update_rating($SONG);
}

###########################################
sub scan_mp3s {
###########################################
  %RATED = map { $_->path() => 1 }
            Rateplay::Song->retrieve_all();

  my $comp = POE::Component::Child->new(
    events => { 'stdout' => 'mp3_stdout' },
  );

  $comp->run($FIND, $SONG_DIR);
}

###########################################
sub add_label {
###########################################
  my($parent, $text, @coords) = @_;
  
  my $lbl = Gtk::Label->new();
  $lbl->set_alignment(0.5, 0.5);
  $lbl->set($text);

  if(ref $parent eq "Gtk::Table") {
   $parent->attach_defaults($lbl, @coords);
  } else {
   $parent->pack_start($lbl, 0, 0, 0);
  }

  return $lbl;
}

###########################################
sub my_gtk_init {
###########################################
  my @btns = ("Play Rated", "Play Next",
           "Play Previous", "Random Rate");

  $poe_kernel->alias_set('main');

  $GUI{mw} = Gtk::Window->new();
  $GUI{mw}->set_default_size(150,200);

  $GUI{vb} = Gtk::VBox->new(0, 0);

  $GUI{$_}= Gtk::Button->new($_) for @btns;

  my $tbl = Gtk::Table->new(2, 6);
  $GUI{vb}->pack_start($tbl, 1, 1, 0);

  add_label($tbl, 'Energize', 0, 1, 0, 1);
  add_buttons($tbl,
    sub { $PLAY_ENERG[$_[1]] ^= 1 }, 0);
  add_label($tbl, 'Schmoop', 0, 1, 1, 2);
  add_buttons($tbl,
    sub { $PLAY_SCHMO[$_[1]] ^= 1}, 1);

        # Status line on top of buttons
  $GUI{status} = add_label($GUI{vb}, "");

      # Pack buttons
  $GUI{vb}->pack_start($GUI{$_}, 0, 0, 0) 
                                 for @btns;

  for(qw(artist title)) {
    $GUI{$_} = add_label($GUI{vb}, "");
  }

  $GUI{rate_table} = Gtk::Table->new(2, 6);
  $GUI{vb}->pack_start($GUI{rate_table}, 
                                  0, 0, 0);

  add_label($GUI{rate_table}, 
            'Energize', 0, 1, 0, 1);
  attach_radio_buttons($GUI{rate_table},
    sub { $RATE_ENERG = $_[1]+1;
        }, 0, \@RATE_ENERG_BUTTONS);
  add_label($GUI{rate_table}, 
            'Schmoop', 0, 1, 1, 2);
  attach_radio_buttons($GUI{rate_table},
    sub { $RATE_SCHMO = $_[1]+1;
        }, 1, \@RATE_SCHMO_BUTTONS);

  my $rate  = Gtk::Button->new('Rate');
  $GUI{vb}->pack_start($rate,  0, 0, 0);
  $GUI{mw}->add($GUI{vb});

      # Destroying window
  $GUI{mw}->signal_connect('destroy',
    sub {Gtk->exit(0)});

        # Pressing Play Rated button
  $GUI{$btns[0]}->signal_connect('clicked',
    sub { @PLAYLIST = select_songs();
      $GUI{status}->set("Playlist has " . 
        scalar @PLAYLIST . " songs.");
      next_in_playlist();
    });

      # Pressing Play Next button
  $GUI{$btns[1]}->signal_connect('clicked',
    sub { next_in_playlist() });

      # Pressing Play Previous button
  $GUI{$btns[2]}->signal_connect('clicked',
    sub { next_in_playlist(1) });

      # Pressing Random Rate Button
  $GUI{$btns[3]}->signal_connect('clicked',
    sub { @PLAYLIST = shuffle @MP3S;
      $GUI{status}->set("Random Rating " . 
             scalar @PLAYLIST . " songs.");
      next_in_playlist();
    });
        # Pressing Rate button
  $rate->signal_connect('clicked',
    sub { return unless defined $TAG;
          process_rating();
          next_in_playlist();
        } );

  $GUI{mw}->show_all();
}

###########################################
sub add_buttons {
###########################################
  my($table, $sub, $row) = @_;

  for (0..4) {
    my $b = Gtk::CheckButton->new($_+1);
    $b->signal_connect(clicked=> $sub, $_);
    $table->attach_defaults($b, 1+$_, 2+$_,
                           0+$row, 1+$row);
  }
}

###########################################
sub attach_radio_buttons {
###########################################
  my($table, $sub, $row, $buttons) = @_;
  my $group;

  for (0..4) {
    my $btn = Gtk::RadioButton->new($_+1, 
        defined $group ? $group : ());
    $group = $btn;
    $btn->signal_connect(clicked => $sub, 
                         $_);
    push @$buttons, $btn;
    $table->attach_defaults($btn, 1+$_, 
                     2+$_, 0+$row, 1+$row);
  }
}

###########################################
sub process_rating {
###########################################
  my $rec = Rateplay::Song->find_or_create(
                    { path     => $SONG });

  $rec->energize($RATE_ENERG);
  $rec->schmoop($RATE_SCHMO);
  $rec->update();
}

###########################################
sub select_songs {
###########################################
  my @energ = grep { $PLAY_ENERG[$_-1] } 
         (1..@PLAY_ENERG);
  my @schmo = grep { $PLAY_SCHMO[$_-1] } 
         (1..@PLAY_SCHMO);

  return sort { rand > 0.5 } 
         map { $_->path() } 
             Rateplay::Song->search_where({
               energize => [@energ, 0],
               schmoop  => [@schmo, 0]},
  );
}

###########################################
sub next_in_playlist {
###########################################
  my($backward) = @_;

  return unless scalar @PLAYLIST;
  my $path;

  { if($backward) {
      $path = pop @PLAYLIST;
      unshift @PLAYLIST, $path;
    } else {
      $path = shift @PLAYLIST;
      push @PLAYLIST, $path;
    }
    redo if defined $SONG and 
          $SONG eq $path and @PLAYLIST > 1;
  }

  $PLAYER->stop();
  $poe_kernel->post('main', 'song', $path);
}

###########################################
sub update_rating {
###########################################
  my ($path) = @_;

  if(my ($song) = Rateplay::Song->search(
                          path => $path)) {
    my $e = $song->energize();
    my $s = $song->schmoop();
    $RATE_SCHMO_BUTTONS[$s-1]->activate();
    $RATE_ENERG_BUTTONS[$e-1]->activate();
  } else {
    $RATE_SCHMO_BUTTONS[0]->activate();
    $RATE_ENERG_BUTTONS[0]->activate();
  }
}

###########################################
sub mp3_stdout {
###########################################
  my ( $self, $args ) = @_[ ARG0 .. $#_ ];

  return if exists $RATED{$args->{out}};

  push @MP3S, $args->{out};

  $GUI{status}->set(scalar @MP3S . 
              " songs ready for rating.");
}