package TaskManager::Task::Crontab;

use strict;
use warnings;

use TaskManager::Constants;
use TaskManager::DB;
use TaskManager::System;
use TaskManager::Task::Generic;

our @ISA = qw(TaskManager::Task::Generic);


sub new {
	my $class = shift || die "Bad method call!";
	my $self = $class->SUPER::new(@_);
	$self->{'table'} = 'task_crontab';
	return $self;
}


sub load_config {
	my $self = shift || die "Bad method call!";
#	my $cfg_file = shift;

	$self->SUPER::load_config(@_);

	$CFG->{'NEXT_CRON_CALC_SLEEP'} = 5
		unless defined $CFG->{'NEXT_CRON_CALC_SLEEP'};

	$CFG->{'PREVOTING_CYCLES'} = 5
		unless defined $CFG->{'PREVOTING_CYCLES'};
}


## Returned values: task exec str
## NB: uid:gid will be loaded with task plan
sub allocate_task {
	my $self = shift || die "Bad method call!";
	my $id_task = shift or die "No id task given!";

	TaskManager::DB::do_query("LOCK TABLES task_crontab WRITE");

	my $REZ = TaskManager::DB::select_query("SELECT exec_str, worker_status, id_worker_server FROM task_crontab WHERE id=?", $id_task);

	unless( scalar @$REZ ) {
		TaskManager::DB::do_query("UNLOCK TABLES");
		drop_log("WARNING! No task_crontab record found for id=$id_task!");
		warn "No task_crontab record found for id=$id_task!";
		return undef;
	}

	my $task_data = pop @$REZ;

	if( $task_data->{'worker_status'}==TM_STATUS_LOCK ) {
		TaskManager::DB::do_query("UNLOCK TABLES");
		#drop_log("WARNING! Task_crontab record id=$id_task already locked by server id=$task_data->{id_worker_server}! Skipped..");
		return undef;
	}

	if( $task_data->{'worker_status'}==TM_STATUS_WORK ) {
		TaskManager::DB::do_query("UNLOCK TABLES");
		#drop_log("WARNING! Task_crontab record id=$id_task already in work by server id=$task_data->{id_worker_server}! Skipped..");
		return undef;
	}

	unless( $task_data->{'exec_str'} ) {
		$self->set_task_error($id_task, 0xFF, 'illegal params');
		TaskManager::DB::do_query("UNLOCK TABLES");
		return undef;
	}

	my $task_exec_str = $self->parse_exec_str($task_data->{'exec_str'});

	unless( $self->validate_exec_str($task_exec_str) ) {
		$self->set_task_error($id_task, 0xFF, 'illegal params');
		TaskManager::DB::do_query("UNLOCK TABLES");
		return undef;
	}

	TaskManager::DB::do_query(
		"UPDATE task_crontab SET dt_start=NOW(), worker_status=?, attempt_no=attempt_no+1 WHERE id=?",
		TM_STATUS_LOCK, $id_task
	)
		or make_die($TaskManager::DB::error);

	TaskManager::DB::do_query("UNLOCK TABLES") 
		or make_die($TaskManager::DB::error);

	return [ split(/ +/, $task_exec_str) ];
}


sub startup_check_server {
	my $self = shift || die "Bad method call!";

	$self->SUPER::startup_check_server(@_);

## XXX! This rule should (may) be changed, it's possible to run controllers on queue server
## TBD
	die "Crontab daemon can be run only on loadavg type servers!"
		if $self->{'server_params'}->{'limit_type'} ne TM_SERVER_LIMIT_TYPE_LOADAVG;

	## check for 'stale' tasks
	my $REZ = TaskManager::DB::select_query("SELECT id FROM task_crontab WHERE id_worker_server=? AND worker_status IN(?,?) AND status=?",
		$CFG->{'SERVER_ID'}, TM_STATUS_LOCK, TM_STATUS_WORK, TM_TASK_STATUS_ACTIVE
	);

	drop_log("WARN: ".scalar(@$REZ)." uncompleted tasks for server id=$CFG->{SERVER_ID} found! They 99% can be reset to 'new' status")
		if scalar @$REZ;

	TaskManager::DB::do_query("UPDATE task_crontab SET worker_status=? WHERE id=?", TM_STATUS_NEW, $_->{'id'})
		&& drop_log("Task id=$_->{id} restored to 'new'")
		for(@$REZ);

	$CFG->{'CONTROLLER_SERVER_ID'} = 0;
	$CFG->{'CONTROLLER_SERVER_PID'} = 0;

	return undef;
}


sub load_crontab_plan {

	## XXX! here should be additional checks in SQL to prevent loading already taken tasks!

	## XXX: not here! allocate_task should work with this

#	my $SQL = <<SQL;
#SELECT
#	tc.*,
#	u.name as username,
#	u.groupname,
#	m.mailto
#FROM
#	project_server ps,
#	project_list pl,
#	task_crontab tc,
#	user_list as u,
#	mailto_list as m
#WHERE
#	ps.id_server=? AND
#	pl.id=ps.id_project AND
#	pl.status=? AND
#	tc.id_project=ps.id_project AND
#	tc.status=? AND
#	u.id=tc.id_user AND
#	m.id=tc.id_mailto
#SQL
#
#	my $REZ = TaskManager::DB::select_query($SQL, $CFG->{'SERVER_ID'}, TM_PROJECT_STATUS_ACTIVE, TM_TASK_STATUS_ACTIVE);


	my $SQL = <<SQL;
SELECT
	tc.*,
	u.name as username,
	u.groupname,
	m.mailto
FROM
	project_list pl,
	task_crontab tc,
	user_list as u,
	mailto_list as m
WHERE
	pl.status=? AND
	tc.id_project=pl.id AND
	tc.status=? AND
	u.id=tc.id_user AND
	m.id=tc.id_mailto
SQL

	my $REZ = TaskManager::DB::select_query($SQL, TM_PROJECT_STATUS_ACTIVE, TM_TASK_STATUS_ACTIVE);

	#drop_log("Loaded ".scalar(@$REZ)." crontab tasks.");


	my %prj;
	$prj{$_->{'id_project'}}=1
		foreach( @$REZ );

	my $prj_list = join(',',keys %prj);


	$SQL = <<SQL;
SELECT
	ps.id_project,
	ps.id_server
FROM
	project_server ps,
	server_list sl
WHERE
	ps.id_project IN ($prj_list) AND
	sl.id=ps.id_server AND
	sl.status=?
SQL

	my $REZ_SRV = TaskManager::DB::select_query($SQL, TM_SERVER_STATUS_ACTIVE);

	#drop_log("Loaded ".scalar(@$REZ_SRV)." servers list.");


	my $PRJ_SERVER_LIST;
	my $SRV_LIST;
	foreach (@$REZ_SRV) {
		$PRJ_SERVER_LIST->{$_->{'id_project'}}->{$_->{'id_server'}} = 1;
		$SRV_LIST->{$_->{'id_server'}} = 1;
	}


	return {'task'=>$REZ, 'prj_srv'=>$PRJ_SERVER_LIST, 'srv'=>[keys %$SRV_LIST]};
}


sub load_active_servers_details {

	my $SQL = "SELECT id, load_avg_1, max_load FROM server_list WHERE status=? AND limit_type=? AND UNIX_TIMESTAMP(last_ping_dt)+? > UNIX_TIMESTAMP(NOW())";

	my $rez = TaskManager::DB::select_query($SQL, TM_SERVER_STATUS_ACTIVE, TM_SERVER_LIMIT_TYPE_LOADAVG, $CFG->{'IS_ALIVE_PING_TIMEOUT'});

	return $rez && $rez->[0] ? $rez : undef;
}


sub find_controller {

	my $SQL = "SELECT id FROM server_list WHERE status=? AND limit_type=? AND is_controller=1 AND UNIX_TIMESTAMP(last_ping_dt)+? > UNIX_TIMESTAMP(NOW())";

	my $rez = TaskManager::DB::select_query($SQL, TM_SERVER_STATUS_ACTIVE, TM_SERVER_LIMIT_TYPE_LOADAVG, $CFG->{'IS_ALIVE_PING_TIMEOUT'});

	$CFG->{'CONTROLLER_SERVER_ID'} = $rez->[0] ? $rez->[0]->{'id'} : 0;

	return $CFG->{'CONTROLLER_SERVER_ID'};
}


sub set_controller {
	my $self = shift || die "Bad method call!";
	my $force_controller = shift;

	TaskManager::DB::do_query("LOCK TABLES server_list WRITE");

	if( $self->find_controller() ) {
		TaskManager::DB::do_query("UNLOCK TABLES");
		return undef;
	}

	my $SQL = "SELECT id FROM server_list WHERE status=? AND limit_type=? AND UNIX_TIMESTAMP(last_ping_dt)+? > UNIX_TIMESTAMP(NOW()) ORDER BY weight DESC LIMIT 0,1";

	my $rez = TaskManager::DB::select_query($SQL, TM_SERVER_STATUS_ACTIVE, TM_SERVER_LIMIT_TYPE_LOADAVG, $CFG->{'IS_ALIVE_PING_TIMEOUT'});

	make_die("Collision detected! No one alive daemon to become the controller!!!")
		unless scalar @$rez;

	$rez->[0]->{'id'} = $CFG->{'SERVER_ID'}
		if $force_controller;

	## only the same server can set itself as controller
	if( $rez->[0]->{'id'}==$CFG->{'SERVER_ID'} ) {
		TaskManager::DB::do_query("UPDATE server_list SET is_controller=0 WHERE limit_type=?", TM_SERVER_LIMIT_TYPE_LOADAVG);

		## also update last_ping_dt
		TaskManager::DB::do_query("UPDATE server_list SET is_controller=1, last_ping_dt=NOW() WHERE id=?", $rez->[0]->{'id'});
		$CFG->{'CONTROLLER_SERVER_ID'} = $rez->[0]->{'id'};
		$CFG->{'CONTROLLER_SERVER_PID'} = $$;
	}

	TaskManager::DB::do_query("UNLOCK TABLES");

	return undef;
}


sub stop_controller {

	TaskManager::DB::do_query("UPDATE server_list SET is_controller=0 WHERE id=?", $CFG->{'SERVER_ID'})
		if $CFG->{'CONTROLLER_SERVER_ID'}==$CFG->{'SERVER_ID'};

	$CFG->{'CONTROLLER_SERVER_ID'} = 0;

	return undef;
}


sub check_reload_crontab_plan {

	my $rez = TaskManager::DB::select_query("SELECT sig_reload FROM server_list WHERE id=?", $CFG->{'SERVER_ID'});

	my $sig_reload = $rez->[0] ? $rez->[0]->{'sig_reload'} : 0;

	TaskManager::DB::do_query("UPDATE server_list SET sig_reload=0 WHERE id=?", $CFG->{'SERVER_ID'})
		if $sig_reload;

	return $sig_reload;
}


sub create_job {
	my $self = shift || die "Bad method call!";
	my %args = (@_);

	TaskManager::DB::do_query("INSERT IGNORE INTO task_crontab_job (id_task, id_server, run_at, create_dt) VALUES (?,?,?,NOW())",
		$args{'task_id'}, $args{'server_id'}, $args{'run_at'});

	return undef;
}


sub start_job {
	my $self = shift || die "Bad method call!";
	my $id_job = shift || die "No id job given!";
	#my %args = (@_);

	TaskManager::DB::do_query("UPDATE task_crontab_job SET start_dt=NOW() WHERE id=?", $id_job);

	#TaskManager::DB::do_query("UPDATE task_crontab_job SET start_dt=NOW() WHERE id_server=? AND run_at=? AND id_task=?",
	#	$args{'server_id'}, $args{'run_at'}, $args{'task_id'});

	return undef;
}


sub finish_job {
	my $self = shift || die "Bad method call!";
	my $id_job = shift || die "No id job given!";
	#my %args = (@_);

	TaskManager::DB::do_query("UPDATE task_crontab_job SET finish_dt=NOW() WHERE id=?", $id_job);

#	TaskManager::DB::do_query("UPDATE task_crontab_job SET finish_dt=NOW() WHERE id_server=? AND run_at=? AND id_task=?",
#		$args{'server_id'}, $args{'run_at'}, $args{'task_id'});

	return undef;
}


sub update_avg_time {
	my $self = shift || die "Bad method call!";
	my $id_task = shift || die "No id task given!";
	my $time_elapsed = shift;
	#my %args = (@_);

	die "No time_elapsed given!"
		unless defined $time_elapsed;

	TaskManager::DB::do_query("UPDATE task_crontab SET avg_time=ROUND(((avg_time*avg_time_cnt + ?)/(avg_time_cnt+1))), avg_time_cnt=avg_time_cnt+1, max_success_time=IF(? > max_success_time, ?, max_success_time) WHERE id=?", $time_elapsed, $time_elapsed, $time_elapsed, $id_task);

#	TaskManager::DB::do_query("UPDATE task_crontab_job SET finish_dt=NOW() WHERE id_server=? AND run_at=? AND id_task=?",
#		$args{'server_id'}, $args{'run_at'}, $args{'task_id'});

	return undef;	
}


sub load_current_job_list {

	my ($min, $hour, $day, $month, $year) = (localtime(time()))[1..5];

	my $timemark = sprintf('%04d-%02d-%02d %02d:%02d:00', $year+1900, $month+1, $day, $hour, $min );

	my $SQL = "SELECT id,id_task FROM task_crontab_job WHERE id_server=? AND run_at=? AND start_dt=?";

	my $rez = TaskManager::DB::select_query($SQL, $CFG->{'SERVER_ID'}, $timemark, '0000-00-00 00:00:00');

	return $rez;
}


1;
