#!/usr/bin/perl
use strict;
my($app_svnid) = '$HeadURL$ $LastChangedRevision$';
#  Allow bare words, so &ade_err_error() calls look nicer.
no strict 'subs';
use lib substr `ade-config ade_include_prefix`,0,-1;
use ADE;
use lib substr `js-config js_include_prefix`,0,-1;
#  see perlipc(1) for explanation of why this is needed
use POSIX ":sys_wait_h";
#use Data::Dumper;
no warnings 'experimental::smartmatch';

my(%jsd_defined_errors) = (
    jsd_err_misc         => { fmt => "%s" },
);

#  Options
my(%opt_resources);
my($opt_schedule_flag);
my($dbinit_file) = sprintf("%s/%s", (substr `js-config js_include_prefix`,0,-1), "jsd.sql");

#  Other globals
my(%exit_code_of_recently_reaped_pids);

sub jsd
{
    my($errstack_ref) = @_;
    my($rc, $db_file, $pid, $dbh);
    my($sql_statement);
    my($resource_id, $i);
    my($quit_flag, $command);
    my(@possible_job_ids, @command_ids);
    my($commands_count, $possible_jobs_count);
    my($lock_file);

    #  Set ADE options
    &ade_err_registerdefderrs(\%jsd_defined_errors);
    &ade_err_resetstack($errstack_ref, dumpall=>0);

    #  Defaults for options 
    #  (identical line in jsd, jss, jsq).
    $opt_schedule_flag = 1;

    #  Register options
    if (($rc=&ade_opt_register($errstack_ref, "r:s", "resources:s,suspend", "main::jsd_opt_handler_%s")) != $ade_err_ok) {
        return($rc);
    }

    #  Register handler functions
    if (($rc=&ade_msg_register($errstack_ref, \&jsd_usage, \&jsd_version, \&jsd_listpaths)) != $ade_err_ok) {
        return($rc);
    } 

    #  Process options
    if (($rc=&ade_opt_process($errstack_ref)) != $ade_err_ok) {
        return($rc);
    } 

    #  Process arguments
    &ade_msg_usage($errstack_ref, \&app_usage, 1) if ($#ARGV+1 != 0);

    #  Sanity checks and derivations
    $lock_file = $ENV{JSD_LOCKFILE} ? $ENV{JSD_LOCKFILE} : "$ade_tmp_dir/jsd-$ENV{'LOGNAME'}.lock";
    if (scalar(keys(%opt_resources)) == 0) {
        &ade_err_error($errstack_ref, jsd_err_misc, "no resources specified");
        return($ade_err_fail);
    }
    foreach $resource_id (keys(%opt_resources)) {
       if ($resource_id !~ /^[a-z][a-z0-9_]*$/) {
           &ade_err_error($errstack_ref, jsd_err_misc, "$resource_id: invalid resource name (hint: it should be word-like)");
           return($ade_err_fail);
       }
       &ade_err_debug($errstack_ref, 10, "jsd: resource_id=$resource_id, quantity_total=$opt_resources{$resource_id}{quantity_total}, affinous=$opt_resources{$resource_id}{affinous}");
       if (!defined($opt_resources{$resource_id}{quantity_total}) or $opt_resources{$resource_id}{quantity_total} !~ /^[1-9][0-9]*$/) {
           &ade_err_error($errstack_ref, jsd_err_misc, (defined($opt_resources{$resource_id}{quantity_total}) ? $opt_resources{$resource_id}{quantity_total} : "") . ": invalid quantity (hint: should be an integer)");
           return($ade_err_fail);
       }
       if (!defined($opt_resources{$resource_id}{affinous})) {
           $opt_resources{$resource_id}{affinous} = 'false';
       } elsif ($opt_resources{$resource_id}{affinous} ~~ ['t', 'true', '1', 'yes', 'y']) {
           $opt_resources{$resource_id}{affinous} = 'true';
       } elsif ($opt_resources{$resource_id}{affinous} ~~ ['f', 'false', '0', 'no', 'n']) {
           $opt_resources{$resource_id}{affinous} = 'false';
       } else {
           &ade_err_error($errstack_ref, jsd_err_misc, "$opt_resources{$resource_id}{affinous}: invalid affinousness (hint: should be empty or a logical)");
           return($ade_err_fail);
       }
    }
    #  Predicable path to database. Tests should not normally need to access the database.
    $db_file = "/var/tmp/jsd-$$.sqlite";

    #  Lock
    if (($rc=&ade_lck_lock($errstack_ref, $lock_file)) != $ade_err_ok) {
        return($rc);
    }

    #  Define and initialise database
    if (-f $db_file) {
        &ade_err_warning($errstack_ref, jsd_err_misc, "$db_file: removing old database ...");
        unlink($db_file);
    }
    &ade_tmp_registerfile($errstack_ref, $db_file);
    &ade_err_debug($errstack_ref, 10, "jsd: db_file=$db_file");
    #  Pass one arg to system() in order that it uses the shell, which is needed to use redirection, 
    #  which is needed to get sqlite not to output stuff about loading ~/.sqliterc.
    if (system("sqlite3 $db_file \".read $dbinit_file\" < /dev/null") != 0) {
        &ade_err_error($errstack_ref, jsd_err_misc, "failed to initialise database");
        return($ade_err_fail);
    }
    #  Now connect to to the database for real work.
    if (!defined($dbh = DBI->connect("dbi:SQLite:dbname=$db_file","",""))) {
        &ade_err_error($errstack_ref, jsd_err_misc, $DBI::errstr);
        return($ade_err_fail);
    }
    $dbh->do("PRAGMA foreign_keys = ON");
    $dbh->sqlite_busy_timeout(10000);

    #  Populate resources table
    &ade_err_debug($errstack_ref, 10, "jsd: populating resources table ...");
    foreach $resource_id (keys %opt_resources) {
        $sql_statement = "INSERT INTO resources (resource_id, quantity_total, affinous) VALUES ('$resource_id', $opt_resources{$resource_id}{quantity_total}, '$opt_resources{$resource_id}{affinous}');";
        if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
    }
    undef %opt_resources;			#  let's be clear; this is no longer needed.
    #  Expand resources into resource pools. We assign arbitrary priorities to the pools. This
    #  will be used to help select which of the a resource pool sets that satisfies a job's
    #  requirements will be preferred.
    #
    #  Note that we use i+1 instead of i as the resource_pool_priority. If the pools have
    #  priority:
    #
    #      pool#0: 0
    #      pool#1: 1
    #
    #  then pool *set* #3 (consisting of pool#0 *and* pool#1) has the same priority as pool set #2.
    #  More specifically, if a job wants only one CPU, then pool #5 - *which consists of two CPUs*
    #  is equally attractive to pool #4, which consists of one CPU. Which of these sqlite chooses
    #  to show is not certain. For this reason we make the priority i+1.
    #  
    $sql_statement = "INSERT INTO resource_pools (resource_id, resource_pool_id, resource_pool_priority, quantity_total)
                          SELECT resource_id, i AS resource_pool_id, i+1 AS resource_pool_priority, 1 AS quantity_total
                              FROM resources, i1000
                              WHERE affinous = 'true' and i < quantity_total
                          UNION SELECT resource_id, 0 AS resource_pool_id, 0 as resource_pool_priority, quantity_total
                              FROM resources
                              WHERE affinous = 'false';";
    if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    #  Main loop
    $SIG{CHLD} = \&sighandler_chld;		#  help reap children 
    $SIG{USR1} = \&sighandler_usr1;		#  handler does nothing but allows sleep to be interrupted
    $quit_flag = 0;
    while (1) {
        &ade_err_debug($errstack_ref, 10, "jsd: top of loop");

        #  Process PIDs whose recent death caused the sleep to be interrupted. (This
        #  cannot be done from inside the signal handler because the
        #  database may be locked when the signal handler tries to do this.)
        &ade_err_debug($errstack_ref, 10, "jsd: processing recently reaped pids ...");
        if (($rc=&process_recently_reaped_pids($errstack_ref, $dbh)) != $ade_err_ok) {
            return($rc);
        }

        #  Process incoming commands 
        &ade_err_debug($errstack_ref, 10, "jsd: checking for incoming commands ...");
        if (($rc=&get_command_ids($errstack_ref, $dbh, \@command_ids)) != $ade_err_ok) {
            return($rc);
        }
        $commands_count = $#command_ids+1;
        &ade_err_debug($errstack_ref, 10, "jsd: $commands_count commands pending");
        if ($commands_count > 0) {
            &ade_err_debug($errstack_ref, 10, "jsd: processing command $command_ids[0] ...");
            if (($rc=&process_command_id($errstack_ref, $dbh, $command_ids[0], \$quit_flag, \$opt_schedule_flag)) != $ade_err_ok) {
                return($rc);
            }
        }
        #  Quit if instructed to do so.
        if ($quit_flag) {
            last;
        }

        #   If possible start a job 
        if ($opt_schedule_flag) {
            &ade_err_debug($errstack_ref, 10, "jsd: checking for runnable jobs ...");
            if (($rc=&get_job_ids($errstack_ref, $dbh, "possible", \@possible_job_ids)) != $ade_err_ok) {
                return($rc);
            }
            $possible_jobs_count = $#possible_job_ids+1;
            &ade_err_debug($errstack_ref, 10, "jsd: $possible_jobs_count jobs runnable ...");
            if ($possible_jobs_count > 0) {
                &ade_err_debug($errstack_ref, 10, "jsd: starting job $possible_job_ids[0] ...");
                if (($rc=&start_job_id($errstack_ref, $dbh, $possible_job_ids[0])) != $ade_err_ok) {
                    return($rc);
                }
            }
        } else {
            &ade_err_debug($errstack_ref, 10, "jsd: scheduler is disabled");
            $possible_jobs_count = 0;
        }

        #  Sleep (unless we may be able to run another job or commands are pending.
        if ($possible_jobs_count == 0 && $commands_count == 0) {
            &ade_err_debug($errstack_ref, 10, "jsd: going to sleep ...");
            sleep 10000000;
        }
    }
    $SIG{CHLD} = 'DEFAULT';

    #  Tidy up
    $dbh->disconnect;
    unlink $db_file;
    &ade_tmp_deregisterfile($errstack_ref, $db_file);
    return($ade_err_ok);
}

sub jsd_opt_handler_r
{
    my($errstack_ref, $resource_specs_string) = @_;
    my($resource_spec, $resource_id, $quantity_total, $affinous);

    foreach $resource_spec (split(/,/, $resource_specs_string)) {
        ($resource_id, $quantity_total, $affinous) = split(/:/, $resource_spec);
        $opt_resources{$resource_id}{quantity_total} = $quantity_total;
        $opt_resources{$resource_id}{affinous} = $affinous;
    }

    return $ade_err_ok;
} 

sub jsd_opt_handler_resources
{
    return(&jsd_opt_handler_r(@_));
}

sub jsd_opt_handler_suspend
{
    my($errstack_ref) = @_;

    $opt_schedule_flag = 0;

    return $ade_err_ok;
}

sub jsd_version
{
    my($errstack_ref, $version_text_ref) = @_;

    return(&ade_smf_extractversionfromsvnstring($errstack_ref, $app_svnid, $version_text_ref));
}

sub jsd_listpaths
{
    my($errstack_ref, $pathlist_text_ref) = @_;
    my($rc);

    #${$pathlist_text_ref} = "";

    return($ade_err_ok);
}

sub jsd_usage
{
    my($errstack_ref, $usage_text_ref, $passno) = @_;

    if ($passno == 1) {
        ${$usage_text_ref} = "\n";
    } elsif ($passno == 2) {
        ${$usage_text_ref} = "                     --suspend               start with scheduler suspended\n" .
                             "         -r ...    | --resources=...         set system resources\n";
    }

    return($ade_err_ok);
}

sub process_recently_reaped_pids
{
    my($errstack_ref, $dbh) = @_;
    my($pid, $rc, $job_id, $sql_statement);

    foreach $pid (keys %exit_code_of_recently_reaped_pids) {
        #  Translate exited PID to job number.
        if (($rc=get_job_id_by_pid($errstack_ref, $dbh, $pid, \$job_id)) != $ade_err_ok) {
            return($rc);
        }

        $sql_statement = "BEGIN EXCLUSIVE TRANSACTION;";
        if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }

        # Release the running demands (i.e. delete all entries related to this job from the running_job_resource_pools table).  
        &ade_err_debug($errstack_ref, 10, "process_recently_reaped_pids: $job_id: deleting running resources allocations ...");
        $sql_statement = "delete from running_job_resource_pools where job_id = $job_id;";
        if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
    
        #  Finally change the state of the job.
        &ade_err_debug($errstack_ref, 10, "process_recently_reaped_pids: $job_id: changing job state to completed ...");
        if (($rc=&set_job_state_pid_exit_code_by_job_id($errstack_ref, $dbh, $job_id, "completed", 0, $exit_code_of_recently_reaped_pids{$pid})) != $ade_err_ok) {
            return($rc);
        }
    
        #  And if we managed to do all of that then we can commit the transaction.
        &ade_err_debug($errstack_ref, 10, "process_recently_reaped_pids: $job_id: committing transaction ...");
        $sql_statement = "END TRANSACTION;";
        if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
    
        &ade_err_debug($errstack_ref, 10, "process_recently_reaped_pids: $job_id: marking job as completed ...");
        delete $exit_code_of_recently_reaped_pids{$pid};
    }

    return($ade_err_ok);
}

sub start_job_id
{
    my($errstack_ref, $dbh, $job_id) = @_;
    my($pid, $command, $rc, $i, $current_resource_value, $demanded_resource_value, @select_results, $resource);
    my($sql_statement, @wanted_resources, @available_resources);
    my($resource_id, $quantity_wanted, $affinous, $tuple_ref);
    my($resource_id2, $resource_pool_id, $quantity_available, $tuple_ref2, $quantity_allocated_so_far, $quantity_taking);
    my($taking_resource, @taking_resources, @new_envs, $new_env_ref);

    #  Get the job's command
    $sql_statement = "SELECT command FROM jobs WHERE job_id == '$job_id';";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    $command = ${$select_results[0]}[0];
    &ade_err_debug($errstack_ref, 10, "start_job_id: $job_id: command is \"$command\"");

    #  Determine which resources we'll use
    $sql_statement = "select pending_job_resources.resource_id, pending_job_resources.quantity_wanted, resources.affinous from pending_job_resources, resources where pending_job_resources.resource_id=resources.resource_id and pending_job_resources.job_id = $job_id";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@wanted_resources, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    $sql_statement = "select resource_id, resource_pool_id, quantity_available from pending_job_preferred_resource_pool_quantities_available where job_id = $job_id";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@available_resources, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    @taking_resources = ();
    @new_envs = ();
    foreach $tuple_ref (@wanted_resources) {
        $quantity_allocated_so_far = 0;
        ($resource_id, $quantity_wanted, $affinous) = @{$tuple_ref};
        &ade_err_debug($errstack_ref, 10, "start_job_id: resource_id=$resource_id, quantity_wanted=$quantity_wanted, affinous=$affinous");
        foreach $tuple_ref2 (@available_resources) {
            ($resource_id2, $resource_pool_id, $quantity_available) = @{$tuple_ref2};
            next if ($resource_id2 ne $resource_id);
            &ade_err_debug($errstack_ref, 10, "start_job_id: resource_id2=$resource_id2, resource_pool_id=$resource_pool_id, quantity_available=$quantity_available");
            $quantity_taking = ($quantity_available<$quantity_wanted-$quantity_allocated_so_far)?$quantity_available:$quantity_wanted-$quantity_allocated_so_far;
            &ade_err_debug($errstack_ref, 10, "start_job_id: would take $quantity_taking");
            push @taking_resources, { resource_id=>$resource_id, resource_pool_id=>$resource_pool_id, quantity_taking=>$quantity_taking };
            $quantity_allocated_so_far += $quantity_taking;
        }
        if ($affinous eq 'true') {
            #  Since this code is executed inside the outer loop, the first time @taking_resources will contain resources of only 1 type, but on subsequent
            #  loops it will contain also others.
            push @new_envs, { var=>sprintf("JS_%s_AFFINITY", uc($resource_id)), val=>join(',', map { ($_->{resource_id} eq $resource_id) ? $_->{resource_pool_id} : () } @taking_resources) };
        } else {
            push @new_envs, { var=>sprintf("JS_%s_LIMIT", uc($resource_id)), val=>$quantity_wanted };
        }
    }
    push@new_envs, { var=>'JS_JOB_ID', val=>$job_id };

    #  Start the job
    &ade_err_debug($errstack_ref, 10, "start_job_id: $job_id: forking ...");
    if (($pid=fork) < 0) {
        &ade_err_error($errstack_ref, jsd_err_misc, "fork: failed");
        return($ade_err_fail);
    } elsif ($pid == 0) {
        &ade_err_debug($errstack_ref, 10, "start_job_id: $job_id: child setting up environment and execing command ...");
        foreach $new_env_ref (@new_envs) {
            $ENV{$new_env_ref->{var}} = $new_env_ref->{val};
        }
        exec $command;
        &ade_err_error($errstack_ref, jsd_err_misc, "failed to exec '$command'");
        return($ade_err_fail);
    }

    #  Take the resources
    foreach $taking_resource (@taking_resources) {
        $sql_statement = "insert into running_job_resource_pools values ($job_id, '$taking_resource->{resource_id}', $taking_resource->{resource_pool_id}, $taking_resource->{quantity_taking});";
        if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
    }
    # Release the pending demands (i.e. delete all entries related to this job from the pending_job_resources table).  
    $sql_statement = "delete from pending_job_resources where job_id = $job_id;";
    if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    #  Finally change the state of the job.
    if (($rc=&set_job_state_pid_exit_code_by_job_id($errstack_ref, $dbh, $job_id, "running", $pid, 0)) != $ade_err_ok) {
        return($rc);
    }

    return($ade_err_ok);
}
        
sub sighandler_usr1
{
    my($sig) = @_;
    my($pid, @errstack_local, $rc, $job_id);

    @errstack_local = ();

    &ade_err_debug(\@errstack_local, 10, "sighandler_usr1: sof (presumably jss woke us or jsc put something in command table and then woke us)");
}

sub sighandler_chld
{
    my($sig) = @_;
    my($pid, @errstack_local, $rc, $job_id);

    @errstack_local = ();

    #  Reap and not exit codes. The main loop will update the database table with this info.
    while (($pid = waitpid(-1,WNOHANG)) > 0) {
        $exit_code_of_recently_reaped_pids{$pid} = $?;
    }
}

sub set_job_state_pid_exit_code_by_job_id
{
    my($errstack_ref, $dbh, $job_id, $job_state, $pid, $exit_code) = @_;
    my($rc, $sql_statement);

    &ade_err_debug($errstack_ref, 10, "set_job_state_pid_exit_code_by_job_id: sof (job_id=$job_id, job_state=$job_state, pid=$pid)");
    $sql_statement = "UPDATE jobs SET job_state='$job_state', pid=$pid, exit_code=$exit_code WHERE job_id == '$job_id';";
    if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    
    return($ade_err_ok);
}

sub get_job_id_by_pid
{
    my($errstack_ref, $dbh, $pid, $job_id_ref) = @_;
    my($rc, @select_results, $sql_statement);

    $sql_statement = "SELECT job_id FROM jobs WHERE pid == '$pid';";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    #  Extract the first column out of each row, assemble an array out of that
    #  and write it where to caller told us to write it.
    ${$job_id_ref} = ${$select_results[0]}[0];

    return($ade_err_ok);
}

sub get_current_resource_value_by_name
{
    my($errstack_ref, $dbh, $resource_id, $resource_value_ref) = @_;
    my(@select_results, $rc, $sql_statement);

    $sql_statement = "SELECT current FROM resources WHERE resource_id == '$resource_id';";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    ${$resource_value_ref} = ${$select_results[0]}[0];
    return($ade_err_ok);
}

sub get_demanded_resource_value_by_job_id
{
    my($errstack_ref, $dbh, $resource_id, $job_id, $demanded_resource_value_ref) = @_;
    my(@select_results, $rc, $sql_statement);

    &ade_err_debug($errstack_ref, 10, "get_demanded_resource_value_by_job_id: sof (resource_id=$resource_id, job_id=$job_id)");

    $sql_statement = "SELECT $resource_id FROM jobs WHERE job_id == '$job_id';";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    ${$demanded_resource_value_ref} = ${$select_results[0]}[0];
    return($ade_err_ok);
}

sub dump_tables
{
    my($errstack_ref, $level, $dbh) = @_;
    my($table, $sql_command, $sth, @row, $select_result, @select_results, $rc, $sql_statement);

    foreach $table (qw(resources jobs)) {
        &ade_err_debug($errstack_ref, $level, "dump_tables: name=$table");
        undef @select_results;
        $sql_statement = "SELECT * FROM $table;";
        if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
        foreach $select_result (@select_results) {
           &ade_err_debug($errstack_ref, $level, sprintf("dump_tables: " . scalar("%-10s" x ($#{$select_result}+1)), @{$select_result}));
        }
    }
}

sub get_job_ids
{
    my($errstack_ref, $dbh, $job_state, $job_ids_arrayref) = @_;
    my($rc, @select_results, $sql_statement);

    if ($job_state eq "pending" || $job_state eq "running" || $job_state eq "completed") {
        $sql_statement = "SELECT job_id FROM jobs WHERE job_state == '$job_state';";
        if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
        #  Extract the first column out of each row, assemble an array out of that
        #  and write it where to caller told us to write it.
        @{$job_ids_arrayref} = map { ${$_}[0] } @select_results;

    } elsif ($job_state eq "possible") {
        #  find the IDs of jobs whose count of *met* resource requirements are *equal*
        #  to the number of resource types on the system. (E.g. a job with *unmet* RAM
        #  requirement and *met* cores requirement is not eligble to run ('cos 1!=2).

        #sqlite> select * from next_runnable_job;
        #job_id    
        #----------
        #1         

        $sql_statement = "SELECT job_id FROM runnable_jobs;";
        if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
            return($rc);
        }
        #  Extract the first column out of each row, assemble an array out of that
        #  and write it where to caller told us to write it.
        @{$job_ids_arrayref} = map { ${$_}[0] } @select_results;

    } else {
       &ade_err_internal($errstack_ref, "get_job_ids: $job_state: invalid job_state");
    }
 
    return($ade_err_ok);
}

sub get_command_ids
{
    my($errstack_ref, $dbh, $command_ids_arrayref) = @_;
    my($rc, @select_results, $sql_statement);

    #  Get next command
    $sql_statement = "SELECT command_id FROM commands ORDER BY command_id;";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    #  Extract the first column out of each row, assemble an array out of that
    #  and write it where to caller told us to write it.
    @{$command_ids_arrayref} = map { ${$_}[0] } @select_results;

#    ${$command_ref}  = ${$select_results[0]}[1];
#    &ade_err_debug($errstack_ref, 10, "get_command_ids: command_id=$command_id, deref'd command_ref=${$command_ref}");
#
#    #  Delete that command.
#    $sql_statement = "DELETE FROM commands WHERE command_id = $command_id;";
#    if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
#        return($rc);
#    }
#
#    #  Are there still commands pending? Use 'LIMIT 1' to cast 0->N into 0->1. Save doing it in perl.
#    $sql_statement = "SELECT count(*) FROM commands LIMIT 1;";
#    if (($rc=&ade_sql_select_count($errstack_ref, $dbh, $cmd_pending_flag_ref, $sql_statement)) != $ade_err_ok) {
#        return($rc);
#    }
#    &ade_err_debug($errstack_ref, 10, "get_command_ids: deref'd cmd_pending_flag_ref=${$cmd_pending_flag_ref}");

    return($ade_err_ok);
}

sub process_command_id
{
    my($errstack_ref, $dbh, $command_id, $quit_flag_ref, $schedule_flag_ref) = @_;
    my($sql_statement, $rc, $command, @select_results);

    #  Get the command
    $sql_statement = "SELECT command FROM commands WHERE command_id = $command_id;";
    if (($rc=&ade_sql_select($errstack_ref, $dbh, \@select_results, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }
    $command = ${$select_results[0]}[0];
    &ade_err_debug($errstack_ref, 10, "process_command_id: $command_id: command is \"$command\"");

    #  Delete that command from the queue.
    $sql_statement = "DELETE FROM commands WHERE command_id = $command_id;";
    if (($rc=&ade_sql_execute($errstack_ref, $dbh, $sql_statement)) != $ade_err_ok) {
        return($rc);
    }

    if ($command eq 'quit') {
        ${$quit_flag_ref} = 1;
    } elsif ($command eq 'suspend') {
        ${$schedule_flag_ref} = 0;
    } elsif ($command eq 'resume') {
        ${$schedule_flag_ref} = 1;
    } else {
        &ade_err_warning($errstack_ref, jsd_err_misc, "$command: invalid command; ignoring ...");
    }
 
    return($ade_err_ok);
}

&ade_gep_main(\&jsd);
