# modulos
use warnings;
use strict;
use Win32::IPConfig;
use Net::NBName;
use Net::NetSend qw(:all);

# variables
my $version = '4.1';
my %chatconfig;

system("cls");

# reading config file
open(CONFIG,"<chat.conf") or die "Cannot read configuration file chat.conf: $!\n";

# small file, throw everything into memory is faster
my @content = <CONFIG>;
close(CONFIG);

my @temp;

foreach ( @content ) {

    next if $_ =~ /#/;
    next if $_ =~ /^\n$/;
    chomp;
    $_ =~ s/\s//g;
    @temp = split(/=/,$_);
    $chatconfig{$temp[0]} = $temp[1];
    @temp = ();

}	
@content = ();

my $userdb = $chatconfig{userdb};

open(FILE,"<$userdb") or die "cannot open file: $!";
@content = <FILE>;
close(FILE);

# criando a lista de usuários
my $owner;
# lista de usuários temporária, sem ordenação
my %temp_list;

foreach ( @content ) {
	
    next if $_ =~ /#/;
    next unless ($_ =~ /:/);
    chomp;
# LOGIN:apelido    
    @temp = split(/:/,$_);
    
# o usuário que esta rodando o
# programa não deve ser incluído no mesmo
    if ( $temp[0] eq $ENV{username} ) {

        $owner = $temp[1];
        next;

    }
# apelido:login - para poder ordernar    
    $temp_list{$temp[1]} = $temp[0];

}

undef @temp;
# @user_list is a array of arrays (bidimensional)
# position 0 is for login
# position 1 is for alias
# position 2 is for hostname
my @user_list = create_list(\%temp_list);
undef %temp_list;

# creates the menu
my $index;
my $option;

my $start_count;
my $end_count;

while (0 < 1) {

$start_count = time;

print <<BLOCK;
----------------------------------------------------------
Programa de envio de mensagens do Seo Creysson versao $version

Ola $owner.
Escolha o sujeito para quem voce quer mandar uma mensagem:
BLOCK


    for ( $index = 1; $index < @user_list; $index++ ) {
	
        print $index, '. ', $user_list[$index]->[1], "\n";
    
    }

    my $broadcast = $index;
    print "$index. Broadcast para los hermanos\n";
    my $end = ++$index;
    print "$index. Sair\n";
    print "Use i<identificador numerico> para obter informacoes de um membro da lista\n";
    print "Opcao selecionada: ";
    $option = <STDIN>;
    chomp $option;

# teste de entrada de dados
    if ( $option =~ /^\d+$/ ) {

        unless ( $option >= 1 and $option <= $end ) {

            invalid_operation("Digite um identificador numerico valido entre 1 e $end.");
            next;

        }
        
    } else {
    
         if ( $option =~ /^i\d+$/ ) {

             my $user = substr($option,1,1);         
# checando se o usuario nao tenta pegar informacoes de BROADCAST
# ou outro indice invalido
             my $max = @user_list - 1;

             if ( ( $user > 0 ) and ( $user <= $max ) ) {         

print <<BLOCK;
+--------------------+------------------------
| Apelido            | $user_list[$user]->[1]
+--------------------+------------------------
| Login              | $user_list[$user]->[0]
+--------------------+------------------------
| Nome do computador | $user_list[$user]->[2]
+--------------------+------------------------
BLOCK

                 print "Pressione ENTER para voltar ao menu ";
                 $user = <STDIN>;
                 system('cls');
                 next;
                 
             } else {
             
                 invalid_operation("Digite um identificador numerico entre 1 e $max.");
                 next;
             
             }
           
         
         } else {
         
             invalid_operation();
             next;
         
         }
    
    
    }

# saída do programa

    if ($end == $option) {

        system('cls');
        print "Firma cara. Falouz.\n";
        print "-----------------------------------------------------------------\n\n";
        print "\t\t\tby Alceu (glasswalk3r\@yahoo.com.br)\n";
	    sleep 1;
	    exit;

    }


# definindo confirmacao e a mensagem

    unless ( $broadcast == $option ) {
    
        if ( $user_list[$option]->[2] eq 'unavailable' ) {

            invalid_operation("Ao que parece $user_list[$option]->[1] nao efetuou logon na rede ainda...\nNao posso enviar uma mensagem a ele");
            next;
            
        }
        
    }
    
    my $destinatario;
        
    ( $broadcast == $option ) ? ( $destinatario = 'BROADCAST' ) : ( $destinatario = $user_list[$option]->[1] );
    print "Legal, agora digite a mensagem para > $destinatario <. Quando acabar, digite ENTER.\n";
    print "Se quiser abortar o envio, nao digite nada e pressione ENTER.\n\n";
    print "Mensagem: ";

    my $message = <STDIN>;
    
    invalid_operation('Me recuso a enviar uma mensagem nula.') unless ( defined( $message ) );

    if ( $message =~ /^\n$/ ) {
	
        system("cls");
	    print "A mensagem NAO foi enviada.\n";
	    next;

    }
        
    chomp($message);
    
    $end_count = time - $start_count;

    if ($broadcast == $option) {
	
        system('cls');
       
# ignores zero position (since it has no one)
        my $position;
       
        for ( $position = 1; $position < @user_list; $position++ ) {

            send_message( $position, \@user_list, $owner, "[broadcast]: $message", \$end_count );
      
        }

        next;

    } else {
	
        system('cls');
        send_message( $option, \@user_list, $owner, $message, \$end_count );

    }
 

# fim do while
}



#FUNCOES

# função para envio de mensagens

sub send_message {

# has the array index for the menu member
    my $ID = shift;
    my $destiny_ref = shift;
    my $source_netbios_name = shift;
    my $message = shift;
    my $counter_ref = shift;
    
    my $target_ip;
# improving performance avoiding new request to WINS Server
    ( $$counter_ref < $chatconfig{refresh_value} ) ? 
        ( $target_ip = $destiny_ref->[$ID][2] ) : 
        ( $target_ip = search_name( $destiny_ref->[$ID][1] ) );

    my $target_netbios_name;
    my $debug = 0;
    my $success;
    
    unless ( $target_ip eq 'unavailable' ) {

        $target_netbios_name = getNbName( $target_ip, $debug );
  
        die "No netbios name found: $@\n" unless ( defined( $target_netbios_name ) );

        $success = sendMsg($target_netbios_name, $source_netbios_name, $target_ip, $message, $debug);
        print ( $success ? "Mensagem entregue com sucesso para $destiny_ref->[$ID][1]\n" : "Ocorreu um erro na entrega da mensagem para $destiny_ref->[$ID][1]: n$@\n" );

    } else {
    
        print "Ao que parece $destiny_ref->[$ID][1] nao efetuou logon na rede ainda...\n";
    
    }
}

# checa se o computador possui ao menos um servidor WINS configurado para evitar broadcast
# deveria permitir o uso de mais um adaptador (usar o chat.conf para determinar qual) e
# retornar uma lista de servidores WINS ao invés de somente o primeiro
sub check_wins {

    my $host = Win32::IPConfig->new();
    my @adapters = $host->get_adapters; 
    my @wins_servers;

# lets work with just the first adapter  
    @wins_servers = $adapters[0]->get_wins();
    
    ( @wins_servers > 0 ) ? return $wins_servers[0] : 0;

}

# queries thru WINS server or broadcast for machine name giving username
sub search_name {

    my $login = shift;
    $login =~ tr/[a-z]/[A-Z]/;
    
    my $query = Net::NBName->new();
    my $wins_server;
    my $suffix = hex 3;
    my $host;
    
    $wins_server = check_wins() if ( $chatconfig{use_wins} eq 'yes' );
    
    if ( defined( $wins_server ) ) {

# debug
#        printf "querying %s for %s<%02X>...\n", $wins_server, $login, $suffix;
        $host = $query->name_query( $wins_server, $login, $suffix );
    

# no WINS available, trying     
    } else {

# debug   
#        printf "broadcasting for %s<%02X>...\n", $login, $suffix;
        $host = $query->name_query(undef, $login, $suffix);
    
    }

# ugly, but the module doesn't give me a clear interface
    my @list;
    if ( defined( $host ) ) {
    
        @list = $host->addresses; 
        return $list[0]->{address};
        
    } else {
    
        return 'unavailable';
    
    }

}

sub create_list {

# reference to an hash list
    my $list_ref = shift;
    my $index = 1;
    my $host;
    my @user_list;
    my $alias;

# ordenando os nomes da lista por apelidos (ordem crescente)
    foreach $alias( sort( keys( %{ $list_ref } ) ) ) {

# procurando informações sobre a máquina aonde o login
# está logado no momento
        $host = search_name( $list_ref->{$alias} );

# forçando o array a começar do índice 1, já que faz mais sentido usar esse numero no menu
        $user_list[$index] = [ $list_ref->{$alias}, $alias, $host ];
        $index++;
        $host = '';

    }

    return @user_list;

}


sub invalid_operation {

    my $msg = shift;
    print '----------------------',"\n";
    print "ERRO: operacao invalida\n";
    print "$msg\n" if ( defined( $msg ) );
    print 'Pressione ENTER para retornar ao menu';
    my $dummy = <STDIN>;
    system("cls");

}