# include "pcontrol.h"

Stack *JobPool_AllJobs;  // virtual pool for user status queries

Stack *JobPool_Pending;
Stack *JobPool_Busy;
Stack *JobPool_Resp;
Stack *JobPool_Done;
Stack *JobPool_Kill;
Stack *JobPool_Exit;
Stack *JobPool_Crash;

void InitJobStacks () {
  JobPool_AllJobs = InitStack ();

  JobPool_Pending = InitStack ();
  JobPool_Busy    = InitStack ();
  JobPool_Resp    = InitStack ();
  JobPool_Done    = InitStack ();
  JobPool_Kill    = InitStack ();
  JobPool_Exit    = InitStack ();
  JobPool_Crash   = InitStack ();
}

void FreeJobStack (Stack *stack) {
  Job *job;
  while ((job = PullStackByLocation (stack, stack[0].Nobject - 1)) != NULL) {
    DelJob (job);
  }
  FreeStack (stack);
}

void FreeJobStacks () {
  FreeJobStack (JobPool_Pending);
  FreeJobStack (JobPool_Busy   );
  FreeJobStack (JobPool_Resp   );
  FreeJobStack (JobPool_Done   );
  FreeJobStack (JobPool_Kill   );
  FreeJobStack (JobPool_Exit   );
  FreeJobStack (JobPool_Crash  );

  // AllJobs is a virtual stack : all jobs are references
  FreeStack (JobPool_AllJobs);
}

char *GetJobStackName (int StackID) {
  switch (StackID) {
    case PCONTROL_JOB_ALLJOBS: return ("ALLJOBS");

    case PCONTROL_JOB_PENDING: return ("PENDING");
    case PCONTROL_JOB_BUSY:    return ("BUSY");
    case PCONTROL_JOB_RESP:    return ("RESP");
    case PCONTROL_JOB_DONE:    return ("DONE");
    case PCONTROL_JOB_KILL:    return ("KILL");
    case PCONTROL_JOB_EXIT:    return ("EXIT");
    case PCONTROL_JOB_CRASH:   return ("CRASH");
  }
  gprint (GP_ERR, "error: unknown host stack : programming error\n");
  pcontrol_exit (53);
  return (NULL);
}

Stack *GetJobStack (int StackID) {
  switch (StackID) {
    case PCONTROL_JOB_ALLJOBS: return (JobPool_AllJobs);

    case PCONTROL_JOB_PENDING: return (JobPool_Pending);
    case PCONTROL_JOB_BUSY:    return (JobPool_Busy);
    case PCONTROL_JOB_RESP:    return (JobPool_Resp);
    case PCONTROL_JOB_DONE:    return (JobPool_Done);
    case PCONTROL_JOB_KILL:    return (JobPool_Kill);
    case PCONTROL_JOB_EXIT:    return (JobPool_Exit);
    case PCONTROL_JOB_CRASH:   return (JobPool_Crash);
  }
  gprint (GP_ERR, "error: unknown job stack : programming error\n");
  pcontrol_exit (54);
  return (NULL);
}

Stack *GetJobStackByName (char *name) {

  if (!strcasecmp (name, "all"))     return (JobPool_AllJobs);

  if (!strcasecmp (name, "pending")) return (JobPool_Pending);
  if (!strcasecmp (name, "busy"))    return (JobPool_Busy);
  if (!strcasecmp (name, "resp"))    return (JobPool_Resp);
  if (!strcasecmp (name, "done"))    return (JobPool_Done);
  if (!strcasecmp (name, "exit"))    return (JobPool_Exit);
  if (!strcasecmp (name, "crash"))   return (JobPool_Crash);
  return (NULL);
}

/* add job to position in stack, use StackID as default state */
int PutJob (Job *job, int StackID, int where) {

  int stat;
  Stack *stack;

  // fprintf (stderr, "move job %s to %s\n", job[0].argv[0], GetJobStackName(StackID));

  stack = GetJobStack (StackID);
  if (stack == NULL) return (FALSE);

  /* by default, these are both the same - to override, use PutJobSetState */
  job[0].state = StackID;
  job[0].stack = StackID;
  stat = PushStack (stack, where, job, job[0].JobID, job[0].argv[0]);
  // XXX need to handle the error conditions, or we drop the host & leak memory
  return (stat);
}
  
/* add job to position in stack.  set state to 'state' */
int PutJobSetState (Job *job, int StackID, int where, int state) {

  int stat;
  Stack *stack;

  stack = GetJobStack (StackID);
  if (stack == NULL) return (FALSE);

  /* alternate state specified by user */
  job[0].state = state;
  job[0].stack = StackID;
  stat = PushStack (stack, where, job, job[0].JobID, job[0].argv[0]);
  // XXX need to handle the error conditions, or we drop the host & leak memory
  return (stat);
}
  
Job *PullJobByID (IDtype JobID, int *StackID) {

  Job *job;

  *StackID = PCONTROL_JOB_PENDING;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_BUSY;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_RESP;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_EXIT;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_CRASH;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_DONE;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  *StackID = PCONTROL_JOB_KILL;
  job = PullJobFromStackByID (*StackID, JobID);
  if (job != NULL) return (job);

  return (NULL);
}

/* remove job from position in stack */
Job *PullJobFromStackByID (int StackID, int ID) {

  Job *job;
  Stack *stack;

  stack = GetJobStack (StackID);
  if (stack == NULL) return (NULL);

  job = PullStackByID (stack, ID);
  return (job);
}

int InitJobOutput (JobOutput *output) {

  output[0].size = 0;
  output[0].requested = FALSE;
  output[0].completed = FALSE;
  InitIOBuffer (&output[0].buffer, 0x1000);

  return TRUE;
}

int ResetJobOutput (JobOutput *output) {

  output[0].size = 0;
  output[0].requested = FALSE;
  output[0].completed = FALSE;
  FlushIOBuffer (&output[0].buffer);

  return TRUE;
}

IDtype AddJob (char *hostname, JobMode mode, int timeout, int argc, char **argv) {

  int JobID;
  Job *job;

  ALLOCATE (job, Job, 1);

  job[0].argc     = argc;
  job[0].argv     = argv;
  job[0].hostname = hostname;
  job[0].realhost = NULL;

  job[0].exit_status = 0;
  job[0].Reset    = FALSE;

  InitJobOutput (&job[0].stdout);
  InitJobOutput (&job[0].stderr);

  job[0].mode     = mode;

  job[0].state = 0;
  job[0].stack = 0;

  job[0].dtime = 0.0;
  job[0].pid = 0;

  job[0].JobID    = NextJobID();
  job[0].host     = NULL;

  JobID = job[0].JobID;

  // Put a copy of all created jobs on the ALLJOBS stack
  // This is a virtual stack: do not free the job from this stack
  PutJob (job, PCONTROL_JOB_ALLJOBS, STACK_BOTTOM);
  PutJob (job, PCONTROL_JOB_PENDING, STACK_BOTTOM);

  // until the job is launched, we use 'start' to time how long the job is waiting on the queue
  gettimeofday (&job[0].start, (void *) NULL);

  if (VerboseMode()) gprint (GP_ERR, "added new job\n");
  return (JobID);
}

void DelJob (Job *job) {

  int i;

  Job *copy;

  copy = PullStackByID (JobPool_AllJobs, job[0].JobID);
  ASSERT (copy == job, "programming error: ALLJOBS entry does not match");

  FREE (job[0].hostname);
  for (i = 0; i < job[0].argc; i++) {
    FREE (job[0].argv[i]);
  }
  FREE (job[0].argv);

  FreeIOBuffer (&job[0].stdout.buffer);
  FreeIOBuffer (&job[0].stderr.buffer);

  FREE (job);
}
