package Lingua::Stem;

use strict;
use warnings;
require Exporter;
use Lingua::Stem::AutoLoader;

BEGIN {
    $Lingua::Stem::VERSION     = '2.31';
    @Lingua::Stem::ISA         = qw (Exporter);
    @Lingua::Stem::EXPORT      = ();
    @Lingua::Stem::EXPORT_OK   = qw (stem stem_in_place clear_stem_cache stem_caching add_exceptions delete_exceptions get_exceptions set_locale get_locale);
    %Lingua::Stem::EXPORT_TAGS = ( 'all' => [qw (stem stem_in_place stem_caching clear_stem_cache add_exceptions delete_exceptions get_exceptions set_locale get_locale)],
                    'stem' => [qw (stem)],
           'stem_in_place' => [qw (stem_in_place)],
                 'caching' => [qw (stem_caching clear_stem_cache)],
                  'locale' => [qw (set_locale get_locale)],
              'exceptions' => [qw (add_exceptions delete_exceptions get_exceptions)],
                 );
}

my $defaults = {
            -locale       => 'en',
           -stemmer       => \&Lingua::Stem::En::stem,
           -stem_in_place => \&Lingua::Stem::En::stem,
      -stem_caching       => \&Lingua::Stem::En::stem_caching,
  -clear_stem_cache       => \&Lingua::Stem::En::clear_stem_cache,
        -exceptions       => {},
      -known_locales => {
                          'da' => { -stemmer => \&Lingua::Stem::Da::stem,
                               -stem_caching => \&Lingua::Stem::Da::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Da::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'da' locale"); },
                           },
                          'de' => { -stemmer => \&Lingua::Stem::De::stem,
                               -stem_caching => \&Lingua::Stem::De::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::De::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'de' locale"); },
                           },
                          'en' => { -stemmer => \&Lingua::Stem::En::stem,
                               -stem_caching => \&Lingua::Stem::En::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En::clear_stem_cache,
                           -stem_in_place    => \&Lingua::Stem::En::stem,
                           },
                       'en_us' => { -stemmer => \&Lingua::Stem::En::stem,
                               -stem_caching => \&Lingua::Stem::En::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En::clear_stem_cache,
                           -stem_in_place    => \&Lingua::Stem::En::stem,
                           },
                       'en-us' => { -stemmer => \&Lingua::Stem::En::stem,
                               -stem_caching => \&Lingua::Stem::En::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En::clear_stem_cache,
                           -stem_in_place    => \&Lingua::Stem::En::stem,
                           },
                       'en_uk' => { -stemmer => \&Lingua::Stem::En::stem,
                               -stem_caching => \&Lingua::Stem::En::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En::clear_stem_cache,
                           -stem_in_place    => \&Lingua::Stem::En::stem,
                           },
                       'en-uk' => { -stemmer => \&Lingua::Stem::En::stem,
                               -stem_caching => \&Lingua::Stem::En::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En::clear_stem_cache,
                           -stem_in_place    => \&Lingua::Stem::En::stem,
                           },
                   'en-broken' => { -stemmer => \&Lingua::Stem::En_Broken::stem,
                               -stem_caching => \&Lingua::Stem::En_Broken::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::En_Broken::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'en-broken' locale"); },
                           },
                          'fr' => { -stemmer => \&Lingua::Stem::Fr::stem,
                               -stem_caching => \&Lingua::Stem::Fr::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Fr::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'fr' locale"); },
                           },
                          'gl' => { -stemmer => \&Lingua::Stem::Gl::stem,
                               -stem_caching => \&Lingua::Stem::Gl::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Gl::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'gl' locale"); },
                           },
                          'it' => { -stemmer => \&Lingua::Stem::It::stem,
                               -stem_caching => \&Lingua::Stem::It::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::It::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'it' locale"); },
                           },
                          'no' => { -stemmer => \&Lingua::Stem::No::stem,
                               -stem_caching => \&Lingua::Stem::No::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::No::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'no' locale"); },
                           },
                          'pt' => { -stemmer => \&Lingua::Stem::Pt::stem,
                               -stem_caching => \&Lingua::Stem::Pt::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Pt::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'pt' locale"); },
                           },
                          'sv' => { -stemmer => \&Lingua::Stem::Sv::stem,
                               -stem_caching => \&Lingua::Stem::Sv::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Sv::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'sv' locale"); },
                           },
                          'ru' => { -stemmer => \&Lingua::Stem::Ru::stem,
                               -stem_caching => \&Lingua::Stem::Ru::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Ru::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'ru' locale"); },
                           },
                          'ru_ru' => {
                                    -stemmer => \&Lingua::Stem::Ru::stem,
                               -stem_caching => \&Lingua::Stem::Ru::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Ru::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'ru_ru' locale"); },
                           },
                          'ru-ru' => {
                                    -stemmer => \&Lingua::Stem::Ru::stem,
                               -stem_caching => \&Lingua::Stem::Ru::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Ru::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'ru-ru' locale"); },
                           },
                          'ru-ru.koi8-r' => {
                                    -stemmer => \&Lingua::Stem::Ru::stem,
                               -stem_caching => \&Lingua::Stem::Ru::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Ru::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'ru-ru.koi8-r' locale"); },
                           },
                          'ru_ru.koi8-r' => {
                                    -stemmer => \&Lingua::Stem::Ru::stem,
                               -stem_caching => \&Lingua::Stem::Ru::stem_caching,
                           -clear_stem_cache => \&Lingua::Stem::Ru::clear_stem_cache,
                           -stem_in_place    => sub { require Carp; Carp::croak("'stem_in_place' not available for 'ru_ru.koi8-r' locale"); },
                           },
                   },
};

###

sub new {
    my $proto = shift;
    my $package = __PACKAGE__;
    my $proto_ref = ref($proto);
    my $class;
    if ($proto_ref) {
        $class = $proto_ref;
    } elsif ($proto) {
        $class = $proto;
    } else {
        $class = $package;
    }
    my $self = bless {},$class;

    # Set the defaults
    %{$self->{'Lingua::Stem'}->{-exceptions}}     = %{$defaults->{-exceptions}};
    $self->{'Lingua::Stem'}->{-locale}            = $defaults->{-locale};
    $self->{'Lingua::Stem'}->{-stemmer}           = $defaults->{-stemmer};
    $self->{'Lingua::Stem'}->{-stem_in_place}     = $defaults->{-stem_in_place};
    $self->{'Lingua::Stem'}->{-stem_caching}      = $defaults->{-stem_caching};
    $self->{'Lingua::Stem'}->{-clear_stem_cache}  = $defaults->{-clear_stem_cache};

    # Handle any passed parms
    my @errors = ();
    if ($#_ > -1) {
        my $parm_ref = $_[0];
        if (not ref $parm_ref) {
            $parm_ref = {@_};
        }
        foreach my $key (keys %$parm_ref) {
            my $lc_key = lc ($key);
            if    ($lc_key eq '-locale')         { $self->set_locale($parm_ref->{$key});            }
            elsif ($lc_key eq '-default_locale') { set_locale($parm_ref->{$key});                   }
            else                                 { push (@errors," '$key' => '$parm_ref->{$key}'"); }
        }
    }
    if ($#errors > -1) {
        require Carp;
        Carp::croak ($package . "::new() - unrecognized parameters passed:" . join(', ',@errors));
    }

    return $self;
}

###

sub set_locale {
    my ($self)   = shift;

    my ($locale);
    if (ref $self) {
        ($locale) = @_;
        $locale   = lc $locale;
        if (not exists $defaults->{-known_locales}->{$locale}) {
            require Carp;
            Carp::croak (__PACKAGE__ . "::set_locale() - Unknown locale '$locale'");
        }
        $self->{'Lingua::Stem'}->{-locale}           = $locale;
        $self->{'Lingua::Stem'}->{-stemmer}          = $defaults->{-known_locales}->{$locale}->{-stemmer};
        $self->{'Lingua::Stem'}->{-stem_in_place}    = $defaults->{-known_locales}->{$locale}->{-stem_in_place};
        $self->{'Lingua::Stem'}->{-stem_caching}     = $defaults->{-known_locales}->{$locale}->{-stem_caching};
        $self->{'Lingua::Stem'}->{-clear_stem_cache} = $defaults->{-known_locales}->{$locale}->{-clear_stem_cache};
    } else {
        $locale = lc $self;
        if (not exists $defaults->{-known_locales}->{$locale}) {
            require Carp;
            Carp::croak (__PACKAGE__ . "::set_locale() - Unknown locale '$locale'");
        }
        $defaults->{-locale}           = $locale;
        $defaults->{-stemmer}          = $defaults->{-known_locales}->{$locale}->{-stemmer};
        $defaults->{-stem_in_place}    = $defaults->{-known_locales}->{$locale}->{-stem_in_place};
        $defaults->{-stem_caching}     = $defaults->{-known_locales}->{$locale}->{-stem_caching};
        $defaults->{-clear_stem_cache} = $defaults->{-known_locales}->{$locale}->{-clear_stem_cache};
    }
    return;
}

###

sub get_locale {
    my $self = shift;

    if (ref $self) {
        return $self->{'Lingua::Stem'}->{-locale};
    } else {
        return $defaults->{-locale};
    }
}

###

sub add_exceptions {
    my $self;

    my ($exceptions, $exception_list);
    my $reference = ref $_[0];
    if ($reference eq 'HASH') {
        ($exceptions) =  @_;
        $exception_list = $defaults->{-exceptions};
    } elsif (not $reference) {
        $exceptions = { @_ };
        $exception_list = $defaults->{-exceptions};
    } else {
        $self = shift;
        ($exceptions) = @_;
        $exception_list = $self->{'Lingua::Stem'}->{-exceptions};
    }
    while (my ($exception,$replace_with) = each %$exceptions) {
            $exception_list->{$exception} = $replace_with;
    }
    return;
}

###

sub delete_exceptions {
    my $self;

    my ($exception_list,$exceptions);
    if ($#_ == -1) {
        $defaults->{-exceptions} = {};
        return;
    }
    my $reference =ref $_[0];
    if ($reference eq 'ARRAY') {
        ($exceptions) =  @_;
        $exception_list = $defaults->{-exceptions};
    } elsif (not $reference) {
        $exceptions = [@_];
        $exception_list = $defaults->{-exceptions};
    } else {
        $self = shift;
        if ($#_ == -1) {
            $self->{'Lingua::Stem'}->{-exceptions} = {};
        } else {
            $reference = ref $_[0];
            if ($reference eq 'ARRAY') {
                ($exceptions) =  @_;
                $exception_list = $self->{'Lingua::Stem'}->{-exceptions};
            } else {
                ($exceptions) = [@_];
                $exception_list = $self->{'Lingua::Stem'}->{-exceptions};
            }
        }
    }

    foreach (@$exceptions) { delete $exception_list->{$_}; }
    return;
}

###

sub get_exceptions {

    my $exception_list = {};
    if ($#_ == -1) {
        %$exception_list = %{$defaults->{-exceptions}};
        return $exception_list;
    }
    my $reference = ref $_[0];
    if ($reference eq 'ARRAY') {
        %$exception_list = %{$defaults->{-exceptions}};
    } elsif ($reference) {
        my $self = shift;
        if ($#_ > -1) {
            foreach (@_) {
                $exception_list->{$_} = $self->{'Lingua::Stem'}->{-exceptions}->{$_};
            }
        } else {
            %$exception_list = %{$self->{'Lingua::Stem'}->{-exceptions}};
        }
    } else {
        foreach (@_) {
            $exception_list->{$_} = $_;
        }
    }
    return $exception_list;
}

####

sub stem {
    my $self;
    return [] if ($#_ == -1);
    my ($exceptions,$locale,$stemmer);
    if (ref $_[0]) {
        my $self = shift;
        $exceptions = $self->{'Lingua::Stem'}->{-exceptions};
        $stemmer    = $self->{'Lingua::Stem'}->{-stemmer};
        $locale     = $self->{'Lingua::Stem'}->{-locale};
    } else {
        $exceptions = $defaults->{-exceptions};
        $stemmer    = $defaults->{-stemmer};
        $locale     = $defaults->{-locale};
    }
    &$stemmer({ -words => \@_,
               -locale => $locale,
           -exceptions => $exceptions });
}

###

sub stem_in_place {
    my $self;
    return [] if ($#_ == -1);
    my ($exceptions,$locale,$stemmer);
    if (ref $_[0]) {
        my $self = shift;
        $exceptions = $self->{'Lingua::Stem'}->{-exceptions};
        $stemmer    = $self->{'Lingua::Stem'}->{-stem_in_place};
        $locale     = $self->{'Lingua::Stem'}->{-locale};
    } else {
        $exceptions = $defaults->{-exceptions};
        $stemmer    = $defaults->{-stem_in_place};
        $locale     = $defaults->{-locale};
    }
    &$stemmer({ -words => [\@_],
               -locale => $locale,
           -exceptions => $exceptions });
}

###

sub clear_stem_cache {
    my $clear_stem_cache_sub;
    if (ref $_[0]) {
        my $self = shift;
        $clear_stem_cache_sub = $self->{'Lingua::Stem'}->{-clear_stem_cache};
    } else {
        $clear_stem_cache_sub = $defaults->{-clear_stem_cache};
    }
    &$clear_stem_cache_sub;
}

###

sub stem_caching {
    my $stem_caching_sub;
    my $first_parm_ref = ref $_[0];
    if ($first_parm_ref && ($first_parm_ref ne 'HASH')) {
        my $self = shift;
        $stem_caching_sub = $self->{'Lingua::Stem'}->{-stem_caching};
    } else {
        $stem_caching_sub = $defaults->{-stem_caching};
    }
    &$stem_caching_sub(@_);
}

1;
