#!/usr/bin/perl -w

# Original Author: Phil Carmody 
# (The FatPhil associated with asdf.org and fatphil.org)
# Placed in the Public Domain 2011-11-11

use strict qw(subs refs vars);
use Tk;
use Tk::LabEntry;
use Tk::BrowseEntry;
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );

my $theDictionaryFile=$ENV{'ANDICT'};
if(!$theDictionaryFile or !-f $theDictionaryFile) { $theDictionaryFile=$ENV{'DICT'}; }
if(!$theDictionaryFile or !-f $theDictionaryFile) { $theDictionaryFile='/usr/share/dict/words'; }

my $thePhrase='';
my $theWords='';
my @theWordList=();
my $theLetters='';
my $theMinWordLen=1;
my $theMaxWords=10;
my $theMaxNum=100000;
my $theConfigBoxVisible=0;
my $theNumWords='#words/time';
my $theNumAnagrams='#grams/time';


my $mainWindow=MainWindow->new;
$mainWindow->optionAdd('*font', 'Helvetica 16');
$mainWindow->title("An Affront!");

my $tlConfig;

my $pPhrase=$mainWindow->Frame;
my $lPhrase=$pPhrase->Label(-text => 'Phrase:');
my $ePhrase=$pPhrase->Entry(-textvariable => \$thePhrase, -exportselection => 1);

my $pWords=$mainWindow->Frame;
my $lWords=$pWords->Label(-text => 'Words:');
my $eWords=$pWords->Entry(-textvariable => \$theWords, -exportselection => 1);
my $bvDelWord=0;
my $bDelWord=$pWords->Checkbutton(-text=>'delete', 
                                  -indicatoron=>0, 
                                  -variable=>\$bvDelWord,
                                  -command=>sub { $theWords =~ s/\s*\S+\s*$//; $bvDelWord=0; });

my $pStack=$mainWindow->Frame;
my $theStackTop='';
my $iStackTopIndex=undef;
my $eStack=$pStack->BrowseEntry(-label => 'Stack:',
                                -variable => \$theStackTop,
                                -browse2cmd => sub { $theWords=$theStackTop; $iStackTopIndex=$_[1]; });
my $bPushStack=$pStack->Button(-text=>'store',
                               -padx=>0, -pady=>0,
                               -command=>sub {
                                   if(length($theWords)) {
                                       $theStackTop=$theWords;
                                       $iStackTopIndex=0;
                                       $eStack->insert(0, $theWords); 
                                   }
                               }
                               );
my $bDropStack=$pStack->Button(-text=>'drop',
                               -padx=>0, -pady=>0,
                               -command=>sub {
                                   return if(!defined($iStackTopIndex));
                                   $eStack->delete($iStackTopIndex, $iStackTopIndex);
                                   if($iStackTopIndex>0) {
                                       --$iStackTopIndex;
                                   }
                                   $theStackTop=$eStack->get($iStackTopIndex) || '';
                                   if(!length($theStackTop)) { $iStackTopIndex=undef; }
                               }
                               );

my $pAction=$mainWindow->Frame;
my $bvGetDict=0;
my $bGetDict=$pAction->Checkbutton(-text=>'Get Words!', 
                                   -indicatoron=>0, 
                                   -variable=>\$bvGetDict,
                                   -command=>\&getDict);
my $lNumWords=$pAction->Label(-textvariable => \$theNumWords);
my $cbConfigure=$pAction->Checkbutton(-text=>'Configuration', 
                                      -indicatoron=>0, 
                                      -variable=>\$theConfigBoxVisible, 
                                      -command=>\&configure);
my $lNumAnagrams=$pAction->Label(-textvariable => \$theNumAnagrams);
my $bvGetAnagrams=0;
my $bGetAnagrams=$pAction->Checkbutton(-text=>'Get Anagrams', 
                                       -indicatoron=>0, 
                                       -variable=>\$bvGetAnagrams,
                                       -command=>\&getAnagrams);

my $pDict=$mainWindow->Frame;
my $pFilter=$pDict->Frame;
my $eWContains=$pFilter->Entry(-textvariable => \$theLetters, -width=>4);
#my $bvFilter=0;
#my $cbFilter=$pFilter->Checkbutton(-text=>'Filter',
#                                   -indicatoron=>0,
#                                   -variable=>\$bvFilter,
#                                   -command=>\&filterDict);
my $bFilter=$pFilter->Button(-text=>'Filter',
                             -padx=>0, -pady=>0,
                             -command=>\&filterDict);
my $slDict=$pDict->Scrolled('Listbox', -scrollbars => 'e', -selectmode => "single");
$slDict->bind('<Double-Button-1>', \&selectWord);

my $slAnag=$mainWindow->Scrolled('Listbox', -scrollbars => 'e', -selectmode => "single");
$slAnag->bind('<Double-Button-1>', sub { print $slAnag->get($slAnag->curselection()); });



$lPhrase->pack(-side=>'left');
$ePhrase->pack(-side=>'right', -expand=>1, -fill=>'x');
$pPhrase->pack(-side=>'top', -fill=>'x');

$lWords->pack(-side=>'left');
$bDelWord->pack(-side=>'right');
$eWords->pack(-side=>'left', -expand=>1, -fill=>'x');
$pWords->pack(-side=>'top', -fill=>'x');

$bDropStack->pack(-side=>'right');
$bPushStack->pack(-side=>'right');
$eStack->pack(-side=>'left', -expand=>1, -fill=>'x');
$pStack->pack(-side=>'top', -fill=>'x');

$lNumWords->pack(-side=>'left');
$bGetDict->pack(-side=>'left');
$lNumAnagrams->pack(-side=>'right');
$bGetAnagrams->pack(-side=>'right');
$cbConfigure->pack(-side=>'left');
$pAction->pack(-side=>'top');

$bFilter->pack(-side=>'left');
$eWContains->pack(-side=>'left');
$pFilter->pack(-side=>'top');
$slDict->pack(-side=>'left', -fill=>'y');
$pDict->pack(-side=>'left', -fill=>'y');
$slAnag->pack(-side=>'right', -fill=>'both', -expand=>1);


sub filterDict
{
    $slDict->delete(0,'end');
    if($theLetters ne '') 
    {
        my $letterre=join('.*',sort(split('',$theLetters)));
        my $word;
        my @fdict=grep { $word=join('',sort(split('',$_))); $word=~m/$letterre/i; } @theWordList;
        $slDict->insert('end',@fdict);
    }
    else
    {
        $slDict->insert('end',@theWordList);
    }
}


sub getDict
{
    if($bvGetDict && -r $theDictionaryFile)
    {
        my $time = [gettimeofday];
        my $dict    =$theDictionaryFile?" -d '$theDictionaryFile'":'';
        my $used    =$theWords;
	$used=~s/[^[:alpha:] ]//g;
	if($used) { $used=" -u '$used'"; }
        my $phrase=$thePhrase; 
        $phrase=~s/[^[:alpha:]]//g;
        my $minlen  =$theMinWordLen>1?" -m $theMinWordLen":'';
        @theWordList=();
        if(open(AN, "an $dict$used$minlen -w '$phrase'|"))
        {
            while(<AN>) { chomp; push(@theWordList, $_); }
            close(AN);
        }
        else
        {
            print STDERR "Command failed: $@\nan $dict$used$minlen -w '$phrase'|";
        }    
        filterDict();
        my $elapsed = int(100*tv_interval( $time, [gettimeofday])+.5)/100;
        $theNumWords = scalar(@theWordList)."w in ${elapsed}s.";
    }
    $bvGetDict=0;
}

sub getAnagrams
{
    if($bvGetAnagrams && -r $theDictionaryFile)
    {
        my $time = [gettimeofday];
        my $dict    =$theDictionaryFile?" -d '$theDictionaryFile'":'';
        my $used    =$theWords;
	$used=~s/[^[:alpha:] ]//g;
	if($used) { $used=" -c '$used'"; } # faster than -u and rebuild
        my $phrase=$thePhrase; 
        $phrase=~s/[^[:alpha:]]//g;
        my $maxnum  =$theMaxNum      ?" -n $theMaxNum":'';
        my $maxwords=$theMaxWords<10 ?" -l $theMaxWords":'';
        open(AN, "an $dict$used$maxnum$maxwords '$phrase'|");
        my @grams=();
	while(<AN>) { chomp; push(@grams, $_); } # faster than block read
        close(AN);
        my $elapsed = int(100*tv_interval( $time, [gettimeofday])+.5)/100;
        $theNumAnagrams = scalar(@grams)." in ${elapsed}s.";
        $slAnag->delete(0,'end');
        $slAnag->insert('end',@grams);
    }
    $bvGetAnagrams=0;
}


sub selectWord
{
    my @words=();
    if($theWords)
    {
        @words=split(/\s+/, $theWords);
        if(!$words[0]) { shift(@words); }
        if(!$words[-1]) { pop(@words); }
    }
    push @words, $slDict->get($slDict->curselection( ));
    $theWords = join(' ', @words);
}


sub configure
{
    if(!$theConfigBoxVisible)
    {
        $tlConfig->withdraw();
    }
    elsif(!$tlConfig)
    {
        $tlConfig=$mainWindow->Toplevel();
        $tlConfig->title('Configure An Affront');
        $tlConfig->Button(-text => 'Close', 
                          -command=>sub { $tlConfig->withdraw(); $theConfigBoxVisible=0; })
            ->pack();
        $tlConfig->LabEntry(-label => 'dictionary', 
                            -textvariable => \$theDictionaryFile)
            ->pack(-expand=>1, -fill=>'x');
        $tlConfig->Scale(-label=>'dictionary min word length', 
                         -variable=>\$theMinWordLen, 
                         -from=>1, -to=>12, 
                         -orient=>'horizontal')
            ->pack(-expand=>1, -fill=>'x');
    }
    else
    {
        $tlConfig->deiconify();
        $tlConfig->raise();
    }
}
MainLoop;
