#!/usr/bin/perl

# Для пущего порядку
use strict;
use warnings;
use FCGI;
use POSIX;
use FCGI::ProcManager qw(pm_manage pm_pre_dispatch pm_post_dispatch);
use File::Basename;
use Data::Dumper;
use Fcntl ':flock';
use CGI::Fast;
use Time::HiRes qw( gettimeofday tv_interval );
use JSON qw(to_json from_json);

use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../wlib";
use lib "$FindBin::Bin/../cpan";
use Utils::Common;
use Utils::Hosts qw( get_curr_host get_host_info );
use Utils::Sys qw(
    print_err
    mem_usage
    handle_errors
);
use BaseForm;
use CatalogiaMediaProject;

use BM::SolomonClient;

my $log_file = $Utils::Common::options->{dirs}{log} . "/fcgi-proj.err";
handle_errors(log_warnings_to_file => $log_file);

my $COMMAND = $ARGV[0] || die ("no command!");
if ( $COMMAND eq 'stop' ) {
	DoStop() && print_err("server stopped on port");
	exit(0);
} elsif ( $COMMAND eq 'restart' ) {
	DoStop() && print_err("previous server stopped on port");
} elsif ( $COMMAND eq 'start' ) {
	if ( GetPid() && IsProcessExists( GetPid() ) ) {
		print_err("do not start server, old server exists!");
		exit(0);
	}
	print_err("start server...");
} else {
	die("UnknownCommand:$COMMAND");
}

my $root_dir = $Utils::Common::options->{dirs}{root};

# unlink cmd_log dirs:
my $unlink_dir = "$root_dir/log/cmdlog/";
print_err("unlink cmdlog files... $unlink_dir");
opendir DIR, $unlink_dir;
my @unlink_files = map { $unlink_dir . $_ } grep { /cmdlog\d+/ } readdir(DIR);
for my $unlink_file ( @unlink_files ) {
     unlink $unlink_file or die "Cannot unlink file:$unlink_file, $!\n";
}
closedir DIR;
print_err("unlink cmdlog files done");


# Форк
# избавляемся от родителя
fork_proc() && exit 0;
POSIX::setsid() or die "Can't set sid: $!";
chdir '/' or die "Can't chdir: $!";

# pidfile
SetPid($$);

# Открываем сокет
print_err("we are here - 1");
my $socket = FCGI::OpenSocket(":9093", 200);
#my $socket = FCGI::OpenSocket("/tmp/catalogia-beta.sock", 10);
print_err("we are here - 2");

# Начинаем слушать
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket, FCGI::FAIL_ACCEPT_ON_INTR);
#my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket );

my $host_info = get_host_info();
my $fcgi_pid_table = 'FcgiProcess';
$fcgi_pid_table .= 'Beta'   if $host_info->{test};  # Для catalogia-media-dev
print_err("fcgi_pid_table: $fcgi_pid_table");
ResetPidTable($fcgi_pid_table);

pm_manage( n_processes => 10, die_timeout => 5 );

#$SIG{CHLD} = 'IGNORE';
$SIG{INT} = $SIG{TERM} = sub { 
	print_err("SIGNAL TO STOP CALLED called, stopping server");
	exit(0);
};

# project initialize
print_err("cmproject load...");
my $proj = CatalogiaMediaProject->new({
        no_form => 1,
        no_auth => 1,
        log_file => $log_file,
});

#   load_dicts => 1,
#   load_minicategs => 1,

print_err("cmproject load done");
my $solomon_client = BM::SolomonClient->new();

WebCommon::set_template_path("$FindBin::Bin/../wtemplate");

reopen_std();

my $host = get_curr_host();

while( $request->Accept() >= 0 ) {

    pm_pre_dispatch();

    my $start_time = [gettimeofday];
    $solomon_client->push_single_sensor({
        cluster => "host_info",
        service => "fcgi_server",
        sensor  => "begin_count",
        labels  => {
            host   => $host,
        },
        value   => 1,
    });

    my $env_str = to_json(\%ENV);
    my $url = $ENV{HTTP_HOST} . $ENV{REQUEST_URI};
    $proj->log("FCGI cmd processing started. Previous Login=" . $proj->login() . " url=$url ENV: $env_str");
    $proj->Do_SQL("replace into $fcgi_pid_table (Host,Pid,Login,Cmd) values ('$host'," . int($$) . "," . $proj->dbh->quote( $proj->login() ) . ", '__pseudo_Cmd'" . ")") or die $DBI::errstr;
    $proj->Do_SQL("set names utf8");
    $proj->parse_form();
    my $cmd_str = $proj->form->{cmd} || 'empty';
    my $act_str = $proj->form->{act} || 'empty';
    my $cmd_act_str = $cmd_str . "." . $act_str;

    $solomon_client->push_single_sensor({
        cluster => "host_info",
        service => "fcgi_server",
        sensor  => "cmds_begin",
        labels  => {
            act    => $act_str,
            cmd    => $cmd_str,
            host   => $host,
        },
        value   => 1,
    });
    $proj->Do_SQL("delete from $fcgi_pid_table where Host = '$host' and Pid=" . int($$) ) or die $DBI::errstr;
    $proj->do_auth();
    $proj->log("do_auth done. Current Login=" . $proj->login() . " Cmd=" . ($proj->form->{cmd} // 'UNDEF') . " " . " url=$url");
    $proj->Do_SQL("replace into $fcgi_pid_table (Host,Pid,Login,Cmd,Act) values ('$host'," . int($$) . "," . $proj->dbh->quote( $proj->login() ) . "," . $proj->dbh->quote( $proj->form->{cmd}) . "," . $proj->dbh->quote( $proj->form->{act} // '' ) . ")") or die $DBI::errstr;

    $proj->make_cmd;

    $proj->Do_SQL("delete from $fcgi_pid_table where Host = '$host' and Pid=" . int($$) ) or die $DBI::errstr;

    my $duration = tv_interval($start_time);
    $solomon_client->push_single_sensor({
        cluster => "host_info",
        service => "fcgi_server",
        sensor  => "cmds_timings",
        labels  => {
            act    => $act_str,
            cmd    => $cmd_str,
            host   => $host,
        },
        value   => $duration,
    });
    $proj->log("FCGI cmd processing done. duration: $duration Cmd=" . ($proj->form->{cmd} // 'UNDEF') . " Login=" . $proj->login() . " url=$url");

    pm_post_dispatch();

    my $mem = int( mem_usage() / (1024 * 1024));
    print_err("Memory: $mem");
    my $max_mem = 2048;
    if ($mem > $max_mem) {
        $proj->log("Too much memory is used ($mem), stop");
        print_err("Too much memory is used ($mem), stop");
        last;
    }
};

print_err("HERE WE EXIT.");

exit(0);

sub ResetPidTable {
    my ($table) = @_;

    my $light_proj = CatalogiaMediaProject->new({
            no_form => 1,
            no_auth => 1,
            log_file => $log_file,
    });

    #$light_proj->Do_SQL("drop table if exists $table") or die $DBI::errstr;

    # TODO исправить в scripts/wlib/Cmds/Interface.pm и scripts/monitors/check-monrun.pl

    if ($light_proj->List_SQL("show tables like '$table'")->[0]) {
        # Если таблица есть, удаляем записи для curr_host
        my $host = get_curr_host();
        $light_proj->Do_SQL("delete from $table where Host = '$host'") or die $DBI::errstr;
    } else {
        # Если таблицы нет, создаем ее
        $light_proj->Do_SQL("
        CREATE TABLE `$table` (
          `Host` char(255) NOT NULL DEFAULT '',
          `Pid` int(11) NOT NULL DEFAULT '0',
          `Login` char(255) NOT NULL DEFAULT '',
          `Cmd` char(255) NOT NULL DEFAULT '',
          `Act` char(255) NOT NULL DEFAULT '',
          `StartTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
           PRIMARY KEY (`Host`, `Pid`)
        ) ENGINE=MyISAM DEFAULT CHARSET=utf8
        ") or die $DBI::errstr;
    }

    return 1;
}

# Форк
sub fork_proc {
    my $pid;
   
    FORK: {
        if (defined($pid = fork)) {
            return $pid;
        }
        elsif ($! =~ /No more process/) {
            sleep 5;
            redo FORK;
        }
        else {
            die "Can't fork: $!";
        };
    };
};

# Переоткрыть стандартные дескрипторы на /dev/null
sub reopen_std {   
    open( STDIN,  "+>/dev/null" ) or die "Could not redirect STDIN to /dev/null";
    open(STDOUT, "+>&STDIN") or die "Can't open STDOUT: $!";
    open(STDERR, "+>&STDIN") or die "Can't open STDERR: $!";
#    open STDOUT, ">>", $Utils::Common::options->{dirs}{'log'} . '/' . basename($0) . '.log' or die("Cannot open STDOUT log");
#    open STDERR, ">>", $Utils::Common::options->{dirs}{'log'} . '/' . basename($0) . '.err' or die("Cannot open STDERR log");
};

sub DoStop {
	my $old_process_pid = GetPid();
	if ( $old_process_pid ) {
                print_err("old process really exists!, pid:$old_process_pid");
		KillProcess( $old_process_pid );	
	} else {
                print_err("NO OLD pid:$old_process_pid");
        }

        # kill fcgi-processes
        sleep(3);
        my $kill_command = 'ps axuf | grep perl-fcgi-pm | grep -v grep | awk {\'print $2\'}';
        my $prev_server_pid = int(`$kill_command`);
        print_err("prev_server_pid: $prev_server_pid");
        if ( $prev_server_pid > 0 ) {
            KillProcess($prev_server_pid);
        }
        sleep(3);

        # kill all bad things wich are lisening to 9093:
        for ( my $att = 0; $att < 5; $att++ ) {
	    my $command = 'netstat -nlp | grep \':9093\' | grep LISTEN | awk {\'print $7\'} | tail -n1';
            my $process_result = `$command`;
            print_err("COMMAND:$command");
            print_err("RESULT:$process_result");
            my $process2kill = 0;
            if ( $process_result =~ /^(\d+)/ ) {
                $process2kill = $1;
            }
            if ( $process2kill > 0 ) {
                print_err("also we want to kill some bad: $process2kill");
                KillProcess($process2kill);         
            }
            sleep(1);
        }
        # /kill all bad things wich are lisening to 9093:
        
	return 1;
}

sub IsProcessExists {
	my ( $pid ) = @_;
	my $ps_command = 'ps axuf | awk {' . "'" . 'print $2' . "'" . '} | grep ^'.$pid.'$';
	return `$ps_command`;
}

sub KillProcess {
	my ( $pid ) = @_;
	print_err("KillProcess ($pid): " . join("\n", (grep {/^\s*$pid\s/} `ps ax --format pid,ppid,user,lstart,args`)) );
	if ( IsProcessExists($pid) ) {
	        print_err("kill $pid ...");
		`kill $pid`;
		my $wait_start = time;
		while ( time - $wait_start < 9 ) { # максимум 2 минуты пока все умрет
			sleep 1;
			if ( !IsProcessExists($pid) ) {
				return 1;
			}
		}
		if ( IsProcessExists($pid) ) {
		        print_err("kill -9 $pid ...");
                        `kill -9 $pid`;
                        sleep 2;
		        if ( IsProcessExists($pid) ) {
   			    print_err("ERROR: cannot kill process:$pid, exiting");
			    exit(1);
                        }
		}
	}
	return 1;
}

sub GetPid {
    	my $pidfile = $Utils::Common::options->{dirs}{'lock'} . '/' . basename($0) . '.pid';
	if ( -f $pidfile ) {
		open (fF,"<$pidfile") or die("cannot open pidfile, $!");
		my $pid = <fF>;
		close fF;
		return int($pid);
	}
	return 0;
}

sub SetPid {
	my ( $pid ) = @_;
    	my $pidfile = $Utils::Common::options->{dirs}{'lock'} . '/' . basename($0) . '.pid';
	open (tF,">$pidfile") or die("cannot open pidfile, $!");
	print tF $pid;
	close tF;
	return 0;
}
