# include "opihi.h"
# include "scheduler.h"

# define MAX_N_JOBS 1000
static char *JobIDList;
static int   JobIDPtr = 0;

static char dot[] = ".";

static Job *jobs;
static int   Njobs;
static int   NJOBS;

static char *JobName = dot;
static int   CurrentJob = 0;

/* provide a mechanism to loop over the list of jobs */
Job *NextJob () {
  
  Job *job;

  if (CurrentJob >= Njobs) {
    CurrentJob = 0;
    return (NULL);
  }

  job = &jobs[CurrentJob];
  CurrentJob ++;
  return (job);
}

void InitJobs () {
  NJOBS = 20;
  Njobs = 0;
  ALLOCATE (jobs, Job, NJOBS);
  JobName = dot;

  ALLOCATE (JobIDList, char, MAX_N_JOBS);
  bzero (JobIDList, MAX_N_JOBS*sizeof(char));
}

/* return next unique ID, recycle every MAX_N_JOBS */
int NextJobID () {

  int Ntry;

  JobIDPtr ++;
  if (JobIDPtr >= MAX_N_JOBS) JobIDPtr = 0;

  Ntry = 0;
  while (JobIDList[JobIDPtr]) {
    Ntry ++;
    JobIDPtr ++;
    if (JobIDPtr >= MAX_N_JOBS) JobIDPtr = 0;
    if (Ntry == MAX_N_JOBS) return (-1);
  }
  JobIDList[JobIDPtr] = TRUE;
  return (JobIDPtr);
}

/* list known jobs */
void ListJobs () {

  int i;

  for (i = 0; i < Njobs; i++) {
    fprintf (stderr, "%d: %-15s %5d %20s (%x)\n", Njobs, jobs[i].task.name, jobs[i].JobID, jobs[i].task.argv[0], jobs[i].task.argv);
  }
  return;
}

/* return job with given name */
Job *FindJob (int JobID) {

  int i;

  /* try for an exact match first */
  for (i = 0; i < Njobs; i++) {
    if (jobs[i].JobID == JobID) {
      return (&jobs[i]);
    }
  }
  return (NULL);
}  

/* make a new job from a task */
Job *CreateJob (Task *task) {
  
  int i;

  jobs[Njobs].JobID = NextJobID ();
  jobs[Njobs].PID = 0;
  jobs[Njobs].task = task[0];

  /* we need our own copy of task[0].argv */
  ALLOCATE (jobs[Njobs].task.argv, char *, MAX (jobs[Njobs].task.argc, 1));
  for (i = 0; i < jobs[Njobs].task.argc; i++) {
    jobs[Njobs].task.argv[i] = strcreate (task[0].argv[i]);
  }
  jobs[Njobs].task.host = strcreate (task[0].host);

  Njobs ++;
  if (Njobs == NJOBS) {
    NJOBS += 20;
    REALLOCATE (jobs, Job, NJOBS);
  }
  return (&jobs[Njobs-1]);
}

void SetCurrentJob (char *name) {
  JobName = name;
}

char *GetCurrentJob () {
  return (JobName);
}

void FreeJob (Job *job) {
  
  int i;

  if (job == NULL) return;

  if ((job[0].JobID >= 0) || (job[0].JobID < MAX_N_JOBS)) {
    JobIDList[job[0].JobID] = FALSE;
  }

  for (i = 0; i < job[0].task.argc; i++) {
    free (job[0].task.argv[i]);
  }
  free (job[0].task.argv);
  free (job[0].task.host);
  return;
}

int DeleteJob (Job *job) {

  int i, Nm;

  Nm = -1;
  for (i = 0; i < Njobs; i++) {
    if (job == &jobs[i]) {
      Nm = i;
      break;
    }
  }
  if (Nm == -1) {
    fprintf (stderr, "programming error: job not found\n");
    return (FALSE);
  }

  FreeJob (&jobs[Nm]);
  for (i = Nm + 1; i < Njobs; i++)
    jobs[i - 1] = jobs[i];
  Njobs --;

  CurrentJob --;
  CurrentJob = MAX (CurrentJob, 0);

  return (TRUE);
}

/* this needs to: 1) distinguish local from controller jobs
   2) fork the local jobs in the background
   3) send the controller jobs to the controller */

int SubmitJob (Job *job) {

  int i, Nchar;
  char *string;

  Nchar = 0;
  for (i = 0; i < job[0].task.argc; i++) {
    Nchar += strlen (job[0].task.argv[i]) + 1;
  }
  ALLOCATE (string, char, Nchar);
  bzero (string, Nchar);

  strcat (string, job[0].task.argv[0]);
  for (i = 1; i < job[0].task.argc; i++) {
    strcat (string, " ");
    strcat (string, job[0].task.argv[i]);
  }
  
  fprintf (stderr, "executing job %d ...%s...\n", job[0].JobID, string);
  free (string);

  /* reset clock for start and poll-test */
  gettimeofday (&job[0].start, (void *) NULL);
  job[0].last = job[0].start;
  
  return (TRUE);
}

int CheckJob (Job *job) {

  float f;
  f = drand48 ();

  if ((0.00 < f) && (f < 0.25)) {
    job[0].state = JOB_BUSY;
    job[0].exit_status = -1;
    return (JOB_BUSY);
  }
  if ((0.25 < f) && (f < 0.50)) {
    job[0].state = JOB_BUSY;
    job[0].exit_status = -1;
    return (JOB_CRASH);
  }
  if ((0.50 < f) && (f < 0.75)) {
    job[0].state = JOB_BUSY;
    job[0].exit_status = 0;
    return (JOB_EXIT);
  }
  if ((0.75 < f) && (f < 1.00)) {
    job[0].state = JOB_BUSY;
    job[0].exit_status = 1;
    return (JOB_EXIT);
  }
}
