package Mojolicious::Plugin::CheckEmails;
use Mojo::Base 'Mojolicious::Plugin';

use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Queue;
use Thread::Semaphore;
use Time::HiRes qw (sleep);
use Mail::CheckUser qw(:constants check_email last_check);

my $MAX_TRIES = 10;
my $MAX_THREADS = 26;

my $s;
my $item_list;

my $totalitems : shared = 0;
my %emailchecks : shared = ();
my %validsmtps : shared = ();
my %requeued : shared = ();

$validsmtps{'@hotmail.com'} = 1;
$validsmtps{'@gmail.com'} = 1;
$validsmtps{'@uol.com.br'} = 1;
$validsmtps{'@yahoo.com.br'} = 1;
$validsmtps{'@yahoo.com'} = 1;
$validsmtps{'@oi.com.br'} = 1;
$validsmtps{'@superig.com.br'} = 1;
$validsmtps{'@globo.com'} = 1;
$validsmtps{'@ig.com.br'} = 1;
$validsmtps{'@terra.com.br'} = 1;
$validsmtps{'@bol.com.br'} = 1;

sub register {
  my ($self, $app, $config) = @_;

  $app->helper(checkemails => sub {
    my $self = shift;
    my ($aremails) = @_;
    
    $s = Thread::Semaphore->new;
    $item_list = Thread::Queue->new;
    $totalitems = 0;
    %emailchecks = ();
    %validsmtps = ();
    %requeued = ();
    
    for (@{$aremails}){
      $item_list->enqueue($_);      
    }

    $totalitems = $item_list->pending;
    if($totalitems < $MAX_THREADS){
      $MAX_THREADS = $totalitems;
    }
    # start N worker threads
    #my %workers = ();
    for (0..$MAX_THREADS-1){
     #   $workers->[$_] = threads->new(\&WorkerThread, $item_list, $s);
        # print "Worker " . $workers->[$_]->tid . " created.\n";
        threads->new(\&WorkerThread, $item_list, $s);
    }

    # Letting worker threads run until item queue is empty...
    while ($item_list->pending > 0){
       sleep 1;
    }

    #sleep 5;

    foreach my $thr (threads->list) { 
       # don't join the main or monitor threads or ourselves 
       if ($thr->tid && !threads::equal($thr, threads->self)){ 
           # print "Waiting for worker " . $thr->tid . " to join\n";
           # print "Worker " . $thr->join . " has joined.\n";
           $thr->join;
       } 
    } 

    #sleep 1;

    # my $code = '';
    # my $countok = 0;
    # my $counttotal = 0;
    # foreach my $e (keys %emailchecks){
    #   $code = $emailchecks{$e};
    #   $counttotal++;
    #   if($code eq 'OK'){
    #     $countok++;
    #   }
    #   open OUT, ">>checkemailsvalidityMT$code.txt";
    #   print OUT "$e\n";
    #   close OUT;
    # }

    # open OUT, ">checkemailsvalidityMTVALIDSMTP.txt"; # overwrites since we loaded (avoids duplication)
    # foreach my $vasmtp (keys %validsmtps){
    #   print OUT "$vasmtp\n";
    # }
    # close OUT;

    # my $counterrors = $counttotal - $countok;
    # print "$counttotal emails checked: $countok ok, $counterrors errors\n";

    return \%emailchecks;
  });
}

# open IN, "<checkemailsvalidityMTVALIDSMTP.txt";
# my @vsmtps = <IN>;
# close IN;
# chomp @vsmtps;
# foreach my $vsmtp (@vsmtps){
#   $validsmtps{$vsmtp} = 1;
# }

sub WorkerThread{
   my ($item_list, $s) = @_;
  #**********************************************
  # Processes an item
  #**********************************************
  while($item_list->pending > 0) { 
#    $s->down;
    my $email = $item_list->dequeue_nb;
    chomp $email;
    next if(!$email);
    my $ok = check_email($email);
    my $code = last_check()->{code};
    if($code == CU_BAD_SYNTAX){
      $code = 'SYNTAX'; # discards bad email
    }
    elsif($code == CU_UNKNOWN_DOMAIN){
      $code = 'DOMAIN';
    }
    elsif($code == CU_DNS_TIMEOUT){
      $code = 'TIMEOUTDNS';      
    }
    elsif($code == CU_UNKNOWN_USER){
      $code = 'USER'; # for sure a bad email: discards email
      $email =~ m/(@.*)$/; $validsmtps{$1} = 1;
    }
    elsif($code == CU_SMTP_UNREACHABLE || $code == CU_SMTP_TIMEOUT){
      $code = 'SMTP'; # requeues the email until SMTP becomes reachable
      if($code == CU_SMTP_TIMEOUT){
        $code = 'TIMEOUTSMTP';
      }
      foreach my $vsmtp (keys %validsmtps){
		if($email =~ m/$vsmtp/){
		  # checks if item is not already on the queue...
		  my $i; my $e; my $ja = 0;
		  $s->down;
		  for($i=0; $i<$item_list->pending; $i++){
		    $e = $item_list->peek($i);
		    chomp $e;
		    if($email eq $e){
		      $ja = 1; last;
		    }
		  }
		  if(!$ja && !exists($requeued{$email}) || $requeued{$email} < $MAX_TRIES){
		    $item_list->enqueue($email . "\n");
		    $requeued{$email}++;
		    #print "Requeued $email (" . $requeued{$email} . ")\n";
		    $code = 'REQUEUED(' . $requeued{$email} . ')';
		  }
		  elsif(exists($requeued{$email}) && $requeued{$email} >= $MAX_TRIES){
		    $code = 'MAXTRIES';
		  }
		  $s->up;
		}
      }
    }
    elsif($code == CU_MAILBOX_FULL){
      $code = 'MAILBOX'; # for sure a bad user: discards email
      $email =~ m/(@.*)$/; $validsmtps{$1} = 1;
    }
    elsif($code == CU_OK){
      $code = 'OK';
      $email =~ m/(@.*)$/; $validsmtps{$1} = 1;
    }
    else{
      $code = 'UNKNOWN';
    }
    $emailchecks{$email} = $code;
    my $percentdone = 100.0;
    if($totalitems>0){
      $percentdone = sprintf("%.1f", 100.0 - ($item_list->pending*100.0)/$totalitems);
    }
    # print "$email:$code ($percentdone)\n";
#    $s->up;
    sleep 0.05;
  }

  # print "Worker " . threads->self->tid . " ready to exit.\n";
  return threads->self->tid;
}

1;
