#!/usr/bin/perl -w

# ** This script is a 10-minutes-hack, so it's EXPERIMENTAL. **
#
# Requires:
#  - Irssi 0.8.12 or newer (http://irssi.org/).
#  - GNU Aspell with appropriate dictionaries (http://aspell.net/).
#  - Perl module Text::Aspell (available from CPAN).
#
#
# Description:
#  Works as you type, printing suggestions when Aspell thinks
#  your last word was misspelled. 
#  It also adds suggestions to the list of tabcompletions,
#  so once you know last word is wrong, you can go back 
#  and tabcomplete through what Aspell suggests.
#
#
# Settings:
#
#  spellcheck_languages  -- a list of space and/or comma
#    separated languages to use on certain networks/channels.
#    Example: 
#    /set spellcheck_languages netA/#chan1/en_US, #chan2/fi_FI, netB/!chan3/pl_PL
#    will use en_US for #chan1 on network netA, fi_FI for #chan2
#    on every network, and pl_PL for !chan3 on network netB.
#    By default this setting is empty.
#
#  spellcheck_default_language  -- language to use in empty
#    windows, or when nothing from spellcheck_languages matches.
#    Defaults to 'en_US'.
#
#  spellcheck_enabled [ON/OFF]  -- self explaining. Sometimes
#    (like when pasting foreign-language text) you don't want
#    the script to spit out lots of suggestions, and turning it
#    off for a while is the easiest way. By default it's ON.
#
#
# BUGS:
#  - won't catch all mistakes
#  - picking actual words from what you type is very kludgy,
#    you may occasionally see some leftovers like digits or punctuation
#  - works every time you press space or a dot (so won't work for
#    the last word before pressing enter, unless you're using dot
#    to finish your sentences)
#  - when you press space and realize that the word is wrong,
#    you can't tabcomplete to the suggestions right away - you need
#    to use backspace and then tabcomplete. With dot you get an extra
#    space after tabcompletion.
#  - all words will be marked and no suggestions given if 
#    dictionary is missing (ie. wrong spellcheck_default_language)
#  - probably more, please report to $IRSSI{'contact'}
#
#
# $Id: spellcheck.pl 5 2008-05-28 22:31:06Z shasta $
#

use strict;
use vars qw($VERSION %IRSSI);
use Irssi 20070804;
use Text::Aspell;

$VERSION = '0.4';
%IRSSI = (
    authors     => 'Jakub Jankowski',
    contact     => 'shasta@toxcorp.com',
    name        => 'Spellcheck',
    description => 'Checks for spelling errors using Aspell.',
    license     => 'GPLv2',
    url         => 'http://toxcorp.com/irc/irssi/spellcheck/',
);

my %speller;

sub spellcheck_setup
{
    return if (exists $speller{$_[0]} && defined $speller{$_[0]});
    $speller{$_[0]} = Text::Aspell->new or return undef;
    $speller{$_[0]}->set_option('lang', $_[0]) or return undef;
    $speller{$_[0]}->set_option('sug-mode', 'fast') or return undef;
    return 1;
}

# add_rest means "add (whatever you chopped from the word before
# spellchecking it) to the suggestions returned"
sub spellcheck_check_word
{
    my ($lang, $word, $add_rest) = @_;
    my $win = Irssi::active_win();
    my @suggestions = ();

    # setup Text::Aspell for that lang if needed
    if (!exists $speller{$lang} || !defined $speller{$lang})
    {
	if (!spellcheck_setup($lang))
	{
	    $win->print("Error while setting up spellchecker for $lang");
	    # don't change the message
	    return @suggestions;
	}
    }

    # do the spellchecking
    my ($stripped, $rest) = $word =~ /([^[:punct:][:digit:]]{2,})(.*)/; # HAX
    # Irssi::print("Debug: stripped $word is '$stripped', rest is '$rest'");
    if (defined $stripped && !$speller{$lang}->check($stripped))
    {
	push(@suggestions, $add_rest ? $_ . $rest : $_) for ($speller{$lang}->suggest($stripped));
    }
    return @suggestions;
}

sub spellcheck_find_language
{
    my ($network, $target) = @_;
    return Irssi::settings_get_str('spellcheck_default_language') unless (defined $network && defined $target);

    # support !channels correctly
    $target = '!' . substr($target, 6) if ($target =~ /^\!/);

    # lowercase net/chan
    $network = lc($network);
    $target  = lc($target);

    # possible settings: network/channel/lang  or  channel/lang
    my @languages = split(/[ ,]/, Irssi::settings_get_str('spellcheck_languages'));
    for my $langstr (@languages)
    {
	# strip trailing slashes
	$langstr =~ s=/+$==;
	# Irssi::print("Debug: checking network $network target $target against langstr $langstr");
	my ($s1, $s2, $s3) = split(/\//, $langstr, 3);
	my ($t, $c, $l);
	if (defined $s3 && $s3 ne '')
	{
	    # network/channel/lang
	    $t = lc($s1); $c = lc($s2); $l = $s3;
	}
	else
	{
	    # channel/lang
	    $c = lc($s1); $l = $s2;
	}

	if ($c eq $target && (!defined $t || $t eq $network))
	{
	    # Irssi::print("Debug: language found: $l");
	    return $l;
	}
    }

    # Irssi::print("Debug: language not found, using default");
    # no match, use defaults
    return Irssi::settings_get_str('spellcheck_default_language');
}

sub spellcheck_key_pressed
{
    my ($key) = @_;
    my $win = Irssi::active_win();

    # I know no way to *mark* misspelled words in the input line,
    # that's why there's no spellcheck_print_suggestions -
    # because printing suggestions is our only choice.
    return unless Irssi::settings_get_bool('spellcheck_enabled');

    # don't bother unless pressed key is space or dot
    return unless (chr $key eq ' ' or chr $key eq '.');

    # get current inputline
    my $inputline = Irssi::parse_special('$L');

    # check if inputline starts with any of cmdchars
    # we shouldn't spellcheck commands
    my $cmdchars = Irssi::settings_get_str('cmdchars');
    my $cmdre = qr/^[$cmdchars]/;
    return if ($inputline =~ $cmdre);

    # get last bit from the inputline
    my ($word) = $inputline =~ /\s*([^\s]+)$/;

    # do not spellcheck urls
    my $urlre = qr/(^[a-zA-Z]+:\/\/\S+)|(^www)/;
    return if ($word =~ $urlre);

    # find appropriate language for current window item
    my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});

    my @suggestions = spellcheck_check_word($lang, $word, 0);
    # Irssi::print("Debug: spellcheck_check_word($word) returned array of " . scalar @suggestions);
    return if (scalar @suggestions == 0);

    # we found a mistake, print suggestions
    $win->print("Suggestions for $word - " . join(", ", @suggestions));
}


sub spellcheck_complete_word
{
    my ($complist, $win, $word, $lstart, $wantspace) = @_;

    return unless Irssi::settings_get_bool('spellcheck_enabled');

    # find appropriate language for the current window item
    my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});

    # add suggestions to the completion list
    push(@$complist, spellcheck_check_word($lang, $word, 1));
}


Irssi::settings_add_bool('spellcheck', 'spellcheck_enabled', 1);
Irssi::settings_add_str( 'spellcheck', 'spellcheck_default_language', 'en_US');
Irssi::settings_add_str( 'spellcheck', 'spellcheck_languages', '');

Irssi::signal_add_first('gui key pressed', 'spellcheck_key_pressed');
Irssi::signal_add_last('complete word', 'spellcheck_complete_word');
