The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package AI::Util;

BEGIN {
    use Exporter ();
    our ( $VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS );

    # set the version for version checking
    $VERSION = 0.01;

    @ISA = qw(Exporter);

    # functions
    @EXPORT = qw(
        &say
        &connect_to
        &only_have_ability
        &be_service
        &kill_all_subprocesses
        &kill_process_from
        &client_setup
        &LANGUAGE
        &get_data
        &exit_handler
        &is_array
        &ascii
        &get_client_response
    );
    %EXPORT_TAGS = ();    # eg: TAG => [ qw!name1 name2! ],
    
    my @subpaths = ('AI/FreeHAL/Module/');
    my @modules_with_functions;
        
    our $data = {};    
    sub get_data {
        return $data;
    }

    foreach my $_inc_path ('.', @INC) {
        foreach my $subpath (@subpaths) {
            my $inc_path = $_inc_path . '/' . $subpath;
            print "- Looking in ", $inc_path, "\n";
            opendir(my $directory, $inc_path);
            my @files = grep { /\.pm$/i } readdir($directory);
            closedir($directory);
            
            FILE:
            foreach my $file (@files) {
                my $filename = $inc_path . $file;
                print "  - Found: ", $filename, "\n";
                
                my $module = $subpath . $file;
                $module =~ s/\..*?$//gm;
                $module =~ s/\//::/gm;
                print "    - Module: ", $file, "\n";

                eval 'use ' . $module . ';';
                if ($@) {
                    print '      - ', $@, "\n" ;                    
                    print '      - next...', "\n";
                    next FILE;
                };
                print '      - ', $@, "\n" if $@;
                

                eval {
                    $SIG{__DIE__} = 'IGNORE';
                    my @funcs;
                    eval '@funcs = @' . $module . '::functions;';
                    print '      - ', $@, "\n" if $@;
                    eval '*' . $module . '::get_data = *get_data;';
                    eval '*' . $module . '::data = *data;';
                    print '      - ', $@, "\n" if $@;
                    
                    if ( @funcs ) {
                        foreach my $func (@funcs) {
                            print "      - Function: ", $func, "\n";
                            eval '*' . $func . ' = *' . $module . '::' . $func . ';';
                            push @modules_with_functions, [ '*' . $module . '::' . $func, $func ];
                            print '      -', $@, "\n" if $@;
                            push @EXPORT, '&' . $func;
                        }
                    }
                    else {
                        print "      - No functions given.\n";
                    }
                };
                print $@, "\n" if $@;
            }
        }
    }
    
    my @modules_found = ();
    
    foreach my $_inc_path ('.', @INC) {
        foreach my $subpath (@subpaths) {
            my $inc_path = $_inc_path . '/' . $subpath;
            opendir(my $directory, $inc_path);
            my @files = grep { /\.pm$/i } readdir($directory);
            closedir($directory);
            
            FILE:
            foreach my $file (@files) {
                my $filename = $inc_path . $file;
                
                my $module = $subpath . $file;
                $module =~ s/\..*?$//gm;
                $module =~ s/\//::/gm;

                eval 'use ' . $module . ';';
                if ($@) {
                    print '      - ', $@, "\n" ;                    
                    print '      - next...', "\n";
                    next FILE;
                };
                
                push @modules_found, $module;
                

                eval {
                    $SIG{__DIE__} = 'IGNORE';
                    
                    if ( @modules_with_functions ) {
                        foreach my $func (@modules_with_functions) {
                            my $str_to_eval = '*' . $module . '::' . $func->[1] . ' = ' . $func->[0] . ' if ' . $func->[0] . ';';
                            #print 'eval ', $str_to_eval , ';', "\n";
                            eval $str_to_eval;
                            print $@, "\n" if $@;
                        }
                    }
                };
                print $@, "\n" if $@;
            }
        }
    }
    
    
    my $str_to_eval = map { 'use ' . $_ . ';' . "\n" } @modules_found;
    print $str_to_eval;
    eval $str_to_eval;
    print $@, "\n" if $@;
    foreach my $module (@modules_found) {
        eval {
            $SIG{__DIE__} = 'IGNORE';
            
            if ( @modules_with_functions ) {
                foreach my $func (@modules_with_functions) {
                    my $str_to_eval = '*' . $module . '::' . $func->[1] . ' = ' . $func->[0] . ' if ' . $func->[0] . ';';
                    #print 'eval ', $str_to_eval , ';', "\n";
                    eval $str_to_eval;
                    print $@, "\n" if $@;
                }
            }
        };
        print $@, "\n" if $@;
    }

    # your exported package globals go here,
    # as well as any optionally exported functions
    @EXPORT_OK = qw();
}
our @EXPORT_OK;

use Data::Dumper;
use AI::FreeHAL::Config;

our $LANGUAGE;
our $batch;

sub LANGUAGE {
    return &$LANGUAGE;
}

sub say {
    print scalar localtime;
    print ': ';
    print grep { defined $_ } @_;
    print "\n";
    return 1;
}

=head2 get_client_response( $CLIENT_ref )

Returns the next line sent from socket reference $CLIENT_ref.

=cut

sub get_client_response {
    my ( $CLIENT_ref, $not_exit_this_thread ) = @_;
    my $CLIENT = $$CLIENT_ref;

    read_config $data->{intern}{config_file} => my %config;

    my $line = '';
    while ( defined( $line = <$CLIENT> ) ) {
        chomp $line;
        next if $line =~ /^EMPTY/;

        #        print $line . "\n";

        $line =~ s/[:]/: /igm;
        $line =~ s/[:]\s+[:]\s+/::/igm;
        my @parts = split /[:]\s+/, $line;

        if ( $line =~ /^EXIT/ ) {
            kill_all_subprocesses();
            exit 0;
        }

        if ( $line =~ /^HERE_IS_.*?EXIT$/ ) {
            next;
            threads->self()->kill('TERM') if !$not_exit_this_thread;
            threads->self()->kill('KILL') if !$not_exit_this_thread;
            return 'EXIT_THREAD'          if $not_exit_this_thread;
        }

        if ( $line =~ /^OFFLINE_MODE/ ) {
            $config{'modes'}{'offline_mode'} = $parts[1];
            $config{'modes'}{'offline_mode'} = 0
              if !$config{'modes'}{'offline_mode'};
            print '$config{\'modes\'}{\'offline_mode\'} = ',
              $config{'modes'}{'offline_mode'}, "\n";
            write_config %config, $data->{intern}{config_file};

            next;
        }

        if ( $line =~ /^SPEECH_MODE/ ) {
            $config{'modes'}{'speech_mode'} = $parts[1];
            print '$config{\'speech\'}{\'activated\'} = ',
              $config{'modes'}{'speech_mode'}, "\n";
            write_config %config, $data->{intern}{config_file};

            next;
        }

        if ( $line =~ /^GET_DB_STRING/ ) {
            my @lines = ();

            opendir( my $dir_handle,
                $data->{intern}{dir} . "/lang_" . LANGUAGE() );
            foreach my $filename ( readdir $dir_handle ) {
                next if $filename =~ /^\./;
                next if $filename !~ /\.pro$/;

                open my $db_file, "<",
                  $data->{intern}{dir} . "/lang_" . LANGUAGE() . '/' . $filename
                  or warn "Error while opening file: $!";
                push @lines, <$db_file>;
                close $db_file;
            }
            closedir $dir_handle;

            my $db_string = q{};    # empty
            map { chomp $_ } @lines;

            $db_string .= join '<BR>', sort @lines;
            $parts[1] =~ s/[:]/::/igm;
            print $CLIENT "HERE_IS_DB_STRING:", $db_string, ':', $parts[1],
              "\n";

            next;
        }

        last;
    }
    return $line;
}

sub connect_to {
    my %arg = ();
    %arg = ( %arg, @_ );
    
    local *data = $arg{data};
    read_config $data->{intern}{config_file} => my %config;

    my $server =
         $arg{host}
      || $config{'servers'}{ 'host_' . $arg{name} }
      || '127.0.0.1';
    my $port = $arg{port} || $config{'servers'}{ 'port_' . $arg{name} };

    if ( !$server || !$port ) {
        say ' information for: ', $arg{name};
        say 'server: ',                     $server;
        say 'port:   ',                     $port;
        return \undef;
    }

    if ( $port =~ /no/ && $server =~ /no/ ) {
        $data->{abilities}->{ $arg{name} } = 1;

        only_have_ability( name => $arg{name} );

        return \undef;
    }

    my $sock = new IO::Socket::INET(
        PeerAddr => $server,
        PeerPort => $port,
        Proto    => 'tcp',
        Blocking => 1,
    );

    if ( !$sock && !$arg{no_reconnect} ) {
        if ( !$sock && !$data->{abilities}->{ $arg{name} } ) {
            $sock = ${
                connect_to(
                    data         => $data,
                    name         => $arg{name},
                    host         => '127.0.0.1',
                    no_reconnect => 1
                )
              };
        }

        select undef, undef, undef, 1;

        if ( !$sock && !$data->{abilities}->{ $arg{name} } ) {
            $sock = ${
                connect_to(
                    data         => $data,
                    name         => $arg{name},
                    host         => '127.0.0.1',
                    no_reconnect => 1
                )
              };
        }

        select undef, undef, undef, 1;

        if ( !$sock && !$data->{abilities}->{ $arg{name} } ) {
            $sock = ${
                connect_to(
                    data         => $data,
                    name         => $arg{name},
                    host         => '127.0.0.1',
                    no_reconnect => 1
                )
              };
        }

        select undef, undef, undef, 1;

        if ( !$sock && !$data->{abilities}->{ $arg{name} } ) {
            $sock = ${
                connect_to(
                    data         => $data,
                    name         => $arg{name},
                    host         => '127.0.0.1',
                    no_reconnect => 1
                )
              };
        }

        select undef, undef, undef, 1;

        if ( !$sock && !$data->{abilities}->{ $arg{name} } ) {
            say 'service is down: ', $arg{name};
            say 'server: ',          $server;
            say 'port:   ',          $port;
            say 'I will be ',        $arg{name}, '!';

            be_service( name => $arg{name} );

            return connect_to(
                    data         => $data,
                    name => $arg{name},
                    host => '127.0.0.1'
                );
        }
    }

    return \$sock;
};

sub only_have_ability {
    my %arg = ();
    %arg = ( %arg, @_ );


    my $service = $arg{name};

    if ( !$service ) {
        say 'cannot be nothing. no service specified to only_have_ability();';
        return;
    }

    eval "AI::FreeHAL::Engine::start_ability_$service();";
    if ($@) {
        say $@;
    }
}

sub be_service {
    my %arg = ();
    %arg = ( %arg, @_ );

    my $service = $arg{name};

    if ( !$service ) {
        say 'cannot be nothing. no service specified to be_service();';
        return;
    }

    # eval 'start_service_' . $service . '();';

    my $filename = 'jeliza-service-' . $service . '.pl';
    open my $file, '>', $filename;
    print $file q[#!/usr/bin/env perl
#
#   This program is free software; you can redistribute it and/or modify  
#   it under the terms of the GNU General Public License as published by  
#   the Free Software Foundation; either version 3 of the License, or     
#   (at your option) any later version.                                   
#                                                                         

#fork && exit;

open my $pidfile, '>', '] . $service . q[.pid';
print $pidfile $$;
close $pidfile;

use strict;
use warnings;
use AI::FreeHAL::Config;

sub LANGUAGE {
	return '] . LANGUAGE() . qq[';
}
sub no_answers_found {
}

require './jeliza-engine.pl' or require 'jeliza-engine.pl';

] . q[
sub impl_get_genus {
	my ( $CLIENT_ref, $word ) = @_;
	my $CLIENT = $$CLIENT_ref;
    
    alarm(60);

	print $CLIENT 'GET_GENUS:' . $word . "\n"
	  or die "Error:" . 'GET_GENUS:' . $word . "\n";
	while ( my $line = get_client_response( $CLIENT_ref ) ) {
		chomp $line;
		print 'line: ' . $line . "\n";
		if ( $line =~ /HERE_IS_GENUS/ ) {
			$line =~ s/HERE_IS_GENUS[:]//i;
			return $line;
		}
	}
}

sub impl_get_noun_or_not {
	my ( $CLIENT_ref, $word ) = @_;
	my $CLIENT = $$CLIENT_ref;

    alarm(60);

    print $CLIENT 'GET_NOUN_OR_NOT:' . $word . "\n"
	  or die "Error:" . 'GET_NOUN_OR_NOT:' . $word . "\n";
	while ( my $line = get_client_response( $CLIENT_ref ) ) {
		chomp $line;
		print 'line: ' . $line . "\n";
		if ( $line =~ /HERE_IS_NOUN_OR_NOT/ ) {
			$line =~ s/HERE_IS_NOUN_OR_NOT[:]//i;
			return $line;
		}
	}
	print "\n\n\nCommunication Error!\n" . $CLIENT . "\n" . $word . "\n\n";
}

sub impl_get_pos {
	my ( $CLIENT_ref, $word ) = @_;
	my $CLIENT = $$CLIENT_ref;

    alarm(60);

	print $CLIENT 'GET_POS:' . $word . "\n"
	  or die "Error:" . 'GET_POS:' . $word . "\n";
	while ( my $line = get_client_response( $CLIENT_ref ) ) {
		chomp $line;
		print 'line: ' . $line . "\n";
		if ( $line =~ /HERE_IS_POS/ ) {
			$line =~ s/HERE_IS_POS[:]//i;    
			return $line;
		}
	}
}

] . qq[

eval 'start_service_$service();';
    
    ];
    close $file;

    kill_process_from( $service . '.pid' );

    select undef, undef, undef, 2;

    if ( lc($^O) =~ /win/ ) {
        if ( -f 'tagger-perl.exe' ) {
            system( 'start', 'tagger-perl.exe', $filename );
        }
        else {
            system( 'start', 'perl.exe', $filename );
        }
    }
    else {
        system( 'perl ' . $filename . ' &' );
    }

    select undef, undef, undef, 10;
}

sub kill_all_subprocesses {
    my %arg = @_;

    opendir( my $direc, '.' );
    my @pidfiles = grep { /pid$/ } readdir($direc);
    closedir $direc;
    foreach my $file ( @pidfiles ) {
        #kill_process_from($file);
        print "killed $file";
    }
}

sub kill_process_from {
    my ($file) = @_;

    -f $file or return;

    open my $pidfile, '<', $file or return;
    my $pid = <$pidfile> or return;
    print "\n", $pid, "\n";
    chomp $pid;
    close $pidfile or return;


    return if !$pid;
    return if $pid == $$;
    return if -$pid == $$;
    return if $pid == -$$;


    if ( lc($^O) =~ /win/ ) {
        kill( -9, $pid );
    }
    else {
        kill 9 => $pid;
    }
    unlink $pidfile;
}

sub client_setup {
    my ($data) = $_[1];

    $data->{connection}{client_info} = {
        username => 'human',

        # socket_tagger => connect_to(name => 'tagger'),
        @_,
    };
    close ${ connect_to(
            data         => *data,
            name => 'tagger'
        ) };
    #say 'client_setup:';
    say 'client_setup!';
}

sub exit_handler {
    kill_all_subprocesses();
    say @_;
    exit(0);
}

sub is_array {
    my ($ref) = @_;

    print ref($ref) . "\n";
    return ref($ref) eq 'ARRAY';
}

$data->{lang}{is_ascii_character} = {
    map { $_ => 1 } (
        'a' .. 'z',
        'A' .. 'Z',
        '0' .. '9',
        ' ', qw{/ ( ) & % $ " ' ! . ? ; - = > < + _},
        '[', ']', '{', '}', ',',
    )
};

my %UMLAUTE = (
    'Ä' => 'Ae',
    'Ö' => 'Oe',
    'Ü' => 'Ue',
    'ä' => 'ae',
    'ö' => 'oe',
    'ü' => 'ue'
);
my @UMLKEYS = join( "|", keys(%UMLAUTE) );

=head2 ascii($sent)

Returns $sent without non-ascii characters.

=cut

sub ascii {
    my ($sent) = @_;

    $sent =~ s/(@UMLKEYS)/$UMLAUTE{$1}/g;

    my @old = split //, $sent;

    #	say Dumper \@old;
    $sent = '';
    while ( defined( my $c = shift @old ) ) {

        #		say $c;
        #		say ord $c;

        if    ( ( ord $c ) == 228 ) { $sent .= 'ae' }
        elsif ( ( ord $c ) == 196 ) { $sent .= 'Ae' }
        elsif ( ( ord $c ) == 252 ) { $sent .= 'ue' }
        elsif ( ( ord $c ) == 220 ) { $sent .= 'Ue' }
        elsif ( ( ord $c ) == 246 ) { $sent .= 'oe' }
        elsif ( ( ord $c ) == 214 ) { $sent .= 'Oe' }
        elsif ( ( ord $c ) == 223 ) { $sent .= 'ss' }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 164 ) {
            shift @old;
            $sent .= 'ae';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 188 ) {
            shift @old;
            $sent .= 'ue';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 182 ) {
            shift @old;
            $sent .= 'oe';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 132 ) {
            shift @old;
            $sent .= 'Ae';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 165 ) {
            shift @old;
            $sent .= 'Ue';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 150 ) {
            shift @old;
            $sent .= 'Oe';
        }
        elsif ( ( ord $c ) == 195 && ord $old[0] == 159 ) {
            shift @old;
            $sent .= 'ss';
        }
        elsif ( $data->{lang}{is_ascii_character}{$c} ) {
            $sent .= $c;
        }
    }

    return $sent;
}



1;