#!/usr/bin/perl

use DBI;
use strict;
use POSIX;
#use ESL;
use Switch;
use Time::HiRes;
use WWW::Curl::Easy;
use JSON;
use Config::Tiny;
use Data::Dumper;
use Cwd;
use URI::Encode qw(uri_encode uri_decode);
use threads;
use Thread::Semaphore;
use threads::shared;
use POSIX qw(strftime);
use Net::Ping;
use Dinstar;
use Email::Send;   
use Email::Simple::Creator; # or other Email::
#use Syntax::Keyword::Try

# ----------------------------------------
# Need to Install Config-Tiny Perl Module
# ----------------------------------------
my $app_folder = getcwd;
chdir "/var/www/html/PortalSMS";
my $filename = "./SmsOutbound.ini";
my $config = Config::Tiny->read( $filename, 'utf8' );
my $daemon = 0;
my $dbh;
my $logfile = "./SmsOutbound.log";
my $threadCount = 10;
my $site = 1;
my $debug = 7;
my $firstRun = 1;

# --------------------------------
# Variables
# --------------------------------
my $sth;
my $forever = 1;
my $dbConnect = 1;
my $i;
my @thr;
my @Used:shared;
my $globalThreadCount:shared = 0;
my $semaphore = Thread::Semaphore->new();
my $coding = 0;

my $ip = "";
my $port = 0;
my $id = 0;
my $auth = "";
my @jsessionid;
my $countRegister = 4;
my $tac = "";
my $imeiChange = 0;

use constant {
    CONSOLE => 0,
    ALERT => 1,
    CRIT  => 2,
    ERROR  => 3,
    WARNING  => 4,
    NOTICE  => 5,
    INFO => 6,
    DEBUG  => 7
};


if( $config->{core}{daemon} eq "Yes" ) {
   $daemon = 1;
}

# ---------------------------
# Is program already running
# ---------------------------
writeToLog( DEBUG, $logfile, "", 1 );
my $progname = "SmsOutbound.pl";
my $pidx = `ps -C $progname -o pid=`;
#my $pidx = `ps -C $progname -o command=`;
if( $pidx != "" ) {
   if( $pidx != getpid() ) {
      writeToLog( INFO, $logfile, sprintf( "SmsOutbound PID: %d is ALREADY RUNNING", $pidx ) ,0 );
      exit( 0 );
   }
   else {
      writeToLog( INFO, $logfile, sprintf( "SmsOutbound.pl daemon PID: %d IS NOT RUNNING", $pidx ),0 ); 
   }
}
else {
   writeToLog( INFO, $logfile, sprintf( "SmsOutbound.pl daemon PID: %d IS NOT RUNNING", $pidx ),0 ); 
}

if( $daemon == 1 ) {
   fork and exit;
   POSIX::setsid();
   fork and exit;
   umask 0;

   writeToLog( INFO, $logfile, sprintf( "Start 'SmsOutbound' daemon PID: %d", getpid() ),0 );
   if( open(my $fp, '>', "./SmsOutbound.pid") ) {
      printf $fp "%d", getpid();    
      close $fp;
   }

   close STDIN;
   close STDOUT;
   close STDERR;
}
else {
   writeToLog( INFO, $logfile, sprintf( "Start 'SmsOutbound' as Foreground" ) ,0 );
}   
writeToLog( INFO, $logfile, "", 1 );


$SIG{INT} = sub { 
   print("\nControl-C pressed, terminating process. Please wait...\n\n");
   $forever = 1;
   exit;
};

$SIG{TERM} = sub {
   $forever = 1;
   exit;
};

END {
  while( $globalThreadCount > 0 ) {
     Time::HiRes::sleep(1);
  }
  if( $dbh ) {
     $dbh->disconnect();
  }   
  writeToLog( DEBUG, $logfile, "", 1 );
  writeToLog( DEBUG, $logfile, sprintf( "Terminated 'SmsOutbound' daemon PID: %d", getpid() ) ,0 );
  writeToLog( DEBUG, $logfile, "", 1 );
  unlink("./SmsOutbound.pid");
}

# --------------------------------
# MariaDB
# --------------------------------
my $driver = "mysql"; 
my $database = "TestSMS-2";
my $dsn = "DBI:$driver:database=smpp:127.0.0.1";
my $userid = "root";
my $password = "dialer19";
my $dbhost = "10.0.66.119";
my $datatable = "outbound_request";
my $error = "";


# --------------------------------
# MariaDB Configuration
# --------------------------------
if( $config->{database}{name} ne "" ) {
   $database = $config->{database}{name};
}
if( $config->{database}{host} ne "" ) {
   $dbhost = $config->{database}{host};
}
if( $config->{database}{host} ne "" ) {
   $userid = $config->{database}{user};
}
if( $config->{database}{password} ne "" ) {
   $password = $config->{database}{password};
}
if( $config->{database}{datatable} ne "" ) {
   $datatable = $config->{database}{datatable};
}

$dsn = "DBI:$driver:database=$database:$dbhost";

if( $config->{core}{debug} ne "" ) {
   $debug = $config->{core}{debug}; 
}

if( $config->{core}{threads} ne "" ) {
   $threadCount = $config->{core}{threads}; 
}

if( $config->{core}{site} ne "" ) {
   $site = $config->{core}{site}; 
}

if( $config->{core}{TAC} ne "" ) {
   $tac = $config->{core}{TAC}; 
}

if( $config->{core}{codigg} ne "" ) {
   $coding = $config->{core}{coding}; 
}

if( $config->{core}{IMEI} ne "" ) {
   $imeiChange = $config->{core}{IMEI}; 
}

writeToLog( DEBUG, $logfile, sprintf( "Datatable: %s", $datatable) );
writeToLog( DEBUG, $logfile, sprintf( "Debug Level: %d", $debug ) );
writeToLog( DEBUG, $logfile, sprintf( "Max Threads: %d", $threadCount ) );


# --------------------------------
# Connect to MariaDB
# --------------------------------
while( $dbConnect ) {

   if( ( $dbh = DBI->connect($dsn, $userid, $password ) ) == 0 ) {
      writeToLog( DEBUG, $logfile, sprintf( "Cannot connect to MariaDB: $DBI::errstr", 0 ) );
      Time::HiRes::sleep(10);
   }
   else {
      $dbConnect = 0;
      writeToLog( DEBUG, $logfile, sprintf( "Connected to MariaDB @%s", $dbhost ) );
      last;
   }
}

for( $i= 0; $i < $threadCount; $i++ ) {
   $Used[$i] = 0;
}
writeToLog( DEBUG, $logfile, "", 1 );

while( $forever ) {

   my $foundThread = 0;

   if ( not $dbh->ping ) {
      if( ( $dbh = DBI->connect($dsn, $userid, $password ) ) == 0 ) {
         writeToLog( DEBUG, $logfile, sprintf( "Cannot connect to MariaDB $DBI::errstr", 0 ) );
         Time::HiRes::sleep(10);
         next;
      }
   }

   my $auth = "";
   my $sql = "";
   my $resultSms;
   my $resultUpdate;
   my $resultDelivery;
   my $result;
   my $sn = "";
   my $index = 0;


   my $sql = sprintf( "select sql_no_cache id, ip, api_user, api_password, user, password, descripcion, tipo_gw, sn, canales from gateways where active=1;");
   my $resultSms = $dbh->prepare($sql);
   if ( $resultSms->err ) {
      sleep( 15 );
      next;
   }
   if( $resultSms->execute() ) {

      while (my @rowSms = $resultSms->fetchrow_array()) {
         my ($gw, $ip, $api_user, $api_password, $gw_user, $gw_password, $descripcion, $tipo_gw, $sn, $channels) = @rowSms;
         $auth = $api_user . ":" . $api_password;

         if( !defined($jsessionid[$id]) ) {
            my $session = loginGateway( $ip, $gw_user, $gw_password ); 
            $jsessionid[$id] = $session;
         }

         $semaphore->down();
         $foundThread = 0;
         for( $i = 0; $i < $threadCount; $i++ ) {
            if( $Used[$i] == 0 ) {
               $Used[$i] = 1;
               $foundThread = 1;
               $index = $i;
               last;
            }
         }

         # -----------------------------
         # Actualizo Thread en Uso
         # -----------------------------
         UsedThreads();
         $semaphore->up();

         if( $foundThread == 1 ) {
            my $fecha = "2025-01-01 00:00:00";
            $thr[$i] = threads->create(\&queryResultThread, $ip, $auth, $database, $dbhost, $userid, $password, $sn, $jsessionid[$gw], $datatable, $channels, $index, $fecha );
         }

         while( $globalThreadCount >= $threadCount ) {
            UsedThreads();
            Time::HiRes::sleep(1);
         }
      }
   }
   $resultSms->finish();
   sleep( 15 );
}
$dbh->disconnect();


writeToLog( DEBUG, $logfile, sprintf( "End 'SmsOutbound' daemon PID: %d", getpid() ),0 );
print ( "-------------------------------------------------------------------------------------------------------------------------------------------------------------------\n" );


   # ------------------------------------
   # THREAD
   # ------------------------------------

   sub queryResultThread
   {
      my $LocalCopy;
      my $dbh;
      my $resultUpdate;
      my $resultDelivery;
      my $sql;

      my $ip = $_[0];
      my $auth = $_[1];
      my $database = $_[2];
      my $dbhost = $_[3];
      my $userid = $_[4];
      my $password = $_[5];
      my $sn = $_[6];
      my $jsessionid = $_[7];
      my $datatable = $_[8];
      my $channels = $_[9];
      my $index = $_[10];
      my $fecha = $_[11];
      my $recipient = "N/D";

#            $thr[$i] = threads->create(\&queryResultThread, $ip, $auth, $database, $dbhost, $user, $password, $sn, $jsessionid[$gw], $datatable, $channels );

      threads->detach();
      my $dsn = "DBI:$driver:database=$database:$dbhost";
      if( ( $dbh = DBI->connect($dsn, $userid, $password ) ) == 0 ) {
         $Used[$index] = 0;
         $semaphore->up();
         return;
      }

      $LocalCopy = $globalThreadCount; 
      $Used[$index] = 1;
      $LocalCopy++;
      $globalThreadCount = $LocalCopy; 
      $semaphore->up();

      # -------------------------------------------
      # Query resultado de envio de Sms del puerto
      # -------------------------------------------
      my @ports = (0..$channels-1);
      my $rd = querySmsResults( $ip, \@ports, $auth, $id, $fecha, $recipient );
      print( Dumper( $rd ) );
      my $rc = decode_json($rd);
#     print( Dumper( $rc ) );


=begin
          'sn' => 'db49-c230-9183-0071',
          'result' => [
                        {
                          'imsi' => '722310137182341',
                          'count' => 1,
                          'succ_count' => 1,
                          'number' => '1144255139',
                          'port' => 0,
                          'time' => '2025-04-19 19:15:51',
                          'status' => 'DELIVERED',
                          'slot' => 3,
                          'ref_id' => 58,
                          'user_id' => 42
                        },
                        {
                          'imsi' => '722310137182341',
                          'count' => 1,
                          'succ_count' => 1,
                          'number' => '1144255139',
                          'port' => 0,
                          'time' => '2025-04-19 19:39:35',
                          'status' => 'DELIVERED',
                          'slot' => 3,
                          'ref_id' => 59,
                          'user_id' => 43
                        },
                      ],
      'error_code' => 200
=cut


      if( int($rc->{error_code}) == 200 ) {
         my @resultArray = $rc->{result};
         my $len = scalar @resultArray;
   
         if( $len < 1 ) {
   
            # ---------------------------------------------
            # Sin STATUS 5 veces, cancelo con codigo 401,
            # Si no llegue al maximo de 5, codigo 400
            # y sigo esperando respuesta
            # ---------------------------------------------
         }
         else {
            my $errCode;
            my $user_id;
   
            # ---------------------   
            # JSON Perl Array
            # ---------------------   
            my @resultArray = @{$rc->{result}};
            my $items = @resultArray;
            $errCode = $rc->{result}[0]->{status}; 

            for (my $i = 0; $i < $items; $i++) {

               my $imsi = $rc->{result}[$i]->{imsi}; 
               my $port = $rc->{result}[$i]->{port}; 
               my $number = $rc->{result}[$i]->{number}; 
               my $fecha = $rc->{result}[$i]->{time}; 
               my $status = $rc->{result}[$i]->{status}; 
               my $slot = $rc->{result}[$i]->{slot}; 
               my $ref_id = $rc->{result}[$i]->{ref_id}; 
               my $user_id = $rc->{result}[$i]->{user_id}; 
               my $count = $rc->{result}[$i]->{count}; 
               my $succ_count = $rc->{result}[$i]->{succ_count}; 

               writeToLog( DEBUG, $logfile, sprintf( "IMSI: %s", $imsi ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Number: %s", $number ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Time: %s", $fecha ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Port: %s", $port ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Slot: %s", $slot ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Status: %s", $status ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "User ID: %s", $user_id ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Ref ID: %s", $ref_id ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Count: %s", $count ), 0 );
               writeToLog( DEBUG, $logfile, sprintf( "Succ Count: %s", $succ_count ), 0 );
               writeToLog( DEBUG, $logfile, "", 1 );
            }            
         } 
      } #rc 

      $semaphore->down();
      $LocalCopy = $globalThreadCount; 
      $LocalCopy--;
      $globalThreadCount = $LocalCopy; 
      $Used[$index] = 0;
      if( defined($resultDelivery) ) {
         $resultDelivery->finish();
      }                  
      if( defined($resultUpdate) ) {
         $resultUpdate->finish();
      }         
      $dbh->disconnect();
      $semaphore->up();
      threads->exit();
   }   


   # ---------------------------
   # Cuantos Thread hay en uso
   # ---------------------------

   sub UsedThreads
   {
      my $str = "";   
      for( my $x = 0; $x < $threadCount; $x++ ) {
         if( $x == 0 ) {
            if( $Used[$x] == 0 ) {
               $str .= "0";
            }
            else {
               $str .= "1";
            }
         }
         else {
            if( $Used[$x] == 0 ) {
               $str .= ", 0";
            }
            else {
               $str .= ", 1";
            }
         }

      }
#      writeToLog( DEBUG, $logfile, sprintf( "Threads %d: (%s)", $globalThreadCount, $str ), 0 );
#      writeToLog( DEBUG, $logfile, "", 1 );
   }


   # -------------------
   # db_Error
   # -------------------
   sub db_Error {
      my $level = $_[0];
      my $err = $_[1];
      my $errStr = $_[2];

      writeToLog( $level, $logfile, sprintf( "DBI Error: $err : $errStr" ), 0 );
   }

   # ---------------
   # Test for ping
   # ---------------

   sub PingTest {
      my $my_addr = "10.0.66.119";
      my $host = $_[0];
      my $rc = 0;
   
      my $p = Net::Ping->new("icmp");
      $p->hires();
      $p->bind($my_addr); # Specify source interface of pings
      $rc = $p->ping($host,500);
      $p->close();
      return $rc;
   }
   # --------------------------------------
   # writeToLog
   # --------------------------------------

   sub writeToLog 
   {  
      my $level = $_[0];
      my $filename = $_[1];
      my $text = $_[2];
      my $mode = $_[3];

      if( $debug == DEBUG || $level <= $debug ) {

         if( open(FH, '>>', $filename) ) {
            if( $mode == 1 ) {
               $text = "+--------------------------------------------------------------------------------------------------+";
            }
            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
            my $hoy = sprintf( "%02d.%02d.%04d %02d:%02d:%02d", $mday, $mon + 1, $year + 1900, $hour, $min, $sec );
            printf( FH sprintf( "%s: %s\n", $hoy, $text ) );
            printf( sprintf( "%s: %s\n", $hoy, $text ) );
            close(FH);
         }      
      }         
   }


