#!/usr/bin/perl

# grenamr v0.1
# 2008-08-01
# Yoran Heling

# License: MIT


use warnings;
use strict;
use Gtk2 -init;

our $VERSION = '0.1';

my %w;
my $m;
use constant {
  C_OLD => 0,
  C_NEW => 1,
  C_FLG => 2,
  F_REG => 0x01,
  F_MAN => 0x02, # not implemented yet...
  F_CON => 0x04,
};


create();
Gtk2->main;


sub create {
  $w{win} = Gtk2::Window->new('toplevel');
  $w{win}->signal_connect(destroy => \&close);
  $w{win}->resize(500, 500);
  $w{win}->move(300, 300);
  $w{win}->set(title => 'GRenamR v'.$VERSION);
  
   $w{vbGlobal} = Gtk2::VBox->new(0, 0);
   $w{win}->add($w{vbGlobal});

    $w{hbDir} = Gtk2::HBox->new(0, 0);
    $w{vbGlobal}->pack_start($w{hbDir}, 0, 1, 0);

     $w{lbDir} = Gtk2::Label->new('_Directory');
     $w{hbDir}->pack_start($w{lbDir}, 0, 0, 5);
    
     $w{fcDir} = Gtk2::FileChooserButton->new('Select a directory', 'select-folder');
     $w{fcDir}->signal_connect('current-folder-changed', \&fetchdir);
     $w{hbDir}->pack_start($w{fcDir}, 1, 1, 0);
     $w{lbDir}->set(mnemonic_widget => $w{fcDir}, use_underline => 1);

    $w{swFiles} = Gtk2::ScrolledWindow->new;
    $w{swFiles}->set_policy('automatic', 'automatic');
    $w{vbGlobal}->pack_start($w{swFiles}, 1, 1, 0);

     $w{tvFiles} = Gtk2::TreeView->new;
     $w{swFiles}->add($w{tvFiles});
     $m = $w{lsFiles} = Gtk2::ListStore->new('Glib::String', 'Glib::String', 'Glib::Int');
     $w{tvFiles}->set_model($w{lsFiles});
     $w{tvFiles}->insert_column_with_data_func(0, '', Gtk2::CellRendererPixbuf->new, \&column_func, 0);
     my $c = Gtk2::CellRendererText->new;
     $w{tvFiles}->insert_column_with_data_func(1, 'Old', $c, \&column_func, 1);
     $w{tvFiles}->insert_column_with_data_func(2, 'New', $c, \&column_func, 2);
     $w{tvFiles}->get_column(1)->set(sizing => 'autosize');
     $w{tvFiles}->get_column(2)->set(sizing => 'autosize');

    $w{hbPerl} = Gtk2::HBox->new(0, 0);
    $w{vbGlobal}->pack_start($w{hbPerl}, 0, 1, 0);

     $w{lbPerl} = Gtk2::Label->new('_Expression');
     $w{hbPerl}->pack_start($w{lbPerl}, 0, 0, 5);
  
     $w{enPerl} = Gtk2::Entry->new;
     $w{enPerl}->signal_connect('changed', \&applyfilter);
     $w{hbPerl}->pack_start($w{enPerl}, 1, 1, 0);
     $w{lbPerl}->set(mnemonic_widget => $w{enPerl}, use_underline => 1);

     $w{imPerl} = Gtk2::Image->new_from_stock('gtk-yes', 'menu');
     $w{hbPerl}->pack_start($w{imPerl}, 0, 1, 0);

    $w{hbBottom} = Gtk2::HBox->new(0, 0);
    $w{vbGlobal}->pack_start($w{hbBottom}, 0, 1, 0);

     $w{alStatus} = Gtk2::Alignment->new(0,0.8,0,0);
     $w{hbBottom}->pack_start($w{alStatus}, 1, 1, 5);

      $w{lbStatus} = Gtk2::Label->new('');
      $w{alStatus}->add($w{lbStatus});
    
     $w{alBottom} = Gtk2::Alignment->new(1,1,0,1);
     $w{hbBottom}->pack_start($w{alBottom}, 0, 1, 0);
    
      $w{bbBottom} = Gtk2::HButtonBox->new;
      $w{bbBottom}->set(spacing => 5);
      $w{alBottom}->add($w{bbBottom});

       $w{btApply} = Gtk2::Button->new('_Rename');
       $w{btApply}->signal_connect('clicked', \&dorename);
       $w{bbBottom}->pack_start($w{btApply}, 0, 0, 5);

       $w{btClose} = Gtk2::Button->new('_Close');
       $w{btClose}->signal_connect('clicked', \&close);
       $w{bbBottom}->pack_start($w{btClose}, 0, 0, 5); 

  $w{win}->show_all;
  $w{enPerl}->grab_focus;

  if($ARGV[0] && -d $ARGV[0]) {
    $w{fcDir}->set_current_folder($ARGV[0]);
  } else {
    fetchdir();
  }
}


sub column_func {
  my ($column, $cell, $model, $iter, $col) = @_;
  my $flags = $model->get($iter, 2);
  $cell->set(
    $col > 0 ? (
      text => $model->get($iter, $col-1),
    ) : (
      'stock-id' => $flags & F_CON ? 'gtk-dialog-error'
                  : $flags & F_REG ? 'gtk-edit'
                  : $flags & F_MAN ? 'gtk-apply' 
                  : 'gtk-file',
    ),
    $flags & F_CON ? ( cell_background => '#ffcccc' ) :
    $flags & F_REG ? ( cell_background => '#ccffcc' ) :
    $flags & F_MAN ? ( cell_background => '#ffffcc' ) :
                     ( cell_background_set => 0 ),
  );
}


sub close {
  Gtk2->main_quit;
}


sub fetchdir {
  Gtk2->main_iteration_do(0);
  my $d = $w{fcDir}->get_current_folder;
  
  $w{lbStatus}->set(label => 'Loading...');

  my @lst;
  opendir(my $D, $d)
    || return $w{lbStatus}->set(label => 'Unable to open the selected directory');
  -f $d.'/'.$_ and push @lst, $_ for (readdir $D);
  closedir $D;

  $m->clear;
  $m->set($m->append, C_OLD, $_, C_NEW, $_, C_FLG, 0)
    for (sort @lst);
  $w{lbStatus}->set(label => sprintf '%d files found.', scalar @lst);
  applyfilter();
}


sub applyfilter {
  my $p = $w{enPerl}->get_text;

 # create a list of files
  my @list;
  my $i = $m->get_iter_first;
  do {
    push @list, $m->get($i, C_OLD)
  } while ($i = $m->iter_next($i));

 # eval using a for loop
  $i=-1;
  eval 'no strict; no warnings; for (@list) { ++$i; '.$p.' }';
  my $e = $@;
  $w{imPerl}->set(stock => $e ? 'gtk-no' : 'gtk-yes');

 # compare and update the list
  my $j=0; my $matched=0;
  $i = $m->get_iter_first;
  do {{
    next if $m->get($i, C_FLG) & F_MAN;
    my $match = $e || $m->get($i, C_OLD) eq $list[$j] ? 0 : 1;
    $m->set($i, C_NEW, $list[$j++], C_FLG, $match);
    $matched += $match;
  }} while ($i = $m->iter_next($i));

 # update status
  $w{lbStatus}->set(label => $e
    ? 'Invalid expression'
    : sprintf 'Matched %d/%d files.', $matched, scalar @list);

  findconflicts();
}


sub findconflicts {
 # create a list of old filenames
  my @old;
  my $i = $m->get_iter_first;
  do {
    push @old, $m->get($i, C_OLD);
  } while ($i = $m->iter_next($i));

 # search for modified items having a new filename in that list
  my $e=0;
  $i = $m->get_iter_first;
  do {{
    next if !$m->get($i, C_FLG);
    my $new = $m->get($i, C_NEW);
    my $match = $new eq '' || $new =~ /\// || grep $_ eq $new, @old;
    $m->set($i, C_FLG, $m->get($i, C_FLG) | F_CON)
      if $match;
    $e++ if $match;
  }} while ($i = $m->iter_next($i));
  
  $w{lbStatus}->set(label => 'Errors found!') if $e;
}


sub dorename {
  my $d = $w{fcDir}->get_current_folder;

 # count number of renamable files
  my $i = $m->get_iter_first;
  my $count=0;
  do {
    $count++ if $m->get($i, C_FLG) && !($m->get($i, C_FLG) & F_CON);
  } while ($i = $m->iter_next($i));

  return $w{lbStatus}->set(label => 'Nothing to do...') if !$count;

  $w{lbStatus}->set(label => 'Renaming...');
  my $j=0;
  $i = $m->get_iter_first;
  do {{
    next if !$m->get($i, C_FLG) || $m->get($i, C_FLG) & F_CON;
    rename $d.'/'.$m->get($i, C_OLD), $d.'/'.$m->get($i, C_NEW);
    $m->set($i, C_OLD, $m->get($i, C_NEW), C_FLG, 0);
    $w{lbStatus}->set(label => sprintf 'Renaming file %d/%d...', ++$j, $count);
    Gtk2->main_iteration_do(0);
  }} while ($i = $m->iter_next($i));

  $w{lbStatus}->set(label => sprintf 'Renamed %d files.', $j);
}



