#!/usr/bin/perl
#############################
# rateplay - Rate & Play MP3s
# 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 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"
  $GUI{ $btns[2] }
    ->signal_connect(
      'clicked', sub {
        next_in_playlist(1);
      });

  # Pressing "Random Rate"
  $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.");
}